mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-17 18:22:19 +00:00
Compare commits
177 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 196bcf39cf | |||
| afec1c3a5b | |||
| 6025339b46 | |||
| 40c6cc59d3 | |||
| 514e568e04 | |||
| 2f34b0a5ed | |||
| 708c076885 | |||
| f0e0b918af | |||
| e45f5e1122 | |||
| 932015668b | |||
| 4011bc3fe6 | |||
| 1dcccdc434 | |||
| 3380ed9360 | |||
| ed9c14e63d | |||
| 4762793adc | |||
| 7df3946189 | |||
| d5fb75fe43 | |||
| c0b32a5584 | |||
| e2b1276d7b | |||
| f6aea13fae | |||
| 646b113c55 | |||
| 58074f499f | |||
| 1483ffd7ff | |||
| cdaef851a0 | |||
| 04068d7117 | |||
| 41e8ddea8c | |||
| cc49791997 | |||
| ba4a8050ba | |||
| 7c5067a59a | |||
| 69eb920173 | |||
| 07d761941a | |||
| ca52585759 | |||
| decc9d09f8 | |||
| 466a18447c | |||
| 27902ee107 | |||
| 8a8b806362 | |||
| 40bb181c78 | |||
| 8099847e29 | |||
| 6e8edd95ec | |||
| 5112c9598b | |||
| 749158c086 | |||
| b3d4024f21 | |||
| c283b66c1d | |||
| 3209843318 | |||
| d35e5cc0f7 | |||
| e5eeb592a2 | |||
| 3d38f4d17a | |||
| 2d91647e0b | |||
| cac0f30673 | |||
| c5ecba3389 | |||
| e0397dff47 | |||
| e08fb2e972 | |||
| c02ae82003 | |||
| e6fc301b0d | |||
| a385ed57fb | |||
| 09dafe4b1d | |||
| ba8505c983 | |||
| fba359d389 | |||
| c56763b68f | |||
| 03c2943545 | |||
| 98039b9048 | |||
| 5065d3b068 | |||
| 6828f7e262 | |||
| a685f3ffbd | |||
| e84156d2e9 | |||
| 0ce1c31e1c | |||
| f7e14bb60c | |||
| 7b907df816 | |||
| 7f5be2829c | |||
| fa5afae783 | |||
| b17507d0fa | |||
| 5ed43a3190 | |||
| d2a5144a23 | |||
| fb2031d7ca | |||
| 4a42571925 | |||
| 38413625c2 | |||
| 2d544b7a98 | |||
| b1e5e1840e | |||
| e6f9b4a125 | |||
| 5260b2b430 | |||
| 0213bc7aef | |||
| ca8b0899d0 | |||
| a1f5c0ab9b | |||
| b0e3f82d27 | |||
| 327ab4766b | |||
| 3aee8a49c1 | |||
| 57de0b7351 | |||
| 4df1e310a3 | |||
| 802a77238a | |||
| 85a7e18fae | |||
| 26b1fd3487 | |||
| ae7a363012 | |||
| 473362af45 | |||
| cde0d09f0a | |||
| 94f093826b | |||
| 84f231ea08 | |||
| 3d9bb14713 | |||
| 6c6cdcf763 | |||
| 00c8690e61 | |||
| a0d492cd2d | |||
| ba5ccc1021 | |||
| a4163017d5 | |||
| 7f4a5c48ec | |||
| 9f7588b245 | |||
| 63cbc0c55b | |||
| 28eb7a678c | |||
| a95c49915a | |||
| b7adc6add6 | |||
| 2aeebf280b | |||
| 8ac06979f0 | |||
| 2d4f90d5eb | |||
| d7a44a639d | |||
| 7bfa05f47d | |||
| c7bbb09195 | |||
| 41c9c15ae5 | |||
| 964831becf | |||
| e847b92cce | |||
| c2ecd86bb4 | |||
| 70d922fad6 | |||
| 854bee0519 | |||
| d54d991bdd | |||
| a4b600b97d | |||
| d6cfaa5050 | |||
| 00d1aec210 | |||
| f62e3391e1 | |||
| 4a406604bd | |||
| 55cee46a8d | |||
| 607c52c0b7 | |||
| 2694321256 | |||
| e8e790102a | |||
| 9c5561921b | |||
| 78fab9ab0c | |||
| 058c750bc6 | |||
| f29da39dff | |||
| 624a7c70c9 | |||
| 0447ee2d9e | |||
| 456fcd38d0 | |||
| a3cab7097a | |||
| 848e2f1294 | |||
| ef4dc48ea1 | |||
| 2138a33292 | |||
| a59343b987 | |||
| a4ca4bdc99 | |||
| a57b9a9081 | |||
| 249a6af456 | |||
| 18685d1a94 | |||
| 493b090534 | |||
| 389adab9b8 | |||
| 6360b8c58f | |||
| b0818977cf | |||
| 32deb160ef | |||
| 2ba5215036 | |||
| 73ea4c7b32 | |||
| 46c37ff06a | |||
| ff4708b624 | |||
| 82b2c84f13 | |||
| 48e8b1a9b3 | |||
| 1ad7c54ace | |||
| 9998c86c1f | |||
| 01a707ae0a | |||
| 570e8388fc | |||
| 9c5b5d4633 | |||
| 822bf13626 | |||
| c520de0087 | |||
| 0e9076efa3 | |||
| 74cd5e3387 | |||
| 65c92be09e | |||
| 8e776cb3ab | |||
| ab6cd2b751 | |||
| 18c3d990fc | |||
| 19d5cb8ecb | |||
| 2b56a78255 | |||
| 176d9df476 | |||
| c94275cb8b | |||
| 45dbce3677 | |||
| 18745158a3 | |||
| 98c6c88391 |
@@ -28,13 +28,6 @@ local.properties
|
||||
.buildpath
|
||||
|
||||
|
||||
#####################
|
||||
## Visual Studio Code
|
||||
#####################
|
||||
*.code-workspace
|
||||
.vscode/
|
||||
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
"",
|
||||
"\\Moose Development",
|
||||
"\\Moose Development\\Moose",
|
||||
"\\Moose Development\\Moose\\Ops"
|
||||
],
|
||||
"SelectedNode": "\\Moose Development\\Moose\\Ops\\Airboss.lua",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' )
|
||||
|
||||
local base = _G
|
||||
|
||||
__Moose = {}
|
||||
|
||||
__Moose.Include = function( IncludeFile )
|
||||
if not __Moose.Includes[ IncludeFile ] then
|
||||
__Moose.Includes[IncludeFile] = IncludeFile
|
||||
local f = assert( base.loadfile( IncludeFile ) )
|
||||
if f == nil then
|
||||
error ("Moose: Could not load Moose file " .. IncludeFile )
|
||||
else
|
||||
env.info( "Moose: " .. IncludeFile .. " dynamically loaded." )
|
||||
return f()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
__Moose.Includes = {}
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Modules.lua' )
|
||||
BASE:TraceOnOff( true )
|
||||
env.info( '*** MOOSE INCLUDE END *** ' )
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
# See https://github.com/Koihik/LuaFormatter
|
||||
# Use '-- LuaFormatter off' and '-- LuaFormatter on' around code blocks to inhibit formatting
|
||||
|
||||
column_limit: 500
|
||||
indent_width: 2
|
||||
use_tab: false
|
||||
continuation_indent_width: 2
|
||||
keep_simple_control_block_one_line: false
|
||||
keep_simple_function_one_line: false
|
||||
align_args: true
|
||||
break_after_functioncall_lp: false
|
||||
break_before_functioncall_rp: false
|
||||
align_parameter: true
|
||||
chop_down_parameter: true
|
||||
break_after_functiondef_lp: false
|
||||
break_before_functiondef_rp: false
|
||||
align_table_field: true
|
||||
break_after_table_lb: true
|
||||
break_before_table_rb: true
|
||||
chop_down_table: true
|
||||
chop_down_kv_table: true
|
||||
column_table_limit: 500
|
||||
table_sep: ','
|
||||
extra_sep_at_table_end: true
|
||||
break_after_operator: true
|
||||
single_quote_to_double_quote: false
|
||||
double_quote_to_single_quote: false
|
||||
spaces_before_call: 1
|
||||
spaces_inside_functiondef_parens: true
|
||||
spaces_inside_functioncall_parens: true
|
||||
spaces_inside_table_braces: true
|
||||
spaces_around_equals_in_field: true
|
||||
line_breaks_after_function_body: 1
|
||||
@@ -40,8 +40,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -71,7 +71,7 @@
|
||||
-- * **@{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.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,8 +42,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -73,7 +73,7 @@
|
||||
-- * **@{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.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -68,7 +68,7 @@
|
||||
-- * **RTB** ( Group ): Route the AI to the home base.
|
||||
-- * **Detect** ( Group ): The AI is detecting targets.
|
||||
-- * **Detected** ( Group ): The AI has detected new targets.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set or Get the AI controllable
|
||||
--
|
||||
@@ -100,8 +100,8 @@
|
||||
-- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL
|
||||
--
|
||||
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- while a new AI is targetted to the AI_A2A_PATROL.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place.
|
||||
@@ -109,7 +109,7 @@
|
||||
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
|
||||
--
|
||||
-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
|
||||
--
|
||||
-- ===
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,8 +42,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
|
||||
@@ -253,6 +253,9 @@ function AI_AIR:New( AIGroup )
|
||||
|
||||
self.IdleCount = 0
|
||||
|
||||
self.RTBSpeedMaxFactor = 0.6
|
||||
self.RTBSpeedMinFactor = 0.5
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -370,11 +373,11 @@ end
|
||||
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number FuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
|
||||
@@ -387,14 +390,14 @@ function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
|
||||
return self
|
||||
end
|
||||
|
||||
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.
|
||||
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached,
|
||||
-- Therefore, when the damage threshold is reached,
|
||||
-- the AI will return immediately to the home base (RTB).
|
||||
-- Note that for groups, the average damage of the complete group will be calculated.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
|
||||
|
||||
@@ -476,7 +479,7 @@ function AI_AIR:onafterStatus()
|
||||
|
||||
local Fuel = self.Controllable:GetFuelMin()
|
||||
|
||||
-- If the fuel in the controllable is below the treshold percentage,
|
||||
-- If the fuel in the controllable is below the threshold percentage,
|
||||
-- then send for refuel in case of a tanker, otherwise RTB.
|
||||
if Fuel < self.FuelThresholdPercentage then
|
||||
|
||||
@@ -576,6 +579,19 @@ function AI_AIR.RTBHold( AIGroup, Fsm )
|
||||
|
||||
end
|
||||
|
||||
--- Set the min and max factors on RTB speed. Use this, if your planes are heading back to base too fast. Default values are 0.5 and 0.6.
|
||||
-- The RTB speed is calculated as the max speed of the unit multiplied by MinFactor (lower bracket) and multiplied by MaxFactor (upper bracket).
|
||||
-- A random value in this bracket is then applied in the waypoint routing generation.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number MinFactor Lower bracket factor. Defaults to 0.5.
|
||||
-- @param #number MaxFactor Upper bracket factor. Defaults to 0.6.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor)
|
||||
self.RTBSpeedMaxFactor = MaxFactor or 0.6
|
||||
self.RTBSpeedMinFactor = MinFactor or 0.5
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
@@ -591,7 +607,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
--AIGroup:ClearTasks()
|
||||
|
||||
AIGroup:OptionProhibitAfterburner(true)
|
||||
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
@@ -599,12 +615,14 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
local FromCoord = AIGroup:GetCoordinate()
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
|
||||
local ToTargetVec3 = ToTargetCoord:GetVec3()
|
||||
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+1000 -- let's set this 1000m/3000 feet above ground
|
||||
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground
|
||||
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
|
||||
|
||||
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
|
||||
local RTBSpeedMax = AIGroup:GetSpeedMax()
|
||||
self:SetRTBSpeed( RTBSpeedMax * 0.5, RTBSpeedMax * 0.6 )
|
||||
local RTBSpeedMaxFactor = self.RTBSpeedMaxFactor or 0.6
|
||||
local RTBSpeedMinFactor = self.RTBSpeedMinFactor or 0.5
|
||||
self:SetRTBSpeed( RTBSpeedMax * RTBSpeedMinFactor, RTBSpeedMax * RTBSpeedMaxFactor)
|
||||
end
|
||||
|
||||
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
-- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations.
|
||||
-- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets.
|
||||
-- * Define and use a detection network controlled by recce.
|
||||
-- * Define AIR defense squadrons at airbases, farps and carriers.
|
||||
-- * Define AIR defense squadrons at airbases, FARPs and carriers.
|
||||
-- * Enable airbases for AIR defenses.
|
||||
-- * Add different planes and helicopter templates to squadrons.
|
||||
-- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition.
|
||||
-- * Add multiple squadrons to different airbases, farps or carriers.
|
||||
-- * Add multiple squadrons to different airbases, FARPs or carriers.
|
||||
-- * Define different ranges to engage upon.
|
||||
-- * Establish an automatic in air refuel process for planes using refuel tankers.
|
||||
-- * Setup default settings for all squadrons and AIR defenses.
|
||||
@@ -40,7 +40,7 @@
|
||||
--
|
||||
-- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system.
|
||||
--
|
||||
-- Before you start using the AI_AIR_DISPATCHER, ask youself the following questions.
|
||||
-- Before you start using the AI_AIR_DISPATCHER, ask yourself the following questions.
|
||||
--
|
||||
--
|
||||
-- ## 1. Which coalition am I modeling an AIR defense system for? blue or red?
|
||||
@@ -128,7 +128,7 @@
|
||||
-- Depending on the defense type, different payloads will be needed. See further points on squadron definition.
|
||||
--
|
||||
--
|
||||
-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps?
|
||||
-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs?
|
||||
--
|
||||
-- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**.
|
||||
-- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses.
|
||||
@@ -354,7 +354,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups.
|
||||
-- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set.
|
||||
--
|
||||
-- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen.
|
||||
-- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen.
|
||||
--
|
||||
-- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network
|
||||
-- configuration and setup the AIR defense detection mechanism.
|
||||
@@ -647,7 +647,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway.
|
||||
-- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines.
|
||||
--
|
||||
-- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency.
|
||||
-- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency.
|
||||
-- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the
|
||||
-- A2A defense system, as no new CAP or GCI planes can takeoff.
|
||||
-- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft.
|
||||
@@ -724,13 +724,13 @@ do -- AI_AIR_DISPATCHER
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron.
|
||||
--
|
||||
-- ## 4. Set the **fuel treshold**.
|
||||
-- ## 4. Set the **fuel threshold**.
|
||||
--
|
||||
-- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken:
|
||||
-- When aircraft get **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken:
|
||||
-- - The aircraft will go RTB, and will be replaced with a new aircraft if possible.
|
||||
-- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons.
|
||||
--
|
||||
-- ## 6. Other configuration options
|
||||
--
|
||||
@@ -786,17 +786,17 @@ do -- AI_AIR_DISPATCHER
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.5. Default RTB fuel treshold.
|
||||
-- ## 10.5. Default RTB fuel threshold.
|
||||
--
|
||||
-- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
-- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.6. Default RTB damage treshold.
|
||||
-- ## 10.6. Default RTB damage threshold.
|
||||
--
|
||||
-- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
-- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.7. Default settings for **patrol**.
|
||||
--
|
||||
@@ -829,7 +829,7 @@ do -- AI_AIR_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_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed.
|
||||
--
|
||||
-- When the tanker specified is alive and in the air, the tanker will be used for refuelling.
|
||||
--
|
||||
@@ -843,7 +843,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 )
|
||||
-- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 )
|
||||
--
|
||||
-- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left.
|
||||
-- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left.
|
||||
-- A2ADispatcher:SetDefaultFuelThreshold( 0.9 )
|
||||
-- A2ADispatcher:SetDefaultTanker( "Tanker" )
|
||||
--
|
||||
@@ -882,10 +882,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- As a result, the GCI and CAP will stop!
|
||||
-- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes
|
||||
-- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players.
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #AI_AIR_DISPATCHER
|
||||
AI_AIR_DISPATCHER = {
|
||||
ClassName = "AI_AIR_DISPATCHER",
|
||||
@@ -914,10 +911,10 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Enumerator for spawns at airbases
|
||||
-- @type AI_AIR_DISPATCHER.Takeoff
|
||||
-- @extends Wrapper.Group#GROUP.Takeoff
|
||||
|
||||
|
||||
--- @field #AI_AIR_DISPATCHER.Takeoff Takeoff
|
||||
AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff
|
||||
|
||||
|
||||
--- Defnes Landing location.
|
||||
-- @field #AI_AIR_DISPATCHER.Landing
|
||||
AI_AIR_DISPATCHER.Landing = {
|
||||
@@ -925,7 +922,7 @@ do -- AI_AIR_DISPATCHER
|
||||
AtRunway = 2,
|
||||
AtEngineShutdown = 3,
|
||||
}
|
||||
|
||||
|
||||
--- A defense queue item description
|
||||
-- @type AI_AIR_DISPATCHER.DefenseQueueItem
|
||||
-- @field Squadron
|
||||
@@ -936,7 +933,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- @field Functional.Detection#DETECTION_BASE AttackerDetection
|
||||
-- @field DefenderGrouping
|
||||
-- @field #string SquadronName The name of the squadron.
|
||||
|
||||
|
||||
--- Queue of planned defenses to be launched.
|
||||
-- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers.
|
||||
-- And some of these platforms have very limited amount of "launching" platforms.
|
||||
@@ -945,40 +942,39 @@ do -- AI_AIR_DISPATCHER
|
||||
-- This guarantees that launched defenders are also directly existing ...
|
||||
-- @type AI_AIR_DISPATCHER.DefenseQueue
|
||||
-- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ...
|
||||
|
||||
|
||||
--- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue
|
||||
AI_AIR_DISPATCHER.DefenseQueue = {}
|
||||
|
||||
|
||||
|
||||
--- Defense approach types
|
||||
-- @type #AI_AIR_DISPATCHER.DefenseApproach
|
||||
AI_AIR_DISPATCHER.DefenseApproach = {
|
||||
Random = 1,
|
||||
Distance = 2,
|
||||
}
|
||||
|
||||
|
||||
--- AI_AIR_DISPATCHER constructor.
|
||||
-- This is defining the AIR DISPATCHER for one coaliton.
|
||||
-- This is defining the AIR DISPATCHER for one coalition.
|
||||
-- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units.
|
||||
-- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently.
|
||||
-- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network.
|
||||
-- @return #AI_AIR_DISPATCHER self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Setup the Detection, using DETECTION_AREAS.
|
||||
-- -- First define the SET of GROUPs that are defining the EWR network.
|
||||
-- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR.
|
||||
-- DetectionSetGroup = SET_GROUP:New()
|
||||
-- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
|
||||
-- DetectionSetGroup:FilterStart()
|
||||
--
|
||||
-- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius.
|
||||
-- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) --
|
||||
--
|
||||
--
|
||||
-- -- Setup the Detection, using DETECTION_AREAS.
|
||||
-- -- First define the SET of GROUPs that are defining the EWR network.
|
||||
-- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR.
|
||||
-- DetectionSetGroup = SET_GROUP:New()
|
||||
-- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
|
||||
-- DetectionSetGroup:FilterStart()
|
||||
--
|
||||
-- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius.
|
||||
-- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
function AI_AIR_DISPATCHER:New( Detection )
|
||||
|
||||
-- Inherits from DETECTION_MANAGER
|
||||
@@ -1435,17 +1431,17 @@ do -- AI_AIR_DISPATCHER
|
||||
end
|
||||
|
||||
|
||||
--- Set the default damage treshold when defenders will RTB.
|
||||
-- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.
|
||||
--- Set the default damage threshold when defenders will RTB.
|
||||
-- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB.
|
||||
-- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default damage treshold.
|
||||
-- -- Now Setup the default damage threshold.
|
||||
-- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold )
|
||||
@@ -1989,7 +1985,7 @@ do -- AI_AIR_DISPATCHER
|
||||
|
||||
--- Defines the default amount of extra planes that will take-off as part of the defense system.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2028,7 +2024,7 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Defines the amount of extra planes that will take-off as part of the defense system.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2068,7 +2064,7 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Gets the overhead of planes as part of the defense system, in comparison with the attackers.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2674,17 +2670,17 @@ do -- AI_AIR_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the default fuel treshold when defenders will RTB or Refuel in the air.
|
||||
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
--- Set the default fuel threshold when defenders will RTB or Refuel in the air.
|
||||
-- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold )
|
||||
@@ -2695,18 +2691,18 @@ do -- AI_AIR_DISPATCHER
|
||||
end
|
||||
|
||||
|
||||
--- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air.
|
||||
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
--- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air.
|
||||
-- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold )
|
||||
@@ -2726,7 +2722,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
-- -- Now Setup the default tanker.
|
||||
@@ -2749,7 +2745,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the squadron fuel treshold.
|
||||
-- -- Now Setup the squadron fuel threshold.
|
||||
-- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
-- -- Now Setup the squadron tanker.
|
||||
@@ -2847,7 +2843,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount )
|
||||
|
||||
-- First, count the active AIGroups Units, targetting the DetectedSet
|
||||
-- First, count the active AIGroups Units, targeting the DetectedSet
|
||||
local DefendersEngaged = 0
|
||||
local DefendersTotal = 0
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -70,7 +70,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
|
||||
-- using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -87,7 +87,7 @@
|
||||
-- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone.
|
||||
-- It can be notified to go RTB through the **RTB** event.
|
||||
--
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -117,7 +117,7 @@
|
||||
-- * **@{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.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object**
|
||||
--
|
||||
@@ -602,7 +602,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start Targetting
|
||||
self:__Target( -2 ) -- Start targeting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AIB%20-%20AI%20Balancing)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel treshold to RTB on time.
|
||||
-- * Manage a fuel threshold 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)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAP%20-%20Combat%20Air%20Patrol)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -65,8 +65,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -96,7 +96,7 @@
|
||||
-- * **@{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.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
@@ -428,7 +428,9 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
if not CurrentVec2 then return self end
|
||||
if not CurrentVec2 then -- flight dead at this point
|
||||
return self
|
||||
end
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAS%20-%20Close%20Air%20Support)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAS%20-%20Close%20Air%20Support)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -49,7 +49,7 @@
|
||||
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
|
||||
-- using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -87,7 +87,7 @@
|
||||
-- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone.
|
||||
-- It can be notified to go RTB through the **RTB** event.
|
||||
--
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -117,7 +117,7 @@
|
||||
-- * **@{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.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -459,7 +459,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
@@ -520,7 +520,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start Targetting
|
||||
self:__Target( -2 ) -- Start targeting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -174,8 +174,8 @@ function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZone
|
||||
self:SetPickupSpeed( 350, 150 )
|
||||
self:SetDeploySpeed( 350, 150 )
|
||||
|
||||
self:SetPickupRadius( 40, 12 )
|
||||
self:SetDeployRadius( 40, 12 )
|
||||
self:SetPickupRadius( 0, 0 )
|
||||
self:SetDeployRadius( 0, 0 )
|
||||
|
||||
self:SetPickupHeight( 500, 200 )
|
||||
self:SetDeployHeight( 500, 200 )
|
||||
@@ -186,9 +186,6 @@ end
|
||||
|
||||
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
|
||||
|
||||
local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
dispatcher:SetLandingSpeedAndHeight(27, 6)
|
||||
return dispatcher
|
||||
|
||||
return AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
end
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
-- @image AI_Cargo_Dispatching_For_Ship.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_SHIP
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
-- @image AI_Cargo_Dispatching_For_Ship.JPG
|
||||
|
||||
--- @type AI_CARGO_SHIP
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
@@ -1140,8 +1140,8 @@ end
|
||||
-- @param DCS#Vec3 CV2 Vec3
|
||||
function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
|
||||
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then
|
||||
|
||||
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then
|
||||
|
||||
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
|
||||
|
||||
FollowGroup:OptionROTEvadeFire()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel treshold to RTB on time.
|
||||
-- * Manage a fuel threshold to RTB on time.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -16,7 +16,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/PAT%20-%20Patrolling)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -72,8 +72,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -101,7 +101,7 @@
|
||||
-- * **RTB** ( Group ): Route the AI to the home base.
|
||||
-- * **Detect** ( Group ): The AI is detecting targets.
|
||||
-- * **Detected** ( Group ): The AI has detected new targets.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set or Get the AI controllable
|
||||
--
|
||||
@@ -133,8 +133,8 @@
|
||||
-- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE
|
||||
--
|
||||
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- while a new AI is targetted to the AI_PATROL_ZONE.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place.
|
||||
@@ -142,7 +142,7 @@
|
||||
-- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE
|
||||
--
|
||||
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place.
|
||||
--
|
||||
-- ===
|
||||
@@ -581,11 +581,11 @@ function AI_PATROL_ZONE:ClearDetectedUnits()
|
||||
end
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
|
||||
@@ -596,14 +596,14 @@ function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFu
|
||||
return self
|
||||
end
|
||||
|
||||
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.
|
||||
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached,
|
||||
-- Therefore, when the damage threshold is reached,
|
||||
-- the AI will return immediately to the home base (RTB).
|
||||
-- Note that for groups, the average damage of the complete group will be calculated.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold )
|
||||
|
||||
@@ -725,6 +725,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local life = self.Controllable:GetLife() or 0
|
||||
if self.Controllable:IsAlive() and life > 1 then
|
||||
-- Determine if the AIControllable is within the PatrolZone.
|
||||
@@ -744,7 +745,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
self:T( "Not in the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
if not CurrentVec2 then return end
|
||||
--Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
-- * is of type `Workmaterials`
|
||||
-- * will report when a carrier is within 500 meters
|
||||
-- * will board to carriers when the carrier is within 500 meters from the cargo object
|
||||
-- * will dissapear when the cargo is within 25 meters from the carrier during boarding
|
||||
-- * will disappear when the cargo is within 25 meters from the carrier during boarding
|
||||
--
|
||||
-- So the overall syntax of the #CARGO naming tag and arguments are:
|
||||
--
|
||||
@@ -220,7 +220,7 @@
|
||||
-- * is of type `Workmaterials`
|
||||
-- * will report when a carrier is within 500 meters
|
||||
-- * will board to carriers when the carrier is within 500 meters from the cargo object
|
||||
-- * will dissapear when the cargo is within 25 meters from the carrier during boarding
|
||||
-- * will disappear when the cargo is within 25 meters from the carrier during boarding
|
||||
--
|
||||
-- So the overall syntax of the #CARGO naming tag and arguments are:
|
||||
--
|
||||
|
||||
@@ -1,943 +0,0 @@
|
||||
--- **Core** - A* Pathfinding.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Find path from A to B.
|
||||
-- * Pre-defined as well as custom valid neighbour functions.
|
||||
-- * Pre-defined as well as custom cost functions.
|
||||
-- * Easy rectangular grid setup.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Core.Astar
|
||||
-- @image CORE_Astar.png
|
||||
|
||||
|
||||
--- ASTAR class.
|
||||
-- @type ASTAR
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table nodes Table of nodes.
|
||||
-- @field #number counter Node counter.
|
||||
-- @field #number Nnodes Number of nodes.
|
||||
-- @field #number nvalid Number of nvalid calls.
|
||||
-- @field #number nvalidcache Number of cached valid evals.
|
||||
-- @field #number ncost Number of cost evaluations.
|
||||
-- @field #number ncostcache Number of cached cost evals.
|
||||
-- @field #ASTAR.Node startNode Start node.
|
||||
-- @field #ASTAR.Node endNode End node.
|
||||
-- @field Core.Point#COORDINATE startCoord Start coordinate.
|
||||
-- @field Core.Point#COORDINATE endCoord End coordinate.
|
||||
-- @field #function ValidNeighbourFunc Function to check if a node is valid.
|
||||
-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function.
|
||||
-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another.
|
||||
-- @field #table CostArg Optional arguments passed to the cost function.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *When nothing goes right... Go left!*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The ASTAR Concept
|
||||
--
|
||||
-- Pathfinding algorithm.
|
||||
--
|
||||
--
|
||||
-- # Start and Goal
|
||||
--
|
||||
-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually.
|
||||
--
|
||||
-- ## Start
|
||||
--
|
||||
-- The start
|
||||
--
|
||||
-- ## Goal
|
||||
--
|
||||
--
|
||||
-- # Nodes
|
||||
--
|
||||
-- ## Rectangular Grid
|
||||
--
|
||||
-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where
|
||||
--
|
||||
-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid.
|
||||
-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km).
|
||||
-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km).
|
||||
-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km).
|
||||
-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*.
|
||||
-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created.
|
||||
--
|
||||
-- ## Valid Surfaces
|
||||
--
|
||||
-- Certain unit types can only travel on certain surfaces types, for example
|
||||
--
|
||||
-- * Naval units can only travel on water (that also excludes shallow water in DCS currently),
|
||||
-- * Ground units can only traval on land.
|
||||
--
|
||||
-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient.
|
||||
--
|
||||
-- ## Box Width (BoxHY)
|
||||
--
|
||||
-- The box width needs to be large enough to capture all paths you want to consider.
|
||||
--
|
||||
-- ## Space in X
|
||||
--
|
||||
-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node.
|
||||
--
|
||||
-- ## Grid Spacing
|
||||
--
|
||||
-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible.
|
||||
-- However, if the value is too large, the algorithm might fail to get a valid path.
|
||||
--
|
||||
-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path.
|
||||
--
|
||||
-- # Valid Neighbours
|
||||
--
|
||||
-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible.
|
||||
--
|
||||
-- ## Line of Sight
|
||||
--
|
||||
-- For naval
|
||||
--
|
||||
--
|
||||
-- # Heuristic Cost
|
||||
--
|
||||
-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another.
|
||||
-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes.
|
||||
--
|
||||
--
|
||||
-- # Calculate the Path
|
||||
--
|
||||
-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
|
||||
-- describe the optimal path from the start node to the end node.
|
||||
--
|
||||
-- By default, the start and end node are include in the table that is returned.
|
||||
--
|
||||
-- Note that a valid path must not always exist. So you should check if the function returns *nil*.
|
||||
--
|
||||
-- Common reasons that a path cannot be found are:
|
||||
--
|
||||
-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid.
|
||||
-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY*
|
||||
-- * There simply is no valid path ==> you are screwed :(
|
||||
--
|
||||
--
|
||||
-- # Examples
|
||||
--
|
||||
-- ## Strait of Hormuz
|
||||
--
|
||||
-- Carrier Group finds its way through the Stait of Hormuz.
|
||||
--
|
||||
-- ##
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #ASTAR
|
||||
ASTAR = {
|
||||
ClassName = "ASTAR",
|
||||
Debug = nil,
|
||||
lid = nil,
|
||||
nodes = {},
|
||||
counter = 1,
|
||||
Nnodes = 0,
|
||||
ncost = 0,
|
||||
ncostcache = 0,
|
||||
nvalid = 0,
|
||||
nvalidcache = 0,
|
||||
}
|
||||
|
||||
--- Node data.
|
||||
-- @type ASTAR.Node
|
||||
-- @field #number id Node id.
|
||||
-- @field Core.Point#COORDINATE coordinate Coordinate of the node.
|
||||
-- @field #number surfacetype Surface type.
|
||||
-- @field #table valid Cached valid/invalid nodes.
|
||||
-- @field #table cost Cached cost.
|
||||
|
||||
--- ASTAR infinity.
|
||||
-- @field #number INF
|
||||
ASTAR.INF=1/0
|
||||
|
||||
--- ASTAR class version.
|
||||
-- @field #string version
|
||||
ASTAR.version="0.4.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add more valid neighbour functions.
|
||||
-- TODO: Write docs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new ASTAR object.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:New()
|
||||
|
||||
-- Inherit everything from INTEL class.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#ASTAR
|
||||
|
||||
self.lid="ASTAR | "
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set coordinate from where to start.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate Start coordinate.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetStartCoordinate(Coordinate)
|
||||
|
||||
self.startCoord=Coordinate
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set coordinate where you want to go.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate end coordinate.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetEndCoordinate(Coordinate)
|
||||
|
||||
self.endCoord=Coordinate
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a node from a given coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node.
|
||||
-- @return #ASTAR.Node The node.
|
||||
function ASTAR:GetNodeFromCoordinate(Coordinate)
|
||||
|
||||
local node={} --#ASTAR.Node
|
||||
|
||||
node.coordinate=Coordinate
|
||||
node.surfacetype=Coordinate:GetSurfaceType()
|
||||
node.id=self.counter
|
||||
|
||||
node.valid={}
|
||||
node.cost={}
|
||||
|
||||
self.counter=self.counter+1
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
|
||||
--- Add a node to the table of grid nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:AddNode(Node)
|
||||
|
||||
self.nodes[Node.id]=Node
|
||||
self.Nnodes=self.Nnodes+1
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a node to the table of grid nodes specifying its coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created.
|
||||
-- @return #ASTAR.Node The node.
|
||||
function ASTAR:AddNodeFromCoordinate(Coordinate)
|
||||
|
||||
local node=self:GetNodeFromCoordinate(Coordinate)
|
||||
|
||||
self:AddNode(node)
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
--- Check if the coordinate of a node has is at a valid surface type.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added.
|
||||
-- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid.
|
||||
-- @return #boolean If true, surface type of node is valid.
|
||||
function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes)
|
||||
|
||||
if SurfaceTypes then
|
||||
|
||||
if type(SurfaceTypes)~="table" then
|
||||
SurfaceTypes={SurfaceTypes}
|
||||
end
|
||||
|
||||
for _,surface in pairs(SurfaceTypes) do
|
||||
if surface==Node.surfacetype then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
else
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Add a function to determine if a neighbour of a node is valid.
|
||||
-- @param #ASTAR self
|
||||
-- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid.
|
||||
-- @param ... Condition function arguments if any.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...)
|
||||
|
||||
self.ValidNeighbourFunc=NeighbourFunction
|
||||
|
||||
self.ValidNeighbourArg={}
|
||||
if arg then
|
||||
self.ValidNeighbourArg=arg
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set valid neighbours to require line of sight between two nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number CorridorWidth Width of LoS corridor in meters.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourLoS(CorridorWidth)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid neighbours to be in a certain distance.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourDistance(MaxDistance)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid neighbours to be in a certain distance.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourRoad(MaxDistance)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.Road, MaxDistance)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the function which calculates the "cost" to go from one to another node.
|
||||
-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments.
|
||||
-- Very often the distance between nodes is a good measure for the cost.
|
||||
-- @param #ASTAR self
|
||||
-- @param #function CostFunction Function that returns the "cost".
|
||||
-- @param ... Condition function arguments if any.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostFunction(CostFunction, ...)
|
||||
|
||||
self.CostFunc=CostFunction
|
||||
|
||||
self.CostArg={}
|
||||
if arg then
|
||||
self.CostArg=arg
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 2D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostDist2D()
|
||||
|
||||
self:SetCostFunction(ASTAR.Dist2D)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 3D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostDist3D()
|
||||
|
||||
self:SetCostFunction(ASTAR.Dist3D)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 3D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostRoad()
|
||||
|
||||
self:SetCostFunction(ASTAR)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Grid functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a rectangular grid of nodes between star and end coordinate.
|
||||
-- The coordinate system is oriented along the line between start and end point.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed.
|
||||
-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km).
|
||||
-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km).
|
||||
-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters.
|
||||
-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX.
|
||||
-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid)
|
||||
|
||||
-- Note that internally
|
||||
-- x coordinate is z: x-->z Line from start to end
|
||||
-- y coordinate is x: y-->x Perpendicular
|
||||
|
||||
-- Grid length and width.
|
||||
local Dz=SpaceX or 10000
|
||||
local Dx=BoxHY and BoxHY/2 or 20000
|
||||
|
||||
-- Increments.
|
||||
local dz=deltaX or 2000
|
||||
local dx=deltaY or dz
|
||||
|
||||
-- Heading from start to end coordinate.
|
||||
local angle=self.startCoord:HeadingTo(self.endCoord)
|
||||
|
||||
--Distance between start and end.
|
||||
local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz
|
||||
|
||||
-- Origin of map. Needed to translate back to wanted position.
|
||||
local co=COORDINATE:New(0, 0, 0)
|
||||
local do1=co:Get2DDistance(self.startCoord)
|
||||
local ho1=co:HeadingTo(self.startCoord)
|
||||
|
||||
-- Start of grid.
|
||||
local xmin=-Dx
|
||||
local zmin=-Dz
|
||||
|
||||
-- Number of grid points.
|
||||
local nz=dist/dz+1
|
||||
local nx=2*Dx/dx+1
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Loop over x and z coordinate to create a 2D grid.
|
||||
for i=1,nx do
|
||||
|
||||
-- x coordinate perpendicular to z.
|
||||
local x=xmin+dx*(i-1)
|
||||
|
||||
for j=1,nz do
|
||||
|
||||
-- z coordinate connecting start and end.
|
||||
local z=zmin+dz*(j-1)
|
||||
|
||||
-- Rotate 2D.
|
||||
local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle)
|
||||
|
||||
-- Coordinate of the node.
|
||||
local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true)
|
||||
|
||||
-- Create a node at this coordinate.
|
||||
local node=self:GetNodeFromCoordinate(c)
|
||||
|
||||
-- Check if node has valid surface type.
|
||||
if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then
|
||||
|
||||
if MarkGrid then
|
||||
c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype))
|
||||
end
|
||||
|
||||
-- Add node to grid.
|
||||
self:AddNode(node)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("Done building grid!")
|
||||
self:T2(self.lid..text)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Valid neighbour functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function to check if two nodes have line of sight (LoS).
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @param #number corridor (Optional) Width of corridor in meters.
|
||||
-- @return #boolean If true, two nodes have LoS.
|
||||
function ASTAR.LoS(nodeA, nodeB, corridor)
|
||||
|
||||
local offset=1
|
||||
|
||||
local dx=corridor and corridor/2 or nil
|
||||
local dy=dx
|
||||
|
||||
local cA=nodeA.coordinate:GetVec3()
|
||||
local cB=nodeB.coordinate:GetVec3()
|
||||
cA.y=offset
|
||||
cB.y=offset
|
||||
|
||||
local los=land.isVisible(cA, cB)
|
||||
|
||||
if los and corridor then
|
||||
|
||||
-- Heading from A to B.
|
||||
local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate)
|
||||
|
||||
local Ap=UTILS.VecTranslate(cA, dx, heading+90)
|
||||
local Bp=UTILS.VecTranslate(cB, dx, heading+90)
|
||||
|
||||
los=land.isVisible(Ap, Bp)
|
||||
|
||||
if los then
|
||||
|
||||
local Am=UTILS.VecTranslate(cA, dx, heading-90)
|
||||
local Bm=UTILS.VecTranslate(cB, dx, heading-90)
|
||||
|
||||
los=land.isVisible(Am, Bm)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return los
|
||||
end
|
||||
|
||||
--- Function to check if two nodes have a road connection.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #boolean If true, two nodes are connected via a road.
|
||||
function ASTAR.Road(nodeA, nodeB)
|
||||
|
||||
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
|
||||
|
||||
if path then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Function to check if distance between two nodes is less than a threshold distance.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @param #number distmax Max distance in meters. Default is 2000 m.
|
||||
-- @return #boolean If true, distance between the two nodes is below threshold.
|
||||
function ASTAR.DistMax(nodeA, nodeB, distmax)
|
||||
|
||||
distmax=distmax or 2000
|
||||
|
||||
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
|
||||
|
||||
return dist<=distmax
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Heuristic cost functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Heuristic cost is given by the 2D distance between the nodes.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.Dist2D(nodeA, nodeB)
|
||||
local dist=nodeA.coordinate:Get2DDistance(nodeB)
|
||||
return dist
|
||||
end
|
||||
|
||||
--- Heuristic cost is given by the 3D distance between the nodes.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.Dist3D(nodeA, nodeB)
|
||||
local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate)
|
||||
return dist
|
||||
end
|
||||
|
||||
--- Heuristic cost is given by the distance between the nodes on road.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.DistRoad(nodeA, nodeB)
|
||||
|
||||
-- Get the path.
|
||||
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
|
||||
|
||||
if path then
|
||||
|
||||
local dist=0
|
||||
|
||||
for i=2,#path do
|
||||
local b=path[i] --DCS#Vec2
|
||||
local a=path[i-1] --DCS#Vec2
|
||||
|
||||
dist=dist+UTILS.VecDist2D(a,b)
|
||||
|
||||
end
|
||||
|
||||
return dist
|
||||
end
|
||||
|
||||
|
||||
return math.huge
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Find the closest node from a given coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate.
|
||||
-- @return #ASTAR.Node Cloest node to the coordinate.
|
||||
-- @return #number Distance to closest node in meters.
|
||||
function ASTAR:FindClosestNode(Coordinate)
|
||||
|
||||
local distMin=math.huge
|
||||
local closeNode=nil
|
||||
|
||||
for _,_node in pairs(self.nodes) do
|
||||
local node=_node --#ASTAR.Node
|
||||
|
||||
local dist=node.coordinate:Get2DDistance(Coordinate)
|
||||
|
||||
if dist<distMin then
|
||||
distMin=dist
|
||||
closeNode=node
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return closeNode, distMin
|
||||
end
|
||||
|
||||
--- Find the start node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:FindStartNode()
|
||||
|
||||
local node, dist=self:FindClosestNode(self.startCoord)
|
||||
|
||||
self.startNode=node
|
||||
|
||||
if dist>1000 then
|
||||
self:T(self.lid.."Adding start node to node grid!")
|
||||
self:AddNode(node)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:FindEndNode()
|
||||
|
||||
local node, dist=self:FindClosestNode(self.endCoord)
|
||||
|
||||
self.endNode=node
|
||||
|
||||
if dist>1000 then
|
||||
self:T(self.lid.."Adding end node to node grid!")
|
||||
self:AddNode(node)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Main A* pathfinding function
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates.
|
||||
-- @param #ASTAR self
|
||||
-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it.
|
||||
-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it.
|
||||
-- @return #table Table of nodes from start to finish.
|
||||
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||
|
||||
self:FindStartNode()
|
||||
self:FindEndNode()
|
||||
|
||||
local nodes=self.nodes
|
||||
local start=self.startNode
|
||||
local goal=self.endNode
|
||||
|
||||
-- Sets.
|
||||
local openset = {}
|
||||
local closedset = {}
|
||||
local came_from = {}
|
||||
local g_score = {}
|
||||
local f_score = {}
|
||||
|
||||
openset[start.id]=true
|
||||
local Nopen=1
|
||||
|
||||
-- Initial scores.
|
||||
g_score[start.id]=0
|
||||
f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start, goal)
|
||||
|
||||
-- Set start time.
|
||||
local T0=timer.getAbsTime()
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Starting A* pathfinding with %d Nodes", self.Nnodes)
|
||||
self:T(self.lid..text)
|
||||
|
||||
local Tstart=UTILS.GetOSTime()
|
||||
|
||||
-- Loop while we still have an open set.
|
||||
while Nopen > 0 do
|
||||
|
||||
-- Get current node.
|
||||
local current=self:_LowestFscore(openset, f_score)
|
||||
|
||||
-- Check if we are at the end node.
|
||||
if current.id==goal.id then
|
||||
|
||||
local path=self:_UnwindPath({}, came_from, goal)
|
||||
|
||||
if not ExcludeEndNode then
|
||||
table.insert(path, goal)
|
||||
end
|
||||
|
||||
if ExcludeStartNode then
|
||||
table.remove(path, 1)
|
||||
end
|
||||
|
||||
local Tstop=UTILS.GetOSTime()
|
||||
|
||||
local dT=nil
|
||||
if Tstart and Tstop then
|
||||
dT=Tstop-Tstart
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Found path with %d nodes (%d total)", #path, self.Nnodes)
|
||||
if dT then
|
||||
text=text..string.format(", OS Time %.6f sec", dT)
|
||||
end
|
||||
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
|
||||
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
|
||||
self:T(self.lid..text)
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
-- Move Node from open to closed set.
|
||||
openset[current.id]=nil
|
||||
Nopen=Nopen-1
|
||||
closedset[current.id]=true
|
||||
|
||||
-- Get neighbour nodes.
|
||||
local neighbors=self:_NeighbourNodes(current, nodes)
|
||||
|
||||
-- Loop over neighbours.
|
||||
for _,neighbor in pairs(neighbors) do
|
||||
|
||||
if self:_NotIn(closedset, neighbor.id) then
|
||||
|
||||
local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor)
|
||||
|
||||
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
|
||||
|
||||
came_from[neighbor]=current
|
||||
|
||||
g_score[neighbor.id]=tentative_g_score
|
||||
f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor, goal)
|
||||
|
||||
if self:_NotIn(openset, neighbor.id) then
|
||||
-- Add to open set.
|
||||
openset[neighbor.id]=true
|
||||
Nopen=Nopen+1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("WARNING: Could NOT find valid path!")
|
||||
self:E(self.lid..text)
|
||||
MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug)
|
||||
|
||||
return nil -- no valid path
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- A* pathfinding helper functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node nodeA Node A.
|
||||
-- @param #ASTAR.Node nodeB Node B.
|
||||
-- @return #number "Cost" to go from node A to node B.
|
||||
function ASTAR:_HeuristicCost(nodeA, nodeB)
|
||||
|
||||
-- Counter.
|
||||
self.ncost=self.ncost+1
|
||||
|
||||
-- Get chached cost if available.
|
||||
local cost=nodeA.cost[nodeB.id]
|
||||
if cost~=nil then
|
||||
self.ncostcache=self.ncostcache+1
|
||||
return cost
|
||||
end
|
||||
|
||||
local cost=nil
|
||||
if self.CostFunc then
|
||||
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
|
||||
else
|
||||
cost=self:_DistNodes(nodeA, nodeB)
|
||||
end
|
||||
|
||||
nodeA.cost[nodeB.id]=cost
|
||||
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
|
||||
|
||||
return cost
|
||||
end
|
||||
|
||||
--- Check if going from a node to a neighbour is possible.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node node A node.
|
||||
-- @param #ASTAR.Node neighbor Neighbour node.
|
||||
-- @return #boolean If true, transition between nodes is possible.
|
||||
function ASTAR:_IsValidNeighbour(node, neighbor)
|
||||
|
||||
-- Counter.
|
||||
self.nvalid=self.nvalid+1
|
||||
|
||||
local valid=node.valid[neighbor.id]
|
||||
if valid~=nil then
|
||||
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
|
||||
self.nvalidcache=self.nvalidcache+1
|
||||
return valid
|
||||
end
|
||||
|
||||
local valid=nil
|
||||
if self.ValidNeighbourFunc then
|
||||
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
|
||||
else
|
||||
valid=true
|
||||
end
|
||||
|
||||
node.valid[neighbor.id]=valid
|
||||
neighbor.valid[node.id]=valid -- Symmetric problem.
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
--- Calculate 2D distance between two nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node nodeA Node A.
|
||||
-- @param #ASTAR.Node nodeB Node B.
|
||||
-- @return #number Distance between nodes in meters.
|
||||
function ASTAR:_DistNodes(nodeA, nodeB)
|
||||
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
|
||||
end
|
||||
|
||||
--- Function that calculates the lowest F score.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table set The set of nodes IDs.
|
||||
-- @param #number f_score F score.
|
||||
-- @return #ASTAR.Node Best node.
|
||||
function ASTAR:_LowestFscore(set, f_score)
|
||||
|
||||
local lowest, bestNode = ASTAR.INF, nil
|
||||
|
||||
for nid,node in pairs(set) do
|
||||
|
||||
local score=f_score[nid]
|
||||
|
||||
if score<lowest then
|
||||
lowest, bestNode = score, nid
|
||||
end
|
||||
end
|
||||
|
||||
return self.nodes[bestNode]
|
||||
end
|
||||
|
||||
--- Function to get valid neighbours of a node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node theNode The node.
|
||||
-- @param #table nodes Possible neighbours.
|
||||
-- @param #table Valid neighbour nodes.
|
||||
function ASTAR:_NeighbourNodes(theNode, nodes)
|
||||
|
||||
local neighbors = {}
|
||||
|
||||
for _,node in pairs(nodes) do
|
||||
|
||||
if theNode.id~=node.id then
|
||||
|
||||
local isvalid=self:_IsValidNeighbour(theNode, node)
|
||||
|
||||
if isvalid then
|
||||
table.insert(neighbors, node)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return neighbors
|
||||
end
|
||||
|
||||
--- Function to check if a node is not in a set.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table set Set of nodes.
|
||||
-- @param #ASTAR.Node theNode The node to check.
|
||||
-- @return #boolean If true, the node is not in the set.
|
||||
function ASTAR:_NotIn(set, theNode)
|
||||
return set[theNode]==nil
|
||||
end
|
||||
|
||||
--- Unwind path function.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table flat_path Flat path.
|
||||
-- @param #table map Map.
|
||||
-- @param #ASTAR.Node current_node The current node.
|
||||
-- @return #table Unwinded path.
|
||||
function ASTAR:_UnwindPath( flat_path, map, current_node )
|
||||
|
||||
if map [current_node] then
|
||||
table.insert (flat_path, 1, map[current_node])
|
||||
return self:_UnwindPath(flat_path, map, map[current_node])
|
||||
else
|
||||
return flat_path
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,20 +16,20 @@
|
||||
--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon
|
||||
--
|
||||
-- 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
|
||||
-- There are two types of BEACONs available : the (aircraft) 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 useful to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for exemple.
|
||||
--
|
||||
-- ## AA TACAN Beacon usage
|
||||
-- ## Aircraft TACAN Beacon usage
|
||||
--
|
||||
-- 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.
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON.ActivateTACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON.StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- ## General Purpose Radio Beacon usage
|
||||
--
|
||||
-- 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.
|
||||
-- @{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
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
@@ -97,11 +97,11 @@ BEACON.Type={
|
||||
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glideslope.
|
||||
-- @field #number ILS_GLIDESLOPE ILS glide slope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glide slope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omni-directional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
|
||||
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
|
||||
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
|
||||
@@ -211,7 +211,7 @@ function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop sheduler.
|
||||
-- Stop scheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
@@ -237,7 +237,7 @@ function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop sheduler
|
||||
-- Stop scheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
@@ -245,7 +245,8 @@ function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
--- DEPRECATED: Please use @{BEACON:ActivateTACAN}() instead.
|
||||
-- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
@@ -325,10 +326,10 @@ function BEACON:StopAATACAN()
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
--- Activates a general purpose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- Although any frequency could be used, only a few DCS Modules can home on radio beacons at the time of writing, i.e. the Mi-8, Huey, Gazelle etc.
|
||||
-- The following e.g. can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
@@ -402,7 +403,7 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
--- Stop the Radio Beacon
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
@@ -448,4 +449,4 @@ function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
||||
-- 
|
||||
--
|
||||
-- Within a running mission, various DCS events occur. Units are dynamically created, crash, die, shoot stuff, get hit etc.
|
||||
-- This module provides a mechanism to dispatch those events occuring within your running mission, to the different objects orchestrating your mission.
|
||||
-- This module provides a mechanism to dispatch those events occurring within your running mission, to the different objects orchestrating your mission.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -32,11 +32,11 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- There are 5 levels of kind of objects that the _EVENTDISPATCHER services:
|
||||
-- There are 5 types/levels of objects that the _EVENTDISPATCHER services:
|
||||
--
|
||||
-- * _DATABASE object: The core of the MOOSE objects. Any object that is created, deleted or updated, is done in this database.
|
||||
-- * SET_ derived classes: Subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority.
|
||||
-- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to teh subscribed UNIT object.
|
||||
-- * SET_ derived classes: These are subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority.
|
||||
-- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed UNIT object.
|
||||
-- * GROUP objects: GROUP objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed GROUP object.
|
||||
-- * Any other object: Various other objects can subscribe to DCS events. Each DCS event triggered will be published to each subscribed object.
|
||||
--
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,89 +1,87 @@
|
||||
--- **Core** - Models the process to achieve goal(s).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Define the goal.
|
||||
-- * Monitor the goal achievement.
|
||||
-- * Manage goal contribution by players.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Classes that implement a goal achievement, will derive from GOAL to implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.Goal
|
||||
-- @image Core_Goal.JPG
|
||||
|
||||
|
||||
do -- Goal
|
||||
|
||||
--- @type GOAL
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- # 1. GOAL constructor
|
||||
--
|
||||
--
|
||||
-- * @{#GOAL.New}(): Creates a new GOAL object.
|
||||
--
|
||||
--
|
||||
-- # 2. GOAL is a finite state machine (FSM).
|
||||
--
|
||||
--
|
||||
-- ## 2.1. GOAL States
|
||||
--
|
||||
--
|
||||
-- * **Pending**: The goal object is in progress.
|
||||
-- * **Achieved**: The goal objective is Achieved.
|
||||
--
|
||||
--
|
||||
-- ## 2.2. GOAL Events
|
||||
--
|
||||
--
|
||||
-- * **Achieved**: Set the goal objective to Achieved.
|
||||
--
|
||||
--
|
||||
-- # 3. Player contributions.
|
||||
--
|
||||
--
|
||||
-- Goals are most of the time achieved by players. These player achievements can be registered as part of the goal achievement.
|
||||
-- Use @{#GOAL.AddPlayerContribution}() to add a player contribution to the goal.
|
||||
-- The player contributions are based on a points system, an internal counter per player.
|
||||
-- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(),
|
||||
-- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(),
|
||||
-- that retrieves all contributions done by the players. For one player, the contribution can be queried using @{#GOAL.GetPlayerContribution}().
|
||||
-- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}().
|
||||
--
|
||||
--
|
||||
-- # 4. Goal achievement.
|
||||
--
|
||||
--
|
||||
-- Once the goal is achieved, the mission designer will need to trigger the goal achievement using the **Achieved** event.
|
||||
-- The underlying 2 examples will achieve the goals for the `Goal` object:
|
||||
--
|
||||
--
|
||||
-- Goal:Achieved() -- Achieve the goal immediately.
|
||||
-- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds.
|
||||
--
|
||||
--
|
||||
-- # 5. Check goal achievement.
|
||||
--
|
||||
--
|
||||
-- The method @{#GOAL.IsAchieved}() will return true if the goal is achieved (the trigger **Achieved** was executed).
|
||||
-- You can use this method to check asynchronously if a goal has been achieved, for example using a scheduler.
|
||||
--
|
||||
--
|
||||
-- @field #GOAL
|
||||
GOAL = {
|
||||
ClassName = "GOAL",
|
||||
}
|
||||
|
||||
|
||||
--- @field #table GOAL.Players
|
||||
GOAL.Players = {}
|
||||
|
||||
--- @field #number GOAL.TotalContributions
|
||||
GOAL.TotalContributions = 0
|
||||
|
||||
|
||||
--- GOAL Constructor.
|
||||
-- @param #GOAL self
|
||||
-- @return #GOAL
|
||||
function GOAL:New()
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #GOAL
|
||||
self:F( {} )
|
||||
|
||||
@@ -104,11 +102,10 @@ do -- Goal
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
|
||||
|
||||
self:SetStartState( "Pending" )
|
||||
self:AddTransition( "*", "Achieved", "Achieved" )
|
||||
|
||||
self:AddTransition( "*", "Achieved", "Achieved" )
|
||||
|
||||
--- Achieved Handler OnBefore for GOAL
|
||||
-- @function [parent=#GOAL] OnBeforeAchieved
|
||||
-- @param #GOAL self
|
||||
@@ -116,47 +113,44 @@ do -- Goal
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #boolean
|
||||
|
||||
|
||||
--- Achieved Handler OnAfter for GOAL
|
||||
-- @function [parent=#GOAL] OnAfterAchieved
|
||||
-- @param #GOAL self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
|
||||
--- Achieved Trigger for GOAL
|
||||
-- @function [parent=#GOAL] Achieved
|
||||
-- @param #GOAL self
|
||||
|
||||
|
||||
--- Achieved Asynchronous Trigger for GOAL
|
||||
-- @function [parent=#GOAL] __Achieved
|
||||
-- @param #GOAL self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Add a new contribution by a player.
|
||||
-- @param #GOAL self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
function GOAL:AddPlayerContribution( PlayerName )
|
||||
self:F({PlayerName})
|
||||
self:F( { PlayerName } )
|
||||
self.Players[PlayerName] = self.Players[PlayerName] or 0
|
||||
self.Players[PlayerName] = self.Players[PlayerName] + 1
|
||||
self.TotalContributions = self.TotalContributions + 1
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #GOAL self
|
||||
-- @param #number Player contribution.
|
||||
function GOAL:GetPlayerContribution( PlayerName )
|
||||
return self.Players[PlayerName] or 0
|
||||
return self.Players[PlayerName] or 0
|
||||
end
|
||||
|
||||
|
||||
--- Get the players who contributed to achieve the goal.
|
||||
-- The result is a list of players, sorted by the name of the players.
|
||||
-- @param #GOAL self
|
||||
@@ -165,7 +159,6 @@ do -- Goal
|
||||
return self.Players or {}
|
||||
end
|
||||
|
||||
|
||||
--- Gets the total contributions that happened to achieve the goal.
|
||||
-- The result is a number.
|
||||
-- @param #GOAL self
|
||||
@@ -173,9 +166,7 @@ do -- Goal
|
||||
function GOAL:GetTotalContributions()
|
||||
return self.TotalContributions or 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Validates if the goal is achieved.
|
||||
-- @param #GOAL self
|
||||
-- @return #boolean true if the goal is achieved.
|
||||
@@ -183,4 +174,4 @@ do -- Goal
|
||||
return self:Is( "Achieved" )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
--- **Core** - MarkerOps_Base.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Create an easy way to tap into markers added to the F10 map by users.
|
||||
-- * Recognize own tag and list of keywords.
|
||||
-- * Matched keywords are handed down to functions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
-- @module Core.MarkerOps_Base
|
||||
-- @image MOOSE_Core.JPG
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
--- MARKEROPS_BASE class.
|
||||
-- @type MARKEROPS_BASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string Tag Tag to identify commands.
|
||||
-- @field #table Keywords Table of keywords to recognize.
|
||||
-- @field #string version Version of #MARKEROPS_BASE.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Fiat lux.* -- Latin proverb.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The MARKEROPS_BASE Concept
|
||||
--
|
||||
-- This class enable scripting text-based actions from markers.
|
||||
--
|
||||
-- @field #MARKEROPS_BASE
|
||||
MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.0.1",
|
||||
debug = false,
|
||||
}
|
||||
|
||||
--- Function to instantiate a new #MARKEROPS_BASE object.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Tagname Name to identify us from the event text.
|
||||
-- @param #table Keywords Table of keywords recognized from the event text.
|
||||
-- @return #MARKEROPS_BASE self
|
||||
function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||
-- Inherit FSM
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version))
|
||||
|
||||
self.Tag = Tagname or "mytag"-- #string
|
||||
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
|
||||
self.debug = false
|
||||
|
||||
-----------------------
|
||||
--- FSM Transitions ---
|
||||
-----------------------
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkAdded", "*") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkChanged", "*") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM.
|
||||
self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM.
|
||||
|
||||
self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark)
|
||||
self:HandleEvent(EVENTS.MarkChange, self.OnEventMark)
|
||||
self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark)
|
||||
|
||||
-- start
|
||||
self:I(self.lid..string.format("started for %s",self.Tag))
|
||||
self:__Start(1)
|
||||
return self
|
||||
|
||||
-------------------
|
||||
-- PSEUDO Functions
|
||||
-------------------
|
||||
|
||||
--- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
|
||||
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
|
||||
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
|
||||
--- "Stop" trigger. Used to stop the function an unhandle events
|
||||
-- @function [parent=#MARKEROPS_BASE] Stop
|
||||
|
||||
end
|
||||
|
||||
--- (internal) Handle events.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function MARKEROPS_BASE:OnEventMark(Event)
|
||||
self:T({Event})
|
||||
if Event == nil or Event.idx == nil then
|
||||
self:E("Skipping onEvent. Event or Event.idx unknown.")
|
||||
return true
|
||||
end
|
||||
--position
|
||||
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
|
||||
local coord=COORDINATE:NewFromVec3(vec3)
|
||||
if self.debug then
|
||||
local coordtext = coord:ToStringLLDDM()
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
-- decision
|
||||
if Event.id==world.event.S_EVENT_MARK_ADDED then
|
||||
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
|
||||
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Hande event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
self:MarkDeleted()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- (internal) Match tag.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #boolean
|
||||
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
local matches = false
|
||||
local type = string.lower(self.Tag) -- #string
|
||||
if string.find(string.lower(Eventtext),type) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
--- (internal) Match keywords table.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #table
|
||||
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
|
||||
local matchtable = {}
|
||||
local keytable = self.Keywords
|
||||
for _index,_word in pairs (keytable) do
|
||||
if string.find(string.lower(Eventtext),string.lower(_word))then
|
||||
table.insert(matchtable,_word)
|
||||
end
|
||||
end
|
||||
return matchtable
|
||||
end
|
||||
|
||||
--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
|
||||
self:T({self.lid,From,Event,To})
|
||||
end
|
||||
|
||||
--- On enter "Stopped" event. Unsubscribe events.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
function MARKEROPS_BASE:onenterStopped(From,Event,To)
|
||||
self:T({self.lid,From,Event,To})
|
||||
-- unsubscribe from events
|
||||
self:UnHandleEvent(EVENTS.MarkAdded)
|
||||
self:UnHandleEvent(EVENTS.MarkChange)
|
||||
self:UnHandleEvent(EVENTS.MarkRemoved)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition End.
|
||||
--------------------------------------------------------------------------
|
||||
@@ -1180,3 +1180,4 @@ do
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
--- **Core** - Informs the players using messages during a simulation.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * A more advanced messaging system using the DCS message system.
|
||||
-- * Time messages.
|
||||
-- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS.
|
||||
@@ -11,9 +11,10 @@
|
||||
-- * Send messages to a coalition.
|
||||
-- * Send messages to a specific group.
|
||||
-- * Send messages to a specific unit or client.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.Message
|
||||
-- @image Core_Message.JPG
|
||||
|
||||
@@ -24,14 +25,14 @@
|
||||
--- 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.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.ToClient}().
|
||||
@@ -41,26 +42,26 @@
|
||||
-- * 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.ToAllIf}().
|
||||
-- * To a coalition using @{#MESSAGE.ToCoalitionIf}().
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @field #MESSAGE
|
||||
MESSAGE = {
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
}
|
||||
|
||||
--- Message Types
|
||||
@@ -70,10 +71,9 @@ MESSAGE.Type = {
|
||||
Information = "Information",
|
||||
Briefing = "Briefing Report",
|
||||
Overview = "Overview Report",
|
||||
Detailed = "Detailed Report"
|
||||
Detailed = "Detailed Report",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
|
||||
-- @param self
|
||||
-- @param #string MessageText is the text of the Message.
|
||||
@@ -82,52 +82,52 @@ MESSAGE.Type = {
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Create a series of new Messages.
|
||||
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
|
||||
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
|
||||
-- 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")
|
||||
--
|
||||
-- -- Create a series of new Messages.
|
||||
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
|
||||
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
|
||||
-- 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, ClearScreen )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
|
||||
self.MessageType = nil
|
||||
|
||||
|
||||
-- When no MessageCategory is given, we don't show it as a title...
|
||||
if MessageCategory and MessageCategory ~= "" then
|
||||
if MessageCategory:sub(-1) ~= "\n" then
|
||||
if MessageCategory and MessageCategory ~= "" then
|
||||
if MessageCategory:sub( -1 ) ~= "\n" then
|
||||
self.MessageCategory = MessageCategory .. ": "
|
||||
else
|
||||
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
|
||||
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
|
||||
end
|
||||
else
|
||||
self.MessageCategory = ""
|
||||
end
|
||||
|
||||
self.ClearScreen=false
|
||||
if ClearScreen~=nil then
|
||||
self.ClearScreen=ClearScreen
|
||||
|
||||
self.ClearScreen = false
|
||||
if ClearScreen ~= nil then
|
||||
self.ClearScreen = ClearScreen
|
||||
end
|
||||
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
||||
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
return self
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new MESSAGE object of a certain type.
|
||||
-- Note that these MESSAGE objects are not yet displayed on the display panel.
|
||||
--- Creates a new MESSAGE object of a certain type.
|
||||
-- Note that these MESSAGE objects are not yet displayed on the display panel.
|
||||
-- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
|
||||
-- The message display times are automatically defined based on the timing settings in the @{Settings} menu.
|
||||
-- @param self
|
||||
@@ -136,65 +136,65 @@ end
|
||||
-- @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, ClearScreen )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText } )
|
||||
|
||||
|
||||
self.MessageType = MessageType
|
||||
|
||||
self.ClearScreen=false
|
||||
if ClearScreen~=nil then
|
||||
self.ClearScreen=ClearScreen
|
||||
|
||||
self.ClearScreen = false
|
||||
if ClearScreen ~= nil then
|
||||
self.ClearScreen = ClearScreen
|
||||
end
|
||||
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
||||
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
return self
|
||||
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.
|
||||
--- 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
|
||||
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".
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Client#CLIENT Client is the Group of the Client.
|
||||
-- @param Core.Settings#SETTINGS Settings Settings used to display the message.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send the 2 messages created with the @{New} method to the Client Group.
|
||||
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
|
||||
-- ClientGroup = Group.getByName( "ClientGroup" )
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
|
||||
-- MessageClient1:ToClient( ClientGroup )
|
||||
-- MessageClient2:ToClient( ClientGroup )
|
||||
-- -- Send the 2 messages created with the @{New} method to the Client Group.
|
||||
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
|
||||
-- ClientGroup = Group.getByName( "ClientGroup" )
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
|
||||
-- MessageClient1:ToClient( ClientGroup )
|
||||
-- MessageClient2:ToClient( ClientGroup )
|
||||
--
|
||||
function MESSAGE:ToClient( Client, Settings )
|
||||
self:F( Client )
|
||||
self:F( Client )
|
||||
|
||||
if Client and Client:GetClientGroupID() then
|
||||
if Client and Client:GetClientGroupID() then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
@@ -205,17 +205,17 @@ function MESSAGE:ToClient( Client, Settings )
|
||||
local Unit = Client:GetClient()
|
||||
|
||||
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 , self.ClearScreen)
|
||||
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
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 , self.ClearScreen)
|
||||
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Group.
|
||||
--- Sends a MESSAGE to a Group.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Group#GROUP Group to which the message is displayed.
|
||||
-- @return #MESSAGE Message object.
|
||||
@@ -223,19 +223,19 @@ function MESSAGE:ToGroup( Group, Settings )
|
||||
self:F( Group.GroupName )
|
||||
|
||||
if Group then
|
||||
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
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, self.ClearScreen )
|
||||
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, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -264,58 +264,64 @@ function MESSAGE:ToUnit( Unit, Settings )
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to the Blue coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send a message created with the @{New} method to the BLUE coalition.
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageBLUE:ToBlue()
|
||||
function MESSAGE:ToBlue()
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to the Red Coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToRed()
|
||||
function MESSAGE:ToRed( )
|
||||
self:F()
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the BLUE coalition.
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageBLUE:ToBlue()
|
||||
--
|
||||
function MESSAGE:ToBlue()
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition.
|
||||
--- Sends a MESSAGE to the Red Coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToRed()
|
||||
--
|
||||
function MESSAGE:ToRed()
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE Message object.
|
||||
-- @usage
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
--
|
||||
function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
self:F( CoalitionSide )
|
||||
self:F( CoalitionSide )
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
@@ -323,20 +329,20 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if CoalitionSide then
|
||||
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, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition if the given Condition is true.
|
||||
--- Sends a MESSAGE to a Coalition if the given Condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}.
|
||||
-- @param #boolean Condition Sends the message only if the condition is true.
|
||||
-- @param #boolean Condition Sends the message only if the condition is true.
|
||||
-- @return #MESSAGE self
|
||||
function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
|
||||
self:F( CoalitionSide )
|
||||
@@ -344,7 +350,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
|
||||
if Condition and Condition == true then
|
||||
self:ToCoalition( CoalitionSide )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -353,14 +359,16 @@ end
|
||||
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send a message created to all players.
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
|
||||
-- MessageAll:ToAll()
|
||||
function MESSAGE:ToAll(Settings)
|
||||
--
|
||||
-- -- Send a message created to all players.
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
|
||||
-- MessageAll:ToAll()
|
||||
--
|
||||
function MESSAGE:ToAll( Settings )
|
||||
self:F()
|
||||
|
||||
if self.MessageType then
|
||||
@@ -370,14 +378,13 @@ function MESSAGE:ToAll(Settings)
|
||||
end
|
||||
|
||||
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, self.ClearScreen )
|
||||
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, self.ClearScreen )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Sends a MESSAGE to all players if the given Condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
@@ -387,5 +394,5 @@ function MESSAGE:ToAllIf( Condition )
|
||||
self:ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2751,7 +2751,7 @@ do -- COORDINATE
|
||||
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings )
|
||||
end
|
||||
|
||||
--- Return a BRA string from a COORDINATE to the COORDINATE.
|
||||
--- Return a BRAA string from a COORDINATE to the COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
-- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object.
|
||||
@@ -2769,8 +2769,11 @@ do -- COORDINATE
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
-- @param #boolean Bogey Add "Bogey" at the end if true (not yet declared hostile or friendly)
|
||||
-- @param #boolean Spades Add "Spades" at the end if true (no IFF/VID ID yet known)
|
||||
-- @param #boolean SSML Add SSML tags speaking aspect as 0 1 2 and "brah" instead of BRAA
|
||||
-- @param #boolean Angels If true, altitude is e.g. "Angels 25" (i.e., a friendly plane), else "25 thousand"
|
||||
-- @param #boolean Zeros If using SSML, be aware that Google TTS will say "oh" and not "zero" for "0"; if Zeros is set to true, "0" will be replaced with "zero"
|
||||
-- @return #string The BRAA text.
|
||||
function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades)
|
||||
function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades,SSML,Angels,Zeros)
|
||||
|
||||
-- Thanks to @Pikey
|
||||
local BRAANATO = "Merged."
|
||||
@@ -2788,25 +2791,64 @@ do -- COORDINATE
|
||||
|
||||
local alt = UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)--*1000
|
||||
|
||||
local alttext = string.format("%d thousand",alt)
|
||||
|
||||
if Angels then
|
||||
alttext = string.format("Angels %d",alt)
|
||||
end
|
||||
|
||||
if alt < 1 then
|
||||
alttext = "very low"
|
||||
end
|
||||
|
||||
local track = UTILS.BearingToCardinal(bearing) or "North"
|
||||
|
||||
if rangeNM > 3 then
|
||||
if aspect == "" then
|
||||
BRAANATO = string.format("BRA, %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track)
|
||||
if SSML then -- google says "oh" instead of zero, be aware
|
||||
if Zeros then
|
||||
bearing = string.format("%03d",bearing)
|
||||
local AngleDegText = string.gsub(bearing,"%d","%1 ") -- "0 5 1 "
|
||||
AngleDegText = string.gsub(AngleDegText," $","") -- "0 5 1"
|
||||
AngleDegText = string.gsub(AngleDegText,"0","zero")
|
||||
if aspect == "" then
|
||||
BRAANATO = string.format("brah %s, %d miles, %s, Track %s", AngleDegText, rangeNM, alttext, track)
|
||||
else
|
||||
BRAANATO = string.format("brah %s, %d miles, %s, %s, Track %s", AngleDegText, rangeNM, alttext, aspect, track)
|
||||
end
|
||||
else
|
||||
if aspect == "" then
|
||||
BRAANATO = string.format("brah <say-as interpret-as='characters'>%03d</say-as>, %d miles, %s, Track %s", bearing, rangeNM, alttext, track)
|
||||
else
|
||||
BRAANATO = string.format("brah <say-as interpret-as='characters'>%03d</say-as>, %d miles, %s, %s, Track %s", bearing, rangeNM, alttext, aspect, track)
|
||||
end
|
||||
end
|
||||
if Bogey and Spades then
|
||||
BRAANATO = BRAANATO..", Bogey, Spades."
|
||||
elseif Bogey then
|
||||
BRAANATO = BRAANATO..", Bogey."
|
||||
elseif Spades then
|
||||
BRAANATO = BRAANATO..", Spades."
|
||||
else
|
||||
BRAANATO = BRAANATO.."."
|
||||
end
|
||||
else
|
||||
BRAANATO = string.format("BRAA, %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track)
|
||||
end
|
||||
if Bogey and Spades then
|
||||
BRAANATO = BRAANATO..", Bogey, Spades."
|
||||
elseif Bogey then
|
||||
BRAANATO = BRAANATO..", Bogey."
|
||||
elseif Spades then
|
||||
BRAANATO = BRAANATO..", Spades."
|
||||
else
|
||||
BRAANATO = BRAANATO.."."
|
||||
if aspect == "" then
|
||||
BRAANATO = string.format("BRA %03d, %d miles, %s, Track %s",bearing, rangeNM, alttext, track)
|
||||
else
|
||||
BRAANATO = string.format("BRAA %03d, %d miles, %s, %s, Track %s",bearing, rangeNM, alttext, aspect, track)
|
||||
end
|
||||
if Bogey and Spades then
|
||||
BRAANATO = BRAANATO..", Bogey, Spades."
|
||||
elseif Bogey then
|
||||
BRAANATO = BRAANATO..", Bogey."
|
||||
elseif Spades then
|
||||
BRAANATO = BRAANATO..", Spades."
|
||||
else
|
||||
BRAANATO = BRAANATO.."."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return BRAANATO
|
||||
end
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
--- **Core** - Provides a handy means to create messages and reports.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Create text blocks that are formatted.
|
||||
-- * Create automatic indents.
|
||||
-- * Variate the delimiters between reporting lines.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: FlightControl : Design & Programming
|
||||
@@ -15,7 +15,6 @@
|
||||
-- @module Core.Report
|
||||
-- @image Core_Report.JPG
|
||||
|
||||
|
||||
--- @type REPORT
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
@@ -36,7 +35,7 @@ function REPORT:New( Title )
|
||||
|
||||
self.Report = {}
|
||||
|
||||
self:SetTitle( Title or "" )
|
||||
self:SetTitle( Title or "" )
|
||||
self:SetIndent( 3 )
|
||||
|
||||
return self
|
||||
@@ -45,28 +44,26 @@ end
|
||||
--- Has the REPORT Text?
|
||||
-- @param #REPORT self
|
||||
-- @return #boolean
|
||||
function REPORT:HasText() --R2.1
|
||||
|
||||
function REPORT:HasText() -- R2.1
|
||||
|
||||
return #self.Report > 0
|
||||
end
|
||||
|
||||
|
||||
--- Set indent of a REPORT.
|
||||
-- @param #REPORT self
|
||||
-- @param #number Indent
|
||||
-- @return #REPORT
|
||||
function REPORT:SetIndent( Indent ) --R2.1
|
||||
function REPORT:SetIndent( Indent ) -- R2.1
|
||||
self.Indent = Indent
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add a new line to a REPORT.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Text
|
||||
-- @return #REPORT
|
||||
function REPORT:Add( Text )
|
||||
self.Report[#self.Report+1] = Text
|
||||
self.Report[#self.Report + 1] = Text
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -76,17 +73,17 @@ end
|
||||
-- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best.
|
||||
-- @return #REPORT
|
||||
function REPORT:AddIndent( Text, Separator )
|
||||
self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
|
||||
self.Report[#self.Report + 1] = ((Separator and Separator .. string.rep( " ", self.Indent - 1 )) or string.rep( " ", self.Indent )) .. Text:gsub( "\n", "\n" .. string.rep( " ", self.Indent ) )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
|
||||
--- Produces the text of the report, taking into account an optional delimiter, which is \n by default.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Delimiter (optional) A delimiter text.
|
||||
-- @return #string The report text.
|
||||
function REPORT:Text( Delimiter )
|
||||
Delimiter = Delimiter or "\n"
|
||||
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or ""
|
||||
local ReportText = (self.Title ~= "" and self.Title .. Delimiter or self.Title) .. table.concat( self.Report, Delimiter ) or ""
|
||||
return ReportText
|
||||
end
|
||||
|
||||
@@ -95,7 +92,7 @@ end
|
||||
-- @param #string Title The title of the report.
|
||||
-- @return #REPORT
|
||||
function REPORT:SetTitle( Title )
|
||||
self.Title = Title
|
||||
self.Title = Title
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
--- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects.
|
||||
--
|
||||
--
|
||||
-- This class is tricky and needs some thorough explanation.
|
||||
-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects.
|
||||
-- The SCHEDULEDISPATCHER class ensures that:
|
||||
--
|
||||
--
|
||||
-- - Scheduled functions are planned according the SCHEDULER object parameters.
|
||||
-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters.
|
||||
-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters.
|
||||
--
|
||||
--
|
||||
-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection:
|
||||
--
|
||||
--
|
||||
-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory.
|
||||
-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection!
|
||||
--
|
||||
-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected.
|
||||
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
|
||||
--
|
||||
-- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected.
|
||||
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
|
||||
-- these will not be executed anymore when the SCHEDULER object has been destroyed.
|
||||
--
|
||||
--
|
||||
-- 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 @{Core.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.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Contributions: -
|
||||
-- ### Authors: FlightControl : Design & Programming
|
||||
--
|
||||
--
|
||||
-- @module Core.ScheduleDispatcher
|
||||
-- @image Core_Schedule_Dispatcher.JPG
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number CallID Call ID counter.
|
||||
-- @field #table PersistentSchedulers Persistant schedulers.
|
||||
-- @field #table PersistentSchedulers Persistent schedulers.
|
||||
-- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists.
|
||||
-- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ).
|
||||
-- @extends Core.Base#BASE
|
||||
@@ -46,11 +46,11 @@
|
||||
--- The SCHEDULEDISPATCHER structure
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
SCHEDULEDISPATCHER = {
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
}
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
@@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = {
|
||||
-- @field #function Function The schedule function to be called.
|
||||
-- @field #table Arguments Schedule function arguments.
|
||||
-- @field #number Start Start time in seconds.
|
||||
-- @field #number Repeat Repeat time intervall in seconds.
|
||||
-- @field #number Repeat Repeat time interval in seconds.
|
||||
-- @field #number Randomize Randomization factor [0,1].
|
||||
-- @field #number Stop Stop time in seconds.
|
||||
-- @field #number StartTime Time in seconds when the scheduler is created.
|
||||
@@ -77,7 +77,7 @@ end
|
||||
|
||||
--- Add a Schedule to the ScheduleDispatcher.
|
||||
-- The development of this method was really tidy.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil.
|
||||
-- Nothing of this code should be modified without testing it thoroughly.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
@@ -85,7 +85,7 @@ end
|
||||
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
|
||||
-- @param #number Start Start time in seconds.
|
||||
-- @param #number Repeat Repeat interval in seconds.
|
||||
-- @param #number Randomize Radomization factor [0,1].
|
||||
-- @param #number Randomize Randomization factor [0,1].
|
||||
-- @param #number Stop Stop time in seconds.
|
||||
-- @param #number TraceLevel Trace level [0,3].
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
@@ -95,41 +95,38 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
-- Increase counter.
|
||||
self.CallID = self.CallID + 1
|
||||
|
||||
|
||||
-- Create ID.
|
||||
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
|
||||
|
||||
self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID))
|
||||
local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or ""
|
||||
|
||||
self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) )
|
||||
|
||||
-- Initialize PersistentSchedulers
|
||||
self.PersistentSchedulers = self.PersistentSchedulers or {}
|
||||
|
||||
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
|
||||
-- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
|
||||
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
||||
|
||||
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
||||
|
||||
if Scheduler.MasterObject then
|
||||
--env.info("FF Object Scheduler")
|
||||
self.ObjectSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } )
|
||||
else
|
||||
--env.info("FF Persistent Scheduler")
|
||||
self.PersistentSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
|
||||
end
|
||||
|
||||
|
||||
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
|
||||
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
|
||||
self.Schedule[Scheduler][CallID] = {} --#SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
|
||||
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.001
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + (Start or 0)
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.1
|
||||
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
|
||||
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
|
||||
self.Schedule[Scheduler][CallID].Stop = Stop
|
||||
|
||||
|
||||
|
||||
-- This section handles the tracing of the scheduled calls.
|
||||
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
|
||||
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
|
||||
@@ -151,10 +148,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
-- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result.
|
||||
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
|
||||
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
|
||||
-- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often.
|
||||
-- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often.
|
||||
|
||||
local Info = {}
|
||||
|
||||
|
||||
if debug then
|
||||
TraceLevel = TraceLevel or 2
|
||||
Info = debug.getinfo( TraceLevel, "nlS" )
|
||||
@@ -168,7 +165,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
--- Function passed to the DCS timer.scheduleFunction()
|
||||
self.Schedule[Scheduler][CallID].CallHandler = function( Params )
|
||||
|
||||
|
||||
local CallID = Params.CallID
|
||||
local Info = Params.Info or {}
|
||||
local Source = Info.source or "?"
|
||||
@@ -182,27 +179,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
end
|
||||
return errmsg
|
||||
end
|
||||
|
||||
-- Get object or persistant scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER
|
||||
|
||||
-- Get object or persistent scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER
|
||||
if not Scheduler then
|
||||
Scheduler = self.PersistentSchedulers[CallID]
|
||||
end
|
||||
|
||||
--self:T3( { Scheduler = Scheduler } )
|
||||
|
||||
|
||||
-- self:T3( { Scheduler = Scheduler } )
|
||||
|
||||
if Scheduler then
|
||||
|
||||
local MasterObject = tostring(Scheduler.MasterObject)
|
||||
|
||||
-- Schedule object.
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
--self:T3( { Schedule = Schedule } )
|
||||
local MasterObject = tostring( Scheduler.MasterObject )
|
||||
|
||||
local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object?
|
||||
-- Schedule object.
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- self:T3( { Schedule = Schedule } )
|
||||
|
||||
local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object?
|
||||
local ShowTrace = Scheduler.ShowTrace
|
||||
|
||||
|
||||
local ScheduleFunction = Schedule.Function
|
||||
local ScheduleArguments = Schedule.Arguments or {}
|
||||
local Start = Schedule.Start
|
||||
@@ -210,19 +207,17 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
local Randomize = Schedule.Randomize or 0
|
||||
local Stop = Schedule.Stop or 0
|
||||
local ScheduleID = Schedule.ScheduleID
|
||||
|
||||
|
||||
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
|
||||
|
||||
|
||||
local Prefix = (Repeat == 0) and "--->" or "+++>"
|
||||
|
||||
local Status, Result
|
||||
--self:E( { SchedulerObject = SchedulerObject } )
|
||||
-- self:E( { SchedulerObject = SchedulerObject } )
|
||||
if SchedulerObject then
|
||||
local function Timer()
|
||||
if ShowTrace then
|
||||
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
-- The master object is passed as first parameter. A few :Schedule() calls in MOOSE expect this currently. But in principle it should be removed.
|
||||
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
|
||||
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
else
|
||||
@@ -230,40 +225,39 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
if ShowTrace then
|
||||
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
end
|
||||
|
||||
|
||||
local CurrentTime = timer.getTime()
|
||||
local StartTime = Schedule.StartTime
|
||||
|
||||
-- Debug info.
|
||||
self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
|
||||
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
|
||||
|
||||
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy
|
||||
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
self:F3( { CallID = CallID, ScheduleID = ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
if Status and ((Result == nil) or (Result and Result ~= false)) then
|
||||
|
||||
if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random( -(Randomize * Repeat / 2), (Randomize * Repeat / 2) ) + 0.0001 -- Accuracy
|
||||
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
return ScheduleTime -- returns the next time the function needs to be called.
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
end
|
||||
else
|
||||
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
self:Start( Scheduler, CallID, Info )
|
||||
|
||||
|
||||
return CallID
|
||||
end
|
||||
|
||||
@@ -287,67 +281,67 @@ end
|
||||
-- @param #string Info (Optional) Debug info.
|
||||
function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
|
||||
self:F2( { Start = CallID, Scheduler = Scheduler } )
|
||||
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only start when there is no ScheduleID defined!
|
||||
-- This prevents to "Start" the scheduler twice with the same CallID...
|
||||
if not Schedule.ScheduleID then
|
||||
|
||||
|
||||
-- Current time in seconds.
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
local Tnow = timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
-- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction
|
||||
Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start)
|
||||
|
||||
self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start )
|
||||
|
||||
self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
-- Recursive.
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Start( Scheduler, CallID, Info ) -- Recursive
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- Stop dispatcher.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
-- @param #string CallID (Optional) Scheduler Call ID. If nil, all pending schedules are stopped recursively.
|
||||
-- @param #table CallID Call ID.
|
||||
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
|
||||
self:F2( { Stop = CallID, Scheduler = Scheduler } )
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
|
||||
if Schedule.ScheduleID then
|
||||
|
||||
self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
|
||||
|
||||
self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
|
||||
-- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
|
||||
timer.removeFunction(Schedule.ScheduleID)
|
||||
|
||||
timer.removeFunction( Schedule.ScheduleID )
|
||||
|
||||
Schedule.ScheduleID = nil
|
||||
|
||||
|
||||
else
|
||||
self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID)))
|
||||
self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) )
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Stop( Scheduler, CallID ) -- Recursive
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -362,7 +356,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
|
||||
end
|
||||
end
|
||||
|
||||
--- Shopw tracing info.
|
||||
--- Show tracing info.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
--- **Core** - Prepares and handles the execution of functions over scheduled time (intervals).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Schedule functions over time,
|
||||
-- * optionally in an optional specified time interval,
|
||||
-- * optionally **repeating** with a specified time repeat interval,
|
||||
-- * optionally **randomizing** with a specified time interval randomization factor,
|
||||
-- * optionally **stop** the repeating after a specified time interval.
|
||||
-- * optionally in an optional specified time interval,
|
||||
-- * optionally **repeating** with a specified time repeat interval,
|
||||
-- * optionally **randomizing** with a specified time interval randomization factor,
|
||||
-- * optionally **stop** the repeating after a specified time interval.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SCH%20-%20Scheduler)
|
||||
--
|
||||
--
|
||||
-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
|
||||
--
|
||||
-- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
|
||||
--
|
||||
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ### [SCHEDULER YouTube Channel (none)]()
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ### [SCHEDULER YouTube Channel (none)]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * FlightControl : Concept & Testing
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
-- * FlightControl : Design & Programming
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.Scheduler
|
||||
@@ -48,62 +48,61 @@
|
||||
-- @field #boolean ShowTrace Trace info if true.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- 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.
|
||||
-- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(),
|
||||
-- which can start and stop specific repeating schedules respectively within a SCHEDULER object.
|
||||
--
|
||||
-- ## SCHEDULER constructor
|
||||
--
|
||||
--
|
||||
-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters:
|
||||
--
|
||||
--
|
||||
-- The @{#SCHEDULER.New}() method returns 2 variables:
|
||||
--
|
||||
--
|
||||
-- 1. The SCHEDULER object reference.
|
||||
-- 2. The first schedule planned in the SCHEDULER object.
|
||||
--
|
||||
--
|
||||
-- To clarify the different appliances, lets have a look at the following examples:
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a persistent schedule.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
|
||||
--
|
||||
--
|
||||
-- MasterObject = SCHEDULER:New()
|
||||
-- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, but does not schedule anything.
|
||||
-- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence...
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE.
|
||||
-- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID
|
||||
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
|
||||
-- As a result, the MasterObject will cancel any planned schedule.
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object with a persistent schedule.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
|
||||
--
|
||||
--
|
||||
-- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence...
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
@@ -112,13 +111,13 @@
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction),
|
||||
-- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject).
|
||||
-- Both a MasterObject and a SchedulerID variable are returned.
|
||||
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
|
||||
-- As a result, the MasterObject will cancel the planned schedule.
|
||||
--
|
||||
--
|
||||
-- ## SCHEDULER timer stopping and (re-)starting.
|
||||
--
|
||||
-- The SCHEDULER can be stopped and restarted with the following methods:
|
||||
@@ -133,70 +132,70 @@
|
||||
-- MasterObject:Stop( SchedulerID )
|
||||
-- ...
|
||||
-- MasterObject:Start( SchedulerID )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- ## Create a new schedule
|
||||
--
|
||||
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
|
||||
--
|
||||
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
|
||||
-- This method is used by the :New() constructor when a new schedule is planned.
|
||||
--
|
||||
--
|
||||
-- Consider the following code fragment of the SCHEDULER object creation.
|
||||
--
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
--
|
||||
-- Several parameters can be specified that influence the behaviour of a Schedule.
|
||||
--
|
||||
--
|
||||
-- Several parameters can be specified that influence the behavior of a Schedule.
|
||||
--
|
||||
-- ### A single schedule, immediately executed
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ...
|
||||
--
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ...
|
||||
--
|
||||
-- ### A single schedule, planned over time
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
|
||||
--
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ...
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- and repeating 60 every seconds ...
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- and repeating 60 seconds, with a 50% time interval randomization ...
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- which is in this example between **30** and **90** seconds.
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- The schedule will repeat every 60 seconds.
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- which is in this example between **30** and **90** seconds.
|
||||
-- The schedule will stop after **300** seconds.
|
||||
--
|
||||
--
|
||||
-- @field #SCHEDULER
|
||||
SCHEDULER = {
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
}
|
||||
|
||||
--- SCHEDULER constructor.
|
||||
@@ -211,15 +210,15 @@ SCHEDULER = {
|
||||
-- @return #SCHEDULER self.
|
||||
-- @return #table The ScheduleID of the planned schedule.
|
||||
function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
|
||||
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
local ScheduleID = nil
|
||||
|
||||
|
||||
self.MasterObject = MasterObject
|
||||
self.ShowTrace = false
|
||||
|
||||
|
||||
if SchedulerFunction then
|
||||
ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 )
|
||||
end
|
||||
@@ -235,48 +234,47 @@ end
|
||||
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
|
||||
-- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function.
|
||||
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stoppe.
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stopped.
|
||||
-- @param #number TraceLevel Trace level [0,3]. Default 3.
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
-- @return #string The Schedule ID of the planned schedule.
|
||||
-- @return #table The ScheduleID of the planned schedule.
|
||||
function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
|
||||
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
|
||||
self:T3( { SchedulerArguments } )
|
||||
|
||||
-- Debug info.
|
||||
local ObjectName = "-"
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
ObjectName = MasterObject.ClassName .. MasterObject.ClassID
|
||||
end
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
-- Set master object.
|
||||
self.MasterObject = MasterObject
|
||||
|
||||
|
||||
-- Add schedule.
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules+1] = ScheduleID
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules + 1] = ScheduleID
|
||||
|
||||
return ScheduleID
|
||||
end
|
||||
|
||||
--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided.
|
||||
-- @param #SCHEDULER self
|
||||
-- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule.
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Start( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:Start( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -285,7 +283,7 @@ end
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Stop( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:Stop( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -294,15 +292,15 @@ end
|
||||
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Remove( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID )
|
||||
end
|
||||
|
||||
--- Clears all pending schedules.
|
||||
-- @param #SCHEDULER self
|
||||
function SCHEDULER:Clear()
|
||||
self:F3( )
|
||||
self:T(string.format("Clearing scheduler"))
|
||||
self:F3()
|
||||
self:T( string.format( "Clearing scheduler" ) )
|
||||
_SCHEDULEDISPATCHER:Clear( self )
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,15 +29,14 @@
|
||||
-- @module Core.Settings
|
||||
-- @image Core_Settings.JPG
|
||||
|
||||
|
||||
--- @type SETTINGS
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- 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 behavior of certain functionalities and classes within the MOOSE framework.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
-- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
-- SETTINGS can work on 2 levels:
|
||||
--
|
||||
-- - **Default settings**: A running mission has **Default settings**.
|
||||
@@ -59,7 +58,7 @@
|
||||
--
|
||||
-- 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.
|
||||
-- Note that the **Default settings** will only be used when a player has not chosen its own settings.
|
||||
--
|
||||
-- ## 2.2) Player settings menu
|
||||
--
|
||||
@@ -69,7 +68,7 @@
|
||||
--
|
||||
-- ## 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**.
|
||||
-- Of course, it may be required 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.
|
||||
-- Note that when this method is used, any player already in a slot will not have its menus visibility changed.
|
||||
-- The option will only have effect when a player enters a new slot or changes a slot.
|
||||
@@ -94,8 +93,8 @@
|
||||
--
|
||||
-- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)).
|
||||
-- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
|
||||
-- - 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.
|
||||
-- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
--
|
||||
-- ### 3.1.2) A2G coordinates setting **menu**
|
||||
--
|
||||
@@ -183,7 +182,7 @@
|
||||
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
|
||||
--
|
||||
-- Each Message Type has specific timings that will be applied when the message is displayed.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen.
|
||||
-- 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!
|
||||
--
|
||||
@@ -196,7 +195,7 @@
|
||||
--
|
||||
-- ## 3.5) **Era** of the battle
|
||||
--
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare.
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare.
|
||||
-- Therefore, there are 4 era that are defined within the settings:
|
||||
--
|
||||
-- - **WWII** era: Use for warfare with equipment during the world war II time.
|
||||
@@ -213,8 +212,8 @@
|
||||
SETTINGS = {
|
||||
ClassName = "SETTINGS",
|
||||
ShowPlayerMenu = true,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
}
|
||||
|
||||
SETTINGS.__Enum = {}
|
||||
@@ -231,7 +230,6 @@ SETTINGS.__Enum.Era = {
|
||||
Modern = 4,
|
||||
}
|
||||
|
||||
|
||||
do -- SETTINGS
|
||||
|
||||
--- SETTINGS constructor.
|
||||
@@ -253,7 +251,6 @@ do -- SETTINGS
|
||||
self:SetMessageTime( MESSAGE.Type.Overview, 60 )
|
||||
self:SetMessageTime( MESSAGE.Type.Update, 15 )
|
||||
self:SetEraModern()
|
||||
self:SetLocale("en")
|
||||
return self
|
||||
else
|
||||
local Settings = _DATABASE:GetPlayerSettings( PlayerName )
|
||||
@@ -269,14 +266,14 @@ do -- SETTINGS
|
||||
-- Short text are better suited for, e.g., VR.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default).
|
||||
function SETTINGS:SetMenutextShort(onoff)
|
||||
function SETTINGS:SetMenutextShort( onoff )
|
||||
_SETTINGS.MenuShort = onoff
|
||||
end
|
||||
|
||||
--- Set menu to be static.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default).
|
||||
function SETTINGS:SetMenuStatic(onoff)
|
||||
function SETTINGS:SetMenuStatic( onoff )
|
||||
_SETTINGS.MenuStatic = onoff
|
||||
end
|
||||
|
||||
@@ -285,26 +282,12 @@ do -- SETTINGS
|
||||
function SETTINGS:SetMetric()
|
||||
self.Metric = true
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS default text locale.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string Locale
|
||||
function SETTINGS:SetLocale(Locale)
|
||||
self.Locale = Locale or "en"
|
||||
end
|
||||
|
||||
--- Gets the SETTINGS text locale.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #string
|
||||
function SETTINGS:GetLocale()
|
||||
return self.Locale or _SETTINGS:GetLocale()
|
||||
end
|
||||
|
||||
|
||||
--- Gets if the SETTINGS is metric.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if metric.
|
||||
function SETTINGS:IsMetric()
|
||||
return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric())
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS imperial.
|
||||
@@ -317,7 +300,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if imperial.
|
||||
function SETTINGS:IsImperial()
|
||||
return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsMetric())
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS LL accuracy.
|
||||
@@ -359,13 +342,12 @@ do -- SETTINGS
|
||||
self.MessageTypeTimings[MessageType] = MessageTime
|
||||
end
|
||||
|
||||
|
||||
--- Gets the SETTINGS Message Display Timing of a MessageType
|
||||
-- @param #SETTINGS self
|
||||
-- @param Core.Message#MESSAGE MessageType The type of the message.
|
||||
-- @return #number
|
||||
function SETTINGS:GetMessageTime( MessageType )
|
||||
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType )
|
||||
return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType )
|
||||
end
|
||||
|
||||
--- Sets A2G LL DMS
|
||||
@@ -386,14 +368,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2G_LL_DMS()
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() )
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2G_LL_DDM()
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() )
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
|
||||
end
|
||||
|
||||
--- Sets A2G MGRS
|
||||
@@ -407,7 +389,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2G_MGRS()
|
||||
return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() )
|
||||
return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
|
||||
end
|
||||
|
||||
--- Sets A2G BRA
|
||||
@@ -421,7 +403,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2G_BR()
|
||||
return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() )
|
||||
return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR())
|
||||
end
|
||||
|
||||
--- Sets A2A BRA
|
||||
@@ -435,7 +417,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2A_BRAA()
|
||||
return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() )
|
||||
return (self.A2ASystem and self.A2ASystem == "BRAA") or (not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
|
||||
end
|
||||
|
||||
--- Sets A2A BULLS
|
||||
@@ -449,7 +431,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BULLS
|
||||
function SETTINGS:IsA2A_BULLS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
|
||||
end
|
||||
|
||||
--- Sets A2A LL DMS
|
||||
@@ -470,14 +452,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2A_LL_DMS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2A_LL_DDM()
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() )
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
|
||||
end
|
||||
|
||||
--- Sets A2A MGRS
|
||||
@@ -491,7 +473,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2A_MGRS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
@@ -510,37 +492,37 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
-------
|
||||
|
||||
local text="A2G Coordinate System"
|
||||
local text = "A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G Coordinates"
|
||||
text = "A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
-- Set LL DMS
|
||||
if not self:IsA2G_LL_DMS() then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DMS"
|
||||
text = "LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DDM
|
||||
if not self:IsA2G_LL_DDM() then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DDM"
|
||||
text = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DMS accuracy.
|
||||
if self:IsA2G_LL_DDM() then
|
||||
local text1="LL DDM Accuracy 1"
|
||||
local text2="LL DDM Accuracy 2"
|
||||
local text3="LL DDM Accuracy 3"
|
||||
local text1 = "LL DDM Accuracy 1"
|
||||
local text2 = "LL DDM Accuracy 2"
|
||||
local text3 = "LL DDM Accuracy 3"
|
||||
if _SETTINGS.MenuShort then
|
||||
text1="LL DDM"
|
||||
text1 = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
|
||||
@@ -549,18 +531,18 @@ do -- SETTINGS
|
||||
|
||||
-- Set BR.
|
||||
if not self:IsA2G_BR() then
|
||||
local text="Bearing, Range (BR)"
|
||||
local text = "Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="BR"
|
||||
text = "BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set MGRS.
|
||||
if not self:IsA2G_MGRS() then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="MGRS"
|
||||
text = "MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -578,24 +560,24 @@ do -- SETTINGS
|
||||
-- A2A Coordinate System
|
||||
-------
|
||||
|
||||
local text="A2A Coordinate System"
|
||||
local text = "A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A Coordinates"
|
||||
text = "A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if not self:IsA2A_LL_DMS() then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DMS"
|
||||
text = "LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DDM"
|
||||
text = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -608,25 +590,25 @@ do -- SETTINGS
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() then
|
||||
local text="Bullseye (BULLS)"
|
||||
local text = "Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Bulls"
|
||||
text = "Bulls"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() then
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="BRAA"
|
||||
text = "BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="MGRS"
|
||||
text = "MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -639,31 +621,31 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text="Measures and Weights System"
|
||||
local text = "Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Unit System"
|
||||
text = "Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if self:IsMetric() then
|
||||
local text="Imperial (Miles,Feet)"
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Imperial"
|
||||
text = "Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if self:IsImperial() then
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Metric"
|
||||
text = "Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text="Messages and Reports"
|
||||
local text = "Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Messages & Reports"
|
||||
text = "Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
@@ -704,7 +686,6 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
|
||||
|
||||
|
||||
SettingsMenu:Remove( MenuTime )
|
||||
|
||||
return self
|
||||
@@ -748,11 +729,11 @@ do -- SETTINGS
|
||||
|
||||
self.PlayerMenu = PlayerMenu
|
||||
|
||||
self:I(string.format("Setting menu for player %s", tostring(PlayerName)))
|
||||
self:I( string.format( "Setting menu for player %s", tostring( PlayerName ) ) )
|
||||
|
||||
local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
|
||||
@@ -769,40 +750,40 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
------
|
||||
|
||||
local text="A2G Coordinate System"
|
||||
local text = "A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G Coordinates"
|
||||
text = "A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G LL DMS"
|
||||
text = "A2G LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G LL DDM"
|
||||
text = "A2G LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_BR() or _SETTINGS.MenuStatic then
|
||||
local text="Bearing, Range (BR)"
|
||||
local text = "Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G BR"
|
||||
text = "A2G BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G MGRS"
|
||||
text = "A2G MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -811,49 +792,48 @@ do -- SETTINGS
|
||||
-- A2A Coordinates Menu
|
||||
------
|
||||
|
||||
local text="A2A Coordinate System"
|
||||
local text = "A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A Coordinates"
|
||||
text = "A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
|
||||
if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A LL DMS"
|
||||
text = "A2A LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A LL DDM"
|
||||
text = "A2A LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then
|
||||
local text="Bullseye (BULLS)"
|
||||
local text = "Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A BULLS"
|
||||
text = "A2A BULLS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A BRAA"
|
||||
text = "A2A BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A MGRS"
|
||||
text = "A2A MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -862,24 +842,24 @@ do -- SETTINGS
|
||||
-- Unit system
|
||||
---
|
||||
|
||||
local text="Measures and Weights System"
|
||||
local text = "Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Unit System"
|
||||
text = "Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if self:IsMetric() or _SETTINGS.MenuStatic then
|
||||
local text="Imperial (Miles,Feet)"
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Imperial"
|
||||
text = "Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
|
||||
end
|
||||
|
||||
if self:IsImperial() or _SETTINGS.MenuStatic then
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Metric"
|
||||
text = "Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
|
||||
end
|
||||
@@ -888,9 +868,9 @@ do -- SETTINGS
|
||||
-- Messages and Reports
|
||||
---
|
||||
|
||||
local text="Messages and Reports"
|
||||
local text = "Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Messages & Reports"
|
||||
text = "Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
@@ -950,39 +930,38 @@ do -- SETTINGS
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
@@ -995,12 +974,12 @@ do -- SETTINGS
|
||||
do
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
|
||||
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
|
||||
BASE:E( { self, PlayerUnit:GetName(), A2GSystem } )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1008,9 +987,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1018,9 +997,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1028,9 +1007,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1038,9 +1017,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1070,7 +1049,6 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Cold war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1080,7 +1058,6 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Modern war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1090,7 +1067,4 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
+1479
-1513
File diff suppressed because it is too large
Load Diff
@@ -1,202 +0,0 @@
|
||||
--- **Core** - TEXTANDSOUND (MOOSE gettext) system
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * A GetText for Moose
|
||||
-- * Build a set of localized text entries, alongside their sounds and subtitles
|
||||
-- * Aimed at class developers to offer localizable language support
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- ## Date: April 2022
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.TextAndSound
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- Text and Sound class.
|
||||
-- @type TEXTANDSOUND
|
||||
-- @field #string ClassName Name of this class.
|
||||
-- @field #string version Versioning.
|
||||
-- @field #string lid LID for log entries.
|
||||
-- @field #string locale Default locale of this object.
|
||||
-- @field #table entries Table of entries.
|
||||
-- @field #string textclass Name of the class the texts belong to.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
---
|
||||
--
|
||||
-- @field #TEXTANDSOUND
|
||||
TEXTANDSOUND = {
|
||||
ClassName = "TEXTANDSOUND",
|
||||
version = "0.0.1",
|
||||
lid = "",
|
||||
locale = "en",
|
||||
entries = {},
|
||||
textclass = "",
|
||||
}
|
||||
|
||||
--- Text and Sound entry.
|
||||
-- @type TEXTANDSOUND.Entry
|
||||
-- @field #string Classname Name of the class this entry is for.
|
||||
-- @field #string Locale Locale of this entry, defaults to "en".
|
||||
-- @field #table Data The list of entries.
|
||||
|
||||
--- Text and Sound data
|
||||
-- @type TEXTANDSOUND.Data
|
||||
-- @field #string ID ID of this entry for retrieval.
|
||||
-- @field #string Text Text of this entry.
|
||||
-- @field #string Soundfile (optional) Soundfile File name of the corresponding sound file.
|
||||
-- @field #number Soundlength (optional) Length of the sound file in seconds.
|
||||
-- @field #string Subtitle (optional) Subtitle for the sound file.
|
||||
|
||||
--- Instantiate a new object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string ClassName Name of the class this instance is providing texts for.
|
||||
-- @param #string Defaultlocale (Optional) Default locale of this instance, defaults to "en".
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:New(ClassName,Defaultlocale)
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New())
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("%s (%s) | ", self.ClassName, self.version)
|
||||
self.locale = Defaultlocale or (_SETTINGS:GetLocale() or "en")
|
||||
self.textclass = ClassName or "none"
|
||||
self.entries = {}
|
||||
local initentry = {} -- #TEXTANDSOUND.Entry
|
||||
initentry.Classname = ClassName
|
||||
initentry.Data = {}
|
||||
initentry.Locale = self.locale
|
||||
self.entries[self.locale] = initentry
|
||||
self:I(self.lid .. "Instantiated.")
|
||||
self:T({self.entries[self.locale]})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an entry
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string Locale Locale to set for this entry, e.g. "de".
|
||||
-- @param #string ID Unique(!) ID of this entry under this locale (i.e. use the same ID to get localized text for the entry in another language).
|
||||
-- @param #string Text Text for this entry.
|
||||
-- @param #string Soundfile (Optional) Sound file name for this entry.
|
||||
-- @param #number Soundlength (Optional) Length of the sound file in seconds.
|
||||
-- @param #string Subtitle (Optional) Subtitle to be used alongside the sound file.
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle)
|
||||
self:T(self.lid .. "AddEntry")
|
||||
local locale = Locale or self.locale
|
||||
local dataentry = {} -- #TEXTANDSOUND.Data
|
||||
dataentry.ID = ID or "1"
|
||||
dataentry.Text = Text or "none"
|
||||
dataentry.Soundfile = Soundfile
|
||||
dataentry.Soundlength = Soundlength or 0
|
||||
dataentry.Subtitle = Subtitle
|
||||
if not self.entries[locale] then
|
||||
local initentry = {} -- #TEXTANDSOUND.Entry
|
||||
initentry.Classname = self.textclass -- class name entry
|
||||
initentry.Data = {} -- data array
|
||||
initentry.Locale = locale -- default locale
|
||||
self.entries[locale] = initentry
|
||||
end
|
||||
self.entries[locale].Data[ID] = dataentry
|
||||
self:T({self.entries[locale].Data})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get an entry
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string ID The unique ID of the data to be retrieved.
|
||||
-- @param #string Locale (Optional) The locale of the text to be retrieved - defauls to default locale set with `New()`.
|
||||
-- @return #string Text Text or nil if not found and no fallback.
|
||||
-- @return #string Soundfile Filename or nil if not found and no fallback.
|
||||
-- @return #string Soundlength Length of the sound or 0 if not found and no fallback.
|
||||
-- @return #string Subtitle Text for subtitle or nil if not found and no fallback.
|
||||
function TEXTANDSOUND:GetEntry(ID,Locale)
|
||||
self:T(self.lid .. "GetEntry")
|
||||
local locale = Locale or self.locale
|
||||
if not self.entries[locale] then
|
||||
-- fall back to default "en"
|
||||
locale = self.locale
|
||||
end
|
||||
local Text,Soundfile,Soundlength,Subtitle = nil, nil, 0, nil
|
||||
if self.entries[locale] then
|
||||
if self.entries[locale].Data then
|
||||
local data = self.entries[locale].Data[ID] -- #TEXTANDSOUND.Data
|
||||
if data then
|
||||
Text = data.Text
|
||||
Soundfile = data.Soundfile
|
||||
Soundlength = data.Soundlength
|
||||
Subtitle = data.Subtitle
|
||||
elseif self.entries[self.locale].Data[ID] then
|
||||
-- no matching entry, try default
|
||||
local data = self.entries[self.locale].Data[ID]
|
||||
Text = data.Text
|
||||
Soundfile = data.Soundfile
|
||||
Soundlength = data.Soundlength
|
||||
Subtitle = data.Subtitle
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil, nil, 0, nil
|
||||
end
|
||||
return Text,Soundfile,Soundlength,Subtitle
|
||||
end
|
||||
|
||||
--- Get the default locale of this object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #string locale
|
||||
function TEXTANDSOUND:GetDefaultLocale()
|
||||
self:T(self.lid .. "GetDefaultLocale")
|
||||
return self.locale
|
||||
end
|
||||
|
||||
--- Set default locale of this object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string locale
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:SetDefaultLocale(locale)
|
||||
self:T(self.lid .. "SetDefaultLocale")
|
||||
self.locale = locale or "en"
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a locale exists
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #boolean outcome
|
||||
function TEXTANDSOUND:HasLocale(Locale)
|
||||
self:T(self.lid .. "HasLocale")
|
||||
return self.entries[Locale] and true or false
|
||||
end
|
||||
|
||||
--- Flush all entries to the log
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:FlushToLog()
|
||||
self:I(self.lid .. "Flushing entries:")
|
||||
local text = string.format("Textclass: %s | Default Locale: %s",self.textclass, self.locale)
|
||||
for _,_entry in pairs(self.entries) do
|
||||
local entry = _entry -- #TEXTANDSOUND.Entry
|
||||
local text = string.format("Textclassname: %s | Locale: %s",entry.Classname, entry.Locale)
|
||||
self:I(text)
|
||||
for _ID,_data in pairs(entry.Data) do
|
||||
local data = _data -- #TEXTANDSOUND.Data
|
||||
local text = string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID), data.Text or "none",data.Soundfile or "none",data.Soundlength or 0,data.Subtitle or "none")
|
||||
self:I(text)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- End TextAndSound
|
||||
----------------------------------------------------------------
|
||||
@@ -34,6 +34,8 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The TIMER Concept
|
||||
--
|
||||
-- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases.
|
||||
@@ -105,17 +107,19 @@ TIMER = {
|
||||
--- Timer ID.
|
||||
_TIMERID=0
|
||||
|
||||
--- Timer data base.
|
||||
--_TIMERDB={}
|
||||
|
||||
--- TIMER class version.
|
||||
-- @field #string version
|
||||
TIMER.version="0.1.2"
|
||||
TIMER.version="0.1.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Randomization.
|
||||
-- TODO: Pause/unpause.
|
||||
-- DONE: Write docs.
|
||||
-- TODO: A lot.
|
||||
-- TODO: Write docs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -152,6 +156,9 @@ function TIMER:New(Function, ...)
|
||||
-- Log id.
|
||||
self.lid=string.format("TIMER UID=%d | ", self.uid)
|
||||
|
||||
-- Add to DB.
|
||||
--_TIMERDB[self.uid]=self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -212,7 +219,10 @@ function TIMER:Stop(Delay)
|
||||
|
||||
-- Not running any more.
|
||||
self.isrunning=false
|
||||
|
||||
|
||||
-- Remove DB entry.
|
||||
--_TIMERDB[self.uid]=nil
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -229,15 +239,6 @@ function TIMER:SetMaxFunctionCalls(Nmax)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set time interval. Can also be set when the timer is already running and is applied after the next function call.
|
||||
-- @param #TIMER self
|
||||
-- @param #number dT Time interval in seconds.
|
||||
-- @return #TIMER self
|
||||
function TIMER:SetTimeInterval(dT)
|
||||
self.dT=dT
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if the timer has been started and was not stopped.
|
||||
-- @param #TIMER self
|
||||
-- @return #boolean If `true`, the timer is running.
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
-- @field #number DrawID Unique ID of the drawn zone on the F10 map.
|
||||
-- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
|
||||
-- @field #number ZoneID ID of zone. Only zones defined in the ME have an ID!
|
||||
-- @field #number Surface Type of surface. Only determined at the center of the zone!
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -112,7 +111,6 @@ ZONE_BASE = {
|
||||
DrawID=nil,
|
||||
Color={},
|
||||
ZoneID=nil,
|
||||
Sureface=nil,
|
||||
}
|
||||
|
||||
|
||||
@@ -338,22 +336,15 @@ end
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #nil The bounding square.
|
||||
function ZONE_BASE:GetBoundingSquare()
|
||||
--return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 }
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get surface type of the zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return DCS#SurfaceType Type of surface.
|
||||
function ZONE_BASE:GetSurfaceType()
|
||||
local coord=self:GetCoordinate()
|
||||
local surface=coord:GetSurfaceType()
|
||||
return surface
|
||||
end
|
||||
|
||||
--- Bound the zone boundaries with a tires.
|
||||
-- @param #ZONE_BASE self
|
||||
function ZONE_BASE:BoundZone()
|
||||
self:F2()
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1166,9 +1157,7 @@ end
|
||||
-- @return #boolean true if the location is within the zone.
|
||||
function ZONE_RADIUS:IsVec2InZone( Vec2 )
|
||||
self:F2( Vec2 )
|
||||
|
||||
if not Vec2 then return false end
|
||||
|
||||
local ZoneVec2 = self:GetVec2()
|
||||
|
||||
if ZoneVec2 then
|
||||
@@ -1186,7 +1175,7 @@ end
|
||||
-- @return #boolean true if the point is within the zone.
|
||||
function ZONE_RADIUS:IsVec3InZone( Vec3 )
|
||||
self:F2( Vec3 )
|
||||
if not Vec3 then return false end
|
||||
if not Vec3 then return false end
|
||||
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
|
||||
|
||||
return InZone
|
||||
@@ -1200,13 +1189,13 @@ end
|
||||
-- @return DCS#Vec2 The random location within the zone.
|
||||
function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
|
||||
|
||||
local Vec2 = self:GetVec2()
|
||||
local _inner = inner or 0
|
||||
local _outer = outer or self:GetRadius()
|
||||
local Vec2 = self:GetVec2()
|
||||
local _inner = inner or 0
|
||||
local _outer = outer or self:GetRadius()
|
||||
|
||||
if surfacetypes and type(surfacetypes)~="table" then
|
||||
if surfacetypes and type(surfacetypes)~="table" then
|
||||
surfacetypes={surfacetypes}
|
||||
end
|
||||
end
|
||||
|
||||
local function _getpoint()
|
||||
local point = {}
|
||||
@@ -1226,10 +1215,10 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
|
||||
return false
|
||||
end
|
||||
|
||||
local point=_getpoint()
|
||||
local point=_getpoint()
|
||||
|
||||
if surfacetypes then
|
||||
local N=1 ; local Nmax=100 ; local gotit=false
|
||||
if surfacetypes then
|
||||
local N=1 ; local Nmax=100 ; local gotit=false
|
||||
while gotit==false and N<=Nmax do
|
||||
gotit=_checkSurface(point)
|
||||
if gotit then
|
||||
@@ -1241,7 +1230,7 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
|
||||
end
|
||||
end
|
||||
|
||||
return point
|
||||
return point
|
||||
end
|
||||
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
|
||||
@@ -1293,8 +1282,8 @@ end
|
||||
|
||||
--- 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 in meters. Default is 0 m.
|
||||
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone.
|
||||
-- @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.
|
||||
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
|
||||
-- @return Core.Point#COORDINATE The random coordinate.
|
||||
function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes)
|
||||
@@ -1977,7 +1966,7 @@ end
|
||||
-- @return #boolean true if the location is within the zone.
|
||||
function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
|
||||
self:F2( Vec2 )
|
||||
if not Vec2 then return false end
|
||||
if not Vec2 then return false end
|
||||
local Next
|
||||
local Prev
|
||||
local InPolygon = false
|
||||
@@ -2009,7 +1998,7 @@ function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 )
|
||||
self:F2( Vec3 )
|
||||
|
||||
if not Vec3 then return false end
|
||||
|
||||
|
||||
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
|
||||
|
||||
return InZone
|
||||
|
||||
@@ -471,22 +471,6 @@ do -- Types
|
||||
--@field #boolean lateActivated
|
||||
--@field #boolean uncontrolled
|
||||
|
||||
--- DCS template data structure.
|
||||
-- @type Template
|
||||
-- @field #boolean uncontrolled Aircraft is uncontrolled.
|
||||
-- @field #boolean lateActivation Group is late activated.
|
||||
-- @field #number x 2D Position on x-axis in meters.
|
||||
-- @field #number y 2D Position on y-axis in meters.
|
||||
-- @field #table units Unit list.
|
||||
--
|
||||
|
||||
--- Unit data structure.
|
||||
--@type Template.Unit
|
||||
--@field #string name Name of the unit.
|
||||
--@field #number x
|
||||
--@field #number y
|
||||
--@field #number alt
|
||||
|
||||
end --
|
||||
|
||||
|
||||
@@ -506,6 +490,7 @@ do -- Object
|
||||
-- @field BASE
|
||||
-- @field SCENERY
|
||||
-- @field CARGO
|
||||
|
||||
|
||||
--- @type Object.Desc
|
||||
-- @extends #Desc
|
||||
@@ -515,10 +500,6 @@ do -- Object
|
||||
--- @function [parent=#Object] isExist
|
||||
-- @param #Object self
|
||||
-- @return #boolean
|
||||
|
||||
--- @function [parent=#Object] isActive
|
||||
-- @param #Object self
|
||||
-- @return #boolean
|
||||
|
||||
--- @function [parent=#Object] destroy
|
||||
-- @param #Object self
|
||||
@@ -1151,11 +1132,6 @@ do -- Unit
|
||||
-- @function [parent=#Unit] getAmmo
|
||||
-- @param #Unit self
|
||||
-- @return #Unit.Ammo
|
||||
|
||||
--- Returns the number of infantry that can be embark onto the aircraft. Only returns a value if run on airplanes or helicopters. Returns nil if run on ground or ship units.
|
||||
-- @function [parent=#Unit] getDescentCapacity
|
||||
-- @param #Unit self
|
||||
-- @return #number Number of soldiers that embark.
|
||||
|
||||
--- Returns the unit sensors.
|
||||
-- @function [parent=#Unit] getSensors
|
||||
|
||||
@@ -1,980 +0,0 @@
|
||||
--- **Functional** - AI CSAR system
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * Send out helicopters to downed pilots
|
||||
-- * Rescues players and AI alike
|
||||
-- * Coalition specific
|
||||
-- * Starting from a FARP or Airbase
|
||||
-- * Dedicated MASH zone
|
||||
-- * Some FSM functions to include in your mission scripts
|
||||
-- * Limit number of available helos
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/CSR-001%20-%20Basics).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- Last Update April 2022
|
||||
--
|
||||
-- ===
|
||||
-- @module Functional.AICSAR
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
|
||||
--- AI CSAR class.
|
||||
-- @type AICSAR
|
||||
-- @field #string ClassName Name of this class.
|
||||
-- @field #string version Versioning.
|
||||
-- @field #string lid LID for log entries.
|
||||
-- @field #number coalition Colition side.
|
||||
-- @field #string template Template for pilot.
|
||||
-- @field #string helotemplate Template for CSAR helo.
|
||||
-- @field #string alias Alias Name.
|
||||
-- @field Wrapper.Airbase#AIRBASE farp FARP object from where to start.
|
||||
-- @field Core.Zone#ZONE farpzone MASH zone to drop rescued pilots.
|
||||
-- @field #number maxdistance Max distance to go for a rescue.
|
||||
-- @field #table pilotqueue Queue of pilots to rescue.
|
||||
-- @field #number pilotindex Table index to bind pilot to helo.
|
||||
-- @field #table helos Table of Ops.FlightGroup#FLIGHTGROUP objects
|
||||
-- @field #boolean verbose Switch more output.
|
||||
-- @field #number rescuezoneradius Radius around downed pilot for the helo to land in.
|
||||
-- @field #table rescued Track number of rescued pilot.
|
||||
-- @field #boolean autoonoff Only send a helo when no human heli pilots are available.
|
||||
-- @field Core.Set#SET_CLIENT playerset Track if alive heli pilots are available.
|
||||
-- @field #boolean limithelos limit available number of helos going on mission (defaults to true)
|
||||
-- @field #number helonumber number of helos available (default: 3)
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- *I once donated a pint of my finest red corpuscles to the great American Red Cross and the doctor opined my blood was very helpful; contained so much alcohol they could use it to sterilize their instruments.*
|
||||
-- W.C.Fields
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # AICSAR Concept
|
||||
--
|
||||
-- For an AI or human pilot landing with a parachute, a rescue mission will be spawned. The helicopter will fly to the pilot, pick him or her up,
|
||||
-- and fly back to a designated MASH (medical) zone, drop the pilot and then return to base.
|
||||
-- Operational maxdistance can be set as well as the landing radius around the downed pilot.
|
||||
-- Keep in mind that AI helicopters cannot hover-load at the time of writing, so rescue operations over water or in the mountains might not
|
||||
-- work.
|
||||
-- Optionally, if you have a CSAR operation with human pilots in your mission, you can set AICSAR to ignore missions when human helicopter
|
||||
-- pilots are around.
|
||||
--
|
||||
-- ## Setup
|
||||
--
|
||||
-- Setup is a one-liner:
|
||||
--
|
||||
-- -- @param #string Alias Name of this instance.
|
||||
-- -- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
|
||||
-- -- @param #string Pilottemplate Pilot template name.
|
||||
-- -- @param #string Helotemplate Helicopter template name.
|
||||
-- -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
|
||||
-- -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
|
||||
-- local my_aicsar=AICSAR:New("Luftrettung",coalition.side.BLUE,"Downed Pilot","Rescue Helo",AIRBASE:FindByName("Test FARP"),ZONE:New("MASH"))
|
||||
--
|
||||
-- ## Options are
|
||||
--
|
||||
-- my_aicsar.maxdistance -- maximum operational distance in meters. Defaults to 50NM or 92.6km
|
||||
-- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m
|
||||
-- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true.
|
||||
-- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true.
|
||||
-- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true)
|
||||
-- my_aicsar.helonumber -- number of helos available (default: 3)
|
||||
--
|
||||
-- ## Radio options
|
||||
--
|
||||
-- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking:
|
||||
--
|
||||
-- Defaults are:
|
||||
--
|
||||
-- AICSAR.Messages = {
|
||||
-- EN = {
|
||||
-- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
|
||||
-- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
|
||||
-- PILOTDOWN = "Pilot down at ", -- note that this will be appended with the position
|
||||
-- PILOTKIA = "Pilot KIA!",
|
||||
-- HELODOWN = "CSAR Helo Down!",
|
||||
-- PILOTRESCUED = "Pilot rescued!",
|
||||
-- PILOTINHELO = "Pilot picked up!",
|
||||
-- },
|
||||
-- }
|
||||
--
|
||||
-- Correspondingly, sound file names are defined as these defaults:
|
||||
--
|
||||
-- AICSAR.RadioMessages = {
|
||||
-- EN = {
|
||||
-- INITIALOK = "initialok.ogg",
|
||||
-- INITIALNOTOK = "initialnotok.ogg",
|
||||
-- PILOTDOWN = "pilotdown.ogg",
|
||||
-- PILOTKIA = "pilotkia.ogg",
|
||||
-- HELODOWN = "helodown.ogg",
|
||||
-- PILOTRESCUED = "pilotrescued.ogg",
|
||||
-- PILOTINHELO = "pilotinhelo.ogg",
|
||||
-- },
|
||||
-- }
|
||||
--
|
||||
-- and these default transmission lengths in seconds:
|
||||
--
|
||||
-- AICSAR.RadioLength = {
|
||||
-- EN = {
|
||||
-- INITIALOK = 4.1,
|
||||
-- INITIALNOTOK = 4.6,
|
||||
-- PILOTDOWN = 2.6,
|
||||
-- PILOTKIA = 1.1,
|
||||
-- HELODOWN = 2.1,
|
||||
-- PILOTRESCUED = 3.5,
|
||||
-- PILOTINHELO = 2.6,
|
||||
-- },
|
||||
-- }
|
||||
--
|
||||
-- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively
|
||||
-- save your sound file inside of the .miz mission file.
|
||||
--
|
||||
-- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version:
|
||||
--
|
||||
-- -- parameters are: locale, ID, text, soundfilename, duration
|
||||
-- my_aicsar.gettext:AddEntry("de","INITIALOK","Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind, ein Hubschrauber sammelt Sie auf!","okneu.ogg",5.0)
|
||||
-- my_aicsar.locale = "de" -- plays and shows the defined German language texts and sound. Fallback is "en", if something is undefined.
|
||||
--
|
||||
-- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so:
|
||||
--
|
||||
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,5002)
|
||||
--
|
||||
-- or
|
||||
--
|
||||
-- my_aicsar:SetDCSRadio(true,300,radio.modulation.AM,GROUP:FindByName("FARP-Radio"))
|
||||
--
|
||||
-- See the function documentation for parameter details.
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
--
|
||||
-- @field #AICSAR
|
||||
AICSAR = {
|
||||
ClassName = "AICSAR",
|
||||
version = "0.0.8",
|
||||
lid = "",
|
||||
coalition = coalition.side.BLUE,
|
||||
template = "",
|
||||
helotemplate = "",
|
||||
alias = "",
|
||||
farp = nil,
|
||||
farpzone = nil,
|
||||
maxdistance = UTILS.NMToMeters(50),
|
||||
pilotqueue = {},
|
||||
pilotindex = 0,
|
||||
helos = {},
|
||||
verbose = true,
|
||||
rescuezoneradius = 200,
|
||||
rescued = {},
|
||||
autoonoff = true,
|
||||
playerset = nil,
|
||||
Messages = {},
|
||||
SRS = nil,
|
||||
SRSRadio = false,
|
||||
SRSFrequency = 243,
|
||||
SRSPath = "\\",
|
||||
SRSModulation = radio.modulation.AM,
|
||||
SRSSoundPath = nil, -- defaults to "l10n/DEFAULT/", i.e. add messages via "Sount to..." in the ME
|
||||
SRSPort = 5002,
|
||||
DCSRadio = false,
|
||||
DCSFrequency = 243,
|
||||
DCSModulation = radio.modulation.AM,
|
||||
DCSRadioGroup = nil,
|
||||
limithelos = true,
|
||||
helonumber = 3,
|
||||
gettext = nil,
|
||||
locale ="en", -- default text language
|
||||
}
|
||||
|
||||
-- TODO Messages
|
||||
--- Messages enum
|
||||
-- @field Messages
|
||||
AICSAR.Messages = {
|
||||
EN = {
|
||||
INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
|
||||
INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
|
||||
PILOTDOWN = "Pilot down at ",
|
||||
PILOTKIA = "Pilot KIA!",
|
||||
HELODOWN = "CSAR Helo Down!",
|
||||
PILOTRESCUED = "Pilot rescued!",
|
||||
PILOTINHELO = "Pilot picked up!",
|
||||
},
|
||||
DE = {
|
||||
INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!",
|
||||
INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!",
|
||||
PILOTDOWN = "Pilot abgestürzt: ",
|
||||
PILOTKIA = "Pilot gefallen!",
|
||||
HELODOWN = "CSAR Hubschrauber verloren!",
|
||||
PILOTRESCUED = "Pilot gerettet!",
|
||||
PILOTINHELO = "Pilot an Bord geholt!",
|
||||
},
|
||||
}
|
||||
|
||||
-- TODO Radio Messages
|
||||
--- Radio Messages enum for ogg files
|
||||
-- @field RadioMessages
|
||||
AICSAR.RadioMessages = {
|
||||
EN = {
|
||||
INITIALOK = "initialok.ogg", -- 4.1 secs
|
||||
INITIALNOTOK = "initialnotok.ogg", -- 4.6 secs
|
||||
PILOTDOWN = "pilotdown.ogg", -- 2.6 secs
|
||||
PILOTKIA = "pilotkia.ogg", -- 1.1 sec
|
||||
HELODOWN = "helodown.ogg", -- 2.1 secs
|
||||
PILOTRESCUED = "pilotrescued.ogg", -- 3.5 secs
|
||||
PILOTINHELO = "pilotinhelo.ogg", -- 2.6 secs
|
||||
},
|
||||
}
|
||||
|
||||
-- TODO Radio Messages
|
||||
--- Radio Messages enum for ogg files length in secs
|
||||
-- @field RadioLength
|
||||
AICSAR.RadioLength = {
|
||||
EN = {
|
||||
INITIALOK = 4.1,
|
||||
INITIALNOTOK = 4.6,
|
||||
PILOTDOWN = 2.6,
|
||||
PILOTKIA = 1.1,
|
||||
HELODOWN = 2.1,
|
||||
PILOTRESCUED = 3.5,
|
||||
PILOTINHELO = 2.6,
|
||||
},
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function to create a new AICSAR object
|
||||
-- @param #AICSAR self
|
||||
-- @param #string Alias Name of this instance.
|
||||
-- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
|
||||
-- @param #string Pilottemplate Pilot template name.
|
||||
-- @param #string Helotemplate Helicopter template name.
|
||||
-- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
|
||||
-- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New())
|
||||
|
||||
--set Coalition
|
||||
if Coalition and type(Coalition)=="string" then
|
||||
if Coalition=="blue" then
|
||||
self.coalition=coalition.side.BLUE
|
||||
self.coalitiontxt = Coalition
|
||||
elseif Coalition=="red" then
|
||||
self.coalition=coalition.side.RED
|
||||
self.coalitiontxt = Coalition
|
||||
elseif Coalition=="neutral" then
|
||||
self.coalition=coalition.side.NEUTRAL
|
||||
self.coalitiontxt = Coalition
|
||||
else
|
||||
self:E("ERROR: Unknown coalition in AICSAR!")
|
||||
end
|
||||
else
|
||||
self.coalition = Coalition
|
||||
self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition))
|
||||
end
|
||||
|
||||
-- Set alias.
|
||||
if Alias then
|
||||
self.alias=tostring(Alias)
|
||||
else
|
||||
self.alias="Red Cross"
|
||||
if self.coalition then
|
||||
if self.coalition==coalition.side.RED then
|
||||
self.alias="IFRC"
|
||||
elseif self.coalition==coalition.side.BLUE then
|
||||
self.alias="CSAR"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.template = Pilottemplate
|
||||
self.helotemplate = Helotemplate
|
||||
self.farp = FARP
|
||||
self.farpzone = MASHZone
|
||||
self.playerset = SET_CLIENT:New():FilterActive(true):FilterCategories("helicopter"):FilterStart()
|
||||
|
||||
-- Radio
|
||||
self.SRS = nil
|
||||
self.SRSRadio = false
|
||||
self.SRSFrequency = 243
|
||||
self.SRSPath = "\\"
|
||||
self.SRSModulation = radio.modulation.AM
|
||||
self.SRSSoundPath = nil -- defaults to "l10n/DEFAULT/", i.e. add messages via "Sound to..." in the ME
|
||||
self.SRSPort = 5002
|
||||
|
||||
-- DCS Radio - add messages via "Sound to..." in the ME
|
||||
self.DCSRadio = false
|
||||
self.DCSFrequency = 243
|
||||
self.DCSModulation = radio.modulation.AM
|
||||
self.DCSRadioGroup = nil
|
||||
self.DCSRadioQueue = nil
|
||||
|
||||
self.MGRS_Accuracy = 2
|
||||
|
||||
-- limit number of available helos at the same time
|
||||
self.limithelos = true
|
||||
self.helonumber = 3
|
||||
|
||||
-- localization
|
||||
self:InitLocalization()
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
|
||||
self:AddTransition("*", "Status", "*") -- CSAR status update.
|
||||
self:AddTransition("*", "PilotDown", "*") -- Pilot down
|
||||
self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo
|
||||
self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued
|
||||
self:AddTransition("*", "PilotKIA", "*") -- Pilot dead
|
||||
self:AddTransition("*", "HeloDown", "*") -- Helo dead
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
self:HandleEvent(EVENTS.LandingAfterEjection)
|
||||
|
||||
self:__Start(math.random(2,5))
|
||||
|
||||
local text = string.format("%sAICSAR Version %s Starting",self.lid,self.version)
|
||||
|
||||
self:I(text)
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Status".
|
||||
-- @function [parent=#AICSAR] Status
|
||||
-- @param #AICSAR self
|
||||
|
||||
--- Triggers the FSM event "Status" after a delay.
|
||||
-- @function [parent=#AICSAR] __Status
|
||||
-- @param #AICSAR self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop".
|
||||
-- @function [parent=#AICSAR] Stop
|
||||
-- @param #AICSAR self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay.
|
||||
-- @function [parent=#AICSAR] __Stop
|
||||
-- @param #AICSAR self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- On after "PilotDown" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotDown
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate Location of the pilot.
|
||||
-- @param #boolean InReach True if in maxdistance else false.
|
||||
|
||||
--- On after "PilotPickedUp" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotPickedUp
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param #table CargoTable of Ops.OpsGroup#OPSGROUP Cargo objects
|
||||
-- @param #number Index
|
||||
|
||||
--- On after "PilotRescued" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotRescued
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On after "PilotKIA" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotKIA
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On after "HeloDown" event.
|
||||
-- @function [parent=#AICSAR] OnAfterHeloDown
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param #number Index
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- [Internal] Create the Moose TextAndSoundEntries
|
||||
-- @param #AICSAR self
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:InitLocalization()
|
||||
self:T(self.lid .. "InitLocalization")
|
||||
-- English standard localization
|
||||
self.gettext=TEXTANDSOUND:New(self.ClassName, "en")
|
||||
self.gettext:AddEntry("en","INITIALOK",AICSAR.Messages.EN.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
|
||||
self.gettext:AddEntry("en","INITIALNOTOK",AICSAR.Messages.EN.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
|
||||
self.gettext:AddEntry("en","HELODOWN",AICSAR.Messages.EN.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
|
||||
self.gettext:AddEntry("en","PILOTDOWN",AICSAR.Messages.EN.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
|
||||
self.gettext:AddEntry("en","PILOTINHELO",AICSAR.Messages.EN.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
|
||||
self.gettext:AddEntry("en","PILOTKIA",AICSAR.Messages.EN.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
|
||||
self.gettext:AddEntry("en","PILOTRESCUED",AICSAR.Messages.EN.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
|
||||
-- German localization - we keep the sound files English
|
||||
self.gettext:AddEntry("de","INITIALOK",AICSAR.Messages.DE.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
|
||||
self.gettext:AddEntry("de","INITIALNOTOK",AICSAR.Messages.DE.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
|
||||
self.gettext:AddEntry("de","HELODOWN",AICSAR.Messages.DE.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
|
||||
self.gettext:AddEntry("de","PILOTDOWN",AICSAR.Messages.DE.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
|
||||
self.gettext:AddEntry("de","PILOTINHELO",AICSAR.Messages.DE.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
|
||||
self.gettext:AddEntry("de","PILOTKIA",AICSAR.Messages.DE.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
|
||||
self.gettext:AddEntry("de","PILOTRESCUED",AICSAR.Messages.DE.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
|
||||
self.locale = "en"
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Switch sound output on and use SRS
|
||||
-- @param #AICSAR self
|
||||
-- @param #boolean OnOff Switch on (true) or off (false).
|
||||
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
|
||||
-- @param #number Frequency Defaults to 243 (guard)
|
||||
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM
|
||||
-- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor.
|
||||
-- @param #number Port Port of the SRS, defaults to 5002.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
|
||||
self:T(self.lid .. "SetSRSRadio")
|
||||
self:T(self.lid .. "SetSRSRadio to "..tostring(OnOff))
|
||||
self.SRSRadio = OnOff and true
|
||||
self.SRSFrequency = Frequency or 243
|
||||
self.SRSPath = Path or "c:\\"
|
||||
self.SRSModulation = Modulation or radio.modulation.AM
|
||||
local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME
|
||||
self.SRSSoundPath = SoundPath or soundpath
|
||||
self.SRSPort = Port or 5002
|
||||
if OnOff then
|
||||
self.SRS = MSRS:New(Path,Frequency,Modulation)
|
||||
self.SRS:SetPort(self.SRSPort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Switch sound output on and use normale (DCS) radio
|
||||
-- @param #AICSAR self
|
||||
-- @param #boolean OnOff Switch on (true) or off (false).
|
||||
-- @param #number Frequency Defaults to 243 (guard).
|
||||
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM.
|
||||
-- @param Wrapper.Group#GROUP Group The group to use as sending station.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetDCSRadio(OnOff,Frequency,Modulation,Group)
|
||||
self:T(self.lid .. "SetDCSRadio")
|
||||
self:T(self.lid .. "SetDCSRadio to "..tostring(OnOff))
|
||||
self.DCSRadio = OnOff and true
|
||||
self.DCSFrequency = Frequency or 243
|
||||
self.DCSModulation = Modulation or radio.modulation.AM
|
||||
self.DCSRadioGroup = Group
|
||||
if self.DCSRadio then
|
||||
self.DCSRadioQueue = RADIOQUEUE:New(Frequency,Modulation,"AI-CSAR")
|
||||
self.DCSRadioQueue:Start(5,5)
|
||||
self.DCSRadioQueue:SetRadioPower(1000)
|
||||
self.DCSRadioQueue:SetSenderCoordinate(Group:GetCoordinate())
|
||||
else
|
||||
if self.DCSRadioQueue then
|
||||
self.DCSRadioQueue:Stop()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Sound output via non-SRS Radio. Add message files (.ogg) via "Sound to..." in the ME.
|
||||
-- @param #AICSAR self
|
||||
-- @param #string Soundfile Name of the soundfile
|
||||
-- @param #number Duration Duration of the sound
|
||||
-- @param #string Subtitle Text to display
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:DCSRadioBroadcast(Soundfile,Duration,Subtitle)
|
||||
self:T(self.lid .. "DCSRadioBroadcast")
|
||||
local radioqueue = self.DCSRadioQueue -- Sound.RadioQueue#RADIOQUEUE
|
||||
radioqueue:NewTransmission(Soundfile,Duration,nil,2,nil,Subtitle,10)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Catch the landing after ejection and spawn a pilot in situ.
|
||||
-- @param #AICSAR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:OnEventLandingAfterEjection(EventData)
|
||||
self:T(self.lid .. "OnEventLandingAfterEjection ID=" .. EventData.id)
|
||||
|
||||
-- autorescue on off?
|
||||
if self.autoonoff then
|
||||
if self.playerset:CountAlive() > 0 then
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
local _event = EventData -- Core.Event#EVENTDATA
|
||||
-- get position and spawn in a template pilot
|
||||
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
|
||||
local _country = _event.initiator:getCountry()
|
||||
local _coalition = coalition.getCountryCoalition( _country )
|
||||
|
||||
-- DONE: add distance check
|
||||
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
|
||||
|
||||
-- Mayday Message
|
||||
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
|
||||
local text = ""
|
||||
if _coalition == self.coalition then
|
||||
if self.verbose then
|
||||
local setting = {}
|
||||
setting.MGRS_Accuracy = self.MGRS_Accuracy
|
||||
local location = _LandingPos:ToStringMGRS(setting)
|
||||
text = Text .. location .. "!"
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
end
|
||||
|
||||
-- further processing
|
||||
if _coalition == self.coalition and distancetofarp <= self.maxdistance then
|
||||
-- in reach
|
||||
self:T(self.lid .. "Spawning new Pilot")
|
||||
self.pilotindex = self.pilotindex + 1
|
||||
local newpilot = SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template, self.pilotindex))
|
||||
newpilot:InitDelayOff()
|
||||
newpilot:OnSpawnGroup(
|
||||
function (grp)
|
||||
self.pilotqueue[self.pilotindex] = grp
|
||||
end
|
||||
)
|
||||
newpilot:SpawnFromCoordinate(_LandingPos)
|
||||
|
||||
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
|
||||
self:__PilotDown(2,_LandingPos,true)
|
||||
elseif _coalition == self.coalition and distancetofarp > self.maxdistance then
|
||||
-- out of reach, apologies, too far off
|
||||
self:T(self.lid .. "Pilot out of reach")
|
||||
self:__PilotDown(2,_LandingPos,false)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Get FlightGroup
|
||||
-- @param #AICSAR self
|
||||
-- @return Ops.FlightGroup#FLIGHTGROUP The FlightGroup
|
||||
function AICSAR:_GetFlight()
|
||||
self:T(self.lid .. "_GetFlight")
|
||||
-- Helo Carrier.
|
||||
local newhelo = SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000))
|
||||
:InitDelayOff()
|
||||
:InitUnControlled(true)
|
||||
:Spawn()
|
||||
|
||||
local nhelo=FLIGHTGROUP:New(newhelo)
|
||||
nhelo:SetHomebase(self.farp)
|
||||
nhelo:Activate()
|
||||
return nhelo
|
||||
end
|
||||
|
||||
--- [Internal] Create a new rescue mission
|
||||
-- @param #AICSAR self
|
||||
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
|
||||
-- @param #number Index Index number of this pilot
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_InitMission(Pilot,Index)
|
||||
self:T(self.lid .. "_InitMission")
|
||||
|
||||
local pickupzone = ZONE_GROUP:New(Pilot:GetName(),Pilot,self.rescuezoneradius)
|
||||
--local pilotset = SET_GROUP:New()
|
||||
--pilotset:AddGroup(Pilot)
|
||||
|
||||
-- Cargo transport assignment.
|
||||
local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone)
|
||||
|
||||
local helo = self:_GetFlight()
|
||||
-- inject reservation
|
||||
helo.AICSARReserved = true
|
||||
|
||||
-- Cargo transport assignment to first Huey group.
|
||||
helo:AddOpsTransport(opstransport)
|
||||
|
||||
-- callback functions
|
||||
local function AICPickedUp(Helo,Cargo,Index)
|
||||
self:__PilotPickedUp(2,Helo,Cargo,Index)
|
||||
end
|
||||
|
||||
local function AICHeloDead(Helo,Index)
|
||||
self:__HeloDown(2,Helo,Index)
|
||||
end
|
||||
|
||||
function helo:OnAfterLoadingDone(From,Event,To)
|
||||
AICPickedUp(helo,helo:GetCargoGroups(),Index)
|
||||
end
|
||||
|
||||
function helo:OnAfterDead(From,Event,To)
|
||||
AICHeloDead(helo,Index)
|
||||
end
|
||||
|
||||
self.helos[Index] = helo
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Check if pilot arrived in rescue zone (MASH)
|
||||
-- @param #AICSAR self
|
||||
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
|
||||
-- @return #boolean outcome
|
||||
function AICSAR:_CheckInMashZone(Pilot)
|
||||
self:T(self.lid .. "_CheckQueue")
|
||||
if Pilot:IsInZone(self.farpzone) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- [Internal] Check helo queue
|
||||
-- @param #AICSAR self
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_CheckHelos()
|
||||
self:T(self.lid .. "_CheckHelos")
|
||||
for _index,_helo in pairs(self.helos) do
|
||||
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
||||
if helo and helo.ClassName == "FLIGHTGROUP" then
|
||||
local state = helo:GetState()
|
||||
local name = helo:GetName()
|
||||
self:T("Helo group "..name.." in state "..state)
|
||||
if state == "Arrived" then
|
||||
helo:__Stop(5)
|
||||
self.helos[_index] = nil
|
||||
end
|
||||
else
|
||||
self.helos[_index] = nil
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Count helos queue
|
||||
-- @param #AICSAR self
|
||||
-- @return #number Number of helos on mission
|
||||
function AICSAR:_CountHelos()
|
||||
self:T(self.lid .. "_CountHelos")
|
||||
local count = 0
|
||||
for _index,_helo in pairs(self.helos) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--- [Internal] Check pilot queue for next mission
|
||||
-- @param #AICSAR self
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_CheckQueue()
|
||||
self:T(self.lid .. "_CheckQueue")
|
||||
for _index, _pilot in pairs(self.pilotqueue) do
|
||||
local classname = _pilot.ClassName and _pilot.ClassName or "NONE"
|
||||
local name = _pilot.GroupName and _pilot.GroupName or "NONE"
|
||||
local helocount = self:_CountHelos()
|
||||
--self:T("Looking at " .. classname .. " " .. name)
|
||||
-- find one w/o mission
|
||||
if _pilot and _pilot.ClassName and _pilot.ClassName == "GROUP" then
|
||||
local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP
|
||||
-- rescued?
|
||||
if self:_CheckInMashZone(_pilot) then
|
||||
self:T("Pilot" .. _pilot.GroupName .. " rescued!")
|
||||
_pilot:Destroy(false)
|
||||
self.pilotqueue[_index] = nil
|
||||
self.rescued[_index] = true
|
||||
self:__PilotRescued(2)
|
||||
if flightgroup then
|
||||
flightgroup.AICSARReserved = false
|
||||
end
|
||||
end -- end rescued
|
||||
-- has no mission assigned?
|
||||
if not _pilot.AICSAR then
|
||||
-- helo available?
|
||||
if self.limithelos and helocount >= self.helonumber then
|
||||
-- none free
|
||||
break
|
||||
end -- end limit
|
||||
_pilot.AICSAR = {}
|
||||
_pilot.AICSAR.Status = "Initiated"
|
||||
_pilot.AICSAR.Boarded = false
|
||||
self:_InitMission(_pilot,_index)
|
||||
break
|
||||
else
|
||||
-- update status from OPSGROUP
|
||||
if flightgroup then
|
||||
local state = flightgroup:GetState()
|
||||
_pilot.AICSAR.Status = state
|
||||
end
|
||||
--self:T("Flight for " .. _pilot.GroupName .. " in state " .. state)
|
||||
end -- end has mission
|
||||
end -- end if pilot
|
||||
end -- end loop
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- [Internal] onafterStart
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterStart(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:__Status(3)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterStatus
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterStatus(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:_CheckQueue()
|
||||
self:_CheckHelos()
|
||||
self:__Status(30)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterStop
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterStop(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:UnHandleEvent(EVENTS.LandingAfterEjection)
|
||||
if self.DCSRadioQueue then
|
||||
self.DCSRadioQueue:Stop()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotDown
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate Location of the pilot.
|
||||
-- @param #boolean InReach True if in maxdistance else false.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach)
|
||||
self:T({From, Event, To})
|
||||
local CoordinateText = Coordinate:ToStringMGRS()
|
||||
local inreach = tostring(InReach)
|
||||
--local text = string.format("Pilot down at %s. In reach = %s",CoordinateText,inreach)
|
||||
if InReach then
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALOK",self.locale)
|
||||
--local text = AICSAR.Messages.EN.INITIALOK
|
||||
self:T(text)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
else
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale)
|
||||
--local text = AICSAR.Messages.EN.INITIALNOTOK
|
||||
self:T(text)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotKIA
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotKIA(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTKIA",self.locale)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterHeloDown
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param #number Index
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterHeloDown(From, Event, To, Helo, Index)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("HELODOWN",self.locale)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
local findex = 0
|
||||
local fhname = Helo:GetName()
|
||||
-- find index of Helo
|
||||
if Index and Index > 0 then
|
||||
findex=Index
|
||||
else
|
||||
for _index, _helo in pairs(self.helos) do
|
||||
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
||||
local hname = helo:GetName()
|
||||
if fhname == hname then
|
||||
findex = _index
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- find pilot
|
||||
if findex > 0 and not self.rescued[findex] then
|
||||
local pilot = self.pilotqueue[findex]
|
||||
self.helos[findex] = nil
|
||||
if pilot.AICSAR.Boarded then
|
||||
self:T("Helo Down: Found DEAD Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
||||
-- pilot also dead
|
||||
self:__PilotKIA(2)
|
||||
self.pilotqueue[findex] = nil
|
||||
else
|
||||
-- initiate new mission
|
||||
self:T("Helo Down: Found ALIVE Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
||||
self:_InitMission(pilot,findex)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotRescued
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotRescued(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTRESCUED",self.locale)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotPickedUp
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param #table CargoTable of Ops.OpsGroup#OPSGROUP Cargo objects
|
||||
-- @param #number Index
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTINHELO",self.locale)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
local findex = 0
|
||||
local fhname = Helo:GetName()
|
||||
if Index and Index > 0 then
|
||||
findex = Index
|
||||
else
|
||||
-- find index of Helo
|
||||
for _index, _helo in pairs(self.helos) do
|
||||
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
||||
local hname = helo:GetName()
|
||||
if fhname == hname then
|
||||
findex = _index
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- find pilot
|
||||
if findex > 0 then
|
||||
local pilot = self.pilotqueue[findex]
|
||||
self:T("Boarded: Found Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
||||
pilot.AICSAR.Boarded = true -- mark as boarded
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- END AICSAR
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -59,13 +59,7 @@ function ATC_GROUND:New( Airbases, AirbaseList )
|
||||
|
||||
|
||||
for AirbaseID, Airbase in pairs( self.Airbases ) do
|
||||
-- Specified ZoneBoundary is used if setted or Airbase radius by default
|
||||
if Airbase.ZoneBoundary then
|
||||
Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary " .. AirbaseID, Airbase.ZoneBoundary )
|
||||
else
|
||||
Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone()
|
||||
end
|
||||
|
||||
Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone()
|
||||
Airbase.ZoneRunways = {}
|
||||
for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do
|
||||
Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway )
|
||||
@@ -3269,310 +3263,5 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds )
|
||||
end
|
||||
|
||||
|
||||
--- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
|
||||
--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND}
|
||||
--
|
||||
-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi.
|
||||
-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- The default maximum speed for the airbases at Persian Gulf is **50 km/h**. Warnings are given if this speed limit is trespassed.
|
||||
-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way.
|
||||
--
|
||||
-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi.
|
||||
-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.
|
||||
--
|
||||
-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving
|
||||
-- faster than the maximum allowed speed, the pilot will be kicked.
|
||||
--
|
||||
-- Different airbases have different maximum speeds, according safety regulations.
|
||||
--
|
||||
-- # Airbases monitored
|
||||
--
|
||||
-- The following airbases are monitored at the Mariana Island region.
|
||||
-- Use the @{Wrapper.Airbase#AIRBASE.MarianaIslands} enumeration to select the airbases to be monitored.
|
||||
--
|
||||
-- * AIRBASE.MarianaIslands.Rota_Intl
|
||||
-- * AIRBASE.MarianaIslands.Andersen_AFB
|
||||
-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl
|
||||
-- * AIRBASE.MarianaIslands.Saipan_Intl
|
||||
-- * AIRBASE.MarianaIslands.Tinian_Intl
|
||||
-- * AIRBASE.MarianaIslands.Olf_Orote
|
||||
--
|
||||
-- # Installation
|
||||
--
|
||||
-- ## In Single Player Missions
|
||||
--
|
||||
-- ATC\_GROUND is fully functional in single player.
|
||||
--
|
||||
-- ## In Multi Player Missions
|
||||
--
|
||||
-- ATC\_GROUND is functional in multi player, however ...
|
||||
--
|
||||
-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player.
|
||||
-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed
|
||||
-- by Ciribob.
|
||||
--
|
||||
-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight.
|
||||
-- ATC\_GROUND is communicating with this modified script to kick players!
|
||||
--
|
||||
-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob.
|
||||
--
|
||||
-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock)
|
||||
--
|
||||
-- # Script it!
|
||||
--
|
||||
-- ## 1. ATC_GROUND_MARIANAISLANDS Constructor
|
||||
--
|
||||
-- Creates a new ATC_GROUND_MARIANAISLANDS object that will monitor pilots taxiing behaviour.
|
||||
--
|
||||
-- -- This creates a new ATC_GROUND_MARIANAISLANDS object.
|
||||
--
|
||||
-- -- Monitor for these clients the airbases.
|
||||
-- AirbasePoliceCaucasus = ATC_GROUND_MARIANAISLANDS:New()
|
||||
--
|
||||
-- ATC_Ground = ATC_GROUND_MARIANAISLANDS:New(
|
||||
-- { AIRBASE.MarianaIslands.Andersen_AFB,
|
||||
-- AIRBASE.MarianaIslands.Saipan_Intl
|
||||
-- }
|
||||
-- )
|
||||
--
|
||||
--
|
||||
-- ## 2. Set various options
|
||||
--
|
||||
-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes.
|
||||
--
|
||||
-- ### 2.1 Speed limit at an airbase.
|
||||
--
|
||||
-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second.
|
||||
-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour.
|
||||
-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour.
|
||||
--
|
||||
-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately.
|
||||
--
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second.
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour.
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour.
|
||||
--
|
||||
---- @field #ATC_GROUND_MARIANAISLANDS
|
||||
ATC_GROUND_MARIANAISLANDS = {
|
||||
ClassName = "ATC_GROUND_MARIANAISLANDS",
|
||||
Airbases = {
|
||||
|
||||
[AIRBASE.MarianaIslands.Andersen_AFB] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=16534.138036037,["x"]=11357.42159178,},
|
||||
[2]={["y"]=16193.406442738,["x"]=12080.012957533,},
|
||||
[3]={["y"]=13846.966851869,["x"]=12017.348398727,},
|
||||
[4]={["y"]=13085.815989171,["x"]=11686.317876875,},
|
||||
[5]={["y"]=13157.991797443,["x"]=11307.826209991,},
|
||||
[6]={["y"]=12055.725179065,["x"]=10795.955695916,},
|
||||
[7]={["y"]=12762.455491112,["x"]=8890.9830441032,},
|
||||
[8]={["y"]=15955.829493693,["x"]=10333.527220132,},
|
||||
[9]={["y"]=16537.500532414,["x"]=11302.009499603,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=12586.683049611,["x"]=10224.374497932,},
|
||||
[2]={["y"]=16191.720475696,["x"]=11791.299100017,},
|
||||
[3]={["y"]=16126.93956642,["x"]=11938.855615591,},
|
||||
[4]={["y"]=12520.758127164,["x"]=10385.177131701,},
|
||||
[5]={["y"]=12584.654720512,["x"]=10227.416991581,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=12663.030391743,["x"]=9661.9623015306,},
|
||||
[2]={["y"]=16478.347303358,["x"]=11328.665745976,},
|
||||
[3]={["y"]=16405.4731048,["x"]=11479.11570429,},
|
||||
[4]={["y"]=12597.277684174,["x"]=9817.9733769647,},
|
||||
[5]={["y"]=12661.894752524,["x"]=9674.4462086962,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=2288.5182403943,["x"]=1469.0170841716,},
|
||||
[2]={["y"]=1126.2025877996,["x"]=1174.37135631,},
|
||||
[3]={["y"]=-2015.6461924287,["x"]=-484.62000718931,},
|
||||
[4]={["y"]=-2102.1292389114,["x"]=-988.03393750566,},
|
||||
[5]={["y"]=476.03853524366,["x"]=-1220.1783269883,},
|
||||
[6]={["y"]=2059.2220058047,["x"]=78.889693514402,},
|
||||
[7]={["y"]=1898.1396965104,["x"]=705.67531284795,},
|
||||
[8]={["y"]=2760.1768681934,["x"]=1026.0681119777,},
|
||||
[9]={["y"]=2317.2278959994,["x"]=1460.8143254273,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=-1872.6620108821,["x"]=-924.3572605835,},
|
||||
[2]={["y"]=1763.4754603305,["x"]=735.35988877983,},
|
||||
[3]={["y"]=1700.6941677961,["x"]=866.32615476157,},
|
||||
[4]={["y"]=-1934.0078007732,["x"]=-779.8149298453,},
|
||||
[5]={["y"]=-1875.0113982627,["x"]=-914.95971106094,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=-1512.9403660377,["x"]=-1005.5903386188,},
|
||||
[2]={["y"]=1577.9055714735,["x"]=413.22750176368,},
|
||||
[3]={["y"]=1523.1182807849,["x"]=543.89726442232,},
|
||||
[4]={["y"]=-1572.5102998047,["x"]=-867.04004322806,},
|
||||
[5]={["y"]=-1514.2790162347,["x"]=-1003.5823633233,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Rota_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=47237.615412849,["x"]=76048.890408862,},
|
||||
[2]={["y"]=49938.030053628,["x"]=75921.721582932,},
|
||||
[3]={["y"]=49931.24873272,["x"]=75735.184004851,},
|
||||
[4]={["y"]=49295.999227075,["x"]=75754.716414519,},
|
||||
[5]={["y"]=49286.963307515,["x"]=75510.037806569,},
|
||||
[6]={["y"]=48774.280745707,["x"]=75513.331990155,},
|
||||
[7]={["y"]=48785.021396773,["x"]=75795.691662161,},
|
||||
[8]={["y"]=47232.749278491,["x"]=75839.239059146,},
|
||||
[9]={["y"]=47236.687866223,["x"]=76042.706764692,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=49741.295228062,["x"]=75901.50955922,},
|
||||
[2]={["y"]=49739.033213305,["x"]=75768.333440425,},
|
||||
[3]={["y"]=47448.460520408,["x"]=75857.400271466,},
|
||||
[4]={["y"]=47452.270177742,["x"]=75999.965448133,},
|
||||
[5]={["y"]=49738.502011054,["x"]=75905.338915708,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Saipan_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=100489.08491445,["x"]=179799.05158855,},
|
||||
[2]={["y"]=100869.73415313,["x"]=179948.98719903,},
|
||||
[3]={["y"]=101364.78967515,["x"]=180831.98517043,},
|
||||
[4]={["y"]=101563.85713359,["x"]=180885.21496237,},
|
||||
[5]={["y"]=101733.92591034,["x"]=180457.73296886,},
|
||||
[6]={["y"]=103340.30228775,["x"]=180990.08362622,},
|
||||
[7]={["y"]=103459.55080438,["x"]=180453.77747027,},
|
||||
[8]={["y"]=100406.63048095,["x"]=179266.60983762,},
|
||||
[9]={["y"]=100225.55027532,["x"]=179423.9380961,},
|
||||
[10]={["y"]=100477.48558937,["x"]=179791.9827288,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=103170.38882002,["x"]=180654.56630524,},
|
||||
[2]={["y"]=103235.37868835,["x"]=180497.25368418,},
|
||||
[3]={["y"]=100564.72969504,["x"]=179435.41443498,},
|
||||
[4]={["y"]=100509.30718722,["x"]=179584.65394733,},
|
||||
[5]={["y"]=103163.53918905,["x"]=180651.82645285,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=103048.83223261,["x"]=180819.94107128,},
|
||||
[2]={["y"]=103087.60579257,["x"]=180720.06315265,},
|
||||
[3]={["y"]=101037.52694966,["x"]=179899.50061624,},
|
||||
[4]={["y"]=100994.61708907,["x"]=180009.33151758,},
|
||||
[5]={["y"]=103043.26643227,["x"]=180820.40488798,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Tinian_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=88393.477575413,["x"]=166704.16076438,},
|
||||
[2]={["y"]=91581.732441809,["x"]=167402.54409276,},
|
||||
[3]={["y"]=91533.451647402,["x"]=166826.23670062,},
|
||||
[4]={["y"]=90827.604136952,["x"]=166699.75590414,},
|
||||
[5]={["y"]=90894.853975623,["x"]=166375.37836304,},
|
||||
[6]={["y"]=89995.027922869,["x"]=166224.92495935,},
|
||||
[7]={["y"]=88937.62899352,["x"]=166244.48573911,},
|
||||
[8]={["y"]=88408.916178231,["x"]=166480.39896864,},
|
||||
[9]={["y"]=88387.745481732,["x"]=166685.82715656,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=91329.480937912,["x"]=167204.44064529,},
|
||||
[2]={["y"]=91363.95475433,["x"]=167038.15603429,},
|
||||
[3]={["y"]=88585.849307337,["x"]=166520.3807647,},
|
||||
[4]={["y"]=88554.422227212,["x"]=166686.49505251,},
|
||||
[5]={["y"]=91318.8152578,["x"]=167203.31794212,},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
--- Creates a new ATC_GROUND_MARIANAISLANDS object.
|
||||
-- @param #ATC_GROUND_MARIANAISLANDS self
|
||||
-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.MarianaIslands enumerator).
|
||||
-- @return #ATC_GROUND_MARIANAISLANDS self
|
||||
function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) )
|
||||
|
||||
self:SetKickSpeedKmph( 50 )
|
||||
self:SetMaximumKickSpeedKmph( 150 )
|
||||
|
||||
-- -- Andersen
|
||||
-- local AndersenBoundary = GROUP:FindByName( "Andersen Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneBoundary = ZONE_POLYGON:New( "Andersen Boundary", AndersenBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local AndersenRunway1 = GROUP:FindByName( "Andersen Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[1] = ZONE_POLYGON:New( "Andersen Runway 1", AndersenRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local AndersenRunway2 = GROUP:FindByName( "Andersen Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[2] = ZONE_POLYGON:New( "Andersen Runway 2", AndersenRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Antonio_B_Won_Pat_International_Airport
|
||||
-- local AntonioBoundary = GROUP:FindByName( "Antonio Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneBoundary = ZONE_POLYGON:New( "Antonio Boundary", AntonioBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local AntonioRunway1 = GROUP:FindByName( "Antonio Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Antonio Runway 1", AntonioRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local AntonioRunway2 = GROUP:FindByName( "Antonio Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Antonio Runway 2", AntonioRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Rota_International_Airport
|
||||
-- local RotaBoundary = GROUP:FindByName( "Rota Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneBoundary = ZONE_POLYGON:New( "Rota Boundary", RotaBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local RotaRunway1 = GROUP:FindByName( "Rota Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Rota Runway 1", RotaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Saipan_International_Airport
|
||||
-- local SaipanBoundary = GROUP:FindByName( "Saipan Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneBoundary = ZONE_POLYGON:New( "Saipan Boundary", SaipanBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local SaipanRunway1 = GROUP:FindByName( "Saipan Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Saipan Runway 1", SaipanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local SaipanRunway2 = GROUP:FindByName( "Saipan Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Saipan Runway 2", SaipanRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Tinian_International_Airport
|
||||
-- local TinianBoundary = GROUP:FindByName( "Tinian Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneBoundary = ZONE_POLYGON:New( "Tinian Boundary", TinianBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local TinianRunway1 = GROUP:FindByName( "Tinian Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Tinian Runway 1", TinianRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Start SCHEDULER for ATC_GROUND_MARIANAISLANDS object.
|
||||
-- @param #ATC_GROUND_MARIANAISLANDS self
|
||||
-- @param RepeatScanSeconds Time in second for defining occurency of alerts.
|
||||
-- @return nothing
|
||||
function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
-- @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 #boolean report Arty group sends messages about their current state or target to its coalition.
|
||||
-- @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.
|
||||
|
||||
@@ -1,961 +0,0 @@
|
||||
--- **Functional** - Autolase targets in the field.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **AUOTLASE** - Autolase targets in the field.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- ### [Autolase](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Detect and lase contacts automatically
|
||||
-- * Targets are lased by threat priority order
|
||||
-- * Use FSM events to link functionality into your scripts
|
||||
-- * Easy setup
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--- Spot on!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 1 Autolase concept
|
||||
--
|
||||
-- * Detect and lase contacts automatically
|
||||
-- * Targets are lased by threat priority order
|
||||
-- * Use FSM events to link functionality into your scripts
|
||||
-- * Set laser codes and smoke colors per Recce unit
|
||||
-- * Easy set-up
|
||||
--
|
||||
-- # 2 Basic usage
|
||||
--
|
||||
-- ## 2.2 Set up a group of Recce Units:
|
||||
--
|
||||
-- local FoxSet = SET_GROUP:New():FilterPrefixes("Recce"):FilterCoalitions("blue"):FilterStart()
|
||||
--
|
||||
-- ## 2.3 (Optional) Set up a group of pilots, this will drive who sees the F10 menu entry:
|
||||
--
|
||||
-- local Pilotset = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterStart()
|
||||
--
|
||||
-- ## 2.4 Set up and start Autolase:
|
||||
--
|
||||
-- local autolaser = AUTOLASE:New(FoxSet,coalition.side.BLUE,"Wolfpack",Pilotset)
|
||||
--
|
||||
-- ## 2.5 Example - Using a fixed laser code and color for a specific Recce unit:
|
||||
--
|
||||
-- local recce = SPAWN:New("Reaper")
|
||||
-- :InitDelayOff()
|
||||
-- :OnSpawnGroup(
|
||||
-- function (group)
|
||||
-- local unit = group:GetUnit(1)
|
||||
-- local name = unit:GetName()
|
||||
-- autolaser:SetRecceLaserCode(name,1688)
|
||||
-- autolaser:SetRecceSmokeColor(name,SMOKECOLOR.Red)
|
||||
-- end
|
||||
-- )
|
||||
-- :InitCleanUp(60)
|
||||
-- :InitLimit(1,0)
|
||||
-- :SpawnScheduled(30,0.5)
|
||||
--
|
||||
-- ## 2.6 Example - Inform pilots about events:
|
||||
--
|
||||
-- autolaser:SetNotifyPilots(true) -- defaults to true, also shown if debug == true
|
||||
-- -- Note - message are shown to pilots in the #SET_CLIENT only if using the pilotset option, else to the coalition.
|
||||
--
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- @module Functional.Autolase
|
||||
-- @image Designation.JPG
|
||||
--
|
||||
-- Date: 24 Oct 2021
|
||||
--
|
||||
--- Class AUTOLASE
|
||||
-- @type AUTOLASE
|
||||
-- @field #string ClassName
|
||||
-- @field #string lid
|
||||
-- @field #number verbose
|
||||
-- @field #string alias
|
||||
-- @field #boolean debug
|
||||
-- @field #string version
|
||||
-- @extends Ops.Intel#INTEL
|
||||
|
||||
---
|
||||
-- @field #AUTOLASE
|
||||
AUTOLASE = {
|
||||
ClassName = "AUTOLASE",
|
||||
lid = "",
|
||||
verbose = 0,
|
||||
alias = "",
|
||||
debug = false,
|
||||
}
|
||||
|
||||
--- Laser spot info
|
||||
-- @type AUTOLASE.LaserSpot
|
||||
-- @field Core.Spot#SPOT laserspot
|
||||
-- @field Wrapper.Unit#UNIT lasedunit
|
||||
-- @field Wrapper.Unit#UNIT lasingunit
|
||||
-- @field #number lasercode
|
||||
-- @field #string location
|
||||
-- @field #number timestamp
|
||||
-- @field #string unitname
|
||||
-- @field #string reccename
|
||||
-- @field #string unittype
|
||||
|
||||
--- AUTOLASE class version.
|
||||
-- @field #string version
|
||||
AUTOLASE.version = "0.0.11"
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Begin Functional.Autolase.lua
|
||||
-------------------------------------------------------------------
|
||||
|
||||
--- Constructor for a new Autolase instance.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Core.Set#SET_GROUP RecceSet Set of detecting and lasing units
|
||||
-- @param #number Coalition Coalition side. Can also be passed as a string "red", "blue" or "neutral".
|
||||
-- @param #string Alias (Optional) An alias how this object is called in the logs etc.
|
||||
-- @param Core.Set#SET_CLIENT PilotSet (Optional) Set of clients for precision bombing, steering menu creation. Leave nil for a coalition-wide F10 entry and display.
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
|
||||
BASE:T({RecceSet, Coalition, Alias, PilotSet})
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #AUTOLASE
|
||||
|
||||
if Coalition and type(Coalition)=="string" then
|
||||
if Coalition=="blue" then
|
||||
self.coalition=coalition.side.BLUE
|
||||
elseif Coalition=="red" then
|
||||
self.coalition=coalition.side.RED
|
||||
elseif Coalition=="neutral" then
|
||||
self.coalition=coalition.side.NEUTRAL
|
||||
else
|
||||
self:E("ERROR: Unknown coalition in AUTOLASE!")
|
||||
end
|
||||
end
|
||||
|
||||
-- Set alias.
|
||||
if Alias then
|
||||
self.alias=tostring(Alias)
|
||||
else
|
||||
self.alias="Lion"
|
||||
if self.coalition then
|
||||
if self.coalition==coalition.side.RED then
|
||||
self.alias="Wolf"
|
||||
elseif self.coalition==coalition.side.BLUE then
|
||||
self.alias="Fox"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- inherit from INTEL
|
||||
local self=BASE:Inherit(self, INTEL:New(RecceSet, Coalition, Alias)) -- #AUTOLASE
|
||||
|
||||
self.RecceSet = RecceSet
|
||||
self.DetectVisual = true
|
||||
self.DetectOptical = true
|
||||
self.DetectRadar = true
|
||||
self.DetectIRST = true
|
||||
self.DetectRWR = true
|
||||
self.DetectDLINK = true
|
||||
self.LaserCodes = UTILS.GenerateLaserCodes()
|
||||
self.LaseDistance = 5000
|
||||
self.LaseDuration = 300
|
||||
self.GroupsByThreat = {}
|
||||
self.UnitsByThreat = {}
|
||||
self.RecceNames = {}
|
||||
self.RecceLaserCode = {}
|
||||
self.RecceSmokeColor = {}
|
||||
self.RecceUnitNames= {}
|
||||
self.maxlasing = 4
|
||||
self.CurrentLasing = {}
|
||||
self.lasingindex = 0
|
||||
self.deadunitnotes = {}
|
||||
self.usepilotset = false
|
||||
self.reporttimeshort = 10
|
||||
self.reporttimelong = 30
|
||||
self.smoketargets = false
|
||||
self.smokecolor = SMOKECOLOR.Red
|
||||
self.notifypilots = true
|
||||
self.targetsperrecce = {}
|
||||
self.RecceUnits = {}
|
||||
self.forcecooldown = true
|
||||
self.cooldowntime = 60
|
||||
self.useSRS = false
|
||||
self.SRSPath = ""
|
||||
self.SRSFreq = 251
|
||||
self.SRSMod = radio.modulation.AM
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "Monitor", "*") -- Start FSM
|
||||
self:AddTransition("*", "Lasing", "*") -- Lasing target
|
||||
self:AddTransition("*", "TargetLost", "*") -- Lost target
|
||||
self:AddTransition("*", "TargetDestroyed", "*") -- Target destroyed
|
||||
self:AddTransition("*", "RecceKIA", "*") -- Recce KIA
|
||||
self:AddTransition("*", "LaserTimeout", "*") -- Laser timed out
|
||||
self:AddTransition("*", "Cancel", "*") -- Stop Autolase
|
||||
|
||||
-- Menu Entry
|
||||
if not PilotSet then
|
||||
self.Menu = MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self)
|
||||
else
|
||||
self.usepilotset = true
|
||||
self.pilotset = PilotSet
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft)
|
||||
self:SetPilotMenu()
|
||||
end
|
||||
|
||||
self:SetClusterAnalysis(false, false)
|
||||
|
||||
self:__Start(2)
|
||||
self:__Monitor(math.random(5,10))
|
||||
|
||||
return self
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Monitor".
|
||||
-- @function [parent=#AUTOLASE] Status
|
||||
-- @param #AUTOLASE self
|
||||
|
||||
--- Triggers the FSM event "Monitor" after a delay.
|
||||
-- @function [parent=#AUTOLASE] __Status
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Cancel".
|
||||
-- @function [parent=#AUTOLASE] Cancel
|
||||
-- @param #AUTOLASE self
|
||||
|
||||
--- Triggers the FSM event "Cancel" after a delay.
|
||||
-- @function [parent=#AUTOLASE] __Cancel
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- On After "RecceKIA" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterRecceKIA
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string RecceName The lost Recce
|
||||
|
||||
--- On After "TargetDestroyed" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterTargetDestroyed
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The destroyed unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
|
||||
--- On After "TargetLost" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterTargetLost
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The lost unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
|
||||
--- On After "LaserTimeout" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterLaserTimeout
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The lost unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
|
||||
--- On After "Lasing" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterLasing
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param Functional.Autolase#AUTOLASE.LaserSpot LaserSpot The LaserSpot data table
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Helper Functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
--- (Internal) Function to set pilot menu.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetPilotMenu()
|
||||
if self.usepilotset then
|
||||
local pilottable = self.pilotset:GetSetObjects() or {}
|
||||
for _,_unit in pairs (pilottable) do
|
||||
local Unit = _unit -- Wrapper.Unit#UNIT
|
||||
if Unit and Unit:IsAlive() then
|
||||
local Group = Unit:GetGroup()
|
||||
local lasemenu = MENU_GROUP_COMMAND:New(Group,"Autolase Status",nil,self.ShowStatus,self,Group)
|
||||
lasemenu:Refresh()
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Event function for new pilots.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:OnEventPlayerEnterAircraft(EventData)
|
||||
self:SetPilotMenu()
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to get a laser code by recce name
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName Unit(!) name of the Recce
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:GetLaserCode(RecceName)
|
||||
local code = 1688
|
||||
if self.RecceLaserCode[RecceName] == nil then
|
||||
code = self.LaserCodes[math.random(#self.LaserCodes)]
|
||||
self.RecceLaserCode[RecceName] = code
|
||||
else
|
||||
code = self.RecceLaserCode[RecceName]
|
||||
end
|
||||
return code
|
||||
end
|
||||
|
||||
--- (Internal) Function to get a smoke color by recce name
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName Unit(!) name of the Recce
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:GetSmokeColor(RecceName)
|
||||
local color = self.smokecolor
|
||||
if self.RecceSmokeColor[RecceName] == nil then
|
||||
self.RecceSmokeColor[RecceName] = color
|
||||
else
|
||||
color = self.RecceSmokeColor[RecceName]
|
||||
end
|
||||
return color
|
||||
end
|
||||
|
||||
--- (User) Function enable sending messages via SRS.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch usage on and off
|
||||
-- @param #string Path Path to SRS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalon
|
||||
-- @param #number Frequency Frequency to send, e.g. 243
|
||||
-- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation)
|
||||
self.useSRS = OnOff or true
|
||||
self.SRSPath = Path or "E:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
self.SRSFreq = Frequency or 271
|
||||
self.SRSMod = Modulation or radio.modulation.AM
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function set max lasing targets
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number Number Max number of targets to lase at once
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetMaxLasingTargets(Number)
|
||||
self.maxlasing = Number or 4
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function set notify pilots on events
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch messaging on (true) or off (false)
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetNotifyPilots(OnOff)
|
||||
self.notifypilots = OnOff and true
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set a specific code to a Recce.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName (Unit!) Name of the Recce
|
||||
-- @param #number Code The lase code
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetRecceLaserCode(RecceName, Code)
|
||||
local code = Code or 1688
|
||||
self.RecceLaserCode[RecceName] = code
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set a specific smoke color for a Recce.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName (Unit!) Name of the Recce
|
||||
-- @param #number Color The color, e.g. SMOKECOLOR.Red, SMOKECOLOR.Green etc
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetRecceSmokeColor(RecceName, Color)
|
||||
local color = Color or self.smokecolor
|
||||
self.RecceSmokeColor[RecceName] = color
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to force laser cooldown and cool down time
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch cool down on (true) or off (false) - defaults to true
|
||||
-- @param #number Seconds Number of seconds for cooldown - dafaults to 60 seconds
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetLaserCoolDown(OnOff, Seconds)
|
||||
self.forcecooldown = OnOff and true
|
||||
self.cooldowntime = Seconds or 60
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set message show times.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number long Longer show time
|
||||
-- @param #number short Shorter show time
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetReportingTimes(long, short)
|
||||
self.reporttimeshort = short or 10
|
||||
self.reporttimelong = long or 30
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set lasing distance in meters and duration in seconds
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number Distance (Max) distance for lasing in meters - default 5000 meters
|
||||
-- @param #number Duration (Max) duration for lasing in seconds - default 300 secs
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetLasingParameters(Distance, Duration)
|
||||
self.LaseDistance = Distance or 5000
|
||||
self.LaseDuration = Duration or 300
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set smoking of targets.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch smoking on or off
|
||||
-- @param #number Color Smokecolor, e.g. SMOKECOLOR.Red
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetSmokeTargets(OnOff,Color)
|
||||
self.smoketargets = OnOff
|
||||
self.smokecolor = Color or SMOKECOLOR.Red
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to calculate line of sight.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Wrapper.Unit#UNIT Unit
|
||||
-- @return #number LOS Line of sight in meters
|
||||
function AUTOLASE:GetLosFromUnit(Unit)
|
||||
local lasedistance = self.LaseDistance
|
||||
local unitheight = Unit:GetHeight()
|
||||
local coord = Unit:GetCoordinate()
|
||||
local landheight = coord:GetLandHeight()
|
||||
local asl = unitheight - landheight
|
||||
if asl > 100 then
|
||||
local absquare = lasedistance^2+asl^2
|
||||
lasedistance = math.sqrt(absquare)
|
||||
end
|
||||
return lasedistance
|
||||
end
|
||||
|
||||
--- (Internal) Function to check on lased targets.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:CleanCurrentLasing()
|
||||
local lasingtable = self.CurrentLasing
|
||||
local newtable = {}
|
||||
local newreccecount = {}
|
||||
local lasing = 0
|
||||
|
||||
for _ind,_entry in pairs(lasingtable) do
|
||||
local entry = _entry -- #AUTOLASE.LaserSpot
|
||||
if not newreccecount[entry.reccename] then
|
||||
newreccecount[entry.reccename] = 0
|
||||
end
|
||||
end
|
||||
|
||||
for _,_recce in pairs (self.RecceSet:GetSetObjects()) do
|
||||
local recce = _recce --Wrapper.Group#GROUP
|
||||
if recce and recce:IsAlive() then
|
||||
local unit = recce:GetUnit(1)
|
||||
local name = unit:GetName()
|
||||
if not self.RecceUnits[name] then
|
||||
self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime() }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _ind,_entry in pairs(lasingtable) do
|
||||
local entry = _entry -- #AUTOLASE.LaserSpot
|
||||
local valid = 0
|
||||
local reccedead = false
|
||||
local unitdead = false
|
||||
local lostsight = false
|
||||
local timeout = false
|
||||
local Tnow = timer.getAbsTime()
|
||||
-- check recce dead
|
||||
local recce = entry.lasingunit
|
||||
if recce and recce:IsAlive() then
|
||||
valid = valid + 1
|
||||
else
|
||||
reccedead = true
|
||||
self:__RecceKIA(2,entry.reccename)
|
||||
end
|
||||
-- check entry dead
|
||||
local unit = entry.lasedunit
|
||||
if unit and unit:IsAlive() == true then
|
||||
valid = valid + 1
|
||||
else
|
||||
unitdead = true
|
||||
if not self.deadunitnotes[entry.unitname] then
|
||||
self.deadunitnotes[entry.unitname] = true
|
||||
self:__TargetDestroyed(2,entry.unitname,entry.reccename)
|
||||
end
|
||||
end
|
||||
-- check entry out of sight
|
||||
if not reccedead and not unitdead then
|
||||
if self:CanLase(recce,unit) then
|
||||
valid = valid + 1
|
||||
else
|
||||
lostsight = true
|
||||
entry.laserspot:LaseOff()
|
||||
self:__TargetLost(2,entry.unitname,entry.reccename)
|
||||
end
|
||||
end
|
||||
-- check timed out
|
||||
local timestamp = entry.timestamp
|
||||
if Tnow - timestamp < self.LaseDuration and not lostsight then
|
||||
valid = valid + 1
|
||||
else
|
||||
timeout = true
|
||||
entry.laserspot:LaseOff()
|
||||
|
||||
self.RecceUnits[entry.reccename].cooldown = true
|
||||
self.RecceUnits[entry.reccename].timestamp = timer.getAbsTime()
|
||||
|
||||
if not lostsight then
|
||||
self:__LaserTimeout(2,entry.unitname,entry.reccename)
|
||||
end
|
||||
end
|
||||
if valid == 4 then
|
||||
self.lasingindex = self.lasingindex + 1
|
||||
newtable[self.lasingindex] = entry
|
||||
newreccecount[entry.reccename] = newreccecount[entry.reccename] + 1
|
||||
lasing = lasing + 1
|
||||
end
|
||||
end
|
||||
self.CurrentLasing = newtable
|
||||
self.targetsperrecce = newreccecount
|
||||
return lasing
|
||||
end
|
||||
|
||||
--- (Internal) Function to show status.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Wrapper.Group#GROUP Group (Optional) show to a certain group
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:ShowStatus(Group)
|
||||
local report = REPORT:New("Autolase")
|
||||
local reccetable = self.RecceSet:GetSetObjects()
|
||||
for _,_recce in pairs(reccetable) do
|
||||
if _recce and _recce:IsAlive() then
|
||||
local unit = _recce:GetUnit(1)
|
||||
local name = unit:GetName()
|
||||
local code = self:GetLaserCode(name)
|
||||
report:Add(string.format("Recce %s has code %d",name,code))
|
||||
end
|
||||
end
|
||||
local lines = 0
|
||||
for _ind,_entry in pairs(self.CurrentLasing) do
|
||||
local entry = _entry -- #AUTOLASE.LaserSpot
|
||||
local reccename = entry.reccename
|
||||
local typename = entry.unittype
|
||||
local code = entry.lasercode
|
||||
local locationstring = entry.location
|
||||
local text = string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
|
||||
report:Add(text)
|
||||
lines = lines + 1
|
||||
end
|
||||
if lines == 0 then
|
||||
report:Add("No targets!")
|
||||
end
|
||||
local reporttime = self.reporttimelong
|
||||
if lines == 0 then reporttime = self.reporttimeshort end
|
||||
if Group and Group:IsAlive() then
|
||||
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToGroup(Group)
|
||||
else
|
||||
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToCoalition(self.coalition)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to show messages.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string Message The message to be sent
|
||||
-- @param #number Duration Duration in seconds
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:NotifyPilots(Message,Duration)
|
||||
if self.usepilotset then
|
||||
local pilotset = self.pilotset:GetSetObjects() --#table
|
||||
for _,_pilot in pairs(pilotset) do
|
||||
local pilot = _pilot -- Wrapper.Unit#UNIT
|
||||
if pilot and pilot:IsAlive() then
|
||||
local Group = pilot:GetGroup()
|
||||
local m = MESSAGE:New(Message,Duration,"Autolase"):ToGroup(Group)
|
||||
end
|
||||
end
|
||||
elseif not self.debug then
|
||||
local m = MESSAGE:New(Message,Duration,"Autolase"):ToCoalition(self.coalition)
|
||||
else
|
||||
local m = MESSAGE:New(Message,Duration,"Autolase"):ToAll()
|
||||
end
|
||||
if self.debug then self:I(Message) end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Send messages via SRS.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string Message The (short!) message to be sent, e.g. "Lasing target!"
|
||||
-- @return #AUTOLASE self
|
||||
-- @usage Step 1 - set up the radio basics **once** with
|
||||
-- my_autolase:SetUsingSRS(true,"C:\\path\\SRS-Folder",251,radio.modulation.AM)
|
||||
-- Step 2 - send a message, e.g.
|
||||
-- function my_autolase:OnAfterLasing(From, Event, To, LaserSpot)
|
||||
-- my_autolase:NotifyPilotsWithSRS("Reaper lasing new target!")
|
||||
-- end
|
||||
function AUTOLASE:NotifyPilotsWithSRS(Message)
|
||||
if self.useSRS then
|
||||
-- Create a SOUNDTEXT object.
|
||||
if self.debug then
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SOUNDTEXT")
|
||||
BASE:TraceClass("MSRS")
|
||||
end
|
||||
local path = self.SRSPath or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
local freq = self.SRSFreq or 271
|
||||
local mod = self.SRSMod or radio.modulation.AM
|
||||
local text=SOUNDTEXT:New(Message)
|
||||
-- MOOSE SRS
|
||||
local msrs=MSRS:New(path, freq, mod)
|
||||
-- Text-to speech with default voice after 2 seconds.
|
||||
msrs:PlaySoundText(text, 2)
|
||||
end
|
||||
if self.debug then self:I(Message) end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to check if a unit is already lased.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string unitname Name of the unit to check
|
||||
-- @return #boolean outcome True or false
|
||||
function AUTOLASE:CheckIsLased(unitname)
|
||||
local outcome = false
|
||||
for _,_laserspot in pairs(self.CurrentLasing) do
|
||||
local spot = _laserspot -- #AUTOLASE.LaserSpot
|
||||
if spot.unitname == unitname then
|
||||
outcome = true
|
||||
break
|
||||
end
|
||||
end
|
||||
return outcome
|
||||
end
|
||||
|
||||
--- (Internal) Function to check if a unit can be lased.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Wrapper.Unit#UNIT Recce The Recce #UNIT
|
||||
-- @param Wrapper.Unit#UNIT Unit The lased #UNIT
|
||||
-- @return #boolean outcome True or false
|
||||
function AUTOLASE:CanLase(Recce,Unit)
|
||||
local canlase = false
|
||||
-- cooldown?
|
||||
if Recce and Recce:IsAlive() == true then
|
||||
local name = Recce:GetName()
|
||||
local cooldown = self.RecceUnits[name].cooldown and self.forcecooldown
|
||||
if cooldown then
|
||||
local Tdiff = timer.getAbsTime() - self.RecceUnits[name].timestamp
|
||||
if Tdiff < self.cooldowntime then
|
||||
return false
|
||||
else
|
||||
self.RecceUnits[name].cooldown = false
|
||||
end
|
||||
end
|
||||
-- calculate LOS
|
||||
local reccecoord = Recce:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local islos = reccecoord:IsLOS(unitcoord,2.5)
|
||||
-- calculate distance
|
||||
local distance = math.floor(reccecoord:Get3DDistance(unitcoord))
|
||||
local lasedistance = self:GetLosFromUnit(Recce)
|
||||
if distance <= lasedistance and islos then
|
||||
canlase = true
|
||||
end
|
||||
end
|
||||
return canlase
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
--- (Internal) FSM Function for monitoring
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeMonitor(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
-- Check if group has detected any units.
|
||||
self:UpdateIntel()
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function for monitoring
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
|
||||
-- Housekeeping
|
||||
local countlases = self:CleanCurrentLasing()
|
||||
|
||||
self:SetPilotMenu()
|
||||
|
||||
local detecteditems = self.Contacts or {} -- #table of Ops.Intelligence#INTEL.Contact
|
||||
local groupsbythreat = {}
|
||||
local report = REPORT:New("Detections")
|
||||
local lines = 0
|
||||
for _,_contact in pairs(detecteditems) do
|
||||
local contact = _contact -- Ops.Intelligence#INTEL.Contact
|
||||
local grp = contact.group
|
||||
local coord = contact.position
|
||||
local reccename = contact.recce or "none"
|
||||
local reccegrp = UNIT:FindByName(reccename)
|
||||
if reccegrp then
|
||||
local reccecoord = reccegrp:GetCoordinate()
|
||||
local distance = math.floor(reccecoord:Get3DDistance(coord))
|
||||
local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel)
|
||||
report:Add(text)
|
||||
self:T(text)
|
||||
if self.debug then self:I(text) end
|
||||
lines = lines + 1
|
||||
-- sort out groups beyond sight
|
||||
local lasedistance = self:GetLosFromUnit(reccegrp)
|
||||
if grp:IsGround() and lasedistance >= distance then
|
||||
table.insert(groupsbythreat,{contact.group,contact.threatlevel})
|
||||
self.RecceNames[contact.groupname] = contact.recce
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.GroupsByThreat = groupsbythreat
|
||||
|
||||
if self.verbose > 2 and lines > 0 then
|
||||
local m=MESSAGE:New(report:Text(),self.reporttimeshort,"Autolase"):ToAll()
|
||||
end
|
||||
|
||||
table.sort(self.GroupsByThreat, function(a,b)
|
||||
local aNum = a[2] -- Coin value of a
|
||||
local bNum = b[2] -- Coin value of b
|
||||
return aNum > bNum -- Return their comparisons, < for ascending, > for descending
|
||||
end)
|
||||
|
||||
-- build table of Units
|
||||
local unitsbythreat = {}
|
||||
for _,_entry in pairs(self.GroupsByThreat) do
|
||||
local group = _entry[1] -- Wrapper.Group#GROUP
|
||||
if group and group:IsAlive() then
|
||||
local units = group:GetUnits()
|
||||
local reccename = self.RecceNames[group:GetName()]
|
||||
for _,_unit in pairs(units) do
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local threat = unit:GetThreatLevel()
|
||||
local coord = unit:GetCoordinate()
|
||||
if threat > 0 then
|
||||
local unitname = unit:GetName()
|
||||
table.insert(unitsbythreat,{unit,threat})
|
||||
self.RecceUnitNames[unitname] = reccename
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.UnitsByThreat = unitsbythreat
|
||||
|
||||
table.sort(self.UnitsByThreat, function(a,b)
|
||||
local aNum = a[2] -- Coin value of a
|
||||
local bNum = b[2] -- Coin value of b
|
||||
return aNum > bNum -- Return their comparisons, < for ascending, > for descending
|
||||
end)
|
||||
|
||||
local unitreport = REPORT:New("Detected Units")
|
||||
|
||||
local lines = 0
|
||||
for _,_entry in pairs(self.UnitsByThreat) do
|
||||
local threat = _entry[2]
|
||||
local unit = _entry[1]
|
||||
local unitname = unit:GetName()
|
||||
local text = string.format("Unit %s | Threatlevel %d | Detected by %s",unitname,threat,self.RecceUnitNames[unitname])
|
||||
unitreport:Add(text)
|
||||
lines = lines + 1
|
||||
self:T(text)
|
||||
if self.debug then self:I(text) end
|
||||
end
|
||||
|
||||
if self.verbose > 2 and lines > 0 then
|
||||
local m=MESSAGE:New(unitreport:Text(),self.reporttimeshort,"Autolase"):ToAll()
|
||||
end
|
||||
|
||||
for _,_detectingunit in pairs(self.RecceUnits) do
|
||||
|
||||
local reccename = _detectingunit.name
|
||||
local recce = _detectingunit.unit
|
||||
local reccecount = self.targetsperrecce[reccename] or 0
|
||||
local targets = 0
|
||||
for _,_entry in pairs(self.UnitsByThreat) do
|
||||
local unit = _entry[1] -- Wrapper.Unit#UNIT
|
||||
local unitname = unit:GetName()
|
||||
local canlase = self:CanLase(recce,unit)
|
||||
if targets+reccecount < self.maxlasing and not self:CheckIsLased(unitname) and unit:IsAlive() and canlase then
|
||||
targets = targets + 1
|
||||
local code = self:GetLaserCode(reccename)
|
||||
local spot = SPOT:New(recce)
|
||||
spot:LaseOn(unit,code,self.LaseDuration)
|
||||
local locationstring = unit:GetCoordinate():ToStringLLDDM()
|
||||
local laserspot = { -- #AUTOLASE.LaserSpot
|
||||
laserspot = spot,
|
||||
lasedunit = unit,
|
||||
lasingunit = recce,
|
||||
lasercode = code,
|
||||
location = locationstring,
|
||||
timestamp = timer.getAbsTime(),
|
||||
unitname = unitname,
|
||||
reccename = reccename,
|
||||
unittype = unit:GetTypeName(),
|
||||
}
|
||||
if self.smoketargets then
|
||||
local coord = unit:GetCoordinate()
|
||||
local color = self:GetSmokeColor(reccename)
|
||||
coord:Smoke(color)
|
||||
end
|
||||
self.lasingindex = self.lasingindex + 1
|
||||
self.CurrentLasing[self.lasingindex] = laserspot
|
||||
self:__Lasing(2,laserspot)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:__Monitor(-30)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeRecceKIA
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string RecceName The lost Recce
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeRecceKIA(From,Event,To,RecceName)
|
||||
self:T({From, Event, To, RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
local text = string.format("Recce %s KIA!",RecceName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeTargetDestroyed
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The destroyed unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeTargetDestroyed(From,Event,To,UnitName,RecceName)
|
||||
self:T({From, Event, To, UnitName, RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
local text = string.format("Unit %s destroyed! Good job!",UnitName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeTargetLost
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The lost unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeTargetLost(From,Event,To,UnitName,RecceName)
|
||||
self:T({From, Event, To, UnitName,RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
local text = string.format("%s lost sight of unit %s.",RecceName,UnitName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeLaserTimeout
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The lost unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeLaserTimeout(From,Event,To,UnitName,RecceName)
|
||||
self:T({From, Event, To, UnitName,RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
local text = string.format("%s laser timeout on unit %s.",RecceName,UnitName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeLasing
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param Functional.Autolase#AUTOLASE.LaserSpot LaserSpot The LaserSpot data table
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeLasing(From,Event,To,LaserSpot)
|
||||
self:T({From, Event, To, LaserSpot.unittype})
|
||||
if self.notifypilots or self.debug then
|
||||
local laserspot = LaserSpot -- #AUTOLASE.LaserSpot
|
||||
local text = string.format("%s is lasing %s code %d\nat %s",laserspot.reccename,laserspot.unittype,laserspot.lasercode,laserspot.location)
|
||||
self:NotifyPilots(text,self.reporttimeshort+5)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeCancel
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeCancel(From,Event,To)
|
||||
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
|
||||
self:__Stop(2)
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- End Functional.Autolase.lua
|
||||
-------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,730 +0,0 @@
|
||||
--- **Functional** - (R2.5) - Monitor and export flight model data.
|
||||
--
|
||||
-- Record flight data and export it to a csv file.
|
||||
--
|
||||
-- ## Recorded Data:
|
||||
--
|
||||
-- * Time,
|
||||
-- * Altitude,
|
||||
-- * Temperature,
|
||||
-- * Pressure,
|
||||
-- * Velocity (total and x,y,z components),
|
||||
-- * Acceleration (total and x,y,z components),
|
||||
-- * AoA,
|
||||
-- * Pitch and pitch rate,
|
||||
-- * Roll and roll rate,
|
||||
-- * Yaw and yaw rate,
|
||||
-- * Turn rate.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.FMData
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
|
||||
--- FMD class.
|
||||
-- @type FMD
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table players Player table.
|
||||
-- @field #table menuadded Table of units where the F10 radio menu was added.
|
||||
-- @field #string savepath Path to save data files.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be sure!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The FMD Concept
|
||||
--
|
||||
-- This class can be used to record flight data such as velocity, acceleration, pitch, roll, yaw and turn rates. The output is written to a csv file for later analysis.
|
||||
--
|
||||
-- # Usage
|
||||
--
|
||||
-- The script is very easy to use. It only requires the line
|
||||
--
|
||||
-- fmd=FMD:New()
|
||||
--
|
||||
-- This will automatically start the FMD script.
|
||||
--
|
||||
-- Each player will get an entry "FMD" in the F-10 radio menu. There he can start or stop the data recording.
|
||||
--
|
||||
-- **IMPORTANT**
|
||||
-- Due to a DCS bug, it is necessary that (in single player mode), player/clients have to **hit ESC twice** before entering an aircraft client slot.
|
||||
-- Otherwise, the script will not load and now menus will be created for the player.
|
||||
--
|
||||
-- # Output
|
||||
--
|
||||
-- After the data recording is completed, the data is written to a csv file. The file name starts with **FMD** and contains the employed airframe plus a running number.
|
||||
--
|
||||
-- However, one must desanitize the **io** and **lfs** lines the DCS root directory. Otherwise, DCS will not allow data to be written to file.
|
||||
--
|
||||
-- @field #FMD
|
||||
FMD = {
|
||||
ClassName = "FMD",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
players = {},
|
||||
menuadded = {},
|
||||
savepath = nil,
|
||||
}
|
||||
|
||||
--- Player data table.
|
||||
-- @type FMD.PlayerData
|
||||
-- @field Core.Scheduler#SCHEDULER scheduler Scheduler
|
||||
-- @field Wrapper.Unit#UNIT unit Player unit.
|
||||
-- @field #string unitname Name of the unit.
|
||||
-- @field Wrapper.Client#CLIENT client Player client.
|
||||
-- @field #string actype Aircraft type.
|
||||
-- @field #string name Player name.
|
||||
-- @field #number dt Time step for data recording in seconds. Default 0.01 sec ==> 100 data points per second!
|
||||
-- @field #number rd Recording duration in seconds.
|
||||
-- @field #boolean recording If true, recording started.
|
||||
-- @field #table data Data table.
|
||||
-- @field #number SID Scheduler ID.
|
||||
|
||||
--- Data point table.
|
||||
-- @type FMD.DataPoint
|
||||
-- @field #number time Abs mission time.
|
||||
-- @field #number T Temperature in degrees Celsius.
|
||||
-- @field #number P Pressure.
|
||||
-- @field #number Alt Altitude ASL in meters.
|
||||
-- @field #number AoA Angle of Attack in degrees.
|
||||
-- @field #number Pitch Pitch angle in degrees.
|
||||
-- @field #number Roll Roll angle in degrees.
|
||||
-- @field #number Yaw Yaw angle in degrees.
|
||||
-- @field #number Climbrate Climb rate in m/s.
|
||||
-- @field #number Vtot Total velocity in m/s.
|
||||
-- @field DCS#Vec3 v Velocity vector. Components x,y,z in m/s.
|
||||
-- @field DCS#Vec3 o Orientation vector. Components x,y,z in meters.
|
||||
-- @field #number omega Angle velocity in m/s.
|
||||
-- @field #number Hdg Heading in degrees.
|
||||
-- @field #number Atot Total acceleration m/s^2.
|
||||
-- @field DCS#Vec3 a Acceleration vector.
|
||||
-- @field #number DRoll Roll speed in degrees/second.
|
||||
-- @field #number DPitch Pitch speed in degrees/second.
|
||||
-- @field #number DYaw Yaw speed in degrees/second.
|
||||
|
||||
--- Main group level radio menu: F10 Other/FMD.
|
||||
-- @field #table MenuF10
|
||||
FMD.MenuF10={}
|
||||
|
||||
--- FMD mission level F10 root menu.
|
||||
-- @field #table MenuF10Root
|
||||
FMD.MenuF10Root=nil
|
||||
|
||||
--- FMD script version.
|
||||
-- @field #string version
|
||||
FMD.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FMD class object.
|
||||
-- @param #FMD self
|
||||
-- @return #FMD self.
|
||||
function FMD:New()
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #FMD
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Log string.
|
||||
self.lid="FMD | "
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FMD script.
|
||||
self:AddTransition("*", "Status", "*") -- Start FMD script.
|
||||
|
||||
-- Start FMD.
|
||||
self:Start()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after Start event.
|
||||
-- @param #FMD self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FMD:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting Flight Model Data script version %s", FMD.version)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
end
|
||||
|
||||
--- On after Stop event.
|
||||
-- @param #FMD self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FMD:onafterStop(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Stopping Flight Model Data script version %s", FMD.version)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Handle events.
|
||||
self:UnHandleEvent(EVENTS.Birth)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- FMD event handler for event birth.
|
||||
-- @param #FMD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FMD:OnEventBirth(EventData)
|
||||
self:F3({eventbirth = EventData})
|
||||
|
||||
local _unitName=EventData.IniUnitName
|
||||
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
self:T2(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
|
||||
self:T2(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
|
||||
self:T2(self.lid.."BIRTH: player = "..tostring(_playername))
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
local _uid=_unit:GetID()
|
||||
local _group=_unit:GetGroup()
|
||||
local _callsign=_unit:GetCallsign()
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", _playername, _callsign, _unitName, _group:GetName())
|
||||
self:T(self.lid..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
-- Add Menu commands.
|
||||
self:_AddF10Commands(_unitName)
|
||||
|
||||
-- Player data.
|
||||
self.players[_playername]={}
|
||||
|
||||
local player=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
player.scheduler=SCHEDULER:New()
|
||||
player.unit=_unit
|
||||
player.unitname=_unitName
|
||||
player.name=_playername
|
||||
player.client=CLIENT:FindByName(_unitName, nil, true)
|
||||
player.actype=_unit:GetTypeName()
|
||||
player.dt=0.01
|
||||
player.rd=30
|
||||
player.recording=false
|
||||
player.data={}
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Data Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get data of unit.
|
||||
-- @param #FMD self
|
||||
-- @param Wrapper.Unit#UNIT unit
|
||||
-- @return #FMD.DataPoint Datapoint.
|
||||
function FMD:_GetDataPoint(unit)
|
||||
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
-- Current coordinate.
|
||||
local coord=unit:GetCoordinate()
|
||||
|
||||
local dp={} --#FMD.DataPoint
|
||||
|
||||
dp.a={}
|
||||
dp.Alt=coord.y
|
||||
dp.AoA=unit:GetAoA()
|
||||
dp.Atot=nil
|
||||
dp.P=coord:GetPressure()
|
||||
dp.Pitch=unit:GetPitch()
|
||||
dp.Roll=unit:GetRoll()
|
||||
dp.time=timer.getAbsTime()
|
||||
dp.T=coord:GetTemperature()
|
||||
dp.v=unit:GetVelocityVec3()
|
||||
dp.Vtot=UTILS.VecNorm(dp.v)
|
||||
dp.Yaw=unit:GetYaw()
|
||||
dp.o=unit:GetOrientationX()
|
||||
dp.Hdg=unit:GetHeading()
|
||||
|
||||
return dp
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get data of unit.
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data.
|
||||
-- @return #FMD.DataPoint Datapoint.
|
||||
function FMD:_Derivative(playerData)
|
||||
|
||||
local function numderiv(fpm,fpp,h)
|
||||
return 0.5*(fpp-fpm)/h
|
||||
end
|
||||
|
||||
local datapoints=playerData.data
|
||||
--local dt=playerData.dt
|
||||
|
||||
for i=2,#datapoints-1 do
|
||||
|
||||
local dpm=datapoints[i-1] --#FMD.DataPoint
|
||||
local dpp=datapoints[i+1] --#FMD.DataPoint
|
||||
local dpi=datapoints[i] --#FMD.DataPoint
|
||||
|
||||
-- Time step.
|
||||
local dt=0.5*(dpp.time-dpm.time)
|
||||
|
||||
dpi.Atot=numderiv(dpm.Vtot, dpp.Vtot, dt)
|
||||
|
||||
dpi.a.x=numderiv(dpm.v.x, dpp.v.x, dt)
|
||||
dpi.a.y=numderiv(dpm.v.y, dpp.v.y, dt)
|
||||
dpi.a.z=numderiv(dpm.v.z, dpp.v.z, dt)
|
||||
|
||||
dpi.DPitch=numderiv(dpm.Pitch, dpp.Pitch, dt)
|
||||
--dpi.DRoll=numderiv(dpm.Roll, dpp.Roll, dt)
|
||||
dpi.DYaw=numderiv(dpm.Yaw, dpp.Yaw, dt)
|
||||
|
||||
-- Roll shortcuts.
|
||||
local r1=dpm.Roll
|
||||
local r2=dpp.Roll
|
||||
|
||||
-- Put roll in [0,360)
|
||||
if (r1<0) then
|
||||
r1=r1+360
|
||||
end
|
||||
if (r2<0) then
|
||||
r2=r2+360
|
||||
end
|
||||
|
||||
-- Handle case where 360 deg periodicity strikes.
|
||||
if r1<90 and r2>270 then
|
||||
r1=r1+360
|
||||
end
|
||||
if r1>270 and r2<90 then
|
||||
r2=r2+360
|
||||
end
|
||||
|
||||
--
|
||||
dpi.DRoll=numderiv(r1, r2, dt)
|
||||
|
||||
local ang=UTILS.VecAngle(dpm.o, dpp.o)
|
||||
dpi.omega=numderiv(0, ang, dt)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Record data
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data table.
|
||||
function FMD:_RecordData(playerData)
|
||||
|
||||
-- Check if we are recording already.
|
||||
if not playerData.recording then
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording starts for %.1f sec.", playerData.rd)
|
||||
MESSAGE:New(text, 3, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- Activate recording switch.
|
||||
playerData.recording=true
|
||||
end
|
||||
|
||||
-- Check if unit is alive.
|
||||
if playerData.unit and playerData.unit:IsAlive() then
|
||||
|
||||
-- Get data point.
|
||||
local dp=self:_GetDataPoint(playerData.unit)
|
||||
|
||||
-- Add data point to player table.
|
||||
table.insert(playerData.data, dp)
|
||||
|
||||
else
|
||||
-- Stop recording if player unit is not alive.
|
||||
self:_StopRecording(playerData.unitname)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Save data.
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data table.
|
||||
function FMD:_SaveData(playerData)
|
||||
|
||||
-- Nothing to save.
|
||||
if playerData==nil or #playerData.data==0 or not io then
|
||||
return
|
||||
end
|
||||
|
||||
--- Function that saves data to file
|
||||
local function _savefile(filename, data)
|
||||
local f = assert(io.open(filename, "wb"))
|
||||
f:write(data)
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
local path=self.savepath
|
||||
if lfs then
|
||||
path=path or lfs.writedir()
|
||||
end
|
||||
|
||||
-- Create unused file name.
|
||||
local filename=nil
|
||||
for i=1,9999 do
|
||||
|
||||
-- Create file name.
|
||||
filename=string.format("FMD-%s_%s-%04d.csv", playerData.name, playerData.actype, i)
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
-- Check if file exists.
|
||||
local _exists=UTILS.FileExists(filename)
|
||||
if not _exists then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Info
|
||||
local text=string.format("Saving player %s flight data to file %s", playerData.name, filename)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Header line
|
||||
local data="#Time,Altitude,Temperature,Pressure,Vtot,Vx,Vy,Vz,Atot,ax,ay,az,AoA,Pitch,dPitch/dt,Roll,dRoll/dt,Yaw,dYaw/dt,Turn Rate\n"
|
||||
|
||||
local g0=playerData.data[1] --#FMD.DataPoint
|
||||
local T0=g0.time
|
||||
|
||||
-- Calculate derivatives.
|
||||
self:_Derivative(playerData)
|
||||
|
||||
for i=2,#playerData.data-1 do
|
||||
|
||||
local dp=playerData.data[i] --#FMD.DataPoint
|
||||
|
||||
-- Conversion m/s == Mach.
|
||||
local ms2mach=0.00291545
|
||||
|
||||
local t=(dp.time-T0) or 0
|
||||
local a=dp.Alt or 0
|
||||
local b=dp.T or 0
|
||||
local c=dp.P or 0
|
||||
local d=dp.Vtot or 0
|
||||
local e=dp.v.x or 0
|
||||
local f=dp.v.y or 0
|
||||
local g=dp.v.z or 0
|
||||
local h=dp.Atot or 0
|
||||
local i=dp.a.x or 0
|
||||
local j=dp.a.y or 0
|
||||
local k=dp.a.z or 0
|
||||
local l=dp.AoA or 0
|
||||
local m=dp.Pitch or 0
|
||||
local n=dp.DPitch or 0
|
||||
local roll=dp.Roll or 0
|
||||
if roll<0 then
|
||||
roll=roll+360
|
||||
end
|
||||
local o=roll --dp.Roll or 0
|
||||
local p=dp.DRoll or 0
|
||||
local q=dp.Yaw or 0
|
||||
local r=dp.DYaw or 0
|
||||
local s=dp.omega or 0
|
||||
|
||||
-- Debug output.
|
||||
self:T3(t)
|
||||
self:T3(a)
|
||||
self:T3(b)
|
||||
self:T3(c)
|
||||
self:T3(d)
|
||||
self:T3(e)
|
||||
self:T3(f)
|
||||
self:T3(g)
|
||||
self:T3(h)
|
||||
self:T3(i)
|
||||
self:T3(j)
|
||||
self:T3(k)
|
||||
self:T3(l)
|
||||
self:T3(m)
|
||||
self:T3(n)
|
||||
self:T3(o)
|
||||
self:T3(p)
|
||||
self:T3(q)
|
||||
self:T3(r)
|
||||
self:T3(s)
|
||||
|
||||
-- t a b c d e f g h i j k l m n o p q r s
|
||||
data=data..string.format("%.2f,%.2f,%.2f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f\n",
|
||||
t, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
|
||||
|
||||
end
|
||||
|
||||
-- Save file.
|
||||
_savefile(filename, data)
|
||||
|
||||
-- Clear data.
|
||||
playerData.data={}
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- RADIO MENU Functions
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add menu commands for player.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_AddF10Commands(_unitName)
|
||||
self:F(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check for player unit.
|
||||
if _unit and playername then
|
||||
|
||||
-- Get group and ID.
|
||||
local group=_unit:GetGroup()
|
||||
local gid=group:GetID()
|
||||
|
||||
if group and gid then
|
||||
|
||||
if not self.menuadded[gid] then
|
||||
|
||||
-- Enable switch so we don't do this twice.
|
||||
self.menuadded[gid]=true
|
||||
|
||||
-- Set menu root path.
|
||||
local _rootPath=nil
|
||||
if FMD.MenuF10Root then
|
||||
------------------------
|
||||
-- MISSON LEVEL MENUE --
|
||||
------------------------
|
||||
|
||||
-- F10/FMD/...
|
||||
_rootPath=FMD.MenuF10Root
|
||||
|
||||
else
|
||||
------------------------
|
||||
-- GROUP LEVEL MENUES --
|
||||
------------------------
|
||||
|
||||
-- Main F10 menu: F10/FMD/
|
||||
if FMD.MenuF10[gid]==nil then
|
||||
FMD.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FMD")
|
||||
end
|
||||
|
||||
|
||||
-- F10/FMD/...
|
||||
_rootPath=FMD.MenuF10[gid]
|
||||
|
||||
end
|
||||
|
||||
--------------------------------
|
||||
-- F10/F<X> FMD/F1 Time Interval
|
||||
--------------------------------
|
||||
local _timePath=missionCommands.addSubMenuForGroup(gid, "Time Interval", _rootPath)
|
||||
-- F10/FMD/F1 Time Interval/
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=0.01 s", _timePath, self._SetTimeInterval, self, _unitName, 0.01)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=0.1 s", _timePath, self._SetTimeInterval, self, _unitName, 0.1)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=1.0 s", _timePath, self._SetTimeInterval, self, _unitName, 1.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=10 s", _timePath, self._SetTimeInterval, self, _unitName, 10.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=30 s", _timePath, self._SetTimeInterval, self, _unitName, 30.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=60 s", _timePath, self._SetTimeInterval, self, _unitName, 60.0)
|
||||
|
||||
-------------------------------
|
||||
-- F10/F<X> FMD/F1 Rec Duration
|
||||
-------------------------------
|
||||
local _durPath=missionCommands.addSubMenuForGroup(gid, "Rec Duration", _rootPath)
|
||||
-- F10/FMD/F1 Rec Duration/
|
||||
missionCommands.addCommandForGroup(gid, "T=10 s", _durPath, self._SetRecDuration, self, _unitName, 10)
|
||||
missionCommands.addCommandForGroup(gid, "T=30 s", _durPath, self._SetRecDuration, self, _unitName, 30)
|
||||
missionCommands.addCommandForGroup(gid, "T=60 s", _durPath, self._SetRecDuration, self, _unitName, 60)
|
||||
missionCommands.addCommandForGroup(gid, "T=5 min", _durPath, self._SetRecDuration, self, _unitName, 5*60)
|
||||
missionCommands.addCommandForGroup(gid, "T=10 min", _durPath, self._SetRecDuration, self, _unitName, 10*60)
|
||||
|
||||
--------------------------------
|
||||
-- F10/F<X> FMD/
|
||||
--------------------------------
|
||||
|
||||
-- F10/FMD/Start Recording
|
||||
missionCommands.addCommandForGroup(gid, "Start Recording", _rootPath, self._StartRecording, self, _unitName) -- F1
|
||||
missionCommands.addCommandForGroup(gid, "Stop Recording", _rootPath, self._StopRecording, self, _unitName) -- F2
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set recording duration.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
-- @param #number rd Recording duration in sec.
|
||||
function FMD:_SetRecDuration(_unitName, rd)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Set dt.
|
||||
playerData.rd=rd
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording duration set to %.1f sec.", playerData.rd)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set time interval for data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
-- @param #number dt Time interval in seconds.
|
||||
function FMD:_SetTimeInterval(_unitName, dt)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Set dt.
|
||||
playerData.dt=dt
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording time interval set to %.3f sec.", playerData.dt)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Start data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_StartRecording(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Delay before recording starts.
|
||||
local delay=3
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording will be started in %d seconds with %.3f sec timestep for %.1f sec.", delay, playerData.dt, playerData.rd)
|
||||
MESSAGE:New(text, 3, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- Start scheduler.
|
||||
playerData.SID=playerData.scheduler:Schedule(nil, self._RecordData, {self, playerData}, delay, playerData.dt, 0.0)
|
||||
|
||||
-- Stop scheduler once.
|
||||
playerData.scheduler:ScheduleOnce(playerData.rd+delay, self._StopRecording, self,_unitName)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Stop data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_StopRecording(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
local ndata=#playerData.data
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording stopped. Data points recorded: %d", ndata)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- No recording switch.
|
||||
playerData.recording=false
|
||||
|
||||
-- Stop scheduler.
|
||||
playerData.scheduler:Stop(playerData.SID)
|
||||
|
||||
-- Save data.
|
||||
self:_SaveData(playerData)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of the player unit.
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @return #string Name of the player or nil.
|
||||
function FMD:_GetPlayerUnitAndName(_unitName)
|
||||
self:F2(_unitName)
|
||||
|
||||
if _unitName ~= nil then
|
||||
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit=Unit.getByName(_unitName)
|
||||
|
||||
if DCSunit then
|
||||
|
||||
local playername=DCSunit:getPlayerName()
|
||||
local unit=UNIT:Find(DCSunit)
|
||||
|
||||
self:T2({DCSunit=DCSunit, unit=unit, playername=playername})
|
||||
if DCSunit and unit and playername then
|
||||
return unit, playername
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Return nil if we could not find a player.
|
||||
return nil,nil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,28 @@
|
||||
--- **Functional** -- Train missile defence and deflection.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range °
|
||||
-- * Provide alerts when a missile would have killed your aircraft.
|
||||
-- * Provide alerts when the missile self destructs.
|
||||
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Uses the MOOSE 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.
|
||||
--
|
||||
--
|
||||
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
|
||||
--
|
||||
--
|
||||
-- * **Messages**: Menu to configure all messages.
|
||||
-- * **Messages On**: Show all messages.
|
||||
-- * **Messages Off**: Disable all messages.
|
||||
@@ -45,23 +45,23 @@
|
||||
-- * **Range Off**: Disable range information when a missile is fired to a target.
|
||||
-- * **Bearing On**: Shows bearing information when a missile is fired to a target.
|
||||
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
|
||||
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
|
||||
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
|
||||
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- Danny has shared his ideas and together we made a design.
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- 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!
|
||||
-- * **132nd Squadron**: Testing and optimizing the logic.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.MissileTrainer
|
||||
@@ -76,7 +76,7 @@
|
||||
---
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
--
|
||||
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
|
||||
@@ -84,7 +84,7 @@
|
||||
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
|
||||
--
|
||||
-- # Initialization:
|
||||
--
|
||||
--
|
||||
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
|
||||
@@ -97,8 +97,8 @@
|
||||
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
MISSILETRAINER = {
|
||||
ClassName = "MISSILETRAINER",
|
||||
TrackingMissiles = {},
|
||||
@@ -167,7 +167,7 @@ end
|
||||
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @return #MISSILETRAINER
|
||||
function MISSILETRAINER:New( Distance, Briefing )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
@@ -194,8 +194,8 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
-- self:F( "ForEach:" .. Client.UnitName )
|
||||
-- Client:Alive( self._Alive, self )
|
||||
-- end
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
function( Client )
|
||||
self:F( "ForEach:" .. Client.UnitName )
|
||||
Client:Alive( self._Alive, self )
|
||||
@@ -207,9 +207,9 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
-- self.DB:ForEachClient(
|
||||
-- --- @param Wrapper.Client#CLIENT Client
|
||||
-- function( Client )
|
||||
--
|
||||
--
|
||||
-- ... actions ...
|
||||
--
|
||||
--
|
||||
-- end
|
||||
-- )
|
||||
|
||||
@@ -225,7 +225,7 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
|
||||
self.DetailsRangeOnOff = true
|
||||
self.DetailsBearingOnOff = true
|
||||
|
||||
|
||||
self.MenusOnOff = true
|
||||
|
||||
self.TrackingMissiles = {}
|
||||
@@ -293,7 +293,7 @@ end
|
||||
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
|
||||
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
|
||||
self:F( TrackingFrequency )
|
||||
@@ -478,30 +478,30 @@ function MISSILETRAINER:OnEventShot( EVentData )
|
||||
if TrainerTargetDCSUnit then
|
||||
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
|
||||
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
|
||||
|
||||
|
||||
self:T(TrainerTargetDCSUnitName )
|
||||
|
||||
|
||||
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
|
||||
if Client then
|
||||
|
||||
|
||||
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
|
||||
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
|
||||
|
||||
|
||||
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
|
||||
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched a %s",
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerWeaponName
|
||||
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
|
||||
|
||||
|
||||
if self.AlertsToAll then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local ClientID = Client:GetID()
|
||||
self:T( ClientID )
|
||||
local MissileData = {}
|
||||
@@ -579,52 +579,52 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
end
|
||||
|
||||
-- ALERTS PART
|
||||
|
||||
|
||||
-- Loop for all Player Clients to check the alerts and deletion of missiles.
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
local Client = ClientData.Client
|
||||
|
||||
|
||||
if Client and Client:IsAlive() then
|
||||
|
||||
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
|
||||
self:T3( MissileDataID )
|
||||
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
local PositionMissile = TrainerWeapon:getPosition().p
|
||||
local TargetVec3 = Client:GetVec3()
|
||||
|
||||
|
||||
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
|
||||
( PositionMissile.y - TargetVec3.y )^2 +
|
||||
( PositionMissile.z - TargetVec3.z )^2
|
||||
) ^ 0.5 / 1000
|
||||
|
||||
|
||||
if Distance <= self.Distance then
|
||||
-- Hit alert
|
||||
TrainerWeapon:destroy()
|
||||
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
|
||||
|
||||
|
||||
self:T( "killed" )
|
||||
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched by %s killed %s",
|
||||
TrainerWeapon:getTypeName(),
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerTargetUnit:GetPlayerName()
|
||||
), 15, "Hit Alert" )
|
||||
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
|
||||
|
||||
MissileData = nil
|
||||
table.remove( ClientData.MissileData, MissileDataID )
|
||||
self:T(ClientData.MissileData)
|
||||
@@ -639,7 +639,7 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
TrainerWeaponTypeName,
|
||||
TrainerSourceUnit:GetTypeName()
|
||||
), 5, "Tracking" )
|
||||
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
@@ -660,41 +660,41 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
|
||||
|
||||
-- TRACKING PART
|
||||
|
||||
|
||||
-- For the current client, the missile range and bearing details are displayed To the Player Client.
|
||||
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- Main Player Client loop
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
|
||||
local Client = ClientData.Client
|
||||
--self:T2( { Client:GetName() } )
|
||||
|
||||
|
||||
|
||||
|
||||
ClientData.MessageToClient = ""
|
||||
ClientData.MessageToAll = ""
|
||||
|
||||
|
||||
-- Other Players Client loop
|
||||
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
|
||||
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
|
||||
--self:T3( MissileDataID )
|
||||
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
|
||||
|
||||
if ShowMessages == true then
|
||||
local TrackingTo
|
||||
TrackingTo = string.format( " -> %s",
|
||||
TrainerWeaponTypeName
|
||||
)
|
||||
|
||||
|
||||
if ClientDataID == TrackingDataID then
|
||||
if ClientData.MessageToClient == "" then
|
||||
ClientData.MessageToClient = "Missiles to You:\n"
|
||||
@@ -712,7 +712,7 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
|
||||
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
|
||||
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,855 +0,0 @@
|
||||
--- **Ops** - (R2.5) - Random Air Traffic.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- RAT2 creates random air traffic on the map.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * It's very random.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.Rat2
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
|
||||
--- RAT2 class.
|
||||
-- @type RAT2
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table Qcraft Table of rat crafts.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The RAT2 Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #RAT2
|
||||
RAT2 = {
|
||||
ClassName = "RAT2",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
Qcraft = {},
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new RAT2 class object.
|
||||
-- @param #RAT2 self
|
||||
-- @return #RAT2 self.
|
||||
function RAT2:New()
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #RAT2
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
self.lid="RAT2 | "
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Load", "Stopped") -- Load player scores from file.
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script.
|
||||
self:AddTransition("*", "Status", "*") -- Start RAT2 script.
|
||||
self:AddTransition("*", "Spawned", "*") -- A group was spawned.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new RAT2 class object.
|
||||
-- @param #RAT2 self
|
||||
-- @param #RATAC ratcraft The aircraft to add.
|
||||
-- @return #RAT2 self.
|
||||
function RAT2:AddAircraft(ratcraft)
|
||||
|
||||
-- Add ratcraft to spawn queue.
|
||||
table.insert(self.Qcraft, ratcraft)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Start random air traffic.
|
||||
-- @param #RAT2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function RAT2:onafterStart(From, Event, To)
|
||||
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RAT2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function RAT2:onafterStatus(From, Event, To)
|
||||
|
||||
self:I(string.format("RAT status %s", self:GetState()))
|
||||
|
||||
-- Check queue of aircraft to spawn.
|
||||
self:_CheckQueueSpawn()
|
||||
|
||||
self:__Status(-10)
|
||||
end
|
||||
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RAT2 self
|
||||
function RAT2:_CheckQueueSpawn()
|
||||
|
||||
self:I(string.format("Checking flights"))
|
||||
|
||||
-- Loop over all ratcraft.
|
||||
for i,_ratcraft in pairs(self.Qcraft) do
|
||||
local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT
|
||||
|
||||
-- Get number of alive groups.
|
||||
local Nalive=ratcraft:_GetAliveGroups()
|
||||
|
||||
for _,_flight in pairs(ratcraft.flights) do
|
||||
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
|
||||
|
||||
self:I(string.format("Checking flight group %s in status %s", flight.groupname, flight:GetState()))
|
||||
|
||||
if flight:IsInUtero() or flight:IsArrived() or flight:IsDead() then
|
||||
|
||||
self:I(string.format("Try Spawning new flight group %s in status %s", flight.groupname, flight:GetState()))
|
||||
|
||||
-- Get departure and parking.
|
||||
local departure, parking=ratcraft:GetDeparture()
|
||||
|
||||
-- Get destination depending on departure.
|
||||
local destination=ratcraft:GetDestination(departure)
|
||||
|
||||
if departure and destination and parking then
|
||||
|
||||
self:I(string.format("Spawning new flight group %s in status %s", flight.groupname, flight:GetState()))
|
||||
|
||||
-- Try to spawn a ratcraft group.
|
||||
local group=self:_SpawnRatcraft(ratcraft, flight, departure, destination, parking)
|
||||
|
||||
-- Remove queue item and break loop.
|
||||
if group then
|
||||
|
||||
-- We add a little delay
|
||||
self:__Spawned(0.1, group, flight, ratcraft)
|
||||
|
||||
break
|
||||
end -- if group
|
||||
|
||||
end -- if departure
|
||||
end -- if spawn flight
|
||||
end -- for flights
|
||||
end -- for ratcraft
|
||||
end
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RAT2 self
|
||||
function RAT2:_CheckArrived()
|
||||
|
||||
for _,_ratcraft in pairs(self.Qcraft) do
|
||||
local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT
|
||||
for _,_flight in pairs(ratcraft.flights) do
|
||||
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
|
||||
|
||||
if flight:IsArrived() then
|
||||
-- Destroy group and create a remove unit event.
|
||||
flight.group:Destroy(nil)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function called after a RAT group was spawned.
|
||||
-- @param #RAT2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Wrapper.Group#GROUP group The spawned group.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP flight The flight group.
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft The ratcraft object.
|
||||
function RAT2:onafterSpawned(From, Event, To, group, flight, ratcraft)
|
||||
|
||||
group:SmokeGreen()
|
||||
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Spawn functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse.
|
||||
-- @param #RAT2 self
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft to spawn.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group.
|
||||
-- @param Functional.RatCraft#RATCRAFT.Departure departure Departure.
|
||||
-- @param Functional.RatCraft#RATCRAFT.Departure destination Destination.
|
||||
-- @param #table parking Parking data for this group.
|
||||
-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned.
|
||||
function RAT2:_SpawnRatcraft(ratcraft, flight, departure, destination, parking)
|
||||
|
||||
-- Prepare the spawn template.
|
||||
local template=self:_SpawnAssetPrepareTemplate(ratcraft, flight.groupname)
|
||||
|
||||
-- Get flight path if the group goes to another warehouse by itself.
|
||||
template.route.points=self:_GetFlightplan(ratcraft, AIRBASE:FindByName(departure.name), AIRBASE:FindByName(destination.name))
|
||||
|
||||
local airbase=AIRBASE:FindByName(departure.name) --self:_GetAirbase(departure)
|
||||
|
||||
-- Get airbase ID and category.
|
||||
local AirbaseID = airbase:GetID()
|
||||
local AirbaseCategory = airbase:GetAirbaseCategory()
|
||||
|
||||
-- Check enough parking spots.
|
||||
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
|
||||
|
||||
--TODO Figure out what's necessary in this case.
|
||||
|
||||
else
|
||||
|
||||
if #parking<#template.units then
|
||||
local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units)
|
||||
self:_DebugMessage(text)
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Position the units.
|
||||
for i=1,#template.units do
|
||||
|
||||
-- Unit template.
|
||||
local unit = template.units[i]
|
||||
|
||||
if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then
|
||||
|
||||
-- Helipads we take the position of the airbase location, since the exact location of the spawn point does not make sense.
|
||||
local coord=airbase:GetCoordinate()
|
||||
|
||||
unit.x=coord.x
|
||||
unit.y=coord.z
|
||||
unit.alt=coord.y
|
||||
|
||||
unit.parking_id = nil
|
||||
unit.parking = nil
|
||||
|
||||
else
|
||||
|
||||
local coord=parking[i].Coordinate --Core.Point#COORDINATE
|
||||
local terminal=parking[i].TerminalID --#number
|
||||
|
||||
if self.Debug then
|
||||
coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal))
|
||||
end
|
||||
|
||||
unit.x=coord.x
|
||||
unit.y=coord.z
|
||||
unit.alt=coord.y
|
||||
|
||||
unit.parking_id = nil
|
||||
unit.parking = terminal
|
||||
|
||||
end
|
||||
|
||||
if destination.parking then
|
||||
local spot=destination.parking[i] --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
unit.parking_landing=spot.TerminalID
|
||||
end
|
||||
|
||||
if ratcraft.livery==nil and #ratcraft.liveries>0 then
|
||||
ratcraft.livery=ratcraft.liveries[math.random(#ratcraft.liveries)]
|
||||
end
|
||||
|
||||
-- Set livery.
|
||||
if ratcraft.livery then
|
||||
unit.livery_id = ratcraft.livery
|
||||
end
|
||||
|
||||
-- Set skill.
|
||||
if ratcraft.skill then
|
||||
unit.skill= ratcraft.skill
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- And template position.
|
||||
template.x = template.units[1].x
|
||||
template.y = template.units[1].y
|
||||
|
||||
-- Uncontrolled spawning.
|
||||
template.uncontrolled=ratcraft.uncontrolled
|
||||
|
||||
-- Debug info.
|
||||
self:T2({airtemplate=template})
|
||||
|
||||
-- Spawn group.
|
||||
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
|
||||
|
||||
return group
|
||||
end
|
||||
|
||||
|
||||
--- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids.
|
||||
-- @param #RAT2 self
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Aircraft that will be spawned.
|
||||
-- @param #string groupname Name for the group to be spawned.
|
||||
-- @return #table Prepared new spawn template.
|
||||
function RAT2:_SpawnAssetPrepareTemplate(ratcraft, groupname)
|
||||
|
||||
-- Create an own copy of the template!
|
||||
local template=UTILS.DeepCopy(ratcraft.template)
|
||||
|
||||
-- Set unique name.
|
||||
template.name=groupname
|
||||
|
||||
-- Set current(!) coalition and country.
|
||||
template.CoalitionID=ratcraft.coalition
|
||||
template.CountryID=ratcraft.country
|
||||
|
||||
-- Nillify the group ID.
|
||||
template.groupId=nil
|
||||
|
||||
-- No late activation.
|
||||
template.lateActivation=false
|
||||
|
||||
-- Set and empty route.
|
||||
template.route = {}
|
||||
template.route.routeRelativeTOT=true
|
||||
template.route.points = {}
|
||||
|
||||
-- Handle units.
|
||||
for i=1,#template.units do
|
||||
|
||||
-- Unit template.
|
||||
local unit=template.units[i]
|
||||
|
||||
-- Nillify the unit ID.
|
||||
unit.unitId=nil
|
||||
|
||||
-- Set unit name: <alias>-01, <alias>-02, ...
|
||||
unit.name=string.format("%s-%02d", template.name , i)
|
||||
|
||||
end
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
--@param #RCRAFT.Attribute _attribute Generlized attribute of unit.
|
||||
|
||||
--- Get the proper terminal type based on generalized attribute of the group.
|
||||
--@param #RAT2 self
|
||||
--@param #number _category Airbase category.
|
||||
--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group.
|
||||
function RAT2:_GetTerminal(_attribute, _category)
|
||||
|
||||
-- Default terminal is "large".
|
||||
local _terminal=AIRBASE.TerminalType.OpenBig
|
||||
|
||||
if _attribute==RCRAFT.Attribute.FIGHTER then
|
||||
-- Fighter ==> small.
|
||||
_terminal=AIRBASE.TerminalType.FighterAircraft
|
||||
elseif _attribute==RCRAFT.Attribute.BOMBER or _attribute==RCRAFT.Attribute.TRANSPORTPLANE or _attribute==RCRAFT.Attribute.TANKER or _attribute==RCRAFT.Attribute.AWACS then
|
||||
-- Bigger aircraft.
|
||||
_terminal=AIRBASE.TerminalType.OpenBig
|
||||
elseif _attribute==RCRAFT.Attribute.TRANSPORTHELO or _attribute==RCRAFT.Attribute.ATTACKHELO then
|
||||
-- Helicopter.
|
||||
_terminal=AIRBASE.TerminalType.HelicopterUsable
|
||||
else
|
||||
--_terminal=AIRBASE.TerminalType.OpenMedOrBig
|
||||
end
|
||||
|
||||
-- For ships, we allow medium spots for all fixed wing aircraft. There are smaller tankers and AWACS aircraft that can use a carrier.
|
||||
if _category==Airbase.Category.SHIP then
|
||||
if not (_attribute==RCRAFT.Attribute.TRANSPORTHELO or _attribute==RCRAFT.Attribute.ATTACKHELO) then
|
||||
_terminal=AIRBASE.TerminalType.OpenMedOrBig
|
||||
end
|
||||
end
|
||||
|
||||
return _terminal
|
||||
end
|
||||
|
||||
|
||||
--- Seach unoccupied parking spots at the airbase for a list of assets. For each asset group a list of parking spots is returned.
|
||||
-- During the search also the not yet spawned asset aircraft are considered.
|
||||
-- If not enough spots for all asset units could be found, the routine returns nil!
|
||||
-- @param #RAT2 self
|
||||
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots.
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft.
|
||||
-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID.
|
||||
function RAT2:_FindParking(airbase, ratcraft)
|
||||
|
||||
-- Init default
|
||||
local scanradius=50
|
||||
local scanunits=true
|
||||
local scanstatics=true
|
||||
local scanscenery=false
|
||||
local verysafe=false
|
||||
|
||||
-- Function calculating the overlap of two (square) objects.
|
||||
local function _overlap(l1,l2,dist)
|
||||
local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance!
|
||||
local safe = (dist > safedist)
|
||||
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe)))
|
||||
return safe
|
||||
end
|
||||
|
||||
-- Get parking spot data table. This contains all free and "non-free" spots.
|
||||
local parkingdata=airbase:GetParkingSpotsTable()
|
||||
|
||||
-- List of obstacles.
|
||||
local obstacles={}
|
||||
|
||||
-- Loop over all parking spots and get the currently present obstacles.
|
||||
-- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay!
|
||||
for _,parkingspot in pairs(parkingdata) do
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
|
||||
-- Scan a radius of 100 meters around the spot.
|
||||
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery)
|
||||
|
||||
-- Check all units.
|
||||
for _,_unit in pairs(_units) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
local _coord=unit:GetCoordinate()
|
||||
local _size=self:_GetObjectSize(unit:GetDCSObject())
|
||||
local _name=unit:GetName()
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"})
|
||||
end
|
||||
|
||||
-- Check all statics.
|
||||
for _,static in pairs(_statics) do
|
||||
local _vec3=static:getPoint()
|
||||
local _coord=COORDINATE:NewFromVec3(_vec3)
|
||||
local _name=static:getName()
|
||||
local _size=self:_GetObjectSize(static)
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"})
|
||||
end
|
||||
|
||||
-- Check all scenery.
|
||||
for _,scenery in pairs(_sceneries) do
|
||||
local _vec3=scenery:getPoint()
|
||||
local _coord=COORDINATE:NewFromVec3(_vec3)
|
||||
local _name=scenery:getTypeName()
|
||||
local _size=self:_GetObjectSize(scenery)
|
||||
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
|
||||
end
|
||||
|
||||
-- TODO check clients. Clients cannot be spawned. So we can loop over them.
|
||||
|
||||
end
|
||||
|
||||
-- Parking data for all assets.
|
||||
local parking={}
|
||||
|
||||
-- Get terminal type of this asset
|
||||
local terminaltype=self:_GetTerminal(ratcraft.attribute, airbase:GetAirbaseCategory())
|
||||
|
||||
local Nunits=#ratcraft.templategroup:GetUnits()
|
||||
|
||||
-- Loop over all units - each one needs a spot.
|
||||
for i=1,Nunits do
|
||||
|
||||
-- Loop over all parking spots.
|
||||
local gotit=false
|
||||
for _,_parkingspot in pairs(parkingdata) do
|
||||
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
-- Check correct terminal type for asset. We don't want helos in shelters etc.
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
local _toac=parkingspot.TOAC
|
||||
|
||||
--env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles))
|
||||
|
||||
local free=true
|
||||
local problem=nil
|
||||
|
||||
-- Safe parking using TO_AC from DCS result.
|
||||
if self.safeparking and _toac then
|
||||
free=false
|
||||
self:T("Parking spot %d is occupied by other aircraft taking off or landing.", _termid)
|
||||
end
|
||||
|
||||
-- Loop over all obstacles.
|
||||
for _,obstacle in pairs(obstacles) do
|
||||
|
||||
-- Check if aircraft overlaps with any obstacle.
|
||||
local dist=_spot:Get2DDistance(obstacle.coord)
|
||||
local safe=_overlap(ratcraft.size, obstacle.size, dist)
|
||||
|
||||
-- Spot is blocked.
|
||||
if not safe then
|
||||
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", _asset.templatename, _asset.uid, _termid, dist))
|
||||
free=false
|
||||
problem=obstacle
|
||||
problem.dist=dist
|
||||
break
|
||||
else
|
||||
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", _asset.templatename, _asset.uid, _termid, dist))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if spot is free
|
||||
if free then
|
||||
|
||||
-- Add parkingspot for this asset unit.
|
||||
table.insert(parking, parkingspot)
|
||||
|
||||
self:T(self.lid..string.format("Parking spot #%d is free for ratcraft unit id=%d!", _termid, i))
|
||||
|
||||
-- Add the unit as obstacle so that this spot will not be available for the next unit.
|
||||
table.insert(obstacles, {coord=_spot, size=ratcraft.size, name=ratcraft.templatename, type="ratcraft"})
|
||||
|
||||
-- Break loop over parking spots.
|
||||
gotit=true
|
||||
break
|
||||
|
||||
else
|
||||
|
||||
-- Debug output for occupied spots.
|
||||
self:T(self.lid..string.format("Parking spot #%d is occupied or not big enough!", _termid))
|
||||
if self.Debug then
|
||||
local coord=problem.coord --Core.Point#COORDINATE
|
||||
local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist)
|
||||
coord:MarkToAll(string.format(text))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end -- check terminal type
|
||||
end -- loop over parking spots
|
||||
|
||||
-- No parking spot for at least one unit :(
|
||||
if not gotit then
|
||||
self:T(self.lid..string.format("WARNING: No free parking spot for ratcraft unit i=%d", i))
|
||||
return nil
|
||||
end
|
||||
|
||||
end -- loop over units
|
||||
|
||||
return parking
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Flightplan functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Calculate the maximum height an aircraft can reach for the given parameters.
|
||||
-- @param #RAT2 self
|
||||
-- @param #number D Total distance in meters from Departure to holding point at destination.
|
||||
-- @param #number alphaC Climb angle in rad.
|
||||
-- @param #number alphaD Descent angle in rad.
|
||||
-- @param #number Hdep AGL altitude of departure point.
|
||||
-- @param #number Hdest AGL altitude of destination point.
|
||||
-- @param #number Deltahhold Relative altitude of holding point above destination.
|
||||
-- @return #number Maximum height the aircraft can reach.
|
||||
function RAT2:_GetMaxHeight(D, alphaC, alphaD, Hdep, Hdest, Deltahhold)
|
||||
|
||||
local Hhold=Hdest+Deltahhold
|
||||
local hdest=Hdest-Hdep
|
||||
local hhold=hdest+Deltahhold
|
||||
|
||||
local Dp=math.sqrt(D^2 + hhold^2)
|
||||
|
||||
local alphaS=math.atan(hdest/D) -- slope angle
|
||||
local alphaH=math.atan(hhold/D) -- angle to holding point (could be necative!)
|
||||
|
||||
local alphaCp=alphaC-alphaH -- climb angle with slope
|
||||
local alphaDp=alphaD+alphaH -- descent angle with slope
|
||||
|
||||
-- ASA triangle.
|
||||
local gammap=math.pi-alphaCp-alphaDp
|
||||
local sCp=Dp*math.sin(alphaDp)/math.sin(gammap)
|
||||
local sDp=Dp*math.sin(alphaCp)/math.sin(gammap)
|
||||
|
||||
-- Max height from departure.
|
||||
local hmax=sCp*math.sin(alphaC)
|
||||
|
||||
return hmax
|
||||
end
|
||||
|
||||
|
||||
--- Make a flight plan from a departure to a destination airport.
|
||||
-- @param #RAT2 self
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft object.
|
||||
-- @param Wrapper.Airbase#AIRBASE departure Departure airbase.
|
||||
-- @param Wrapper.Airbase#AIRBASE destination Destination airbase.
|
||||
-- @return #table Table of flightplan waypoints.
|
||||
-- @return #table Table of flightplan coordinates.
|
||||
function RAT2:_GetFlightplan(ratcraft, departure, destination)
|
||||
|
||||
-- Parameters
|
||||
local Vmax=ratcraft.speedmax
|
||||
local Range=ratcraft.range
|
||||
local category=ratcraft.category
|
||||
local ceiling=ratcraft.DCSdesc.Hmax
|
||||
local Vymax=ratcraft.DCSdesc.VyMax
|
||||
|
||||
-- Max cruise speed 90% of max speed.
|
||||
local VxCruiseMax=0.90*Vmax
|
||||
|
||||
-- Min cruise speed 70% of max cruise or 600 km/h whichever is lower.
|
||||
local VxCruiseMin = math.min(VxCruiseMax*0.75, 750)
|
||||
|
||||
-- Cruise speed (randomized). Expectation value at midpoint between min and max.
|
||||
local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax)
|
||||
|
||||
-- Climb speed 90% ov Vmax but max 720 km/h.
|
||||
local VxClimb = math.min(Vmax*0.90, 720)
|
||||
|
||||
-- Descent speed 60% of Vmax but max 500 km/h.
|
||||
local VxDescent = math.min(Vmax*0.60, 500)
|
||||
|
||||
-- Holding speed is 90% of descent speed.
|
||||
local VxHolding = VxDescent*0.9
|
||||
|
||||
-- Final leg is 90% of holding speed.
|
||||
local VxFinal = VxHolding*0.9
|
||||
|
||||
-- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate.
|
||||
local VyClimb=math.min(7.6, Vymax)
|
||||
|
||||
-- Climb angle in rad.
|
||||
local AlphaClimb=math.rad(4)
|
||||
|
||||
-- Descent angle in rad. Moderate 4 degrees.
|
||||
local AlphaDescent=math.rad(4)
|
||||
|
||||
-- Expected cruise level (peak of Gaussian distribution)
|
||||
local FLcruise_expect=150*RAT.unit.FL2m
|
||||
if category==Group.Category.HELICOPTER then
|
||||
FLcruise_expect=1000 -- 1000 m ASL
|
||||
end
|
||||
|
||||
-------------------------
|
||||
--- DEPARTURE AIRPORT ---
|
||||
-------------------------
|
||||
|
||||
-- Coordinates of departure point.
|
||||
local Pdeparture=departure:GetCoordinate()
|
||||
|
||||
-- Height ASL of departure point.
|
||||
local H_departure=Pdeparture.y
|
||||
|
||||
---------------------------
|
||||
--- DESTINATION AIRPORT ---
|
||||
---------------------------
|
||||
|
||||
-- Position of destination airport.
|
||||
local Pdestination=destination:GetCoordinate()
|
||||
|
||||
-- Height ASL of destination airport/zone.
|
||||
local H_destination=Pdestination.y
|
||||
|
||||
-----------------------------
|
||||
--- DESCENT/HOLDING POINT ---
|
||||
-----------------------------
|
||||
|
||||
-- Get a random point between 5 and 10 km away from the destination.
|
||||
local Rhmin=5000
|
||||
local Rhmax=10000
|
||||
|
||||
-- For helos we set a distance between 500 to 1000 m.
|
||||
if category==Group.Category.HELICOPTER then
|
||||
Rhmin=500
|
||||
Rhmax=1000
|
||||
end
|
||||
|
||||
-- Coordinates of the holding point. y is the land height at that point.
|
||||
local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax, Rhmin)
|
||||
|
||||
-- Distance from holding point to final destination (not used).
|
||||
local d_holding=Pholding:Get2DDistance(Pdestination)
|
||||
|
||||
-- AGL height of holding point.
|
||||
local H_holding=Pholding.y
|
||||
|
||||
---------------
|
||||
--- GENERAL ---
|
||||
---------------
|
||||
|
||||
-- We go directly to the holding point not the destination airport. From there, planes are guided by DCS to final approach.
|
||||
local heading=Pdeparture:HeadingTo(Pholding)
|
||||
local d_total=Pdeparture:Get2DDistance(Pholding)
|
||||
|
||||
------------------------------
|
||||
--- Holding Point Altitude ---
|
||||
------------------------------
|
||||
|
||||
-- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL.
|
||||
local h_holding=1200
|
||||
if category==Group.Category.HELICOPTER then
|
||||
h_holding=150
|
||||
end
|
||||
h_holding=UTILS.Randomize(h_holding, 0.2)
|
||||
|
||||
-- Max holding altitude.
|
||||
local DeltaholdingMax=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, 0)
|
||||
|
||||
if h_holding>DeltaholdingMax then
|
||||
h_holding=math.abs(DeltaholdingMax)
|
||||
end
|
||||
|
||||
-- This is the height ASL of the holding point we want to fly to.
|
||||
local Hh_holding=H_holding+h_holding
|
||||
|
||||
---------------------------
|
||||
--- Max Flight Altitude ---
|
||||
---------------------------
|
||||
|
||||
-- Get max flight altitude relative to H_departure.
|
||||
local h_max=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, h_holding)
|
||||
|
||||
-- Max flight level ASL aircraft can reach for given angles and distance.
|
||||
local FLmax = h_max+H_departure
|
||||
|
||||
--CRUISE
|
||||
-- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
|
||||
local FLmin=math.max(H_departure, Hh_holding)
|
||||
|
||||
-- Ensure that FLmax not above its service ceiling.
|
||||
FLmax=math.min(FLmax, ceiling)
|
||||
|
||||
-- If the route is very short we set FLmin a bit lower than FLmax.
|
||||
if FLmin>FLmax then
|
||||
FLmin=FLmax
|
||||
end
|
||||
|
||||
-- Expected cruise altitude - peak of gaussian distribution.
|
||||
if FLcruise_expect<FLmin then
|
||||
FLcruise_expect=FLmin
|
||||
end
|
||||
if FLcruise_expect>FLmax then
|
||||
FLcruise_expect=FLmax
|
||||
end
|
||||
|
||||
-- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax.
|
||||
local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax)
|
||||
|
||||
-- Climb and descent heights.
|
||||
local h_climb = FLcruise - H_departure
|
||||
local h_descent = FLcruise - Hh_holding
|
||||
|
||||
-- Get distances.
|
||||
local d_climb = h_climb/math.tan(AlphaClimb)
|
||||
local d_descent = h_descent/math.tan(AlphaDescent)
|
||||
local d_cruise = d_total-d_climb-d_descent
|
||||
|
||||
-- Debug.
|
||||
local text=string.format("Flight plan:\n")
|
||||
text=text..string.format("Vx max = %.2f km/h\n", Vmax)
|
||||
text=text..string.format("Vx climb = %.2f km/h\n", VxClimb*3.6)
|
||||
text=text..string.format("Vx cruise = %.2f km/h\n", VxCruise*3.6)
|
||||
text=text..string.format("Vx descent = %.2f km/h\n", VxDescent*3.6)
|
||||
text=text..string.format("Vx holding = %.2f km/h\n", VxHolding*3.6)
|
||||
text=text..string.format("Vx final = %.2f km/h\n", VxFinal*3.6)
|
||||
text=text..string.format("Vy max = %.2f m/s\n", Vymax)
|
||||
text=text..string.format("Vy climb = %.2f m/s\n", VyClimb)
|
||||
text=text..string.format("Alpha Climb = %.2f Deg\n", math.deg(AlphaClimb))
|
||||
text=text..string.format("Alpha Descent = %.2f Deg\n", math.deg(AlphaDescent))
|
||||
text=text..string.format("Dist climb = %.3f km\n", d_climb/1000)
|
||||
text=text..string.format("Dist cruise = %.3f km\n", d_cruise/1000)
|
||||
text=text..string.format("Dist descent = %.3f km\n", d_descent/1000)
|
||||
text=text..string.format("Dist total = %.3f km\n", d_total/1000)
|
||||
text=text..string.format("h_climb = %.3f km\n", h_climb/1000)
|
||||
text=text..string.format("h_desc = %.3f km\n", h_descent/1000)
|
||||
text=text..string.format("h_holding = %.3f km\n", h_holding/1000)
|
||||
text=text..string.format("h_max = %.3f km\n", h_max/1000)
|
||||
text=text..string.format("FL min = %.3f km\n", FLmin/1000)
|
||||
text=text..string.format("FL expect = %.3f km\n", FLcruise_expect/1000)
|
||||
text=text..string.format("FL cruise * = %.3f km\n", FLcruise/1000)
|
||||
text=text..string.format("FL max = %.3f km\n", FLmax/1000)
|
||||
text=text..string.format("Ceiling = %.3f km\n", ceiling/1000)
|
||||
text=text..string.format("Max range = %.3f km\n", Range/1000)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back.
|
||||
if d_cruise<0 then
|
||||
d_cruise=100
|
||||
end
|
||||
|
||||
------------------------
|
||||
--- Create Waypoints ---
|
||||
------------------------
|
||||
|
||||
-- Waypoints and coordinates
|
||||
local wp={}
|
||||
local c={}
|
||||
|
||||
--- Departure/Take-off
|
||||
c[#c+1]=Pdeparture
|
||||
wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true, departure, nil, "Departure")
|
||||
|
||||
--- Begin of Cruise
|
||||
local Pcruise=Pdeparture:Translate(d_climb, heading)
|
||||
Pcruise.y=FLcruise
|
||||
c[#c+1]=Pcruise
|
||||
wp[#wp+1]=Pcruise:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Cruise")
|
||||
|
||||
--- Descent
|
||||
local Pdescent=Pcruise:Translate(d_cruise, heading)
|
||||
Pdescent.y=FLcruise
|
||||
c[#c+1]=Pdescent
|
||||
wp[#wp+1]=Pdescent:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent")
|
||||
|
||||
--- Holding point
|
||||
Pholding.y=H_holding+h_holding
|
||||
c[#c+1]=Pholding
|
||||
wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding, true, nil, nil, "Holding")
|
||||
|
||||
--- Final destination.
|
||||
c[#c+1]=Pdestination
|
||||
wp[#wp+1]=Pdestination:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination")
|
||||
|
||||
|
||||
-- Mark points at waypoints for debugging.
|
||||
if self.Debug or true then
|
||||
for i,coord in pairs(c) do
|
||||
local coord=coord --Core.Point#COORDINATE
|
||||
local dist=0
|
||||
if i>1 then
|
||||
dist=coord:Get2DDistance(c[i-1])
|
||||
end
|
||||
coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i, dist/1000))
|
||||
end
|
||||
end
|
||||
|
||||
return wp,c
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,596 +0,0 @@
|
||||
--- **Functional** - (R2.5) - Random Air Traffic.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- RATCRAFT creates random air traffic on the map.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * It's very random.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.RatCraft
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
--- RATCRAFT
|
||||
--
|
||||
-- @type RATCRAFT
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debugmode Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #string templatename Name of the group template.
|
||||
-- @field #string alias Alias for spawn prefix. Default template group name.
|
||||
-- @field #table template Table of the group template.
|
||||
-- @field Wrapper.Group#GROUP templategroup Template group.
|
||||
-- @field #number coalition Coalition side.
|
||||
-- @field #number country Country number.
|
||||
-- @field #string actype Aircraft type name.
|
||||
-- @field #number category Category (plane=X, helo=Y).
|
||||
-- @field #string attribute Generalized attribute.
|
||||
-- @field #number ceiling Max altitude in meters of the aircraft group.
|
||||
-- @field #number speedmax Max speed in km/h of the aircraft group.
|
||||
-- @field #number sizex Size X-axis in meters.
|
||||
-- @field #number sizez Size Z-axis in meters.
|
||||
-- @field #number size Max size (X, Z) in meters.
|
||||
-- @field #table liveries Table of liveries.
|
||||
-- @field #string livery Current livery.
|
||||
-- @field #string skill Skill of group.
|
||||
-- @field #string onboard Onboard number. This should better be on UNIT level!
|
||||
-- @field #table departures Defined departures.
|
||||
-- @field #table departure Current departure airbase or zone.
|
||||
-- @field #table destinations Defined destinations.
|
||||
-- @field #table destination Current destination.
|
||||
-- @field #boolean commute If true, commute between to airbases/zones.
|
||||
-- @field #boolean journey If true, continue journey from the current destination.
|
||||
-- @field #string takeoff Takeoff type cold, hot, air.
|
||||
-- @field #string landing Landing type.
|
||||
-- @field #table flights Table of flight groups.
|
||||
-- @field #number Nspawn Number of max spawned aircraft groups.
|
||||
-- @field #number Nunits Number of units in group.
|
||||
-- @extends Core.Fsm#FSM
|
||||
RATCRAFT = {
|
||||
ClassName = "RATCRAFT",
|
||||
Debugmode = false,
|
||||
lid = nil,
|
||||
templatename = nil,
|
||||
alias = nil,
|
||||
template = nil,
|
||||
templategroup = nil,
|
||||
coalition = nil,
|
||||
country = nil,
|
||||
actype = nil,
|
||||
category = nil,
|
||||
attribute = nil,
|
||||
ceiling = nil,
|
||||
speedmax = nil,
|
||||
sizex = nil,
|
||||
sizez = nil,
|
||||
size = nil,
|
||||
liveries = {},
|
||||
livery = nil,
|
||||
skill = nil,
|
||||
onboard = nil,
|
||||
departures = {},
|
||||
destinations = {},
|
||||
departure = nil,
|
||||
destination = nil,
|
||||
commute = nil,
|
||||
journey = nil,
|
||||
takeoff = nil,
|
||||
landing = nil,
|
||||
flights = {},
|
||||
Nspawn = 2,
|
||||
Nunits = nil,
|
||||
}
|
||||
|
||||
--- Generalized asset attributes. Can be used to request assets with certain general characteristics. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit.
|
||||
-- @type RATCRAFT.Attribute
|
||||
-- @field #string TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets.
|
||||
-- @field #string AWACS Airborne Early Warning and Control System.
|
||||
-- @field #string FIGHTER Fighter, interceptor, ... airplane.
|
||||
-- @field #string BOMBER Aircraft which can be used for strategic bombing.
|
||||
-- @field #string TANKER Airplane which can refuel other aircraft.
|
||||
-- @field #string TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets.
|
||||
-- @field #string ATTACKHELO Attack helicopter.
|
||||
-- @field #string UAV Unpiloted Aerial Vehicle, e.g. drones.
|
||||
RATCRAFT.Attribute = {
|
||||
TRANSPORTPLANE="TransportPlane",
|
||||
AWACS="AWACS",
|
||||
FIGHTER="Fighter",
|
||||
BOMBER="Bomber",
|
||||
TANKER="Tanker",
|
||||
TRANSPORTHELO="TransportHelo",
|
||||
ATTACKHELO="AttackHelo",
|
||||
UAV="UAV",
|
||||
OTHER="Other",
|
||||
}
|
||||
|
||||
--- Departure/destination type, i.e. airbase or zone.
|
||||
-- @type RATCRAFT.DeType
|
||||
-- @field #string AIRBASE Departure/destination is an airbase.
|
||||
-- @field #string ZONE Departure/destination is a zone.
|
||||
RATCRAFT.DeType={
|
||||
AIRBASE="Airbase",
|
||||
ZONE="Zone",
|
||||
}
|
||||
|
||||
--- Departure/destination parameters.
|
||||
-- @type RATCRAFT.Departure
|
||||
-- @field #string name Name of the departure/destination airbase/zone.
|
||||
-- @field #string type Type of the departure/destination, i.e. an airbase or a zone.
|
||||
-- @field #table parking Number of parking spot IDs for the aircraft.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: helos
|
||||
-- TODO: despawn when arrived
|
||||
-- TODO: zones as departures
|
||||
-- TODO: zones as destinations
|
||||
-- TODO: monitor if stationary
|
||||
-- TODO: commute
|
||||
-- TODO: continue journey
|
||||
-- TODO: return zone
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new RATCRAFT class object from a template group defined in the mission editor.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #string groupname Name of the **late activated** template group.
|
||||
-- @param #number nspawn Number of groups to spawn. Default 1.
|
||||
-- @param #string alias (Optional) Alias for the group name used as spawn name prefix. Default is the group name.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:New(groupname, nspawn, alias)
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) --#RATCRAFT
|
||||
|
||||
-- Defaults
|
||||
self.Nspawn=nspawn or 1
|
||||
self.alias=alias or groupname
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Load", "Stopped") -- Load ratcraft from file.
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script.
|
||||
self:AddTransition("Running", "Pause", "Paused") -- Pause spawning RATCRAFT.
|
||||
self:AddTransition("Paused", "Unpause", "Running") -- UnPause spawning RATCRAFT.
|
||||
|
||||
-- Initialize aircraft parameters.
|
||||
self:_Init(groupname)
|
||||
|
||||
self:Start()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Init aircraft parameters.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #string groupname Name of the template group.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:_Init(groupname)
|
||||
|
||||
self.templatename=groupname
|
||||
self.templategroup=GROUP:FindByName(groupname)
|
||||
self.template=self.templategroup:GetTemplate()
|
||||
|
||||
|
||||
self.speedmax=self.templategroup:GetSpeedMax()
|
||||
self.range=1000000
|
||||
self.DCSdesc=self.templategroup:GetDCSDesc(1)
|
||||
|
||||
self.coalition=self.templategroup:GetCoalition()
|
||||
self.country=self.templategroup:GetUnit(1):GetCountry()
|
||||
|
||||
self.category=self.templategroup:GetUnit(1):GetCategory()
|
||||
|
||||
|
||||
self.Nunits=#self.templategroup:GetUnits()
|
||||
|
||||
-- aircraft dimensions
|
||||
self.sizex=self.DCSdesc.box.max.x
|
||||
self.sizey=self.DCSdesc.box.max.y
|
||||
self.sizez=self.DCSdesc.box.max.z
|
||||
self.size =math.max(self.sizex, self.sizez)
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add adeparture airbase for the aircraft.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #string airbasename Departure airbase name.
|
||||
-- @param #table parking (Optional) Table of parking spot IDs. At least the number of units in the group must be given.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:AddDepartureAirbase(airbasename, parking)
|
||||
|
||||
local departure={} --#RATCRAFT.Departure
|
||||
|
||||
local spots=nil
|
||||
if parking then
|
||||
if type(parking)=="number" then
|
||||
parking={parking}
|
||||
end
|
||||
|
||||
-- Convert parking IDs to complete parking data.
|
||||
spots={}
|
||||
local airbase=AIRBASE:FindByName(airbasename)
|
||||
for _,TerminalID in pairs(parking) do
|
||||
local spot=airbase:GetParkingSpotData(TerminalID)
|
||||
if spot then
|
||||
self:I(string.format("Add in parking spot %d", spot.TerminalID))
|
||||
table.insert(spots, spot)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
departure.name=airbasename
|
||||
departure.type=RATCRAFT.DeType.AIRBASE
|
||||
departure.parking=spots
|
||||
table.insert(self.departures, departure)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add destination airbase for the aircraft and optionally specific parking spot IDs to be used by this group.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #string airbasename Destination airbase name.
|
||||
-- @param #table parking (Optional) Table of parking spot IDs. At least the number of units in the group must be given.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:AddDestinationAirbase(airbasename, parking)
|
||||
|
||||
local destination={} --#RATCRAFT.Departure
|
||||
|
||||
-- Convert parking IDs to complete parking data.
|
||||
local spots=nil
|
||||
if parking then
|
||||
if type(parking)=="number" then
|
||||
parking={parking}
|
||||
end
|
||||
|
||||
spots={}
|
||||
local airbase=AIRBASE:FindByName(airbasename)
|
||||
for _,TerminalID in pairs(parking) do
|
||||
local spot=airbase:GetParkingSpotData(TerminalID)
|
||||
if spot then
|
||||
table.insert(spots, spot)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
destination.name=airbasename
|
||||
destination.type=RATCRAFT.DeType.AIRBASE
|
||||
destination.parking=spots
|
||||
table.insert(self.destinations, destination)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Add a departure for the aircraft.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param Core.Zone#ZONE zone Zone.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:AddDepartureZone(zone)
|
||||
|
||||
local departure={} --#RATCRAFT.Departure
|
||||
|
||||
departure.type={}
|
||||
|
||||
table.insert(self.departures, zone)
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add a departure for the aircraft.
|
||||
-- @param #RATCRAFT self
|
||||
function RATCRAFT:onafterStart()
|
||||
|
||||
-- Create flight groups.
|
||||
for i=1,self.Nspawn do
|
||||
|
||||
-- Group name.
|
||||
local groupname=string.format("%s-%02d", self.alias, i)
|
||||
|
||||
-- New flight group.
|
||||
local flightgroup=FLIGHTGROUP:New(groupname)
|
||||
|
||||
-- Add flight to tabloe.
|
||||
table.insert(self.flights, flightgroup)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Departure Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get departure.
|
||||
-- @param #RATCRAFT self
|
||||
-- @return #RATCRAFT.Departure
|
||||
-- @return #table Parking data table.
|
||||
function RATCRAFT:GetDeparture()
|
||||
|
||||
-- TODO: for commute/continue, set departure to current airbase/zone. Get same parking spot.
|
||||
|
||||
local departures=UTILS.DeepCopy(self.departures)
|
||||
|
||||
-- Try each departure in random order.
|
||||
for i=1,#departures do
|
||||
|
||||
-- Roll dice.
|
||||
local r=math.random(#departures)
|
||||
|
||||
-- Get random departure
|
||||
local departure=departures[r] --#RATCRAFT.Departure
|
||||
|
||||
-- Check if airbase or zone.
|
||||
if departure.type==RATCRAFT.DeType.AIRBASE then
|
||||
|
||||
---
|
||||
-- Airbase
|
||||
---
|
||||
|
||||
-- Departure airbase.
|
||||
local airbase=AIRBASE:FindByName(departure.name)
|
||||
|
||||
if airbase then
|
||||
|
||||
-- Template group.
|
||||
local group=self.templategroup --Wrapper.Group#GROUP
|
||||
|
||||
-- Get number of free parking spots.
|
||||
--local parking=airbase:FindFreeParkingSpotForAircraft(group)
|
||||
local parking=self:GetParkingDeparture(departure)
|
||||
|
||||
|
||||
-- Check if parking is sufficient.
|
||||
if parking and #parking>=self.Nunits then
|
||||
|
||||
self:I(string.format("Returning departure %s", departure.name))
|
||||
return departure, parking
|
||||
|
||||
else
|
||||
|
||||
self:I(string.format("Removing departure %s", departure.name))
|
||||
|
||||
-- Remove from table so it does not get selected again.
|
||||
table.remove(departures, r)
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
self:E("ERROR: Could not find departure airbase!")
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Zone
|
||||
---
|
||||
|
||||
-- Departure airbase.
|
||||
local zone=ZONE:New(departure.name)
|
||||
|
||||
if zone then
|
||||
|
||||
-- TODO: check if in range.
|
||||
|
||||
return departure
|
||||
|
||||
else
|
||||
self:E("ERROR: Could not find departure zone!")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Get parking at departure.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #RATCRAFT.Departure departure The chosen departure.
|
||||
-- @return #table Parking data table or nil if not enough spots.
|
||||
function RATCRAFT:GetParkingDeparture(departure)
|
||||
|
||||
if departure.type==RATCRAFT.DeType.AIRBASE then
|
||||
|
||||
local airbase=AIRBASE:FindByName(departure.name)
|
||||
|
||||
if airbase then
|
||||
|
||||
local parkingdata=airbase:GetParkingSpotsTable()
|
||||
|
||||
if departure.parking then
|
||||
---
|
||||
-- User specified parking spots
|
||||
---
|
||||
|
||||
-- Check if free?
|
||||
local parking={}
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
self:I(string.format("Checking spot %d", spot.TerminalID))
|
||||
-- Check if spot if free?
|
||||
if spot.Free then
|
||||
|
||||
self:I(string.format("Spot %d is FREE", spot.TerminalID))
|
||||
-- Check if spot is in user table?
|
||||
for _,_park in pairs(departure.parking) do
|
||||
local park=_park --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
if park.TerminalID==spot.TerminalID then
|
||||
self:I(string.format("Adding spot %d to parking", spot.TerminalID))
|
||||
table.insert(parking, spot)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Only return data if sufficient spots available.
|
||||
if #parking>=self.Nunits then
|
||||
self:I(string.format("Found valid parking for %d spots", #parking))
|
||||
return parking
|
||||
end
|
||||
|
||||
else
|
||||
---
|
||||
-- Choose parking from what is free
|
||||
---
|
||||
|
||||
local parking={}
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
if spot.Free then
|
||||
table.insert(parking, spot)
|
||||
end
|
||||
|
||||
if #parking>=self.Nunits then
|
||||
return parking
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:I(string.format("Could not find valid parking data"))
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Get destination.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #RATCRAFT.Departure departure The chosen departure.
|
||||
-- @return #RATCRAFT.Departure
|
||||
function RATCRAFT:GetDestination(departure)
|
||||
|
||||
if departure==nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- TODO: for commute, switch departure and destination.
|
||||
|
||||
-- Create copy of all destinations.
|
||||
local destinations=UTILS.DeepCopy(self.destinations)
|
||||
|
||||
-- Try each destination in random order.
|
||||
for i=1,#destinations do
|
||||
|
||||
-- Roll dice.
|
||||
local r=math.random(#destinations)
|
||||
|
||||
-- Get random departure
|
||||
local destination=destinations[r] --#RATCRAFT.Departure
|
||||
|
||||
-- Check if airbase or zone.
|
||||
if destination.type==RATCRAFT.DeType.AIRBASE then
|
||||
|
||||
---
|
||||
-- Airbase
|
||||
---
|
||||
|
||||
-- Get airbase.
|
||||
local airbase=AIRBASE:FindByName(destination.name)
|
||||
|
||||
local depart=AIRBASE:FindByName(departure.name)
|
||||
|
||||
if airbase then
|
||||
-- TODO: check
|
||||
-- coalition, terminal type, distance
|
||||
|
||||
-- Distance between departure and destination.
|
||||
local distance=depart:GetCoordinate():Get2DDistance(airbase:GetCoordinate())
|
||||
|
||||
if distance then
|
||||
|
||||
self:I(string.format("Returning destination %s", destination.name))
|
||||
return destination
|
||||
|
||||
else
|
||||
|
||||
self:I(string.format("Removing destination %s", destination.name))
|
||||
|
||||
-- Remove from table so it does not get selected again.
|
||||
table.remove(destinations, r)
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
self:E("ERROR: Could not find airbase!")
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Zone
|
||||
---
|
||||
|
||||
-- Departure airbase.
|
||||
local zone=ZONE:New(destination.name)
|
||||
|
||||
if zone then
|
||||
|
||||
-- TODO: check if in range.
|
||||
return destination
|
||||
|
||||
else
|
||||
self:E("ERROR: Could not find destination zone!")
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get number of alive groups
|
||||
-- @param #RATCRAFT self
|
||||
-- @return #number N live.
|
||||
function RATCRAFT:_GetAliveGroups()
|
||||
local n=0
|
||||
|
||||
for _,_flight in pairs(self.flights) do
|
||||
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
|
||||
|
||||
if not flight:IsDead() then
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return n
|
||||
end
|
||||
@@ -1,757 +0,0 @@
|
||||
--- **Functional** - (R2.5) - Simple and Persistent Player Scoring.
|
||||
--
|
||||
--
|
||||
-- Score!
|
||||
--
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * Good stuff.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.SimpleScore
|
||||
-- @image Functional_SimpleScore.png
|
||||
|
||||
|
||||
--- SCORE class.
|
||||
-- @type SCORE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table menuadded Table of groups the menu was added for.
|
||||
-- @field #boolean menudisabled If true, F10 menu for players is disabled.
|
||||
-- @field #table playerscores Player score table.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Goooooaaaallllll!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The Scoring Concept
|
||||
--
|
||||
-- Score
|
||||
--
|
||||
-- # Basic Script
|
||||
--
|
||||
-- -- Create a new score object.
|
||||
-- score=SCORE:New()
|
||||
--
|
||||
-- -- Start score.
|
||||
-- SCORE:Start()
|
||||
--
|
||||
-- # Training Zones
|
||||
--
|
||||
-- Players are only protected if they are inside one of the training zones.
|
||||
--
|
||||
-- -- Create a new missile trainer object.
|
||||
-- SCORE=SCORE:New()
|
||||
--
|
||||
-- -- Add training zones.
|
||||
-- SCORE:AddSafeZone(ZONE:New("Training Zone Alpha")
|
||||
-- SCORE:AddSafeZone(ZONE:New("Training Zone Bravo")
|
||||
--
|
||||
-- -- Start missile trainer.
|
||||
-- SCORE:Start()
|
||||
--
|
||||
--
|
||||
-- Todo!
|
||||
--
|
||||
--
|
||||
-- @field #SCORE
|
||||
SCORE = {
|
||||
ClassName = "SCORE",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
menuadded = {},
|
||||
menudisabled = nil,
|
||||
playerscores = nil,
|
||||
}
|
||||
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
-- @type SCORE.PlayerData
|
||||
-- @field Wrapper.Unit#UNIT unit Aircraft of the player.
|
||||
-- @field #string unitname Name of the unit.
|
||||
-- @field Wrapper.Client#CLIENT client Client object of player.
|
||||
-- @field #string callsign Callsign of player.
|
||||
-- @field Wrapper.Group#GROUP group Aircraft group of player.
|
||||
-- @field #string groupname Name of the the player aircraft group.
|
||||
-- @field #string name Player name.
|
||||
-- @field #number coalition Coalition number of player.
|
||||
-- @field #table hit Table of hit units.
|
||||
|
||||
--- Hit unit data.
|
||||
-- @type SCORE.HitData
|
||||
-- @field #string unitname Name of the hit unit.
|
||||
-- @field #string category Category of hit unit.
|
||||
-- @field #number score Score
|
||||
-- @field #boolean friendly If true, unit is friendly.
|
||||
-- @field #boolean destroyed If true, unit was destroyed.
|
||||
|
||||
|
||||
|
||||
--- Create a new SCORE class object.
|
||||
-- @param #SCORE self
|
||||
-- @return #SCORE self.
|
||||
function SCORE:New()
|
||||
|
||||
self.lid="SCORE | "
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #SCORE
|
||||
|
||||
-- Defaults:
|
||||
self:SetDefaultMissileDestruction(true)
|
||||
self:SetDefaultLaunchAlerts(true)
|
||||
self:SetDefaultLaunchMarks(true)
|
||||
self:SetExplosionPower()
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start SCORE script.
|
||||
self:AddTransition("*", "Status", "*") -- Status update.
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the SCORE. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#SCORE] Start
|
||||
-- @param #SCORE self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the SCORE. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#SCORE] __Start
|
||||
-- @param #SCORE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the SCORE and all its event handlers.
|
||||
-- @param #SCORE self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the SCORE and all its event handlers.
|
||||
-- @function [parent=#SCORE] __Stop
|
||||
-- @param #SCORE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Status".
|
||||
-- @function [parent=#SCORE] Status
|
||||
-- @param #SCORE self
|
||||
|
||||
--- Triggers the FSM event "Status" after a delay.
|
||||
-- @function [parent=#SCORE] __Status
|
||||
-- @param #SCORE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- On after Start event. Starts the missile trainer and adds event handlers.
|
||||
-- @param #SCORE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function SCORE:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting SCORE v%s", SCORE.version)
|
||||
env.info(text)
|
||||
|
||||
-- Init player scores table.
|
||||
self.playerscores={}
|
||||
|
||||
-- Event handlers
|
||||
self:HandleEvent(EVENTS.Dead, self._EventDeadOrCrash)
|
||||
self:HandleEvent(EVENTS.Crash, self._EventDeadOrCrash)
|
||||
self:HandleEvent(EVENTS.Hit, self._EventHit)
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
self:HandleEvent(EVENTS.PlayerLeaveUnit)
|
||||
|
||||
-- Handle events:
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
self:HandleEvent(EVENTS.Shot)
|
||||
|
||||
if self.Debug then
|
||||
self:TraceClass(self.ClassName)
|
||||
self:TraceLevel(2)
|
||||
end
|
||||
|
||||
self:__Status(-10)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Event Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- SCORE event handler for event birth.
|
||||
-- @param #SCORE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SCORE:OnEventBirth(EventData)
|
||||
self:F3({eventbirth = EventData})
|
||||
|
||||
-- Nil checks.
|
||||
if EventData==nil then
|
||||
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
|
||||
self:E(EventData)
|
||||
return
|
||||
end
|
||||
if EventData.IniUnit==nil then
|
||||
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
|
||||
self:E(EventData)
|
||||
return
|
||||
end
|
||||
|
||||
-- Player unit and name.
|
||||
local _unitName=EventData.IniUnitName
|
||||
local playerunit, playername=self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
|
||||
self:T(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
|
||||
self:T(self.lid.."BIRTH: player = "..tostring(playername))
|
||||
|
||||
-- Check if player entered.
|
||||
if playerunit and playername then
|
||||
|
||||
local _uid=playerunit:GetID()
|
||||
local _group=playerunit:GetGroup()
|
||||
local _callsign=playerunit:GetCallsign()
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", playername, _callsign, _unitName, _group:GetName())
|
||||
self:T(self.lid..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
-- Add F10 radio menu for player.
|
||||
if not self.menudisabled then
|
||||
SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
|
||||
end
|
||||
|
||||
-- Player data.
|
||||
local playerData={} --#SCORE.PlayerData
|
||||
|
||||
-- Player unit, client and callsign.
|
||||
playerData.unit = playerunit
|
||||
playerData.unitname = _unitName
|
||||
playerData.group = _group
|
||||
playerData.groupname = _group:GetName()
|
||||
playerData.name = playername
|
||||
playerData.callsign = playerData.unit:GetCallsign()
|
||||
playerData.client = CLIENT:FindByName(_unitName, nil, true)
|
||||
playerData.coalition = _group:GetCoalition()
|
||||
|
||||
playerData.hit = playerData.hit or {}
|
||||
|
||||
-- Init player data.
|
||||
self.players[playername]=playerData
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Handles the OnHit event for the scoring.
|
||||
-- @param #SCORE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function SCORE:_EventOnHit(Event)
|
||||
self:F( { Event } )
|
||||
|
||||
local InitUnit = nil
|
||||
local InitUNIT = nil
|
||||
local InitUnitName = ""
|
||||
local InitGroup = nil
|
||||
local InitGroupName = ""
|
||||
local InitPlayerName = nil
|
||||
|
||||
local InitCoalition = nil
|
||||
local InitCategory = nil
|
||||
local InitType = nil
|
||||
local InitUnitCoalition = nil
|
||||
local InitUnitCategory = nil
|
||||
local InitUnitType = nil
|
||||
|
||||
local TargetUnit = nil
|
||||
local TargetUNIT = nil
|
||||
local TargetUnitName = ""
|
||||
local TargetGroup = nil
|
||||
local TargetGroupName = ""
|
||||
local TargetPlayerName = nil
|
||||
|
||||
local TargetCoalition = nil
|
||||
local TargetCategory = nil
|
||||
local TargetType = nil
|
||||
local TargetUnitCoalition = nil
|
||||
local TargetUnitCategory = nil
|
||||
local TargetUnitType = nil
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
InitUnit = Event.IniDCSUnit
|
||||
InitUNIT = Event.IniUnit
|
||||
InitUnitName = Event.IniDCSUnitName
|
||||
InitGroup = Event.IniDCSGroup
|
||||
InitGroupName = Event.IniDCSGroupName
|
||||
InitPlayerName = Event.IniPlayerName
|
||||
|
||||
InitCoalition = Event.IniCoalition
|
||||
InitCategory = Event.IniCategory
|
||||
InitType = Event.IniTypeName
|
||||
|
||||
InitUnitCoalition = _SCORINGCoalition[InitCoalition]
|
||||
InitUnitCategory = _SCORINGCategory[InitCategory]
|
||||
InitUnitType = InitType
|
||||
|
||||
self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } )
|
||||
end
|
||||
|
||||
|
||||
if Event.TgtDCSUnit then
|
||||
|
||||
TargetUnit = Event.TgtDCSUnit
|
||||
TargetUNIT = Event.TgtUnit
|
||||
TargetUnitName = Event.TgtDCSUnitName
|
||||
TargetGroup = Event.TgtDCSGroup
|
||||
TargetGroupName = Event.TgtDCSGroupName
|
||||
TargetPlayerName = Event.TgtPlayerName
|
||||
|
||||
TargetCoalition = Event.TgtCoalition
|
||||
TargetCategory = Event.TgtCategory
|
||||
TargetType = Event.TgtTypeName
|
||||
|
||||
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
||||
TargetUnitCategory = _SCORINGCategory[TargetCategory]
|
||||
TargetUnitType = TargetType
|
||||
|
||||
end
|
||||
|
||||
-- Current time.
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
if InitPlayerName~=nil then -- It is a player that is hitting something
|
||||
|
||||
self:T("Hitting Something")
|
||||
|
||||
-- What is he hitting?
|
||||
if TargetCategory then
|
||||
|
||||
-- A target got hit, score it.
|
||||
-- Player contains the score data from self.Players[InitPlayerName]
|
||||
local player=self.Players[InitPlayerName] --#SCORE.PlayerData
|
||||
|
||||
-- Hit table.
|
||||
player.hit[TargetUnitName]=player.hit[TargetUnitName] or {}
|
||||
|
||||
local hit=player.hit[TargetUnitName] --#SCORE.HitData
|
||||
|
||||
hit.score = hit.score or 0
|
||||
hit.penalty = hit.penalty or 0
|
||||
hit.timeStamp = hit.timeStamp or 0
|
||||
hit.unit = hit.unit or TargetUNIT
|
||||
hit.threatLevel, hit.threatType = hit.unit:GetThreatLevel()
|
||||
|
||||
-- Only grant hit scores if there was more than one second between the last hit.
|
||||
if Tnow-hit.timeStamp>1 then
|
||||
|
||||
-- Update timestamp
|
||||
hit.timeStamp = Tnow
|
||||
|
||||
if TargetPlayerName~=nil then
|
||||
-- It is a player hitting another player ...
|
||||
hit.otherplayer=true
|
||||
end
|
||||
|
||||
local Score = 0
|
||||
|
||||
if InitCoalition then -- A coalition object was hit.
|
||||
if InitCoalition == TargetCoalition then
|
||||
|
||||
------------------
|
||||
-- Hit Friendly --
|
||||
------------------
|
||||
|
||||
hit.friendly=true
|
||||
|
||||
player.penalty = player.penalty-10
|
||||
hit.penalty=hit.penalty-10
|
||||
|
||||
-- Check if target was another player.
|
||||
if TargetPlayerName~=nil then -- It is a player hitting another player ...
|
||||
|
||||
else
|
||||
|
||||
end
|
||||
|
||||
-- Write penalty to CSV file.
|
||||
--self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
|
||||
else
|
||||
|
||||
----------------
|
||||
-- Hit Enemey --
|
||||
----------------
|
||||
|
||||
hit.friendly=true
|
||||
|
||||
player.score=player.score+1
|
||||
hit.score=hit.score+1
|
||||
|
||||
--hit.scoreHit = hit.ScoreHit + 1
|
||||
|
||||
-- Check if it was a player.
|
||||
if TargetPlayerName~=nil then -- It is a player hitting another player ...
|
||||
|
||||
else
|
||||
|
||||
end
|
||||
|
||||
-- Write score to CSV file. Score 1 PT.
|
||||
|
||||
end
|
||||
|
||||
else -- A scenery object was hit.
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Track DEAD or CRASH events for the scoring.
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function SCORING:_EventOnDeadOrCrash( Event )
|
||||
self:F( { Event } )
|
||||
|
||||
local TargetUnit = nil
|
||||
local TargetGroup = nil
|
||||
local TargetUnitName = ""
|
||||
local TargetGroupName = ""
|
||||
local TargetPlayerName = ""
|
||||
local TargetCoalition = nil
|
||||
local TargetCategory = nil
|
||||
local TargetType = nil
|
||||
local TargetUnitCoalition = nil
|
||||
local TargetUnitCategory = nil
|
||||
local TargetUnitType = nil
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
TargetUnit = Event.IniUnit
|
||||
TargetUnitName = Event.IniDCSUnitName
|
||||
TargetGroup = Event.IniDCSGroup
|
||||
TargetGroupName = Event.IniDCSGroupName
|
||||
TargetPlayerName = Event.IniPlayerName
|
||||
|
||||
TargetCoalition = Event.IniCoalition
|
||||
TargetCategory = Event.IniCategory
|
||||
TargetType = Event.IniTypeName
|
||||
|
||||
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
||||
TargetUnitCategory = _SCORINGCategory[TargetCategory]
|
||||
TargetUnitType = TargetType
|
||||
|
||||
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
|
||||
end
|
||||
|
||||
-- Player contains the score and reference data for the player.
|
||||
for PlayerName, _player in pairs(self.Players) do
|
||||
local player=_player --#SCORE.PlayerData
|
||||
|
||||
if player and player.unit and player.unit:IsAlive() then
|
||||
|
||||
self:T( "Something got destroyed" )
|
||||
|
||||
-- Some variables
|
||||
local InitUnitName = Player.UnitName
|
||||
local InitUnitType = Player.UnitType
|
||||
local InitCoalition = Player.UnitCoalition
|
||||
local InitCategory = Player.UnitCategory
|
||||
local InitUnitCoalition = _SCORINGCoalition[InitCoalition]
|
||||
local InitUnitCategory = _SCORINGCategory[InitCategory]
|
||||
|
||||
self:T( { InitUnitName, InitUnitType, InitUnitCoalition, InitCoalition, InitUnitCategory, InitCategory } )
|
||||
|
||||
local Destroyed = false
|
||||
|
||||
local hit=player.hit[TargetUnitName] --#SCORE.HitData
|
||||
|
||||
-- What is the player destroying?
|
||||
if hit and hit.timeStamp~=0 then -- Was there a hit for this unit for this player before registered?
|
||||
|
||||
local TargetThreatLevel = hit.threatLevel
|
||||
local TargetThreatType = hit.threatType
|
||||
|
||||
Player.Destroy[TargetCategory] = Player.Destroy[TargetCategory] or {}
|
||||
Player.Destroy[TargetCategory][TargetType] = Player.Destroy[TargetCategory][TargetType] or {}
|
||||
|
||||
local destroy={} --#SCORE.DestroyData
|
||||
|
||||
-- PlayerDestroy contains the destroy score data per category and target type of the player.
|
||||
local TargetDestroy = Player.Destroy[TargetCategory][TargetType]
|
||||
TargetDestroy.Score = TargetDestroy.Score or 0
|
||||
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy or 0
|
||||
TargetDestroy.Penalty = TargetDestroy.Penalty or 0
|
||||
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy or 0
|
||||
|
||||
if TargetCoalition then
|
||||
if InitCoalition == TargetCoalition then
|
||||
|
||||
---
|
||||
-- Hit Friendly
|
||||
---
|
||||
|
||||
local ThreatLevelTarget = TargetThreatLevel
|
||||
local ThreatTypeTarget = TargetThreatType
|
||||
local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1
|
||||
local ThreatPenalty = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyPenalty / 10 )
|
||||
self:F( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
|
||||
|
||||
Player.Penalty = Player.Penalty + ThreatPenalty
|
||||
TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty
|
||||
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1
|
||||
|
||||
if Player.HitPlayers[TargetPlayerName] then
|
||||
-- A player destroyed another friendly player.
|
||||
else
|
||||
-- A player destroyed another friendly target.
|
||||
end
|
||||
|
||||
-- Write CSV penalty.
|
||||
Destroyed = true
|
||||
--self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_PENALTY", 1, ThreatPenalty, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Hit Enemy
|
||||
---
|
||||
|
||||
local ThreatLevelTarget = TargetThreatLevel
|
||||
local ThreatTypeTarget = TargetThreatType
|
||||
local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1
|
||||
local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyScore / 10 )
|
||||
|
||||
self:F( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
|
||||
|
||||
Player.Score = Player.Score + ThreatScore
|
||||
TargetDestroy.Score = TargetDestroy.Score + ThreatScore
|
||||
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
|
||||
|
||||
if Player.HitPlayers[TargetPlayerName] then
|
||||
-- A player destroyed another enemy player.
|
||||
else
|
||||
-- A player destroyed another enemy target.
|
||||
end
|
||||
|
||||
-- Write CSV.
|
||||
Destroyed = true
|
||||
--self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, ThreatScore, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
|
||||
local UnitName = TargetUnit:GetName()
|
||||
|
||||
-- Scoring for special objects.
|
||||
local Score = self.ScoringObjects[UnitName]
|
||||
|
||||
if Score then
|
||||
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
|
||||
MESSAGE
|
||||
:NewType( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information
|
||||
)
|
||||
:ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() )
|
||||
|
||||
-- Write CSV file.
|
||||
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
Destroyed = true
|
||||
end
|
||||
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs(self.ScoringZones) do
|
||||
self:F( { ScoringZone = ScoreZoneData } )
|
||||
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
|
||||
local Score = ScoreZoneData.Score
|
||||
|
||||
-- Check if target is in zone.
|
||||
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
|
||||
MESSAGE
|
||||
:NewType( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
|
||||
"Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
-- Write CSV.
|
||||
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
Destroyed = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Scenery Destroyed
|
||||
---
|
||||
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs(self.ScoringZones) do
|
||||
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
local Score = ScoreZoneData.Score
|
||||
|
||||
if ScoreZone:IsVec2InZone(TargetUnit:GetVec2()) then
|
||||
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
|
||||
-- Player destroyed scenery in scoring zone.
|
||||
|
||||
--Write CSV.
|
||||
Destroyed = true
|
||||
--self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Delete now the hit cache if the target was destroyed.
|
||||
-- Otherwise points will be granted every time a target gets killed by the players that hit that target.
|
||||
-- This is only relevant for player to player destroys.
|
||||
if Destroyed then
|
||||
Player.Hit[TargetCategory][TargetUnitName].TimeStamp = 0
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On before "Save" event. Checks if io and lfs are available.
|
||||
-- @param #SCORE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
||||
-- @param #string filename (Optional) File name for saving the player grades. Default is "SCORE-<ALIAS>_LSOgrades.csv".
|
||||
function SCORE:onbeforeSave(From, Event, To, path, filename)
|
||||
|
||||
-- Check io module is available.
|
||||
if not io then
|
||||
self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check default path.
|
||||
if path==nil and not lfs then
|
||||
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games/DCS\" folder.")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--- On after "Save" event. Player data is saved to file.
|
||||
-- @param #SCORE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized.
|
||||
-- @param #string filename (Optional) File name for saving the player grades. Default is "SCORE-<ALIAS>_LSOgrades.csv".
|
||||
function SCORE:onafterSave(From, Event, To, path, filename)
|
||||
|
||||
--- Function that saves data to file
|
||||
local function _savefile(filename, data)
|
||||
local f = io.open(filename, "wb")
|
||||
if f then
|
||||
f:write(data)
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
if lfs then
|
||||
path=path or lfs.writedir()
|
||||
end
|
||||
|
||||
-- Set file name.
|
||||
filename=filename or string.format("Score.csv")
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
-- Header line
|
||||
local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n"
|
||||
|
||||
local scores="Name,Player Unit,Player Coalition,Player Category,Score Type,Target Category,Target Name, Target Coalition,Target Player,Target Score,Mission Date, Mission Time, OS Date"
|
||||
|
||||
-- Loop over all players.
|
||||
local n=0
|
||||
for playername,grades in pairs(self.playerscores) do
|
||||
|
||||
-- Loop over player grades table.
|
||||
for i,_grade in pairs(grades) do
|
||||
local grade=_grade --#SCORE.LSOgrade
|
||||
|
||||
-- Check some stuff that could be nil.
|
||||
local wire="n/a"
|
||||
if grade.wire and grade.wire<=4 then
|
||||
wire=tostring(grade.wire)
|
||||
end
|
||||
|
||||
local Tgroove="n/a"
|
||||
if grade.Tgroove and grade.Tgroove<=360 and grade.case<3 then
|
||||
Tgroove=tostring(UTILS.Round(grade.Tgroove, 1))
|
||||
end
|
||||
|
||||
local finalscore="n/a"
|
||||
if grade.finalscore then
|
||||
finalscore=tostring(UTILS.Round(grade.finalscore, 1))
|
||||
end
|
||||
|
||||
-- Compile grade line.
|
||||
scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
|
||||
playername, i, finalscore, grade.points, grade.grade, grade.details, wire, Tgroove, grade.case,
|
||||
grade.wind, grade.modex, grade.airframe, grade.carriertype, grade.carriername, grade.theatre, grade.mitime, grade.midate, grade.osdate)
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
|
||||
-- Info
|
||||
local text=string.format("Saving %d player LSO grades to file %s", n, filename)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Save file.
|
||||
_savefile(filename, scores)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -79,7 +79,6 @@ SEAD = {
|
||||
["Kh25"] = "Kh25",
|
||||
["BGM_109"] = "BGM_109",
|
||||
["AGM_154"] = "AGM_154",
|
||||
["HY-2"] = "HY-2",
|
||||
}
|
||||
|
||||
--- Missile enumerators - from DCS ME and Wikipedia
|
||||
@@ -99,7 +98,6 @@ SEAD = {
|
||||
["Kh25"] = {25, 0.8},
|
||||
["BGM_109"] = {460, 0.705}, --in-game ~465kn
|
||||
["AGM_154"] = {130, 0.61},
|
||||
["HY-2"] = {90,1},
|
||||
}
|
||||
|
||||
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: Nov 2021
|
||||
-- Date: July 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -41,7 +41,6 @@
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
|
||||
--
|
||||
-- Simple Class for a more intelligent Short Range Air Defense System
|
||||
@@ -132,7 +131,6 @@ do
|
||||
["Kh29"] = "Kh29",
|
||||
["Kh31"] = "Kh31",
|
||||
["Kh66"] = "Kh66",
|
||||
--["BGM_109"] = "BGM_109",
|
||||
}
|
||||
|
||||
--- Instantiates a new SHORAD object
|
||||
@@ -140,13 +138,13 @@ do
|
||||
-- @param #string Name Name of this SHORAD
|
||||
-- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP
|
||||
-- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend
|
||||
-- @param #number Radius Defense radius in meters, used to switch on SHORAD groups **within** this radius
|
||||
-- @param #number Radius Defense radius in meters, used to switch on groups
|
||||
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
|
||||
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
|
||||
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
|
||||
-- @retunr #SHORAD self
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
|
||||
@@ -164,17 +162,11 @@ do
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.3.1")
|
||||
self:I("*** SHORAD - Started Version 0.2.8")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Running")
|
||||
self:AddTransition("*", "WakeUpShorad", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -318,7 +310,7 @@ do
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
for _,_name in pairs (SHORAD.Harms) do
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
@@ -334,7 +326,7 @@ do
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
for _,_name in pairs (SHORAD.Mavs) do
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
@@ -375,7 +367,7 @@ do
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
returnname = true
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
@@ -396,7 +388,7 @@ do
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
@@ -408,7 +400,6 @@ do
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
function SHORAD:_ShotIsDetected()
|
||||
self:T(self.lid .. " _ShotIsDetected")
|
||||
if self.debug then return true end
|
||||
local IsDetected = false
|
||||
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
|
||||
local ActualDetection = math.random(1,100) -- value for this shot
|
||||
@@ -432,7 +423,7 @@ do
|
||||
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mymantis:AddShorad(myshorad,720)
|
||||
-- mymantis:Start()
|
||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
@@ -486,76 +477,6 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Calculate hit zone of an AGM-88
|
||||
-- @param #SHORAD self
|
||||
-- @param #table SEADWeapon DCS.Weapon object
|
||||
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
|
||||
-- @param #number height Height when the missile was fired
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
|
||||
self:T("**** Calculating hit zone")
|
||||
if SEADWeapon and SEADWeapon:isExist() then
|
||||
--local pos = SEADWeapon:getPoint()
|
||||
|
||||
-- postion and height
|
||||
local position = SEADWeapon:getPosition()
|
||||
local mheight = height
|
||||
-- heading
|
||||
local wph = math.atan2(position.x.z, position.x.x)
|
||||
if wph < 0 then
|
||||
wph=wph+2*math.pi
|
||||
end
|
||||
wph=math.deg(wph)
|
||||
|
||||
-- velocity
|
||||
local wpndata = SEAD.HarmData["AGM_88"]
|
||||
local mveloc = math.floor(wpndata[2] * 340.29)
|
||||
local c1 = (2*mheight*9.81)/(mveloc^2)
|
||||
local c2 = (mveloc^2) / 9.81
|
||||
local Ropt = c2 * math.sqrt(c1+1)
|
||||
if height <= 5000 then
|
||||
Ropt = Ropt * 0.72
|
||||
elseif height <= 7500 then
|
||||
Ropt = Ropt * 0.82
|
||||
elseif height <= 10000 then
|
||||
Ropt = Ropt * 0.87
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
local predpos= pos0:Translate(dist,wph)
|
||||
if predpos then
|
||||
|
||||
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
|
||||
|
||||
if self.debug then
|
||||
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
|
||||
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
|
||||
end
|
||||
|
||||
local seadset = self.Groupset
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
local _targetskill = "Random"
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Main function - work on the EventData
|
||||
-- @param #SHORAD self
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
@@ -583,48 +504,13 @@ end
|
||||
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
|
||||
-- get target data
|
||||
local targetdata = EventData.Weapon:getTarget() -- Identify target
|
||||
-- Is there target data?
|
||||
if not targetdata or self.debug then
|
||||
if string.find(ShootingWeaponName,"AGM_88",1,true) then
|
||||
self:I("**** Tracking AGM-88 with no target data.")
|
||||
local pos0 = EventData.IniUnit:GetCoordinate()
|
||||
local fheight = EventData.IniUnit:GetHeight()
|
||||
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
local targetcat = targetdata:getCategory() -- Identify category
|
||||
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
|
||||
self:T({targetdata})
|
||||
local targetunit = nil
|
||||
if targetcat == Object.Category.UNIT then -- UNIT
|
||||
targetunit = UNIT:Find(targetdata)
|
||||
elseif targetcat == Object.Category.STATIC then -- STATIC
|
||||
--self:T("Static Target Data")
|
||||
--self:T({targetdata:isExist()})
|
||||
--self:T({targetdata:getPoint()})
|
||||
local tgtcoord = COORDINATE:NewFromVec3(targetdata:getPoint())
|
||||
--tgtcoord:MarkToAll("Missile Target",true)
|
||||
|
||||
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord1 = tgtgrp1:GetCoordinate()
|
||||
--tgtcoord1:MarkToAll("Close target SAM",true)
|
||||
|
||||
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord2 = tgtgrp2:GetCoordinate()
|
||||
--tgtcoord2:MarkToAll("Close target SHORAD",true)
|
||||
|
||||
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
|
||||
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
|
||||
|
||||
if dist1 < dist2 then
|
||||
targetunit = tgtgrp1
|
||||
targetcat = Object.Category.UNIT
|
||||
else
|
||||
targetunit = tgtgrp2
|
||||
targetcat = Object.Category.UNIT
|
||||
end
|
||||
targetunit = STATIC:Find(targetdata)
|
||||
end
|
||||
--local targetunitname = Unit.getName(targetdata) -- Unit name
|
||||
if targetunit and targetunit:IsAlive() then
|
||||
@@ -633,11 +519,7 @@ end
|
||||
local targetgroup = nil
|
||||
local targetgroupname = "none"
|
||||
if targetcat == Object.Category.UNIT then
|
||||
if targetunit.ClassName == "UNIT" then
|
||||
targetgroup = targetunit:GetGroup()
|
||||
elseif targetunit.ClassName == "GROUP" then
|
||||
targetgroup = targetunit
|
||||
end
|
||||
targetgroup = targetunit:GetGroup()
|
||||
targetgroupname = targetgroup:GetName() -- group name
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetgroup = targetunit
|
||||
@@ -658,7 +540,6 @@ end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
--
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -804,7 +804,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
return IsEmpty
|
||||
end
|
||||
|
||||
--- Check if zone is "Guarded", i.e. only one (the defending) coaliton is present inside the zone.
|
||||
--- Check if zone is "Guarded", i.e. only one (the defending) coalition is present inside the zone.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @return #boolean self:IsAllInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_CAPTURE_COALITION:IsGuarded()
|
||||
@@ -826,7 +826,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
return IsCaptured
|
||||
end
|
||||
|
||||
--- Check if zone is "Attacked", i.e. another coaliton entered the zone.
|
||||
--- Check if zone is "Attacked", i.e. another coalition entered the zone.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @return #boolean self:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_CAPTURE_COALITION:IsAttacked()
|
||||
@@ -899,24 +899,23 @@ do -- ZONE_CAPTURE_COALITION
|
||||
end
|
||||
self:I(text)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--- Update Mark on F10 map.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:Mark()
|
||||
|
||||
|
||||
if self.MarkOn then
|
||||
|
||||
|
||||
local Coord = self:GetCoordinate()
|
||||
local ZoneName = self:GetZoneName()
|
||||
local State = self:GetState()
|
||||
|
||||
|
||||
-- Remove marks.
|
||||
if self.MarkRed then
|
||||
Coord:RemoveMark(self.MarkRed)
|
||||
@@ -924,21 +923,21 @@ do -- ZONE_CAPTURE_COALITION
|
||||
if self.MarkBlue then
|
||||
Coord:RemoveMark(self.MarkBlue)
|
||||
end
|
||||
|
||||
-- Create new marks for each coaliton.
|
||||
|
||||
-- Create new marks for each coalition.
|
||||
if self.Coalition == coalition.side.BLUE then
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
elseif self.Coalition == coalition.side.RED then
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
else
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
--- **Functional (WIP)** -- Base class that models processes to achieve goals 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.
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Functional.ZoneGoalCoalition
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -24,34 +24,33 @@ do -- ZoneGoal
|
||||
-- @field #table ObjectCategories Table of object categories that are able to hold a zone. Default is UNITS and STATICS.
|
||||
-- @extends Functional.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
|
||||
--
|
||||
--
|
||||
-- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object.
|
||||
--
|
||||
--
|
||||
-- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM).
|
||||
--
|
||||
--
|
||||
-- ### 2.1 ZONE_GOAL_COALITION States
|
||||
--
|
||||
--
|
||||
-- ### 2.2 ZONE_GOAL_COALITION Events
|
||||
--
|
||||
--
|
||||
-- ### 2.3 ZONE_GOAL_COALITION State Machine
|
||||
--
|
||||
--
|
||||
-- @field #ZONE_GOAL_COALITION
|
||||
ZONE_GOAL_COALITION = {
|
||||
ClassName = "ZONE_GOAL_COALITION",
|
||||
Coalition = nil,
|
||||
PreviousCoaliton = nil,
|
||||
UnitCategories = nil,
|
||||
ClassName = "ZONE_GOAL_COALITION",
|
||||
Coalition = nil,
|
||||
PreviousCoalition = nil,
|
||||
UnitCategories = nil,
|
||||
ObjectCategories = nil,
|
||||
}
|
||||
|
||||
|
||||
--- @field #table ZONE_GOAL_COALITION.States
|
||||
ZONE_GOAL_COALITION.States = {}
|
||||
|
||||
|
||||
--- ZONE_GOAL_COALITION Constructor.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
|
||||
@@ -59,33 +58,32 @@ do -- ZoneGoal
|
||||
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories )
|
||||
|
||||
|
||||
if not Zone then
|
||||
BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITON!")
|
||||
BASE:E( "ERROR: No Zone specified in ZONE_GOAL_COALITION!" )
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- Inherit ZONE_GOAL.
|
||||
local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION
|
||||
self:F( { Zone = Zone, Coalition = Coalition } )
|
||||
self:F( { Zone = Zone, Coalition = Coalition } )
|
||||
|
||||
-- Set initial owner.
|
||||
self:SetCoalition( Coalition or coalition.side.NEUTRAL)
|
||||
|
||||
self:SetCoalition( Coalition or coalition.side.NEUTRAL )
|
||||
|
||||
-- Set default unit and object categories for the zone scan.
|
||||
self:SetUnitCategories(UnitCategories)
|
||||
self:SetUnitCategories( UnitCategories )
|
||||
self:SetObjectCategories()
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @param DCSCoalition.DCSCoalition#coalition Coalition The coalition ID, e.g. *coalition.side.RED*.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetCoalition( Coalition )
|
||||
self.PreviousCoalition=self.Coalition or Coalition
|
||||
self.PreviousCoalition = self.Coalition or Coalition
|
||||
self.Coalition = Coalition
|
||||
return self
|
||||
end
|
||||
@@ -95,31 +93,31 @@ do -- ZoneGoal
|
||||
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetUnitCategories( UnitCategories )
|
||||
|
||||
if UnitCategories and type(UnitCategories)~="table" then
|
||||
UnitCategories={UnitCategories}
|
||||
|
||||
if UnitCategories and type( UnitCategories ) ~= "table" then
|
||||
UnitCategories = { UnitCategories }
|
||||
end
|
||||
|
||||
self.UnitCategories=UnitCategories or {Unit.Category.GROUND_UNIT}
|
||||
|
||||
|
||||
self.UnitCategories = UnitCategories or { Unit.Category.GROUND_UNIT }
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @param #table ObjectCategories Table of unit categories. See [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object). Default {Object.Category.UNIT, Object.Category.STATIC}, i.e. all UNITS and STATICS.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetObjectCategories( ObjectCategories )
|
||||
|
||||
if ObjectCategories and type(ObjectCategories)~="table" then
|
||||
ObjectCategories={ObjectCategories}
|
||||
|
||||
if ObjectCategories and type( ObjectCategories ) ~= "table" then
|
||||
ObjectCategories = { ObjectCategories }
|
||||
end
|
||||
|
||||
self.ObjectCategories=ObjectCategories or {Object.Category.UNIT, Object.Category.STATIC}
|
||||
|
||||
|
||||
self.ObjectCategories = ObjectCategories or { Object.Category.UNIT, Object.Category.STATIC }
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Get the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
|
||||
@@ -127,39 +125,37 @@ do -- ZoneGoal
|
||||
return self.Coalition
|
||||
end
|
||||
|
||||
--- Get the previous coaliton, i.e. the one owning the zone before the current one.
|
||||
--- Get the previous coalition, i.e. the one owning the zone before the current one.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
|
||||
function ZONE_GOAL_COALITION:GetPreviousCoalition()
|
||||
return self.PreviousCoalition
|
||||
end
|
||||
|
||||
|
||||
--- Get the owning coalition name of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return #string Coalition name.
|
||||
function ZONE_GOAL_COALITION:GetCoalitionName()
|
||||
return UTILS.GetCoalitionName(self.Coalition)
|
||||
return UTILS.GetCoalitionName( self.Coalition )
|
||||
end
|
||||
|
||||
|
||||
--- Check status Coalition ownership.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:StatusZone()
|
||||
|
||||
|
||||
-- Get current state.
|
||||
local State = self:GetState()
|
||||
|
||||
|
||||
-- Debug text.
|
||||
local text=string.format("Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName())
|
||||
self:F(text)
|
||||
|
||||
local text = string.format( "Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName() )
|
||||
self:F( text )
|
||||
|
||||
-- Scan zone.
|
||||
self:Scan( self.ObjectCategories, self.UnitCategories )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ if io then
|
||||
BASE:I("- io available")
|
||||
else
|
||||
BASE:I("- io NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
__na=true
|
||||
end
|
||||
if lfs then
|
||||
BASE:I("- lfs available")
|
||||
else
|
||||
BASE:I("- lfs NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
__na=true
|
||||
end
|
||||
if __na then
|
||||
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
|
||||
|
||||
@@ -28,9 +28,6 @@ __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
|
||||
@@ -43,7 +40,6 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/DCSTask.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )
|
||||
@@ -73,35 +69,13 @@ __Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Fox.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Autolase.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/AICSAR.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Auftrag.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Target.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/OpsGroup.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Cohort.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Legion.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Brigade.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Commander.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/OpsZone.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Chief.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Flotilla.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Fleet.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Awacs.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -115,7 +115,7 @@
|
||||
-- * [Harrier Ship Landing Mission with Auto LSO!](https://www.youtube.com/watch?v=lqmVvpunk2c)
|
||||
-- * [Updated Airboss V/STOL Features USS Tarawa](https://youtu.be/K7I4pU6j718)
|
||||
-- * [Harrier Practice pattern USS America](https://youtu.be/99NigITYmcI)
|
||||
-- * [Harrier CASE III TACAN Approach USS Tarawa](https://www.youtube.com/watch?v=wWHag5WpNZ0)
|
||||
-- * [Harrier CASE III TACAN Approach USS Tarawa](https://www.youtube.com/watch?v=bTgJXZ9Mhdc&t=1s)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -1201,8 +1201,6 @@ AIRBOSS = {
|
||||
NmaxSection = nil,
|
||||
NmaxStack = nil,
|
||||
handleai = nil,
|
||||
xtVoiceOvers = nil,
|
||||
xtVoiceOversAI = nil,
|
||||
tanker = nil,
|
||||
Corientation = nil,
|
||||
Corientlast = nil,
|
||||
@@ -1732,6 +1730,7 @@ AIRBOSS.MenuF10Root = nil
|
||||
--- Airboss class version.
|
||||
-- @field #string version
|
||||
AIRBOSS.version = "1.2.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1901,12 +1900,6 @@ function AIRBOSS:New( carriername, alias )
|
||||
-- Set AI handling On.
|
||||
self:SetHandleAION()
|
||||
|
||||
-- No extra voiceover/calls from player by default
|
||||
self:SetExtraVoiceOvers(false)
|
||||
|
||||
-- No extra voiceover/calls from AI by default
|
||||
self:SetExtraVoiceOversAI(false)
|
||||
|
||||
-- Airboss is a nice guy.
|
||||
self:SetAirbossNiceGuy()
|
||||
|
||||
@@ -3022,7 +3015,6 @@ end
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number TimeInterval (Optional) Time interval in seconds. Default 1200 sec = 20 min.
|
||||
-- @return #AIRBOSS self
|
||||
|
||||
function AIRBOSS:SetBeaconRefresh( TimeInterval )
|
||||
self.dTbeacon = TimeInterval or (20 * 60)
|
||||
return self
|
||||
@@ -3236,24 +3228,6 @@ function AIRBOSS:SetHandleAION()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Will play the inbound calls, commencing, initial, etc. from the player when requesteing marshal
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #AIRBOSS status Boolean to activate (true) / deactivate (false) the radio inbound calls (default is ON)
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetExtraVoiceOvers(status)
|
||||
self.xtVoiceOvers=status
|
||||
return self
|
||||
end
|
||||
|
||||
--- Will simulate the inbound call, commencing, initial, etc from the AI when requested by Airboss
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #AIRBOSS status Boolean to activate (true) / deactivate (false) the radio inbound calls (default is ON)
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetExtraVoiceOversAI(status)
|
||||
self.xtVoiceOversAI=status
|
||||
return self
|
||||
end
|
||||
|
||||
--- Do not handle AI aircraft.
|
||||
-- @param #AIRBOSS self
|
||||
-- @return #AIRBOSS self
|
||||
@@ -5287,7 +5261,6 @@ function AIRBOSS:_GetAircraftParameters( playerData, step )
|
||||
alt = UTILS.FeetToMeters( 800 )
|
||||
speed = UTILS.KnotsToMps( 350 )
|
||||
elseif skyhawk then
|
||||
|
||||
alt = UTILS.FeetToMeters( 600 )
|
||||
speed = UTILS.KnotsToMps( 250 )
|
||||
elseif goshawk then
|
||||
@@ -5603,12 +5576,6 @@ function AIRBOSS:_ClearForLanding( flight )
|
||||
-- Cleared for Case X recovery.
|
||||
self:_MarshalCallClearedForRecovery( flight.onboard, flight.case )
|
||||
|
||||
-- Voice over of the commencing simulated call from AI
|
||||
if self.xtVoiceOversAI then
|
||||
local leader = flight.group:GetUnits()[1]
|
||||
self:_CommencingCall(leader, flight.onboard)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Cleared for Case X recovery.
|
||||
@@ -5713,7 +5680,7 @@ function AIRBOSS:_ScanCarrierZone()
|
||||
local putintomarshal = false
|
||||
|
||||
-- Get flight group.
|
||||
local flight = _DATABASE:GetOpsGroup( groupname )
|
||||
local flight = _DATABASE:GetFlightGroup( groupname )
|
||||
|
||||
if flight and flight:IsInbound() and flight.destbase:GetName() == self.carrier:GetName() then
|
||||
if flight.ishelo then
|
||||
@@ -5759,6 +5726,7 @@ function AIRBOSS:_ScanCarrierZone()
|
||||
if not self:_IsHuman( group ) then
|
||||
self:_CreateFlightGroup( group )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -5972,12 +5940,7 @@ function AIRBOSS:_MarshalAI( flight, nstack, respawn )
|
||||
end
|
||||
|
||||
-- Check if flight is already in Marshal queue.
|
||||
if not self:_InQueue(self.Qmarshal,flight.group) then
|
||||
-- Simulate inbound call
|
||||
if self.xtVoiceOversAI then
|
||||
local leader = flight.group:GetUnits()[1]
|
||||
self:_MarshallInboundCall(leader, flight.onboard)
|
||||
end
|
||||
if not self:_InQueue( self.Qmarshal, flight.group ) then
|
||||
-- Add group to marshal stack queue.
|
||||
self:_AddMarshalGroup( flight, nstack )
|
||||
end
|
||||
@@ -8206,7 +8169,6 @@ function AIRBOSS:OnEventLand( EventData )
|
||||
|
||||
if self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
|
||||
-- Coordinate at landing event
|
||||
local coord = EventData.IniUnit:GetCoordinate()
|
||||
|
||||
@@ -9331,7 +9293,6 @@ function AIRBOSS:_Ninety( playerData )
|
||||
|
||||
-- Next step: wake.
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
-- Harrier has no wake stop. It stays port of the boat.
|
||||
self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.FINAL )
|
||||
else
|
||||
@@ -10076,7 +10037,6 @@ function AIRBOSS:_GetWire( Lcoord, dc )
|
||||
-- Multiplayer wire correction.
|
||||
if self.mpWireCorrection then
|
||||
d = d - self.mpWireCorrection
|
||||
|
||||
end
|
||||
|
||||
-- Shift wires from stern to their correct position.
|
||||
@@ -10826,6 +10786,7 @@ function AIRBOSS:_GetZoneCommence( case, stack )
|
||||
local Three = self:GetCoordinate():Translate( D, hdg + 275 )
|
||||
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
local Dx = UTILS.NMToMeters( 2.25 )
|
||||
|
||||
local Dz = UTILS.NMToMeters( 2.25 )
|
||||
@@ -11249,7 +11210,7 @@ end
|
||||
|
||||
--- Get wind direction and speed at carrier position.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number alt Altitude ASL in meters. Default 50 m.
|
||||
-- @param #number alt Altitude ASL in meters. Default 15 m.
|
||||
-- @param #boolean magnetic Direction including magnetic declination.
|
||||
-- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position.
|
||||
-- @return #number Direction the wind is blowing **from** in degrees.
|
||||
@@ -11259,8 +11220,8 @@ function AIRBOSS:GetWind( alt, magnetic, coord )
|
||||
-- Current position of the carrier or input.
|
||||
local cv = coord or self:GetCoordinate()
|
||||
|
||||
-- Wind direction and speed. By default at 50 meters ASL.
|
||||
local Wdir, Wspeed = cv:GetWind( alt or 50 )
|
||||
-- Wind direction and speed. By default at 15 meters ASL.
|
||||
local Wdir, Wspeed = cv:GetWind( alt or 15 )
|
||||
|
||||
-- Include magnetic declination.
|
||||
if magnetic then
|
||||
@@ -15107,86 +15068,6 @@ function AIRBOSS:_Number2Radio( radio, number, delay, interval, pilotcall )
|
||||
return wait
|
||||
end
|
||||
|
||||
--- Aircraft request marshal (Inbound call both for players and AI).
|
||||
-- @param #AIRBOSS self
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @param #string modex Tail number.
|
||||
function AIRBOSS:_MarshallInboundCall(unit, modex)
|
||||
|
||||
-- Calculate
|
||||
local vectorCarrier = self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate())
|
||||
local bearing = UTILS.Round(unit:GetCoordinate():GetAngleDegrees( vectorCarrier ), 0)
|
||||
local distance = UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0)
|
||||
local angels = UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0)
|
||||
local state = UTILS.Round(self:_GetFuelState(unit)/1000,1)
|
||||
|
||||
-- Pilot: "Marshall, [modex], marking mom's [bearing] for [distance], angels [XX], state [X.X]"
|
||||
local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f", modex, bearing, distance, angels, state)
|
||||
-- Debug message.
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Fuel state.
|
||||
local FS=UTILS.Split(string.format("%.1f", state), ".")
|
||||
|
||||
-- Create new call to display complete subtitle.
|
||||
local inboundcall=self:_NewRadioCall(self.MarshalCall.CLICK, unit.UnitName:upper() , text, self.Tmessage, nil, unit.UnitName:upper())
|
||||
|
||||
-- CLICK!
|
||||
self:RadioTransmission(self.MarshalRadio, inboundcall)
|
||||
-- Marshal ..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.MARSHAL, nil, nil, nil, nil, true)
|
||||
-- Modex..
|
||||
self:_Number2Radio(self.MarshalRadio, modex, nil, nil, true)
|
||||
-- Marking Mom's,
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.MARKINGMOMS, nil, nil, nil, nil, true)
|
||||
-- Bearing ..
|
||||
self:_Number2Radio(self.MarshalRadio, tostring(bearing), nil, nil, true)
|
||||
-- For ..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.FOR, nil, nil, nil, nil, true)
|
||||
-- Distance ..
|
||||
self:_Number2Radio(self.MarshalRadio, tostring(distance), nil, nil, true)
|
||||
-- Angels ..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.ANGELS, nil, nil, nil, nil, true)
|
||||
-- Angels Number ..
|
||||
self:_Number2Radio(self.MarshalRadio, tostring(angels), nil, nil, true)
|
||||
-- State ..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.STATE, nil, nil, nil, nil, true)
|
||||
-- X..
|
||||
self:_Number2Radio(self.MarshalRadio, FS[1], nil, nil, true)
|
||||
-- Point..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.POINT, nil, nil, nil, nil, true)
|
||||
-- Y.
|
||||
self:_Number2Radio(self.MarshalRadio, FS[2], nil, nil, true)
|
||||
-- CLICK!
|
||||
self:RadioTransmission(self.MarshalRadio, self.MarshalRadio.CLICK, nil, nil, nil, nil, true)
|
||||
|
||||
end
|
||||
|
||||
--- Aircraft commencing call (both for players and AI).
|
||||
-- @param #AIRBOSS self
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @param #string modex Tail number.
|
||||
function AIRBOSS:_CommencingCall(unit, modex)
|
||||
|
||||
-- Pilot: "[modex], commencing"
|
||||
local text=string.format("%s, commencing", modex)
|
||||
-- Debug message.
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Create new call to display complete subtitle.
|
||||
local commencingCall=self:_NewRadioCall(self.MarshalCall.CLICK, unit.UnitName:upper() , text, self.Tmessage, nil, unit.UnitName:upper())
|
||||
|
||||
-- Click
|
||||
self:RadioTransmission(self.MarshalRadio, commencingCall)
|
||||
-- Modex..
|
||||
self:_Number2Radio(self.MarshalRadio, modex, nil, nil, true)
|
||||
-- Commencing
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.COMMENCING, nil, nil, nil, nil, true)
|
||||
-- CLICK!
|
||||
self:RadioTransmission(self.MarshalRadio, self.MarshalRadio.CLICK, nil, nil, nil, nil, true)
|
||||
|
||||
end
|
||||
|
||||
--- AI aircraft calls the ball.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #string modex Tail number.
|
||||
@@ -15236,7 +15117,6 @@ function AIRBOSS:_MarshalCallGasAtTanker( modex )
|
||||
-- Debug message.
|
||||
self:I( self.lid .. text )
|
||||
|
||||
|
||||
-- Create new call to display complete subtitle.
|
||||
local call = self:_NewRadioCall( self.PilotCall.BINGOFUEL, modex, text, self.Tmessage, nil, modex )
|
||||
|
||||
@@ -15943,11 +15823,6 @@ function AIRBOSS:_RequestMarshal( _unitName )
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Voice over of inbound call (regardless of airboss rejecting it or not)
|
||||
if self.xtVoiceOvers then
|
||||
self:_MarshallInboundCall(_unit, playerData.onboard)
|
||||
end
|
||||
|
||||
-- Check if player is in CCA
|
||||
local inCCA = playerData.unit:IsInZone( self.zoneCCA )
|
||||
|
||||
@@ -16195,12 +16070,7 @@ function AIRBOSS:_RequestCommence( _unitName )
|
||||
local playerData = self.players[_playername] -- #AIRBOSS.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Voice over of Commencing call (regardless of Airboss will rejected or not)
|
||||
if self.xtVoiceOvers then
|
||||
self:_CommencingCall(_unit, playerData.onboard)
|
||||
end
|
||||
|
||||
|
||||
-- Check if unit is in CCA.
|
||||
local text = ""
|
||||
local cleared = false
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,600 +0,0 @@
|
||||
--- **Ops** - Brigade Warehouse.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Manage platoons
|
||||
-- * Carry out ARTY and PATROLZONE missions (AUFTRAG)
|
||||
-- * Define rearming zones
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Brigade).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Ops.Brigade
|
||||
-- @image OPS_Brigade_.png
|
||||
|
||||
|
||||
--- BRIGADE class.
|
||||
-- @type BRIGADE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.SupplyZone`.
|
||||
-- @field #table refuellingZones Refuelling zones. Each element is of type `#BRIGADE.SupplyZone`.
|
||||
-- @field Core.Set#SET_ZONE retreatZones Retreat zone set.
|
||||
-- @extends Ops.Legion#LEGION
|
||||
|
||||
--- *I am not afraid of an Army of lions lead by a sheep; I am afraid of sheep lead by a lion* -- Alexander the Great
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The BRIGADE Concept
|
||||
--
|
||||
-- A BRIGADE consists of one or multiple PLATOONs. These platoons "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
|
||||
--
|
||||
--
|
||||
-- @field #BRIGADE
|
||||
BRIGADE = {
|
||||
ClassName = "BRIGADE",
|
||||
verbose = 0,
|
||||
rearmingZones = {},
|
||||
refuellingZones = {},
|
||||
}
|
||||
|
||||
--- Supply Zone.
|
||||
-- @type BRIGADE.SupplyZone
|
||||
-- @field Core.Zone#ZONE zone The zone.
|
||||
-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel.
|
||||
-- @field #boolean markerOn If `true`, marker is on.
|
||||
-- @field Wrapper.Marker#MARKER marker F10 marker.
|
||||
|
||||
--- BRIGADE class version.
|
||||
-- @field #string version
|
||||
BRIGADE.version="0.1.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Spawn when hosting warehouse is a ship or oil rig or gas platform.
|
||||
-- TODO: Rearming zones.
|
||||
-- TODO: Retreat zones.
|
||||
-- DONE: Add weapon range.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new BRIGADE class object.
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse.
|
||||
-- @param #string BrigadeName Name of the brigade.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:New(WarehouseName, BrigadeName)
|
||||
|
||||
-- Inherit everything from LEGION class.
|
||||
local self=BASE:Inherit(self, LEGION:New(WarehouseName, BrigadeName)) -- #BRIGADE
|
||||
|
||||
-- Nil check.
|
||||
if not self then
|
||||
BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("BRIGADE %s | ", self.alias)
|
||||
|
||||
-- Defaults
|
||||
self:SetRetreatZones()
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "ArmyOnMission", "*") -- An ARMYGROUP was send on a Mission (AUFTRAG).
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the BRIGADE. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#BRIGADE] Start
|
||||
-- @param #BRIGADE self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the BRIGADE. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#BRIGADE] __Start
|
||||
-- @param #BRIGADE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the BRIGADE and all its event handlers.
|
||||
-- @param #BRIGADE self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the BRIGADE and all its event handlers.
|
||||
-- @function [parent=#BRIGADE] __Stop
|
||||
-- @param #BRIGADE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "ArmyOnMission".
|
||||
-- @function [parent=#BRIGADE] ArmyOnMission
|
||||
-- @param #BRIGADE self
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
--- Triggers the FSM event "ArmyOnMission" after a delay.
|
||||
-- @function [parent=#BRIGADE] __ArmyOnMission
|
||||
-- @param #BRIGADE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
--- On after "ArmyOnMission" event.
|
||||
-- @function [parent=#BRIGADE] OnAfterArmyOnMission
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add a platoon to the brigade.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Ops.Platoon#PLATOON Platoon The platoon object.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:AddPlatoon(Platoon)
|
||||
|
||||
-- Add platoon to brigade.
|
||||
table.insert(self.cohorts, Platoon)
|
||||
|
||||
-- Add assets to platoon.
|
||||
self:AddAssetToPlatoon(Platoon, Platoon.Ngroups)
|
||||
|
||||
-- Set brigade of platoon.
|
||||
Platoon:SetBrigade(self)
|
||||
|
||||
-- Start platoon.
|
||||
if Platoon:IsStopped() then
|
||||
Platoon:Start()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add asset group(s) to platoon.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Ops.Platoon#PLATOON Platoon The platoon object.
|
||||
-- @param #number Nassets Number of asset groups to add.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:AddAssetToPlatoon(Platoon, Nassets)
|
||||
|
||||
if Platoon then
|
||||
|
||||
-- Get the template group of the platoon.
|
||||
local Group=GROUP:FindByName(Platoon.templatename)
|
||||
|
||||
if Group then
|
||||
|
||||
-- Debug text.
|
||||
local text=string.format("Adding asset %s to platoon %s", Group:GetName(), Platoon.name)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Add assets to airwing warehouse.
|
||||
self:AddAsset(Group, Nassets, nil, nil, nil, nil, Platoon.skill, Platoon.livery, Platoon.name)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Group does not exist!")
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Platoon does not exit!")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Define a set of retreat zones.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:SetRetreatZones(RetreatZoneSet)
|
||||
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a retreat zone.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Core.Zone#ZONE RetreatZone Retreat zone.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:AddRetreatZone(RetreatZone)
|
||||
self.retreatZones:AddZone(RetreatZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get retreat zones.
|
||||
-- @param #BRIGADE self
|
||||
-- @return Core.Set#SET_ZONE Set of retreat zones.
|
||||
function BRIGADE:GetRetreatZones()
|
||||
return self.retreatZones
|
||||
end
|
||||
|
||||
--- Add a rearming zone.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Core.Zone#ZONE RearmingZone Rearming zone.
|
||||
-- @return #BRIGADE.SupplyZone The rearming zone data.
|
||||
function BRIGADE:AddRearmingZone(RearmingZone)
|
||||
|
||||
local rearmingzone={} --#BRIGADE.SupplyZone
|
||||
|
||||
rearmingzone.zone=RearmingZone
|
||||
rearmingzone.mission=nil
|
||||
rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition())
|
||||
|
||||
table.insert(self.rearmingZones, rearmingzone)
|
||||
|
||||
return rearmingzone
|
||||
end
|
||||
|
||||
|
||||
--- Add a refuelling zone.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Core.Zone#ZONE RefuellingZone Refuelling zone.
|
||||
-- @return #BRIGADE.SupplyZone The refuelling zone data.
|
||||
function BRIGADE:AddRefuellingZone(RefuellingZone)
|
||||
|
||||
local supplyzone={} --#BRIGADE.SupplyZone
|
||||
|
||||
supplyzone.zone=RefuellingZone
|
||||
supplyzone.mission=nil
|
||||
supplyzone.marker=MARKER:New(supplyzone.zone:GetCoordinate(), "Refuelling Zone"):ToCoalition(self:GetCoalition())
|
||||
|
||||
table.insert(self.refuellingZones, supplyzone)
|
||||
|
||||
return supplyzone
|
||||
end
|
||||
|
||||
|
||||
--- Get platoon by name.
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string PlatoonName Name of the platoon.
|
||||
-- @return Ops.Platoon#PLATOON The Platoon object.
|
||||
function BRIGADE:GetPlatoon(PlatoonName)
|
||||
local platoon=self:_GetCohort(PlatoonName)
|
||||
return platoon
|
||||
end
|
||||
|
||||
--- Get platoon of an asset.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset.
|
||||
-- @return Ops.Platoon#PLATOON The platoon object.
|
||||
function BRIGADE:GetPlatoonOfAsset(Asset)
|
||||
local platoon=self:GetPlatoon(Asset.squadname)
|
||||
return platoon
|
||||
end
|
||||
|
||||
--- Remove asset from platoon.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset.
|
||||
function BRIGADE:RemoveAssetFromPlatoon(Asset)
|
||||
local platoon=self:GetPlatoonOfAsset(Asset)
|
||||
if platoon then
|
||||
platoon:DelAsset(Asset)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- [ GROUND ] Function to load back an asset in the field that has been filed before.
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string Templatename e.g."1 PzDv LogRg I\_AID-976" - that's the alias (name) of an platoon spawned as `"platoon - alias"_AID-"asset-ID"`
|
||||
-- @param Core.Point#COORDINATE Position where to spawn the platoon
|
||||
-- @return #BRIGADE self
|
||||
-- @usage
|
||||
-- Prerequisites:
|
||||
-- Save the assets spawned by BRIGADE/CHIEF regularly (~every 5 mins) into a file, e.g. like this:
|
||||
--
|
||||
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
|
||||
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
|
||||
-- local BlueSaveOps = SET_GROUP:New():FilterCoalitions("blue"):FilterPrefixes("AID"):FilterCategoryGround():FilterOnce()
|
||||
-- UTILS.SaveSetOfGroups(BlueSaveOps,Path,BlueOpsFilename)
|
||||
--
|
||||
-- where Path and Filename are strings, as chosen by you.
|
||||
-- You can then load back the assets at the start of your next mission run. Be aware that it takes a couple of seconds for the
|
||||
-- platoon data to arrive in brigade, so make this an action after ~20 seconds, e.g. like so:
|
||||
--
|
||||
-- function LoadBackAssets()
|
||||
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
|
||||
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
|
||||
-- if UTILS.CheckFileExists(Path,BlueOpsFilename) then
|
||||
-- local loadback = UTILS.LoadSetOfGroups(Path,BlueOpsFilename,false)
|
||||
-- for _,_platoondata in pairs (loadback) do
|
||||
-- local groupname = _platoondata.groupname -- #string
|
||||
-- local coordinate = _platoondata.coordinate -- Core.Point#COORDINATE
|
||||
-- Your_Brigade:LoadBackAssetInPosition(groupname,coordinate)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- local AssetLoader = TIMER:New(LoadBackAssets)
|
||||
-- AssetLoader:Start(20)
|
||||
--
|
||||
-- The assets loaded back into the mission will be considered for AUFTRAG type missions from CHIEF and BRIGADE.
|
||||
function BRIGADE:LoadBackAssetInPosition(Templatename,Position)
|
||||
self:T(self.lid .. "LoadBackAssetInPosition: " .. tostring(Templatename))
|
||||
|
||||
-- get Platoon alias from Templatename
|
||||
local nametbl = UTILS.Split(Templatename,"_")
|
||||
|
||||
local name = nametbl[1]
|
||||
|
||||
self:T(string.format("*** Target Platoon = %s ***",name))
|
||||
|
||||
-- find a matching asset table from BRIGADE
|
||||
local cohorts = self.cohorts or {}
|
||||
local thisasset = nil --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
local found = false
|
||||
|
||||
for _,_cohort in pairs(cohorts) do
|
||||
local asset = _cohort:GetName()
|
||||
self:T(string.format("*** Looking at Platoon = %s ***",asset))
|
||||
if asset == name then
|
||||
self:T("**** Found Platoon ****")
|
||||
local cohassets = _cohort.assets or {}
|
||||
for _,_zug in pairs (cohassets) do
|
||||
local zug = _zug -- Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
if zug.assignment == name and zug.requested == false then
|
||||
self:T("**** Found Asset ****")
|
||||
found = true
|
||||
thisasset = zug --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
|
||||
-- prep asset
|
||||
thisasset.rid = thisasset.uid
|
||||
thisasset.requested = false
|
||||
thisasset.score=100
|
||||
thisasset.missionTask="CAS"
|
||||
thisasset.spawned = true
|
||||
local template = thisasset.templatename
|
||||
local alias = thisasset.spawngroupname
|
||||
|
||||
-- Spawn group
|
||||
local spawnasset = SPAWN:NewWithAlias(template,alias)
|
||||
:InitDelayOff()
|
||||
:SpawnFromCoordinate(Position)
|
||||
|
||||
-- build a new self request
|
||||
local request = {} --Functional.Warehouse#WAREHOUSE.Pendingitem
|
||||
request.assignment = name
|
||||
request.warehouse = self
|
||||
request.assets = {thisasset}
|
||||
request.ntransporthome = 0
|
||||
request.ndelivered = 0
|
||||
request.ntransport = 0
|
||||
request.cargoattribute = thisasset.attribute
|
||||
request.category = thisasset.category
|
||||
request.cargoassets = {thisasset}
|
||||
request.assetdesc = WAREHOUSE.Descriptor.ASSETLIST
|
||||
request.cargocategory = thisasset.category
|
||||
request.toself = true
|
||||
request.transporttype = WAREHOUSE.TransportType.SELFPROPELLED
|
||||
request.assetproblem = {}
|
||||
request.born = true
|
||||
request.prio = 50
|
||||
request.uid = thisasset.uid
|
||||
request.airbase = nil
|
||||
request.timestamp = timer.getAbsTime()
|
||||
request.assetdescval = {thisasset}
|
||||
request.nasset = 1
|
||||
request.cargogroupset = SET_GROUP:New()
|
||||
request.cargogroupset:AddGroup(spawnasset)
|
||||
request.iscargo = true
|
||||
|
||||
-- Call Brigade self
|
||||
self:__AssetSpawned(2, spawnasset, thisasset, request)
|
||||
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Start BRIGADE FSM.
|
||||
-- @param #BRIGADE self
|
||||
function BRIGADE:onafterStart(From, Event, To)
|
||||
|
||||
-- Start parent Warehouse.
|
||||
self:GetParent(self, BRIGADE).onafterStart(self, From, Event, To)
|
||||
|
||||
-- Info.
|
||||
self:I(self.lid..string.format("Starting BRIGADE v%s", BRIGADE.version))
|
||||
|
||||
end
|
||||
|
||||
--- Update status.
|
||||
-- @param #BRIGADE self
|
||||
function BRIGADE:onafterStatus(From, Event, To)
|
||||
|
||||
-- Status of parent Warehouse.
|
||||
self:GetParent(self).onafterStatus(self, From, Event, To)
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
----------------
|
||||
-- Transport ---
|
||||
----------------
|
||||
|
||||
self:CheckTransportQueue()
|
||||
|
||||
--------------
|
||||
-- Mission ---
|
||||
--------------
|
||||
|
||||
-- Check if any missions should be cancelled.
|
||||
self:CheckMissionQueue()
|
||||
|
||||
---------------------
|
||||
-- Rearming Zones ---
|
||||
---------------------
|
||||
|
||||
for _,_rearmingzone in pairs(self.rearmingZones) do
|
||||
local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone
|
||||
if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then
|
||||
rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone)
|
||||
self:AddMission(rearmingzone.mission)
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------
|
||||
-- Refuelling Zones ---
|
||||
-----------------------
|
||||
|
||||
-- Check refuelling zones.
|
||||
for _,_supplyzone in pairs(self.refuellingZones) do
|
||||
local supplyzone=_supplyzone --#BRIGADE.SupplyZone
|
||||
-- Check if mission is nil or over.
|
||||
if (not supplyzone.mission) or supplyzone.mission:IsOver() then
|
||||
supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone)
|
||||
self:AddMission(supplyzone.mission)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-----------
|
||||
-- Info ---
|
||||
-----------
|
||||
|
||||
-- General info:
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- Count missions not over yet.
|
||||
local Nmissions=self:CountMissionsInQueue()
|
||||
|
||||
-- Asset count.
|
||||
local Npq, Np, Nq=self:CountAssetsOnMission()
|
||||
|
||||
-- Asset string.
|
||||
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq)
|
||||
|
||||
-- Output.
|
||||
local text=string.format("%s: Missions=%d, Platoons=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets)
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
------------------
|
||||
-- Mission Info --
|
||||
------------------
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Missions Total=%d:", #self.missionqueue)
|
||||
for i,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end
|
||||
local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0)
|
||||
local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage())
|
||||
|
||||
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- Transport Info --
|
||||
--------------------
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Transports Total=%d:", #self.transportqueue)
|
||||
for i,_transport in pairs(self.transportqueue) do
|
||||
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
|
||||
|
||||
local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end
|
||||
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier)
|
||||
|
||||
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Platoon Info --
|
||||
-------------------
|
||||
if self.verbose>=3 then
|
||||
local text="Platoons:"
|
||||
for i,_platoon in pairs(self.cohorts) do
|
||||
local platoon=_platoon --Ops.Platoon#PLATOON
|
||||
|
||||
local callsign=platoon.callsignName and UTILS.GetCallsignName(platoon.callsignName) or "N/A"
|
||||
local modex=platoon.modex and platoon.modex or -1
|
||||
local skill=platoon.skill and tostring(platoon.skill) or "N/A"
|
||||
|
||||
-- Platoon text.
|
||||
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", platoon.name, platoon:GetState(), platoon.aircrafttype, platoon:CountAssets(true), #platoon.assets, callsign, modex, skill)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Rearming Info --
|
||||
-------------------
|
||||
if self.verbose>=4 then
|
||||
local text="Rearming Zones:"
|
||||
for i,_rearmingzone in pairs(self.rearmingZones) do
|
||||
local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone
|
||||
-- Info text.
|
||||
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", rearmingzone.zone:GetName(), rearmingzone.mission:GetState(), rearmingzone.mission:CountOpsGroups())
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Refuelling Info --
|
||||
-------------------
|
||||
if self.verbose>=4 then
|
||||
local text="Refuelling Zones:"
|
||||
for i,_refuellingzone in pairs(self.refuellingZones) do
|
||||
local refuellingzone=_refuellingzone --#BRIGADE.SupplyZone
|
||||
-- Info text.
|
||||
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", refuellingzone.zone:GetName(), refuellingzone.mission:GetState(), refuellingzone.mission:CountOpsGroups())
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after "ArmyOnMission".
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
|
||||
function BRIGADE:onafterArmyOnMission(From, Event, To, ArmyGroup, Mission)
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Group %s on %s mission %s", ArmyGroup:GetName(), Mission:GetType(), Mission:GetName()))
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -30,7 +30,7 @@
|
||||
-- @module Ops.CSAR
|
||||
-- @image OPS_CSAR.jpg
|
||||
|
||||
-- Date: Feb 2022
|
||||
-- Date: June 2022
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
|
||||
@@ -76,65 +76,69 @@
|
||||
--
|
||||
-- The following options are available (with their defaults). Only set the ones you want changed:
|
||||
--
|
||||
-- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms.
|
||||
-- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only!
|
||||
-- self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
|
||||
-- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near.
|
||||
-- self.autosmokedistance = 1000 -- distance for autosmoke
|
||||
-- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates.
|
||||
-- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well.
|
||||
-- self.enableForAI = false -- set to false to disable AI pilots from being rescued.
|
||||
-- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters.
|
||||
-- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter.
|
||||
-- self.immortalcrew = true -- Set to true to make wounded crew immortal.
|
||||
-- self.invisiblecrew = false -- Set to true to make wounded crew insvisible.
|
||||
-- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters.
|
||||
-- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes.
|
||||
-- self.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined.
|
||||
-- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages.
|
||||
-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons.
|
||||
-- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue.
|
||||
-- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below.
|
||||
-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor!
|
||||
-- self.verbose = 0 -- set to > 1 for stats output for debugging.
|
||||
-- mycsar.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms.
|
||||
-- mycsar.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only!
|
||||
-- mycsar.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
|
||||
-- mycsar.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near.
|
||||
-- mycsar.autosmokedistance = 1000 -- distance for autosmoke
|
||||
-- mycsar.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates.
|
||||
-- mycsar.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well.
|
||||
-- mycsar.enableForAI = false -- set to false to disable AI pilots from being rescued.
|
||||
-- mycsar.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to mycsar.extractDistance in meters.
|
||||
-- mycsar.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter.
|
||||
-- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal.
|
||||
-- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible.
|
||||
-- mycsar.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters.
|
||||
-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes.
|
||||
-- mycsar.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined.
|
||||
-- mycsar.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages.
|
||||
-- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons.
|
||||
-- mycsar.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue.
|
||||
-- mycsar.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below.
|
||||
-- mycsar.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor!
|
||||
-- mycsar.verbose = 0 -- set to > 1 for stats output for debugging.
|
||||
-- -- (added 0.1.4) limit amount of downed pilots spawned by **ejection** events
|
||||
-- self.limitmaxdownedpilots = true
|
||||
-- self.maxdownedpilots = 10
|
||||
-- mycsar.limitmaxdownedpilots = true
|
||||
-- mycsar.maxdownedpilots = 10
|
||||
-- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors
|
||||
-- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters
|
||||
-- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters
|
||||
-- self.pilotmustopendoors = false -- switch to true to enable check of open doors
|
||||
-- mycsar.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters
|
||||
-- mycsar.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters
|
||||
-- mycsar.pilotmustopendoors = false -- switch to true to enable check of open doors
|
||||
-- -- (added 0.1.9)
|
||||
-- self.suppressmessages = false -- switch off all messaging if you want to do your own
|
||||
-- mycsar.suppressmessages = false -- switch off all messaging if you want to do your own
|
||||
-- -- (added 0.1.11)
|
||||
-- self.rescuehoverheight = 20 -- max height for a hovering rescue in meters
|
||||
-- self.rescuehoverdistance = 10 -- max distance for a hovering rescue in meters
|
||||
-- mycsar.rescuehoverheight = 20 -- max height for a hovering rescue in meters
|
||||
-- mycsar.rescuehoverdistance = 10 -- max distance for a hovering rescue in meters
|
||||
-- -- (added 0.1.12)
|
||||
-- -- Country codes for spawned pilots
|
||||
-- self.countryblue= country.id.USA
|
||||
-- self.countryred = country.id.RUSSIA
|
||||
-- self.countryneutral = country.id.UN_PEACEKEEPERS
|
||||
-- mycsar.countryblue= country.id.USA
|
||||
-- mycsar.countryred = country.id.RUSSIA
|
||||
-- mycsar.countryneutral = country.id.UN_PEACEKEEPERS
|
||||
--
|
||||
-- ## 2.1 Experimental Features
|
||||
--
|
||||
-- WARNING - Here\'ll be dragons!
|
||||
-- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in <DCS root>\Scripts\MissionScripting.lua
|
||||
-- Needs SRS => 1.9.6 to work (works on the **server** side of SRS)
|
||||
-- self.useSRS = false -- Set true to use FF\'s SRS integration
|
||||
-- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!)
|
||||
-- self.SRSchannel = 300 -- radio channel
|
||||
-- self.SRSModulation = radio.modulation.AM -- modulation
|
||||
-- self.SRSport = 5002 -- and SRS port
|
||||
-- mycsar.useSRS = false -- Set true to use FF\'s SRS integration
|
||||
-- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!)
|
||||
-- mycsar.SRSchannel = 300 -- radio channel
|
||||
-- mycsar.SRSModulation = radio.modulation.AM -- modulation
|
||||
-- mycsar.SRSport = 5002 -- and SRS Server port
|
||||
-- mycsar.SRSCulture = "en-GB" -- SRS voice culture
|
||||
-- mycsar.SRSVoice = nil -- SRS voice, relevant for Google TTS
|
||||
-- mycsar.SRSGPathToCredentials = nil -- Path to your Google credentials json file, set this if you want to use Google TTS
|
||||
-- mycsar.SRSVolume = 1 -- Volume, between 0 and 1
|
||||
-- --
|
||||
-- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat
|
||||
-- self.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases.
|
||||
-- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection. Requires mycsar.enableForAI to be set to true. --shagrat
|
||||
-- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases.
|
||||
--
|
||||
-- ## 3. Results
|
||||
--
|
||||
-- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object:
|
||||
--
|
||||
-- self.rescues -- number of successful landings *with* saved pilots
|
||||
-- self.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players)
|
||||
-- mycsar.rescues -- number of successful landings *with* saved pilots
|
||||
-- mycsar.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players)
|
||||
--
|
||||
-- ## 4. Events
|
||||
--
|
||||
@@ -260,7 +264,7 @@ CSAR.AircraftType["AH-64D_BLK_II"] = 2
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="1.0.4e"
|
||||
CSAR.version="1.0.5"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -415,6 +419,10 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
self.SRSchannel = 300 -- radio channel
|
||||
self.SRSModulation = radio.modulation.AM -- modulation
|
||||
self.SRSport = 5002 -- port
|
||||
self.SRSCulture = "en-GB"
|
||||
self.SRSVoice = nil
|
||||
self.SRSGPathToCredentials = nil
|
||||
self.SRSVolume = 1
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
@@ -861,12 +869,12 @@ function CSAR:_EventHandler(EventData)
|
||||
|
||||
-- no Player
|
||||
if self.enableForAI == false and _event.IniPlayerName == nil then
|
||||
return
|
||||
return self
|
||||
end
|
||||
|
||||
-- no event
|
||||
if _event == nil or _event.initiator == nil then
|
||||
return false
|
||||
return self
|
||||
|
||||
-- take off
|
||||
elseif _event.id == EVENTS.Takeoff then -- taken off
|
||||
@@ -874,35 +882,43 @@ function CSAR:_EventHandler(EventData)
|
||||
|
||||
local _coalition = _event.IniCoalition
|
||||
if _coalition ~= self.coalition then
|
||||
return --ignore!
|
||||
return self --ignore!
|
||||
end
|
||||
|
||||
if _event.IniGroupName then
|
||||
self.takenOff[_event.IniUnitName] = true
|
||||
end
|
||||
|
||||
return true
|
||||
return self
|
||||
|
||||
-- player enter unit
|
||||
elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit
|
||||
self:T(self.lid .. " Event unit - Player Enter")
|
||||
|
||||
local _coalition = _event.IniCoalition
|
||||
self:T("Coalition = "..UTILS.GetCoalitionName(_coalition))
|
||||
if _coalition ~= self.coalition then
|
||||
return --ignore!
|
||||
return self --ignore!
|
||||
end
|
||||
|
||||
if _event.IniPlayerName then
|
||||
self.takenOff[_event.IniPlayerName] = nil
|
||||
end
|
||||
|
||||
-- jumped into flying plane?
|
||||
self:T("Taken Off: "..tostring(_event.IniUnit:InAir(true)))
|
||||
|
||||
if _event.IniUnit:InAir(true) then
|
||||
self.takenOff[_event.IniPlayerName] = true
|
||||
end
|
||||
|
||||
local _unit = _event.IniUnit
|
||||
local _group = _event.IniGroup
|
||||
if _unit:IsHelicopter() or _group:IsHelicopter() then
|
||||
self:_AddMedevacMenuItem()
|
||||
end
|
||||
|
||||
return true
|
||||
return self
|
||||
|
||||
elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then
|
||||
-- Pilot dead
|
||||
@@ -914,57 +930,68 @@ function CSAR:_EventHandler(EventData)
|
||||
local _group = _event.IniGroup
|
||||
|
||||
if _unit == nil then
|
||||
return -- error!
|
||||
return self -- error!
|
||||
end
|
||||
|
||||
local _coalition = _event.IniCoalition
|
||||
if _coalition ~= self.coalition then
|
||||
return --ignore!
|
||||
return self --ignore!
|
||||
end
|
||||
|
||||
-- Catch multiple events here?
|
||||
if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then
|
||||
if self:_DoubleEjection(_unitname) then
|
||||
return
|
||||
return self
|
||||
end
|
||||
|
||||
else
|
||||
self:T(self.lid .. " Pilot has not taken off, ignore")
|
||||
end
|
||||
|
||||
return
|
||||
return self
|
||||
|
||||
elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then
|
||||
if _event.id == EVENTS.PilotDead and self.csarOncrash == false then
|
||||
return
|
||||
return self
|
||||
end
|
||||
self:T(self.lid .. " Event unit - Pilot Ejected")
|
||||
|
||||
local _unit = _event.IniUnit
|
||||
local _unitname = _event.IniUnitName
|
||||
local _group = _event.IniGroup
|
||||
|
||||
self:T({_unit.UnitName, _unitname, _group.GroupName})
|
||||
|
||||
if _unit == nil then
|
||||
return -- error!
|
||||
self:T("Unit NIL!")
|
||||
return self -- error!
|
||||
end
|
||||
|
||||
local _coalition = _unit:GetCoalition()
|
||||
--local _coalition = _unit:GetCoalition() -- nil now for some reason
|
||||
local _coalition = _group:GetCoalition()
|
||||
if _coalition ~= self.coalition then
|
||||
return --ignore!
|
||||
self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition))
|
||||
return self --ignore!
|
||||
end
|
||||
|
||||
|
||||
|
||||
self:T("Airborne: "..tostring(_group:IsAirborne()))
|
||||
self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName]))
|
||||
|
||||
if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then
|
||||
self:T(self.lid .. " Pilot has not taken off, ignore")
|
||||
return -- give up, pilot hasnt taken off
|
||||
-- return self -- give up, pilot hasnt taken off
|
||||
end
|
||||
|
||||
if self:_DoubleEjection(_unitname) then
|
||||
return
|
||||
self:T("Double Ejection!")
|
||||
return self
|
||||
end
|
||||
|
||||
-- limit no of pilots in the field.
|
||||
if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then
|
||||
return
|
||||
self:T("Maxed Downed Pilot!")
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -973,33 +1000,27 @@ function CSAR:_EventHandler(EventData)
|
||||
|
||||
local wetfeet = false
|
||||
|
||||
local surface = _unit:GetCoordinate():GetSurfaceType()
|
||||
local initdcscoord = nil
|
||||
local initcoord = nil
|
||||
--if _event.id == EVENTS.Ejection then
|
||||
initdcscoord = _event.TgtDCSUnit:getPoint()
|
||||
initcoord = COORDINATE:NewFromVec3(initdcscoord)
|
||||
self:T({initdcscoord})
|
||||
--end
|
||||
|
||||
--local surface = _unit:GetCoordinate():GetSurfaceType()
|
||||
local surface = initcoord:GetSurfaceType()
|
||||
|
||||
if surface == land.SurfaceType.WATER then
|
||||
self:T("Wet feet!")
|
||||
wetfeet = true
|
||||
end
|
||||
-- all checks passed, get going.
|
||||
if self.csarUsePara == false or (self.csarUsePara and wetfeet ) then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land
|
||||
local _freq = self:_GenerateADFFrequency()
|
||||
self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none")
|
||||
return true
|
||||
end
|
||||
|
||||
---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location
|
||||
elseif (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then
|
||||
self:I({EVENT=_event})
|
||||
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
|
||||
local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute'
|
||||
local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot"
|
||||
local _country = _event.initiator:getCountry()
|
||||
local _coalition = coalition.getCountryCoalition( _country )
|
||||
if _coalition == self.coalition then
|
||||
local _freq = self:_GenerateADFFrequency()
|
||||
self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq})
|
||||
self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location.
|
||||
|
||||
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
|
||||
end
|
||||
return true
|
||||
self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none")
|
||||
return self
|
||||
end
|
||||
|
||||
elseif _event.id == EVENTS.Land then
|
||||
self:T(self.lid .. " Landing")
|
||||
@@ -1014,12 +1035,12 @@ function CSAR:_EventHandler(EventData)
|
||||
|
||||
if _unit == nil then
|
||||
self:T(self.lid .. " Unit nil on landing")
|
||||
return -- error!
|
||||
return self -- error!
|
||||
end
|
||||
|
||||
local _coalition = _event.IniCoalition
|
||||
if _coalition ~= self.coalition then
|
||||
return --ignore!
|
||||
return self --ignore!
|
||||
end
|
||||
|
||||
self.takenOff[_event.IniUnitName] = nil
|
||||
@@ -1028,13 +1049,13 @@ function CSAR:_EventHandler(EventData)
|
||||
|
||||
if _place == nil then
|
||||
self:T(self.lid .. " Landing Place Nil")
|
||||
return -- error!
|
||||
return self -- error!
|
||||
end
|
||||
|
||||
-- anyone on board?
|
||||
if self.inTransitGroups[_event.IniUnitName] == nil then
|
||||
-- ignore
|
||||
return
|
||||
return self
|
||||
end
|
||||
|
||||
if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then
|
||||
@@ -1044,8 +1065,27 @@ function CSAR:_EventHandler(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
return self
|
||||
end
|
||||
|
||||
---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location
|
||||
if (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then
|
||||
self:T("LANDING_AFTER_EJECTION")
|
||||
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
|
||||
local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute'
|
||||
local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot"
|
||||
local _country = _event.initiator:getCountry()
|
||||
local _coalition = coalition.getCountryCoalition( _country )
|
||||
self:T("Country = ".._country.." Coalition = ".._coalition)
|
||||
if _coalition == self.coalition then
|
||||
local _freq = self:_GenerateADFFrequency()
|
||||
self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq})
|
||||
self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location.
|
||||
|
||||
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1240,7 +1280,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam
|
||||
_maxUnits = self.max_units
|
||||
end
|
||||
if _unitsInHelicopter + 1 > _maxUnits then
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime)
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true)
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -1328,7 +1368,8 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
local _time = self.landedStatus[_lookupKeyHeli]
|
||||
if _time == nil then
|
||||
self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 )
|
||||
_time = self.landedStatus[_lookupKeyHeli]
|
||||
_time = self.landedStatus[_lookupKeyHeli]
|
||||
_woundedGroup:OptionAlarmStateGreen()
|
||||
self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate())
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false)
|
||||
else
|
||||
@@ -1338,7 +1379,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
--if _time <= 0 or _distance < self.loadDistance then
|
||||
if _distance < self.loadDistance + 5 or _distance <= 13 then
|
||||
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true)
|
||||
return true
|
||||
else
|
||||
self.landedStatus[_lookupKeyHeli] = nil
|
||||
@@ -1350,7 +1391,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
else
|
||||
if (_distance < self.loadDistance) then
|
||||
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true)
|
||||
return true
|
||||
else
|
||||
self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
|
||||
@@ -1392,7 +1433,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true)
|
||||
else
|
||||
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true)
|
||||
return true
|
||||
else
|
||||
self.hoverStatus[_lookupKeyHeli] = nil
|
||||
@@ -1451,7 +1492,7 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport)
|
||||
|
||||
if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then
|
||||
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true, true)
|
||||
else
|
||||
self:_RescuePilots(_heliUnit)
|
||||
return
|
||||
@@ -1526,6 +1567,14 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid
|
||||
local channel = self.SRSchannel
|
||||
local msrs = MSRS:New(path,channel,modulation)
|
||||
msrs:SetPort(self.SRSport)
|
||||
msrs:SetLabel("CSAR")
|
||||
msrs:SetCulture(self.SRSCulture)
|
||||
msrs:SetCoalition(self.coalition)
|
||||
msrs:SetVoice(self.SRSVoice)
|
||||
if self.SRSGPathToCredentials then
|
||||
msrs:SetGoogle(self.SRSGPathToCredentials)
|
||||
end
|
||||
msrs:SetVolume(self.SRSVolume)
|
||||
msrs:PlaySoundText(srstext, 2)
|
||||
end
|
||||
return self
|
||||
|
||||
@@ -722,7 +722,8 @@ do
|
||||
-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
|
||||
-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
|
||||
-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000},
|
||||
-- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500},
|
||||
-- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500},
|
||||
-- ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200},
|
||||
--
|
||||
-- ### 2.1.2 Activate and deactivate zones
|
||||
--
|
||||
@@ -870,12 +871,13 @@ do
|
||||
--
|
||||
-- ## 5. Support for Hercules mod by Anubis
|
||||
--
|
||||
-- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same objects as the helicopters.
|
||||
-- To also cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu, you need to use @{#CTLD_HERCULES.New}() and link
|
||||
-- this object to your CTLD setup. In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod
|
||||
-- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same way and for the same objects as
|
||||
-- the helicopters (main method).
|
||||
-- To cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu (F8), you need to use @{#CTLD_HERCULES.New}() and link
|
||||
-- this object to your CTLD setup (alternative method). In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod
|
||||
-- in your mission!
|
||||
--
|
||||
-- ### 5.1 Create an own CTLD instance and allow the usage of the Hercules mod:
|
||||
-- ### 5.1 Create an own CTLD instance and allow the usage of the Hercules mod (main method)
|
||||
--
|
||||
-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I")
|
||||
--
|
||||
@@ -893,26 +895,38 @@ do
|
||||
--
|
||||
-- my_ctld.useprefix = true -- this is true by default and MUST BE ON.
|
||||
--
|
||||
-- ### 5.2 Integrate Hercules ground crew loadable objects
|
||||
--
|
||||
-- Add ground crew loadable objects to your CTLD instance like so, where `my_ctld` is the previously created CTLD instance:
|
||||
-- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method)
|
||||
--
|
||||
-- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance:
|
||||
--
|
||||
-- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew
|
||||
-- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld)
|
||||
--
|
||||
-- You also need:
|
||||
--
|
||||
-- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate).
|
||||
-- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable.
|
||||
-- You also need:
|
||||
--
|
||||
-- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate).
|
||||
-- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable.
|
||||
--
|
||||
-- There's a **quick check output in the `dcs.log`** which tells you what's there and what not.
|
||||
-- E.g.:
|
||||
-- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING)
|
||||
-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING)
|
||||
-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING)
|
||||
-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK)
|
||||
-- E.g.:
|
||||
--
|
||||
-- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING)
|
||||
-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING)
|
||||
-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING)
|
||||
-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK)
|
||||
--
|
||||
-- Expected template names are the ones in the rounded brackets.
|
||||
--
|
||||
-- ### 5.2.1 Hints
|
||||
--
|
||||
-- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does
|
||||
-- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD (see 5.1).
|
||||
--
|
||||
-- There are two ways of airdropping:
|
||||
--
|
||||
-- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu)
|
||||
-- 2) Higher up and slow (>100m AGL) - here you can drop paratroopers and cargo which has "Air" at the end of the cargo name (loaded via F8 Ground Crew menu)
|
||||
--
|
||||
-- Standard transport capabilities as per the real Hercules are:
|
||||
--
|
||||
-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers
|
||||
@@ -4792,10 +4806,11 @@ end
|
||||
end -- end do
|
||||
|
||||
do
|
||||
--- Hercules Cargo Drop Events by Anubis Yinepu
|
||||
--- **Hercules Cargo AIR Drop Events** by Anubis Yinepu
|
||||
-- Moose CTLD OO refactoring by Applevangelist
|
||||
--
|
||||
-- This script will only work for the Herculus mod by Anubis
|
||||
-- This script will only work for the Herculus mod by Anubis, and only for **Air Dropping** cargo from the Hercules.
|
||||
-- Use the standard Moose CTLD if you want to unload on the ground.
|
||||
-- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file
|
||||
-- Except for Ammo pallets, this script will spawn whatever payload gets launched from pylons 11, 12 and 13
|
||||
-- Pylons 11, 12 and 13 are moveable within the Herculus cargobay area
|
||||
@@ -4883,7 +4898,7 @@ CTLD_HERCULES.Types = {
|
||||
["ART GVOZDIKA [34720lb]"] = {['name'] = "SAU Gvozdika", ['container'] = false},
|
||||
["APC MTLB Air [26400lb]"] = {['name'] = "MTLB", ['container'] = true},
|
||||
["APC MTLB Skid [26290lb]"] = {['name'] = "MTLB", ['container'] = false},
|
||||
["Generic Crate [20000lb]"] = {['name'] = "Hercules_Container_Parachute", ['container'] = true} --nothing generic in Moose CTLD
|
||||
--["Generic Crate [20000lb]"] = {['name'] = "Hercules_Container_Parachute", ['container'] = true} --nothing generic in Moose CTLD
|
||||
}
|
||||
|
||||
--- Cargo Object
|
||||
@@ -4908,7 +4923,8 @@ CTLD_HERCULES.Types = {
|
||||
-- @return #CTLD_HERCULES self
|
||||
-- @usage
|
||||
-- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance:
|
||||
--
|
||||
--
|
||||
-- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew
|
||||
-- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld)
|
||||
--
|
||||
-- You also need:
|
||||
@@ -4922,6 +4938,14 @@ CTLD_HERCULES.Types = {
|
||||
-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK)
|
||||
--
|
||||
-- Expected template names are the ones in the rounded brackets.
|
||||
--
|
||||
-- HINTS
|
||||
--
|
||||
-- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does
|
||||
-- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD.
|
||||
-- There are two ways of airdropping:
|
||||
-- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu)
|
||||
-- 2) Higher up and slow (>100m AGL) - here you can drop paratroopers and cargo which has "Air" at the end of the cargo name (loaded via F8 Ground Crew menu)
|
||||
function CTLD_HERCULES:New(Coalition, Alias, CtldObject)
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #CTLD_HERCULES
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,416 +0,0 @@
|
||||
--- **Ops** - Fleet Warehouse.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Manage flotillas
|
||||
-- * Carry out ARTY and PATROLZONE missions (AUFTRAG)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Fleet).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Ops.Fleet
|
||||
-- @image OPS_Fleet.png
|
||||
|
||||
|
||||
--- FLEET class.
|
||||
-- @type FLEET
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field Core.Set#SET_ZONE retreatZones Retreat zone set.
|
||||
-- @field #boolean pathfinding Set pathfinding on for all spawned navy groups.
|
||||
-- @extends Ops.Legion#LEGION
|
||||
|
||||
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The FLEET Concept
|
||||
--
|
||||
-- A FLEET consists of one or multiple FLOTILLAs. These flotillas "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `FLEET` object can be created with the @{#FLEET.New}(`WarehouseName`, `FleetName`) function, where `WarehouseName` is the name of the static or unit object hosting the fleet
|
||||
-- and `FleetName` is the name you want to give the fleet. This must be *unique*!
|
||||
--
|
||||
-- myFleet=FLEET:New("myWarehouseName", "1st Fleet")
|
||||
-- myFleet:SetPortZone(ZonePort1stFleet)
|
||||
-- myFleet:Start()
|
||||
--
|
||||
-- A fleet needs a *port zone*, which is set via the @{#FLEET.SetPortZone}(`PortZone`) function. This is the zone where the naval assets are spawned and return to.
|
||||
--
|
||||
-- Finally, the fleet needs to be started using the @{#FLEET.Start}() function. If the fleet is not started, it will not process any requests.
|
||||
--
|
||||
-- ## Adding Flotillas
|
||||
--
|
||||
-- Flotillas can be added via the @{#FLEET.AddFlotilla}(`Flotilla`) function. See @{Ops.Flotilla#FLOTILLA} for how to create a flotilla.
|
||||
--
|
||||
-- myFleet:AddFlotilla(FlotillaTiconderoga)
|
||||
-- myFleet:AddFlotilla(FlotillaPerry)
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #FLEET
|
||||
FLEET = {
|
||||
ClassName = "FLEET",
|
||||
verbose = 0,
|
||||
pathfinding = false,
|
||||
}
|
||||
|
||||
--- Supply Zone.
|
||||
-- @type FLEET.SupplyZone
|
||||
-- @field Core.Zone#ZONE zone The zone.
|
||||
-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel.
|
||||
-- @field #boolean markerOn If `true`, marker is on.
|
||||
-- @field Wrapper.Marker#MARKER marker F10 marker.
|
||||
|
||||
--- FLEET class version.
|
||||
-- @field #string version
|
||||
FLEET.version="0.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add routes?
|
||||
-- DONE: Add weapon range.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FLEET class object.
|
||||
-- @param #FLEET self
|
||||
-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse.
|
||||
-- @param #string FleetName Name of the fleet.
|
||||
-- @return #FLEET self
|
||||
function FLEET:New(WarehouseName, FleetName)
|
||||
|
||||
-- Inherit everything from LEGION class.
|
||||
local self=BASE:Inherit(self, LEGION:New(WarehouseName, FleetName)) -- #FLEET
|
||||
|
||||
-- Nil check.
|
||||
if not self then
|
||||
BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("FLEET %s | ", self.alias)
|
||||
|
||||
-- Defaults
|
||||
self:SetRetreatZones()
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "NavyOnMission", "*") -- An NAVYGROUP was send on a Mission (AUFTRAG).
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the FLEET. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#FLEET] Start
|
||||
-- @param #FLEET self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the FLEET. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#FLEET] __Start
|
||||
-- @param #FLEET self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the FLEET and all its event handlers.
|
||||
-- @param #FLEET self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the FLEET and all its event handlers.
|
||||
-- @function [parent=#FLEET] __Stop
|
||||
-- @param #FLEET self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "NavyOnMission".
|
||||
-- @function [parent=#FLEET] NavyOnMission
|
||||
-- @param #FLEET self
|
||||
-- @param Ops.NavyGroup#NAVYGROUP ArmyGroup The NAVYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
--- Triggers the FSM event "NavyOnMission" after a delay.
|
||||
-- @function [parent=#FLEET] __NavyOnMission
|
||||
-- @param #FLEET self
|
||||
-- @param #number delay Delay in seconds.
|
||||
-- @param Ops.NavyGroup#NAVYGROUP ArmyGroup The NAVYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
--- On after "NavyOnMission" event.
|
||||
-- @function [parent=#FLEET] OnAfterNavyOnMission
|
||||
-- @param #FLEET self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add a flotilla to the fleet.
|
||||
-- @param #FLEET self
|
||||
-- @param Ops.Flotilla#FLOTILLA Flotilla The flotilla object.
|
||||
-- @return #FLEET self
|
||||
function FLEET:AddFlotilla(Flotilla)
|
||||
|
||||
-- Add flotilla to fleet.
|
||||
table.insert(self.cohorts, Flotilla)
|
||||
|
||||
-- Add assets to flotilla.
|
||||
self:AddAssetToFlotilla(Flotilla, Flotilla.Ngroups)
|
||||
|
||||
-- Set fleet of flotilla.
|
||||
Flotilla:SetFleet(self)
|
||||
|
||||
-- Start flotilla.
|
||||
if Flotilla:IsStopped() then
|
||||
Flotilla:Start()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add asset group(s) to flotilla.
|
||||
-- @param #FLEET self
|
||||
-- @param Ops.Flotilla#FLOTILLA Flotilla The flotilla object.
|
||||
-- @param #number Nassets Number of asset groups to add.
|
||||
-- @return #FLEET self
|
||||
function FLEET:AddAssetToFlotilla(Flotilla, Nassets)
|
||||
|
||||
if Flotilla then
|
||||
|
||||
-- Get the template group of the flotilla.
|
||||
local Group=GROUP:FindByName(Flotilla.templatename)
|
||||
|
||||
if Group then
|
||||
|
||||
-- Debug text.
|
||||
local text=string.format("Adding asset %s to flotilla %s", Group:GetName(), Flotilla.name)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Add assets to airwing warehouse.
|
||||
self:AddAsset(Group, Nassets, nil, nil, nil, nil, Flotilla.skill, Flotilla.livery, Flotilla.name)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Group does not exist!")
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Flotilla does not exit!")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set pathfinding for all spawned naval groups.
|
||||
-- @param #FLEET self
|
||||
-- @param #boolean Switch If `true`, pathfinding is used.
|
||||
-- @return #FLEET self
|
||||
function FLEET:SetPathfinding(Switch)
|
||||
self.pathfinding=Switch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Define a set of retreat zones.
|
||||
-- @param #FLEET self
|
||||
-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones.
|
||||
-- @return #FLEET self
|
||||
function FLEET:SetRetreatZones(RetreatZoneSet)
|
||||
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a retreat zone.
|
||||
-- @param #FLEET self
|
||||
-- @param Core.Zone#ZONE RetreatZone Retreat zone.
|
||||
-- @return #FLEET self
|
||||
function FLEET:AddRetreatZone(RetreatZone)
|
||||
self.retreatZones:AddZone(RetreatZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get retreat zones.
|
||||
-- @param #FLEET self
|
||||
-- @return Core.Set#SET_ZONE Set of retreat zones.
|
||||
function FLEET:GetRetreatZones()
|
||||
return self.retreatZones
|
||||
end
|
||||
|
||||
--- Get flotilla by name.
|
||||
-- @param #FLEET self
|
||||
-- @param #string FlotillaName Name of the flotilla.
|
||||
-- @return Ops.Flotilla#FLOTILLA The Flotilla object.
|
||||
function FLEET:GetFlotilla(FlotillaName)
|
||||
local flotilla=self:_GetCohort(FlotillaName)
|
||||
return flotilla
|
||||
end
|
||||
|
||||
--- Get flotilla of an asset.
|
||||
-- @param #FLEET self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The flotilla asset.
|
||||
-- @return Ops.Flotilla#FLOTILLA The flotilla object.
|
||||
function FLEET:GetFlotillaOfAsset(Asset)
|
||||
local flotilla=self:GetFlotilla(Asset.squadname)
|
||||
return flotilla
|
||||
end
|
||||
|
||||
--- Remove asset from flotilla.
|
||||
-- @param #FLEET self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The flotilla asset.
|
||||
function FLEET:RemoveAssetFromFlotilla(Asset)
|
||||
local flotilla=self:GetFlotillaOfAsset(Asset)
|
||||
if flotilla then
|
||||
flotilla:DelAsset(Asset)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Start FLEET FSM.
|
||||
-- @param #FLEET self
|
||||
function FLEET:onafterStart(From, Event, To)
|
||||
|
||||
-- Start parent Warehouse.
|
||||
self:GetParent(self, FLEET).onafterStart(self, From, Event, To)
|
||||
|
||||
-- Info.
|
||||
self:I(self.lid..string.format("Starting FLEET v%s", FLEET.version))
|
||||
|
||||
end
|
||||
|
||||
--- Update status.
|
||||
-- @param #FLEET self
|
||||
function FLEET:onafterStatus(From, Event, To)
|
||||
|
||||
-- Status of parent Warehouse.
|
||||
self:GetParent(self).onafterStatus(self, From, Event, To)
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
----------------
|
||||
-- Transport ---
|
||||
----------------
|
||||
|
||||
self:CheckTransportQueue()
|
||||
|
||||
--------------
|
||||
-- Mission ---
|
||||
--------------
|
||||
|
||||
-- Check if any missions should be cancelled.
|
||||
self:CheckMissionQueue()
|
||||
|
||||
-----------
|
||||
-- Info ---
|
||||
-----------
|
||||
|
||||
-- General info:
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- Count missions not over yet.
|
||||
local Nmissions=self:CountMissionsInQueue()
|
||||
|
||||
-- Asset count.
|
||||
local Npq, Np, Nq=self:CountAssetsOnMission()
|
||||
|
||||
-- Asset string.
|
||||
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq)
|
||||
|
||||
-- Output.
|
||||
local text=string.format("%s: Missions=%d, Flotillas=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets)
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
------------------
|
||||
-- Mission Info --
|
||||
------------------
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Missions Total=%d:", #self.missionqueue)
|
||||
for i,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end
|
||||
local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0)
|
||||
local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage())
|
||||
|
||||
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- Transport Info --
|
||||
--------------------
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Transports Total=%d:", #self.transportqueue)
|
||||
for i,_transport in pairs(self.transportqueue) do
|
||||
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
|
||||
|
||||
local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end
|
||||
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier)
|
||||
|
||||
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Flotilla Info --
|
||||
-------------------
|
||||
if self.verbose>=3 then
|
||||
local text="Flotillas:"
|
||||
for i,_flotilla in pairs(self.cohorts) do
|
||||
local flotilla=_flotilla --Ops.Flotilla#FLOTILLA
|
||||
|
||||
local callsign=flotilla.callsignName and UTILS.GetCallsignName(flotilla.callsignName) or "N/A"
|
||||
local modex=flotilla.modex and flotilla.modex or -1
|
||||
local skill=flotilla.skill and tostring(flotilla.skill) or "N/A"
|
||||
|
||||
-- Flotilla text.
|
||||
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", flotilla.name, flotilla:GetState(), flotilla.aircrafttype, flotilla:CountAssets(true), #flotilla.assets, callsign, modex, skill)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after "NavyOnMission".
|
||||
-- @param #FLEET self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
|
||||
function FLEET:onafterNavyOnMission(From, Event, To, NavyGroup, Mission)
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Group %s on %s mission %s", NavyGroup:GetName(), Mission:GetType(), Mission:GetName()))
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,172 +0,0 @@
|
||||
--- **Ops** - Flotilla is a small naval group belonging to a fleet.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Set parameters like livery, skill valid for all flotilla members.
|
||||
-- * Define mission types, this flotilla can perform (see Ops.Auftrag#AUFTRAG).
|
||||
-- * Pause/unpause flotilla operations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Ops.Flotilla
|
||||
-- @image OPS_Flotilla.png
|
||||
|
||||
|
||||
--- FLOTILLA class.
|
||||
-- @type FLOTILLA
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity level.
|
||||
-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
|
||||
-- @extends Ops.Cohort#COHORT
|
||||
|
||||
--- *No captain can do very wrong if he places his ship alongside that of an enemy.* -- Horation Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The FLOTILLA Concept
|
||||
--
|
||||
-- A FLOTILLA is an essential part of a FLEET.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #FLOTILLA
|
||||
FLOTILLA = {
|
||||
ClassName = "FLOTILLA",
|
||||
verbose = 0,
|
||||
weaponData = {},
|
||||
}
|
||||
|
||||
--- FLOTILLA class version.
|
||||
-- @field #string version
|
||||
FLOTILLA.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FLOTILLA object and start the FSM.
|
||||
-- @param #FLOTILLA self
|
||||
-- @param #string TemplateGroupName Name of the template group.
|
||||
-- @param #number Ngroups Number of asset groups of this flotilla. Default 3.
|
||||
-- @param #string FlotillaName Name of the flotilla, e.g. "VFA-37".
|
||||
-- @return #FLOTILLA self
|
||||
function FLOTILLA:New(TemplateGroupName, Ngroups, FlotillaName)
|
||||
|
||||
-- Inherit everything from COHORT class.
|
||||
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, FlotillaName)) -- #FLOTILLA
|
||||
|
||||
-- All flotillas get mission type Nothing.
|
||||
self:AddMissionCapability(AUFTRAG.Type.NOTHING, 50)
|
||||
|
||||
-- Get initial ammo.
|
||||
self.ammo=self:_CheckAmmo()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Flotilla specific user functions.
|
||||
|
||||
--- Set fleet of this flotilla.
|
||||
-- @param #FLOTILLA self
|
||||
-- @param Ops.Fleet#FLEET Fleet The fleet.
|
||||
-- @return #FLOTILLA self
|
||||
function FLOTILLA:SetFleet(Fleet)
|
||||
self.legion=Fleet
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get fleet of this flotilla.
|
||||
-- @param #FLOTILLA self
|
||||
-- @return Ops.Fleet#FLEET The fleet.
|
||||
function FLOTILLA:GetFleet()
|
||||
return self.legion
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
||||
-- @param #FLOTILLA self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FLOTILLA:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Start the status monitoring.
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
--- On after "Status" event.
|
||||
-- @param #FLOTILLA self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FLOTILLA:onafterStatus(From, Event, To)
|
||||
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
|
||||
local skill=self.skill and tostring(self.skill) or "N/A"
|
||||
|
||||
local NassetsTot=#self.assets
|
||||
local NassetsInS=self:CountAssets(true)
|
||||
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
|
||||
if self.legion then
|
||||
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
|
||||
end
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
|
||||
fsmstate, self.aircrafttype, callsign, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Weapon data info.
|
||||
if self.verbose>=3 and self.weaponData then
|
||||
local text="Weapon Data:"
|
||||
for bit,_weapondata in pairs(self.weaponData) do
|
||||
local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData
|
||||
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bit, weapondata.RangeMin/1000, weapondata.RangeMax/1000)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Check if group has detected any units.
|
||||
self:_CheckAssetStatus()
|
||||
|
||||
end
|
||||
|
||||
if not self:IsStopped() then
|
||||
self:__Status(-60)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,170 +0,0 @@
|
||||
--- **Ops** - Brigade Platoon.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Set parameters like livery, skill valid for all platoon members.
|
||||
-- * Define modex and callsigns.
|
||||
-- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG).
|
||||
-- * Pause/unpause platoon operations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Ops.Platoon
|
||||
-- @image OPS_Platoon.png
|
||||
|
||||
|
||||
--- PLATOON class.
|
||||
-- @type PLATOON
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity level.
|
||||
-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
|
||||
-- @extends Ops.Cohort#COHORT
|
||||
|
||||
--- *Some cool cohort quote* -- Known Author
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The PLATOON Concept
|
||||
--
|
||||
-- A PLATOON is essential part of an BRIGADE.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #PLATOON
|
||||
PLATOON = {
|
||||
ClassName = "PLATOON",
|
||||
verbose = 0,
|
||||
weaponData = {},
|
||||
}
|
||||
|
||||
--- PLATOON class version.
|
||||
-- @field #string version
|
||||
PLATOON.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new PLATOON object and start the FSM.
|
||||
-- @param #PLATOON self
|
||||
-- @param #string TemplateGroupName Name of the template group.
|
||||
-- @param #number Ngroups Number of asset groups of this platoon. Default 3.
|
||||
-- @param #string PlatoonName Name of the platoon, e.g. "VFA-37".
|
||||
-- @return #PLATOON self
|
||||
function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName)
|
||||
|
||||
-- Inherit everything from COHORT class.
|
||||
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, PlatoonName)) -- #PLATOON
|
||||
|
||||
-- All platoons get mission type Nothing.
|
||||
self:AddMissionCapability(AUFTRAG.Type.NOTHING, 50)
|
||||
|
||||
-- Get ammo.
|
||||
self.ammo=self:_CheckAmmo()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Platoon specific user functions.
|
||||
|
||||
--- Set brigade of this platoon.
|
||||
-- @param #PLATOON self
|
||||
-- @param Ops.Brigade#BRIGADE Brigade The brigade.
|
||||
-- @return #PLATOON self
|
||||
function PLATOON:SetBrigade(Brigade)
|
||||
self.legion=Brigade
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get brigade of this platoon.
|
||||
-- @param #PLATOON self
|
||||
-- @return Ops.Brigade#BRIGADE The brigade.
|
||||
function PLATOON:GetBrigade()
|
||||
return self.legion
|
||||
end
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
||||
-- @param #PLATOON self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function PLATOON:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Start the status monitoring.
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
--- On after "Status" event.
|
||||
-- @param #PLATOON self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function PLATOON:onafterStatus(From, Event, To)
|
||||
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
|
||||
local skill=self.skill and tostring(self.skill) or "N/A"
|
||||
|
||||
local NassetsTot=#self.assets
|
||||
local NassetsInS=self:CountAssets(true)
|
||||
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
|
||||
if self.legion then
|
||||
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
|
||||
end
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
|
||||
fsmstate, self.aircrafttype, callsign, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Weapon data info.
|
||||
if self.verbose>=3 and self.weaponData then
|
||||
local text="Weapon Data:"
|
||||
for bit,_weapondata in pairs(self.weaponData) do
|
||||
local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData
|
||||
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bit, weapondata.RangeMin/1000, weapondata.RangeMax/1000)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Check if group has detected any units.
|
||||
self:_CheckAssetStatus()
|
||||
|
||||
end
|
||||
|
||||
if not self:IsStopped() then
|
||||
self:__Status(-60)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Misc functions.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -31,7 +31,7 @@
|
||||
-- @field #string tankergroupname Name of the late activated tanker template group.
|
||||
-- @field Wrapper.Group#GROUP tanker Tanker group.
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier.
|
||||
-- @field Core.Beacon#BEACON beacon Tanker TACAN beacon.
|
||||
-- @field Core.Radio#BEACON beacon Tanker TACAN beacon.
|
||||
-- @field #number TACANchannel TACAN channel. Default 1.
|
||||
-- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". Use only "Y" for AA TACAN stations!
|
||||
-- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR".
|
||||
@@ -784,11 +784,10 @@ end
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param #number channel TACAN channel. Default 1.
|
||||
-- @param #string morse TACAN morse code identifier. Three letters. Default "TKR".
|
||||
-- @param #string mode TACAN mode, which can be either "Y" (default) or "X".
|
||||
-- @return #RECOVERYTANKER self
|
||||
function RECOVERYTANKER:SetTACAN(channel, morse, mode)
|
||||
function RECOVERYTANKER:SetTACAN(channel, morse)
|
||||
self.TACANchannel=channel or 1
|
||||
self.TACANmode=mode or "Y"
|
||||
self.TACANmode="Y"
|
||||
self.TACANmorse=morse or "TKR"
|
||||
self.TACANon=true
|
||||
return self
|
||||
@@ -1626,6 +1625,7 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
|
||||
if delay and delay>0 then
|
||||
|
||||
-- Schedule TACAN activation.
|
||||
--SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay)
|
||||
self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self)
|
||||
|
||||
else
|
||||
|
||||
@@ -1,911 +0,0 @@
|
||||
--- **Ops** - (R2.5) - Manage behavior of the Carrier Strike Group.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Nice stuff.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Ops.Skipper
|
||||
-- @image OPS_Skipper.png
|
||||
|
||||
|
||||
--- SKIPPER class.
|
||||
-- @type SKIPPER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string sid Class id string for output to DCS log file.
|
||||
-- @field #string carriername The name of the carrier unit.
|
||||
-- @field Wrapper.Group#GROUP group The carrier strike group.
|
||||
-- @field #table menu Table of menu items.
|
||||
-- @field Functional.Warehouse#WAREHOUSE warehouse The warehouse of the carrier.
|
||||
-- @field Functional.Artillery#ARTY arty The artillery object of the carrier.
|
||||
-- @field #table intruders Table of intruders, i.e. groups inside the CCA. Each element is of type #SKIPPPER.Intruder.
|
||||
-- @field Ops.CarrierAirWing#CVW cvw Carrier Air Wing.
|
||||
-- @extends Ops.Airboss#AIRBOSS
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The SKIPPER Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #SKIPPER
|
||||
SKIPPER = {
|
||||
ClassName = "SKIPPER",
|
||||
sid = nil,
|
||||
carriername = nil,
|
||||
group = nil,
|
||||
menu = nil,
|
||||
warehouse = nil,
|
||||
arty = nil,
|
||||
intruders = {},
|
||||
cvw = nil,
|
||||
}
|
||||
|
||||
--- Intruder.
|
||||
-- @type SKIPPER.Intruder
|
||||
-- @field Wrapper.Group#GROUP group Intruder group object.
|
||||
-- @field #string groupname Name of the intruder group.
|
||||
-- @field #number time0 Abs. mission time first detected inside CCA.
|
||||
-- @field #number dist0 Distance first detected inside CCA.
|
||||
-- @field DCS#Coalition.Side coalition Coalition side.
|
||||
-- @field #number threadlevel Thread level of intruder.
|
||||
-- @field #string threadtext Thread text.
|
||||
-- @field #number category Group category.
|
||||
-- @field #string categoryname Group category name.
|
||||
-- @field #string typename Type name of group.
|
||||
-- @field #table menu The defence menu entries.
|
||||
|
||||
--- FlightControl class version.
|
||||
-- @field #string version
|
||||
SKIPPER.version="0.0.5"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot!
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new SKIPPER class object for a specific aircraft carrier unit.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #string carriername Name of the carrier.
|
||||
-- @param #string alias Skipper alias.
|
||||
-- @return #SKIPPER self
|
||||
function SKIPPER:New(carriername, alias)
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
--local self=BASE:Inherit(self, FSM:New()) -- #SKIPPER
|
||||
local self=BASE:Inherit(self, AIRBOSS:New(carriername, alias)) -- #SKIPPER
|
||||
|
||||
if not self.carrier then
|
||||
BASE:E(string.format("ERROR: Could not find carrier %s!", carriername))
|
||||
return nil
|
||||
end
|
||||
|
||||
--self.alias=alias or carriername
|
||||
|
||||
--self:
|
||||
|
||||
self.carriername=carriername
|
||||
|
||||
self.group=self.carrier:GetGroup()
|
||||
|
||||
self.arty=ARTY:New(self.group, carriername, self.alias)
|
||||
|
||||
self.warehouse=WAREHOUSE:New(carriername, self.alias)
|
||||
|
||||
local skipper=self
|
||||
|
||||
--- Function for WAREHOUSE self request events.
|
||||
function self.warehouse:OnAfterSelfRequest(From,Event,To,groupset,request)
|
||||
env.info("FF Warehous self request!")
|
||||
skipper:_SelfRequest(self, groupset, request)
|
||||
end
|
||||
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.sid=string.format("SKIPPER %s |", self.carriername)
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "SkipperStatus", "*") -- Skipper status update.
|
||||
self:AddTransition("*", "IntruderAlert", "*") -- New intruder detected.
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the SKIPPER. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#SKIPPER] Start
|
||||
-- @param #SKIPPER self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the SKIPPER. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#SKIPPER] __Start
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the SKIPPER and all its event handlers.
|
||||
-- @param #SKIPPER self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the SKIPPER and all its event handlers.
|
||||
-- @function [parent=#SKIPPER] __Stop
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "SkipperStatus".
|
||||
-- @function [parent=#SKIPPER] SkipperStatus
|
||||
-- @param #SKIPPER self
|
||||
|
||||
--- Triggers the FSM event "SkipperStatus" after a delay.
|
||||
-- @function [parent=#SKIPPER] __SkipperStatus
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "IntruderAlert".
|
||||
-- @function [parent=#SKIPPER] IntruderAlert
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder Intruder data table.
|
||||
|
||||
--- Triggers the FSM delayed event "IntruderAlert".
|
||||
-- @function [parent=#SKIPPER] __IntruderAlert
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number delay Delay in seconds before the function is called.
|
||||
-- @param #SKIPPER.Intruder Intruder data table.
|
||||
|
||||
--- On after "IntruderAlert" event user function. Called when a missile was launched.
|
||||
-- @function [parent=#SKIPPER] OnAfterIntruderAlert
|
||||
-- @param #SKIPPER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #SKIPPER.Intruder Intruder data table.
|
||||
|
||||
|
||||
-- Debug trace.
|
||||
if true then
|
||||
self.Debug=true
|
||||
BASE:TraceOnOff(true)
|
||||
BASE:TraceClass(self.ClassName)
|
||||
BASE:TraceLevel(1)
|
||||
self.arty:GetAmmo(true)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Optimized F10 radio menu for a single carrier. The menu entries will be stored directly under F10 Other/Skipper/ and not F10 Other/Skipper/"Carrier Alias"/.
|
||||
-- **WARNING**: If you use this with two SKIPPER objects/carriers, the radio menu will be screwed up!
|
||||
-- @param #SKIPPER self
|
||||
-- @param #boolean switch If true or nil single menu is enabled. If false, menu is for multiple carriers in the mission.
|
||||
-- @return #SKIPPER self
|
||||
function SKIPPER:SetMenuSingleCarrier(switch)
|
||||
if switch==true or switch==nil then
|
||||
self.menusingle=true
|
||||
else
|
||||
self.menusingle=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get carrier coalition.
|
||||
-- @param #SKIPPER self
|
||||
-- @return #number Coalition side of carrier.
|
||||
function SKIPPER:GetCoalition()
|
||||
return self.carrier:GetCoalition()
|
||||
end
|
||||
|
||||
--- Get carrier coordinate.
|
||||
-- @param #SKIPPER self
|
||||
-- @return Core.Point#COORDINATE Carrier coordinate.
|
||||
function SKIPPER:GetCoordinate()
|
||||
return self.carrier:GetCoordinate()
|
||||
end
|
||||
|
||||
--- Get WAREHOUSE object associated with the carrier.
|
||||
-- @param #SKIPPER self
|
||||
-- @return Functional.Warehouse#WAREHOUSE Warehouse object.
|
||||
function SKIPPER:GetWarehouseCarrier()
|
||||
return self.warehouse
|
||||
end
|
||||
|
||||
--- Get ARTY object associated with the carrier strike group.
|
||||
-- @param #SKIPPER self
|
||||
-- @return Functional.Artillery#ARTY Arty object.
|
||||
function SKIPPER:GetArty()
|
||||
return self.arty
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Start SKIPPER FSM. Handle events.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:onafterStart(From, Event, To)
|
||||
|
||||
-- Start parent airboss.
|
||||
self:GetParent(self).onafterStart(self, From, Event, To)
|
||||
|
||||
-- Info.
|
||||
self:I(self.sid..string.format("Starting SKIPPER v%s for carrier %s on map %s.", SKIPPER.version, self.carriername, self.theatre))
|
||||
|
||||
-- Start ARTY.
|
||||
self.arty:Start()
|
||||
|
||||
-- Start Warehouse.
|
||||
self.warehouse:Start()
|
||||
|
||||
-- Add F10 radio menu.
|
||||
self:_SetMenuCoalition()
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
self:HandleEvent(EVENTS.EngineStartup)
|
||||
self:HandleEvent(EVENTS.Takeoff)
|
||||
self:HandleEvent(EVENTS.Land)
|
||||
self:HandleEvent(EVENTS.EngineShutdown)
|
||||
self:HandleEvent(EVENTS.Crash)
|
||||
|
||||
-- Init status updates.
|
||||
self:__SkipperStatus(-1)
|
||||
end
|
||||
|
||||
--- Update status.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:onafterSkipperStatus(From, Event, To)
|
||||
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
-- Check zone for flights inbound.
|
||||
self:_CheckIntruder()
|
||||
|
||||
-- Info text.
|
||||
local text=string.format("State %s", fsmstate)
|
||||
self:I(self.sid..text)
|
||||
|
||||
self:__SkipperStatus(-30)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Events
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- CCA Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Scan carrier zone for (new) units.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:_CheckIntruder()
|
||||
|
||||
-- Carrier position.
|
||||
local coord=self:GetCoordinate()
|
||||
|
||||
-- Scan radius = radius of the CCA.
|
||||
local RCCZ=self.zoneCCA:GetRadius()
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.sid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.", UTILS.MetersToNM(RCCZ)))
|
||||
|
||||
-- Scan units in carrier zone.
|
||||
local _,_,_,unitscan=coord:ScanObjects(RCCZ, true, false, false)
|
||||
|
||||
|
||||
-- Make a table with all groups currently in the CCA zone.
|
||||
local insideCCA={}
|
||||
for _,_unit in pairs(unitscan) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
-- Necessary conditions to be met:
|
||||
local airborne=unit:IsAir()
|
||||
local inzone=unit:IsInZone(self.zoneCCA)
|
||||
local friendly=self:GetCoalition()==unit:GetCoalition()
|
||||
|
||||
-- Check if this an aircraft and that it is airborne and closing in.
|
||||
if inzone then
|
||||
|
||||
local group=unit:GetGroup()
|
||||
local groupname=group:GetName()
|
||||
|
||||
if insideCCA[groupname]==nil and groupname~=self.group:GetName() then
|
||||
insideCCA[groupname]=group
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Intruder menu needs update.
|
||||
local updateintrudermenu=false
|
||||
|
||||
-- Find out if any known intruder is not in the CCA any more.
|
||||
for i=#self.intruders,1,-1 do
|
||||
local intruder=self.intruders[i] --#SKIPPER.Intruder
|
||||
|
||||
-- Loop over current groups in CCA.
|
||||
local gotit=false
|
||||
for groupname,_group in pairs(insideCCA) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
|
||||
if groupname==intruder.groupname then
|
||||
gotit=true
|
||||
end
|
||||
end
|
||||
|
||||
-- Intruder is gone!
|
||||
if not gotit then
|
||||
|
||||
-- Remove from table.
|
||||
table.remove(self.intruders, i)
|
||||
|
||||
-- Menu update required.
|
||||
updateintrudermenu=true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
for groupname,_group in pairs(insideCCA) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
|
||||
-- Find out if any known intruder is not in the CCA any more.
|
||||
local gotit=false
|
||||
for i=1,#self.intruders do
|
||||
local intruder=self.intruders[i] --#SKIPPER.Intruder
|
||||
if groupname==intruder.groupname then
|
||||
gotit=true
|
||||
end
|
||||
end
|
||||
|
||||
if not gotit then
|
||||
|
||||
-- Create a new intruder table.
|
||||
local intruder={} --#SKIPPER.Intruder
|
||||
intruder.coalition=group:GetCoalition()
|
||||
intruder.group=group
|
||||
intruder.threadlevel, intruder.threadtext=group:GetUnit(1):GetThreatLevel()
|
||||
intruder.time0=timer.getAbsTime()
|
||||
intruder.dist0=self:GetCoordinate():Get2DDistance(group:GetCoordinate())
|
||||
intruder.groupname=groupname
|
||||
intruder.typename=group:GetTypeName()
|
||||
intruder.category=group:GetCategory()
|
||||
intruder.categoryname=group:GetCategoryName()
|
||||
|
||||
-- Add intruder to list.
|
||||
table.insert(self.intruders, intruder)
|
||||
|
||||
-- Trigger alert!
|
||||
self:IntruderAlert(intruder)
|
||||
|
||||
-- Menu update required.
|
||||
updateintrudermenu=true
|
||||
end
|
||||
end
|
||||
|
||||
-- Update intruder menu if necessary.
|
||||
if updateintrudermenu then
|
||||
self:_SortIntruders()
|
||||
self:_UpdateIntruderMenu()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Menu Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Patrol carrier.
|
||||
-- @param #SKIPPER self
|
||||
-- @return #SKIPPER self
|
||||
function SKIPPER:_SetMenuCoalition()
|
||||
|
||||
-- Get coalition.
|
||||
local Coalition=self:GetCoalition()
|
||||
|
||||
-- Init menu table.
|
||||
self.menu=self.menu or {}
|
||||
|
||||
-- Init menu coalition table.
|
||||
self.menu[Coalition]=self.menu[Coalition] or {}
|
||||
|
||||
-- Shortcut.
|
||||
local menu=self.menu[Coalition]
|
||||
|
||||
if self.menusingle then
|
||||
-- F10/Skipper/...
|
||||
if not menu.Skipper then
|
||||
menu.Skipper=MENU_COALITION:New(Coalition, "Skipper")
|
||||
end
|
||||
else
|
||||
-- F10/Skipper/<Carrier Alias>/...
|
||||
if not menu.Root then
|
||||
menu.Root=MENU_COALITION:New(Coalition, "Skipper")
|
||||
end
|
||||
menu.Skipper=MENU_COALITION:New(Coalition, self.alias, menu.Root)
|
||||
end
|
||||
|
||||
------------------
|
||||
-- Defence Menu --
|
||||
------------------
|
||||
|
||||
menu.SetSpeed = MENU_COALITION:New(Coalition, "Set Speed", menu.Skipper)
|
||||
menu.SetSpeed_00 = MENU_COALITION_COMMAND:New(Coalition, "Hold Position", menu.SetSpeed, self._SetSpeed, self, 0)
|
||||
menu.SetSpeed_05 = MENU_COALITION_COMMAND:New(Coalition, "5 knots", menu.SetSpeed, self._SetSpeed, self, 5)
|
||||
menu.SetSpeed_10 = MENU_COALITION_COMMAND:New(Coalition, "10 knots", menu.SetSpeed, self._SetSpeed, self, 10)
|
||||
menu.SetSpeed_15 = MENU_COALITION_COMMAND:New(Coalition, "15 knots", menu.SetSpeed, self._SetSpeed, self, 15)
|
||||
menu.SetSpeed_20 = MENU_COALITION_COMMAND:New(Coalition, "20 knots", menu.SetSpeed, self._SetSpeed, self, 20)
|
||||
menu.SetSpeed_25 = MENU_COALITION_COMMAND:New(Coalition, "25 knots", menu.SetSpeed, self._SetSpeed, self, 25)
|
||||
menu.SetSpeed_30 = MENU_COALITION_COMMAND:New(Coalition, "30 knots", menu.SetSpeed, self._SetSpeed, self, 30)
|
||||
menu.SetSpeed_99 = MENU_COALITION_COMMAND:New(Coalition, "Restore Route", menu.SetSpeed, self.CarrierResume, self)
|
||||
|
||||
menu.Recovery=MENU_COALITION:New(Coalition, "Recovery", menu.Skipper)
|
||||
|
||||
-- Set wind on deck.
|
||||
menu.SetWoD = MENU_COALITION:New(Coalition, "Wind on Deck", menu.Recovery)
|
||||
menu.SetWoD_10 = MENU_COALITION_COMMAND:New(Coalition, "10 knots", menu.SetWoD, self._SetWoD, self, 10)
|
||||
menu.SetWoD_15 = MENU_COALITION_COMMAND:New(Coalition, "15 knots", menu.SetWoD, self._SetWoD, self, 15)
|
||||
menu.SetWoD_20 = MENU_COALITION_COMMAND:New(Coalition, "20 knots", menu.SetWoD, self._SetWoD, self, 20)
|
||||
menu.SetWoD_25 = MENU_COALITION_COMMAND:New(Coalition, "25 knots", menu.SetWoD, self._SetWoD, self, 25)
|
||||
menu.SetWoD_30 = MENU_COALITION_COMMAND:New(Coalition, "30 knots", menu.SetWoD, self._SetWoD, self, 30)
|
||||
|
||||
-- Set Duration.
|
||||
menu.SetRtime = MENU_COALITION:New(Coalition, "Duration", menu.Recovery)
|
||||
menu.SetRtime_15 = MENU_COALITION_COMMAND:New(Coalition, "15 min", menu.SetRtime, self._SetRtime, self, 15)
|
||||
menu.SetRtime_30 = MENU_COALITION_COMMAND:New(Coalition, "30 min", menu.SetRtime, self._SetRtime, self, 30)
|
||||
menu.SetRtime_45 = MENU_COALITION_COMMAND:New(Coalition, "45 min", menu.SetRtime, self._SetRtime, self, 45)
|
||||
menu.SetRtime_60 = MENU_COALITION_COMMAND:New(Coalition, "60 min", menu.SetRtime, self._SetRtime, self, 60)
|
||||
menu.SetRtime_90 = MENU_COALITION_COMMAND:New(Coalition, "90 min", menu.SetRtime, self._SetRtime, self, 90)
|
||||
|
||||
-- Start/Stop.
|
||||
menu.SetUturn = MENU_COALITION_COMMAND:New(Coalition, "U-turn On/Off", menu.Recovery, self._SetUturn, self)
|
||||
menu.CaseI = MENU_COALITION_COMMAND:New(Coalition, "Start CASE I", menu.Recovery, self._StartCaseX, self, 1)
|
||||
menu.CaseII = MENU_COALITION_COMMAND:New(Coalition, "Start CASE II", menu.Recovery, self._StartCaseX, self, 2)
|
||||
menu.CaseIII = MENU_COALITION_COMMAND:New(Coalition, "Start CASE III", menu.Recovery, self._StartCaseX, self, 3)
|
||||
menu.Rstop = MENU_COALITION_COMMAND:New(Coalition, "Pause/Unpause", menu.Recovery, self._Rpause, self)
|
||||
menu.Rstop = MENU_COALITION_COMMAND:New(Coalition, "Stop Recovery", menu.Recovery, self._Rstop, self)
|
||||
|
||||
|
||||
------------------
|
||||
-- Defence Menu --
|
||||
------------------
|
||||
|
||||
menu.Defence = MENU_COALITION:New(Coalition, "Defence", menu.Skipper)
|
||||
menu.Defence_SetROE = MENU_COALITION:New(Coalition, "Set ROE", menu.Defence)
|
||||
menu.Defence_SetROE_Hold = MENU_COALITION_COMMAND:New(Coalition, "Weapon Hold", menu.Defence_SetROE, self._SetROE, self, "Hold")
|
||||
menu.Defence_SetROE_Free = MENU_COALITION_COMMAND:New(Coalition, "Weapon Free", menu.Defence_SetROE, self._SetROE, self, "Free")
|
||||
menu.Defence_SetROE_Return = MENU_COALITION_COMMAND:New(Coalition, "Return Fire", menu.Defence_SetROE, self._SetROE, self, "Return")
|
||||
|
||||
menu.Defence_Intruders = MENU_COALITION:New(Coalition, "Intruders", menu.Defence)
|
||||
menu.Defence_Ammo = MENU_COALITION_COMMAND:New(Coalition, "Report Ammo", menu.Defence, self.arty.GetAmmo, self.arty, true)
|
||||
menu.Defence_IntruderR = MENU_COALITION_COMMAND:New(Coalition, "Report Intruders", menu.Defence, self._ListIntruders, self)
|
||||
menu.Defence_LaunchCAP = MENU_COALITION_COMMAND:New(Coalition, "Lauch CAP", menu.Defence, self.LaunchCAP, self)
|
||||
|
||||
--------------------
|
||||
-- Warehouse Menu --
|
||||
--------------------
|
||||
|
||||
if self.warehouse then
|
||||
menu.Warehouse=MENU_COALITION:New(Coalition, "Warehouse", menu.Skipper)
|
||||
menu.Warehouse_Assets = MENU_COALITION_COMMAND:New(Coalition, "Reports On/Off", menu.Warehouse, self.WarehouseReportsToggle, self)
|
||||
menu.Warehouse_Assets = MENU_COALITION_COMMAND:New(Coalition, "Report Assets", menu.Warehouse, self.ReportWarehouseStock, self)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Warehouse reports on/off.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:WarehouseReportsToggle()
|
||||
self.warehouse.Report=not self.warehouse.Report
|
||||
MESSAGE:New(string.format("Warehouse reports are now %s", tostring(self.warehouse.Report)), 10, "SKIPPER", true):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
|
||||
--- Report warehouse stock.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:ReportWarehouseStock()
|
||||
local text=self.warehouse:_GetStockAssetsText(false)
|
||||
MESSAGE:New(text, 10, "SKIPPER", true):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Handle self requests from carrier warehouse.
|
||||
-- @param #SKIPPER self
|
||||
-- @param Functional.Warehouse#WAREHOUSE warehouse The (carrier) warehouse object.
|
||||
-- @param Core.Set#SET_GROUP groupset Group set.
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request Request.
|
||||
function SKIPPER:_SelfRequest(warehouse, groupset, request)
|
||||
|
||||
local assignment=request.assignment
|
||||
|
||||
env.info(string.format("FF warehouse self request assignment %s", tostring(assignment)))
|
||||
|
||||
end
|
||||
|
||||
--- Launch a Intercept flight.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder Intruder group.
|
||||
function SKIPPER:LaunchIntercept(intruder)
|
||||
|
||||
--local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, WAREHOUSE.Attribute.AIR_FIGHTER, false)
|
||||
local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, "Intercept", false)
|
||||
|
||||
if n>0 then
|
||||
|
||||
self.warehouse:AddRequest(self.warehouse, WAREHOUSE.Descriptor.ASSIGNMENT, "Intercept", 1, nil, nil, nil, "Intercept")
|
||||
|
||||
local capcoord=intruder.group:GetCoordinate()
|
||||
|
||||
capcoord:MarkToAll("Intruder coord")
|
||||
|
||||
function self.warehouse.OnAfterSelfRequest(warehouse, From, Event, To, Groupset, Request)
|
||||
local request=Request --Functional.Warehouse#WAREHOUSE.Pendingitem
|
||||
local groupset=Groupset --Core.Set#SET_GROUP
|
||||
|
||||
for _,_group in pairs(groupset:GetSet()) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
|
||||
if request.assignment=="Intercept" then
|
||||
|
||||
-- Task orbit.
|
||||
local tasks={}
|
||||
|
||||
for _,unit in pairs(intruder.group:GetUnits()) do
|
||||
tasks[#tasks+1]=group:TaskAttackUnit(unit)
|
||||
end
|
||||
|
||||
local speed=group:GetSpeedMax()
|
||||
local altitude=intruder.group:GetAltitude()
|
||||
|
||||
-- Create waypoints.
|
||||
local wp={}
|
||||
wp[1]=self:GetCoordinate():WaypointAirTakeOffParking()
|
||||
wp[2]=self:GetCoordinate():SetAltitude(altitude):WaypointAirTurningPoint(COORDINATE.WaypointAltType.BARO, speed, {tasks}, "Attack Intruder")
|
||||
|
||||
-- Start uncontrolled group.
|
||||
group:StartUncontrolled()
|
||||
|
||||
--airboss:SetExcludeAI()
|
||||
|
||||
-- Route group
|
||||
group:Route(wp)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
MESSAGE:New("No INTERCEPT fighters currently available", 5, "SKIPPER"):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Launch a CAP flight.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:LaunchCAP()
|
||||
|
||||
--local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, WAREHOUSE.Attribute.AIR_FIGHTER, false)
|
||||
local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, "CAP", false)
|
||||
|
||||
if n>0 then
|
||||
|
||||
self.warehouse:AddRequest(self.warehouse, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_FIGHTER, 1, nil, nil, nil, "CAP")
|
||||
|
||||
local capcoord=self.zoneCCA:GetRandomCoordinate(self.zoneCCA:GetRadius()/2)
|
||||
|
||||
capcoord:MarkToAll("CAP coord")
|
||||
|
||||
function self.warehouse.OnAfterSelfRequest(warehouse, From, Event, To, Groupset, Request)
|
||||
local request=Request --Functional.Warehouse#WAREHOUSE.Pendingitem
|
||||
local groupset=Groupset --Core.Set#SET_GROUP
|
||||
|
||||
for _,_group in pairs(groupset:GetSet()) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
|
||||
if request.assignment=="CAP" then
|
||||
|
||||
local alt=UTILS.FeetToMeters(20000)
|
||||
|
||||
-- Task orbit.
|
||||
local taskOrbit=group:TaskOrbit(capcoord, alt, UTILS.KnotsToMps(400), capcoord:Translate(UTILS.NMToMeters(15), 0))
|
||||
|
||||
local wp={}
|
||||
wp[1]=self:GetCoordinate():WaypointAirTakeOffParking()
|
||||
wp[2]=self:GetCoordinate():SetAltitude(alt):WaypointAirTurningPoint(COORDINATE.WaypointAltType.BARO, UTILS.KnotsToKmph(350), {taskOrbit}, "CAP")
|
||||
|
||||
-- Start uncontrolled group.
|
||||
group:StartUncontrolled()
|
||||
|
||||
--airboss:SetExcludeAI()
|
||||
|
||||
-- Route group.
|
||||
group:Route(wp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
MESSAGE:New("No CAP fighters currently available", 5, "SKIPPER"):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Enage an intruder by ARTY.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder The intruder data.
|
||||
-- @param #number weapontype Type of weapon.
|
||||
function SKIPPER:EngageArty(intruder, weapontype)
|
||||
|
||||
weapontype=weapontype or ARTY.WeaponType.Auto
|
||||
|
||||
if intruder.group and intruder.group:IsAlive() then
|
||||
|
||||
-- Current position.
|
||||
local coord=intruder.group:GetCoordinate()
|
||||
|
||||
if intruder.category==Group.Category.GROUND or intruder.category==Group.Category.TRAIN then
|
||||
self.arty:AssignTargetCoord(coord, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
|
||||
elseif intruder.category==Group.Category.SHIP then
|
||||
self.arty:AssignAttackGroup(intruder.group, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
|
||||
elseif intruder.category==Group.Category.AIRPLANE or intruder.category==Group.Category.HELICOPTER then
|
||||
self.arty:AssignAttackGroup(intruder.group, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- Sort intruders table.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:_SortIntruders()
|
||||
|
||||
-- Sort potential section members wrt to distance to lead.
|
||||
local function _sort(_a,_b)
|
||||
local a=_a --#SKIPPER.Intruder
|
||||
local b=_b --#SKIPPER.Intruder
|
||||
return a.threadlevel>b.threadlevel
|
||||
end
|
||||
|
||||
table.sort(self.intruders, _sort)
|
||||
end
|
||||
|
||||
--- Add sub menu for this intruder.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder The intruder data.
|
||||
-- @param #boolean removed If true, an intruder was removed. If false or nil, a new intruder was added.
|
||||
function SKIPPER:_UpdateIntruderMenu()
|
||||
|
||||
for _,_intruder in ipairs(self.intruders) do
|
||||
local intruder=_intruder --#SKIPPER.Intruder
|
||||
self:_AddIntruderMenu(intruder)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Add sub menu for this intruder.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder The intruder data.
|
||||
function SKIPPER:_AddIntruderMenu(intruder)
|
||||
|
||||
local Coalition=self:GetCoalition()
|
||||
|
||||
local root=self.menu[Coalition].Defence_Intruders
|
||||
|
||||
local menu=MENU_COALITION:New(Coalition, intruder.typename, root)
|
||||
|
||||
if intruder.category==Group.Category.GROUND then
|
||||
MENU_COALITION_COMMAND:New(Coalition, "Engage Shells", menu, self.EngageArty, self, intruder, ARTY.WeaponType.Cannon)
|
||||
MENU_COALITION_COMMAND:New(Coalition, "Engage Cruise M", menu, self.EngageArty, self, intruder, ARTY.WeaponType.CruiseMissile)
|
||||
elseif intruder.category==Group.Category.SHIP then
|
||||
MENU_COALITION_COMMAND:New(Coalition, "Engage", menu, self.EngageArty, self, intruder)
|
||||
elseif intruder.category==Group.Category.AIRPLANE or intruder.category==Group.Category.HELICOPTER then
|
||||
MENU_COALITION_COMMAND:New(Coalition, "Engage A2A", menu, self.LaunchIntercept, self, intruder)
|
||||
end
|
||||
|
||||
-- Set menu.
|
||||
intruder.menu=menu
|
||||
|
||||
end
|
||||
|
||||
--- Remove sub menu for this intruder.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder The intruder data.
|
||||
function SKIPPER:_RemoveIntruderMenu(intruder)
|
||||
|
||||
if intruder.menu then
|
||||
local menu=intruder.menu --Core.Menu#MENU_COALITION
|
||||
|
||||
menu:Remove()
|
||||
|
||||
intruder.menu=nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Intruders.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:_ListIntruders()
|
||||
|
||||
local text="Current Intruders:"
|
||||
|
||||
for i,_intruder in pairs(self.intruders) do
|
||||
local intruder=_intruder --#SKIPPER.Intruder
|
||||
text=text..string.format("\n[%d] %s*%d, %s [%d/10]", i, intruder.typename, #intruder.group:GetUnits(), intruder.categoryname, intruder.threadlevel)
|
||||
end
|
||||
|
||||
if #self.intruders==0 then
|
||||
text=text.." none."
|
||||
end
|
||||
|
||||
MESSAGE:New(text, 10, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Start Case X recovery.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number case Recovery case (1,2,3).
|
||||
function SKIPPER:_StartCaseX(case)
|
||||
|
||||
--[[
|
||||
self.skipperTime=self.skipperTime or 30
|
||||
self.skipperSpeed=self.skipperSpeed or 25
|
||||
if self.skipperUturn==nil then
|
||||
self.skipperUturn=false
|
||||
end
|
||||
]]
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.", case, self.skipperTime, self.skipperSpeed, tostring(self.skipperUturn))
|
||||
|
||||
if self:IsRecovering() then
|
||||
text="negative, carrier is already recovering."
|
||||
MESSAGE:New(string.format(text), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
return
|
||||
end
|
||||
|
||||
-- Recovery staring in 5 min for 30 min.
|
||||
local t0=timer.getAbsTime()+5*60
|
||||
local t9=t0+self.skipperTime*60
|
||||
local C0=UTILS.SecondsToClock(t0)
|
||||
local C9=UTILS.SecondsToClock(t9)
|
||||
|
||||
-- Carrier will turn into the wind. Wind on deck 25 knots. U-turn on.
|
||||
self:AddRecoveryWindow(C0, C9, case, 30, true, self.skipperSpeed, self.skipperUturn)
|
||||
|
||||
MESSAGE:New(string.format(text), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Toggle recovery U-turn option.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:_SetUturn()
|
||||
|
||||
self.skipperUturn=not self.skipperUturn
|
||||
|
||||
MESSAGE:New(string.format("Recovery U-turn is now %s.", tostring(self.skipperUturn)), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Set manual recovery duration.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number time Duration in minutes.
|
||||
function SKIPPER:_SetRtime(time)
|
||||
|
||||
self.skipperTime=time
|
||||
|
||||
MESSAGE:New(string.format("Recovery duration set to %d min.", time), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
|
||||
--- Set wind on deck for manual recovery start.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number speed Speed in knots.
|
||||
function SKIPPER:_SetWoD(speed)
|
||||
|
||||
self.skipperSpeed=speed
|
||||
|
||||
MESSAGE:New(string.format("Wind on Deck set to %d knots.", speed), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Set new speed for all waypoints.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number speed Speed in knots.
|
||||
function SKIPPER:_SetSpeed(speed)
|
||||
|
||||
-- Loop over waypoints.
|
||||
for n=1,#self.waypoints do
|
||||
local coord=self.waypoints[n] --Core.Point#COORDINATE
|
||||
|
||||
coord.Velocity=UTILS.KnotsToMps(speed)
|
||||
end
|
||||
|
||||
self:_PatrolRoute()
|
||||
|
||||
end
|
||||
|
||||
--- Set rules of engagement.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #string roe "Hold", "Free", "Return".
|
||||
function SKIPPER:_SetROE(roe)
|
||||
|
||||
if roe=="Hold" then
|
||||
self.group:OptionROEHoldFire()
|
||||
elseif roe=="Free" then
|
||||
self.group:OptionROEOpenFire()
|
||||
elseif roe=="Return" then
|
||||
self.group:OptionROEReturnFire()
|
||||
end
|
||||
|
||||
MESSAGE:New(string.format("ROE set to %s", roe), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Set alarm state. (Not useful/working for ships.)
|
||||
-- @param #SKIPPER self
|
||||
-- @param #string state "Green", "Red", "Auto".
|
||||
function SKIPPER:_SetALS(state)
|
||||
|
||||
if state=="Green" then
|
||||
self.group:OptionAlarmStateGreen()
|
||||
elseif state=="Red" then
|
||||
self.group:OptionAlarmStateRed()
|
||||
elseif state=="Auto" then
|
||||
self.group:OptionAlarmStateAuto()
|
||||
end
|
||||
|
||||
MESSAGE:New(string.format("Alarm state set to %s", state), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Function to stop the carrier.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:CarrierHold()
|
||||
env.info("Carrier Hold!")
|
||||
|
||||
-- Get current position.
|
||||
local pos=self.group:GetCoordinate()
|
||||
|
||||
-- Create a new waypoint.
|
||||
local wp=pos:WaypointGround(0)
|
||||
|
||||
-- Create new route consisting of only this position ==> Stop!
|
||||
self.group:Route({wp})
|
||||
|
||||
MESSAGE:New(string.format("Carrier is holding current position."), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Function to stop the carrier.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:CarrierResume()
|
||||
env.info("Carrier Resume Route!")
|
||||
|
||||
self:_InitWaypoints()
|
||||
|
||||
local nextWP,n=self:_GetNextWaypoint()
|
||||
|
||||
self:_PatrolRoute(n)
|
||||
|
||||
MESSAGE:New(string.format("Carrier is resuming route to waypoint #%d.", n), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1,321 +0,0 @@
|
||||
--- **Ops** - Airwing Squadron.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Set parameters like livery, skill valid for all squadron members.
|
||||
-- * Define modex and callsigns.
|
||||
-- * Define mission types, this squadron can perform (see Ops.Auftrag#AUFTRAG).
|
||||
-- * Pause/unpause squadron operations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Ops.Squadron
|
||||
-- @image OPS_Squadron.png
|
||||
|
||||
|
||||
--- SQUADRON class.
|
||||
-- @type SQUADRON
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity level.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #string name Name of the squadron.
|
||||
-- @field #string templatename Name of the template group.
|
||||
-- @field #string aircrafttype Type of the airframe the squadron is using.
|
||||
-- @field Wrapper.Group#GROUP templategroup Template group.
|
||||
-- @field #number ngrouping User defined number of units in the asset group.
|
||||
-- @field #table assets Squadron assets.
|
||||
-- @field #table missiontypes Capabilities (mission types and performances) of the squadron.
|
||||
-- @field #number fuellow Low fuel threshold.
|
||||
-- @field #boolean fuellowRefuel If `true`, flight tries to refuel at the nearest tanker.
|
||||
-- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight.
|
||||
-- @field #number repairtime Time in seconds for each
|
||||
-- @field #string livery Livery of the squadron.
|
||||
-- @field #number skill Skill of squadron members.
|
||||
-- @field #number modex Modex.
|
||||
-- @field #number modexcounter Counter to incease modex number for assets.
|
||||
-- @field #string callsignName Callsign name.
|
||||
-- @field #number callsigncounter Counter to increase callsign names for new assets.
|
||||
-- @field #number Ngroups Number of asset flight groups this squadron has.
|
||||
-- @field #number engageRange Mission range in meters.
|
||||
-- @field #string attribute Generalized attribute of the squadron template group.
|
||||
-- @field #number tankerSystem For tanker squads, the refuel system used (boom=0 or probpe=1). Default nil.
|
||||
-- @field #number refuelSystem For refuelable squads, the refuel system used (boom=0 or probe=1). Default nil.
|
||||
-- @field #table tacanChannel List of TACAN channels available to the squadron.
|
||||
-- @field #number radioFreq Radio frequency in MHz the squad uses.
|
||||
-- @field #number radioModu Radio modulation the squad uses.
|
||||
-- @field #string takeoffType Take of type.
|
||||
-- @field #table parkingIDs Parking IDs for this squadron.
|
||||
-- @field #boolean despawnAfterLanding Aircraft are despawned after landing.
|
||||
-- @field #boolean despawnAfterHolding Aircraft are despawned after holding.
|
||||
-- @extends Ops.Cohort#COHORT
|
||||
|
||||
--- *It is unbelievable what a squadron of twelve aircraft did to tip the balance* -- Adolf Galland
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The SQUADRON Concept
|
||||
--
|
||||
-- A SQUADRON is essential part of an @{Ops.Airwing#AIRWING} and consists of **one** type of aircraft.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #SQUADRON
|
||||
SQUADRON = {
|
||||
ClassName = "SQUADRON",
|
||||
verbose = 0,
|
||||
modex = nil,
|
||||
modexcounter = 0,
|
||||
callsignName = nil,
|
||||
callsigncounter= 11,
|
||||
tankerSystem = nil,
|
||||
refuelSystem = nil,
|
||||
}
|
||||
|
||||
--- SQUADRON class version.
|
||||
-- @field #string version
|
||||
SQUADRON.version="0.8.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- DONE: Parking spots for squadrons?
|
||||
-- DONE: Engage radius.
|
||||
-- DONE: Modex.
|
||||
-- DONE: Call signs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new SQUADRON object and start the FSM.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string TemplateGroupName Name of the template group.
|
||||
-- @param #number Ngroups Number of asset groups of this squadron. Default 3.
|
||||
-- @param #string SquadronName Name of the squadron, e.g. "VFA-37".
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, SquadronName)) -- #SQUADRON
|
||||
|
||||
-- Everyone can ORBIT.
|
||||
self:AddMissionCapability(AUFTRAG.Type.ORBIT)
|
||||
|
||||
-- Refueling system.
|
||||
self.refuelSystem=select(2, self.templategroup:GetUnit(1):IsRefuelable())
|
||||
self.tankerSystem=select(2, self.templategroup:GetUnit(1):IsTanker())
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
-- See COHORT class
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set number of units in groups.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #number nunits Number of units. Must be >=1 and <=4. Default 2.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetGrouping(nunits)
|
||||
self.ngrouping=nunits or 2
|
||||
if self.ngrouping<1 then self.ngrouping=1 end
|
||||
if self.ngrouping>4 then self.ngrouping=4 end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid parking spot IDs. Assets of this squad are only allowed to be spawned at these parking spots. **Note** that the IDs are different from the ones displayed in the mission editor!
|
||||
-- @param #SQUADRON self
|
||||
-- @param #table ParkingIDs Table of parking ID numbers or a single `#number`.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetParkingIDs(ParkingIDs)
|
||||
if type(ParkingIDs)~="table" then
|
||||
ParkingIDs={ParkingIDs}
|
||||
end
|
||||
self.parkingIDs=ParkingIDs
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set takeoff type. All assets of this squadron will be spawned with cold (default) or hot engines.
|
||||
-- Spawning on runways is not supported.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string TakeoffType Take off type: "Cold" (default) or "Hot" with engines on or "Air" for spawning in air.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffType(TakeoffType)
|
||||
TakeoffType=TakeoffType or "Cold"
|
||||
if TakeoffType:lower()=="hot" then
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot
|
||||
elseif TakeoffType:lower()=="cold" then
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
|
||||
elseif TakeoffType:lower()=="air" then
|
||||
self.takeoffType=COORDINATE.WaypointType.TurningPoint
|
||||
else
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type cold (default). All assets of this squadron will be spawned with engines off (cold).
|
||||
-- @param #SQUADRON self
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffCold()
|
||||
self:SetTakeoffType("Cold")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type hot. All assets of this squadron will be spawned with engines on (hot).
|
||||
-- @param #SQUADRON self
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffHot()
|
||||
self:SetTakeoffType("Hot")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type air. All assets of this squadron will be spawned in air above the airbase.
|
||||
-- @param #SQUADRON self
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffAir()
|
||||
self:SetTakeoffType("Air")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set despawn after landing. Aircraft will be despawned after the landing event.
|
||||
-- Can help to avoid DCS AI taxiing issues.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #boolean Switch If `true` (default), activate despawn after landing.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetDespawnAfterLanding(Switch)
|
||||
if Switch then
|
||||
self.despawnAfterLanding=Switch
|
||||
else
|
||||
self.despawnAfterLanding=true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set despawn after holding. Aircraft will be despawned when they arrive at their holding position at the airbase.
|
||||
-- Can help to avoid DCS AI taxiing issues.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #boolean Switch If `true` (default), activate despawn after holding.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetDespawnAfterHolding(Switch)
|
||||
if Switch then
|
||||
self.despawnAfterHolding=Switch
|
||||
else
|
||||
self.despawnAfterHolding=true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set low fuel threshold.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #number LowFuel Low fuel threshold in percent. Default 25.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetFuelLowThreshold(LowFuel)
|
||||
self.fuellow=LowFuel or 25
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set if low fuel threshold is reached, flight tries to refuel at the neares tanker.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #boolean switch If true or nil, flight goes for refuelling. If false, turn this off.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetFuelLowRefuel(switch)
|
||||
if switch==false then
|
||||
self.fuellowRefuel=false
|
||||
else
|
||||
self.fuellowRefuel=true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set airwing.
|
||||
-- @param #SQUADRON self
|
||||
-- @param Ops.AirWing#AIRWING Airwing The airwing.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetAirwing(Airwing)
|
||||
self.legion=Airwing
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get airwing.
|
||||
-- @param #SQUADRON self
|
||||
-- @return Ops.AirWing#AIRWING The airwing.
|
||||
function SQUADRON:GetAirwing(Airwing)
|
||||
return self.legion
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function SQUADRON:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting SQUADRON", self.name)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Start the status monitoring.
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
--- On after "Status" event.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function SQUADRON:onafterStatus(From, Event, To)
|
||||
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
|
||||
local modex=self.modex and self.modex or -1
|
||||
local skill=self.skill and tostring(self.skill) or "N/A"
|
||||
|
||||
local NassetsTot=#self.assets
|
||||
local NassetsInS=self:CountAssets(true)
|
||||
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
|
||||
if self.legion then
|
||||
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
|
||||
end
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
|
||||
fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Check if group has detected any units.
|
||||
self:_CheckAssetStatus()
|
||||
|
||||
end
|
||||
|
||||
if not self:IsStopped() then
|
||||
self:__Status(-60)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,755 +0,0 @@
|
||||
--- **Ops** - Commander Air Wing.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Stuff
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Ops.WingCommander
|
||||
-- @image OPS_WingCommander.png
|
||||
|
||||
|
||||
--- WINGCOMMANDER class.
|
||||
-- @type WINGCOMMANDER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table airwings Table of airwings.
|
||||
-- @field #table missionqueue Mission queue.
|
||||
-- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory.
|
||||
-- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected.
|
||||
-- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged.
|
||||
-- @field #string Defcon Defence condition.
|
||||
-- @extends Ops.Intelligence#INTEL
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The WINGCOMMANDER Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #WINGCOMMANDER
|
||||
WINGCOMMANDER = {
|
||||
ClassName = "WINGCOMMANDER",
|
||||
Debug = nil,
|
||||
lid = nil,
|
||||
airwings = {},
|
||||
missionqueue = {},
|
||||
borderzoneset = nil,
|
||||
yellowzoneset = nil,
|
||||
engagezoneset = nil,
|
||||
}
|
||||
|
||||
--- Contact details.
|
||||
-- @type WINGCOMMANDER.Contact
|
||||
-- @field Ops.Auftrag#AUFTRAG mission The assigned mission.
|
||||
-- @extends Ops.Intelligence#INTEL.DetectedItem
|
||||
|
||||
--- Defence condition.
|
||||
-- @type WINGCOMMANDER.DEFCON
|
||||
-- @field #string GREEN No enemy activities detected.
|
||||
-- @field #string YELLOW Enemy near our border.
|
||||
-- @field #string RED Enemy within our border.
|
||||
WINGCOMMANDER.DEFCON = {
|
||||
GREEN="Green",
|
||||
YELLOW="Yellow",
|
||||
RED="Red",
|
||||
}
|
||||
|
||||
--- WINGCOMMANDER class version.
|
||||
-- @field #string version
|
||||
WINGCOMMANDER.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Define A2A and A2G parameters.
|
||||
-- TODO: Improve airwing selection. Mostly done!
|
||||
-- DONE: Add/remove spawned flightgroups to detection set.
|
||||
-- DONE: Borderzones.
|
||||
-- NOGO: Maybe it's possible to preselect the assets for the mission.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new WINGCOMMANDER object and start the FSM.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set.
|
||||
-- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral".
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:New(AgentSet, Coalition)
|
||||
|
||||
AgentSet=AgentSet or SET_GROUP:New()
|
||||
|
||||
-- Inherit everything from INTEL class.
|
||||
local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#WINGCOMMANDER
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
--self.lid=string.format("WINGCOMMANDER | ")
|
||||
|
||||
self:SetBorderZones()
|
||||
self:SetYellowZones()
|
||||
|
||||
self:SetThreatLevelRange()
|
||||
|
||||
self.Defcon=WINGCOMMANDER.DEFCON.GREEN
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to an AIRWING.
|
||||
self:AddTransition("*", "CancelMission", "*") -- Cancel mission.
|
||||
self:AddTransition("*", "Defcon", "*") -- Cancel mission.
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#WINGCOMMANDER] Start
|
||||
-- @param #WINGCOMMANDER self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#WINGCOMMANDER] __Start
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers.
|
||||
-- @param #WINGCOMMANDER self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers.
|
||||
-- @function [parent=#WINGCOMMANDER] __Stop
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Status".
|
||||
-- @function [parent=#WINGCOMMANDER] Status
|
||||
-- @param #WINGCOMMANDER self
|
||||
|
||||
--- Triggers the FSM event "SkipperStatus" after a delay.
|
||||
-- @function [parent=#WINGCOMMANDER] __Status
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
-- Debug trace.
|
||||
if false then
|
||||
self.Debug=true
|
||||
BASE:TraceOnOff(true)
|
||||
BASE:TraceClass(self.ClassName)
|
||||
BASE:TraceLevel(1)
|
||||
end
|
||||
self.Debug=true
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set this to be an air-to-any dispatcher, i.e. engaging air, ground and naval targets. This is the default anyway.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToAny()
|
||||
|
||||
self:SetFilterCategory({})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this to be an air-to-air dispatcher.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToAir()
|
||||
|
||||
self:SetFilterCategory({Unit.Category.AIRPLANE, Unit.Category.HELICOPTER})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this to be an air-to-ground dispatcher, i.e. engage only ground units
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToGround()
|
||||
|
||||
self:SetFilterCategory({Unit.Category.GROUND_UNIT})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this to be an air-to-sea dispatcher, i.e. engage only naval units.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToSea()
|
||||
|
||||
self:SetFilterCategory({Unit.Category.SHIP})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this to be an air-to-surface dispatcher, i.e. engaging ground and naval groups.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToSurface()
|
||||
|
||||
self:SetFilterCategory({Unit.Category.GROUND_UNIT, Unit.Category.SHIP})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set a threat level range that will be engaged. Threat level is a number between 0 and 10, where 10 is a very dangerous threat.
|
||||
-- Targets with threat level 0 are usually harmless.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #number ThreatLevelMin Min threat level. Default 1.
|
||||
-- @param #number ThreatLevelMax Max threat level. Default 10.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetThreatLevelRange(ThreatLevelMin, ThreatLevelMax)
|
||||
|
||||
self.threatLevelMin=ThreatLevelMin or 1
|
||||
self.threatLevelMax=ThreatLevelMax or 10
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set defence condition.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string Defcon Defence condition. See @{#WINGCOMMANDER.DEFCON}, e.g. `WINGCOMMANDER.DEFCON.RED`.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetDefcon(Defcon)
|
||||
|
||||
self.Defcon=Defcon
|
||||
--self:Defcon(Defcon)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add an airwing to the wingcommander.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Ops.AirWing#AIRWING Airwing The airwing to add.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:AddAirwing(Airwing)
|
||||
|
||||
-- This airwing is managed by this wing commander.
|
||||
Airwing.wingcommander=self
|
||||
|
||||
table.insert(self.airwings, Airwing)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add mission to mission queue.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission Mission to be added.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:AddMission(Mission)
|
||||
|
||||
Mission.wingcommander=self
|
||||
|
||||
table.insert(self.missionqueue, Mission)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove mission from queue.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:RemoveMission(Mission)
|
||||
|
||||
for i,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
if mission.auftragsnummer==Mission.auftragsnummer then
|
||||
self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status))
|
||||
table.remove(self.missionqueue, i)
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set border zone set.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetBorderZones(BorderZoneSet)
|
||||
|
||||
-- Border zones.
|
||||
self.borderzoneset=BorderZoneSet or SET_ZONE:New()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a zone defining your territory.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:AddBorderZone(BorderZone)
|
||||
|
||||
-- Add a border zone.
|
||||
self.borderzoneset:AddZone(BorderZone)
|
||||
|
||||
-- Set accept zone.
|
||||
--self:AddAcceptZone(BorderZone)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetYellowZones(YellowZoneSet)
|
||||
|
||||
-- Border zones.
|
||||
self.yellowzoneset=YellowZoneSet or SET_ZONE:New()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a zone defining an area outside your territory that is monitored for enemy activity.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:AddYellowZone(YellowZone)
|
||||
|
||||
-- Add a border zone.
|
||||
self.yellowzoneset:AddZone(YellowZone)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP Group Flight group.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function WINGCOMMANDER:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting Wing Commander")
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Start parent INTEL.
|
||||
self:GetParent(self).onafterStart(self, From, Event, To)
|
||||
|
||||
-- Start attached airwings.
|
||||
for _,_airwing in pairs(self.airwings) do
|
||||
local airwing=_airwing --Ops.AirWing#AIRWING
|
||||
if airwing:GetState()=="NotReadyYet" then
|
||||
airwing:Start()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after "Status" event.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP Group Flight group.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function WINGCOMMANDER:onafterStatus(From, Event, To)
|
||||
|
||||
-- Start parent INTEL.
|
||||
self:GetParent(self).onafterStatus(self, From, Event, To)
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
|
||||
-- Clean up missions where the contact was lost.
|
||||
for _,_contact in pairs(self.ContactsLost) do
|
||||
local contact=_contact --#WINGCOMMANDER.Contact
|
||||
|
||||
if contact.mission and contact.mission:IsNotOver() then
|
||||
|
||||
local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name)
|
||||
MESSAGE:New(text, 120, "WINGCOMMANDER"):ToAll()
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Cancel this mission.
|
||||
contact.mission:Cancel()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Create missions for all new contacts.
|
||||
local Nred=0
|
||||
local Nyellow=0
|
||||
local Nengage=0
|
||||
for _,_contact in pairs(self.Contacts) do
|
||||
local contact=_contact --#WINGCOMMANDER.Contact
|
||||
local group=contact.group --Wrapper.Group#GROUP
|
||||
|
||||
local inred=self:CheckGroupInBorder(group)
|
||||
if inred then
|
||||
Nred=Nred+1
|
||||
end
|
||||
|
||||
local inyellow=self:CheckGroupInYellow(group)
|
||||
if inyellow then
|
||||
Nyellow=Nyellow+1
|
||||
end
|
||||
|
||||
-- Is this a threat?
|
||||
local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax
|
||||
|
||||
local redalert=true
|
||||
if self.borderzoneset:Count()>0 then
|
||||
redalert=inred
|
||||
end
|
||||
|
||||
if redalert and threat and not contact.mission then
|
||||
|
||||
-- Create a mission based on group category.
|
||||
local mission=AUFTRAG:NewAUTO(group)
|
||||
|
||||
|
||||
-- Add mission to queue.
|
||||
if mission then
|
||||
|
||||
--TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max.
|
||||
mission.nassets=1
|
||||
|
||||
-- Missons are repeated max 3 times on failure.
|
||||
mission.missionRepeatMax=3
|
||||
|
||||
-- Set mission contact.
|
||||
contact.mission=mission
|
||||
|
||||
-- Add mission to queue.
|
||||
self:AddMission(mission)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Set defcon.
|
||||
-- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones.
|
||||
if Nred>0 then
|
||||
self:SetDefcon(WINGCOMMANDER.DEFCON.RED)
|
||||
elseif Nyellow>0 then
|
||||
self:SetDefcon(WINGCOMMANDER.DEFCON.YELLOW)
|
||||
else
|
||||
self:SetDefcon(WINGCOMMANDER.DEFCON.GREEN)
|
||||
end
|
||||
|
||||
|
||||
-- Check mission queue and assign one PLANNED mission.
|
||||
self:CheckMissionQueue()
|
||||
|
||||
local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Infor about contacts.
|
||||
if #self.Contacts>0 then
|
||||
local text="Contacts:"
|
||||
for i,_contact in pairs(self.Contacts) do
|
||||
local contact=_contact --#WINGCOMMANDER.Contact
|
||||
local mtext="N/A"
|
||||
if contact.mission then
|
||||
mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper())
|
||||
end
|
||||
text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Mission queue.
|
||||
if #self.missionqueue>0 then
|
||||
local text="Mission queue:"
|
||||
for i,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
local target=mission:GetTargetName() or "unknown"
|
||||
|
||||
text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Events
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after "MissionAssign" event. Mission is added to the AIRWING mission queue.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.AirWing#AIRWING Airwing The AIRWING.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
function WINGCOMMANDER:onafterMissionAssign(From, Event, To, Airwing, Mission)
|
||||
|
||||
self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias))
|
||||
Airwing:AddMission(Mission)
|
||||
|
||||
end
|
||||
|
||||
--- On after "CancelMission" event.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission)
|
||||
|
||||
self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status))
|
||||
|
||||
if Mission.status==AUFTRAG.Status.PLANNED then
|
||||
|
||||
-- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue.
|
||||
self:RemoveMission(Mission)
|
||||
|
||||
else
|
||||
|
||||
-- Airwing will cancel mission.
|
||||
if Mission.airwing then
|
||||
Mission.airwing:MissionCancel(Mission)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On before "Defcon" event.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Defcon New defence condition.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
function WINGCOMMANDER:onbeforeDefcon(From, Event, To, Defcon)
|
||||
|
||||
local gotit=false
|
||||
for _,defcon in pairs(WINGCOMMANDER.DEFCON) do
|
||||
if defcon==Defcon then
|
||||
gotit=true
|
||||
end
|
||||
end
|
||||
|
||||
if not gotit then
|
||||
self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon)))
|
||||
return false
|
||||
end
|
||||
|
||||
-- Defcon did not change.
|
||||
if Defcon==self.Defcon then
|
||||
self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon)))
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- On after "Defcon" event.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Defcon New defence condition.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
function WINGCOMMANDER:onafterDefcon(From, Event, To, Defcon)
|
||||
self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon))
|
||||
|
||||
-- Set new defcon.
|
||||
self.Defcon=Defcon
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Resources
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Check mission queue and assign ONE planned mission.
|
||||
-- @param #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:CheckMissionQueue()
|
||||
|
||||
-- TODO: Sort mission queue. wrt what? Threat level?
|
||||
|
||||
for _,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
-- We look for PLANNED missions.
|
||||
if mission.status==AUFTRAG.Status.PLANNED then
|
||||
|
||||
---
|
||||
-- PLANNNED Mission
|
||||
---
|
||||
|
||||
local airwing=self:GetAirwingForMission(mission)
|
||||
|
||||
if airwing then
|
||||
|
||||
-- Add mission to airwing.
|
||||
self:MissionAssign(airwing, mission)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Missions NOT in PLANNED state
|
||||
---
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
-- @return Ops.AirWing#AIRWING The airwing best for this mission.
|
||||
function WINGCOMMANDER:GetAirwingForMission(Mission)
|
||||
|
||||
-- Table of airwings that can do the mission.
|
||||
local airwings={}
|
||||
|
||||
-- Loop over all airwings.
|
||||
for _,_airwing in pairs(self.airwings) do
|
||||
local airwing=_airwing --Ops.AirWing#AIRWING
|
||||
|
||||
-- Check if airwing can do this mission.
|
||||
local can,assets=airwing:CanMission(Mission)
|
||||
|
||||
-- Can it?
|
||||
if can then
|
||||
|
||||
-- Get coordinate of the target.
|
||||
local coord=Mission:GetTargetCoordinate()
|
||||
|
||||
if coord then
|
||||
|
||||
-- Distance from airwing to target.
|
||||
local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate()))
|
||||
|
||||
-- Add airwing to table of airwings that can.
|
||||
table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets})
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Can anyone?
|
||||
if #airwings>0 then
|
||||
|
||||
--- Something like:
|
||||
-- * Closest airwing that can should be first prio.
|
||||
-- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job.
|
||||
local function score(a)
|
||||
local d=math.round(a.dist/10)
|
||||
end
|
||||
|
||||
-- Sort table wrt distance and number of assets.
|
||||
-- Distances within 10 NM are equal and the airwing with more assets is preferred.
|
||||
local function sortdist(a,b)
|
||||
local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6
|
||||
local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6
|
||||
return ad<bd or (ad==bd and a.nassets>b.nassets)
|
||||
end
|
||||
table.sort(airwings, sortdist)
|
||||
|
||||
-- This is the closest airwing to the target.
|
||||
local airwing=airwings[1].airwing --Ops.AirWing#AIRWING
|
||||
|
||||
return airwing
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Check if group is inside our border.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP group The group.
|
||||
-- @return #boolean If true, group is in any zone.
|
||||
function WINGCOMMANDER:CheckGroupInBorder(group)
|
||||
|
||||
local inside=self:CheckGroupInZones(group, self.borderzoneset)
|
||||
|
||||
return inside
|
||||
end
|
||||
|
||||
--- Check if group is near our border (yellow zone).
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP group The group.
|
||||
-- @return #boolean If true, group is in any zone.
|
||||
function WINGCOMMANDER:CheckGroupInYellow(group)
|
||||
|
||||
-- Check inside yellow but not inside our border.
|
||||
local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset)
|
||||
|
||||
return inside
|
||||
end
|
||||
|
||||
--- Check if group is inside a zone.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP group The group.
|
||||
-- @param Core.Set#SET_ZONE zoneset Set of zones.
|
||||
-- @return #boolean If true, group is in any zone.
|
||||
function WINGCOMMANDER:CheckGroupInZones(group, zoneset)
|
||||
|
||||
for _,_zone in pairs(zoneset.Set or {}) do
|
||||
local zone=_zone --Core.Zone#ZONE
|
||||
|
||||
if group:IsPartlyOrCompletelyInZone(zone) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check resources.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #table
|
||||
function WINGCOMMANDER:CheckResources()
|
||||
|
||||
local capabilities={}
|
||||
|
||||
for _,MissionType in pairs(AUFTRAG.Type) do
|
||||
capabilities[MissionType]=0
|
||||
|
||||
for _,_airwing in pairs(self.airwings) do
|
||||
local airwing=_airwing --Ops.AirWing#AIRWING
|
||||
|
||||
-- Get Number of assets that can do this type of missions.
|
||||
local _,assets=airwing:CanMission(MissionType)
|
||||
|
||||
-- Add up airwing resources.
|
||||
capabilities[MissionType]=capabilities[MissionType]+#assets
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return capabilities
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1,28 +1,28 @@
|
||||
--- **Sound** - Radio transmissions.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Provide radio functionality to broadcast radio transmissions.
|
||||
--
|
||||
--
|
||||
-- What are radio communications in DCS?
|
||||
--
|
||||
--
|
||||
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
|
||||
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
|
||||
--
|
||||
--
|
||||
-- How to supply DCS my own Sound Files?
|
||||
--
|
||||
--
|
||||
-- * Your sound files need to be encoded in **.ogg** or .wav,
|
||||
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
|
||||
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
|
||||
-- * For simplicity 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 @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
--
|
||||
-- * 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).
|
||||
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
|
||||
@@ -37,41 +37,41 @@
|
||||
|
||||
|
||||
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
|
||||
--
|
||||
--
|
||||
-- # RADIO usage
|
||||
--
|
||||
--
|
||||
-- There are 3 steps to a successful radio transmission.
|
||||
--
|
||||
--
|
||||
-- * 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 @{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 transmitter 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 transmitter 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 @{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 transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
|
||||
-- * This an automated DCS calculation you have no say on,
|
||||
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
|
||||
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
||||
--
|
||||
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
||||
--
|
||||
-- @type RADIO
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
|
||||
-- @field #string FileName Name of the sound file played.
|
||||
@@ -105,12 +105,12 @@ function RADIO:New(Positionable)
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
|
||||
self:F(Positionable)
|
||||
|
||||
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
|
||||
return nil
|
||||
end
|
||||
@@ -137,19 +137,19 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFileName(FileName)
|
||||
self:F2(FileName)
|
||||
|
||||
|
||||
if type(FileName) == "string" then
|
||||
|
||||
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
|
||||
|
||||
self.FileName = FileName
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
|
||||
return self
|
||||
end
|
||||
@@ -161,34 +161,34 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFrequency(Frequency)
|
||||
self:F2(Frequency)
|
||||
|
||||
|
||||
if type(Frequency) == "number" then
|
||||
|
||||
|
||||
-- If frequency is in range
|
||||
--if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
|
||||
|
||||
-- if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
|
||||
|
||||
-- Convert frequency from MHz to Hz
|
||||
self.Frequency = Frequency * 1000000
|
||||
|
||||
|
||||
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
|
||||
local commandSetFrequency={
|
||||
id = "SetFrequency",
|
||||
params = {
|
||||
frequency = self.Frequency,
|
||||
modulation = self.Modulation,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self:T2(commandSetFrequency)
|
||||
self.Positionable:SetCommand(commandSetFrequency)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
--end
|
||||
-- end
|
||||
end
|
||||
|
||||
|
||||
self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
|
||||
return self
|
||||
end
|
||||
@@ -215,13 +215,13 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetPower(Power)
|
||||
self:F2(Power)
|
||||
|
||||
|
||||
if type(Power) == "number" then
|
||||
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
else
|
||||
self:E({"Power is invalid. Power unchanged.", self.Power})
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -249,7 +249,7 @@ end
|
||||
-- -- create the broadcaster and attaches it a RADIO
|
||||
-- local MyUnit = UNIT:FindByName("MyUnit")
|
||||
-- local MyUnitRadio = MyUnit:GetRadio()
|
||||
--
|
||||
--
|
||||
-- -- add a subtitle for the next transmission, which will be up for 10s
|
||||
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
|
||||
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
@@ -264,14 +264,14 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
self.SubtitleDuration = SubtitleDuration
|
||||
else
|
||||
self.SubtitleDuration = 0
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
end
|
||||
return self
|
||||
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 anything but a UNIT or a GROUP,
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- Only the #RADIO and the Filename are mandatory
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of the sound file that will be transmitted.
|
||||
@@ -281,20 +281,20 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
|
||||
self:F({FileName, Frequency, Modulation, Power})
|
||||
|
||||
|
||||
self:SetFileName(FileName)
|
||||
if Frequency then self:SetFrequency(Frequency) end
|
||||
if Modulation then self:SetModulation(Modulation) end
|
||||
if Power then self:SetPower(Power) end
|
||||
if Loop then self:SetLoop(Loop) end
|
||||
|
||||
|
||||
return self
|
||||
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 @{Wrapper.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 Name of sound file.
|
||||
@@ -316,20 +316,20 @@ function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequen
|
||||
end
|
||||
|
||||
-- Set frequency.
|
||||
if Frequency then
|
||||
if Frequency then
|
||||
self:SetFrequency(Frequency)
|
||||
end
|
||||
|
||||
|
||||
-- Set subtitle.
|
||||
if Subtitle then
|
||||
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
|
||||
end
|
||||
|
||||
|
||||
-- Set Looping.
|
||||
if Loop then
|
||||
if Loop then
|
||||
self:SetLoop(Loop)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -346,7 +346,7 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:Broadcast(viatrigger)
|
||||
self:F({viatrigger=viatrigger})
|
||||
|
||||
|
||||
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
|
||||
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
|
||||
self:T("Broadcasting from a UNIT or a GROUP")
|
||||
@@ -359,7 +359,7 @@ function RADIO:Broadcast(viatrigger)
|
||||
subtitle = self.Subtitle,
|
||||
loop = self.Loop,
|
||||
}}
|
||||
|
||||
|
||||
self:T3(commandTransmitMessage)
|
||||
self.Positionable:SetCommand(commandTransmitMessage)
|
||||
else
|
||||
@@ -368,7 +368,7 @@ function RADIO:Broadcast(viatrigger)
|
||||
self:T("Broadcasting from a POSITIONABLE")
|
||||
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -380,11 +380,11 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:StopBroadcast()
|
||||
self:F()
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
|
||||
local commandStopTransmission={id="StopTransmission", params={}}
|
||||
|
||||
|
||||
self.Positionable:SetCommand(commandStopTransmission)
|
||||
else
|
||||
-- Else, we use the appropriate singleton funciton
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
--- **Core** - Makes the radio talk.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Send text strings using a vocabulary that is converted in spoken language.
|
||||
-- * Possiblity to implement multiple language.
|
||||
--
|
||||
@@ -15,10 +15,10 @@
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Makes the radio speak.
|
||||
--
|
||||
--
|
||||
-- # RADIOSPEECH usage
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @type RADIOSPEECH
|
||||
-- @extends Core.RadioQueue#RADIOQUEUE
|
||||
RADIOSPEECH = {
|
||||
@@ -59,24 +59,24 @@ RADIOSPEECH.Vocabulary.EN = {
|
||||
["70"] = { "70", 0.48 },
|
||||
["80"] = { "80", 0.26 },
|
||||
["90"] = { "90", 0.36 },
|
||||
["100"] = { "100", 0.55 },
|
||||
["200"] = { "200", 0.55 },
|
||||
["300"] = { "300", 0.61 },
|
||||
["400"] = { "400", 0.60 },
|
||||
["500"] = { "500", 0.61 },
|
||||
["600"] = { "600", 0.65 },
|
||||
["700"] = { "700", 0.70 },
|
||||
["800"] = { "800", 0.54 },
|
||||
["900"] = { "900", 0.60 },
|
||||
["1000"] = { "1000", 0.60 },
|
||||
["2000"] = { "2000", 0.61 },
|
||||
["3000"] = { "3000", 0.64 },
|
||||
["4000"] = { "4000", 0.62 },
|
||||
["5000"] = { "5000", 0.69 },
|
||||
["6000"] = { "6000", 0.69 },
|
||||
["7000"] = { "7000", 0.75 },
|
||||
["8000"] = { "8000", 0.59 },
|
||||
["9000"] = { "9000", 0.65 },
|
||||
["100"] = { "100", 0.55 },
|
||||
["200"] = { "200", 0.55 },
|
||||
["300"] = { "300", 0.61 },
|
||||
["400"] = { "400", 0.60 },
|
||||
["500"] = { "500", 0.61 },
|
||||
["600"] = { "600", 0.65 },
|
||||
["700"] = { "700", 0.70 },
|
||||
["800"] = { "800", 0.54 },
|
||||
["900"] = { "900", 0.60 },
|
||||
["1000"] = { "1000", 0.60 },
|
||||
["2000"] = { "2000", 0.61 },
|
||||
["3000"] = { "3000", 0.64 },
|
||||
["4000"] = { "4000", 0.62 },
|
||||
["5000"] = { "5000", 0.69 },
|
||||
["6000"] = { "6000", 0.69 },
|
||||
["7000"] = { "7000", 0.75 },
|
||||
["8000"] = { "8000", 0.59 },
|
||||
["9000"] = { "9000", 0.65 },
|
||||
|
||||
["chevy"] = { "chevy", 0.35 },
|
||||
["colt"] = { "colt", 0.35 },
|
||||
@@ -94,10 +94,10 @@ RADIOSPEECH.Vocabulary.EN = {
|
||||
["meters"] = { "meters", 0.41 },
|
||||
["mi"] = { "miles", 0.45 },
|
||||
["feet"] = { "feet", 0.29 },
|
||||
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
|
||||
["returning to base"] = { "returning_to_base", 0.85 },
|
||||
["on route to ground target"] = { "on_route_to_ground_target", 1.05 },
|
||||
@@ -143,24 +143,24 @@ RADIOSPEECH.Vocabulary.RU = {
|
||||
["70"] = { "70", 0.68 },
|
||||
["80"] = { "80", 0.84 },
|
||||
["90"] = { "90", 0.71 },
|
||||
["100"] = { "100", 0.35 },
|
||||
["200"] = { "200", 0.59 },
|
||||
["300"] = { "300", 0.53 },
|
||||
["400"] = { "400", 0.70 },
|
||||
["500"] = { "500", 0.50 },
|
||||
["600"] = { "600", 0.58 },
|
||||
["700"] = { "700", 0.64 },
|
||||
["800"] = { "800", 0.77 },
|
||||
["900"] = { "900", 0.75 },
|
||||
["1000"] = { "1000", 0.87 },
|
||||
["2000"] = { "2000", 0.83 },
|
||||
["3000"] = { "3000", 0.84 },
|
||||
["4000"] = { "4000", 1.00 },
|
||||
["5000"] = { "5000", 0.77 },
|
||||
["6000"] = { "6000", 0.90 },
|
||||
["7000"] = { "7000", 0.77 },
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
["100"] = { "100", 0.35 },
|
||||
["200"] = { "200", 0.59 },
|
||||
["300"] = { "300", 0.53 },
|
||||
["400"] = { "400", 0.70 },
|
||||
["500"] = { "500", 0.50 },
|
||||
["600"] = { "600", 0.58 },
|
||||
["700"] = { "700", 0.64 },
|
||||
["800"] = { "800", 0.77 },
|
||||
["900"] = { "900", 0.75 },
|
||||
["1000"] = { "1000", 0.87 },
|
||||
["2000"] = { "2000", 0.83 },
|
||||
["3000"] = { "3000", 0.84 },
|
||||
["4000"] = { "4000", 1.00 },
|
||||
["5000"] = { "5000", 0.77 },
|
||||
["6000"] = { "6000", 0.90 },
|
||||
["7000"] = { "7000", 0.77 },
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
|
||||
["градусы"] = { "degrees", 0.5 },
|
||||
["километры"] = { "kilometers", 0.65 },
|
||||
@@ -170,11 +170,10 @@ RADIOSPEECH.Vocabulary.RU = {
|
||||
["метров"] = { "meters", 0.41 },
|
||||
["m"] = { "meters", 0.41 },
|
||||
["ноги"] = { "feet", 0.37 },
|
||||
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
|
||||
["возвращение на базу"] = { "returning_to_base", 1.40 },
|
||||
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
|
||||
["перехват боги"] = { "intercepting_bogeys", 1.22 },
|
||||
@@ -200,11 +199,11 @@ function RADIOSPEECH:New(frequency, modulation)
|
||||
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH
|
||||
|
||||
|
||||
self.Language = "EN"
|
||||
|
||||
|
||||
self:BuildTree()
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -262,7 +261,7 @@ end
|
||||
function RADIOSPEECH:BuildTree()
|
||||
|
||||
self.Speech = {}
|
||||
|
||||
|
||||
for Language, Sentences in pairs( self.Vocabulary ) do
|
||||
self:I( { Language = Language, Sentences = Sentences })
|
||||
self.Speech[Language] = {}
|
||||
@@ -271,7 +270,7 @@ function RADIOSPEECH:BuildTree()
|
||||
self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
self:I( { Speech = self.Speech } )
|
||||
|
||||
return self
|
||||
@@ -290,7 +289,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" )
|
||||
|
||||
self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } )
|
||||
|
||||
|
||||
|
||||
if Word then
|
||||
if Word ~= "" and tonumber(Word) == nil then
|
||||
@@ -302,7 +301,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
if Speech[Word].Next == nil then
|
||||
self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } )
|
||||
self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" )
|
||||
else
|
||||
else
|
||||
if RemainderSentence and RemainderSentence ~= "" then
|
||||
return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language )
|
||||
end
|
||||
@@ -310,11 +309,11 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
end
|
||||
return RemainderSentence
|
||||
end
|
||||
return OriginalSentence
|
||||
return OriginalSentence
|
||||
else
|
||||
return ""
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Speak a sentence.
|
||||
@@ -333,7 +332,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
|
||||
|
||||
if Digits then
|
||||
if Digits ~= "" and tonumber( Digits ) ~= nil then
|
||||
|
||||
|
||||
-- Construct numbers
|
||||
local Number = tonumber( Digits )
|
||||
local Multiple = nil
|
||||
@@ -357,7 +356,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
|
||||
end
|
||||
return RemainderSentence
|
||||
end
|
||||
return OriginalSentence
|
||||
return OriginalSentence
|
||||
else
|
||||
return ""
|
||||
end
|
||||
@@ -374,26 +373,26 @@ function RADIOSPEECH:Speak( Sentence, Language )
|
||||
self:I( { Sentence, Language } )
|
||||
|
||||
local Language = Language or "EN"
|
||||
|
||||
|
||||
self:I( { Language = Language } )
|
||||
|
||||
|
||||
-- If there is no node for Speech, then we start at the first nodes of the language.
|
||||
local Speech = self.Speech[Language]
|
||||
|
||||
|
||||
self:I( { Speech = Speech, Language = Language } )
|
||||
|
||||
|
||||
self:NewTransmission( "_In.wav", 0.52, Language .. "/" )
|
||||
|
||||
|
||||
repeat
|
||||
|
||||
Sentence = self:SpeakWords( Sentence, Speech, Language )
|
||||
|
||||
|
||||
self:I( { Sentence = Sentence } )
|
||||
|
||||
Sentence = self:SpeakDigits( Sentence, Speech, Language )
|
||||
|
||||
self:I( { Sentence = Sentence } )
|
||||
|
||||
|
||||
-- Sentence = self:SpeakSymbols( Sentence, Speech )
|
||||
--
|
||||
-- self:I( { Sentence = Sentence } )
|
||||
|
||||
@@ -96,6 +96,14 @@
|
||||
-- For more information on setting up a cloud account, visit: https://cloud.google.com/text-to-speech
|
||||
-- Google's supported SSML reference: https://cloud.google.com/text-to-speech/docs/ssml
|
||||
--
|
||||
-- **NOTE on using GOOGLE TTS with SRS:** You need to have the C# library installed in your SRS folder for Google to work.
|
||||
-- You can obtain it e.g. here: [NuGet](https://www.nuget.org/packages/Grpc.Core)
|
||||
--
|
||||
-- **Pro-Tipp** - use the command line with power shell to call DCS-SR-ExternalAudio.exe - it will tell you what is missing.
|
||||
-- and also the Google Console error, in case you have missed a step in setting up your Google TTS.
|
||||
-- E.g. `.\DCS-SR-ExternalAudio.exe -t "Text Message" -f 255 -m AM -c 2 -s 2 -z -G "Path_To_You_Google.Json"`
|
||||
-- Plays a message on 255AM for the blue coalition in-game.
|
||||
--
|
||||
-- ## Set Voice
|
||||
--
|
||||
-- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`.
|
||||
@@ -135,7 +143,7 @@ MSRS = {
|
||||
|
||||
--- MSRS class version.
|
||||
-- @field #string version
|
||||
MSRS.version="0.0.5"
|
||||
MSRS.version="0.0.6"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -718,7 +726,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
--local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT")
|
||||
|
||||
-- Command.
|
||||
local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, label)
|
||||
local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume)
|
||||
|
||||
-- Set voice or gender/culture.
|
||||
if voice then
|
||||
|
||||
@@ -144,7 +144,7 @@ do -- UserSound
|
||||
-- @return #USERSOUND The usersound instance.
|
||||
-- @usage
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active unit named "PlayerUnit", a human player.
|
||||
-- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active group named "PlayerUnit", a human player.
|
||||
-- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit.
|
||||
--
|
||||
function USERSOUND:ToUnit( Unit, Delay )
|
||||
|
||||
@@ -216,7 +216,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
|
||||
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
|
||||
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup )
|
||||
local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup )
|
||||
--self:ReportSummary( EventGroup )
|
||||
self:ReportSummary( EventGroup )
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
for MissionID, Mission in pairs( self:GetMissions() ) do
|
||||
local Mission = Mission -- Tasking.Mission#MISSION
|
||||
@@ -560,11 +560,9 @@ function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
|
||||
self.AutoAssignTasks = AutoAssign or false
|
||||
|
||||
if self.AutoAssignTasks == true then
|
||||
self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
|
||||
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
|
||||
else
|
||||
self:ScheduleStop()
|
||||
-- FF this is not the schedule ID
|
||||
--self:ScheduleStop( self.AssignTasks )
|
||||
self:ScheduleStop( self.AssignTasks )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user