mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
3 Commits
Apple/Deve
...
kk/usergui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c66fd7cab | ||
|
|
80f76b26c2 | ||
|
|
4e956c3203 |
1
.github/workflows/build-docs.yml
vendored
1
.github/workflows/build-docs.yml
vendored
@@ -57,7 +57,6 @@ jobs:
|
||||
|
||||
- name: Update apt-get (needed for act docker image)
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get -qq update
|
||||
|
||||
- name: Install tree
|
||||
|
||||
5
.github/workflows/build-includes.yml
vendored
5
.github/workflows/build-includes.yml
vendored
@@ -5,8 +5,6 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- Apple/Develop
|
||||
|
||||
paths:
|
||||
- 'Moose Setup/**/*.lua'
|
||||
- 'Moose Development/**/*.lua'
|
||||
@@ -49,7 +47,6 @@ jobs:
|
||||
|
||||
- name: Update apt-get (needed for act docker image)
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get -qq update
|
||||
|
||||
- name: Install tree
|
||||
@@ -110,7 +107,7 @@ jobs:
|
||||
- name: Run LuaSrcDiet
|
||||
run: |
|
||||
luasrcdiet --basic --opt-emptylines ./build/result/Moose_Include_Static/Moose.lua -o ./build/result/Moose_Include_Static/Moose_.lua
|
||||
|
||||
|
||||
#########################################################################
|
||||
# Push to MOOSE_INCLUDE
|
||||
#########################################################################
|
||||
|
||||
10
.github/workflows/gh-pages.yml
vendored
10
.github/workflows/gh-pages.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
working-directory: docs/
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@v4
|
||||
uses: actions/configure-pages@v3
|
||||
- name: Build with Jekyll
|
||||
# Outputs to the './_site' directory by default
|
||||
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
working-directory: docs/
|
||||
- name: Upload artifact
|
||||
# Automatically uploads an artifact from the './_site' directory by default
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
path: docs/_site/
|
||||
|
||||
@@ -66,13 +66,13 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@v1
|
||||
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
steps:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
- run: npm install linkinator
|
||||
- run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --retry-errors --retry-errors-count 3 --retry-errors-jitter
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -28,13 +28,6 @@ local.properties
|
||||
.buildpath
|
||||
|
||||
|
||||
#####################
|
||||
## Visual Studio Code
|
||||
#####################
|
||||
*.code-workspace
|
||||
.vscode/
|
||||
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
@@ -1953,7 +1953,7 @@ local function refct_from_id(id) -- refct = refct_from_id(CTypeID)
|
||||
unsigned = refct.unsigned,
|
||||
size = bit.band(bit.rshift(ctype.info, 16), 127),
|
||||
}
|
||||
refct.bool, refct.const, refct.volatile, refct.unsigned = nil, nil, nil, nil
|
||||
refct.bool, refct.const, refct.volatile, refct.unsigned = nil
|
||||
end
|
||||
|
||||
if CT[4] then -- Merge sibling attributes onto this type.
|
||||
|
||||
14
Moose Development/Moose/.vscode/settings.json
vendored
14
Moose Development/Moose/.vscode/settings.json
vendored
@@ -1,17 +1,7 @@
|
||||
{
|
||||
"Lua.workspace.preloadFileSize": 10000,
|
||||
"Lua.workspace.preloadFileSize": 1000,
|
||||
"Lua.diagnostics.disable": [
|
||||
"undefined-doc-name",
|
||||
"duplicate-set-field",
|
||||
"trailing-space",
|
||||
"need-check-nil",
|
||||
"ambiguity-1",
|
||||
"undefined-doc-param",
|
||||
"redundant-parameter",
|
||||
"param-type-mismatch",
|
||||
"deprecated",
|
||||
"undefined-global",
|
||||
"lowercase-global"
|
||||
"undefined-doc-name"
|
||||
],
|
||||
"Lua.diagnostics.globals": [
|
||||
"BASE",
|
||||
|
||||
2
Moose Development/Moose/AI/.gitignore
vendored
2
Moose Development/Moose/AI/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/AI_Patrol.lua
|
||||
/AI_BAI.lua
|
||||
218
Moose Development/Moose/AI/AI_A2A_Cap.lua
Normal file
218
Moose Development/Moose/AI/AI_A2A_Cap.lua
Normal file
@@ -0,0 +1,218 @@
|
||||
--- **AI** - Models the process of Combat Air Patrol (CAP) for airplanes.
|
||||
--
|
||||
-- This is a class used in the @{AI.AI_A2A_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2A_Cap
|
||||
-- @image AI_Combat_Air_Patrol.JPG
|
||||
|
||||
--- @type AI_A2A_CAP
|
||||
-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE
|
||||
|
||||
--- The AI_A2A_CAP class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
|
||||
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_A2A_CAP constructor
|
||||
--
|
||||
-- * @{#AI_A2A_CAP.New}(): Creates a new AI_A2A_CAP object.
|
||||
--
|
||||
-- ## 2. AI_A2A_CAP is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1 AI_A2A_CAP States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_A2A_CAP Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_A2A_CAP.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_A2A_CAP.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{#AI_A2A_CAP.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Core.Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{#AI_A2A_CAP.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2A_CAP
|
||||
AI_A2A_CAP = {
|
||||
ClassName = "AI_A2A_CAP",
|
||||
}
|
||||
|
||||
--- Creates a new AI_A2A_CAP object
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New2( AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
-- Multiple inheritance ... :-)
|
||||
local AI_Air = AI_AIR:New( AICap )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage ) --#AI_A2A_CAP
|
||||
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new AI_A2A_CAP object
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Patrol.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_CAP:onafterStart( AICap, From, Event, To )
|
||||
|
||||
self:GetParent( self, AI_A2A_CAP ).onafterStart( self, AICap, From, Event, To )
|
||||
AICap:HandleEvent( EVENTS.Takeoff, nil, self )
|
||||
|
||||
end
|
||||
|
||||
--- Set the Engage Zone which defines where the AI will engage bogies.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param #number EngageRange The Engage Range.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:SetEngageRange( EngageRange )
|
||||
self:F2()
|
||||
|
||||
if EngageRange then
|
||||
self.EngageRange = EngageRange
|
||||
else
|
||||
self.EngageRange = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
|
||||
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
|
||||
if AttackUnit and AttackUnit:IsAlive() and AttackUnit:IsAir() then
|
||||
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
|
||||
-- Maybe the detected set also contains
|
||||
self:T( { "Attacking Task:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit )
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
4518
Moose Development/Moose/AI/AI_A2A_Dispatcher.lua
Normal file
4518
Moose Development/Moose/AI/AI_A2A_Dispatcher.lua
Normal file
File diff suppressed because it is too large
Load Diff
145
Moose Development/Moose/AI/AI_A2A_Gci.lua
Normal file
145
Moose Development/Moose/AI/AI_A2A_Gci.lua
Normal file
@@ -0,0 +1,145 @@
|
||||
--- **AI** - Models the process of Ground Controlled Interception (GCI) for airplanes.
|
||||
--
|
||||
-- This is a class used in the @{AI.AI_A2A_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2A_Gci
|
||||
-- @image AI_Ground_Control_Intercept.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2A_GCI
|
||||
-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- The AI_A2A_GCI is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event.
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- ## 1. AI_A2A_GCI constructor
|
||||
--
|
||||
-- * @{#AI_A2A_GCI.New}(): Creates a new AI_A2A_GCI object.
|
||||
--
|
||||
-- ## 2. AI_A2A_GCI is a FSM
|
||||
--
|
||||
-- ### 2.1 AI_A2A_GCI States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_A2A_GCI Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_A2A_GCI.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_A2A_GCI.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2A_GCI
|
||||
AI_A2A_GCI = {
|
||||
ClassName = "AI_A2A_GCI",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_A2A_GCI object
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_GCI
|
||||
function AI_A2A_GCI:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIIntercept )
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air, AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage ) -- #AI_A2A_GCI
|
||||
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new AI_A2A_GCI object
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_GCI
|
||||
function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
return self:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Patrol.
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To )
|
||||
|
||||
self:GetParent( self, AI_A2A_GCI ).onafterStart( self, AIIntercept, From, Event, To )
|
||||
end
|
||||
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2A_GCI self
|
||||
function AI_A2A_GCI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
|
||||
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
|
||||
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
|
||||
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
|
||||
-- Maybe the detected set also contains
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit )
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
409
Moose Development/Moose/AI/AI_A2A_Patrol.lua
Normal file
409
Moose Development/Moose/AI/AI_A2A_Patrol.lua
Normal file
@@ -0,0 +1,409 @@
|
||||
--- **AI** - Models the process of air patrol of airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2A_Patrol
|
||||
-- @image AI_Air_Patrolling.JPG
|
||||
|
||||
|
||||
--- @type AI_A2A_PATROL
|
||||
-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL
|
||||
|
||||
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_A2A_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
---- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or
|
||||
-- use derived AI_ classes to model AI offensive or defensive behaviour.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_A2A_PATROL constructor
|
||||
--
|
||||
-- * @{#AI_A2A_PATROL.New}(): Creates a new AI_A2A_PATROL object.
|
||||
--
|
||||
-- ## 2. AI_A2A_PATROL is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_A2A_PATROL States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base.
|
||||
-- * **Stopped** ( Group ): The process is stopped.
|
||||
-- * **Crashed** ( Group ): The AI has crashed or is dead.
|
||||
--
|
||||
-- ### 2.2. AI_A2A_PATROL Events
|
||||
--
|
||||
-- * **Start** ( Group ): Start the process.
|
||||
-- * **Stop** ( Group ): Stop the process.
|
||||
-- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **RTB** ( Group ): Route the AI to the home base.
|
||||
-- * **Detect** ( Group ): The AI is detecting targets.
|
||||
-- * **Detected** ( Group ): The AI has detected new targets.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set or Get the AI controllable
|
||||
--
|
||||
-- * @{#AI_A2A_PATROL.SetControllable}(): Set the AIControllable.
|
||||
-- * @{#AI_A2A_PATROL.GetControllable}(): Get the AIControllable.
|
||||
--
|
||||
-- ## 4. Set the Speed and Altitude boundaries of the AI controllable
|
||||
--
|
||||
-- * @{#AI_A2A_PATROL.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol.
|
||||
-- * @{#AI_A2A_PATROL.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol.
|
||||
--
|
||||
-- ## 5. Manage the detection process of the AI controllable
|
||||
--
|
||||
-- The detection process of the AI controllable can be manipulated.
|
||||
-- Detection requires an amount of CPU power, which has an impact on your mission performance.
|
||||
-- Only put detection on when absolutely necessary, and the frequency of the detection can also be set.
|
||||
--
|
||||
-- * @{#AI_A2A_PATROL.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
|
||||
-- * @{#AI_A2A_PATROL.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
|
||||
--
|
||||
-- The detection frequency can be set with @{#AI_A2A_PATROL.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
|
||||
-- Use the method @{#AI_A2A_PATROL.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI.
|
||||
--
|
||||
-- The detection can be filtered to potential targets in a specific zone.
|
||||
-- Use the method @{#AI_A2A_PATROL.SetDetectionZone}() to set the zone where targets need to be detected.
|
||||
-- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected
|
||||
-- according the weather conditions.
|
||||
--
|
||||
-- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL
|
||||
--
|
||||
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- while a new AI is targeted to the AI_A2A_PATROL.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place.
|
||||
--
|
||||
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
|
||||
--
|
||||
-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
|
||||
-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2A_PATROL
|
||||
AI_A2A_PATROL = {
|
||||
ClassName = "AI_A2A_PATROL",
|
||||
}
|
||||
|
||||
--- Creates a new AI_A2A_PATROL object
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The patrol group object.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to BARO
|
||||
-- @return #AI_A2A_PATROL self
|
||||
-- @usage
|
||||
-- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol a Group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
|
||||
-- PatrolZone = ZONE:New( 'PatrolZone' )
|
||||
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
|
||||
-- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 )
|
||||
function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIPatrol )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Patrol ) -- #AI_A2A_PATROL
|
||||
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
|
||||
-- defafult PatrolAltType to "BARO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "BARO"
|
||||
|
||||
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
|
||||
|
||||
--- OnBefore Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnBeforePatrol
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnAfterPatrol
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_A2A_PATROL] Patrol
|
||||
-- @param #AI_A2A_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_A2A_PATROL] __Patrol
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnLeavePatrolling
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnEnterPatrolling
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL.
|
||||
|
||||
--- OnBefore Transition Handler for Event Route.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnBeforeRoute
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Route.
|
||||
-- @function [parent=#AI_A2A_PATROL] OnAfterRoute
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Route.
|
||||
-- @function [parent=#AI_A2A_PATROL] Route
|
||||
-- @param #AI_A2A_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Route.
|
||||
-- @function [parent=#AI_A2A_PATROL] __Route
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
|
||||
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum speed of the patrol.
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @return #AI_A2A_PATROL self
|
||||
function AI_A2A_PATROL:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
|
||||
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
|
||||
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sets the floor and ceiling altitude of the patrol.
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @return #AI_A2A_PATROL self
|
||||
function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
|
||||
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
end
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @return #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
self:ClearTargetDistance()
|
||||
|
||||
self:__Route( 1 )
|
||||
|
||||
AIPatrol:OnReSpawn(
|
||||
function( PatrolGroup )
|
||||
self:__Reset( 1 )
|
||||
self:__Route( 5 )
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
--- This static method is called from the route path within the last task at the last waypoint of the AIPatrol.
|
||||
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The AI group.
|
||||
-- @param #AI_A2A_PATROL Fsm The FSM.
|
||||
function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } )
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:Route()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
-- When RTB, don't allow anymore the routing.
|
||||
if From == "RTB" then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local CurrentCoord = AIPatrol:GetCoordinate()
|
||||
|
||||
-- Random altitude.
|
||||
local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude)
|
||||
|
||||
-- Random speed in km/h.
|
||||
local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed)
|
||||
|
||||
-- First waypoint is current position.
|
||||
PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current")
|
||||
|
||||
if self.racetrack then
|
||||
|
||||
-- Random heading.
|
||||
local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
|
||||
|
||||
-- Random leg length.
|
||||
local leg=math.random(self.racetracklegmin, self.racetracklegmax)
|
||||
|
||||
-- Random duration if any.
|
||||
local duration = self.racetrackdurationmin
|
||||
if self.racetrackdurationmax then
|
||||
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
|
||||
end
|
||||
|
||||
-- CAP coordinate.
|
||||
local c0=self.PatrolZone:GetRandomCoordinate()
|
||||
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
|
||||
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
|
||||
end
|
||||
|
||||
-- Race track points.
|
||||
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
|
||||
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance(c0) -- For RTB status check
|
||||
|
||||
-- Debug:
|
||||
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
|
||||
--c1:MarkToAll("Race track c1")
|
||||
--c2:MarkToAll("Race track c2")
|
||||
|
||||
-- Task to orbit.
|
||||
local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
|
||||
|
||||
-- Task function to redo the patrol at other random position.
|
||||
local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self)
|
||||
|
||||
-- Controlled task with task condition.
|
||||
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
|
||||
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
|
||||
|
||||
-- Second waypoint
|
||||
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
|
||||
|
||||
else
|
||||
|
||||
-- Target coordinate.
|
||||
local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() --Core.Point#COORDINATE
|
||||
ToTargetCoord:SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
|
||||
|
||||
local taskReRoute=AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
|
||||
|
||||
PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskReRoute}, "Patrol Point")
|
||||
|
||||
end
|
||||
|
||||
-- ROE
|
||||
AIPatrol:OptionROEReturnFire()
|
||||
AIPatrol:OptionROTEvadeFire()
|
||||
|
||||
-- Patrol.
|
||||
AIPatrol:Route( PatrolRoute, 0.5)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
97
Moose Development/Moose/AI/AI_A2G_BAI.lua
Normal file
97
Moose Development/Moose/AI/AI_A2G_BAI.lua
Normal file
@@ -0,0 +1,97 @@
|
||||
--- **AI** - Models the process of air to ground BAI engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI.AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_BAI
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
--- @type AI_A2G_BAI
|
||||
-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_BAI
|
||||
AI_A2G_BAI = {
|
||||
ClassName = "AI_A2G_BAI",
|
||||
}
|
||||
|
||||
--- Creates a new AI_A2G_BAI object
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_BAI
|
||||
function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new AI_A2G_BAI object
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_BAI
|
||||
function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType)
|
||||
end
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_BAI self
|
||||
function AI_A2G_BAI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
self:T( { "BAI Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
97
Moose Development/Moose/AI/AI_A2G_CAS.lua
Normal file
97
Moose Development/Moose/AI/AI_A2G_CAS.lua
Normal file
@@ -0,0 +1,97 @@
|
||||
--- **AI** - Models the process of air to ground engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI.AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_CAS
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
--- @type AI_A2G_CAS
|
||||
-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_CAS
|
||||
AI_A2G_CAS = {
|
||||
ClassName = "AI_A2G_CAS",
|
||||
}
|
||||
|
||||
--- Creates a new AI_A2G_CAS object
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_CAS
|
||||
function AI_A2G_CAS:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new AI_A2G_CAS object
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_CAS
|
||||
function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType)
|
||||
end
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_CAS self
|
||||
function AI_A2G_CAS:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
self:T( { "CAS Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
4787
Moose Development/Moose/AI/AI_A2G_Dispatcher.lua
Normal file
4787
Moose Development/Moose/AI/AI_A2G_Dispatcher.lua
Normal file
File diff suppressed because it is too large
Load Diff
132
Moose Development/Moose/AI/AI_A2G_SEAD.lua
Normal file
132
Moose Development/Moose/AI/AI_A2G_SEAD.lua
Normal file
@@ -0,0 +1,132 @@
|
||||
--- **AI** - Models the process of air to ground SEAD engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI.AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_SEAD
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2G_SEAD
|
||||
-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE
|
||||
|
||||
|
||||
--- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event.
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- ## 1. AI_A2G_SEAD constructor
|
||||
--
|
||||
-- * @{#AI_A2G_SEAD.New}(): Creates a new AI_A2G_SEAD object.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{#AI_AIR_PATROL.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_SEAD
|
||||
AI_A2G_SEAD = {
|
||||
ClassName = "AI_A2G_SEAD",
|
||||
}
|
||||
|
||||
--- Creates a new AI_A2G_SEAD object
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_SEAD
|
||||
function AI_A2G_SEAD:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_SEAD object
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_SEAD
|
||||
function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
end
|
||||
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_SEAD self
|
||||
function AI_A2G_SEAD:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
local HasRadar = AttackUnit:HasSEAD()
|
||||
if HasRadar then
|
||||
self:F( { "SEAD Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
834
Moose Development/Moose/AI/AI_Air.lua
Normal file
834
Moose Development/Moose/AI/AI_Air.lua
Normal file
@@ -0,0 +1,834 @@
|
||||
--- **AI** - Models the process of AI air operations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- @type AI_AIR
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
--- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_AIR constructor
|
||||
--
|
||||
-- * @{#AI_AIR.New}(): Creates a new AI_AIR object.
|
||||
--
|
||||
-- # 2) AI_AIR is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
--
|
||||
-- ## 2.1) AI_AIR States.
|
||||
--
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_AIR Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- @field #AI_AIR
|
||||
AI_AIR = {
|
||||
ClassName = "AI_AIR",
|
||||
}
|
||||
|
||||
AI_AIR.TaskDelay = 0.5 -- The delay of each task given to the AI.
|
||||
|
||||
--- Creates a new AI_AIR process.
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process.
|
||||
-- @return #AI_AIR
|
||||
function AI_AIR:New( AIGroup )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_AIR
|
||||
|
||||
self:SetControllable( AIGroup )
|
||||
|
||||
self:SetStartState( "Stopped" )
|
||||
|
||||
self:AddTransition( "*", "Queue", "Queued" )
|
||||
|
||||
self:AddTransition( "*", "Start", "Started" )
|
||||
|
||||
--- Start Handler OnBefore for AI_AIR
|
||||
-- @function [parent=#AI_AIR] OnBeforeStart
|
||||
-- @param #AI_AIR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #boolean
|
||||
|
||||
--- Start Handler OnAfter for AI_AIR
|
||||
-- @function [parent=#AI_AIR] OnAfterStart
|
||||
-- @param #AI_AIR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Start Trigger for AI_AIR
|
||||
-- @function [parent=#AI_AIR] Start
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Start Asynchronous Trigger for AI_AIR
|
||||
-- @function [parent=#AI_AIR] __Start
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay
|
||||
|
||||
self:AddTransition( "*", "Stop", "Stopped" )
|
||||
|
||||
--- OnLeave Transition Handler for State Stopped.
|
||||
-- @function [parent=#AI_AIR] OnLeaveStopped
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Stopped.
|
||||
-- @function [parent=#AI_AIR] OnEnterStopped
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- OnBefore Transition Handler for Event Stop.
|
||||
-- @function [parent=#AI_AIR] OnBeforeStop
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Stop.
|
||||
-- @function [parent=#AI_AIR] OnAfterStop
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Stop.
|
||||
-- @function [parent=#AI_AIR] Stop
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Stop.
|
||||
-- @function [parent=#AI_AIR] __Stop
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
|
||||
|
||||
--- OnBefore Transition Handler for Event Status.
|
||||
-- @function [parent=#AI_AIR] OnBeforeStatus
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Status.
|
||||
-- @function [parent=#AI_AIR] OnAfterStatus
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Status.
|
||||
-- @function [parent=#AI_AIR] Status
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Status.
|
||||
-- @function [parent=#AI_AIR] __Status
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
|
||||
|
||||
--- OnBefore Transition Handler for Event RTB.
|
||||
-- @function [parent=#AI_AIR] OnBeforeRTB
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event RTB.
|
||||
-- @function [parent=#AI_AIR] OnAfterRTB
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event RTB.
|
||||
-- @function [parent=#AI_AIR] RTB
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Asynchronous Event Trigger for Event RTB.
|
||||
-- @function [parent=#AI_AIR] __RTB
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Returning.
|
||||
-- @function [parent=#AI_AIR] OnLeaveReturning
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Returning.
|
||||
-- @function [parent=#AI_AIR] OnEnterReturning
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "Refuel", "Refuelling" )
|
||||
|
||||
--- Refuel Handler OnBefore for AI_AIR
|
||||
-- @function [parent=#AI_AIR] OnBeforeRefuel
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #boolean
|
||||
|
||||
--- Refuel Handler OnAfter for AI_AIR
|
||||
-- @function [parent=#AI_AIR] OnAfterRefuel
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Refuel Trigger for AI_AIR
|
||||
-- @function [parent=#AI_AIR] Refuel
|
||||
-- @param #AI_AIR self
|
||||
|
||||
--- Refuel Asynchronous Trigger for AI_AIR
|
||||
-- @function [parent=#AI_AIR] __Refuel
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number Delay
|
||||
|
||||
self:AddTransition( "*", "Takeoff", "Airborne" )
|
||||
self:AddTransition( "*", "Return", "Returning" )
|
||||
self:AddTransition( "*", "Hold", "Holding" )
|
||||
self:AddTransition( "*", "Home", "Home" )
|
||||
self:AddTransition( "*", "LostControl", "LostControl" )
|
||||
self:AddTransition( "*", "Fuel", "Fuel" )
|
||||
self:AddTransition( "*", "Damaged", "Damaged" )
|
||||
self:AddTransition( "*", "Eject", "*" )
|
||||
self:AddTransition( "*", "Crash", "Crashed" )
|
||||
self:AddTransition( "*", "PilotDead", "*" )
|
||||
|
||||
self.IdleCount = 0
|
||||
|
||||
self.RTBSpeedMaxFactor = 0.6
|
||||
self.RTBSpeedMinFactor = 0.5
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function GROUP:OnEventTakeoff( EventData, Fsm )
|
||||
Fsm:Takeoff()
|
||||
self:UnHandleEvent( EVENTS.Takeoff )
|
||||
end
|
||||
|
||||
|
||||
|
||||
function AI_AIR:SetDispatcher( Dispatcher )
|
||||
self.Dispatcher = Dispatcher
|
||||
end
|
||||
|
||||
function AI_AIR:GetDispatcher()
|
||||
return self.Dispatcher
|
||||
end
|
||||
|
||||
function AI_AIR:SetTargetDistance( Coordinate )
|
||||
|
||||
local CurrentCoord = self.Controllable:GetCoordinate()
|
||||
self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate )
|
||||
|
||||
self.ClosestTargetDistance = ( not self.ClosestTargetDistance or self.ClosestTargetDistance > self.TargetDistance ) and self.TargetDistance or self.ClosestTargetDistance
|
||||
end
|
||||
|
||||
|
||||
function AI_AIR:ClearTargetDistance()
|
||||
|
||||
self.TargetDistance = nil
|
||||
self.ClosestTargetDistance = nil
|
||||
end
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum speed of the patrol.
|
||||
-- @param #AI_AIR self
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
|
||||
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
|
||||
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum RTB speed of the patrol.
|
||||
-- @param #AI_AIR self
|
||||
-- @param DCS#Speed RTBMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed RTBMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetRTBSpeed( RTBMinSpeed, RTBMaxSpeed )
|
||||
self:F( { RTBMinSpeed, RTBMaxSpeed } )
|
||||
|
||||
self.RTBMinSpeed = RTBMinSpeed
|
||||
self.RTBMaxSpeed = RTBMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
--- Sets the floor and ceiling altitude of the patrol.
|
||||
-- @param #AI_AIR self
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
|
||||
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
end
|
||||
|
||||
|
||||
--- Sets the home airbase.
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Airbase#AIRBASE HomeAirbase
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetHomeAirbase( HomeAirbase )
|
||||
self:F2( { HomeAirbase } )
|
||||
|
||||
self.HomeAirbase = HomeAirbase
|
||||
end
|
||||
|
||||
--- Sets to refuel at the given tanker.
|
||||
-- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetTanker( TankerName )
|
||||
self:F2( { TankerName } )
|
||||
|
||||
self.TankerName = TankerName
|
||||
end
|
||||
|
||||
|
||||
--- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number DisengageRadius The disengage range.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetDisengageRadius( DisengageRadius )
|
||||
self:F2( { DisengageRadius } )
|
||||
|
||||
self.DisengageRadius = DisengageRadius
|
||||
end
|
||||
|
||||
--- Set the status checking off.
|
||||
-- @param #AI_AIR self
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetStatusOff()
|
||||
self:F2()
|
||||
|
||||
self.CheckStatus = false
|
||||
end
|
||||
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targeted to the AI_AIR.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
|
||||
|
||||
self.FuelThresholdPercentage = FuelThresholdPercentage
|
||||
self.OutOfFuelOrbitTime = OutOfFuelOrbitTime
|
||||
|
||||
self.Controllable:OptionRTBBingoFuel( false )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage threshold is reached,
|
||||
-- the AI will return immediately to the home base (RTB).
|
||||
-- Note that for groups, the average damage of the complete group will be calculated.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
|
||||
|
||||
self.PatrolManageDamage = true
|
||||
self.PatrolDamageThreshold = PatrolDamageThreshold
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
|
||||
-- @param #AI_AIR self
|
||||
-- @return #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
self:__Status( 10 ) -- Check status status every 30 seconds.
|
||||
|
||||
self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnCrash )
|
||||
self:HandleEvent( EVENTS.Ejection, self.OnEjection )
|
||||
|
||||
Controllable:OptionROEHoldFire()
|
||||
Controllable:OptionROTVertical()
|
||||
end
|
||||
|
||||
--- Coordinates the approriate returning action.
|
||||
-- @param #AI_AIR self
|
||||
-- @return #AI_AIR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR:onafterReturn( Controllable, From, Event, To )
|
||||
|
||||
self:__RTB( self.TaskDelay )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
function AI_AIR:onbeforeStatus()
|
||||
|
||||
return self.CheckStatus
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
function AI_AIR:onafterStatus()
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
local RTB = false
|
||||
|
||||
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
|
||||
|
||||
if not self:Is( "Holding" ) and not self:Is( "Returning" ) then
|
||||
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
|
||||
|
||||
if DistanceFromHomeBase > self.DisengageRadius then
|
||||
self:I( self.Controllable:GetName() .. " is too far from home base, RTB!" )
|
||||
self:Hold( 300 )
|
||||
RTB = false
|
||||
end
|
||||
end
|
||||
|
||||
-- I think this code is not requirement anymore after release 2.5.
|
||||
-- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then
|
||||
-- if DistanceFromHomeBase < 5000 then
|
||||
-- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" )
|
||||
-- self:Home( "Destroy" )
|
||||
-- end
|
||||
-- end
|
||||
|
||||
|
||||
if not self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" )then
|
||||
|
||||
local Fuel = self.Controllable:GetFuelMin()
|
||||
|
||||
-- If the fuel in the controllable is below the threshold percentage,
|
||||
-- then send for refuel in case of a tanker, otherwise RTB.
|
||||
if Fuel < self.FuelThresholdPercentage then
|
||||
|
||||
if self.TankerName then
|
||||
self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
|
||||
self:Refuel()
|
||||
else
|
||||
self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
|
||||
local OldAIControllable = self.Controllable
|
||||
|
||||
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
|
||||
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil ) )
|
||||
OldAIControllable:SetTask( TimedOrbitTask, 10 )
|
||||
|
||||
self:Fuel()
|
||||
RTB = true
|
||||
end
|
||||
else
|
||||
end
|
||||
end
|
||||
|
||||
if self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" ) then
|
||||
RTB = true
|
||||
end
|
||||
|
||||
-- TODO: Check GROUP damage function.
|
||||
local Damage = self.Controllable:GetLife()
|
||||
local InitialLife = self.Controllable:GetLife0()
|
||||
|
||||
-- If the group is damaged, then RTB.
|
||||
-- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue.
|
||||
-- The damaged unit will RTB due to DCS logic, and the others will continue to engage.
|
||||
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
|
||||
self:I( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
|
||||
self:Damaged()
|
||||
RTB = true
|
||||
self:SetStatusOff()
|
||||
end
|
||||
|
||||
-- Check if planes went RTB and are out of control.
|
||||
-- We only check if planes are out of control, when they are in duty.
|
||||
if self.Controllable:HasTask() == false then
|
||||
if not self:Is( "Started" ) and
|
||||
not self:Is( "Stopped" ) and
|
||||
not self:Is( "Fuel" ) and
|
||||
not self:Is( "Damaged" ) and
|
||||
not self:Is( "Home" ) then
|
||||
if self.IdleCount >= 10 then
|
||||
if Damage ~= InitialLife then
|
||||
self:Damaged()
|
||||
else
|
||||
self:I( self.Controllable:GetName() .. " control lost! " )
|
||||
|
||||
self:LostControl()
|
||||
end
|
||||
else
|
||||
self.IdleCount = self.IdleCount + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
self.IdleCount = 0
|
||||
end
|
||||
|
||||
if RTB == true then
|
||||
self:__RTB( self.TaskDelay )
|
||||
end
|
||||
|
||||
if not self:Is("Home") then
|
||||
self:__Status( 10 )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR.RTBRoute( AIGroup, Fsm )
|
||||
|
||||
AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } )
|
||||
|
||||
if AIGroup:IsAlive() then
|
||||
Fsm:RTB()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR.RTBHold( AIGroup, Fsm )
|
||||
|
||||
AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } )
|
||||
if AIGroup:IsAlive() then
|
||||
Fsm:__RTB( Fsm.TaskDelay )
|
||||
Fsm:Return()
|
||||
local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
|
||||
AIGroup:SetTask( Task )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Set the min and max factors on RTB speed. Use this, if your planes are heading back to base too fast. Default values are 0.5 and 0.6.
|
||||
-- The RTB speed is calculated as the max speed of the unit multiplied by MinFactor (lower bracket) and multiplied by MaxFactor (upper bracket).
|
||||
-- A random value in this bracket is then applied in the waypoint routing generation.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number MinFactor Lower bracket factor. Defaults to 0.5.
|
||||
-- @param #number MaxFactor Upper bracket factor. Defaults to 0.6.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor)
|
||||
self.RTBSpeedMaxFactor = MaxFactor or 0.6
|
||||
self.RTBSpeedMinFactor = MinFactor or 0.5
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
self:F( { AIGroup, From, Event, To } )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
|
||||
self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
|
||||
|
||||
self:ClearTargetDistance()
|
||||
--AIGroup:ClearTasks()
|
||||
|
||||
AIGroup:OptionProhibitAfterburner(true)
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromCoord = AIGroup:GetCoordinate()
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
|
||||
local ToTargetVec3 = ToTargetCoord:GetVec3()
|
||||
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground
|
||||
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
|
||||
|
||||
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
|
||||
local RTBSpeedMax = AIGroup:GetSpeedMax()
|
||||
local RTBSpeedMaxFactor = self.RTBSpeedMaxFactor or 0.6
|
||||
local RTBSpeedMinFactor = self.RTBSpeedMinFactor or 0.5
|
||||
self:SetRTBSpeed( RTBSpeedMax * RTBSpeedMinFactor, RTBSpeedMax * RTBSpeedMaxFactor)
|
||||
end
|
||||
|
||||
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
|
||||
--local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) )
|
||||
|
||||
local Distance = FromCoord:Get2DDistance( ToTargetCoord2 )
|
||||
|
||||
--local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
|
||||
local ToAirbaseCoord = ToTargetCoord2
|
||||
|
||||
if Distance < 5000 then
|
||||
self:I( "RTB and near the airbase!" )
|
||||
self:Home()
|
||||
return
|
||||
end
|
||||
|
||||
if not AIGroup:InAir() == true then
|
||||
self:I( "Not anymore in the air, considered Home." )
|
||||
self:Home()
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
--- Create a route point of type air.
|
||||
local FromRTBRoutePoint = FromCoord:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
RTBSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
RTBSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = FromRTBRoutePoint
|
||||
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self )
|
||||
|
||||
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks )
|
||||
|
||||
AIGroup:OptionROEHoldFire()
|
||||
AIGroup:OptionROTEvadeFire()
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
AIGroup:Route( EngageRoute, self.TaskDelay )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR:onafterHome( AIGroup, From, Event, To )
|
||||
self:F( { AIGroup, From, Event, To } )
|
||||
|
||||
self:I( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
|
||||
self:F( { AIGroup, From, Event, To } )
|
||||
|
||||
self:I( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
|
||||
local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) )
|
||||
|
||||
local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self )
|
||||
|
||||
local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
|
||||
|
||||
--AIGroup:SetState( AIGroup, "AI_AIR", self )
|
||||
|
||||
AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR.Resume( AIGroup, Fsm )
|
||||
|
||||
AIGroup:I( { "AI_AIR.Resume:", AIGroup:GetName() } )
|
||||
if AIGroup:IsAlive() then
|
||||
Fsm:__RTB( Fsm.TaskDelay )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
|
||||
self:F( { AIGroup, From, Event, To } )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
|
||||
-- Get tanker group.
|
||||
local Tanker = GROUP:FindByName( self.TankerName )
|
||||
|
||||
if Tanker and Tanker:IsAlive() and Tanker:IsAirPlane() then
|
||||
|
||||
self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName )
|
||||
|
||||
local RefuelRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromRefuelCoord = AIGroup:GetCoordinate()
|
||||
local ToRefuelCoord = Tanker:GetCoordinate()
|
||||
local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true)
|
||||
|
||||
--- Create a route point of type air. NOT used!
|
||||
local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true)
|
||||
|
||||
self:F( { ToRefuelSpeed = ToRefuelSpeed } )
|
||||
|
||||
RefuelRoute[#RefuelRoute+1] = FromRefuelRoutePoint
|
||||
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
|
||||
|
||||
AIGroup:OptionROEHoldFire()
|
||||
AIGroup:OptionROTEvadeFire()
|
||||
|
||||
-- Get Class name for .Resume function
|
||||
local classname=self:GetClassName()
|
||||
|
||||
-- AI_A2A_CAP can call this function but does not have a .Resume function. Try to fix.
|
||||
if classname=="AI_A2A_CAP" then
|
||||
classname="AI_AIR_PATROL"
|
||||
end
|
||||
|
||||
env.info("FF refueling classname="..classname)
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = AIGroup:TaskRefueling()
|
||||
Tasks[#Tasks+1] = AIGroup:TaskFunction( classname .. ".Resume", self )
|
||||
RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks )
|
||||
|
||||
AIGroup:Route( RefuelRoute, self.TaskDelay )
|
||||
|
||||
else
|
||||
|
||||
-- No tanker defined ==> RTB!
|
||||
self:RTB()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
function AI_AIR:onafterDead()
|
||||
self:SetStatusOff()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR:OnCrash( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
if #self.Controllable:GetUnits() == 1 then
|
||||
self:__Crash( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR:OnEjection( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:__Eject( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR:OnPilotDead( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:__PilotDead( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
3239
Moose Development/Moose/AI/AI_Air_Dispatcher.lua
Normal file
3239
Moose Development/Moose/AI/AI_Air_Dispatcher.lua
Normal file
File diff suppressed because it is too large
Load Diff
587
Moose Development/Moose/AI/AI_Air_Engage.lua
Normal file
587
Moose Development/Moose/AI/AI_Air_Engage.lua
Normal file
@@ -0,0 +1,587 @@
|
||||
--- **AI** - Models the process of air to ground engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI.AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Engage
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_AIR_ENGAGE
|
||||
-- @extends AI.AI_Air#AI_AIR
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event.
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- ## 1. AI_AIR_ENGAGE constructor
|
||||
--
|
||||
-- * @{#AI_AIR_ENGAGE.New}(): Creates a new AI_AIR_ENGAGE object.
|
||||
--
|
||||
-- ## 2. Set the Zone of Engagement
|
||||
--
|
||||
-- An optional @{Core.Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_CAP#AI_AIR_ENGAGE.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_ENGAGE
|
||||
AI_AIR_ENGAGE = {
|
||||
ClassName = "AI_AIR_ENGAGE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_AIR_ENGAGE object
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM.
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group.
|
||||
-- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_AIR_ENGAGE
|
||||
function AI_AIR_ENGAGE:New( AI_Air, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_ENGAGE
|
||||
|
||||
self.Accomplished = false
|
||||
self.Engaging = false
|
||||
|
||||
local SpeedMax = AIGroup:GetSpeedMax()
|
||||
|
||||
self.EngageMinSpeed = EngageMinSpeed or SpeedMax * 0.5
|
||||
self.EngageMaxSpeed = EngageMaxSpeed or SpeedMax * 0.75
|
||||
self.EngageFloorAltitude = EngageFloorAltitude or 1000
|
||||
self.EngageCeilingAltitude = EngageCeilingAltitude or 1500
|
||||
self.EngageAltType = EngageAltType or "RADIO"
|
||||
|
||||
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "EngageRoute", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterEngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] EngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __EngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterEngage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Engage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Engage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeFired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterFired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Fired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Fired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeDestroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterDestroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Destroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Destroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeAbort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterAbort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Abort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Abort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeAccomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterAccomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Accomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Accomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- onafter event handler for Start event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterStart( AIGroup, From, Event, To )
|
||||
|
||||
self:GetParent( self, AI_AIR_ENGAGE ).onafterStart( self, AIGroup, From, Event, To )
|
||||
|
||||
AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- onafter event handler for Engage event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterEngage( AIGroup, From, Event, To )
|
||||
-- TODO: This function is overwritten below!
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
end
|
||||
|
||||
-- todo: need to fix this global function
|
||||
|
||||
|
||||
--- onbefore event handler for Engage event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onbeforeEngage( AIGroup, From, Event, To )
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- onafter event handler for Abort event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To )
|
||||
AIGroup:ClearTasks()
|
||||
self:Return()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterAccomplish( AIGroup, From, Event, To )
|
||||
self.Accomplished = true
|
||||
--self:SetDetectionOff()
|
||||
end
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.AttackUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR_ENGAGE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
|
||||
self:__Destroy( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIControllable
|
||||
function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit )
|
||||
Fsm:I(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName())))
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
|
||||
|
||||
local DefenderGroupName = DefenderGroup:GetName()
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
if DefenderGroup:IsAlive() then
|
||||
|
||||
local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
|
||||
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
|
||||
|
||||
-- Determine the distance to the target.
|
||||
-- If it is less than 10km, then attack without a route.
|
||||
-- Otherwise perform a route attack.
|
||||
|
||||
local DefenderCoord = DefenderGroup:GetPointVec3()
|
||||
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
|
||||
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
|
||||
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
|
||||
|
||||
-- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes!
|
||||
if TargetDistance <= EngageDistance * 9 then
|
||||
|
||||
--self:I(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000))
|
||||
self:__Engage( 0.1, AttackSetUnit )
|
||||
|
||||
else
|
||||
|
||||
--self:I(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000))
|
||||
|
||||
local EngageRoute = {}
|
||||
local AttackTasks = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = FromWP
|
||||
|
||||
self:SetTargetDistance( TargetCoord ) -- For RTB status check
|
||||
|
||||
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
|
||||
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
|
||||
|
||||
local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToWP
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___EngageRoute", self, AttackSetUnit )
|
||||
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
|
||||
|
||||
DefenderGroup:OptionROEReturnFire()
|
||||
DefenderGroup:OptionROTEvadeFire()
|
||||
|
||||
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
|
||||
self:I( DefenderGroupName .. ": No targets found -> Going RTB")
|
||||
self:Return()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIControllable
|
||||
function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit )
|
||||
|
||||
Fsm:I(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName())))
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
local delay=Fsm.TaskDelay or 0.1
|
||||
Fsm:__Engage(delay, AttackSetUnit)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
|
||||
|
||||
local DefenderGroupName = DefenderGroup:GetName()
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
self:T({AttackCount = AttackCount})
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
if DefenderGroup and DefenderGroup:IsAlive() then
|
||||
|
||||
local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 )
|
||||
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
|
||||
|
||||
local DefenderCoord = DefenderGroup:GetPointVec3()
|
||||
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
|
||||
if not TargetCoord then
|
||||
self:Return()
|
||||
return
|
||||
end
|
||||
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
|
||||
|
||||
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
|
||||
|
||||
local EngageRoute = {}
|
||||
local AttackTasks = {}
|
||||
|
||||
local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
EngageRoute[#EngageRoute+1] = FromWP
|
||||
|
||||
self:SetTargetDistance( TargetCoord ) -- For RTB status check
|
||||
|
||||
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
|
||||
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
|
||||
|
||||
local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
EngageRoute[#EngageRoute+1] = ToWP
|
||||
|
||||
-- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task!
|
||||
if TargetDistance <= EngageDistance * 9 then
|
||||
|
||||
local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic
|
||||
|
||||
if #AttackUnitTasks == 0 then
|
||||
self:I( DefenderGroupName .. ": No valid targets found -> Going RTB")
|
||||
self:Return()
|
||||
return
|
||||
else
|
||||
local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance))
|
||||
self:I(text)
|
||||
DefenderGroup:OptionROEOpenFire()
|
||||
DefenderGroup:OptionROTEvadeFire()
|
||||
DefenderGroup:OptionKeepWeaponsOnThreat()
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks )
|
||||
end
|
||||
end
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___Engage", self, AttackSetUnit )
|
||||
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
|
||||
|
||||
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
|
||||
|
||||
end
|
||||
else
|
||||
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
|
||||
self:I( DefenderGroupName .. ": No targets found -> returning.")
|
||||
self:Return()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIEngage
|
||||
function AI_AIR_ENGAGE.Resume( AIEngage, Fsm )
|
||||
|
||||
AIEngage:F( { "Resume:", AIEngage:GetName() } )
|
||||
if AIEngage and AIEngage:IsAlive() then
|
||||
Fsm:__Reset( Fsm.TaskDelay or 0.1 )
|
||||
Fsm:__EngageRoute( Fsm.TaskDelay or 0.2, Fsm.AttackSetUnit )
|
||||
end
|
||||
|
||||
end
|
||||
391
Moose Development/Moose/AI/AI_Air_Patrol.lua
Normal file
391
Moose Development/Moose/AI/AI_Air_Patrol.lua
Normal file
@@ -0,0 +1,391 @@
|
||||
--- **AI** - Models the process of A2G patrolling and engaging ground targets for airplanes and helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Patrol
|
||||
-- @image AI_Air_To_Ground_Patrol.JPG
|
||||
|
||||
--- @type AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air#AI_AIR
|
||||
|
||||
--- The AI_AIR_PATROL class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group}
|
||||
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_AIR_PATROL constructor
|
||||
--
|
||||
-- * @{#AI_AIR_PATROL.New}(): Creates a new AI_AIR_PATROL object.
|
||||
--
|
||||
-- ## 2. AI_AIR_PATROL is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1 AI_AIR_PATROL States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_AIR_PATROL Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.PatrolRoute}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_AIR_PATROL.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_AIR_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{#AI_AIR_PATROL.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_PATROL
|
||||
AI_AIR_PATROL = {
|
||||
ClassName = "AI_AIR_PATROL",
|
||||
}
|
||||
|
||||
--- Creates a new AI_AIR_PATROL object
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM.
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO.
|
||||
-- @return #AI_AIR_PATROL
|
||||
function AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_PATROL
|
||||
|
||||
local SpeedMax = AIGroup:GetSpeedMax()
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude or 1000
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude or 1500
|
||||
self.PatrolMinSpeed = PatrolMinSpeed or SpeedMax * 0.5
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed or SpeedMax * 0.75
|
||||
|
||||
-- defafult PatrolAltType to "RADIO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "RADIO"
|
||||
|
||||
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
|
||||
|
||||
--- OnBefore Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnBeforePatrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnAfterPatrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] Patrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] __Patrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnLeavePatrolling
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnEnterPatrolling
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "PatrolRoute", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL.
|
||||
|
||||
--- OnBefore Transition Handler for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnBeforePatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnAfterPatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] PatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] __PatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number EngageRange The Engage Range.
|
||||
-- @return #AI_AIR_PATROL self
|
||||
function AI_AIR_PATROL:SetEngageRange( EngageRange )
|
||||
self:F2()
|
||||
|
||||
if EngageRange then
|
||||
self.EngageRange = EngageRange
|
||||
else
|
||||
self.EngageRange = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m.
|
||||
-- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m.
|
||||
-- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North.
|
||||
-- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North.
|
||||
-- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage.
|
||||
-- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage.
|
||||
-- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading.
|
||||
-- @return #AI_AIR_PATROL self
|
||||
function AI_AIR_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates)
|
||||
|
||||
self.racetrack=true
|
||||
self.racetracklegmin=LegMin or 10000
|
||||
self.racetracklegmax=LegMax or 15000
|
||||
self.racetrackheadingmin=HeadingMin or 0
|
||||
self.racetrackheadingmax=HeadingMax or 180
|
||||
self.racetrackdurationmin=DurationMin
|
||||
self.racetrackdurationmax=DurationMax
|
||||
|
||||
if self.racetrackdurationmax and not self.racetrackdurationmin then
|
||||
self.racetrackdurationmin=self.racetrackdurationmax
|
||||
end
|
||||
|
||||
self.racetrackcapcoordinates=CapCoordinates
|
||||
|
||||
end
|
||||
|
||||
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @return #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_PATROL:onafterPatrol( AIPatrol, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
self:ClearTargetDistance()
|
||||
|
||||
self:__PatrolRoute( self.TaskDelay )
|
||||
|
||||
AIPatrol:OnReSpawn(
|
||||
function( PatrolGroup )
|
||||
self:__Reset( self.TaskDelay )
|
||||
self:__PatrolRoute( self.TaskDelay )
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
--- This static method is called from the route path within the last task at the last waypoint of the AIPatrol.
|
||||
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The AI group.
|
||||
-- @param #AI_AIR_PATROL Fsm The FSM.
|
||||
function AI_AIR_PATROL.___PatrolRoute( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_AIR_PATROL.___PatrolRoute:", AIPatrol:GetName() } )
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:PatrolRoute()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To )
|
||||
|
||||
self:F2()
|
||||
|
||||
-- When RTB, don't allow anymore the routing.
|
||||
if From == "RTB" then
|
||||
return
|
||||
end
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local CurrentCoord = AIPatrol:GetCoordinate()
|
||||
|
||||
local altitude= math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
|
||||
|
||||
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
|
||||
ToTargetCoord:SetAlt( altitude )
|
||||
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
|
||||
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
local speedkmh=ToTargetSpeed
|
||||
|
||||
local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true)
|
||||
PatrolRoute[#PatrolRoute+1] = FromWP
|
||||
|
||||
if self.racetrack then
|
||||
|
||||
-- Random heading.
|
||||
local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
|
||||
|
||||
-- Random leg length.
|
||||
local leg=math.random(self.racetracklegmin, self.racetracklegmax)
|
||||
|
||||
-- Random duration if any.
|
||||
local duration = self.racetrackdurationmin
|
||||
if self.racetrackdurationmax then
|
||||
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
|
||||
end
|
||||
|
||||
-- CAP coordinate.
|
||||
local c0=self.PatrolZone:GetRandomCoordinate()
|
||||
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
|
||||
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
|
||||
end
|
||||
|
||||
-- Race track points.
|
||||
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
|
||||
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance(c0) -- For RTB status check
|
||||
|
||||
-- Debug:
|
||||
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
|
||||
--c1:MarkToAll("Race track c1")
|
||||
--c2:MarkToAll("Race track c2")
|
||||
|
||||
-- Task to orbit.
|
||||
local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
|
||||
|
||||
-- Task function to redo the patrol at other random position.
|
||||
local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self)
|
||||
|
||||
-- Controlled task with task condition.
|
||||
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
|
||||
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
|
||||
|
||||
-- Second waypoint
|
||||
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
|
||||
|
||||
else
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true)
|
||||
PatrolRoute[#PatrolRoute+1] = ToWP
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self)
|
||||
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
|
||||
|
||||
end
|
||||
|
||||
AIPatrol:OptionROEReturnFire()
|
||||
AIPatrol:OptionROTEvadeFire()
|
||||
|
||||
AIPatrol:Route( PatrolRoute, self.TaskDelay )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Resumes the AIPatrol
|
||||
-- @param Wrapper.Group#GROUP AIPatrol
|
||||
-- @param Core.Fsm#FSM Fsm
|
||||
function AI_AIR_PATROL.Resume( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_AIR_PATROL.Resume:", AIPatrol:GetName() } )
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:__Reset( Fsm.TaskDelay )
|
||||
Fsm:__PatrolRoute( Fsm.TaskDelay )
|
||||
end
|
||||
|
||||
end
|
||||
294
Moose Development/Moose/AI/AI_Air_Squadron.lua
Normal file
294
Moose Development/Moose/AI/AI_Air_Squadron.lua
Normal file
@@ -0,0 +1,294 @@
|
||||
--- **AI** - Models squadrons for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI.AI_Air_Dispatcher} and derived dispatcher classes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Squadron
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_AIR_SQUADRON
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Implements the core functions modeling squadrons for airplanes and helicopters.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_SQUADRON
|
||||
AI_AIR_SQUADRON = {
|
||||
ClassName = "AI_AIR_SQUADRON",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_AIR_SQUADRON object
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #AI_AIR_SQUADRON
|
||||
function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount )
|
||||
|
||||
self:I( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } )
|
||||
|
||||
local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON
|
||||
|
||||
AI_Air_Squadron.Name = SquadronName
|
||||
AI_Air_Squadron.Airbase = AIRBASE:FindByName( AirbaseName )
|
||||
AI_Air_Squadron.AirbaseName = AI_Air_Squadron.Airbase:GetName()
|
||||
if not AI_Air_Squadron.Airbase then
|
||||
error( "Cannot find airbase with name:" .. AirbaseName )
|
||||
end
|
||||
|
||||
AI_Air_Squadron.Spawn = {}
|
||||
if type( TemplatePrefixes ) == "string" then
|
||||
local SpawnTemplate = TemplatePrefixes
|
||||
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
|
||||
AI_Air_Squadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate]
|
||||
else
|
||||
for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do
|
||||
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
|
||||
AI_Air_Squadron.Spawn[#AI_Air_Squadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate]
|
||||
end
|
||||
end
|
||||
AI_Air_Squadron.ResourceCount = ResourceCount
|
||||
AI_Air_Squadron.TemplatePrefixes = TemplatePrefixes
|
||||
AI_Air_Squadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured.
|
||||
|
||||
self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default.
|
||||
|
||||
return AI_Air_Squadron
|
||||
end
|
||||
|
||||
--- Set the Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #string Name The Squadron Name.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetName( Name )
|
||||
|
||||
self.Name = Name
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #string The Squadron Name.
|
||||
function AI_AIR_SQUADRON:GetName()
|
||||
|
||||
return self.Name
|
||||
end
|
||||
|
||||
--- Set the ResourceCount of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number ResourceCount The Squadron ResourceCount.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetResourceCount( ResourceCount )
|
||||
|
||||
self.ResourceCount = ResourceCount
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the ResourceCount of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron ResourceCount.
|
||||
function AI_AIR_SQUADRON:GetResourceCount()
|
||||
|
||||
return self.ResourceCount
|
||||
end
|
||||
|
||||
--- Add Resources to the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Resources The Resources to be added.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:AddResources( Resources )
|
||||
|
||||
self.ResourceCount = self.ResourceCount + Resources
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove Resources to the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Resources The Resources to be removed.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:RemoveResources( Resources )
|
||||
|
||||
self.ResourceCount = self.ResourceCount - Resources
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Overhead of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Overhead The Squadron Overhead.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetOverhead( Overhead )
|
||||
|
||||
self.Overhead = Overhead
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Overhead of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Overhead.
|
||||
function AI_AIR_SQUADRON:GetOverhead()
|
||||
|
||||
return self.Overhead
|
||||
end
|
||||
|
||||
--- Set the Grouping of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Grouping The Squadron Grouping.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetGrouping( Grouping )
|
||||
|
||||
self.Grouping = Grouping
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Grouping of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Grouping.
|
||||
function AI_AIR_SQUADRON:GetGrouping()
|
||||
|
||||
return self.Grouping
|
||||
end
|
||||
|
||||
--- Set the FuelThreshold of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number FuelThreshold The Squadron FuelThreshold.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetFuelThreshold( FuelThreshold )
|
||||
|
||||
self.FuelThreshold = FuelThreshold
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the FuelThreshold of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron FuelThreshold.
|
||||
function AI_AIR_SQUADRON:GetFuelThreshold()
|
||||
|
||||
return self.FuelThreshold
|
||||
end
|
||||
|
||||
--- Set the EngageProbability of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number EngageProbability The Squadron EngageProbability.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetEngageProbability( EngageProbability )
|
||||
|
||||
self.EngageProbability = EngageProbability
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the EngageProbability of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron EngageProbability.
|
||||
function AI_AIR_SQUADRON:GetEngageProbability()
|
||||
|
||||
return self.EngageProbability
|
||||
end
|
||||
|
||||
--- Set the Takeoff of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Takeoff The Squadron Takeoff.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetTakeoff( Takeoff )
|
||||
|
||||
self.Takeoff = Takeoff
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Takeoff of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Takeoff.
|
||||
function AI_AIR_SQUADRON:GetTakeoff()
|
||||
|
||||
return self.Takeoff
|
||||
end
|
||||
|
||||
--- Set the Landing of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Landing The Squadron Landing.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetLanding( Landing )
|
||||
|
||||
self.Landing = Landing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Landing of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Landing.
|
||||
function AI_AIR_SQUADRON:GetLanding()
|
||||
|
||||
return self.Landing
|
||||
end
|
||||
|
||||
--- Set the TankerName of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #string TankerName The Squadron Tanker Name.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetTankerName( TankerName )
|
||||
|
||||
self.TankerName = TankerName
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Tanker Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #string The Squadron Tanker Name.
|
||||
function AI_AIR_SQUADRON:GetTankerName()
|
||||
|
||||
return self.TankerName
|
||||
end
|
||||
|
||||
|
||||
--- Set the Radio of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number RadioFrequency The frequency of communication.
|
||||
-- @param #number RadioModulation The modulation of communication.
|
||||
-- @param #number RadioPower The power in Watts of communication.
|
||||
-- @param #string Language The language of the radio speech.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetRadio( RadioFrequency, RadioModulation, RadioPower, Language )
|
||||
|
||||
self.RadioFrequency = RadioFrequency
|
||||
self.RadioModulation = RadioModulation or radio.modulation.AM
|
||||
self.RadioPower = RadioPower or 100
|
||||
|
||||
if self.RadioSpeech then
|
||||
self.RadioSpeech:Stop()
|
||||
end
|
||||
|
||||
self.RadioSpeech = nil
|
||||
|
||||
self.RadioSpeech = RADIOSPEECH:New( RadioFrequency, RadioModulation )
|
||||
self.RadioSpeech.power = RadioPower
|
||||
self.RadioSpeech:Start( 0.5 )
|
||||
|
||||
self.RadioSpeech:SetLanguage( Language )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
652
Moose Development/Moose/AI/AI_BAI.lua
Normal file
652
Moose Development/Moose/AI/AI_BAI.lua
Normal file
@@ -0,0 +1,652 @@
|
||||
--- **AI** - Peform Battlefield Area Interdiction (BAI) within an engagement zone.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Hold and standby within a patrol zone.
|
||||
-- * Engage upon command the assigned targets within an engagement zone.
|
||||
-- * Loop the zone until all targets are eliminated.
|
||||
-- * Trigger different events upon the results achieved.
|
||||
-- * After combat, return to the patrol zone and hold.
|
||||
-- * RTB when commanded or after out of fuel.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_BAI)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Gunterlund**: Test case revision.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_BAI
|
||||
-- @image AI_Battlefield_Air_Interdiction.JPG
|
||||
|
||||
|
||||
--- AI_BAI_ZONE class
|
||||
-- @type AI_BAI_ZONE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
|
||||
|
||||
--- Implements the core functions to provide BattleGround Air Interdiction in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
|
||||
--
|
||||
-- The AI_BAI_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_BAI_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_BAI_ZONE process can be started through the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
|
||||
-- using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When the AI is commanded to provide BattleGround Air Interdiction (through the event **Engage**), the AI will fly towards the Engage Zone.
|
||||
-- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will detect the targets and will only destroy the targets within the Engage Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Every target that is destroyed, is reported< by the AI.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party:
|
||||
--
|
||||
-- * a FAC
|
||||
-- * a timed event
|
||||
-- * a menu option selected by a human
|
||||
-- * a condition
|
||||
-- * others ...
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When the AI has accomplished the Bombing, it will fly back to the Patrol Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone.
|
||||
-- It can be notified to go RTB through the **RTB** event.
|
||||
--
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # 1. AI_BAI_ZONE constructor
|
||||
--
|
||||
-- * @{#AI_BAI_ZONE.New}(): Creates a new AI_BAI_ZONE object.
|
||||
--
|
||||
-- ## 2. AI_BAI_ZONE is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_BAI_ZONE States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing BOMB.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2. AI_BAI_ZONE Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_BAI_ZONE.Engage}**: Engage the AI to provide BOMB in the Engage Zone, destroying any target it finds.
|
||||
-- * **@{#AI_BAI_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
|
||||
-- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object**
|
||||
--
|
||||
-- Use the method @{#AI_BAI_ZONE.SearchOff}() to specify that the EngageZone is not to be searched for potential targets (UNITs), but that the center of the zone
|
||||
-- is the point where a map object is to be destroyed (like a bridge).
|
||||
--
|
||||
-- Example:
|
||||
--
|
||||
-- -- Tell the BAI not to search for potential targets in the BAIEngagementZone, but rather use the center of the BAIEngagementZone as the bombing location.
|
||||
-- AIBAIZone:SearchOff()
|
||||
--
|
||||
-- Searching can be switched back on with the method @{#AI_BAI_ZONE.SearchOn}(). Use the method @{#AI_BAI_ZONE.SearchOnOff}() to flexibily switch searching on or off.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_BAI_ZONE
|
||||
AI_BAI_ZONE = {
|
||||
ClassName = "AI_BAI_ZONE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_BAI_ZONE object
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_BAI_ZONE self
|
||||
function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_BAI_ZONE
|
||||
|
||||
self.EngageZone = EngageZone
|
||||
self.Accomplished = false
|
||||
|
||||
self:SetDetectionZone( self.EngageZone )
|
||||
self:SearchOn()
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeEngage
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterEngage
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_BAI_ZONE] Engage
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
|
||||
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Engage
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
|
||||
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnLeaveEngaging
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnEnterEngaging
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeFired
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterFired
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_BAI_ZONE] Fired
|
||||
-- @param #AI_BAI_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Fired
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeDestroy
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterDestroy
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_BAI_ZONE] Destroy
|
||||
-- @param #AI_BAI_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Destroy
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeAbort
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterAbort
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_BAI_ZONE] Abort
|
||||
-- @param #AI_BAI_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Abort
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnBeforeAccomplish
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_BAI_ZONE] OnAfterAccomplish
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_BAI_ZONE] Accomplish
|
||||
-- @param #AI_BAI_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_BAI_ZONE] __Accomplish
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Engage Zone where the AI is performing BOMB. Note that if the EngageZone is changed, the AI needs to re-detect targets.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing BOMB.
|
||||
-- @return #AI_BAI_ZONE self
|
||||
function AI_BAI_ZONE:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Specifies whether to search for potential targets in the zone, or let the center of the zone be the bombing coordinate.
|
||||
-- AI_BAI_ZONE will search for potential targets by default.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @return #AI_BAI_ZONE
|
||||
function AI_BAI_ZONE:SearchOnOff( Search )
|
||||
|
||||
self.Search = Search
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- If Search is Off, the current zone coordinate will be the center of the bombing.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @return #AI_BAI_ZONE
|
||||
function AI_BAI_ZONE:SearchOff()
|
||||
|
||||
self:SearchOnOff( false )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- If Search is On, BAI will search for potential targets in the zone.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @return #AI_BAI_ZONE
|
||||
function AI_BAI_ZONE:SearchOn()
|
||||
|
||||
self:SearchOnOff( true )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- onafter State Transition for Event Start.
|
||||
-- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
-- Call the parent Start event handler
|
||||
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
|
||||
self:SetDetectionDeactivated() -- When not engaging, set the detection off.
|
||||
end
|
||||
|
||||
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
|
||||
function _NewEngageRoute( AIControllable )
|
||||
|
||||
AIControllable:T( "NewEngageRoute" )
|
||||
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_BAI#AI_BAI_ZONE
|
||||
EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection )
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onbeforeEngage( Controllable, From, Event, To )
|
||||
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onafterTarget( Controllable, From, Event, To )
|
||||
self:F({"onafterTarget",self.Search,Controllable:IsAlive()})
|
||||
|
||||
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
if self.Search == true then
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
if DetectedUnit:IsAlive() then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
if Detected == true then
|
||||
self:F( {"Target: ", DetectedUnit } )
|
||||
self.DetectedUnits[DetectedUnit] = false
|
||||
local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil )
|
||||
self.Controllable:PushTask( AttackTask, 1 )
|
||||
end
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
self:F("Attack zone")
|
||||
local AttackTask = Controllable:TaskAttackMapObject(
|
||||
self.EngageZone:GetPointVec2():GetVec2(),
|
||||
true,
|
||||
self.EngageWeaponExpend,
|
||||
self.EngageAttackQty,
|
||||
self.EngageDirection,
|
||||
self.EngageAltitude
|
||||
)
|
||||
self.Controllable:PushTask( AttackTask, 1 )
|
||||
end
|
||||
|
||||
self:__Target( -10 )
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onafterAbort( Controllable, From, Event, To )
|
||||
Controllable:ClearTasks()
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
EngageSpeed,
|
||||
EngageAltitude,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection )
|
||||
|
||||
self:F("onafterEngage")
|
||||
|
||||
self.EngageSpeed = EngageSpeed or 400
|
||||
self.EngageAltitude = EngageAltitude or 2000
|
||||
self.EngageWeaponExpend = EngageWeaponExpend
|
||||
self.EngageAttackQty = EngageAttackQty
|
||||
self.EngageDirection = EngageDirection
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
self.EngageSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
if self.Search == true then
|
||||
|
||||
for DetectedUnitID, DetectedUnitData in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT
|
||||
self:T( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
self:F( {"Engaging ", DetectedUnit } )
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskBombing(
|
||||
DetectedUnit:GetPointVec2():GetVec2(),
|
||||
true,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection,
|
||||
EngageAltitude
|
||||
)
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
else
|
||||
self:F("Attack zone")
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackMapObject(
|
||||
self.EngageZone:GetPointVec2():GetVec2(),
|
||||
true,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection,
|
||||
EngageAltitude
|
||||
)
|
||||
end
|
||||
|
||||
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
|
||||
|
||||
--- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone.
|
||||
|
||||
--- Find a random 2D point in EngageZone.
|
||||
local ToTargetVec2 = self.EngageZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
self.EngageSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
|
||||
|
||||
Controllable:OptionROEOpenFire()
|
||||
Controllable:OptionROTVertical()
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
Controllable:WayPointInitialize( EngageRoute )
|
||||
|
||||
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
|
||||
Controllable:SetState( Controllable, "EngageZone", self )
|
||||
|
||||
Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" )
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
Controllable:WayPointExecute( 1 )
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start targeting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_BAI_ZONE:onafterAccomplish( Controllable, From, Event, To )
|
||||
self.Accomplished = true
|
||||
self:SetDetectionDeactivated()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_BAI_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.DetectedUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_BAI_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_BAI_ZONE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
|
||||
self:__Destroy( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
314
Moose Development/Moose/AI/AI_Balancer.lua
Normal file
314
Moose Development/Moose/AI/AI_Balancer.lua
Normal file
@@ -0,0 +1,314 @@
|
||||
--- **AI** - Balance player slots with AI to create an engaging simulation environment, independent of the amount of players.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Automatically spawn AI as a replacement of free player slots for a coalition.
|
||||
-- * Make the AI to perform tasks.
|
||||
-- * Define a maximum amount of AI to be active at the same time.
|
||||
-- * Configure the behaviour of AI when a human joins a slot for which an AI is active.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Balancer)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Dutch_Baron**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Balancer
|
||||
-- @image AI_Balancing.JPG
|
||||
|
||||
--- @type AI_BALANCER
|
||||
-- @field Core.Set#SET_CLIENT SetClient
|
||||
-- @field Core.Spawn#SPAWN SpawnAI
|
||||
-- @field Wrapper.Group#GROUP Test
|
||||
-- @extends Core.Fsm#FSM_SET
|
||||
|
||||
|
||||
--- Monitors and manages as many replacement AI groups as there are
|
||||
-- CLIENTS in a SET\_CLIENT collection, which are not occupied by human players.
|
||||
-- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions.
|
||||
--
|
||||
-- The parent class @{Core.Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
|
||||
-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
|
||||
-- An explanation about state and event transition methods can be found in the @{Core.Fsm} module documentation.
|
||||
--
|
||||
-- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following:
|
||||
--
|
||||
-- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned.
|
||||
--
|
||||
-- ## 1. AI_BALANCER construction
|
||||
--
|
||||
-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method:
|
||||
--
|
||||
-- ## 2. AI_BALANCER is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_BALANCER States
|
||||
--
|
||||
-- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients.
|
||||
-- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference.
|
||||
-- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
|
||||
-- * **Destroying** ( Set, AIGroup ): The AI is being destroyed.
|
||||
-- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any.
|
||||
--
|
||||
-- ### 2.2. AI_BALANCER Events
|
||||
--
|
||||
-- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set.
|
||||
-- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference.
|
||||
-- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes.
|
||||
-- * **Destroy** ( Set, AIGroup ): The AI is being destroyed.
|
||||
-- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods.
|
||||
--
|
||||
-- ## 3. AI_BALANCER spawn interval for replacement AI
|
||||
--
|
||||
-- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned.
|
||||
--
|
||||
-- ## 4. AI_BALANCER returns AI to Airbases
|
||||
--
|
||||
-- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default.
|
||||
-- However, there are 2 additional options that you can use to customize the destroy behaviour.
|
||||
-- When a human player joins a slot, you can configure to let the AI return to:
|
||||
--
|
||||
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Wrapper.Airbase#AIRBASE}.
|
||||
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Wrapper.Airbase#AIRBASE}.
|
||||
--
|
||||
-- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return,
|
||||
-- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- @field #AI_BALANCER
|
||||
AI_BALANCER = {
|
||||
ClassName = "AI_BALANCER",
|
||||
PatrolZones = {},
|
||||
AIGroups = {},
|
||||
Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds.
|
||||
Latest = 60, -- Latest a new AI can be spawned is in 60 seconds.
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_BALANCER object
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).
|
||||
-- @param Core.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
|
||||
-- @return #AI_BALANCER
|
||||
function AI_BALANCER:New( SetClient, SpawnAI )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER
|
||||
|
||||
-- TODO: Define the OnAfterSpawned event
|
||||
self:SetStartState( "None" )
|
||||
self:AddTransition( "*", "Monitor", "Monitoring" )
|
||||
self:AddTransition( "*", "Spawn", "Spawning" )
|
||||
self:AddTransition( "Spawning", "Spawned", "Spawned" )
|
||||
self:AddTransition( "*", "Destroy", "Destroying" )
|
||||
self:AddTransition( "*", "Return", "Returning" )
|
||||
|
||||
self.SetClient = SetClient
|
||||
self.SetClient:FilterOnce()
|
||||
self.SpawnAI = SpawnAI
|
||||
|
||||
self.SpawnQueue = {}
|
||||
|
||||
self.ToNearestAirbase = false
|
||||
self.ToHomeAirbase = false
|
||||
|
||||
self:__Monitor( 1 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI.
|
||||
-- Provide 2 identical seconds if the interval should be a fixed amount of seconds.
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param #number Earliest The earliest a new AI can be spawned in seconds.
|
||||
-- @param #number Latest The latest a new AI can be spawned in seconds.
|
||||
-- @return self
|
||||
function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
|
||||
|
||||
self.Earliest = Earliest
|
||||
self.Latest = Latest
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}.
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}.
|
||||
-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to.
|
||||
function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet )
|
||||
|
||||
self.ToNearestAirbase = true
|
||||
self.ReturnThresholdRange = ReturnThresholdRange
|
||||
self.ReturnAirbaseSet = ReturnAirbaseSet
|
||||
end
|
||||
|
||||
--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}.
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}.
|
||||
function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange )
|
||||
|
||||
self.ToHomeAirbase = true
|
||||
self.ReturnThresholdRange = ReturnThresholdRange
|
||||
end
|
||||
|
||||
--- AI_BALANCER:onenterSpawning
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_GROUP SetGroup
|
||||
-- @param #string ClientName
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName )
|
||||
|
||||
-- OK, Spawn a new group from the default SpawnAI object provided.
|
||||
local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP
|
||||
if AIGroup then
|
||||
AIGroup:T( { "Spawning new AIGroup", ClientName = ClientName } )
|
||||
--TODO: need to rework UnitName thing ...
|
||||
|
||||
SetGroup:Remove( ClientName ) -- Ensure that the previously allocated AIGroup to ClientName is removed in the Set.
|
||||
SetGroup:Add( ClientName, AIGroup )
|
||||
self.SpawnQueue[ClientName] = nil
|
||||
|
||||
-- Fire the Spawned event. The first parameter is the AIGroup just Spawned.
|
||||
-- Mission designers can catch this event to bind further actions to the AIGroup.
|
||||
self:Spawned( AIGroup )
|
||||
end
|
||||
end
|
||||
|
||||
--- AI_BALANCER:onenterDestroying
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_GROUP SetGroup
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup )
|
||||
|
||||
AIGroup:Destroy()
|
||||
SetGroup:Flush( self )
|
||||
SetGroup:Remove( ClientName )
|
||||
SetGroup:Flush( self )
|
||||
end
|
||||
|
||||
--- RTB
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_GROUP SetGroup
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
|
||||
|
||||
local AIGroupTemplate = AIGroup:GetTemplate()
|
||||
if self.ToHomeAirbase == true then
|
||||
local WayPointCount = #AIGroupTemplate.route.points
|
||||
local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 )
|
||||
AIGroup:SetCommand( SwitchWayPointCommand )
|
||||
AIGroup:MessageToRed( "Returning to home base ...", 30 )
|
||||
else
|
||||
-- Okay, we need to send this Group back to the nearest base of the Coalition of the AI.
|
||||
--TODO: i need to rework the POINT_VEC2 thing.
|
||||
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
|
||||
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
|
||||
self:T( ClosestAirbase.AirbaseName )
|
||||
--[[
|
||||
AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
|
||||
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
|
||||
AIGroupTemplate.route = RTBRoute
|
||||
AIGroup:Respawn( AIGroupTemplate )
|
||||
]]
|
||||
AIGroup:RouteRTB(ClosestAirbase)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- AI_BALANCER:onenterMonitoring
|
||||
-- @param #AI_BALANCER self
|
||||
function AI_BALANCER:onenterMonitoring( SetGroup )
|
||||
|
||||
self:T2( { self.SetClient:Count() } )
|
||||
--self.SetClient:Flush()
|
||||
|
||||
self.SetClient:ForEachClient(
|
||||
--- SetClient:ForEachClient
|
||||
-- @param Wrapper.Client#CLIENT Client
|
||||
function( Client )
|
||||
self:T3(Client.ClientName)
|
||||
|
||||
local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP
|
||||
if AIGroup then self:T( { AIGroup = AIGroup:GetName(), IsAlive = AIGroup:IsAlive() } ) end
|
||||
if Client:IsAlive() == true then
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() == true then
|
||||
|
||||
if self.ToNearestAirbase == false and self.ToHomeAirbase == false then
|
||||
self:Destroy( Client.UnitName, AIGroup )
|
||||
else
|
||||
-- We test if there is no other CLIENT within the self.ReturnThresholdRange of the first unit of the AI group.
|
||||
-- If there is a CLIENT, the AI stays engaged and will not return.
|
||||
-- If there is no CLIENT within the self.ReturnThresholdRange, then the unit will return to the Airbase return method selected.
|
||||
|
||||
local PlayerInRange = { Value = false }
|
||||
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnThresholdRange )
|
||||
|
||||
self:T2( RangeZone )
|
||||
|
||||
_DATABASE:ForEachPlayerUnit(
|
||||
--- Nameless function
|
||||
-- @param Wrapper.Unit#UNIT RangeTestUnit
|
||||
function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
|
||||
self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
|
||||
if RangeTestUnit:IsInZone( RangeZone ) == true then
|
||||
self:T2( "in zone" )
|
||||
if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then
|
||||
self:T2( "in range" )
|
||||
PlayerInRange.Value = true
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
--- Nameless function
|
||||
-- @param Core.Zone#ZONE_RADIUS RangeZone
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function( RangeZone, AIGroup, PlayerInRange )
|
||||
if PlayerInRange.Value == false then
|
||||
self:Return( AIGroup )
|
||||
end
|
||||
end
|
||||
, RangeZone, AIGroup, PlayerInRange
|
||||
)
|
||||
|
||||
end
|
||||
self.Set:Remove( Client.UnitName )
|
||||
end
|
||||
else
|
||||
if not AIGroup or not AIGroup:IsAlive() == true then
|
||||
self:T( "Client " .. Client.UnitName .. " not alive." )
|
||||
self:T( { Queue = self.SpawnQueue[Client.UnitName] } )
|
||||
if not self.SpawnQueue[Client.UnitName] then
|
||||
-- Spawn a new AI taking into account the spawn interval Earliest, Latest
|
||||
self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName )
|
||||
self.SpawnQueue[Client.UnitName] = true
|
||||
self:T( "New AI Spawned for Client " .. Client.UnitName )
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
)
|
||||
|
||||
self:__Monitor( 10 )
|
||||
end
|
||||
541
Moose Development/Moose/AI/AI_CAP.lua
Normal file
541
Moose Development/Moose/AI/AI_CAP.lua
Normal file
@@ -0,0 +1,541 @@
|
||||
--- **AI** - Perform Combat Air Patrolling (CAP) for airplanes.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel threshold to RTB on time.
|
||||
-- * Engage the enemy when detected.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_CAP)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1YCyPxJgoZn-CfhwyeW65L)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Quax**: Concept, Advice & Testing.
|
||||
-- * **Pikey**: Concept, Advice & Testing.
|
||||
-- * **Gunterlund**: Test case revision.
|
||||
-- * **Whisper**: Testing.
|
||||
-- * **Delta99**: Testing.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_CAP
|
||||
-- @image AI_Combat_Air_Patrol.JPG
|
||||
|
||||
--- @type AI_CAP_ZONE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
|
||||
|
||||
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}
|
||||
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_CAP_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_CAP_ZONE constructor
|
||||
--
|
||||
-- * @{#AI_CAP_ZONE.New}(): Creates a new AI_CAP_ZONE object.
|
||||
--
|
||||
-- ## 2. AI_CAP_ZONE is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1 AI_CAP_ZONE States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_CAP_ZONE Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_CAP_ZONE.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_CAP_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{#AI_CAP_ZONE.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Core.Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{#AI_CAP_ZONE.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CAP_ZONE
|
||||
AI_CAP_ZONE = {
|
||||
ClassName = "AI_CAP_ZONE",
|
||||
}
|
||||
|
||||
--- Creates a new AI_CAP_ZONE object
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_CAP_ZONE self
|
||||
function AI_CAP_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAP_ZONE
|
||||
|
||||
self.Accomplished = false
|
||||
self.Engaging = false
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeEngage
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterEngage
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_CAP_ZONE] Engage
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Engage
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnLeaveEngaging
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnEnterEngaging
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeFired
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterFired
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_CAP_ZONE] Fired
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Fired
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeDestroy
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterDestroy
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_CAP_ZONE] Destroy
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Destroy
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeAbort
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterAbort
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_CAP_ZONE] Abort
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Abort
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnBeforeAccomplish
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_CAP_ZONE] OnAfterAccomplish
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_CAP_ZONE] Accomplish
|
||||
-- @param #AI_CAP_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_CAP_ZONE] __Accomplish
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Engage Zone which defines where the AI will engage bogies.
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP.
|
||||
-- @return #AI_CAP_ZONE self
|
||||
function AI_CAP_ZONE:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param #number EngageRange The Engage Range.
|
||||
-- @return #AI_CAP_ZONE self
|
||||
function AI_CAP_ZONE:SetEngageRange( EngageRange )
|
||||
self:F2()
|
||||
|
||||
if EngageRange then
|
||||
self.EngageRange = EngageRange
|
||||
else
|
||||
self.EngageRange = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Start.
|
||||
-- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
-- Call the parent Start event handler
|
||||
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
|
||||
end
|
||||
|
||||
--- @param AI.AI_CAP#AI_CAP_ZONE
|
||||
-- @param Wrapper.Group#GROUP EngageGroup
|
||||
function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm )
|
||||
|
||||
EngageGroup:F( { "AI_CAP_ZONE.EngageRoute:", EngageGroup:GetName() } )
|
||||
|
||||
if EngageGroup:IsAlive() then
|
||||
Fsm:__Engage( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onbeforeEngage( Controllable, From, Event, To )
|
||||
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To )
|
||||
|
||||
if From ~= "Engaging" then
|
||||
|
||||
local Engage = false
|
||||
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
self:T( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then
|
||||
Engage = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if Engage == true then
|
||||
self:F( 'Detected -> Engaging' )
|
||||
self:__Engage( 1 )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterAbort( Controllable, From, Event, To )
|
||||
Controllable:ClearTasks()
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
|
||||
|
||||
if Controllable and Controllable:IsAlive() then
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
if not CurrentVec2 then return self end
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToEngageZoneSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
|
||||
|
||||
--- Find a random 2D point in PatrolZone.
|
||||
local ToTargetVec2 = self.PatrolZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Define Speed and Altitude.
|
||||
local ToTargetAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToTargetSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
|
||||
|
||||
Controllable:OptionROEOpenFire()
|
||||
Controllable:OptionROTEvadeFire()
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
self:T( { DetectedUnit, DetectedUnit:IsAlive(), DetectedUnit:IsAir() } )
|
||||
if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then
|
||||
if self.EngageZone then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
self:F( {"Within Zone and Engaging ", DetectedUnit } )
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
|
||||
end
|
||||
else
|
||||
if self.EngageRange then
|
||||
if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3() ) <= self.EngageRange then
|
||||
self:F( {"Within Range and Engaging", DetectedUnit } )
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
|
||||
end
|
||||
else
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit )
|
||||
end
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if #AttackTasks == 0 then
|
||||
self:F("No targets found -> Going back to Patrolling")
|
||||
self:__Abort( 1 )
|
||||
self:__Route( 1 )
|
||||
self:SetDetectionActivated()
|
||||
else
|
||||
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAP_ZONE.EngageRoute", self )
|
||||
EngageRoute[1].task = Controllable:TaskCombo( AttackTasks )
|
||||
|
||||
self:SetDetectionDeactivated()
|
||||
end
|
||||
|
||||
Controllable:Route( EngageRoute, 0.5 )
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterAccomplish( Controllable, From, Event, To )
|
||||
self.Accomplished = true
|
||||
self:SetDetectionOff()
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_CAP_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.DetectedUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAP_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_CAP_ZONE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
|
||||
self:__Destroy( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
570
Moose Development/Moose/AI/AI_CAS.lua
Normal file
570
Moose Development/Moose/AI/AI_CAS.lua
Normal file
@@ -0,0 +1,570 @@
|
||||
--- **AI** - Perform Close Air Support (CAS) near friendlies.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Hold and standby within a patrol zone.
|
||||
-- * Engage upon command the enemies within an engagement zone.
|
||||
-- * Loop the zone until all enemies are eliminated.
|
||||
-- * Trigger different events upon the results achieved.
|
||||
-- * After combat, return to the patrol zone and hold.
|
||||
-- * RTB when commanded or after fuel.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_CAS)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Quax**: Concept, Advice & Testing.
|
||||
-- * **Pikey**: Concept, Advice & Testing.
|
||||
-- * **Gunterlund**: Test case revision.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_CAS
|
||||
-- @image AI_Close_Air_Support.JPG
|
||||
|
||||
--- AI_CAS_ZONE class
|
||||
-- @type AI_CAS_ZONE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
|
||||
|
||||
--- Implements the core functions to provide Close Air Support in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
|
||||
-- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_CAS_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
|
||||
-- using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When the AI is commanded to provide Close Air Support (through the event **Engage**), the AI will fly towards the Engage Zone.
|
||||
-- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will detect the targets and will only destroy the targets within the Engage Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Every target that is destroyed, is reported< by the AI.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party:
|
||||
--
|
||||
-- * a FAC
|
||||
-- * a timed event
|
||||
-- * a menu option selected by a human
|
||||
-- * a condition
|
||||
-- * others ...
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When the AI has accomplished the CAS, it will fly back to the Patrol Zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone.
|
||||
-- It can be notified to go RTB through the **RTB** event.
|
||||
--
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## AI_CAS_ZONE constructor
|
||||
--
|
||||
-- * @{#AI_CAS_ZONE.New}(): Creates a new AI_CAS_ZONE object.
|
||||
--
|
||||
-- ## AI_CAS_ZONE is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_CAS_ZONE States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing CAS.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2. AI_CAS_ZONE Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_CAS_ZONE.Engage}**: Engage the AI to provide CAS in the Engage Zone, destroying any target it finds.
|
||||
-- * **@{#AI_CAS_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
|
||||
-- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CAS_ZONE
|
||||
AI_CAS_ZONE = {
|
||||
ClassName = "AI_CAS_ZONE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_CAS_ZONE object
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_CAS_ZONE self
|
||||
function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAS_ZONE
|
||||
|
||||
self.EngageZone = EngageZone
|
||||
self.Accomplished = false
|
||||
|
||||
self:SetDetectionZone( self.EngageZone )
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeEngage
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterEngage
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_CAS_ZONE] Engage
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
|
||||
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Engage
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
|
||||
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnEnterEngaging
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeFired
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterFired
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_CAS_ZONE] Fired
|
||||
-- @param #AI_CAS_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Fired
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeDestroy
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterDestroy
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_CAS_ZONE] Destroy
|
||||
-- @param #AI_CAS_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Destroy
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeAbort
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterAbort
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_CAS_ZONE] Abort
|
||||
-- @param #AI_CAS_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Abort
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnBeforeAccomplish
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_CAS_ZONE] OnAfterAccomplish
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_CAS_ZONE] Accomplish
|
||||
-- @param #AI_CAS_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_CAS_ZONE] __Accomplish
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Engage Zone where the AI is performing CAS. Note that if the EngageZone is changed, the AI needs to re-detect targets.
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAS.
|
||||
-- @return #AI_CAS_ZONE self
|
||||
function AI_CAS_ZONE:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- onafter State Transition for Event Start.
|
||||
-- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
-- Call the parent Start event handler
|
||||
self:GetParent(self).onafterStart( self, Controllable, From, Event, To )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
|
||||
self:SetDetectionDeactivated() -- When not engaging, set the detection off.
|
||||
end
|
||||
|
||||
--- @param AI.AI_CAS#AI_CAS_ZONE
|
||||
-- @param Wrapper.Group#GROUP EngageGroup
|
||||
function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm )
|
||||
|
||||
EngageGroup:F( { "AI_CAS_ZONE.EngageRoute:", EngageGroup:GetName() } )
|
||||
|
||||
if EngageGroup:IsAlive() then
|
||||
Fsm:__Engage( 1, Fsm.EngageSpeed, Fsm.EngageAltitude, Fsm.EngageWeaponExpend, Fsm.EngageAttackQty, Fsm.EngageDirection )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onbeforeEngage( Controllable, From, Event, To )
|
||||
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To )
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
if DetectedUnit:IsAlive() then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
if Detected == true then
|
||||
self:F( {"Target: ", DetectedUnit } )
|
||||
self.DetectedUnits[DetectedUnit] = false
|
||||
local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil )
|
||||
self.Controllable:PushTask( AttackTask, 1 )
|
||||
end
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
self:__Target( -10 )
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onafterAbort( Controllable, From, Event, To )
|
||||
Controllable:ClearTasks()
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
|
||||
-- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
|
||||
-- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.
|
||||
-- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo.
|
||||
-- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
|
||||
function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
EngageSpeed,
|
||||
EngageAltitude,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection )
|
||||
self:F("onafterEngage")
|
||||
|
||||
self.EngageSpeed = EngageSpeed or 400
|
||||
self.EngageAltitude = EngageAltitude or 2000
|
||||
self.EngageWeaponExpend = EngageWeaponExpend
|
||||
self.EngageAttackQty = EngageAttackQty
|
||||
self.EngageDirection = EngageDirection
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
|
||||
Controllable:OptionROEOpenFire()
|
||||
Controllable:OptionROTVertical()
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
self.EngageSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = CurrentRoutePoint
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
|
||||
local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT
|
||||
self:T( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() then
|
||||
if DetectedUnit:IsInZone( self.EngageZone ) then
|
||||
self:F( {"Engaging ", DetectedUnit } )
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit,
|
||||
true,
|
||||
EngageWeaponExpend,
|
||||
EngageAttackQty,
|
||||
EngageDirection
|
||||
)
|
||||
end
|
||||
else
|
||||
self.DetectedUnits[DetectedUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAS_ZONE.EngageRoute", self )
|
||||
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
|
||||
|
||||
--- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone.
|
||||
|
||||
--- Find a random 2D point in EngageZone.
|
||||
local ToTargetVec2 = self.EngageZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
self.EngageSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
|
||||
|
||||
Controllable:Route( EngageRoute, 0.5 )
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start targeting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAS_ZONE:onafterAccomplish( Controllable, From, Event, To )
|
||||
self.Accomplished = true
|
||||
self:SetDetectionDeactivated()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_CAS_ZONE:onafterDestroy( Controllable, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.DetectedUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CAS_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_CAS_ZONE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then
|
||||
self:__Destroy( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
589
Moose Development/Moose/AI/AI_Cargo.lua
Normal file
589
Moose Development/Moose/AI/AI_Cargo.lua
Normal file
@@ -0,0 +1,589 @@
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo
|
||||
-- @image Cargo.JPG
|
||||
|
||||
--- @type AI_CARGO
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
--- Base class for the dynamic cargo handling capability for AI groups.
|
||||
--
|
||||
-- Carriers can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
-- The AI_CARGO module uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- CARGO derived objects must be declared within the mission to make the AI_CARGO object recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- The derived classes from this module are:
|
||||
--
|
||||
-- * @{AI.AI_Cargo_APC} - Cargo transportation using APCs and other vehicles between zones.
|
||||
-- * @{AI.AI_Cargo_Helicopter} - Cargo transportation using helicopters between zones.
|
||||
-- * @{AI.AI_Cargo_Airplane} - Cargo transportation using airplanes to and from airbases.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- @field #AI_CARGO
|
||||
AI_CARGO = {
|
||||
ClassName = "AI_CARGO",
|
||||
Coordinate = nil, -- Core.Point#COORDINATE,
|
||||
Carrier_Cargo = {},
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO object.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier Cargo carrier group.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport.
|
||||
-- @return #AI_CARGO self
|
||||
function AI_CARGO:New( Carrier, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO
|
||||
|
||||
self.CargoSet = CargoSet -- Core.Set#SET_CARGO
|
||||
self.CargoCarrier = Carrier -- Wrapper.Group#GROUP
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
|
||||
-- Board
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Reload", "*" )
|
||||
self:AddTransition( "*", "Board", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
-- Unload
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unboard", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnBeforePickup
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
-- @return #boolean
|
||||
|
||||
--- Pickup Handler OnAfter for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnAfterPickup
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Pickup Trigger for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] Pickup
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] __Pickup
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #number Delay
|
||||
-- @param Core.Point#COORDINATE Coordinate Pickup place. If not given, loading starts at the current location.
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Deploy Handler OnBefore for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnBeforeDeploy
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnAfterDeploy
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Deploy Trigger for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] Deploy
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] __Deploy
|
||||
-- @param #AI_CARGO self
|
||||
-- @param #number Delay
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do.
|
||||
|
||||
|
||||
--- Loaded Handler OnAfter for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnAfterLoaded
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Unloaded Handler OnAfter for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnAfterUnloaded
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @function [parent=#AI_CARGO] OnAfterDeployed
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
-- @param #boolean Defend Defend for APCs.
|
||||
|
||||
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
CarrierUnit:SetCargoBayWeightLimit()
|
||||
end
|
||||
|
||||
self.Transporting = false
|
||||
self.Relocating = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
function AI_CARGO:IsTransporting()
|
||||
|
||||
return self.Transporting == true
|
||||
end
|
||||
|
||||
function AI_CARGO:IsRelocating()
|
||||
|
||||
return self.Relocating == true
|
||||
end
|
||||
|
||||
|
||||
--- On after Pickup event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
self.Transporting = false
|
||||
self.Relocating = true
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Deploy place.
|
||||
-- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
self.Relocating = false
|
||||
self.Transporting = true
|
||||
|
||||
end
|
||||
|
||||
--- On before Load event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone )
|
||||
self:F( { Carrier, From, Event, To } )
|
||||
|
||||
local Boarding = false
|
||||
|
||||
local LoadInterval = 2
|
||||
local LoadDelay = 1
|
||||
local Carrier_List = {}
|
||||
local Carrier_Weight = {}
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
self.Carrier_Cargo = {}
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
|
||||
local CargoBayFreeWeight = CarrierUnit:GetCargoBayFreeWeight()
|
||||
self:F({CargoBayFreeWeight=CargoBayFreeWeight})
|
||||
|
||||
Carrier_List[#Carrier_List+1] = CarrierUnit
|
||||
Carrier_Weight[CarrierUnit] = CargoBayFreeWeight
|
||||
end
|
||||
|
||||
local Carrier_Count = #Carrier_List
|
||||
local Carrier_Index = 1
|
||||
|
||||
local Loaded = false
|
||||
|
||||
for _, Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
|
||||
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } )
|
||||
|
||||
-- Try all Carriers, but start from the one according the Carrier_Index
|
||||
for Carrier_Loop = 1, #Carrier_List do
|
||||
|
||||
local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT
|
||||
|
||||
-- This counters loop through the available Carriers.
|
||||
Carrier_Index = Carrier_Index + 1
|
||||
if Carrier_Index > Carrier_Count then
|
||||
Carrier_Index = 1
|
||||
end
|
||||
|
||||
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
|
||||
if Cargo:IsInLoadRadius( CarrierUnit:GetCoordinate() ) then
|
||||
self:F( { "In radius", CarrierUnit:GetName() } )
|
||||
|
||||
local CargoWeight = Cargo:GetWeight()
|
||||
local CarrierSpace=Carrier_Weight[CarrierUnit]
|
||||
|
||||
-- Only when there is space within the bay to load the next cargo item!
|
||||
if CarrierSpace > CargoWeight then
|
||||
Carrier:RouteStop()
|
||||
--Cargo:Ungroup()
|
||||
Cargo:__Board( -LoadDelay, CarrierUnit )
|
||||
self:__Board( LoadDelay, Cargo, CarrierUnit, PickupZone )
|
||||
|
||||
LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval
|
||||
|
||||
-- So now this CarrierUnit has Cargo that is being loaded.
|
||||
-- This will be used further in the logic to follow and to check cargo status.
|
||||
self.Carrier_Cargo[Cargo] = CarrierUnit
|
||||
Boarding = true
|
||||
Carrier_Weight[CarrierUnit] = Carrier_Weight[CarrierUnit] - CargoWeight
|
||||
Loaded = true
|
||||
|
||||
-- Ok, we loaded a cargo, now we can stop the loop.
|
||||
break
|
||||
else
|
||||
self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space", tostring(CarrierUnit:GetName()), CargoWeight, CarrierSpace))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if not Loaded == true then
|
||||
-- No loading happened, so we need to pickup something else.
|
||||
self.Relocating = false
|
||||
end
|
||||
end
|
||||
|
||||
return Boarding
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On before Reload event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onbeforeReload( Carrier, From, Event, To )
|
||||
self:F( { Carrier, From, Event, To } )
|
||||
|
||||
local Boarding = false
|
||||
|
||||
local LoadInterval = 2
|
||||
local LoadDelay = 1
|
||||
local Carrier_List = {}
|
||||
local Carrier_Weight = {}
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
|
||||
Carrier_List[#Carrier_List+1] = CarrierUnit
|
||||
end
|
||||
|
||||
local Carrier_Count = #Carrier_List
|
||||
local Carrier_Index = 1
|
||||
|
||||
local Loaded = false
|
||||
|
||||
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
|
||||
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } )
|
||||
|
||||
-- Try all Carriers, but start from the one according the Carrier_Index
|
||||
for Carrier_Loop = 1, #Carrier_List do
|
||||
|
||||
local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT
|
||||
|
||||
-- This counters loop through the available Carriers.
|
||||
Carrier_Index = Carrier_Index + 1
|
||||
if Carrier_Index > Carrier_Count then
|
||||
Carrier_Index = 1
|
||||
end
|
||||
|
||||
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
|
||||
Carrier:RouteStop()
|
||||
Cargo:__Board( -LoadDelay, CarrierUnit )
|
||||
self:__Board( LoadDelay, Cargo, CarrierUnit )
|
||||
|
||||
LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval
|
||||
|
||||
-- So now this CarrierUnit has Cargo that is being loaded.
|
||||
-- This will be used further in the logic to follow and to check cargo status.
|
||||
self.Carrier_Cargo[Cargo] = CarrierUnit
|
||||
Boarding = true
|
||||
Loaded = true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if not Loaded == true then
|
||||
-- No loading happened, so we need to pickup something else.
|
||||
self.Relocating = false
|
||||
end
|
||||
end
|
||||
|
||||
return Boarding
|
||||
|
||||
end
|
||||
|
||||
--- On after Board event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param Wrapper.Unit#UNIT CarrierUnit
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onafterBoard( Carrier, From, Event, To, Cargo, CarrierUnit, PickupZone )
|
||||
self:F( { Carrier, From, Event, To, Cargo, CarrierUnit:GetName() } )
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } )
|
||||
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
|
||||
self:__Board( -10, Cargo, CarrierUnit, PickupZone )
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self:__Loaded( 0.1, Cargo, CarrierUnit, PickupZone )
|
||||
|
||||
end
|
||||
|
||||
--- On after Loaded event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @return #boolean Cargo loaded.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onafterLoaded( Carrier, From, Event, To, Cargo, PickupZone )
|
||||
self:F( { Carrier, From, Event, To } )
|
||||
|
||||
local Loaded = true
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), Carrier:GetName() } )
|
||||
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
|
||||
Loaded = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Loaded then
|
||||
self:__PickedUp( 0.1, PickupZone )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after PickedUp event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO:onafterPickedUp( Carrier, From, Event, To, PickupZone )
|
||||
self:F( { Carrier, From, Event, To } )
|
||||
|
||||
Carrier:RouteResume()
|
||||
|
||||
local HasCargo = false
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do
|
||||
HasCargo = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self.Relocating = false
|
||||
if HasCargo then
|
||||
self:F( "Transporting" )
|
||||
self.Transporting = true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- On after Unload event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, DeployZone, Defend = Defend } )
|
||||
|
||||
local UnboardInterval = 5
|
||||
local UnboardDelay = 5
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
Carrier:RouteStop()
|
||||
for _, Cargo in pairs( CarrierUnit:GetCargo() ) do
|
||||
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
|
||||
if Cargo:IsLoaded() then
|
||||
Cargo:__UnBoard( UnboardDelay )
|
||||
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
|
||||
self:__Unboard( UnboardDelay, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
if not Defend == true then
|
||||
Cargo:SetDeployed( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after Unboard event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
if not Cargo:IsUnLoaded() then
|
||||
self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self:Unloaded( Cargo, CarrierUnit, DeployZone, Defend )
|
||||
|
||||
end
|
||||
|
||||
--- On after Unloaded event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
local AllUnloaded = true
|
||||
|
||||
--Cargo:Regroup()
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
local IsEmpty = CarrierUnit:IsCargoEmpty()
|
||||
self:I({ IsEmpty = IsEmpty })
|
||||
if not IsEmpty then
|
||||
AllUnloaded = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if AllUnloaded == true then
|
||||
if DeployZone == true then
|
||||
self.Carrier_Cargo = {}
|
||||
end
|
||||
self.CargoCarrier = Carrier
|
||||
end
|
||||
end
|
||||
|
||||
if AllUnloaded == true then
|
||||
self:__Deployed( 5, DeployZone, Defend )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
-- @param #boolean Defend Defend for APCs.
|
||||
function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
if not Defend == true then
|
||||
self.Transporting = false
|
||||
else
|
||||
self:F( "Defending" )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
607
Moose Development/Moose/AI/AI_Cargo_APC.lua
Normal file
607
Moose Development/Moose/AI/AI_Cargo_APC.lua
Normal file
@@ -0,0 +1,607 @@
|
||||
--- **AI** - Models the intelligent transportation of cargo using ground vehicles.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_APC
|
||||
-- @image AI_Cargo_Dispatching_For_APC.JPG
|
||||
|
||||
--- @type AI_CARGO_APC
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI vehicle group.
|
||||
--
|
||||
-- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_APC object recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo loading.
|
||||
--
|
||||
-- The module will load automatically cargo when the APCs are within boarding or loading radius.
|
||||
-- The boarding or loading radius is specified when the cargo is created in the simulation, and therefore, this radius depends on the type of cargo
|
||||
-- and the specified boarding radius.
|
||||
--
|
||||
-- ## **Defending** the APCs when enemies nearby.
|
||||
--
|
||||
-- Cargo will defend the carrier with its available arms, and to avoid cargo being lost within the battlefield.
|
||||
--
|
||||
-- When the APCs are approaching enemy units, something special is happening.
|
||||
-- The APCs will stop moving, and the loaded infantry will unboard and follow the APCs and will help to defend the group.
|
||||
-- The carrier will hold the route once the unboarded infantry is further than 50 meters from the APCs,
|
||||
-- to ensure that the APCs are not too far away from the following running infantry.
|
||||
-- Once all enemies are cleared, the infantry will board again automatically into the APCs. Once boarded, the APCs will follow its pre-defined route.
|
||||
--
|
||||
-- A combat radius needs to be specified in meters at the @{#AI_CARGO_APC.New}() method.
|
||||
-- This combat radius will trigger the unboarding of troops when enemies are within the combat radius around the APCs.
|
||||
-- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit radius (effectiveness) versus
|
||||
-- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered.
|
||||
-- A combat radius of 350 meters to 500 meters has been proven to be the most effective and efficient.
|
||||
--
|
||||
-- However, when the defense of the carrier, is not required, it must be switched off.
|
||||
-- This is done by disabling the defense of the carrier using the method @{#AI_CARGO_APC.SetCombatRadius}(), and providing a combat radius of 0 meters.
|
||||
-- It can be switched on later when required by reenabling the defense using the method and providing a combat radius larger than 0.
|
||||
--
|
||||
-- ## Infantry or cargo **health**.
|
||||
--
|
||||
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
|
||||
-- However, infantry that was destroyed when unboarded and following the APCs, won't be respawned again. Destroyed is destroyed.
|
||||
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
|
||||
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
|
||||
-- time is not so much of an issue ...
|
||||
--
|
||||
-- ## Control the APCs on the map.
|
||||
--
|
||||
-- It is possible also as a human ground commander to influence the path of the APCs, by pointing a new path using the DCS user interface on the map.
|
||||
-- In this case, the APCs will change the direction towards its new indicated route. However, there is a catch!
|
||||
-- Once the APCs are near the enemy, and infantry is unboarded, the APCs won't be able to hold the route until the infantry could catch up.
|
||||
-- The APCs will simply drive on and won't stop! This is a limitation in ED that prevents user actions being controlled by the scripting engine.
|
||||
-- No workaround is possible on this.
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_APC.Deploy}() method, you are able to direct the APCs towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
|
||||
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_APC.Pickup}() method, you are able to direct the APCs towards a point on the battlefield to board/load the cargo at the specific coordinate.
|
||||
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
|
||||
--
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
--
|
||||
-- @field #AI_CARGO_APC
|
||||
AI_CARGO_APC = {
|
||||
ClassName = "AI_CARGO_APC",
|
||||
Coordinate = nil, -- Core.Point#COORDINATE,
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_APC object.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC The carrier APC group.
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported.
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When the combat radius is 0, no defense will happen of the carrier.
|
||||
-- @return #AI_CARGO_APC
|
||||
function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( APC, CargoSet ) ) -- #AI_CARGO_APC
|
||||
|
||||
self:AddTransition( "*", "Monitor", "*" )
|
||||
self:AddTransition( "*", "Follow", "Following" )
|
||||
self:AddTransition( "*", "Guard", "Unloaded" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
self:AddTransition( "*", "Reload", "Boarding" )
|
||||
self:AddTransition( "*", "Deployed", "*" )
|
||||
self:AddTransition( "*", "PickedUp", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
self:SetCombatRadius( CombatRadius )
|
||||
|
||||
self:SetCarrier( APC )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Carrier.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @return #AI_CARGO_APC
|
||||
function AI_CARGO_APC:SetCarrier( CargoCarrier )
|
||||
|
||||
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUP
|
||||
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_APC", self )
|
||||
|
||||
CargoCarrier:HandleEvent( EVENTS.Dead )
|
||||
|
||||
function CargoCarrier:OnEventDead( EventData )
|
||||
self:F({"dead"})
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_APC" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- CargoCarrier:HandleEvent( EVENTS.Hit )
|
||||
--
|
||||
-- function CargoCarrier:OnEventHit( EventData )
|
||||
-- self:F({"hit"})
|
||||
-- local AICargoTroops = self:GetState( self, "AI_CARGO_APC" )
|
||||
-- if AICargoTroops then
|
||||
-- self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
|
||||
-- if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
|
||||
-- -- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
-- AICargoTroops:Unload( false )
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
|
||||
self.Coalition = self.CargoCarrier:GetCoalition()
|
||||
|
||||
self:SetControllable( CargoCarrier )
|
||||
|
||||
self:Guard()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Find a free Carrier within a radius.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Radius
|
||||
-- @return Wrapper.Group#GROUP NewCarrier
|
||||
function AI_CARGO_APC:FindCarrier( Coordinate, Radius )
|
||||
|
||||
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
|
||||
CoordinateZone:Scan( { Object.Category.UNIT } )
|
||||
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
|
||||
local NearUnit = UNIT:Find( DCSUnit )
|
||||
self:F({NearUnit=NearUnit})
|
||||
if not NearUnit:GetState( NearUnit, "AI_CARGO_APC" ) then
|
||||
local Attributes = NearUnit:GetDesc()
|
||||
self:F({Desc=Attributes})
|
||||
if NearUnit:HasAttribute( "Trucks" ) then
|
||||
return NearUnit:GetGroup()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
end
|
||||
|
||||
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
|
||||
-- This is only valid for APCs and trucks etc, thus ground vehicles.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
|
||||
-- When the combat radius is 0, no defense will happen of the carrier.
|
||||
-- When the combat radius is not provided, no defense will happen!
|
||||
-- @return #AI_CARGO_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- Disembark the infantry when the carrier is under attack.
|
||||
-- AICargoAPC:SetCombatRadius( true )
|
||||
--
|
||||
-- -- Keep the cargo in the carrier when the carrier is under attack.
|
||||
-- AICargoAPC:SetCombatRadius( false )
|
||||
function AI_CARGO_APC:SetCombatRadius( CombatRadius )
|
||||
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Follow Infantry to the Carrier.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #AI_CARGO_APC Me
|
||||
-- @param Wrapper.Unit#UNIT APCUnit
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
|
||||
-- @return #AI_CARGO_APC
|
||||
function AI_CARGO_APC:FollowToCarrier( Me, APCUnit, CargoGroup )
|
||||
|
||||
local InfantryGroup = CargoGroup:GetGroup()
|
||||
|
||||
self:F( { self = self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
--if self:Is( "Following" ) then
|
||||
|
||||
if APCUnit:IsAlive() then
|
||||
-- We check if the Cargo is near to the CargoCarrier.
|
||||
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", APCUnit, 25 ) ) then
|
||||
|
||||
-- The Cargo does not need to follow the Carrier.
|
||||
Me:Guard()
|
||||
|
||||
else
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
if InfantryGroup:IsAlive() then
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
local Waypoints = {}
|
||||
|
||||
-- Calculate the new Route.
|
||||
local FromCoord = InfantryGroup:GetCoordinate()
|
||||
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({FromGround=FromGround})
|
||||
table.insert( Waypoints, FromGround )
|
||||
|
||||
local ToCoord = APCUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
|
||||
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({ToGround=ToGround})
|
||||
table.insert( Waypoints, ToGround )
|
||||
|
||||
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_APC.FollowToCarrier", Me, APCUnit, CargoGroup )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- On after Monitor event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function AI_CARGO_APC:onafterMonitor( APC, From, Event, To )
|
||||
self:F( { APC, From, Event, To, IsTransporting = self:IsTransporting() } )
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
if APC and APC:IsAlive() then
|
||||
if self.CarrierCoordinate then
|
||||
if self:IsTransporting() == true then
|
||||
local Coordinate = APC:GetCoordinate()
|
||||
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
if self:Is( "Unloaded" ) then
|
||||
-- There are no enemies within combat radius. Reload the CargoCarrier.
|
||||
self:Reload()
|
||||
end
|
||||
else
|
||||
if self:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
|
||||
else
|
||||
if self:Is( "Unloaded" ) then
|
||||
--self:Follow()
|
||||
end
|
||||
self:F( "I am here" .. self:GetCurrentState() )
|
||||
if self:Is( "Following" ) then
|
||||
for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
|
||||
if Cargo:IsAlive() then
|
||||
if not Cargo:IsNear( APCUnit, 40 ) then
|
||||
APCUnit:RouteStop()
|
||||
self.CarrierStopped = true
|
||||
else
|
||||
if self.CarrierStopped then
|
||||
if Cargo:IsNear( APCUnit, 25 ) then
|
||||
APCUnit:RouteResume()
|
||||
self.CarrierStopped = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
self.CarrierCoordinate = APC:GetCoordinate()
|
||||
end
|
||||
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Follow event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
|
||||
self:F( { APC, From, Event, To } )
|
||||
|
||||
self:F( "Follow" )
|
||||
if APC and APC:IsAlive() then
|
||||
for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
if Cargo:IsUnLoaded() then
|
||||
self:FollowToCarrier( self, APCUnit, Cargo )
|
||||
APCUnit:RouteResume()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Pickup task function. Triggers Load event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC sel `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param #number Speed Speed (not used).
|
||||
-- @param Core.Zone#ZONE PickupZone Pickup zone.
|
||||
function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
|
||||
|
||||
if APC:IsAlive() then
|
||||
self:Load( PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- Deploy task function. Triggers Unload event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC self `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param Core.Zone#ZONE DeployZone Deploy zone.
|
||||
function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
|
||||
|
||||
if APC:IsAlive() then
|
||||
self:Unload( DeployZone )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- On after Pickup event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if APC and APC:IsAlive() then
|
||||
|
||||
if Coordinate then
|
||||
self.RoutePickup = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.pickupOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone )
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
else
|
||||
AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
end
|
||||
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterPickup( self, APC, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Deploy place.
|
||||
-- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if APC and APC:IsAlive() then
|
||||
|
||||
self.RouteDeploy = true
|
||||
|
||||
-- Set speed in km/h.
|
||||
local speedmax=APC:GetSpeedMax()
|
||||
local _speed=Speed or speedmax*0.5
|
||||
_speed=math.min(_speed, speedmax)
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.deployOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
-- Task function
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
-- Last waypoint
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
|
||||
-- Set task function
|
||||
APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Route group
|
||||
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
|
||||
-- Call parent function.
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after Unloaded event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_APC:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterUnloaded( self, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
|
||||
-- If Defend == true then we need to scan for possible enemies within combat zone and engage only ground forces.
|
||||
if Defend == true then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if not self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
-- OK, enemies nearby, now find the enemies and attack them.
|
||||
local AttackUnits = self.Zone:GetScannedUnits() -- #list<DCS#Unit>
|
||||
local Move = {}
|
||||
local CargoGroup = Cargo.CargoObject -- Wrapper.Group#GROUP
|
||||
Move[#Move+1] = CargoGroup:GetCoordinate():WaypointGround( 70, "Custom" )
|
||||
for UnitId, AttackUnit in pairs( AttackUnits ) do
|
||||
local MooseUnit = UNIT:Find( AttackUnit )
|
||||
if MooseUnit:GetCoalition() ~= CargoGroup:GetCoalition() then
|
||||
Move[#Move+1] = MooseUnit:GetCoordinate():WaypointGround( 70, "Line abreast" )
|
||||
--MoveTo.Task = CargoGroup:TaskCombo( CargoGroup:TaskAttackUnit( MooseUnit, true ) )
|
||||
self:F( { MooseUnit = MooseUnit:GetName(), CargoGroup = CargoGroup:GetName() } )
|
||||
end
|
||||
end
|
||||
CargoGroup:RoutePush( Move, 0.1 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_APC:onafterDeployed( APC, From, Event, To, DeployZone, Defend )
|
||||
self:F( { APC, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
self:__Guard( 0.1 )
|
||||
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterDeployed( self, APC, From, Event, To, DeployZone, Defend )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Home event.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate. This parameter is ignored for APCs.
|
||||
function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
|
||||
if APC and APC:IsAlive() ~= nil then
|
||||
|
||||
self.RouteHome = true
|
||||
|
||||
Speed = Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, Speed, "Line abreast", true )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
|
||||
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
510
Moose Development/Moose/AI/AI_Cargo_Airplane.lua
Normal file
510
Moose Development/Moose/AI/AI_Cargo_Airplane.lua
Normal file
@@ -0,0 +1,510 @@
|
||||
--- **AI** - Models the intelligent transportation of cargo using airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Airplane
|
||||
-- @image AI_Cargo_Dispatching_For_Airplanes.JPG
|
||||
|
||||
--- @type AI_CARGO_AIRPLANE
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI airplane group.
|
||||
--
|
||||
-- Airplane carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation between airbases.
|
||||
--
|
||||
-- The AI_CARGO_AIRPLANE module uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission to make AI_CARGO_AIRPLANE recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_AIRPLANE.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate.
|
||||
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_AIRPLANE.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
|
||||
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
|
||||
--
|
||||
-- ## Infantry health.
|
||||
--
|
||||
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
|
||||
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
|
||||
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
|
||||
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
|
||||
-- time is not so much of an issue ...
|
||||
--
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- @field #AI_CARGO_AIRPLANE
|
||||
AI_CARGO_AIRPLANE = {
|
||||
ClassName = "AI_CARGO_AIRPLANE",
|
||||
Coordinate = nil, -- Core.Point#COORDINATE
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_AIRPLANE object.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Plane used for transportation of cargo.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Cargo set to be transported.
|
||||
-- @return #AI_CARGO_AIRPLANE
|
||||
function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Airplane, CargoSet ) ) -- #AI_CARGO_AIRPLANE
|
||||
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Home" , "*" )
|
||||
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up.
|
||||
-- @param #number Speed in km/h for travelling to pickup base.
|
||||
-- @return #boolean
|
||||
|
||||
--- Pickup Handler OnAfter for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] Pickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] __Pickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Deploy Handler OnBefore for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed.
|
||||
-- @param #number Speed Speed in km/h for travelling to deploy base.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] Deploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] __Deploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- On after Loaded event, i.e. triggered when the cargo is inside the carrier.
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterLoaded
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeployed
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed.
|
||||
|
||||
-- Set carrier.
|
||||
self:SetCarrier( Airplane )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Carrier (controllable). Also initializes events for carrier and defines the coalition.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Transport plane.
|
||||
-- @return #AI_CARGO_AIRPLANE self
|
||||
function AI_CARGO_AIRPLANE:SetCarrier( Airplane )
|
||||
|
||||
local AICargo = self
|
||||
|
||||
self.Airplane = Airplane -- Wrapper.Group#GROUP
|
||||
self.Airplane:SetState( self.Airplane, "AI_CARGO_AIRPLANE", self )
|
||||
|
||||
self.RoutePickup = false
|
||||
self.RouteDeploy = false
|
||||
|
||||
Airplane:HandleEvent( EVENTS.Dead )
|
||||
Airplane:HandleEvent( EVENTS.Hit )
|
||||
Airplane:HandleEvent( EVENTS.EngineShutdown )
|
||||
|
||||
function Airplane:OnEventDead( EventData )
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- There are enemies within combat range. Unload the Airplane.
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Airplane:OnEventHit( EventData )
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" )
|
||||
if AICargoTroops then
|
||||
self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
|
||||
if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
|
||||
-- There are enemies within combat range. Unload the Airplane.
|
||||
AICargoTroops:Unload()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Airplane:OnEventEngineShutdown( EventData )
|
||||
AICargo.Relocating = false
|
||||
AICargo:Landed( self.Airplane )
|
||||
end
|
||||
|
||||
self.Coalition = self.Airplane:GetCoalition()
|
||||
|
||||
self:SetControllable( Airplane )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Find a free Carrier within a range.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase
|
||||
-- @param #number Radius
|
||||
-- @return Wrapper.Group#GROUP NewCarrier
|
||||
function AI_CARGO_AIRPLANE:FindCarrier( Coordinate, Radius )
|
||||
|
||||
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
|
||||
CoordinateZone:Scan( { Object.Category.UNIT } )
|
||||
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
|
||||
local NearUnit = UNIT:Find( DCSUnit )
|
||||
self:F({NearUnit=NearUnit})
|
||||
if not NearUnit:GetState( NearUnit, "AI_CARGO_AIRPLANE" ) then
|
||||
local Attributes = NearUnit:GetDesc()
|
||||
self:F({Desc=Attributes})
|
||||
if NearUnit:HasAttribute( "Trucks" ) then
|
||||
self:SetCarrier( NearUnit )
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after "Landed" event. Called on engine shutdown and initiates the pickup mission or unloading event.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To )
|
||||
|
||||
self:F({Airplane, From, Event, To})
|
||||
|
||||
if Airplane and Airplane:IsAlive()~=nil then
|
||||
|
||||
-- Aircraft was sent to this airbase to pickup troops. Initiate loadling.
|
||||
if self.RoutePickup == true then
|
||||
self:Load( self.PickupZone )
|
||||
end
|
||||
|
||||
-- Aircraft was send to this airbase to deploy troops. Initiate unloading.
|
||||
if self.RouteDeploy == true then
|
||||
self:Unload()
|
||||
self.RouteDeploy = false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after "Pickup" event. Routes transport to pickup airbase.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
|
||||
local airbasepickup=Coordinate:GetClosestAirbase()
|
||||
|
||||
self.PickupZone = PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName())
|
||||
|
||||
-- Get closest airbase of current position.
|
||||
local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase()
|
||||
|
||||
-- Two cases. Aircraft spawned in air or at an airbase.
|
||||
if Airplane:InAir() then
|
||||
self.Airbase=nil --> route will start in air
|
||||
else
|
||||
self.Airbase=ClosestAirbase
|
||||
end
|
||||
|
||||
-- Set pickup airbase.
|
||||
local Airbase = self.PickupZone:GetAirbase()
|
||||
|
||||
-- Distance from closest to pickup airbase ==> we need to know if we are already at the pickup airbase.
|
||||
local Dist = Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate())
|
||||
|
||||
if Airplane:InAir() or Dist>500 then
|
||||
|
||||
-- Route aircraft to pickup airbase.
|
||||
self:Route( Airplane, Airbase, Speed, Height )
|
||||
|
||||
-- Set airbase as starting point in the next Route() call.
|
||||
self.Airbase = Airbase
|
||||
|
||||
-- Aircraft is on a pickup mission.
|
||||
self.RoutePickup = true
|
||||
|
||||
else
|
||||
|
||||
-- We are already at the right airbase ==> Landed ==> triggers loading of troops. Is usually called at engine shutdown event.
|
||||
self.RoutePickup=true
|
||||
self:Landed()
|
||||
|
||||
end
|
||||
|
||||
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, self.PickupZone )
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- On after Depoly event. Routes plane to the airbase where the troops are deployed.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Airplane and Airplane:IsAlive()~=nil then
|
||||
|
||||
local Airbase = Coordinate:GetClosestAirbase()
|
||||
|
||||
if DeployZone then
|
||||
Airbase=DeployZone:GetAirbase()
|
||||
end
|
||||
|
||||
-- Activate uncontrolled airplane.
|
||||
if Airplane:IsAlive()==false then
|
||||
Airplane:SetCommand({id = 'Start', params = {}})
|
||||
end
|
||||
|
||||
-- Route to destination airbase.
|
||||
self:Route( Airplane, Airbase, Speed, Height )
|
||||
|
||||
-- Aircraft is on a depoly mission.
|
||||
self.RouteDeploy = true
|
||||
|
||||
-- Set destination airbase for next :Route() command.
|
||||
self.Airbase = Airbase
|
||||
|
||||
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterDeploy( self, Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Unload event. Cargo is beeing unloaded, i.e. the unboarding process is started.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone )
|
||||
|
||||
local UnboardInterval = 10
|
||||
local UnboardDelay = 10
|
||||
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
for _, AirplaneUnit in pairs( Airplane:GetUnits() ) do
|
||||
local Cargos = AirplaneUnit:GetCargo()
|
||||
for CargoID, Cargo in pairs( Cargos ) do
|
||||
|
||||
local Angle = 180
|
||||
local CargoCarrierHeading = Airplane:GetHeading() -- Get Heading of object in degrees.
|
||||
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
|
||||
self:T( { CargoCarrierHeading, CargoDeployHeading } )
|
||||
local CargoDeployCoordinate = Airplane:GetPointVec2():Translate( 150, CargoDeployHeading )
|
||||
|
||||
Cargo:__UnBoard( UnboardDelay, CargoDeployCoordinate )
|
||||
UnboardDelay = UnboardDelay + UnboardInterval
|
||||
Cargo:SetDeployed( true )
|
||||
self:__Unboard( UnboardDelay, Cargo, AirplaneUnit, DeployZone )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Route the airplane from one airport or it's current position to another airbase.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed.
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase.
|
||||
-- @param #number Speed Speed in km/h. Default is 80% of max possible speed the group can do.
|
||||
-- @param #number Height Height in meters to move to the Airbase.
|
||||
-- @param #boolean Uncontrolled If true, spawn group in uncontrolled state.
|
||||
function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled )
|
||||
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
|
||||
-- Set takeoff type.
|
||||
local Takeoff = SPAWN.Takeoff.Cold
|
||||
|
||||
-- Get template of group.
|
||||
local Template = Airplane:GetTemplate()
|
||||
|
||||
-- Nil check
|
||||
if Template==nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Waypoints of the route.
|
||||
local Points={}
|
||||
|
||||
-- To point.
|
||||
local AirbasePointVec2 = Airbase:GetPointVec2()
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase)
|
||||
|
||||
--ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
--ToWaypoint["speed_locked"] = true
|
||||
|
||||
|
||||
-- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned.
|
||||
if self.Airbase then
|
||||
|
||||
-- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine.
|
||||
Template.route.points[2] = ToWaypoint
|
||||
|
||||
-- Respawn group at the current airbase.
|
||||
Airplane:RespawnAtCurrentAirbase(Template, Takeoff, Uncontrolled)
|
||||
|
||||
else
|
||||
|
||||
-- From point.
|
||||
local GroupPoint = Airplane:GetVec2()
|
||||
local FromWaypoint = {}
|
||||
FromWaypoint.x = GroupPoint.x
|
||||
FromWaypoint.y = GroupPoint.y
|
||||
FromWaypoint.type = "Turning Point"
|
||||
FromWaypoint.action = "Turning Point"
|
||||
FromWaypoint.speed = Airplane:GetSpeedMax()*0.8
|
||||
|
||||
-- The two route points.
|
||||
Points[1] = FromWaypoint
|
||||
Points[2] = ToWaypoint
|
||||
|
||||
local PointVec3 = Airplane:GetPointVec3()
|
||||
Template.x = PointVec3.x
|
||||
Template.y = PointVec3.z
|
||||
|
||||
Template.route.points = Points
|
||||
|
||||
local GroupSpawned = Airplane:Respawn(Template)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Home event. Aircraft will be routed to their home base.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane The cargo plane.
|
||||
-- @param From From state.
|
||||
-- @param Event Event.
|
||||
-- @param To To State.
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place (not used).
|
||||
-- @param #number Speed Speed in km/h to fly to the home airbase (zone). Default is 80% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE HomeZone The home airbase (zone) where the plane should return to.
|
||||
function AI_CARGO_AIRPLANE:onafterHome(Airplane, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
|
||||
-- We are going home!
|
||||
self.RouteHome = true
|
||||
|
||||
-- Home Base.
|
||||
local HomeBase=HomeZone:GetAirbase()
|
||||
self.Airbase=HomeBase
|
||||
|
||||
-- Now route the airplane home
|
||||
self:Route( Airplane, HomeBase, Speed, Height )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
1238
Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua
Normal file
1238
Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua
Normal file
File diff suppressed because it is too large
Load Diff
263
Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua
Normal file
263
Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua
Normal file
@@ -0,0 +1,263 @@
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Quickly transport cargo to various deploy zones using ground vehicles (APCs, trucks ...).
|
||||
-- * Various @{Cargo.Cargo#CARGO} types can be transported. These are infantry groups and crates.
|
||||
-- * Define a list of deploy zones of various types to transport the cargo to.
|
||||
-- * The vehicles follow the roads to ensure the fastest possible cargo transportation over the ground.
|
||||
-- * Multiple vehicles can transport multiple cargo as one vehicle group.
|
||||
-- * Multiple vehicle groups can be enabled as one collaborating transportation process.
|
||||
-- * Infantry loaded as cargo, will unboard in case enemies are nearby and will help defending the vehicles.
|
||||
-- * Different ranges can be setup for enemy defenses.
|
||||
-- * Different options can be setup to tweak the cargo transporation behaviour.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- Test missions can be located on the main GITHUB site.
|
||||
--
|
||||
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
|
||||
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_APC
|
||||
-- @image AI_Cargo_Dispatching_For_APC.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_APC
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo transportation capability for AI groups.
|
||||
--
|
||||
-- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_APC module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_APC class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
|
||||
--
|
||||
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
|
||||
--
|
||||
-- On top, the AI_CARGO_DISPATCHER_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_APC constructor.
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.New}(): Creates a new AI_CARGO_DISPATCHER_APC object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_APC is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 3) Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.SetPickupRadius}(): Sets or randomizes the pickup location for the APC around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
--
|
||||
-- # 4) Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.SetDeployRadius}(): Sets or randomizes the deploy location for the APC around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_APC.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
--
|
||||
-- # 5) Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the APCs will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_APC.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the APCs will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_APC
|
||||
AI_CARGO_DISPATCHER_APC = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_APC",
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_APC object.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param Core.Set#SET_GROUP APCSet The set of @{Wrapper.Group#GROUP} objects of vehicles, trucks, APCs that will transport the cargo.
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the APCs.
|
||||
-- @param DCS#Distance CombatRadius The cargo will be unloaded from the APC and engage the enemy if the enemy is within CombatRadius range. The radius is in meters, the default value is 500 meters.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones.
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones )
|
||||
-- AICargoDispatcherAPC:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet, CombatRadius )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_APC
|
||||
|
||||
self:SetDeploySpeed( 120, 70 )
|
||||
self:SetPickupSpeed( 120, 70 )
|
||||
self:SetPickupRadius( 0, 0 )
|
||||
self:SetDeployRadius( 0, 0 )
|
||||
|
||||
self:SetPickupHeight()
|
||||
self:SetDeployHeight()
|
||||
|
||||
self:SetCombatRadius( CombatRadius )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- AI cargo
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param Wrapper.Group#GROUP APC The APC carrier.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Cargo set.
|
||||
-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object.
|
||||
function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet )
|
||||
|
||||
local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius)
|
||||
|
||||
aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation)
|
||||
aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation)
|
||||
|
||||
return aicargoapc
|
||||
end
|
||||
|
||||
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
|
||||
-- This is only valid for APCs and trucks etc, thus ground vehicles.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
|
||||
-- When the combat radius is 0 (default), no defense will happen of the carrier.
|
||||
-- When the combat radius is not provided, no defense will happen!
|
||||
-- @return #AI_CARGO_DISPATCHER_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- Disembark the infantry when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( 500 )
|
||||
--
|
||||
-- -- Keep the cargo in the carrier when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( 0 )
|
||||
function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
169
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua
Normal file
169
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua
Normal file
@@ -0,0 +1,169 @@
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using Planes.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * The airplanes will fly towards the pickup airbases to pickup the cargo.
|
||||
-- * The airplanes will fly towards the deploy airbases to deploy the cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- Test missions can be located on the main GITHUB site.
|
||||
--
|
||||
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
|
||||
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Airplane
|
||||
-- @image AI_Cargo_Dispatching_For_Airplanes.JPG
|
||||
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_AIRPLANE
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- Brings a dynamic cargo handling capability for AI groups.
|
||||
--
|
||||
-- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_AIRPLANE module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_AIRPLANE class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!**
|
||||
--
|
||||
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
|
||||
--
|
||||
-- On top, the AI_CARGO_DISPATCHER_AIRPLANE class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_AIRPLANE constructor.
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_AIRPLANE.New}(): Creates a new AI_CARGO_DISPATCHER_AIRPLANE object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_AIRPLANE is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_AIRPLANE
|
||||
AI_CARGO_DISPATCHER_AIRPLANE = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_AIRPLANE",
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_AIRPLANE object.
|
||||
-- @param #AI_CARGO_DISPATCHER_AIRPLANE self
|
||||
-- @param Core.Set#SET_GROUP AirplaneSet The set of @{Wrapper.Group#GROUP} objects of airplanes that will transport the cargo.
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Zone#SET_ZONE PickupZoneSet The set of zone airbases where the cargo has to be picked up.
|
||||
-- @param Core.Zone#SET_ZONE DeployZoneSet The set of zone airbases where the cargo is deployed. Choice for each cargo is random.
|
||||
-- @return #AI_CARGO_DISPATCHER_AIRPLANE self
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases.
|
||||
--
|
||||
-- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart()
|
||||
-- local PickupZoneSet = SET_ZONE:New()
|
||||
-- local DeployZoneSet = SET_ZONE:New()
|
||||
--
|
||||
-- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) )
|
||||
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) )
|
||||
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) )
|
||||
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) )
|
||||
-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) )
|
||||
--
|
||||
-- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet )
|
||||
-- AICargoDispatcherAirplanes:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_AIRPLANE:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE
|
||||
|
||||
self:SetPickupSpeed( 1200, 600 )
|
||||
self:SetDeploySpeed( 1200, 600 )
|
||||
|
||||
self:SetPickupRadius( 0, 0 )
|
||||
self:SetDeployRadius( 0, 0 )
|
||||
|
||||
self:SetPickupHeight( 8000, 6000 )
|
||||
self:SetDeployHeight( 8000, 6000 )
|
||||
|
||||
self:SetMonitorTimeInterval( 600 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_AIRPLANE:AICargo( Airplane, CargoSet )
|
||||
|
||||
return AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
|
||||
end
|
||||
199
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua
Normal file
199
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua
Normal file
@@ -0,0 +1,199 @@
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using Helicopters.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * The helicopters will fly towards the pickup locations to pickup the cargo.
|
||||
-- * The helicopters will fly towards the deploy zones to deploy the cargo.
|
||||
-- * Precision deployment as well as randomized deployment within the deploy zones are possible.
|
||||
-- * Helicopters will orbit the deploy zones when there is no space for landing until the deploy zone is free.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- Test missions can be located on the main GITHUB site.
|
||||
--
|
||||
-- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/]
|
||||
-- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Helicopter
|
||||
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_HELICOPTER
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo handling capability for AI helicopter groups.
|
||||
--
|
||||
-- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
--
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_HELICOPTER module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_HELICOPTER class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!**
|
||||
--
|
||||
-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful!
|
||||
--
|
||||
-- On top, the AI_CARGO_DISPATCHER_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 1. AI\_CARGO\_DISPATCHER\_HELICOPTER constructor.
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.New}(): Creates a new AI\_CARGO\_DISPATCHER\_HELICOPTER object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2. AI\_CARGO\_DISPATCHER\_HELICOPTER is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## 3. Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupRadius}(): Sets or randomizes the pickup location for the helicopter around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupHeight}(): Set the height or randomizes the height in meters to pickup the cargo.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## 4. Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployRadius}(): Sets or randomizes the deploy location for the helicopter around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
-- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployHeight}(): Set the height or randomizes the height in meters to deploy the cargo.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- ## 5. Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the Helicopters will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_HELICOPTER.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the helicopters will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_HELICOPTER
|
||||
AI_CARGO_DISPATCHER_HELICOPTER = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_HELICOPTER",
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_HELICOPTER object.
|
||||
-- @param #AI_CARGO_DISPATCHER_HELICOPTER self
|
||||
-- @param Core.Set#SET_GROUP HelicopterSet The set of @{Wrapper.Group#GROUP} objects of helicopters that will transport the cargo.
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the Helicopters.
|
||||
-- @return #AI_CARGO_DISPATCHER_HELICOPTER
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones.
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart()
|
||||
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones )
|
||||
-- AICargoDispatcherHelicopter:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER
|
||||
|
||||
self:SetPickupSpeed( 350, 150 )
|
||||
self:SetDeploySpeed( 350, 150 )
|
||||
|
||||
self:SetPickupRadius( 40, 12 )
|
||||
self:SetDeployRadius( 40, 12 )
|
||||
|
||||
self:SetPickupHeight( 500, 200 )
|
||||
self:SetDeployHeight( 500, 200 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
|
||||
|
||||
local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
dispatcher:SetLandingSpeedAndHeight(27, 6)
|
||||
return dispatcher
|
||||
|
||||
end
|
||||
|
||||
198
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua
Normal file
198
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua
Normal file
@@ -0,0 +1,198 @@
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using Ships.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Transport cargo to various deploy zones using naval vehicles.
|
||||
-- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates.
|
||||
-- * Define a deploy zone of various types to determine the destination of the cargo.
|
||||
-- * Ships will follow shipping lanes as defined in the Mission Editor.
|
||||
-- * Multiple ships can transport multiple cargo as a single group.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- NEED TO DO
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_SHIP
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo transportation capability for AI groups.
|
||||
--
|
||||
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
|
||||
--
|
||||
-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_SHIP constructor.
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 3) Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
--
|
||||
-- # 4) Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
--
|
||||
-- # 5) Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_SHIP
|
||||
AI_CARGO_DISPATCHER_SHIP = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_SHIP"
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
-- @param #AI_CARGO_DISPATCHER_SHIP self
|
||||
-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_DISPATCHER_SHIP
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart()
|
||||
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
-- NEED MORE THOUGHT - ShippingLane is part of Warehouse.......
|
||||
-- local ShippingLane = GROUP:New():FilterPrefixes( "ShippingLane" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane )
|
||||
-- AICargoDispatcherShip:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) )
|
||||
|
||||
self:SetPickupSpeed( 60, 10 )
|
||||
self:SetDeploySpeed( 60, 10 )
|
||||
|
||||
self:SetPickupRadius( 500, 6000 )
|
||||
self:SetDeployRadius( 500, 6000 )
|
||||
|
||||
self:SetPickupHeight( 0, 0 )
|
||||
self:SetDeployHeight( 0, 0 )
|
||||
|
||||
self:SetShippingLane( ShippingLane )
|
||||
|
||||
self:SetMonitorTimeInterval( 600 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet )
|
||||
|
||||
return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane )
|
||||
end
|
||||
661
Moose Development/Moose/AI/AI_Cargo_Helicopter.lua
Normal file
661
Moose Development/Moose/AI/AI_Cargo_Helicopter.lua
Normal file
@@ -0,0 +1,661 @@
|
||||
--- **AI** - Models the intelligent transportation of cargo using helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Helicopter
|
||||
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
|
||||
|
||||
--- @type AI_CARGO_HELICOPTER
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI helicopter group.
|
||||
--
|
||||
-- Helicopter carriers can be mobilized to intelligently transport infantry and other cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_HELICOPTER object recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_HELICOPTER.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate.
|
||||
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_HELICOPTER.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
|
||||
-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash!
|
||||
--
|
||||
-- ## Infantry health.
|
||||
--
|
||||
-- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
|
||||
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
|
||||
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
|
||||
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
|
||||
-- time is not so much of an issue ...
|
||||
--
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_HELICOPTER
|
||||
AI_CARGO_HELICOPTER = {
|
||||
ClassName = "AI_CARGO_HELICOPTER",
|
||||
Coordinate = nil, -- Core.Point#COORDINATE,
|
||||
}
|
||||
|
||||
AI_CARGO_QUEUE = {}
|
||||
|
||||
--- Creates a new AI_CARGO_HELICOPTER object.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param Core.Set#SET_CARGO CargoSet
|
||||
-- @return #AI_CARGO_HELICOPTER
|
||||
function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Helicopter, CargoSet ) ) -- #AI_CARGO_HELICOPTER
|
||||
|
||||
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
-- Boarding
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
-- Unboarding
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
self:AddTransition( "*", "Destroyed", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
-- RTB
|
||||
self:AddTransition( "*", "Home" , "*" )
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @return #boolean
|
||||
|
||||
--- Pickup Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object.
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to go to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deploy Handler OnBefore for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforeDeploy
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeploy
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deployed Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Home Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Home
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
--- Home Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Home
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
-- We need to capture the Crash events for the helicopters.
|
||||
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
|
||||
-- So, we need to unlock this when the helo is not anymore ...
|
||||
Helicopter:HandleEvent( EVENTS.Crash,
|
||||
function( Helicopter, EventData )
|
||||
AI_CARGO_QUEUE[Helicopter] = nil
|
||||
end
|
||||
)
|
||||
|
||||
-- We need to capture the Land events for the helicopters.
|
||||
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
|
||||
-- So, we need to unlock this when the helo has landed, which can be anywhere ...
|
||||
-- But only free the landing coordinate after 1 minute, to ensure that all helos have left.
|
||||
Helicopter:HandleEvent( EVENTS.Land,
|
||||
function( Helicopter, EventData )
|
||||
self:ScheduleOnce( 60,
|
||||
function( Helicopter )
|
||||
AI_CARGO_QUEUE[Helicopter] = nil
|
||||
end, Helicopter
|
||||
)
|
||||
end
|
||||
)
|
||||
|
||||
self:SetCarrier( Helicopter )
|
||||
|
||||
self.landingspeed = 15 -- kph
|
||||
self.landingheight = 5.5 -- meter
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--- Set the Carrier.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @return #AI_CARGO_HELICOPTER
|
||||
function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
|
||||
|
||||
local AICargo = self
|
||||
|
||||
self.Helicopter = Helicopter -- Wrapper.Group#GROUP
|
||||
self.Helicopter:SetState( self.Helicopter, "AI_CARGO_HELICOPTER", self )
|
||||
|
||||
self.RoutePickup = false
|
||||
self.RouteDeploy = false
|
||||
|
||||
Helicopter:HandleEvent( EVENTS.Dead )
|
||||
Helicopter:HandleEvent( EVENTS.Hit )
|
||||
Helicopter:HandleEvent( EVENTS.Land )
|
||||
|
||||
function Helicopter:OnEventDead( EventData )
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_HELICOPTER" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- There are enemies within combat range. Unload the Helicopter.
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Helicopter:OnEventLand( EventData )
|
||||
AICargo:Landed()
|
||||
end
|
||||
|
||||
self.Coalition = self.Helicopter:GetCoalition()
|
||||
|
||||
self:SetControllable( Helicopter )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #number speed Landing speed in kph(!), e.g. 15
|
||||
-- @param #number height Landing height in meters(!), e.g. 5.5
|
||||
-- @return #AI_CARGO_HELICOPTER self
|
||||
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
|
||||
--
|
||||
-- BASE:TraceOn()
|
||||
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
|
||||
--
|
||||
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
|
||||
-- Adjust if necessary.
|
||||
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
|
||||
local _speed = speed or 15
|
||||
local _height = height or 5.5
|
||||
self.landingheight = _height
|
||||
self.landingspeed = _speed
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
self:F({From, Event, To})
|
||||
Helicopter:F( { Name = Helicopter:GetName() } )
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
|
||||
-- S_EVENT_LAND is directly called in two situations:
|
||||
-- 1 - When the helo lands normally on the ground.
|
||||
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
|
||||
-- For point 2, this is an issue, the infantry may not unload in this case!
|
||||
-- So we check if the helo is on the ground, and velocity< 15.
|
||||
-- Only then the infantry can unload (and load too, for consistency)!
|
||||
|
||||
self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
|
||||
if self.RoutePickup == true then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
--self:Load( Helicopter:GetPointVec2() )
|
||||
self:Load( self.PickupZone )
|
||||
self.RoutePickup = false
|
||||
end
|
||||
end
|
||||
|
||||
if self.RouteDeploy == true then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
self:Unload( self.DeployZone )
|
||||
self.RouteDeploy = false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
|
||||
self:F({From, Event, To, Coordinate, Speed, DeployZone})
|
||||
local HelicopterInZone = false
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() == true then
|
||||
|
||||
local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() )
|
||||
|
||||
if Distance > 2000 then
|
||||
self:__Queue( -10, Coordinate, Speed, DeployZone )
|
||||
else
|
||||
|
||||
local ZoneFree = true
|
||||
|
||||
for Helicopter, ZoneQueue in pairs( AI_CARGO_QUEUE ) do
|
||||
local ZoneQueue = ZoneQueue -- Core.Zone#ZONE_RADIUS
|
||||
if ZoneQueue:IsCoordinateInZone( Coordinate ) then
|
||||
ZoneFree = false
|
||||
end
|
||||
end
|
||||
|
||||
self:F({ZoneFree=ZoneFree})
|
||||
|
||||
if ZoneFree == true then
|
||||
|
||||
local ZoneQueue = ZONE_RADIUS:New( Helicopter:GetName(), Coordinate:GetVec2(), 100 )
|
||||
|
||||
AI_CARGO_QUEUE[Helicopter] = ZoneQueue
|
||||
|
||||
local Route = {}
|
||||
|
||||
-- local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
-- local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
-- "RADIO",
|
||||
-- POINT_VEC3.RoutePointType.TurningPoint,
|
||||
-- POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
-- Speed,
|
||||
-- true
|
||||
-- )
|
||||
-- Route[#Route+1] = WaypointFrom
|
||||
local CoordinateTo = Coordinate
|
||||
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
50,
|
||||
true
|
||||
)
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
|
||||
-- Keep the DeployZone, because when the helo has landed, we want to provide the DeployZone to the mission designer as part of the Unloaded event.
|
||||
self.DeployZone = DeployZone
|
||||
|
||||
else
|
||||
self:__Queue( -10, Coordinate, Speed, DeployZone )
|
||||
end
|
||||
end
|
||||
else
|
||||
AI_CARGO_QUEUE[Helicopter] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
|
||||
self:F({From, Event, To, Coordinate})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
|
||||
local Route = {}
|
||||
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true)
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 80 ), 150, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Cargo.Cargo#CARGO Cargo Cargo object.
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @return #boolean True if all cargo has been unloaded.
|
||||
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
|
||||
self:F( { From, Event, To, DeployZone = DeployZone } )
|
||||
|
||||
self:Orbit( Helicopter:GetCoordinate(), 50 )
|
||||
|
||||
-- Free the coordinate zone after 30 seconds, so that the original helicopter can fly away first.
|
||||
self:ScheduleOnce( 30,
|
||||
function( Helicopter )
|
||||
AI_CARGO_QUEUE[Helicopter] = nil
|
||||
end, Helicopter
|
||||
)
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone )
|
||||
|
||||
end
|
||||
|
||||
--- On after Pickup event.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Pickup place.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
self:F({Coordinate, Speed, Height, PickupZone })
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
Helicopter:Activate()
|
||||
|
||||
self.RoutePickup = true
|
||||
Coordinate.y = Height
|
||||
|
||||
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
local Route = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
|
||||
--- Create a route point of type air.
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
Helicopter:WayPointInitialize( Route )
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 1 )
|
||||
|
||||
self.PickupZone = PickupZone
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterPickup( self, Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Depoloy function and queue.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP AICargoHelicopter
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate
|
||||
function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate, DeployZone )
|
||||
AICargoHelicopter:__Queue( -10, Coordinate, 100, DeployZone )
|
||||
end
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter Transport helicopter.
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate.
|
||||
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteDeploy = true
|
||||
|
||||
|
||||
local Route = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
Coordinate.y = Height
|
||||
|
||||
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
Helicopter:WayPointInitialize( Route )
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
-- The _Deploy function does not exist.
|
||||
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
|
||||
|
||||
--Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeploy( self, Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- On after Home event.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
self:F({From, Event, To, Coordinate, Speed, Height})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteHome = true
|
||||
|
||||
local Route = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
--Coordinate.y = Height
|
||||
Height = Height or 50
|
||||
|
||||
Speed = Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
Helicopter:WayPointInitialize( Route )
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
402
Moose Development/Moose/AI/AI_Cargo_Ship.lua
Normal file
402
Moose Development/Moose/AI/AI_Cargo_Ship.lua
Normal file
@@ -0,0 +1,402 @@
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_SHIP
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI naval group.
|
||||
--
|
||||
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
|
||||
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo loading.
|
||||
--
|
||||
-- The module will automatically load cargo when the Ship is within boarding or loading radius.
|
||||
-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of
|
||||
-- cargo and the specified boarding radius.
|
||||
--
|
||||
-- ## Defending the Ship when enemies are nearby
|
||||
-- This is not supported for naval cargo because most tanks don't float. Protect your transports...
|
||||
--
|
||||
-- ## Infantry or cargo **health**.
|
||||
-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding cargo is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter.
|
||||
-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for
|
||||
-- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed.
|
||||
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance
|
||||
-- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat
|
||||
-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting
|
||||
-- between warehouses.
|
||||
--
|
||||
-- ## Control the Ships on the map.
|
||||
--
|
||||
-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission
|
||||
-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module.
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the
|
||||
-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{#AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified
|
||||
-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- @field #AI_CARGO_SHIP
|
||||
AI_CARGO_SHIP = {
|
||||
ClassName = "AI_CARGO_SHIP",
|
||||
Coordinate = nil -- Core.Point#COORDINATE
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_SHIP object.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship The carrier Ship group
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP
|
||||
|
||||
self:AddTransition( "*", "Monitor", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
|
||||
self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0
|
||||
self:SetShippingLane ( ShippingLane )
|
||||
|
||||
self:SetCarrier( Ship )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:SetCarrier( CargoCarrier )
|
||||
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP
|
||||
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self )
|
||||
|
||||
CargoCarrier:HandleEvent( EVENTS.Dead )
|
||||
|
||||
function CargoCarrier:OnEventDead( EventData )
|
||||
self:F({"dead"})
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- Better hope they can swim!
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
|
||||
self.Coalition = self.CargoCarrier:GetCoalition()
|
||||
|
||||
self:SetControllable( CargoCarrier )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- FInd a free Carrier within a radius
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Radius
|
||||
-- @return Wrapper.Group#GROUP NewCarrier
|
||||
function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius )
|
||||
|
||||
local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius )
|
||||
CoordinateZone:Scan( { Object.Category.UNIT } )
|
||||
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
|
||||
local NearUnit = UNIT:Find( DCSUnit )
|
||||
self:F({NearUnit=NearUnit})
|
||||
if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then
|
||||
local Attributes = NearUnit:GetDesc()
|
||||
self:F({Desc=Attributes})
|
||||
if NearUnit:HasAttributes( "Trucks" ) then
|
||||
return NearUnit:GetGroup()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetCombatRadius( CombatRadius )
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Follow Infantry to the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param #AI_CARGO_SHIP Me
|
||||
-- @param Wrapper.Unit#UNIT ShipUnit
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup )
|
||||
|
||||
local InfantryGroup = CargoGroup:GetGroup()
|
||||
|
||||
self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
if ShipUnit:IsAlive() then
|
||||
-- Check if the Cargo is near the CargoCarrier
|
||||
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then
|
||||
|
||||
-- Cargo does not need to navigate to Carrier
|
||||
Me:Guard()
|
||||
else
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
if InfantryGroup:IsAlive() then
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
local Waypoints = {}
|
||||
|
||||
-- Calculate new route
|
||||
local FromCoord = InfantryGroup:GetCoordinate()
|
||||
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({FromGround=FromGround})
|
||||
table.insert( Waypoints, FromGround )
|
||||
|
||||
local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
|
||||
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({ToGround=ToGround})
|
||||
table.insert( Waypoints, ToGround )
|
||||
|
||||
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup )
|
||||
|
||||
self:F({Waypoints=Waypoints})
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone
|
||||
|
||||
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To )
|
||||
self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } )
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
-- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0.
|
||||
-- This is to avoid Unloading the Ship in the middle of the sea.
|
||||
if Ship and Ship:IsAlive() then
|
||||
if self.CarrierCoordinate then
|
||||
if self:IsTransporting() == true then
|
||||
local Coordinate = Ship:GetCoordinate()
|
||||
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
if self:Is( "Unloaded" ) then
|
||||
-- There are no enemies within combat radius. Reload the CargoCarrier.
|
||||
self:Reload()
|
||||
end
|
||||
else
|
||||
if self:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
|
||||
else
|
||||
if self:Is( "Unloaded" ) then
|
||||
--self:Follow()
|
||||
end
|
||||
self:F( "I am here" .. self:GetCurrentState() )
|
||||
if self:Is( "Following" ) then
|
||||
for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
if Cargo:IsAlive() then
|
||||
if not Cargo:IsNear( ShipUnit, 40 ) then
|
||||
ShipUnit:RouteStop()
|
||||
self.CarrierStopped = true
|
||||
else
|
||||
if self.CarrierStopped then
|
||||
if Cargo:IsNear( ShipUnit, 25 ) then
|
||||
ShipUnit:RouteResume()
|
||||
self.CarrierStopped = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.CarrierCoordinate = Ship:GetCoordinate()
|
||||
end
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Load event
|
||||
-- @param Wrapper.Group#Group Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
|
||||
Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Load( PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone )
|
||||
Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Unload( DeployZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- on after Pickup event.
|
||||
-- @param AI_CARGO_SHIP Ship
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point
|
||||
-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided
|
||||
function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP SHIP
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point
|
||||
-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
for i=1, #lane do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone )
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:SetTaskWaypoint( Waypoint, TaskFunction )
|
||||
Ship:Route(Waypoints, 1)
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Unload event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } )
|
||||
|
||||
local UnboardInterval = 5
|
||||
local UnboardDelay = 5
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
for _, ShipUnit in pairs( Ship:GetUnits() ) do
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
Ship:RouteStop()
|
||||
for _, Cargo in pairs( ShipUnit:GetCargo() ) do
|
||||
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
|
||||
if Cargo:IsLoaded() then
|
||||
local unboardCoord = DeployZone:GetRandomPointVec2()
|
||||
Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000)
|
||||
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
|
||||
self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend )
|
||||
if not Defend == true then
|
||||
Cargo:SetDeployed( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
self.RouteHome = true
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
-- Need to find a more generalized way to do this instead of reversing the shipping lane.
|
||||
-- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1
|
||||
for i=#lane, 1, -1 do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:Route(Waypoints, 1)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
2191
Moose Development/Moose/AI/AI_Escort.lua
Normal file
2191
Moose Development/Moose/AI/AI_Escort.lua
Normal file
File diff suppressed because it is too large
Load Diff
190
Moose Development/Moose/AI/AI_Escort_Dispatcher.lua
Normal file
190
Moose Development/Moose/AI/AI_Escort_Dispatcher.lua
Normal file
@@ -0,0 +1,190 @@
|
||||
--- **AI** - Models the automatic assignment of AI escorts to player flights.
|
||||
--
|
||||
-- ## Features:
|
||||
-- --
|
||||
-- * Provides the facilities to trigger escorts when players join flight slots.
|
||||
-- *
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Escort_Dispatcher
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
--- @type AI_ESCORT_DISPATCHER
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models the automatic assignment of AI escorts to player flights.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_ESCORT_DISPATCHER
|
||||
AI_ESCORT_DISPATCHER = {
|
||||
ClassName = "AI_ESCORT_DISPATCHER",
|
||||
}
|
||||
|
||||
--- @field #list
|
||||
AI_ESCORT_DISPATCHER.AI_Escorts = {}
|
||||
|
||||
|
||||
--- Creates a new AI_ESCORT_DISPATCHER object.
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are spawned in.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned.
|
||||
-- @param #string EscortName Name of the escort, which will also be the name of the escort menu.
|
||||
-- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Create a new escort when a player joins an SU-25T plane.
|
||||
-- Create a carrier set, which contains the player slots that can be joined by the players, for which escorts will be defined.
|
||||
-- local Red_SU25T_CarrierSet = SET_GROUP:New():FilterPrefixes( "Red A2G Player Su-25T" ):FilterStart()
|
||||
--
|
||||
-- -- Create a spawn object that will spawn in the escorts, once the player has joined the player slot.
|
||||
-- local Red_SU25T_EscortSpawn = SPAWN:NewWithAlias( "Red A2G Su-25 Escort", "Red AI A2G SU-25 Escort" ):InitLimit( 10, 10 )
|
||||
--
|
||||
-- -- Create an airbase object, where the escorts will be spawned.
|
||||
-- local Red_SU25T_Airbase = AIRBASE:FindByName( AIRBASE.Caucasus.Maykop_Khanskaya )
|
||||
--
|
||||
-- -- Park the airplanes at the airbase, visible before start.
|
||||
-- Red_SU25T_EscortSpawn:ParkAtAirbase( Red_SU25T_Airbase, AIRBASE.TerminalType.OpenMedOrBig )
|
||||
--
|
||||
-- -- New create the escort dispatcher, using the carrier set, the escort spawn object at the escort airbase.
|
||||
-- -- Provide a name of the escort, which will be also the name appearing on the radio menu for the group.
|
||||
-- -- And a briefing to appear when the player joins the player slot.
|
||||
-- Red_SU25T_EscortDispatcher = AI_ESCORT_DISPATCHER:New( Red_SU25T_CarrierSet, Red_SU25T_EscortSpawn, Red_SU25T_Airbase, "Escort Su-25", "You Su-25T is escorted by one Su-25. Use the radio menu to control the escorts." )
|
||||
--
|
||||
-- -- The dispatcher needs to be started using the :Start() method.
|
||||
-- Red_SU25T_EscortDispatcher:Start()
|
||||
function AI_ESCORT_DISPATCHER:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER
|
||||
|
||||
self.CarrierSet = CarrierSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
self.EscortName = EscortName
|
||||
self.EscortBriefing = EscortBriefing
|
||||
|
||||
self:SetStartState( "Idle" )
|
||||
|
||||
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
|
||||
|
||||
self:AddTransition( "Idle", "Start", "Monitoring" )
|
||||
self:AddTransition( "Monitoring", "Stop", "Idle" )
|
||||
|
||||
-- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset.
|
||||
function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier )
|
||||
self:F( { Carrier = Carrier:GetName() } )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_ESCORT_DISPATCHER:onafterStart( From, Event, To )
|
||||
|
||||
self:HandleEvent( EVENTS.Birth )
|
||||
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventExit )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER:OnEventExit( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
self:I({EscortAirbase= self.EscortAirbase } )
|
||||
self:I({PlayerGroupName = PlayerGroupName } )
|
||||
self:I({PlayerGroup = PlayerGroup})
|
||||
self:I({FirstGroup = self.CarrierSet:GetFirst()})
|
||||
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if self.AI_Escorts[PlayerGroupName] then
|
||||
self.AI_Escorts[PlayerGroupName]:Stop()
|
||||
self.AI_Escorts[PlayerGroupName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER:OnEventBirth( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
self:I({EscortAirbase= self.EscortAirbase } )
|
||||
self:I({PlayerGroupName = PlayerGroupName } )
|
||||
self:I({PlayerGroup = PlayerGroup})
|
||||
self:I({FirstGroup = self.CarrierSet:GetFirst()})
|
||||
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if not self.AI_Escorts[PlayerGroupName] then
|
||||
local LeaderUnit = PlayerUnit
|
||||
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
|
||||
self:I({EscortGroup = EscortGroup})
|
||||
|
||||
self:ScheduleOnce( 1,
|
||||
function( EscortGroup )
|
||||
local EscortSet = SET_GROUP:New()
|
||||
EscortSet:AddGroup( EscortGroup )
|
||||
self.AI_Escorts[PlayerGroupName] = AI_ESCORT:New( LeaderUnit, EscortSet, self.EscortName, self.EscortBriefing )
|
||||
self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 )
|
||||
if EscortGroup:IsHelicopter() then
|
||||
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
|
||||
else
|
||||
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
|
||||
end
|
||||
self.AI_Escorts[PlayerGroupName]:__Start( 0.1 )
|
||||
end, EscortGroup
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Start Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] Start
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
|
||||
--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] __Start
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param #number Delay
|
||||
|
||||
--- Stop Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
|
||||
--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] __Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
151
Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua
Normal file
151
Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua
Normal file
@@ -0,0 +1,151 @@
|
||||
--- **AI** - Models the assignment of AI escorts to player flights upon request using the radio menu.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provides the facilities to trigger escorts when players join flight units.
|
||||
-- * Provide a menu for which escorts can be requested.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Escort_Dispatcher_Request
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
--- @type AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models the assignment of AI escorts to player flights upon request using the radio menu.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_ESCORT_DISPATCHER_REQUEST
|
||||
AI_ESCORT_DISPATCHER_REQUEST = {
|
||||
ClassName = "AI_ESCORT_DISPATCHER_REQUEST",
|
||||
}
|
||||
|
||||
--- @field #list
|
||||
AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts = {}
|
||||
|
||||
|
||||
--- Creates a new AI_ESCORT_DISPATCHER_REQUEST object.
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are requested.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned.
|
||||
-- @param #string EscortName Name of the escort, which will also be the name of the escort menu.
|
||||
-- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_DISPATCHER_REQUEST
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER_REQUEST
|
||||
|
||||
self.CarrierSet = CarrierSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
self.EscortName = EscortName
|
||||
self.EscortBriefing = EscortBriefing
|
||||
|
||||
self:SetStartState( "Idle" )
|
||||
|
||||
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
|
||||
|
||||
self:AddTransition( "Idle", "Start", "Monitoring" )
|
||||
self:AddTransition( "Monitoring", "Stop", "Idle" )
|
||||
|
||||
-- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset.
|
||||
function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier )
|
||||
self:F( { Carrier = Carrier:GetName() } )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:onafterStart( From, Event, To )
|
||||
|
||||
self:HandleEvent( EVENTS.Birth )
|
||||
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventExit )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if self.AI_Escorts[PlayerGroupName] then
|
||||
self.AI_Escorts[PlayerGroupName]:Stop()
|
||||
self.AI_Escorts[PlayerGroupName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if not self.AI_Escorts[PlayerGroupName] then
|
||||
local LeaderUnit = PlayerUnit
|
||||
self:ScheduleOnce( 0.1,
|
||||
function()
|
||||
self.AI_Escorts[PlayerGroupName] = AI_ESCORT_REQUEST:New( LeaderUnit, self.EscortSpawn, self.EscortAirbase, self.EscortName, self.EscortBriefing )
|
||||
self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 )
|
||||
if PlayerGroup:IsHelicopter() then
|
||||
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
|
||||
else
|
||||
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
|
||||
end
|
||||
self.AI_Escorts[PlayerGroupName]:__Start( 0.1 )
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Start Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Start
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
|
||||
--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Start
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param #number Delay
|
||||
|
||||
--- Stop Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
|
||||
--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
321
Moose Development/Moose/AI/AI_Escort_Request.lua
Normal file
321
Moose Development/Moose/AI/AI_Escort_Request.lua
Normal file
@@ -0,0 +1,321 @@
|
||||
--- **AI** - Taking the lead of AI escorting your flight or of other AI, upon request using the menu.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Escort navigation commands.
|
||||
-- * Escort hold at position commands.
|
||||
-- * Escorts reporting detected targets.
|
||||
-- * Escorts scanning targets in advance.
|
||||
-- * Escorts attacking specific targets.
|
||||
-- * Request assistance from other groups for attack.
|
||||
-- * Manage rule of engagement of escorts.
|
||||
-- * Manage the allowed evasion techniques of escorts.
|
||||
-- * Make escort to execute a defined mission or path.
|
||||
-- * Escort tactical situation reporting.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Escort)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- Allows you to interact with escorting AI on your flight and take the lead.
|
||||
--
|
||||
-- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10).
|
||||
--
|
||||
-- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes.
|
||||
-- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts.
|
||||
--
|
||||
-- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval.
|
||||
-- Once targets are reported, each escort has these targets as menu options to command the attack of these targets.
|
||||
-- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered.
|
||||
--
|
||||
-- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available.
|
||||
-- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target.
|
||||
--
|
||||
-- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location.
|
||||
-- In this way, you can spread out the escorts over the battle field before a coordinated attack.
|
||||
--
|
||||
-- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight.
|
||||
--
|
||||
-- ## Leading the flight
|
||||
--
|
||||
-- When leading the flight, you are expected to guide the escorts towards the target areas,
|
||||
-- and carefully coordinate the attack based on the threat levels reported, and the available weapons
|
||||
-- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack.
|
||||
--
|
||||
-- ## Following the flight
|
||||
--
|
||||
-- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead.
|
||||
-- You as a player, are following the escorts, and are commanding them to progress the mission while
|
||||
-- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets
|
||||
-- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target
|
||||
-- type. Once the attack is finished, the escort will resume the mission it was assigned.
|
||||
-- In other words, you can use the escorts for reconnaissance, and for guiding the attack.
|
||||
-- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are
|
||||
-- following. You are in control. The ka-50s detect targets, report them, and you command how the attack
|
||||
-- will commence and from where. You can control where the escorts are holding position and which targets
|
||||
-- are attacked first. You are in control how the ka-50s will follow their mission path.
|
||||
--
|
||||
-- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control.
|
||||
-- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the
|
||||
-- attack is well coordinated.
|
||||
--
|
||||
-- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism
|
||||
-- it brings in your DCS world simulations.
|
||||
--
|
||||
-- # RADIO MENUs that can be created:
|
||||
--
|
||||
-- Find a summary below of the current available commands:
|
||||
--
|
||||
-- ## Navigation ...:
|
||||
--
|
||||
-- Escort group navigation functions:
|
||||
--
|
||||
-- * **"Join-Up":** The escort group fill follow you in the assigned formation.
|
||||
-- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
|
||||
-- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
|
||||
--
|
||||
-- ## Hold position ...:
|
||||
--
|
||||
-- Escort group navigation functions:
|
||||
--
|
||||
-- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter.
|
||||
-- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter.
|
||||
--
|
||||
-- ## Report targets ...:
|
||||
--
|
||||
-- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below).
|
||||
--
|
||||
-- * **"Report now":** Will report the current detected targets.
|
||||
-- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list.
|
||||
-- * **"Report targets off":** Will stop detecting targets.
|
||||
--
|
||||
-- ## Attack targets ...:
|
||||
--
|
||||
-- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
|
||||
-- This menu will be available in Flight menu or in each Escort menu.
|
||||
--
|
||||
-- ## Scan targets ...:
|
||||
--
|
||||
-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation.
|
||||
--
|
||||
-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
|
||||
-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
|
||||
--
|
||||
-- ## Request assistance from ...:
|
||||
--
|
||||
-- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**.
|
||||
-- This menu item allows to request attack support from other ground based escorts supporting the current escort.
|
||||
-- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles.
|
||||
-- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area.
|
||||
--
|
||||
-- ## ROE ...:
|
||||
--
|
||||
-- Sets the Rules of Engagement (ROE) of the escort group when in flight.
|
||||
--
|
||||
-- * **"Hold Fire":** The escort group will hold fire.
|
||||
-- * **"Return Fire":** The escort group will return fire.
|
||||
-- * **"Open Fire":** The escort group will open fire on designated targets.
|
||||
-- * **"Weapon Free":** The escort group will engage with any target.
|
||||
--
|
||||
-- ## Evasion ...:
|
||||
--
|
||||
-- Will define the evasion techniques that the escort group will perform during flight or combat.
|
||||
--
|
||||
-- * **"Fight until death":** The escort group will have no reaction to threats.
|
||||
-- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed.
|
||||
-- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
|
||||
-- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
|
||||
--
|
||||
-- ## Resume Mission ...:
|
||||
--
|
||||
-- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
|
||||
-- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Escort_Request
|
||||
-- @image Escorting.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_ESCORT_REQUEST
|
||||
-- @extends AI.AI_Escort#AI_ESCORT
|
||||
|
||||
--- AI_ESCORT_REQUEST class
|
||||
--
|
||||
-- # AI_ESCORT_REQUEST construction methods.
|
||||
--
|
||||
-- Create a new AI_ESCORT_REQUEST object with the @{#AI_ESCORT_REQUEST.New} method:
|
||||
--
|
||||
-- * @{#AI_ESCORT_REQUEST.New}: Creates a new AI_ESCORT_REQUEST object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text.
|
||||
--
|
||||
-- @usage
|
||||
-- -- Declare a new EscortPlanes object as follows:
|
||||
--
|
||||
-- -- First find the GROUP object and the CLIENT object.
|
||||
-- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor.
|
||||
-- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client.
|
||||
--
|
||||
-- -- Now use these 2 objects to construct the new EscortPlanes object.
|
||||
-- EscortPlanes = AI_ESCORT_REQUEST:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." )
|
||||
--
|
||||
-- @field #AI_ESCORT_REQUEST
|
||||
AI_ESCORT_REQUEST = {
|
||||
ClassName = "AI_ESCORT_REQUEST",
|
||||
}
|
||||
|
||||
--- AI_ESCORT_REQUEST.Mode class
|
||||
-- @type AI_ESCORT_REQUEST.MODE
|
||||
-- @field #number FOLLOW
|
||||
-- @field #number MISSION
|
||||
|
||||
--- MENUPARAM type
|
||||
-- @type MENUPARAM
|
||||
-- @field #AI_ESCORT_REQUEST ParamSelf
|
||||
-- @field #Distance ParamDistance
|
||||
-- @field #function ParamFunction
|
||||
-- @field #string ParamMessage
|
||||
|
||||
--- AI_ESCORT_REQUEST class constructor for an AI group
|
||||
-- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object of AI, escorting the EscortUnit.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where escorts will be spawned once requested.
|
||||
-- @param #string EscortName Name of the escort.
|
||||
-- @param #string EscortBriefing A text showing the AI_ESCORT_REQUEST briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_REQUEST
|
||||
-- @usage
|
||||
-- EscortSpawn = SPAWN:NewWithAlias( "Red A2G Escort Template", "Red A2G Escort AI" ):InitLimit( 10, 10 )
|
||||
-- EscortSpawn:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Sochi_Adler ), AIRBASE.TerminalType.OpenBig )
|
||||
--
|
||||
-- local EscortUnit = UNIT:FindByName( "Red A2G Pilot" )
|
||||
--
|
||||
-- Escort = AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, AIRBASE:FindByName(AIRBASE.Caucasus.Sochi_Adler), "A2G", "Briefing" )
|
||||
-- Escort:FormationTrail( 50, 100, 100 )
|
||||
-- Escort:Menus()
|
||||
-- Escort:__Start( 5 )
|
||||
function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local EscortGroupSet = SET_GROUP:New():FilterDeads():FilterCrashes()
|
||||
local self = BASE:Inherit( self, AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT_REQUEST
|
||||
|
||||
self.EscortGroupSet = EscortGroupSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
|
||||
self.LeaderGroup = self.PlayerUnit:GetGroup()
|
||||
|
||||
self.Detection = DETECTION_AREAS:New( self.EscortGroupSet, 5000 )
|
||||
self.Detection:__Start( 30 )
|
||||
|
||||
self.SpawnMode = self.__Enum.Mode.Mission
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
function AI_ESCORT_REQUEST:SpawnEscort()
|
||||
|
||||
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
|
||||
|
||||
self:ScheduleOnce( 0.1,
|
||||
function( EscortGroup )
|
||||
|
||||
EscortGroup:OptionROTVertical()
|
||||
EscortGroup:OptionROEHoldFire()
|
||||
|
||||
self.EscortGroupSet:AddGroup( EscortGroup )
|
||||
|
||||
local LeaderEscort = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP
|
||||
local Report = REPORT:New()
|
||||
Report:Add( "Joining Up " .. self.EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.EscortUnit ) )
|
||||
LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit )
|
||||
|
||||
self:SetFlightModeFormation( EscortGroup )
|
||||
self:FormationTrail()
|
||||
|
||||
self:_InitFlightMenus()
|
||||
self:_InitEscortMenus( EscortGroup )
|
||||
self:_InitEscortRoute( EscortGroup )
|
||||
|
||||
--- @param #AI_ESCORT self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function EscortGroup:OnEventDeadOrCrash( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
self.EscortMenu:Remove()
|
||||
end
|
||||
|
||||
EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash )
|
||||
EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash )
|
||||
|
||||
end, EscortGroup
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP EscortGroupSet
|
||||
function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet )
|
||||
|
||||
self:F()
|
||||
|
||||
if not self.MenuRequestEscort then
|
||||
self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName )
|
||||
self.MenuRequestEscort = MENU_GROUP_COMMAND:New( self.LeaderGroup, "Request new escort ", self.MainMenu,
|
||||
function()
|
||||
self:SpawnEscort()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
self:GetParent( self ).onafterStart( self, EscortGroupSet )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventDeadOrCrash )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP EscortGroupSet
|
||||
function AI_ESCORT_REQUEST:onafterStop( EscortGroupSet )
|
||||
|
||||
self:F()
|
||||
|
||||
EscortGroupSet:ForEachGroup(
|
||||
--- @param Wrapper.Group#GROUP EscortGroup
|
||||
function( EscortGroup )
|
||||
EscortGroup:WayPointInitialize()
|
||||
|
||||
EscortGroup:OptionROTVertical()
|
||||
EscortGroup:OptionROEOpenFire()
|
||||
end
|
||||
)
|
||||
|
||||
self.Detection:Stop()
|
||||
|
||||
self.MainMenu:Remove()
|
||||
|
||||
end
|
||||
|
||||
--- Set the spawn mode to be mission execution.
|
||||
-- @param #AI_ESCORT_REQUEST self
|
||||
function AI_ESCORT_REQUEST:SetEscortSpawnMission()
|
||||
|
||||
self.SpawnMode = self.__Enum.Mode.Mission
|
||||
|
||||
end
|
||||
1279
Moose Development/Moose/AI/AI_Formation.lua
Normal file
1279
Moose Development/Moose/AI/AI_Formation.lua
Normal file
File diff suppressed because it is too large
Load Diff
939
Moose Development/Moose/AI/AI_Patrol.lua
Normal file
939
Moose Development/Moose/AI/AI_Patrol.lua
Normal file
@@ -0,0 +1,939 @@
|
||||
--- **AI** - Perform Air Patrolling for airplanes.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel threshold to RTB on time.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- AI PATROL classes makes AI Controllables execute an Patrol.
|
||||
--
|
||||
-- There are the following types of PATROL classes defined:
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE}: Perform a PATROL in a zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Patrol)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Dutch_Baron**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)
|
||||
-- * **Pikey**: Testing and API concept review.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Patrol
|
||||
-- @image AI_Air_Patrolling.JPG
|
||||
|
||||
--- AI_PATROL_ZONE class
|
||||
-- @type AI_PATROL_ZONE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
|
||||
-- @field Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @field DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @field DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @field DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @field DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @field Core.Spawn#SPAWN CoordTest
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
--- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_PATROL_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- This cycle will continue.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
---- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or
|
||||
-- use derived AI_ classes to model AI offensive or defensive behaviour.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_PATROL_ZONE constructor
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE.New}(): Creates a new AI_PATROL_ZONE object.
|
||||
--
|
||||
-- ## 2. AI_PATROL_ZONE is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1. AI_PATROL_ZONE States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base.
|
||||
-- * **Stopped** ( Group ): The process is stopped.
|
||||
-- * **Crashed** ( Group ): The AI has crashed or is dead.
|
||||
--
|
||||
-- ### 2.2. AI_PATROL_ZONE Events
|
||||
--
|
||||
-- * **Start** ( Group ): Start the process.
|
||||
-- * **Stop** ( Group ): Stop the process.
|
||||
-- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **RTB** ( Group ): Route the AI to the home base.
|
||||
-- * **Detect** ( Group ): The AI is detecting targets.
|
||||
-- * **Detected** ( Group ): The AI has detected new targets.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set or Get the AI controllable
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE.SetControllable}(): Set the AIControllable.
|
||||
-- * @{#AI_PATROL_ZONE.GetControllable}(): Get the AIControllable.
|
||||
--
|
||||
-- ## 4. Set the Speed and Altitude boundaries of the AI controllable
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol.
|
||||
-- * @{#AI_PATROL_ZONE.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol.
|
||||
--
|
||||
-- ## 5. Manage the detection process of the AI controllable
|
||||
--
|
||||
-- The detection process of the AI controllable can be manipulated.
|
||||
-- Detection requires an amount of CPU power, which has an impact on your mission performance.
|
||||
-- Only put detection on when absolutely necessary, and the frequency of the detection can also be set.
|
||||
--
|
||||
-- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
|
||||
-- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
|
||||
--
|
||||
-- The detection frequency can be set with @{#AI_PATROL_ZONE.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
|
||||
-- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI.
|
||||
--
|
||||
-- The detection can be filtered to potential targets in a specific zone.
|
||||
-- Use the method @{#AI_PATROL_ZONE.SetDetectionZone}() to set the zone where targets need to be detected.
|
||||
-- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected
|
||||
-- according the weather conditions.
|
||||
--
|
||||
-- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE
|
||||
--
|
||||
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- while a new AI is targeted to the AI_PATROL_ZONE.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this process in place.
|
||||
--
|
||||
-- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE
|
||||
--
|
||||
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
|
||||
-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this process in place.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_PATROL_ZONE
|
||||
AI_PATROL_ZONE = {
|
||||
ClassName = "AI_PATROL_ZONE",
|
||||
}
|
||||
|
||||
--- Creates a new AI_PATROL_ZONE object
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
-- @usage
|
||||
-- -- Define a new AI_PATROL_ZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
|
||||
-- PatrolZone = ZONE:New( 'PatrolZone' )
|
||||
-- PatrolSpawn = SPAWN:New( 'Patrol Group' )
|
||||
-- PatrolArea = AI_PATROL_ZONE:New( PatrolZone, 3000, 6000, 600, 900 )
|
||||
function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_PATROL_ZONE
|
||||
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
|
||||
-- defafult PatrolAltType to "BARO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "BARO"
|
||||
|
||||
self:SetRefreshTimeInterval( 30 )
|
||||
|
||||
self.CheckStatus = true
|
||||
|
||||
self:ManageFuel( .2, 60 )
|
||||
self:ManageDamage( 1 )
|
||||
|
||||
|
||||
self.DetectedUnits = {} -- This table contains the targets detected during patrol.
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
self:AddTransition( "*", "Stop", "Stopped" )
|
||||
|
||||
--- OnLeave Transition Handler for State Stopped.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnLeaveStopped
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Stopped.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnEnterStopped
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- OnBefore Transition Handler for Event Stop.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStop
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Stop.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterStop
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Stop.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Stop
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Stop.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Stop
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "None", "Start", "Patrolling" )
|
||||
|
||||
--- OnBefore Transition Handler for Event Start.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStart
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Start.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterStart
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Start.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Start
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Start.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Start
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnLeavePatrolling
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnEnterPatrolling
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Route.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeRoute
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Route.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterRoute
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Route.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Route
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Route.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Route
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Status.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeStatus
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Status.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterStatus
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Status.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Status
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Status.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Status
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Detect", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Detect.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeDetect
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Detect.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterDetect
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Detect.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Detect
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Detect.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Detect
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Detected", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Detected.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeDetected
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Detected.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterDetected
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Detected.
|
||||
-- @function [parent=#AI_PATROL_ZONE] Detected
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Detected.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __Detected
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "RTB", "Returning" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
--- OnBefore Transition Handler for Event RTB.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnBeforeRTB
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event RTB.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnAfterRTB
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event RTB.
|
||||
-- @function [parent=#AI_PATROL_ZONE] RTB
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event RTB.
|
||||
-- @function [parent=#AI_PATROL_ZONE] __RTB
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Returning.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnLeaveReturning
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Returning.
|
||||
-- @function [parent=#AI_PATROL_ZONE] OnEnterReturning
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE.
|
||||
|
||||
self:AddTransition( "*", "Eject", "*" )
|
||||
self:AddTransition( "*", "Crash", "Crashed" )
|
||||
self:AddTransition( "*", "PilotDead", "*" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Sets (modifies) the minimum and maximum speed of the patrol.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
|
||||
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
|
||||
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sets the floor and ceiling altitude of the patrol.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
|
||||
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude
|
||||
end
|
||||
|
||||
-- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets.
|
||||
-- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased.
|
||||
|
||||
--- Set the detection on. The AI will detect for targets.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionOn()
|
||||
self:F2()
|
||||
|
||||
self.DetectOn = true
|
||||
end
|
||||
|
||||
--- Set the detection off. The AI will NOT detect for targets.
|
||||
-- However, the list of already detected targets will be kept and can be enquired!
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionOff()
|
||||
self:F2()
|
||||
|
||||
self.DetectOn = false
|
||||
end
|
||||
|
||||
--- Set the status checking off.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetStatusOff()
|
||||
self:F2()
|
||||
|
||||
self.CheckStatus = false
|
||||
end
|
||||
|
||||
--- Activate the detection. The AI will detect for targets if the Detection is switched On.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionActivated()
|
||||
self:F2()
|
||||
|
||||
self:ClearDetectedUnits()
|
||||
self.DetectActivated = true
|
||||
self:__Detect( -self.DetectInterval )
|
||||
end
|
||||
|
||||
--- Deactivate the detection. The AI will NOT detect for targets.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionDeactivated()
|
||||
self:F2()
|
||||
|
||||
self:ClearDetectedUnits()
|
||||
self.DetectActivated = false
|
||||
end
|
||||
|
||||
--- Set the interval in seconds between each detection executed by the AI.
|
||||
-- The list of already detected targets will be kept and updated.
|
||||
-- Newly detected targets will be added, but already detected targets that were
|
||||
-- not detected in this cycle, will NOT be removed!
|
||||
-- The default interval is 30 seconds.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number Seconds The interval in seconds.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetRefreshTimeInterval( Seconds )
|
||||
self:F2()
|
||||
|
||||
if Seconds then
|
||||
self.DetectInterval = Seconds
|
||||
else
|
||||
self.DetectInterval = 30
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the detection zone where the AI is detecting targets.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Zone#ZONE DetectionZone The zone where to detect targets.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:SetDetectionZone( DetectionZone )
|
||||
self:F2()
|
||||
|
||||
if DetectionZone then
|
||||
self.DetectZone = DetectionZone
|
||||
else
|
||||
self.DetectZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Gets a list of @{Wrapper.Unit#UNIT}s that were detected by the AI.
|
||||
-- No filtering is applied, so, ANY detected UNIT can be in this list.
|
||||
-- It is up to the mission designer to use the @{Wrapper.Unit} class and methods to filter the targets.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @return #table The list of @{Wrapper.Unit#UNIT}s
|
||||
function AI_PATROL_ZONE:GetDetectedUnits()
|
||||
self:F2()
|
||||
|
||||
return self.DetectedUnits
|
||||
end
|
||||
|
||||
--- Clears the list of @{Wrapper.Unit#UNIT}s that were detected by the AI.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ClearDetectedUnits()
|
||||
self:F2()
|
||||
self.DetectedUnits = {}
|
||||
end
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targeted to the AI_PATROL_ZONE.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
|
||||
|
||||
self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage
|
||||
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage threshold is reached,
|
||||
-- the AI will return immediately to the home base (RTB).
|
||||
-- Note that for groups, the average damage of the complete group will be calculated.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold )
|
||||
|
||||
self.PatrolManageDamage = true
|
||||
self.PatrolDamageThreshold = PatrolDamageThreshold
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Defines a new patrol route using the @{#AI_PATROL_ZONE} parameters and settings.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
self:__Route( 1 ) -- Route to the patrol point. The asynchronous trigger is important, because a spawned group and units takes at least one second to come live.
|
||||
self:__Status( 60 ) -- Check status status every 30 seconds.
|
||||
self:SetDetectionActivated()
|
||||
|
||||
self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnCrash )
|
||||
self:HandleEvent( EVENTS.Ejection, self.OnEjection )
|
||||
|
||||
Controllable:OptionROEHoldFire()
|
||||
Controllable:OptionROTVertical()
|
||||
|
||||
self.Controllable:OnReSpawn(
|
||||
function( PatrolGroup )
|
||||
self:T( "ReSpawn" )
|
||||
self:__Reset( 1 )
|
||||
self:__Route( 5 )
|
||||
end
|
||||
)
|
||||
|
||||
self:SetDetectionOn()
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
--- @param Wrapper.Controllable#CONTROLLABLE Controllable
|
||||
function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To )
|
||||
|
||||
return self.DetectOn and self.DetectActivated
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
--- @param Wrapper.Controllable#CONTROLLABLE Controllable
|
||||
function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To )
|
||||
|
||||
local Detected = false
|
||||
|
||||
local DetectedTargets = Controllable:GetDetectedTargets()
|
||||
for TargetID, Target in pairs( DetectedTargets or {} ) do
|
||||
local TargetObject = Target.object
|
||||
|
||||
if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then
|
||||
|
||||
local TargetUnit = UNIT:Find( TargetObject )
|
||||
|
||||
-- Check that target is alive due to issue https://github.com/FlightControl-Master/MOOSE/issues/1234
|
||||
if TargetUnit and TargetUnit:IsAlive() then
|
||||
|
||||
local TargetUnitName = TargetUnit:GetName()
|
||||
|
||||
if self.DetectionZone then
|
||||
if TargetUnit:IsInZone( self.DetectionZone ) then
|
||||
self:T( {"Detected ", TargetUnit } )
|
||||
if self.DetectedUnits[TargetUnit] == nil then
|
||||
self.DetectedUnits[TargetUnit] = true
|
||||
end
|
||||
Detected = true
|
||||
end
|
||||
else
|
||||
if self.DetectedUnits[TargetUnit] == nil then
|
||||
self.DetectedUnits[TargetUnit] = true
|
||||
end
|
||||
Detected = true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:__Detect( -self.DetectInterval )
|
||||
|
||||
if Detected == true then
|
||||
self:__Detected( 1.5 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
|
||||
-- This static method is called from the route path within the last task at the last waypoint of the Controllable.
|
||||
-- Note that this method is required, as triggers the next route when patrolling for the Controllable.
|
||||
function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable )
|
||||
|
||||
local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROL_ZONE
|
||||
PatrolZone:__Route( 1 )
|
||||
end
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{#AI_PATROL_ZONE} parameters and settings.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
|
||||
self:F2()
|
||||
|
||||
-- When RTB, don't allow anymore the routing.
|
||||
if From == "RTB" then
|
||||
return
|
||||
end
|
||||
|
||||
local life = self.Controllable:GetLife() or 0
|
||||
if self.Controllable:IsAlive() and life > 1 then
|
||||
-- Determine if the AIControllable is within the PatrolZone.
|
||||
-- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point.
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
-- Calculate the current route point of the controllable as the start point of the route.
|
||||
-- However, when the controllable is not in the air,
|
||||
-- the controllable current waypoint is probably the airbase...
|
||||
-- Thus, if we would take the current waypoint as the startpoint, upon take-off, the controllable flies
|
||||
-- immediately back to the airbase, and this is not correct.
|
||||
-- Therefore, when on a runway, get as the current route point a random point within the PatrolZone.
|
||||
-- This will make the plane fly immediately to the patrol zone.
|
||||
|
||||
if self.Controllable:InAir() == false then
|
||||
self:T( "Not in the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
if not CurrentVec2 then return end
|
||||
--Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TakeOffParking,
|
||||
POINT_VEC3.RoutePointAction.FromParkingArea,
|
||||
ToPatrolZoneSpeed,
|
||||
true
|
||||
)
|
||||
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
|
||||
else
|
||||
self:T( "In the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
if not CurrentVec2 then return end
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToPatrolZoneSpeed,
|
||||
true
|
||||
)
|
||||
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
|
||||
end
|
||||
|
||||
|
||||
--- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone.
|
||||
|
||||
--- Find a random 2D point in PatrolZone.
|
||||
local ToTargetVec2 = self.PatrolZone:GetRandomVec2()
|
||||
self:T2( ToTargetVec2 )
|
||||
|
||||
--- Define Speed and Altitude.
|
||||
local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } )
|
||||
|
||||
--- Obtain a 3D @{Point} from the 2D point + altitude.
|
||||
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToTargetSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
--self.CoordTest:SpawnFromVec3( ToTargetPointVec3:GetVec3() )
|
||||
|
||||
--ToTargetPointVec3:SmokeRed()
|
||||
|
||||
PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
self.Controllable:WayPointInitialize( PatrolRoute )
|
||||
|
||||
--- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ...
|
||||
self.Controllable:SetState( self.Controllable, "PatrolZone", self )
|
||||
self.Controllable:WayPointFunction( #PatrolRoute, 1, "AI_PATROL_ZONE:_NewPatrolRoute" )
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
self.Controllable:WayPointExecute( 1, 2 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onbeforeStatus()
|
||||
|
||||
return self.CheckStatus
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onafterStatus()
|
||||
self:F2()
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
local RTB = false
|
||||
|
||||
local Fuel = self.Controllable:GetFuelMin()
|
||||
if Fuel < self.PatrolFuelThresholdPercentage then
|
||||
self:I( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
|
||||
local OldAIControllable = self.Controllable
|
||||
|
||||
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
|
||||
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) )
|
||||
OldAIControllable:SetTask( TimedOrbitTask, 10 )
|
||||
|
||||
RTB = true
|
||||
else
|
||||
end
|
||||
|
||||
-- TODO: Check GROUP damage function.
|
||||
local Damage = self.Controllable:GetLife()
|
||||
if Damage <= self.PatrolDamageThreshold then
|
||||
self:I( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
|
||||
RTB = true
|
||||
end
|
||||
|
||||
if RTB == true then
|
||||
self:RTB()
|
||||
else
|
||||
self:__Status( 60 ) -- Execute the Patrol event after 30 seconds.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onafterRTB()
|
||||
self:F2()
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
self:SetDetectionOff()
|
||||
self.CheckStatus = false
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
if not CurrentVec2 then return end
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
--local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToPatrolZoneSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
|
||||
|
||||
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
|
||||
self.Controllable:WayPointInitialize( PatrolRoute )
|
||||
|
||||
--- NOW ROUTE THE GROUP!
|
||||
self.Controllable:WayPointExecute( 1, 1 )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:onafterDead()
|
||||
self:SetDetectionOff()
|
||||
self:SetStatusOff()
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_PATROL_ZONE:OnCrash( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
if #self.Controllable:GetUnits() == 1 then
|
||||
self:__Crash( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_PATROL_ZONE:OnEjection( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:__Eject( 1, EventData )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_PATROL_ZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_PATROL_ZONE:OnPilotDead( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:__PilotDead( 1, EventData )
|
||||
end
|
||||
end
|
||||
310
Moose Development/Moose/Actions/Act_Account.lua
Normal file
310
Moose Development/Moose/Actions/Act_Account.lua
Normal file
@@ -0,0 +1,310 @@
|
||||
--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occurring on UNITs.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Act_Account
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
do -- ACT_ACCOUNT
|
||||
|
||||
--- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
-- ## ACT_ACCOUNT state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ACCOUNT States
|
||||
--
|
||||
-- * **Assigned**: The player is assigned.
|
||||
-- * **Waiting**: Waiting for an event.
|
||||
-- * **Report**: Reporting.
|
||||
-- * **Account**: Account for an event.
|
||||
-- * **Accounted**: All events have been accounted for, end of the process.
|
||||
-- * **Failed**: Failed the process.
|
||||
--
|
||||
-- ### ACT_ACCOUNT Events
|
||||
--
|
||||
-- * **Start**: Start the process.
|
||||
-- * **Wait**: Wait for an event.
|
||||
-- * **Report**: Report the status of the accounting.
|
||||
-- * **Event**: An event happened, process the event.
|
||||
-- * **More**: More targets.
|
||||
-- * **NoMore (*)**: No more targets.
|
||||
-- * **Fail (*)**: The action process has failed.
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
-- ### ACT_ACCOUNT state transition methods:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- @type ACT_ACCOUNT
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ACCOUNT = {
|
||||
ClassName = "ACT_ACCOUNT",
|
||||
TargetSetUnit = nil,
|
||||
}
|
||||
|
||||
--- Creates a new DESTROY process.
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @return #ACT_ACCOUNT
|
||||
function ACT_ACCOUNT:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
self:AddTransition( "Assigned", "Start", "Waiting" )
|
||||
self:AddTransition( "*", "Wait", "Waiting" )
|
||||
self:AddTransition( "*", "Report", "Report" )
|
||||
self:AddTransition( "*", "Event", "Account" )
|
||||
self:AddTransition( "Account", "Player", "AccountForPlayer" )
|
||||
self:AddTransition( "Account", "Other", "AccountForOther" )
|
||||
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "More", "Wait" )
|
||||
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "NoMore", "Accounted" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
self:AddEndState( "Failed" )
|
||||
|
||||
self:SetStartState( "Assigned" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Process Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT:onafterStart( ProcessUnit, From, Event, To )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead, self.onfuncEventDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.onfuncEventCrash )
|
||||
self:HandleEvent( EVENTS.Hit )
|
||||
|
||||
self:__Wait( 1 )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT:onenterWaiting( ProcessUnit, From, Event, To )
|
||||
|
||||
if self.DisplayCount >= self.DisplayInterval then
|
||||
self:Report()
|
||||
self.DisplayCount = 1
|
||||
else
|
||||
self.DisplayCount = self.DisplayCount + 1
|
||||
end
|
||||
|
||||
return true -- Process always the event.
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event )
|
||||
|
||||
self:__NoMore( 1 )
|
||||
end
|
||||
|
||||
end -- ACT_ACCOUNT
|
||||
|
||||
do -- ACT_ACCOUNT_DEADS
|
||||
|
||||
--- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{#ACT_ACCOUNT}
|
||||
--
|
||||
-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units.
|
||||
-- The process is given a @{Core.Set} of units that will be tracked upon successful destruction.
|
||||
-- The process will end after each target has been successfully destroyed.
|
||||
-- Each successful dead will trigger an Account state transition that can be scored, modified or administered.
|
||||
--
|
||||
--
|
||||
-- ## ACT_ACCOUNT_DEADS constructor:
|
||||
--
|
||||
-- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object.
|
||||
--
|
||||
-- @type ACT_ACCOUNT_DEADS
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @extends #ACT_ACCOUNT
|
||||
ACT_ACCOUNT_DEADS = {
|
||||
ClassName = "ACT_ACCOUNT_DEADS",
|
||||
}
|
||||
|
||||
--- Creates a new DESTROY process.
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param #string TaskName
|
||||
function ACT_ACCOUNT_DEADS:New()
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
self.DisplayCategory = "HQ" -- Targets is the default display category
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ACCOUNT_DEADS:Init( FsmAccount )
|
||||
|
||||
self.Task = self:GetTask()
|
||||
self.TaskName = self.Task:GetName()
|
||||
end
|
||||
|
||||
--- Process Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
local MessageText = "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param Tasking.Task#TASK Task
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
|
||||
local PlayerName = ProcessUnit:GetPlayerName()
|
||||
local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
|
||||
if PlayerHit == PlayerName then
|
||||
self:Player( EventData )
|
||||
else
|
||||
self:Other( EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param Tasking.Task#TASK Task
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
|
||||
Task.TargetSetUnit:Remove( EventData.IniUnitName )
|
||||
|
||||
local MessageText = "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
|
||||
local PlayerName = ProcessUnit:GetPlayerName()
|
||||
Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 )
|
||||
|
||||
if Task.TargetSetUnit:Count() > 0 then
|
||||
self:__More( 1 )
|
||||
else
|
||||
self:__NoMore( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param Tasking.Task#TASK Task
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
Task.TargetSetUnit:Remove( EventData.IniUnitName )
|
||||
|
||||
local MessageText = "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
|
||||
if Task.TargetSetUnit:Count() > 0 then
|
||||
self:__More( 1 )
|
||||
else
|
||||
self:__NoMore( 1 )
|
||||
end
|
||||
end
|
||||
|
||||
--- DCS Events
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:OnEventHit( EventData )
|
||||
self:T( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniPlayerName and EventData.TgtDCSUnitName then
|
||||
self.PlayerHits = self.PlayerHits or {}
|
||||
self.PlayerHits[EventData.TgtDCSUnitName] = EventData.IniPlayerName
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData )
|
||||
self:T( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
self:Event( EventData )
|
||||
end
|
||||
end
|
||||
|
||||
--- DCS Events
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData )
|
||||
self:T( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
self:Event( EventData )
|
||||
end
|
||||
end
|
||||
|
||||
end -- ACT_ACCOUNT DEADS
|
||||
292
Moose Development/Moose/Actions/Act_Assign.lua
Normal file
292
Moose Development/Moose/Actions/Act_Assign.lua
Normal file
@@ -0,0 +1,292 @@
|
||||
--- (SP) (MP) (FSM) Accept or reject process for player (task) assignments.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
-- ## ACT_ASSIGN state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ASSIGN **Events**:
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
-- * **Start**: Start the tasking acceptance process.
|
||||
-- * **Assign**: Assign the task.
|
||||
-- * **Reject**: Reject the task..
|
||||
--
|
||||
-- ### ACT_ASSIGN **Event methods**:
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ASSIGN **States**:
|
||||
--
|
||||
-- * **UnAssigned**: The player has not accepted the task.
|
||||
-- * **Assigned (*)**: The player has accepted the task.
|
||||
-- * **Rejected (*)**: The player has not accepted the task.
|
||||
-- * **Waiting**: The process is awaiting player feedback.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
-- ### ACT_ASSIGN state transition methods:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Core.Fsm#ACT_ASSIGN}
|
||||
--
|
||||
-- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task.
|
||||
--
|
||||
-- ## 1.1) ACT_ASSIGN_ACCEPT constructor:
|
||||
--
|
||||
-- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Core.Fsm#ACT_ASSIGN}
|
||||
--
|
||||
-- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option.
|
||||
-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task.
|
||||
-- The assignment type also allows to reject the task.
|
||||
--
|
||||
-- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor:
|
||||
-- -----------------------------------------
|
||||
--
|
||||
-- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Act_Assign
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
do -- ACT_ASSIGN
|
||||
|
||||
--- ACT_ASSIGN class
|
||||
-- @type ACT_ASSIGN
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ASSIGN = {
|
||||
ClassName = "ACT_ASSIGN",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted.
|
||||
-- @param #ACT_ASSIGN self
|
||||
-- @return #ACT_ASSIGN The task acceptance process.
|
||||
function ACT_ASSIGN:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
self:AddTransition( "UnAssigned", "Start", "Waiting" )
|
||||
self:AddTransition( "Waiting", "Assign", "Assigned" )
|
||||
self:AddTransition( "Waiting", "Reject", "Rejected" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
self:AddEndState( "Assigned" )
|
||||
self:AddEndState( "Rejected" )
|
||||
self:AddEndState( "Failed" )
|
||||
|
||||
self:SetStartState( "UnAssigned" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end -- ACT_ASSIGN
|
||||
|
||||
|
||||
|
||||
do -- ACT_ASSIGN_ACCEPT
|
||||
|
||||
--- ACT_ASSIGN_ACCEPT class
|
||||
-- @type ACT_ASSIGN_ACCEPT
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIGN
|
||||
ACT_ASSIGN_ACCEPT = {
|
||||
ClassName = "ACT_ASSIGN_ACCEPT",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted.
|
||||
-- @param #ACT_ASSIGN_ACCEPT self
|
||||
-- @param #string TaskBriefing
|
||||
function ACT_ASSIGN_ACCEPT:New( TaskBriefing )
|
||||
|
||||
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ASSIGN_ACCEPT:Init( FsmAssign )
|
||||
|
||||
self.TaskBriefing = FsmAssign.TaskBriefing
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
self:__Assign( 1 )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
|
||||
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
|
||||
end
|
||||
|
||||
end -- ACT_ASSIGN_ACCEPT
|
||||
|
||||
|
||||
do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
|
||||
--- ACT_ASSIGN_MENU_ACCEPT class
|
||||
-- @type ACT_ASSIGN_MENU_ACCEPT
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIGN
|
||||
ACT_ASSIGN_MENU_ACCEPT = {
|
||||
ClassName = "ACT_ASSIGN_MENU_ACCEPT",
|
||||
}
|
||||
|
||||
--- Init.
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param #string TaskBriefing
|
||||
-- @return #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:New( TaskBriefing )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param #string TaskBriefing
|
||||
-- @return #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing )
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
self:GetCommandCenter():MessageToGroup( "Task " .. self.Task:GetName() .. " has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!", ProcessUnit:GetGroup(), 120 )
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
|
||||
self.Menu = MENU_GROUP:New( TaskGroup, "Task " .. self.Task:GetName() .. " CONFIRMATION" )
|
||||
self.MenuAcceptTask = MENU_GROUP_COMMAND:New( TaskGroup, "Accept task " .. self.Task:GetName(), self.Menu, self.MenuAssign, self, TaskGroup )
|
||||
self.MenuRejectTask = MENU_GROUP_COMMAND:New( TaskGroup, "Reject task " .. self.Task:GetName(), self.Menu, self.MenuReject, self, TaskGroup )
|
||||
|
||||
self:__Reject( 120, TaskGroup )
|
||||
end
|
||||
|
||||
--- Menu function.
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign( TaskGroup )
|
||||
|
||||
self:__Assign( -1, TaskGroup )
|
||||
end
|
||||
|
||||
--- Menu function.
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup )
|
||||
|
||||
self:__Reject( -1, TaskGroup )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
|
||||
self.Menu:Remove()
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
self:F( { TaskGroup = TaskGroup } )
|
||||
|
||||
self.Menu:Remove()
|
||||
--TODO: need to resolve this problem ... it has to do with the events ...
|
||||
--self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event
|
||||
self.Task:RejectGroup( TaskGroup )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
|
||||
--self.Task:AssignToGroup( TaskGroup )
|
||||
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
|
||||
end
|
||||
|
||||
end -- ACT_ASSIGN_MENU_ACCEPT
|
||||
219
Moose Development/Moose/Actions/Act_Assist.lua
Normal file
219
Moose Development/Moose/Actions/Act_Assist.lua
Normal file
@@ -0,0 +1,219 @@
|
||||
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
|
||||
--
|
||||
-- ## ACT_ASSIST state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ASSIST **Events**:
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
-- * **Start**: The process is started.
|
||||
-- * **Next**: The process is smoking the targets in the given zone.
|
||||
--
|
||||
-- ### ACT_ASSIST **Event methods**:
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ASSIST **States**:
|
||||
--
|
||||
-- * **None**: The controllable did not receive route commands.
|
||||
-- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone.
|
||||
-- * **Smoking (*)**: The process is smoking the targets in the zone.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
-- ### ACT_ASSIST state transition methods:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{#ACT_ASSIST}
|
||||
--
|
||||
-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Core.Zone}.
|
||||
-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour.
|
||||
-- At random intervals, a new target is smoked.
|
||||
--
|
||||
-- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor:
|
||||
--
|
||||
-- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Act_Assist
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
do -- ACT_ASSIST
|
||||
|
||||
--- ACT_ASSIST class
|
||||
-- @type ACT_ASSIST
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ASSIST = {
|
||||
ClassName = "ACT_ASSIST",
|
||||
}
|
||||
|
||||
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
|
||||
-- @param #ACT_ASSIST self
|
||||
-- @return #ACT_ASSIST
|
||||
function ACT_ASSIST:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
self:AddTransition( "None", "Start", "AwaitSmoke" )
|
||||
self:AddTransition( "AwaitSmoke", "Next", "Smoking" )
|
||||
self:AddTransition( "Smoking", "Next", "AwaitSmoke" )
|
||||
self:AddTransition( "*", "Stop", "Success" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
self:AddEndState( "Failed" )
|
||||
self:AddEndState( "Success" )
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIST self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST:onafterStart( ProcessUnit, From, Event, To )
|
||||
|
||||
local ProcessGroup = ProcessUnit:GetGroup()
|
||||
local MissionMenu = self:GetMission():GetMenu( ProcessGroup )
|
||||
|
||||
local function MenuSmoke( MenuParam )
|
||||
local self = MenuParam.self
|
||||
local SmokeColor = MenuParam.SmokeColor
|
||||
self.SmokeColor = SmokeColor
|
||||
self:__Next( 1 )
|
||||
end
|
||||
|
||||
self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu )
|
||||
self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } )
|
||||
self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } )
|
||||
self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } )
|
||||
self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } )
|
||||
self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIST self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST:onafterStop( ProcessUnit, From, Event, To )
|
||||
|
||||
self.Menu:Remove() -- When stopped, remove the menus
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
|
||||
--- ACT_ASSIST_SMOKE_TARGETS_ZONE class
|
||||
-- @type ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIST
|
||||
ACT_ASSIST_SMOKE_TARGETS_ZONE = {
|
||||
ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE",
|
||||
}
|
||||
|
||||
-- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor()
|
||||
-- self:E("_Destructor")
|
||||
--
|
||||
-- self.Menu:Remove()
|
||||
-- self:EventRemoveAll()
|
||||
-- end
|
||||
|
||||
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
|
||||
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone )
|
||||
local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
self.TargetZone = TargetZone
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke )
|
||||
|
||||
self.TargetSetUnit = FsmSmoke.TargetSetUnit
|
||||
self.TargetZone = FsmSmoke.TargetZone
|
||||
end
|
||||
|
||||
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
|
||||
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone
|
||||
-- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone )
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
self.TargetZone = TargetZone
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To )
|
||||
|
||||
self.TargetSetUnit:ForEachUnit(
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( SmokeUnit )
|
||||
if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then
|
||||
SCHEDULER:New( self,
|
||||
function()
|
||||
if SmokeUnit:IsAlive() then
|
||||
SmokeUnit:Smoke( self.SmokeColor, 150 )
|
||||
end
|
||||
end, {}, math.random( 10, 60 )
|
||||
)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
483
Moose Development/Moose/Actions/Act_Route.lua
Normal file
483
Moose Development/Moose/Actions/Act_Route.lua
Normal file
@@ -0,0 +1,483 @@
|
||||
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
-- ## ACT_ROUTE state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ROUTE **Events**:
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
-- * **Start**: The process is started. The process will go into the Report state.
|
||||
-- * **Report**: The process is reporting to the player the route to be followed.
|
||||
-- * **Route**: The process is routing the controllable.
|
||||
-- * **Pause**: The process is pausing the route of the controllable.
|
||||
-- * **Arrive**: The controllable has arrived at a route point.
|
||||
-- * **More**: There are more route points that need to be followed. The process will go back into the Report state.
|
||||
-- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state.
|
||||
--
|
||||
-- ### ACT_ROUTE **Event methods**:
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ROUTE **States**:
|
||||
--
|
||||
-- * **None**: The controllable did not receive route commands.
|
||||
-- * **Arrived (*)**: The controllable has arrived at a route point.
|
||||
-- * **Aborted (*)**: The controllable has aborted the route path.
|
||||
-- * **Routing**: The controllable is understay to the route point.
|
||||
-- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around.
|
||||
-- * **Success (*)**: All route points were reached.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
-- ### ACT_ROUTE state transition methods:
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{#ACT_ROUTE}
|
||||
--
|
||||
-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{Core.Zone}.
|
||||
-- The player receives on perioding times messages with the coordinates of the route to follow.
|
||||
-- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended.
|
||||
--
|
||||
-- # 1.1) ACT_ROUTE_ZONE constructor:
|
||||
--
|
||||
-- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Act_Route
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
do -- ACT_ROUTE
|
||||
|
||||
--- ACT_ROUTE class
|
||||
-- @type ACT_ROUTE
|
||||
-- @field Tasking.Task#TASK TASK
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
-- @field Core.Point#COORDINATE Coordinate
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ROUTE = {
|
||||
ClassName = "ACT_ROUTE",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE self
|
||||
function ACT_ROUTE:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
self:AddTransition( "*", "Reset", "None" )
|
||||
self:AddTransition( "None", "Start", "Routing" )
|
||||
self:AddTransition( "*", "Report", "*" )
|
||||
self:AddTransition( "Routing", "Route", "Routing" )
|
||||
self:AddTransition( "Routing", "Pause", "Pausing" )
|
||||
self:AddTransition( "Routing", "Arrive", "Arrived" )
|
||||
self:AddTransition( "*", "Cancel", "Cancelled" )
|
||||
self:AddTransition( "Arrived", "Success", "Success" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
self:AddTransition( "", "", "" )
|
||||
self:AddTransition( "", "", "" )
|
||||
|
||||
self:AddEndState( "Arrived" )
|
||||
self:AddEndState( "Failed" )
|
||||
self:AddEndState( "Cancelled" )
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
self:SetRouteMode( "C" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set a Cancel Menu item.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE
|
||||
function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag )
|
||||
|
||||
self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New(
|
||||
MenuGroup,
|
||||
MenuText,
|
||||
ParentMenu,
|
||||
self.MenuCancel,
|
||||
self
|
||||
):SetTime( MenuTime ):SetTag( MenuTag )
|
||||
|
||||
ParentMenu:SetTime( MenuTime )
|
||||
|
||||
ParentMenu:Remove( MenuTime, MenuTag )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the route mode.
|
||||
-- There are 2 route modes supported:
|
||||
--
|
||||
-- * SetRouteMode( "B" ): Route mode is Bearing and Range.
|
||||
-- * SetRouteMode( "C" ): Route mode is LL or MGRS according coordinate system setup.
|
||||
--
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE
|
||||
function ACT_ROUTE:SetRouteMode( RouteMode )
|
||||
|
||||
self.RouteMode = RouteMode
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the routing text to be displayed.
|
||||
-- The route mode determines the text displayed.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT Controllable
|
||||
-- @return #string
|
||||
function ACT_ROUTE:GetRouteText( Controllable )
|
||||
|
||||
local RouteText = ""
|
||||
|
||||
local Coordinate = nil -- Core.Point#COORDINATE
|
||||
|
||||
if self.Coordinate then
|
||||
Coordinate = self.Coordinate
|
||||
end
|
||||
|
||||
if self.Zone then
|
||||
Coordinate = self.Zone:GetPointVec3( self.Altitude )
|
||||
Coordinate:SetHeading( self.Heading )
|
||||
end
|
||||
|
||||
|
||||
local Task = self:GetTask() -- This is to dermine that the coordinates are for a specific task mode (A2A or A2G).
|
||||
local CC = self:GetTask():GetMission():GetCommandCenter()
|
||||
if CC then
|
||||
if CC:IsModeWWII() then
|
||||
-- Find closest reference point to the target.
|
||||
local ShortestDistance = 0
|
||||
local ShortestReferencePoint = nil
|
||||
local ShortestReferenceName = ""
|
||||
self:F( { CC.ReferencePoints } )
|
||||
for ZoneName, Zone in pairs( CC.ReferencePoints ) do
|
||||
self:F( { ZoneName = ZoneName } )
|
||||
local Zone = Zone -- Core.Zone#ZONE
|
||||
local ZoneCoord = Zone:GetCoordinate()
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate )
|
||||
self:F( { ShortestDistance, ShortestReferenceName } )
|
||||
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
|
||||
ShortestDistance = ZoneDistance
|
||||
ShortestReferencePoint = ZoneCoord
|
||||
ShortestReferenceName = CC.ReferenceNames[ZoneName]
|
||||
end
|
||||
end
|
||||
if ShortestReferencePoint then
|
||||
RouteText = Coordinate:ToStringFromRP( ShortestReferencePoint, ShortestReferenceName, Controllable )
|
||||
end
|
||||
else
|
||||
RouteText = Coordinate:ToString( Controllable, nil, Task )
|
||||
end
|
||||
end
|
||||
|
||||
return RouteText
|
||||
end
|
||||
|
||||
|
||||
function ACT_ROUTE:MenuCancel()
|
||||
self:F("Cancelled")
|
||||
self.CancelMenuGroupCommand:Remove()
|
||||
self:__Cancel( 1 )
|
||||
end
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE:onafterStart( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
--- Check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @return #boolean
|
||||
function ACT_ROUTE:onfuncHasArrived( ProcessUnit )
|
||||
return false
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To )
|
||||
|
||||
if ProcessUnit:IsAlive() then
|
||||
local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic
|
||||
if self.DisplayCount >= self.DisplayInterval then
|
||||
self:T( { HasArrived = HasArrived } )
|
||||
if not HasArrived then
|
||||
self:Report()
|
||||
end
|
||||
self.DisplayCount = 1
|
||||
else
|
||||
self.DisplayCount = self.DisplayCount + 1
|
||||
end
|
||||
|
||||
if HasArrived then
|
||||
self:__Arrive( 1 )
|
||||
else
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
return HasArrived -- if false, then the event will not be executed...
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
end -- ACT_ROUTE
|
||||
|
||||
|
||||
do -- ACT_ROUTE_POINT
|
||||
|
||||
--- ACT_ROUTE_POINT class
|
||||
-- @type ACT_ROUTE_POINT
|
||||
-- @field Tasking.Task#TASK TASK
|
||||
-- @extends #ACT_ROUTE
|
||||
ACT_ROUTE_POINT = {
|
||||
ClassName = "ACT_ROUTE_POINT",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new routing state machine.
|
||||
-- The task will route a controllable to a Coordinate until the controllable is within the Range.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Core.Point#COORDINATE The Coordinate to Target.
|
||||
-- @param #number Range The Distance to Target.
|
||||
-- @param Core.Zone#ZONE_BASE Zone
|
||||
function ACT_ROUTE_POINT:New( Coordinate, Range )
|
||||
local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_POINT
|
||||
|
||||
self.Coordinate = Coordinate
|
||||
self.Range = Range or 0
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new routing state machine.
|
||||
-- The task will route a controllable to a Coordinate until the controllable is within the Range.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
function ACT_ROUTE_POINT:Init( FsmRoute )
|
||||
|
||||
self.Coordinate = FsmRoute.Coordinate
|
||||
self.Range = FsmRoute.Range or 0
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
self:SetStartState("None")
|
||||
end
|
||||
|
||||
--- Set Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Core.Point#COORDINATE Coordinate The Coordinate to route to.
|
||||
function ACT_ROUTE_POINT:SetCoordinate( Coordinate )
|
||||
self:F2( { Coordinate } )
|
||||
self.Coordinate = Coordinate
|
||||
end
|
||||
|
||||
--- Get Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @return Core.Point#COORDINATE Coordinate The Coordinate to route to.
|
||||
function ACT_ROUTE_POINT:GetCoordinate()
|
||||
self:F2( { self.Coordinate } )
|
||||
return self.Coordinate
|
||||
end
|
||||
|
||||
--- Set Range around Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param #number Range The Range to consider the arrival. Default is 10000 meters.
|
||||
function ACT_ROUTE_POINT:SetRange( Range )
|
||||
self:F2( { Range } )
|
||||
self.Range = Range or 10000
|
||||
end
|
||||
|
||||
--- Get Range around Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @return #number The Range to consider the arrival. Default is 10000 meters.
|
||||
function ACT_ROUTE_POINT:GetRange()
|
||||
self:F2( { self.Range } )
|
||||
return self.Range
|
||||
end
|
||||
|
||||
--- Method override to check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @return #boolean
|
||||
function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit )
|
||||
|
||||
if ProcessUnit:IsAlive() then
|
||||
local Distance = self.Coordinate:Get2DDistance( ProcessUnit:GetCoordinate() )
|
||||
|
||||
if Distance <= self.Range then
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived."
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To )
|
||||
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit )
|
||||
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
|
||||
end
|
||||
|
||||
end -- ACT_ROUTE_POINT
|
||||
|
||||
|
||||
do -- ACT_ROUTE_ZONE
|
||||
|
||||
--- ACT_ROUTE_ZONE class
|
||||
-- @type ACT_ROUTE_ZONE
|
||||
-- @field Tasking.Task#TASK TASK
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
-- @extends #ACT_ROUTE
|
||||
ACT_ROUTE_ZONE = {
|
||||
ClassName = "ACT_ROUTE_ZONE",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE.
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE Zone
|
||||
function ACT_ROUTE_ZONE:New( Zone )
|
||||
local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE
|
||||
|
||||
self.Zone = Zone
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ROUTE_ZONE:Init( FsmRoute )
|
||||
|
||||
self.Zone = FsmRoute.Zone
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
end
|
||||
|
||||
--- Set Zone
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to.
|
||||
-- @param #number Altitude
|
||||
-- @param #number Heading
|
||||
function ACT_ROUTE_ZONE:SetZone( Zone, Altitude, Heading ) -- R2.2 Added altitude and heading
|
||||
self.Zone = Zone
|
||||
self.Altitude = Altitude
|
||||
self.Heading = Heading
|
||||
end
|
||||
|
||||
--- Get Zone
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @return Core.Zone#ZONE_BASE Zone The Zone object where to route to.
|
||||
function ACT_ROUTE_ZONE:GetZone()
|
||||
return self.Zone
|
||||
end
|
||||
|
||||
--- Method override to check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @return #boolean
|
||||
function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit )
|
||||
|
||||
if ProcessUnit:IsInZone( self.Zone ) then
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived within the zone."
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
end
|
||||
|
||||
return ProcessUnit:IsInZone( self.Zone )
|
||||
end
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE_ZONE:onafterReport( ProcessUnit, From, Event, To )
|
||||
self:F( { ProcessUnit = ProcessUnit } )
|
||||
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit )
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
|
||||
end
|
||||
|
||||
end -- ACT_ROUTE_ZONE
|
||||
1399
Moose Development/Moose/Cargo/Cargo.lua
Normal file
1399
Moose Development/Moose/Cargo/Cargo.lua
Normal file
File diff suppressed because it is too large
Load Diff
337
Moose Development/Moose/Cargo/CargoCrate.lua
Normal file
337
Moose Development/Moose/Cargo/CargoCrate.lua
Normal file
@@ -0,0 +1,337 @@
|
||||
--- **Cargo** - Management of single cargo crates, which are based on a STATIC object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions]()
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Cargo.CargoCrate
|
||||
-- @image Cargo_Crates.JPG
|
||||
|
||||
do -- CARGO_CRATE
|
||||
|
||||
--- Models the behaviour of cargo crates, which can be slingloaded and boarded on helicopters.
|
||||
-- @type CARGO_CRATE
|
||||
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
|
||||
|
||||
--- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
|
||||
-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_CRATE objects to and from carriers.
|
||||
--
|
||||
-- The above cargo classes are used by the following AI_CARGO_ classes to allow AI groups to transport cargo:
|
||||
--
|
||||
-- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC} module.
|
||||
-- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter} module.
|
||||
-- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane} module.
|
||||
-- * AI Ships is planned.
|
||||
--
|
||||
-- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking:
|
||||
--
|
||||
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players.
|
||||
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #CARGO_CRATE
|
||||
CARGO_CRATE = {
|
||||
ClassName = "CARGO_CRATE"
|
||||
}
|
||||
|
||||
--- CARGO_CRATE Constructor.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @param Wrapper.Static#STATIC CargoStatic
|
||||
-- @param #string Type
|
||||
-- @param #string Name
|
||||
-- @param #number LoadRadius (optional)
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @return #CARGO_CRATE
|
||||
function CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_CRATE
|
||||
self:T( { Type, Name, NearRadius } )
|
||||
|
||||
self.CargoObject = CargoStatic -- Wrapper.Static#STATIC
|
||||
|
||||
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
|
||||
_EVENTDISPATCHER:CreateEventNewCargo( self )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
|
||||
--self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
|
||||
|
||||
self:SetEventPriority( 4 )
|
||||
|
||||
self.NearRadius = NearRadius or 25
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #CARGO_CRATE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function CARGO_CRATE:OnEventCargoDead( EventData )
|
||||
|
||||
local Destroyed = false
|
||||
|
||||
if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() then
|
||||
if self.CargoObject:GetName() == EventData.IniUnitName then
|
||||
if not self.NoDestroy then
|
||||
Destroyed = true
|
||||
end
|
||||
end
|
||||
else
|
||||
if self:IsLoaded() then
|
||||
local CarrierName = self.CargoCarrier:GetName()
|
||||
if CarrierName == EventData.IniDCSUnitName then
|
||||
MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll()
|
||||
Destroyed = true
|
||||
self.CargoCarrier:ClearCargo()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Destroyed then
|
||||
self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } )
|
||||
self:Destroyed()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Enter UnLoaded State.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2
|
||||
function CARGO_CRATE:onenterUnLoaded( From, Event, To, ToPointVec2 )
|
||||
--self:T( { ToPointVec2, From, Event, To } )
|
||||
|
||||
local Angle = 180
|
||||
local Speed = 10
|
||||
local Distance = 10
|
||||
|
||||
if From == "Loaded" then
|
||||
local StartCoordinate = self.CargoCarrier:GetCoordinate()
|
||||
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
|
||||
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
|
||||
local CargoDeployCoord = StartCoordinate:Translate( Distance, CargoDeployHeading )
|
||||
|
||||
ToPointVec2 = ToPointVec2 or COORDINATE:NewFromVec2( { x= CargoDeployCoord.x, y = CargoDeployCoord.z } )
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self.CargoObject:ReSpawnAt( ToPointVec2, 0 )
|
||||
self.CargoCarrier = nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if self.OnUnLoadedCallBack then
|
||||
self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) )
|
||||
self.OnUnLoadedCallBack = nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Loaded State.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT CargoCarrier
|
||||
function CARGO_CRATE:onenterLoaded( From, Event, To, CargoCarrier )
|
||||
--self:T( { From, Event, To, CargoCarrier } )
|
||||
|
||||
self.CargoCarrier = CargoCarrier
|
||||
|
||||
-- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects).
|
||||
if self.CargoObject then
|
||||
self:T("Destroying")
|
||||
self.NoDestroy = true
|
||||
self.CargoObject:Destroy( false ) -- Do not generate a remove unit event, because we want to keep the template for later respawn in the database.
|
||||
--local Coordinate = self.CargoObject:GetCoordinate():GetRandomCoordinateInRadius( 50, 20 )
|
||||
--self.CargoObject:ReSpawnAt( Coordinate, 0 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the cargo can be Boarded.
|
||||
-- @param #CARGO_CRATE self
|
||||
function CARGO_CRATE:CanBoard()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if the cargo can be Unboarded.
|
||||
-- @param #CARGO_CRATE self
|
||||
function CARGO_CRATE:CanUnboard()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if the cargo can be sling loaded.
|
||||
-- @param #CARGO_CRATE self
|
||||
function CARGO_CRATE:CanSlingload()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @return #boolean true if the Cargo Crate is within the report radius.
|
||||
function CARGO_CRATE:IsInReportRadius( Coordinate )
|
||||
--self:T( { Coordinate, LoadRadius = self.LoadRadius } )
|
||||
|
||||
local Distance = 0
|
||||
if self:IsUnLoaded() then
|
||||
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
|
||||
--self:T( Distance )
|
||||
if Distance <= self.LoadRadius then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Check if Cargo Crate is in the radius for the Cargo to be Boarded or Loaded.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @param Core.Point#Coordinate Coordinate
|
||||
-- @return #boolean true if the Cargo Crate is within the loading radius.
|
||||
function CARGO_CRATE:IsInLoadRadius( Coordinate )
|
||||
--self:T( { Coordinate, LoadRadius = self.NearRadius } )
|
||||
|
||||
local Distance = 0
|
||||
if self:IsUnLoaded() then
|
||||
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
|
||||
--self:T( Distance )
|
||||
if Distance <= self.NearRadius then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Get the current Coordinate of the CargoGroup.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
|
||||
-- @return #nil There is no valid Cargo in the CargoGroup.
|
||||
function CARGO_CRATE:GetCoordinate()
|
||||
--self:T()
|
||||
|
||||
return self.CargoObject:GetCoordinate()
|
||||
end
|
||||
|
||||
--- Check if the CargoGroup is alive.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @return #boolean true if the CargoGroup is alive.
|
||||
-- @return #boolean false if the CargoGroup is dead.
|
||||
function CARGO_CRATE:IsAlive()
|
||||
|
||||
local Alive = true
|
||||
|
||||
-- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive.
|
||||
-- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive.
|
||||
if self:IsLoaded() then
|
||||
Alive = Alive == true and self.CargoCarrier:IsAlive()
|
||||
else
|
||||
Alive = Alive == true and self.CargoObject:IsAlive()
|
||||
end
|
||||
|
||||
return Alive
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Route Cargo to Coordinate and randomize locations.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
function CARGO_CRATE:RouteTo( Coordinate )
|
||||
self:T( {Coordinate = Coordinate } )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Check if Cargo is near to the Carrier.
|
||||
-- The Cargo is near to the Carrier within NearRadius.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @param #number NearRadius
|
||||
-- @return #boolean The Cargo is near to the Carrier.
|
||||
-- @return #nil The Cargo is not near to the Carrier.
|
||||
function CARGO_CRATE:IsNear( CargoCarrier, NearRadius )
|
||||
self:T( {NearRadius = NearRadius } )
|
||||
|
||||
return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius )
|
||||
end
|
||||
|
||||
--- Respawn the CargoGroup.
|
||||
-- @param #CARGO_CRATE self
|
||||
function CARGO_CRATE:Respawn()
|
||||
|
||||
self:T( { "Respawning crate " .. self:GetName() } )
|
||||
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event.
|
||||
self:__Reset( -0.1 )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Respawn the CargoGroup.
|
||||
-- @param #CARGO_CRATE self
|
||||
function CARGO_CRATE:onafterReset()
|
||||
|
||||
self:T( { "Reset crate " .. self:GetName() } )
|
||||
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self:SetDeployed( false )
|
||||
self:SetStartState( "UnLoaded" )
|
||||
self.CargoCarrier = nil
|
||||
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
|
||||
_EVENTDISPATCHER:CreateEventNewCargo( self )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Get the transportation method of the Cargo.
|
||||
-- @param #CARGO_CRATE self
|
||||
-- @return #string The transportation method of the Cargo.
|
||||
function CARGO_CRATE:GetTransportationMethod()
|
||||
if self:IsLoaded() then
|
||||
return "for unloading"
|
||||
else
|
||||
if self:IsUnLoaded() then
|
||||
return "for loading"
|
||||
else
|
||||
if self:IsDeployed() then
|
||||
return "delivered"
|
||||
end
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
773
Moose Development/Moose/Cargo/CargoGroup.lua
Normal file
773
Moose Development/Moose/Cargo/CargoGroup.lua
Normal file
@@ -0,0 +1,773 @@
|
||||
--- **Cargo** - Management of grouped cargo logistics, which are based on a GROUP object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions]()
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Cargo.CargoGroup
|
||||
-- @image Cargo_Groups.JPG
|
||||
|
||||
|
||||
do -- CARGO_GROUP
|
||||
|
||||
--- @type CARGO_GROUP
|
||||
-- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects.
|
||||
-- @field #string GroupName The name of the CargoGroup.
|
||||
-- @extends Cargo.Cargo#CARGO_REPORTABLE
|
||||
|
||||
--- Defines a cargo that is represented by a @{Wrapper.Group} object within the simulator.
|
||||
-- The cargo can be Loaded, UnLoaded, Boarded, UnBoarded to and from Carriers.
|
||||
--
|
||||
-- The above cargo classes are used by the following AI_CARGO_ classes to allow AI groups to transport cargo:
|
||||
--
|
||||
-- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC} module.
|
||||
-- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter} module.
|
||||
-- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane} module.
|
||||
-- * AI Ships is planned.
|
||||
--
|
||||
-- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking:
|
||||
--
|
||||
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players.
|
||||
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- @field #CARGO_GROUP CARGO_GROUP
|
||||
--
|
||||
CARGO_GROUP = {
|
||||
ClassName = "CARGO_GROUP",
|
||||
}
|
||||
|
||||
--- CARGO_GROUP constructor.
|
||||
-- This make a new CARGO_GROUP from a @{Wrapper.Group} object.
|
||||
-- It will "ungroup" the group object within the sim, and will create a @{Core.Set} of individual Unit objects.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param Wrapper.Group#GROUP CargoGroup Group to be transported as cargo.
|
||||
-- @param #string Type Cargo type, e.g. "Infantry". This is the type used in SET_CARGO:New():FilterTypes("Infantry") to define the valid cargo groups of the set.
|
||||
-- @param #string Name A user defined name of the cargo group. This name CAN be the same as the group object but can also have a different name. This name MUST be unique!
|
||||
-- @param #number LoadRadius (optional) Distance in meters until which a cargo is loaded into the carrier. Cargo outside this radius has to be routed by other means to within the radius to be loaded.
|
||||
-- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene.
|
||||
-- @return #CARGO_GROUP Cargo group object.
|
||||
function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CAROG_REPORTABLE
|
||||
local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP
|
||||
self:T( { Type, Name, LoadRadius } )
|
||||
|
||||
self.CargoSet = SET_CARGO:New()
|
||||
self.CargoGroup = CargoGroup
|
||||
self.Grouped = true
|
||||
self.CargoUnitTemplate = {}
|
||||
|
||||
self.NearRadius = NearRadius
|
||||
|
||||
self:SetDeployed( false )
|
||||
|
||||
local WeightGroup = 0
|
||||
local VolumeGroup = 0
|
||||
|
||||
self.CargoGroup:Destroy() -- destroy and generate a unit removal event, so that the database gets cleaned, and the linked sets get properly cleaned.
|
||||
|
||||
local GroupName = CargoGroup:GetName()
|
||||
self.CargoName = Name
|
||||
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
|
||||
|
||||
-- Deactivate late activation.
|
||||
self.CargoTemplate.lateActivation=false
|
||||
|
||||
self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
|
||||
self.GroupTemplate.name = self.CargoName .. "#CARGO"
|
||||
self.GroupTemplate.groupId = nil
|
||||
|
||||
self.GroupTemplate.units = {}
|
||||
|
||||
for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do
|
||||
UnitTemplate.name = UnitTemplate.name .. "#CARGO"
|
||||
local CargoUnitName = UnitTemplate.name
|
||||
self.CargoUnitTemplate[CargoUnitName] = UnitTemplate
|
||||
|
||||
self.GroupTemplate.units[#self.GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
|
||||
self.GroupTemplate.units[#self.GroupTemplate.units].unitId = nil
|
||||
|
||||
-- And we register the spawned unit as part of the CargoSet.
|
||||
local Unit = UNIT:Register( CargoUnitName )
|
||||
|
||||
end
|
||||
|
||||
-- Then we register the new group in the database
|
||||
self.CargoGroup = GROUP:NewTemplate( self.GroupTemplate, self.GroupTemplate.CoalitionID, self.GroupTemplate.CategoryID, self.GroupTemplate.CountryID )
|
||||
|
||||
-- Now we spawn the new group based on the template created.
|
||||
self.CargoObject = _DATABASE:Spawn( self.GroupTemplate )
|
||||
|
||||
for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do
|
||||
|
||||
|
||||
local CargoUnitName = CargoUnit:GetName()
|
||||
|
||||
local Cargo = CARGO_UNIT:New( CargoUnit, Type, CargoUnitName, LoadRadius, NearRadius )
|
||||
self.CargoSet:Add( CargoUnitName, Cargo )
|
||||
|
||||
WeightGroup = WeightGroup + Cargo:GetWeight()
|
||||
|
||||
end
|
||||
|
||||
self:SetWeight( WeightGroup )
|
||||
|
||||
self:T( { "Weight Cargo", WeightGroup } )
|
||||
|
||||
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
|
||||
_EVENTDISPATCHER:CreateEventNewCargo( self )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
|
||||
--self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
|
||||
|
||||
self:SetEventPriority( 4 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Respawn the CargoGroup.
|
||||
-- @param #CARGO_GROUP self
|
||||
function CARGO_GROUP:Respawn()
|
||||
|
||||
self:T( { "Respawning" } )
|
||||
|
||||
for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do
|
||||
local Cargo = CargoData -- Cargo.Cargo#CARGO
|
||||
Cargo:Destroy() -- Destroy the cargo and generate a remove unit event to update the sets.
|
||||
Cargo:SetStartState( "UnLoaded" )
|
||||
end
|
||||
|
||||
-- Now we spawn the new group based on the template created.
|
||||
_DATABASE:Spawn( self.GroupTemplate )
|
||||
|
||||
for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do
|
||||
|
||||
local CargoUnitName = CargoUnit:GetName()
|
||||
|
||||
local Cargo = CARGO_UNIT:New( CargoUnit, self.Type, CargoUnitName, self.LoadRadius )
|
||||
self.CargoSet:Add( CargoUnitName, Cargo )
|
||||
|
||||
end
|
||||
|
||||
self:SetDeployed( false )
|
||||
self:SetStartState( "UnLoaded" )
|
||||
|
||||
end
|
||||
|
||||
--- Ungroup the cargo group into individual groups with one unit.
|
||||
-- This is required because by default a group will move in formation and this is really an issue for group control.
|
||||
-- Therefore this method is made to be able to ungroup a group.
|
||||
-- This works for ground only groups.
|
||||
-- @param #CARGO_GROUP self
|
||||
function CARGO_GROUP:Ungroup()
|
||||
|
||||
if self.Grouped == true then
|
||||
|
||||
self.Grouped = false
|
||||
|
||||
self.CargoGroup:Destroy()
|
||||
|
||||
for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do
|
||||
local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT
|
||||
|
||||
if CargoUnit:IsUnLoaded() then
|
||||
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
|
||||
--local GroupName = env.getValueDictByKey( GroupTemplate.name )
|
||||
|
||||
-- We create a new group object with one unit...
|
||||
-- First we prepare the template...
|
||||
GroupTemplate.name = self.CargoName .. "#CARGO#" .. CargoUnitName
|
||||
GroupTemplate.groupId = nil
|
||||
|
||||
if CargoUnit:IsUnLoaded() then
|
||||
GroupTemplate.units = {}
|
||||
GroupTemplate.units[1] = self.CargoUnitTemplate[CargoUnitName]
|
||||
GroupTemplate.units[#GroupTemplate.units].unitId = nil
|
||||
GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX()
|
||||
GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY()
|
||||
GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading()
|
||||
end
|
||||
|
||||
|
||||
-- Then we register the new group in the database
|
||||
local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
|
||||
|
||||
-- Now we spawn the new group based on the template created.
|
||||
_DATABASE:Spawn( GroupTemplate )
|
||||
end
|
||||
end
|
||||
|
||||
self.CargoObject = nil
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Regroup the cargo group into one group with multiple unit.
|
||||
-- This is required because by default a group will move in formation and this is really an issue for group control.
|
||||
-- Therefore this method is made to be able to regroup a group.
|
||||
-- This works for ground only groups.
|
||||
-- @param #CARGO_GROUP self
|
||||
function CARGO_GROUP:Regroup()
|
||||
|
||||
self:T("Regroup")
|
||||
|
||||
if self.Grouped == false then
|
||||
|
||||
self.Grouped = true
|
||||
|
||||
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
|
||||
GroupTemplate.name = self.CargoName .. "#CARGO"
|
||||
GroupTemplate.groupId = nil
|
||||
GroupTemplate.units = {}
|
||||
|
||||
for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do
|
||||
local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT
|
||||
|
||||
self:T( { CargoUnit:GetName(), UnLoaded = CargoUnit:IsUnLoaded() } )
|
||||
|
||||
if CargoUnit:IsUnLoaded() then
|
||||
|
||||
CargoUnit.CargoObject:Destroy()
|
||||
|
||||
GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
|
||||
GroupTemplate.units[#GroupTemplate.units].unitId = nil
|
||||
GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX()
|
||||
GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY()
|
||||
GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading()
|
||||
end
|
||||
end
|
||||
|
||||
-- Then we register the new group in the database
|
||||
self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID )
|
||||
|
||||
self:T( { "Regroup", GroupTemplate } )
|
||||
|
||||
-- Now we spawn the new group based on the template created.
|
||||
self.CargoObject = _DATABASE:Spawn( GroupTemplate )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #CARGO_GROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function CARGO_GROUP:OnEventCargoDead( EventData )
|
||||
|
||||
self:T(EventData)
|
||||
|
||||
local Destroyed = false
|
||||
|
||||
if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() or self:IsUnboarding() then
|
||||
Destroyed = true
|
||||
for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do
|
||||
local Cargo = CargoData -- Cargo.Cargo#CARGO
|
||||
if Cargo:IsAlive() then
|
||||
Destroyed = false
|
||||
else
|
||||
Cargo:Destroyed()
|
||||
end
|
||||
end
|
||||
else
|
||||
local CarrierName = self.CargoCarrier:GetName()
|
||||
if CarrierName == EventData.IniDCSUnitName then
|
||||
MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll()
|
||||
Destroyed = true
|
||||
self.CargoCarrier:ClearCargo()
|
||||
end
|
||||
end
|
||||
|
||||
if Destroyed then
|
||||
self:Destroyed()
|
||||
self:T( { "Cargo group destroyed" } )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- After Board Event.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT CargoCarrier
|
||||
-- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier.
|
||||
function CARGO_GROUP:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... )
|
||||
self:T( { CargoCarrier.UnitName, From, Event, To, NearRadius = NearRadius } )
|
||||
|
||||
NearRadius = NearRadius or self.NearRadius
|
||||
|
||||
-- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2
|
||||
self.CargoSet:ForEach(
|
||||
function( Cargo, ... )
|
||||
self:T( { "Board Unit", Cargo:GetName( ), Cargo:IsDestroyed(), Cargo.CargoObject:IsAlive() } )
|
||||
local CargoGroup = Cargo.CargoObject --Wrapper.Group#GROUP
|
||||
CargoGroup:OptionAlarmStateGreen()
|
||||
Cargo:__Board( 1, CargoCarrier, NearRadius, ... )
|
||||
end, ...
|
||||
)
|
||||
|
||||
self:__Boarding( -1, CargoCarrier, NearRadius, ... )
|
||||
|
||||
end
|
||||
|
||||
--- Enter Loaded State.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT CargoCarrier
|
||||
function CARGO_GROUP:onafterLoad( From, Event, To, CargoCarrier, ... )
|
||||
--self:T( { From, Event, To, CargoCarrier, ...} )
|
||||
|
||||
if From == "UnLoaded" then
|
||||
-- For each Cargo object within the CARGO_GROUP, load each cargo to the CargoCarrier.
|
||||
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
|
||||
if not Cargo:IsDestroyed() then
|
||||
Cargo:Load( CargoCarrier )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--self.CargoObject:Destroy()
|
||||
self.CargoCarrier = CargoCarrier
|
||||
self.CargoCarrier:AddCargo( self )
|
||||
|
||||
end
|
||||
|
||||
--- Leave Boarding State.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT CargoCarrier
|
||||
-- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier.
|
||||
function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
|
||||
--self:T( { CargoCarrier.UnitName, From, Event, To } )
|
||||
|
||||
local Boarded = true
|
||||
local Cancelled = false
|
||||
local Dead = true
|
||||
|
||||
self.CargoSet:Flush()
|
||||
|
||||
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
|
||||
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
|
||||
--self:T( { Cargo:GetName(), Cargo.current } )
|
||||
|
||||
|
||||
if not Cargo:is( "Loaded" )
|
||||
and (not Cargo:is( "Destroyed" )) then -- If one or more units of a group defined as CARGO_GROUP died, the CARGO_GROUP:Board() command does not trigger the CARGO_GRUOP:OnEnterLoaded() function.
|
||||
Boarded = false
|
||||
end
|
||||
|
||||
if Cargo:is( "UnLoaded" ) then
|
||||
Cancelled = true
|
||||
end
|
||||
|
||||
if not Cargo:is( "Destroyed" ) then
|
||||
Dead = false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if not Dead then
|
||||
|
||||
if not Cancelled then
|
||||
if not Boarded then
|
||||
self:__Boarding( -5, CargoCarrier, NearRadius, ... )
|
||||
else
|
||||
self:T("Group Cargo is loaded")
|
||||
self:__Load( 1, CargoCarrier, ... )
|
||||
end
|
||||
else
|
||||
self:__CancelBoarding( 1, CargoCarrier, NearRadius, ... )
|
||||
end
|
||||
else
|
||||
self:__Destroyed( 1, CargoCarrier, NearRadius, ... )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Enter UnBoarding State.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
-- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier.
|
||||
function CARGO_GROUP:onafterUnBoard( From, Event, To, ToPointVec2, NearRadius, ... )
|
||||
self:T( {From, Event, To, ToPointVec2, NearRadius } )
|
||||
|
||||
NearRadius = NearRadius or 25
|
||||
|
||||
local Timer = 1
|
||||
|
||||
if From == "Loaded" then
|
||||
|
||||
if self.CargoObject then
|
||||
self.CargoObject:Destroy()
|
||||
end
|
||||
|
||||
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
|
||||
self.CargoSet:ForEach(
|
||||
--- @param Cargo.Cargo#CARGO Cargo
|
||||
function( Cargo, NearRadius )
|
||||
if not Cargo:IsDestroyed() then
|
||||
local ToVec=nil
|
||||
if ToPointVec2==nil then
|
||||
ToVec=self.CargoCarrier:GetPointVec2():GetRandomPointVec2InRadius(2*NearRadius, NearRadius)
|
||||
else
|
||||
ToVec=ToPointVec2
|
||||
end
|
||||
Cargo:__UnBoard( Timer, ToVec, NearRadius )
|
||||
Timer = Timer + 1
|
||||
end
|
||||
end, { NearRadius }
|
||||
)
|
||||
|
||||
|
||||
self:__UnBoarding( 1, ToPointVec2, NearRadius, ... )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Leave UnBoarding State.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
-- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier.
|
||||
function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... )
|
||||
--self:T( { From, Event, To, ToPointVec2, NearRadius } )
|
||||
|
||||
--local NearRadius = NearRadius or 25
|
||||
|
||||
local Angle = 180
|
||||
local Speed = 10
|
||||
local Distance = 5
|
||||
|
||||
if From == "UnBoarding" then
|
||||
local UnBoarded = true
|
||||
|
||||
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
|
||||
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
|
||||
self:T( { Cargo:GetName(), Cargo.current } )
|
||||
if not Cargo:is( "UnLoaded" ) and not Cargo:IsDestroyed() then
|
||||
UnBoarded = false
|
||||
end
|
||||
end
|
||||
|
||||
if UnBoarded then
|
||||
self:__UnLoad( 1, ToPointVec2, ... )
|
||||
else
|
||||
self:__UnBoarding( 1, ToPointVec2, NearRadius, ... )
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Enter UnLoaded State.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... )
|
||||
--self:T( { From, Event, To, ToPointVec2 } )
|
||||
|
||||
if From == "Loaded" then
|
||||
|
||||
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
|
||||
self.CargoSet:ForEach(
|
||||
function( Cargo )
|
||||
--Cargo:UnLoad( ToPointVec2 )
|
||||
local RandomVec2=nil
|
||||
if ToPointVec2 then
|
||||
RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
|
||||
end
|
||||
Cargo:UnBoard( RandomVec2 )
|
||||
end
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
self.CargoCarrier:RemoveCargo( self )
|
||||
self.CargoCarrier = nil
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Get the current Coordinate of the CargoGroup.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
|
||||
-- @return #nil There is no valid Cargo in the CargoGroup.
|
||||
function CARGO_GROUP:GetCoordinate()
|
||||
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
|
||||
|
||||
if Cargo then
|
||||
return Cargo.CargoObject:GetCoordinate()
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get the x position of the cargo.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @return #number
|
||||
function CARGO:GetX()
|
||||
|
||||
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
|
||||
|
||||
if Cargo then
|
||||
return Cargo:GetCoordinate().x
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get the y position of the cargo.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @return #number
|
||||
function CARGO:GetY()
|
||||
|
||||
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
|
||||
|
||||
if Cargo then
|
||||
return Cargo:GetCoordinate().z
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Check if the CargoGroup is alive.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @return #boolean true if the CargoGroup is alive.
|
||||
-- @return #boolean false if the CargoGroup is dead.
|
||||
function CARGO_GROUP:IsAlive()
|
||||
|
||||
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
|
||||
return Cargo ~= nil
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Get the first alive Cargo Unit of the Cargo Group.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @return #CARGO_GROUP
|
||||
function CARGO_GROUP:GetFirstAlive()
|
||||
|
||||
local CargoFirstAlive = nil
|
||||
|
||||
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
|
||||
if not Cargo:IsDestroyed() then
|
||||
CargoFirstAlive = Cargo
|
||||
break
|
||||
end
|
||||
end
|
||||
return CargoFirstAlive
|
||||
end
|
||||
|
||||
|
||||
--- Get the amount of cargo units in the group.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @return #CARGO_GROUP
|
||||
function CARGO_GROUP:GetCount()
|
||||
return self.CargoSet:Count()
|
||||
end
|
||||
|
||||
|
||||
--- Get the amount of cargo units in the group.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @return #CARGO_GROUP
|
||||
function CARGO_GROUP:GetGroup( Cargo )
|
||||
local Cargo = Cargo or self:GetFirstAlive() -- Cargo.Cargo#CARGO
|
||||
return Cargo.CargoObject:GetGroup()
|
||||
end
|
||||
|
||||
|
||||
--- Route Cargo to Coordinate and randomize locations.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
function CARGO_GROUP:RouteTo( Coordinate )
|
||||
--self:T( {Coordinate = Coordinate } )
|
||||
|
||||
-- For each Cargo within the CargoSet, route each object to the Coordinate
|
||||
self.CargoSet:ForEach(
|
||||
function( Cargo )
|
||||
Cargo.CargoObject:RouteGroundTo( Coordinate, 10, "vee", 0 )
|
||||
end
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
--- Check if Cargo is near to the Carrier.
|
||||
-- The Cargo is near to the Carrier if the first unit of the Cargo Group is within NearRadius.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @param #number NearRadius
|
||||
-- @return #boolean The Cargo is near to the Carrier or #nil if the Cargo is not near to the Carrier.
|
||||
function CARGO_GROUP:IsNear( CargoCarrier, NearRadius )
|
||||
self:T( {NearRadius = NearRadius } )
|
||||
|
||||
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
if Cargo:IsAlive() then
|
||||
if Cargo:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) then
|
||||
self:T( "Near" )
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Check if Cargo Group is in the radius for the Cargo to be Boarded.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @return #boolean true if the Cargo Group is within the load radius.
|
||||
function CARGO_GROUP:IsInLoadRadius( Coordinate )
|
||||
--self:T( { Coordinate } )
|
||||
|
||||
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
|
||||
|
||||
if Cargo then
|
||||
local Distance = 0
|
||||
local CargoCoordinate
|
||||
if Cargo:IsLoaded() then
|
||||
CargoCoordinate = Cargo.CargoCarrier:GetCoordinate()
|
||||
else
|
||||
CargoCoordinate = Cargo.CargoObject:GetCoordinate()
|
||||
end
|
||||
|
||||
-- FF check if coordinate could be obtained. This was commented out for some (unknown) reason. But the check seems valid!
|
||||
if CargoCoordinate then
|
||||
Distance = Coordinate:Get2DDistance( CargoCoordinate )
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
self:T( { Distance = Distance, LoadRadius = self.LoadRadius } )
|
||||
if Distance <= self.LoadRadius then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Check if Cargo Group is in the report radius.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param Core.Point#Coordinate Coordinate
|
||||
-- @return #boolean true if the Cargo Group is within the report radius.
|
||||
function CARGO_GROUP:IsInReportRadius( Coordinate )
|
||||
--self:T( { Coordinate } )
|
||||
|
||||
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
|
||||
|
||||
if Cargo then
|
||||
self:T( { Cargo } )
|
||||
local Distance = 0
|
||||
if Cargo:IsUnLoaded() then
|
||||
Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() )
|
||||
--self:T( Distance )
|
||||
if Distance <= self.LoadRadius then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Signal a flare at the position of the CargoGroup.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param Utilities.Utils#FLARECOLOR FlareColor
|
||||
function CARGO_GROUP:Flare( FlareColor )
|
||||
|
||||
local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO
|
||||
if Cargo then
|
||||
Cargo:Flare( FlareColor )
|
||||
end
|
||||
end
|
||||
|
||||
--- Smoke the CargoGroup.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke.
|
||||
-- @param #number Radius The radius of randomization around the center of the first element of the CargoGroup.
|
||||
function CARGO_GROUP:Smoke( SmokeColor, Radius )
|
||||
|
||||
local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO
|
||||
|
||||
if Cargo then
|
||||
Cargo:Smoke( SmokeColor, Radius )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the first element of the CargoGroup is the given @{Core.Zone}.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @param Core.Zone#ZONE_BASE Zone
|
||||
-- @return #boolean **true** if the first element of the CargoGroup is in the Zone
|
||||
-- @return #boolean **false** if there is no element of the CargoGroup in the Zone.
|
||||
function CARGO_GROUP:IsInZone( Zone )
|
||||
--self:T( { Zone } )
|
||||
|
||||
local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO
|
||||
|
||||
if Cargo then
|
||||
return Cargo:IsInZone( Zone )
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
end
|
||||
|
||||
--- Get the transportation method of the Cargo.
|
||||
-- @param #CARGO_GROUP self
|
||||
-- @return #string The transportation method of the Cargo.
|
||||
function CARGO_GROUP:GetTransportationMethod()
|
||||
if self:IsLoaded() then
|
||||
return "for unboarding"
|
||||
else
|
||||
if self:IsUnLoaded() then
|
||||
return "for boarding"
|
||||
else
|
||||
if self:IsDeployed() then
|
||||
return "delivered"
|
||||
end
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
|
||||
|
||||
end -- CARGO_GROUP
|
||||
275
Moose Development/Moose/Cargo/CargoSlingload.lua
Normal file
275
Moose Development/Moose/Cargo/CargoSlingload.lua
Normal file
@@ -0,0 +1,275 @@
|
||||
--- **Cargo** - Management of single cargo crates, which are based on a STATIC object. The cargo can only be slingloaded.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions]()
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Cargo.CargoSlingload
|
||||
-- @image Cargo_Slingload.JPG
|
||||
|
||||
|
||||
do -- CARGO_SLINGLOAD
|
||||
|
||||
--- Models the behaviour of cargo crates, which can only be slingloaded.
|
||||
-- @type CARGO_SLINGLOAD
|
||||
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
|
||||
|
||||
--- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
|
||||
--
|
||||
-- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking:
|
||||
--
|
||||
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players.
|
||||
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #CARGO_SLINGLOAD
|
||||
CARGO_SLINGLOAD = {
|
||||
ClassName = "CARGO_SLINGLOAD"
|
||||
}
|
||||
|
||||
--- CARGO_SLINGLOAD Constructor.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
-- @param Wrapper.Static#STATIC CargoStatic
|
||||
-- @param #string Type
|
||||
-- @param #string Name
|
||||
-- @param #number LoadRadius (optional)
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @return #CARGO_SLINGLOAD
|
||||
function CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_SLINGLOAD
|
||||
self:T( { Type, Name, NearRadius } )
|
||||
|
||||
self.CargoObject = CargoStatic
|
||||
|
||||
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
|
||||
_EVENTDISPATCHER:CreateEventNewCargo( self )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
|
||||
--self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
|
||||
|
||||
self:SetEventPriority( 4 )
|
||||
|
||||
self.NearRadius = NearRadius or 25
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #CARGO_SLINGLOAD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function CARGO_SLINGLOAD:OnEventCargoDead( EventData )
|
||||
|
||||
local Destroyed = false
|
||||
|
||||
if self:IsDestroyed() or self:IsUnLoaded() then
|
||||
if self.CargoObject:GetName() == EventData.IniUnitName then
|
||||
if not self.NoDestroy then
|
||||
Destroyed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Destroyed then
|
||||
self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } )
|
||||
self:Destroyed()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Check if the cargo can be Slingloaded.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
function CARGO_SLINGLOAD:CanSlingload()
|
||||
return true
|
||||
end
|
||||
|
||||
--- Check if the cargo can be Boarded.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
function CARGO_SLINGLOAD:CanBoard()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if the cargo can be Unboarded.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
function CARGO_SLINGLOAD:CanUnboard()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if the cargo can be Loaded.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
function CARGO_SLINGLOAD:CanLoad()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if the cargo can be Unloaded.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
function CARGO_SLINGLOAD:CanUnload()
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @return #boolean true if the Cargo Crate is within the report radius.
|
||||
function CARGO_SLINGLOAD:IsInReportRadius( Coordinate )
|
||||
--self:T( { Coordinate, LoadRadius = self.LoadRadius } )
|
||||
|
||||
local Distance = 0
|
||||
if self:IsUnLoaded() then
|
||||
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
|
||||
if Distance <= self.LoadRadius then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Check if Cargo Slingload is in the radius for the Cargo to be Boarded or Loaded.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @return #boolean true if the Cargo Slingload is within the loading radius.
|
||||
function CARGO_SLINGLOAD:IsInLoadRadius( Coordinate )
|
||||
--self:T( { Coordinate } )
|
||||
|
||||
local Distance = 0
|
||||
if self:IsUnLoaded() then
|
||||
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
|
||||
if Distance <= self.NearRadius then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Get the current Coordinate of the CargoGroup.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
|
||||
-- @return #nil There is no valid Cargo in the CargoGroup.
|
||||
function CARGO_SLINGLOAD:GetCoordinate()
|
||||
--self:T()
|
||||
|
||||
return self.CargoObject:GetCoordinate()
|
||||
end
|
||||
|
||||
--- Check if the CargoGroup is alive.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
-- @return #boolean true if the CargoGroup is alive.
|
||||
-- @return #boolean false if the CargoGroup is dead.
|
||||
function CARGO_SLINGLOAD:IsAlive()
|
||||
|
||||
local Alive = true
|
||||
|
||||
-- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive.
|
||||
-- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive.
|
||||
if self:IsLoaded() then
|
||||
Alive = Alive == true and self.CargoCarrier:IsAlive()
|
||||
else
|
||||
Alive = Alive == true and self.CargoObject:IsAlive()
|
||||
end
|
||||
|
||||
return Alive
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Route Cargo to Coordinate and randomize locations.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
function CARGO_SLINGLOAD:RouteTo( Coordinate )
|
||||
--self:T( {Coordinate = Coordinate } )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Check if Cargo is near to the Carrier.
|
||||
-- The Cargo is near to the Carrier within NearRadius.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @param #number NearRadius
|
||||
-- @return #boolean The Cargo is near to the Carrier.
|
||||
-- @return #nil The Cargo is not near to the Carrier.
|
||||
function CARGO_SLINGLOAD:IsNear( CargoCarrier, NearRadius )
|
||||
--self:T( {NearRadius = NearRadius } )
|
||||
|
||||
return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius )
|
||||
end
|
||||
|
||||
|
||||
--- Respawn the CargoGroup.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
function CARGO_SLINGLOAD:Respawn()
|
||||
|
||||
--self:T( { "Respawning slingload " .. self:GetName() } )
|
||||
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event.
|
||||
self:__Reset( -0.1 )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Respawn the CargoGroup.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
function CARGO_SLINGLOAD:onafterReset()
|
||||
|
||||
--self:T( { "Reset slingload " .. self:GetName() } )
|
||||
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self:SetDeployed( false )
|
||||
self:SetStartState( "UnLoaded" )
|
||||
self.CargoCarrier = nil
|
||||
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
|
||||
_EVENTDISPATCHER:CreateEventNewCargo( self )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Get the transportation method of the Cargo.
|
||||
-- @param #CARGO_SLINGLOAD self
|
||||
-- @return #string The transportation method of the Cargo.
|
||||
function CARGO_SLINGLOAD:GetTransportationMethod()
|
||||
if self:IsLoaded() then
|
||||
return "for sling loading"
|
||||
else
|
||||
if self:IsUnLoaded() then
|
||||
return "for sling loading"
|
||||
else
|
||||
if self:IsDeployed() then
|
||||
return "delivered"
|
||||
end
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
end
|
||||
395
Moose Development/Moose/Cargo/CargoUnit.lua
Normal file
395
Moose Development/Moose/Cargo/CargoUnit.lua
Normal file
@@ -0,0 +1,395 @@
|
||||
--- **Cargo** - Management of single cargo logistics, which are based on a UNIT object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions]()
|
||||
--
|
||||
-- ### [YouTube Playlist]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Cargo.CargoUnit
|
||||
-- @image Cargo_Units.JPG
|
||||
|
||||
do -- CARGO_UNIT
|
||||
|
||||
--- Models CARGO in the form of units, which can be boarded, unboarded, loaded, unloaded.
|
||||
-- @type CARGO_UNIT
|
||||
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
|
||||
|
||||
--- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
|
||||
-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO_UNIT objects to and from carriers.
|
||||
-- Note that ground forces behave in a group, and thus, act in formation, regardless if one unit is commanded to move.
|
||||
--
|
||||
-- This class is used in CARGO_GROUP, and is not meant to be used by mission designers individually.
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #CARGO_UNIT CARGO_UNIT
|
||||
--
|
||||
CARGO_UNIT = {
|
||||
ClassName = "CARGO_UNIT"
|
||||
}
|
||||
|
||||
--- CARGO_UNIT Constructor.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @param Wrapper.Unit#UNIT CargoUnit
|
||||
-- @param #string Type
|
||||
-- @param #string Name
|
||||
-- @param #number Weight
|
||||
-- @param #number LoadRadius (optional)
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @return #CARGO_UNIT
|
||||
function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CARGO_REPRESENTABLE.
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
|
||||
|
||||
-- Debug info.
|
||||
self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius})
|
||||
|
||||
-- Set cargo object.
|
||||
self.CargoObject = CargoUnit
|
||||
|
||||
-- Set event prio.
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enter UnBoarding State.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
-- @param #number NearRadius (optional) Defaut 25 m.
|
||||
function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius )
|
||||
self:T( { From, Event, To, ToPointVec2, NearRadius } )
|
||||
|
||||
local Angle = 180
|
||||
local Speed = 60
|
||||
local DeployDistance = 9
|
||||
local RouteDistance = 60
|
||||
|
||||
if From == "Loaded" then
|
||||
|
||||
if not self:IsDestroyed() then
|
||||
|
||||
local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE
|
||||
|
||||
if CargoCarrier:IsAlive() then
|
||||
|
||||
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
|
||||
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
|
||||
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
|
||||
|
||||
|
||||
local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading )
|
||||
|
||||
|
||||
-- if there is no ToPointVec2 given, then use the CargoRoutePointVec2
|
||||
local FromDirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3( ToPointVec2 or CargoRoutePointVec2 )
|
||||
local FromAngle = CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3)
|
||||
local FromPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, FromAngle )
|
||||
--local CargoDeployPointVec2 = CargoCarrierPointVec2:GetRandomCoordinateInRadius( 10, 5 )
|
||||
|
||||
ToPointVec2 = ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius( NearRadius, DeployDistance )
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
if CargoCarrier:IsShip() then
|
||||
-- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead.
|
||||
self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading )
|
||||
else
|
||||
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
|
||||
end
|
||||
self:T( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } )
|
||||
self.CargoCarrier = nil
|
||||
|
||||
local Points = {}
|
||||
|
||||
-- From
|
||||
Points[#Points+1] = FromPointVec2:WaypointGround( Speed, "Vee" )
|
||||
|
||||
-- To
|
||||
Points[#Points+1] = ToPointVec2:WaypointGround( Speed, "Vee" )
|
||||
|
||||
local TaskRoute = self.CargoObject:TaskRoute( Points )
|
||||
self.CargoObject:SetTask( TaskRoute, 1 )
|
||||
|
||||
|
||||
self:__UnBoarding( 1, ToPointVec2, NearRadius )
|
||||
end
|
||||
else
|
||||
-- the Carrier is dead. This cargo is dead too!
|
||||
self:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Leave UnBoarding State.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
-- @param #number NearRadius (optional) Defaut 100 m.
|
||||
function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius )
|
||||
self:T( { From, Event, To, ToPointVec2, NearRadius } )
|
||||
|
||||
local Angle = 180
|
||||
local Speed = 10
|
||||
local Distance = 5
|
||||
|
||||
if From == "UnBoarding" then
|
||||
--if self:IsNear( ToPointVec2, NearRadius ) then
|
||||
return true
|
||||
--else
|
||||
|
||||
--self:__UnBoarding( 1, ToPointVec2, NearRadius )
|
||||
--end
|
||||
--return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- UnBoard Event.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
-- @param #number NearRadius (optional) Defaut 100 m.
|
||||
function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius )
|
||||
self:T( { From, Event, To, ToPointVec2, NearRadius } )
|
||||
|
||||
self.CargoInAir = self.CargoObject:InAir()
|
||||
|
||||
self:T( self.CargoInAir )
|
||||
|
||||
-- Only unboard the cargo when the carrier is not in the air.
|
||||
-- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea).
|
||||
if not self.CargoInAir then
|
||||
|
||||
end
|
||||
|
||||
self:__UnLoad( 1, ToPointVec2, NearRadius )
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Enter UnLoaded State.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2
|
||||
function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 )
|
||||
self:T( { ToPointVec2, From, Event, To } )
|
||||
|
||||
local Angle = 180
|
||||
local Speed = 10
|
||||
local Distance = 5
|
||||
|
||||
if From == "Loaded" then
|
||||
local StartPointVec2 = self.CargoCarrier:GetPointVec2()
|
||||
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
|
||||
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
|
||||
local CargoDeployCoord = StartPointVec2:Translate( Distance, CargoDeployHeading )
|
||||
|
||||
ToPointVec2 = ToPointVec2 or COORDINATE:New( CargoDeployCoord.x, CargoDeployCoord.z )
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self.CargoObject:ReSpawnAt( ToPointVec2, 0 )
|
||||
self.CargoCarrier = nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if self.OnUnLoadedCallBack then
|
||||
self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) )
|
||||
self.OnUnLoadedCallBack = nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Board Event.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @param #number NearRadius
|
||||
function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... )
|
||||
self:T( { From, Event, To, CargoCarrier, NearRadius = NearRadius } )
|
||||
|
||||
self.CargoInAir = self.CargoObject:InAir()
|
||||
|
||||
local Desc = self.CargoObject:GetDesc()
|
||||
local MaxSpeed = Desc.speedMaxOffRoad
|
||||
local TypeName = Desc.typeName
|
||||
|
||||
--self:T({Unit=self.CargoObject:GetName()})
|
||||
|
||||
-- A cargo unit can only be boarded if it is not dead
|
||||
|
||||
-- Only move the group to the carrier when the cargo is not in the air
|
||||
-- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea).
|
||||
if not self.CargoInAir then
|
||||
-- If NearRadius is given, then use the given NearRadius, otherwise calculate the NearRadius
|
||||
-- based upon the Carrier bounding radius, which is calculated from the bounding rectangle on the Y axis.
|
||||
local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius() + 5
|
||||
if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then
|
||||
self:Load( CargoCarrier, NearRadius, ... )
|
||||
else
|
||||
if MaxSpeed and MaxSpeed == 0 or TypeName and TypeName == "Stinger comm" then
|
||||
self:Load( CargoCarrier, NearRadius, ... )
|
||||
else
|
||||
|
||||
local Speed = 90
|
||||
local Angle = 180
|
||||
local Distance = 0
|
||||
|
||||
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
|
||||
local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees.
|
||||
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
|
||||
local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading )
|
||||
|
||||
-- Set the CargoObject to state Green to ensure it is boarding!
|
||||
self.CargoObject:OptionAlarmStateGreen()
|
||||
|
||||
local Points = {}
|
||||
|
||||
local PointStartVec2 = self.CargoObject:GetPointVec2()
|
||||
|
||||
Points[#Points+1] = PointStartVec2:WaypointGround( Speed )
|
||||
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed )
|
||||
|
||||
local TaskRoute = self.CargoObject:TaskRoute( Points )
|
||||
self.CargoObject:SetTask( TaskRoute, 2 )
|
||||
self:__Boarding( -5, CargoCarrier, NearRadius, ... )
|
||||
self.RunCount = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Boarding Event.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Client#CLIENT CargoCarrier
|
||||
-- @param #number NearRadius Default 25 m.
|
||||
function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
|
||||
self:T( { From, Event, To, CargoCarrier:GetName(), NearRadius = NearRadius } )
|
||||
|
||||
self:T( { IsAlive=self.CargoObject:IsAlive() } )
|
||||
|
||||
if CargoCarrier and CargoCarrier:IsAlive() then -- and self.CargoObject and self.CargoObject:IsAlive() then
|
||||
if (CargoCarrier:IsAir() and not CargoCarrier:InAir()) or true then
|
||||
local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius( NearRadius ) + 5
|
||||
if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then
|
||||
self:__Load( -1, CargoCarrier, ... )
|
||||
else
|
||||
if self:IsNear( CargoCarrier:GetPointVec2(), 20 ) then
|
||||
self:__Boarding( -1, CargoCarrier, NearRadius, ... )
|
||||
self.RunCount = self.RunCount + 1
|
||||
else
|
||||
self:__Boarding( -2, CargoCarrier, NearRadius, ... )
|
||||
self.RunCount = self.RunCount + 2
|
||||
end
|
||||
if self.RunCount >= 40 then
|
||||
self.RunCount = 0
|
||||
local Speed = 90
|
||||
local Angle = 180
|
||||
local Distance = 0
|
||||
|
||||
--self:T({Unit=self.CargoObject:GetName()})
|
||||
|
||||
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
|
||||
local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees.
|
||||
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
|
||||
local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading )
|
||||
|
||||
-- Set the CargoObject to state Green to ensure it is boarding!
|
||||
self.CargoObject:OptionAlarmStateGreen()
|
||||
|
||||
local Points = {}
|
||||
|
||||
local PointStartVec2 = self.CargoObject:GetPointVec2()
|
||||
|
||||
Points[#Points+1] = PointStartVec2:WaypointGround( Speed, "Off road" )
|
||||
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed, "Off road" )
|
||||
|
||||
local TaskRoute = self.CargoObject:TaskRoute( Points )
|
||||
self.CargoObject:SetTask( TaskRoute, 0.2 )
|
||||
end
|
||||
end
|
||||
else
|
||||
self.CargoObject:MessageToGroup( "Cancelling Boarding... Get back on the ground!", 5, CargoCarrier:GetGroup(), self:GetName() )
|
||||
self:CancelBoarding( CargoCarrier, NearRadius, ... )
|
||||
self.CargoObject:SetCommand( self.CargoObject:CommandStopRoute( true ) )
|
||||
end
|
||||
else
|
||||
self:T("Something is wrong")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Loaded State.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT CargoCarrier
|
||||
function CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier )
|
||||
self:T( { From, Event, To, CargoCarrier } )
|
||||
|
||||
self.CargoCarrier = CargoCarrier
|
||||
|
||||
--self:T({Unit=self.CargoObject:GetName()})
|
||||
|
||||
-- Only destroy the CargoObject if there is a CargoObject (packages don't have CargoObjects).
|
||||
if self.CargoObject then
|
||||
self.CargoObject:Destroy( false )
|
||||
--self.CargoObject:ReSpawnAt( COORDINATE:NewFromVec2( {x=0,y=0} ), 0 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the transportation method of the Cargo.
|
||||
-- @param #CARGO_UNIT self
|
||||
-- @return #string The transportation method of the Cargo.
|
||||
function CARGO_UNIT:GetTransportationMethod()
|
||||
if self:IsLoaded() then
|
||||
return "for unboarding"
|
||||
else
|
||||
if self:IsUnLoaded() then
|
||||
return "for boarding"
|
||||
else
|
||||
if self:IsDeployed() then
|
||||
return "delivered"
|
||||
end
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
end -- CARGO_UNIT
|
||||
@@ -26,7 +26,7 @@
|
||||
-- @module Core.Base
|
||||
-- @image Core_Base.JPG
|
||||
|
||||
local _TraceOnOff = false -- default to no tracing
|
||||
local _TraceOnOff = true
|
||||
local _TraceLevel = 1
|
||||
local _TraceAll = false
|
||||
local _TraceClass = {}
|
||||
@@ -34,12 +34,11 @@ local _TraceClassMethod = {}
|
||||
|
||||
local _ClassID = 0
|
||||
|
||||
--- Base class of everything
|
||||
---
|
||||
-- @type BASE
|
||||
-- @field #string ClassName The name of the class.
|
||||
-- @field #number ClassID The ID number of the class.
|
||||
-- @field #string ClassNameAndID The name of the class concatenated with the ID number of the class.
|
||||
-- @field Core.Scheduler#SCHEDULER Scheduler The scheduler object.
|
||||
-- @field ClassName The name of the class.
|
||||
-- @field ClassID The ID number of the class.
|
||||
-- @field ClassNameAndID The name of the class concatenated with the ID number of the class.
|
||||
|
||||
--- BASE class
|
||||
--
|
||||
@@ -201,7 +200,6 @@ BASE = {
|
||||
States = {},
|
||||
Debug = debug,
|
||||
Scheduler = nil,
|
||||
Properties = {},
|
||||
}
|
||||
|
||||
-- @field #BASE.__
|
||||
@@ -212,6 +210,14 @@ BASE._ = {
|
||||
Schedules = {}, --- Contains the Schedulers Active
|
||||
}
|
||||
|
||||
--- The Formation Class
|
||||
-- @type FORMATION
|
||||
-- @field Cone A cone formation.
|
||||
FORMATION = {
|
||||
Cone = "Cone",
|
||||
Vee = "Vee",
|
||||
}
|
||||
|
||||
--- BASE constructor.
|
||||
--
|
||||
-- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE.
|
||||
@@ -735,31 +741,7 @@ do -- Event Handling
|
||||
-- @function [parent=#BASE] OnEventPlayerEnterAircraft
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a player creates a dynamic cargo object from the F8 ground crew menu.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
-- @function [parent=#BASE] OnEventNewDynamicCargo
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a player loads a dynamic cargo object with the F8 ground crew menu into a helo.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
-- @function [parent=#BASE] OnEventDynamicCargoLoaded
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a player unloads a dynamic cargo object with the F8 ground crew menu from a helo.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
-- @function [parent=#BASE] OnEventDynamicCargoUnloaded
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a dynamic cargo crate is removed.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
-- @function [parent=#BASE] OnEventDynamicCargoRemoved
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Creation of a Birth Event.
|
||||
@@ -880,62 +862,6 @@ end
|
||||
|
||||
world.onEvent(Event)
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function BASE:CreateEventNewDynamicCargo(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.NewDynamicCargo,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_LOADED event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function BASE:CreateEventDynamicCargoLoaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoLoaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_UNLOADED event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function BASE:CreateEventDynamicCargoUnloaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoUnloaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_REMOVED event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function BASE:CreateEventDynamicCargoRemoved(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoRemoved,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- The main event handling function... This function captures all events generated for the class.
|
||||
-- @param #BASE self
|
||||
@@ -974,7 +900,7 @@ do -- Scheduling
|
||||
-- @param #BASE self
|
||||
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
|
||||
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
|
||||
-- @param ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
|
||||
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
|
||||
-- @return #string The Schedule ID of the planned schedule.
|
||||
function BASE:ScheduleOnce( Start, SchedulerFunction, ... )
|
||||
|
||||
@@ -1110,31 +1036,6 @@ function BASE:ClearState( Object, StateName )
|
||||
end
|
||||
end
|
||||
|
||||
--- Set one property of an object.
|
||||
-- @param #BASE self
|
||||
-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type!
|
||||
-- @param Value The value that is stored. Note that the value can be a #string, but it can also be any other type!
|
||||
function BASE:SetProperty(Key,Value)
|
||||
self.Properties = self.Properties or {}
|
||||
self.Properties[Key] = Value
|
||||
end
|
||||
|
||||
--- Get one property of an object by the key.
|
||||
-- @param #BASE self
|
||||
-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type!
|
||||
-- @return Value The value that is stored. Note that the value can be a #string, but it can also be any other type! Nil if not found.
|
||||
function BASE:GetProperty(Key)
|
||||
self.Properties = self.Properties or {}
|
||||
return self.Properties[Key]
|
||||
end
|
||||
|
||||
--- Get all of the properties of an object in a table.
|
||||
-- @param #BASE self
|
||||
-- @return #table of values, indexed by keys.
|
||||
function BASE:GetProperties()
|
||||
return self.Properties
|
||||
end
|
||||
|
||||
-- Trace section
|
||||
|
||||
-- Log a trace (only shown when trace is on)
|
||||
@@ -1243,28 +1144,6 @@ function BASE:TraceClassMethod( Class, Method )
|
||||
self:I( "Tracing method " .. Method .. " of class " .. Class )
|
||||
end
|
||||
|
||||
--- (Internal) Serialize arguments
|
||||
-- @param #BASE self
|
||||
-- @param #table Arguments
|
||||
-- @return #string Text
|
||||
function BASE:_Serialize(Arguments)
|
||||
local text = UTILS.PrintTableToLog({Arguments}, 0, true)
|
||||
text = string.gsub(text,"(\n+)","")
|
||||
text = string.gsub(text,"%(%(","%(")
|
||||
text = string.gsub(text,"%)%)","%)")
|
||||
text = string.gsub(text,"(%s+)"," ")
|
||||
return text
|
||||
end
|
||||
|
||||
----- (Internal) Serialize arguments
|
||||
---- @param #BASE self
|
||||
---- @param #table Arguments
|
||||
---- @return #string Text
|
||||
--function BASE:_Serialize(Arguments)
|
||||
-- local text=UTILS.BasicSerialize(Arguments)
|
||||
-- return text
|
||||
--end
|
||||
|
||||
--- Trace a function call. This function is private.
|
||||
-- @param #BASE self
|
||||
-- @param Arguments A #table or any field.
|
||||
@@ -1289,7 +1168,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, BASE:_Serialize(Arguments) ) )
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1299,7 +1178,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:F( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff == true then
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1314,7 +1193,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:F2( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 2 then
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1329,7 +1208,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:F3( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 3 then
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1363,7 +1242,7 @@ function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) )
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1373,7 +1252,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:T( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff == true then
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1388,7 +1267,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:T2( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 2 then
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1403,7 +1282,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:T3( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 3 then
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1435,7 +1314,7 @@ function BASE:E( Arguments )
|
||||
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
|
||||
else
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments) ) )
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1462,7 +1341,39 @@ function BASE:I( Arguments )
|
||||
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
|
||||
else
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments)) )
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- old stuff
|
||||
|
||||
-- function BASE:_Destructor()
|
||||
-- --self:E("_Destructor")
|
||||
--
|
||||
-- --self:EventRemoveAll()
|
||||
-- end
|
||||
|
||||
-- THIS IS WHY WE NEED LUA 5.2 ...
|
||||
-- function BASE:_SetDestructor()
|
||||
--
|
||||
-- -- TODO: Okay, this is really technical...
|
||||
-- -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak...
|
||||
-- -- Therefore, I am parking this logic until I've properly discussed all this with the community.
|
||||
--
|
||||
-- local proxy = newproxy(true)
|
||||
-- local proxyMeta = getmetatable(proxy)
|
||||
--
|
||||
-- proxyMeta.__gc = function ()
|
||||
-- env.info("In __gc for " .. self:GetClassNameAndID() )
|
||||
-- if self._Destructor then
|
||||
-- self:_Destructor()
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- -- keep the userdata from newproxy reachable until the object
|
||||
-- -- table is about to be garbage-collected - then the __gc hook
|
||||
-- -- will be invoked and the destructor called
|
||||
-- rawset( self, '__proxy', proxy )
|
||||
--
|
||||
-- end
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Beacon)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Beacon
|
||||
@@ -38,13 +34,11 @@
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{Wrapper.Controllable#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @field #number UniqueName Counter to make the unique naming work.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name = nil,
|
||||
UniqueName = 0,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
@@ -292,7 +286,6 @@ end
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
self:E("This method is DEPRECATED! Please use ActivateTACAN() instead.")
|
||||
|
||||
local IsValid = true
|
||||
|
||||
@@ -386,9 +379,7 @@ end
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
Modulation = Modulation or radio.modulation.AM
|
||||
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
@@ -399,7 +390,7 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension? ", FileName})
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
@@ -425,9 +416,7 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
BEACON.UniqueName = BEACON.UniqueName + 1
|
||||
self.BeaconName = "MooseBeacon"..tostring(BEACON.UniqueName)
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, self.BeaconName)
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
@@ -435,8 +424,7 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the Radio Beacon
|
||||
@@ -445,7 +433,7 @@ end
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(self.BeaconName)
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -1,924 +0,0 @@
|
||||
--- **Core** - Client Menu Management.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * For complex, non-static menu structures
|
||||
-- * Lightweigt implementation as alternative to MENU
|
||||
-- * Separation of menu tree creation from menu on the clients's side
|
||||
-- * Works with a SET_CLIENT set of clients
|
||||
-- * Allow manipulation of the shadow tree in various ways
|
||||
-- * Push to all or only one client
|
||||
-- * Change entries' menu text
|
||||
-- * Option to make an entry usable once only across all clients
|
||||
-- * Auto appends GROUP and CLIENT objects to menu calls
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.ClientMenu
|
||||
-- @image Core_Menu.JPG
|
||||
-- last change: Jan 2025
|
||||
|
||||
-- TODO
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
-- CLIENTMENU
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
---
|
||||
-- @type CLIENTMENU
|
||||
-- @field #string ClassName Class Name
|
||||
-- @field #string lid Lid for log entries
|
||||
-- @field #string version Version string
|
||||
-- @field #string name Name
|
||||
-- @field #string groupname Group name
|
||||
-- @field #table path
|
||||
-- @field #table parentpath
|
||||
-- @field #CLIENTMENU Parent
|
||||
-- @field Wrapper.Client#CLIENT client
|
||||
-- @field #number GroupID Group ID
|
||||
-- @field #number ID Entry ID
|
||||
-- @field Wrapper.Group#GROUP group
|
||||
-- @field #string UUID Unique ID based on path+name
|
||||
-- @field #string Function
|
||||
-- @field #table Functionargs
|
||||
-- @field #table Children
|
||||
-- @field #boolean Once
|
||||
-- @field #boolean Generic
|
||||
-- @field #boolean debug
|
||||
-- @field #CLIENTMENUMANAGER Controller
|
||||
-- @field #active boolean
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
---
|
||||
-- @field #CLIENTMENU
|
||||
CLIENTMENU = {
|
||||
ClassName = "CLIENTMENU",
|
||||
lid = "",
|
||||
version = "0.1.3",
|
||||
name = nil,
|
||||
path = nil,
|
||||
group = nil,
|
||||
client = nil,
|
||||
GroupID = nil,
|
||||
Children = {},
|
||||
Once = false,
|
||||
Generic = false,
|
||||
debug = false,
|
||||
Controller = nil,
|
||||
groupname = nil,
|
||||
active = false,
|
||||
}
|
||||
|
||||
---
|
||||
-- @field #CLIENTMENU_ID
|
||||
CLIENTMENU_ID = 0
|
||||
|
||||
--- Create an new CLIENTMENU object.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param Wrapper.Client#CLIENT Client The client for whom this entry is. Leave as nil for a generic entry.
|
||||
-- @param #string Text Text of the F10 menu entry.
|
||||
-- @param #CLIENTMENU Parent The parent menu entry.
|
||||
-- @param #string Function (optional) Function to call when the entry is used.
|
||||
-- @param ... (optional) Arguments for the Function, comma separated
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #CLIENTMENU
|
||||
CLIENTMENU_ID = CLIENTMENU_ID + 1
|
||||
self.ID = CLIENTMENU_ID
|
||||
if Client then
|
||||
self.group = Client:GetGroup()
|
||||
self.client = Client
|
||||
self.GroupID = self.group:GetID()
|
||||
self.groupname = self.group:GetName() or "Unknown Groupname"
|
||||
else
|
||||
self.Generic = true
|
||||
end
|
||||
self.name = Text or "unknown entry"
|
||||
if Parent then
|
||||
if Parent:IsInstanceOf("MENU_BASE") then
|
||||
self.parentpath = Parent.MenuPath
|
||||
else
|
||||
self.parentpath = Parent:GetPath()
|
||||
Parent:AddChild(self)
|
||||
end
|
||||
end
|
||||
self.Parent = Parent
|
||||
self.Function = Function
|
||||
self.Functionargs = arg or {}
|
||||
table.insert(self.Functionargs,self.group)
|
||||
table.insert(self.Functionargs,self.client)
|
||||
if self.Functionargs and self.debug then
|
||||
self:T({"Functionargs",self.Functionargs})
|
||||
end
|
||||
if not self.Generic and self.active == false then
|
||||
if Function ~= nil then
|
||||
local ErrorHandler = function( errmsg )
|
||||
env.info( "MOOSE Error in CLIENTMENU COMMAND function: " .. errmsg )
|
||||
if BASE.Debug ~= nil then
|
||||
env.info( BASE.Debug.traceback() )
|
||||
end
|
||||
return errmsg
|
||||
end
|
||||
self.CallHandler = function()
|
||||
local function MenuFunction()
|
||||
return self.Function( unpack( self.Functionargs ) )
|
||||
end
|
||||
local Status, Result = xpcall( MenuFunction, ErrorHandler)
|
||||
if self.Once == true then
|
||||
self:Clear()
|
||||
end
|
||||
end
|
||||
self.path = missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath, self.CallHandler)
|
||||
self.active = true
|
||||
else
|
||||
self.path = missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath)
|
||||
self.active = true
|
||||
end
|
||||
else
|
||||
if self.parentpath then
|
||||
self.path = UTILS.DeepCopy(self.parentpath)
|
||||
else
|
||||
self.path = {}
|
||||
end
|
||||
self.path[#self.path+1] = Text
|
||||
end
|
||||
self.UUID = table.concat(self.path,";")
|
||||
self:T({self.UUID})
|
||||
self.Once = false
|
||||
-- Log id.
|
||||
self.lid=string.format("CLIENTMENU %s | %s | ", self.ID, self.name)
|
||||
self:T(self.lid.."Created")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a UUID
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param #CLIENTMENU Parent The parent object if any
|
||||
-- @param #string Text The menu entry text
|
||||
-- @return #string UUID
|
||||
function CLIENTMENU:CreateUUID(Parent,Text)
|
||||
local path = {}
|
||||
if Parent and Parent.path then
|
||||
path = Parent.path
|
||||
end
|
||||
path[#path+1] = Text
|
||||
local UUID = table.concat(path,";")
|
||||
return UUID
|
||||
end
|
||||
|
||||
--- Set the CLIENTMENUMANAGER for this entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param #CLIENTMENUMANAGER Controller The controlling object.
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:SetController(Controller)
|
||||
self.Controller = Controller
|
||||
return self
|
||||
end
|
||||
|
||||
--- The entry will be deleted after being used used - for menu entries with functions only.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:SetOnce()
|
||||
self:T(self.lid.."SetOnce")
|
||||
self.Once = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the entry from the F10 menu.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:RemoveF10()
|
||||
self:T(self.lid.."RemoveF10")
|
||||
if self.GroupID then
|
||||
--self:I(self.lid.."Removing "..table.concat(self.path,";"))
|
||||
local function RemoveFunction()
|
||||
return missionCommands.removeItemForGroup(self.GroupID , self.path )
|
||||
end
|
||||
local status, err = pcall(RemoveFunction)
|
||||
if not status then
|
||||
self:I(string.format("**** Error Removing Menu Entry %s for %s!",tostring(self.name),self.groupname))
|
||||
end
|
||||
self.active = false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the menu path table.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #table Path
|
||||
function CLIENTMENU:GetPath()
|
||||
self:T(self.lid.."GetPath")
|
||||
return self.path
|
||||
end
|
||||
|
||||
--- Get the UUID.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #string UUID
|
||||
function CLIENTMENU:GetUUID()
|
||||
self:T(self.lid.."GetUUID")
|
||||
return self.UUID
|
||||
end
|
||||
|
||||
--- Link a child entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param #CLIENTMENU Child The entry to link as a child.
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:AddChild(Child)
|
||||
self:T(self.lid.."AddChild "..Child.ID)
|
||||
table.insert(self.Children,Child.ID,Child)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a child entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @param #CLIENTMENU Child The entry to remove from the children.
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:RemoveChild(Child)
|
||||
self:T(self.lid.."RemoveChild "..Child.ID)
|
||||
table.remove(self.Children,Child.ID)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove all subentries (children) from this entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:RemoveSubEntries()
|
||||
self:T(self.lid.."RemoveSubEntries")
|
||||
self:T({self.Children})
|
||||
for _id,_entry in pairs(self.Children) do
|
||||
self:T("Removing ".._id)
|
||||
if _entry then
|
||||
_entry:RemoveSubEntries()
|
||||
_entry:RemoveF10()
|
||||
if _entry.Parent then
|
||||
_entry.Parent:RemoveChild(self)
|
||||
end
|
||||
--if self.Controller then
|
||||
--self.Controller:_RemoveByID(_entry.ID)
|
||||
--end
|
||||
--_entry = nil
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove this entry and all subentries (children) from this entry.
|
||||
-- @param #CLIENTMENU self
|
||||
-- @return #CLIENTMENU self
|
||||
function CLIENTMENU:Clear()
|
||||
self:T(self.lid.."Clear")
|
||||
for _id,_entry in pairs(self.Children) do
|
||||
if _entry then
|
||||
_entry:RemoveSubEntries()
|
||||
_entry = nil
|
||||
end
|
||||
end
|
||||
self:RemoveF10()
|
||||
if self.Parent then
|
||||
self.Parent:RemoveChild(self)
|
||||
end
|
||||
--if self.Controller then
|
||||
--self.Controller:_RemoveByID(self.ID)
|
||||
--end
|
||||
return self
|
||||
end
|
||||
|
||||
-- TODO
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
-- CLIENTMENUMANAGER
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--- Class CLIENTMENUMANAGER
|
||||
-- @type CLIENTMENUMANAGER
|
||||
-- @field #string ClassName Class Name
|
||||
-- @field #string lid Lid for log entries
|
||||
-- @field #string version Version string
|
||||
-- @field #string name Name
|
||||
-- @field Core.Set#SET_CLIENT clientset The set of clients this menu manager is for
|
||||
-- @field #table flattree
|
||||
-- @field #table rootentries
|
||||
-- @field #table menutree
|
||||
-- @field #number entrycount
|
||||
-- @field #boolean debug
|
||||
-- @field #table PlayerMenu
|
||||
-- @field #number Coalition
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *As a child my family's menu consisted of two choices: take it, or leave it.*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## CLIENTMENU and CLIENTMENUMANAGER
|
||||
--
|
||||
-- Manage menu structures for a SET_CLIENT of clients.
|
||||
--
|
||||
-- ## Concept
|
||||
--
|
||||
-- Separate creation of a menu tree structure from pushing it to each client. Create a shadow "reference" menu structure tree for your client pilot's in a mission.
|
||||
-- This can then be propagated to all clients. Manipulate the entries in the structure with removing, clearing or changing single entries, create replacement sub-structures
|
||||
-- for entries etc, push to one or all clients.
|
||||
--
|
||||
-- Many functions can either change the tree for one client or for all clients.
|
||||
--
|
||||
-- ## Conceptual remarks
|
||||
--
|
||||
-- There's a couple of things to fully understand:
|
||||
--
|
||||
-- 1) **CLIENTMENUMANAGER** manages a set of entries from **CLIENTMENU**, it's main purpose is to administer the *shadow menu tree*, ie. a menu structure which is not
|
||||
-- (yet) visible to any client
|
||||
-- 2) The entries are **CLIENTMENU** objects, which are linked in a tree form. There's two ways to create them:
|
||||
-- A) in the manager with ":NewEntry()" which initially
|
||||
-- adds it to the shadow menu **only**
|
||||
-- B) stand-alone directly as `CLIENTMENU:NewEntry()` - here it depends on whether or not you gave a CLIENT object if the entry is created as generic entry or pushed
|
||||
-- a **specific** client. **Be aware** though that the entries are not managed by the CLIENTMANAGER before the next step!
|
||||
-- A generic entry can be added to the manager (and the shadow tree) with `:AddEntry()` - this will also push it to all clients(!) if no client is given, or a specific client only.
|
||||
-- 3) Pushing only works for alive clients.
|
||||
-- 4) Live and shadow tree entries are managed via the CLIENTMENUMANAGER object.
|
||||
-- 5) `Propagate()`refreshes the menu tree for all, or a single client.
|
||||
--
|
||||
-- ## Create a base reference tree and send to all clients
|
||||
--
|
||||
-- local clientset = SET_CLIENT:New():FilterStart()
|
||||
--
|
||||
-- local menumgr = CLIENTMENUMANAGER:New(clientset,"Dayshift")
|
||||
-- local mymenu = menumgr:NewEntry("Top")
|
||||
-- local mymenu_lv1a = menumgr:NewEntry("Level 1 a",mymenu)
|
||||
-- local mymenu_lv1b = menumgr:NewEntry("Level 1 b",mymenu)
|
||||
-- -- next one is a command menu entry, which can only be used once
|
||||
-- local mymenu_lv1c = menumgr:NewEntry("Action Level 1 c",mymenu, testfunction, "testtext"):SetOnce()
|
||||
--
|
||||
-- local mymenu_lv2a = menumgr:NewEntry("Go here",mymenu_lv1a)
|
||||
-- local mymenu_lv2b = menumgr:NewEntry("Level 2 ab",mymenu_lv1a)
|
||||
-- local mymenu_lv2c = menumgr:NewEntry("Level 2 ac",mymenu_lv1a)
|
||||
--
|
||||
-- local mymenu_lv2ba = menumgr:NewEntry("Level 2 ba",mymenu_lv1b)
|
||||
-- local mymenu_lv2bb = menumgr:NewEntry("Level 2 bb",mymenu_lv1b)
|
||||
-- local mymenu_lv2bc = menumgr:NewEntry("Level 2 bc",mymenu_lv1b)
|
||||
--
|
||||
-- local mymenu_lv3a = menumgr:NewEntry("Level 3 aaa",mymenu_lv2a)
|
||||
-- local mymenu_lv3b = menumgr:NewEntry("Level 3 aab",mymenu_lv2a)
|
||||
-- local mymenu_lv3c = menumgr:NewEntry("Level 3 aac",mymenu_lv2a)
|
||||
--
|
||||
-- menumgr:Propagate() -- propagate **once** to all clients in the SET_CLIENT
|
||||
--
|
||||
-- ## Remove a single entry's subtree
|
||||
--
|
||||
-- menumgr:RemoveSubEntries(mymenu_lv3a)
|
||||
--
|
||||
-- ## Remove a single entry and also it's subtree
|
||||
--
|
||||
-- menumgr:DeleteEntry(mymenu_lv3a)
|
||||
--
|
||||
-- ## Add a single entry
|
||||
--
|
||||
-- local baimenu = menumgr:NewEntry("BAI",mymenu_lv1b)
|
||||
--
|
||||
-- menumgr:AddEntry(baimenu)
|
||||
--
|
||||
-- ## Add an entry with a function
|
||||
--
|
||||
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, Argument1, Argument1)
|
||||
--
|
||||
-- Now, the class will **automatically append the call with GROUP and CLIENT objects**, as this is can only be done when pushing the entry to the clients. So, the actual function implementation needs to look like this:
|
||||
--
|
||||
-- function TestFunction( Argument1, Argument2, Group, Client)
|
||||
--
|
||||
-- **Caveat is**, that you need to ensure your arguments are not **nil** or **false**, as LUA will optimize those away. You would end up having Group and Client in wrong places in the function call. Hence,
|
||||
-- if you need/ want to send **nil** or **false**, send a place holder instead and ensure your function can handle this, e.g.
|
||||
--
|
||||
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, "nil", Argument1)
|
||||
--
|
||||
-- ## Change the text of a leaf entry in the menu tree
|
||||
--
|
||||
-- menumgr:ChangeEntryTextForAll(mymenu_lv1b,"Attack")
|
||||
--
|
||||
-- ## Reset a single clients menu tree
|
||||
--
|
||||
-- menumgr:ResetMenu(client)
|
||||
--
|
||||
-- ## Reset all and clear the reference tree
|
||||
--
|
||||
-- menumgr:ResetMenuComplete()
|
||||
--
|
||||
-- ## Set to auto-propagate for CLIENTs joining the SET_CLIENT **after** the script is loaded - handy if you have a single menu tree.
|
||||
--
|
||||
-- menumgr:InitAutoPropagation()
|
||||
--
|
||||
-- @field #CLIENTMENUMANAGER
|
||||
CLIENTMENUMANAGER = {
|
||||
ClassName = "CLIENTMENUMANAGER",
|
||||
lid = "",
|
||||
version = "0.1.6",
|
||||
name = nil,
|
||||
clientset = nil,
|
||||
menutree = {},
|
||||
flattree = {},
|
||||
playertree = {},
|
||||
entrycount = 0,
|
||||
rootentries = {},
|
||||
debug = true,
|
||||
PlayerMenu = {},
|
||||
Coalition = nil,
|
||||
}
|
||||
|
||||
--- Create a new ClientManager instance.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Core.Set#SET_CLIENT ClientSet The set of clients to manage.
|
||||
-- @param #string Alias The name of this manager.
|
||||
-- @param #number Coalition (Optional) Coalition of this Manager, defaults to coalition.side.BLUE
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:New(ClientSet, Alias, Coalition)
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #CLIENTMENUMANAGER
|
||||
self.clientset = ClientSet
|
||||
self.PlayerMenu = {}
|
||||
self.name = Alias or "Nightshift"
|
||||
self.Coalition = Coalition or coalition.side.BLUE
|
||||
-- Log id.
|
||||
self.lid=string.format("CLIENTMENUMANAGER %s | %s | ", self.version, self.name)
|
||||
if self.debug then
|
||||
self:I(self.lid.."Created")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Event handling
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:_EventHandler(EventData,Retry)
|
||||
self:T(self.lid.."_EventHandler: "..EventData.id)
|
||||
--self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName))
|
||||
if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then
|
||||
self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName))
|
||||
local Client = _DATABASE:FindClient( EventData.IniUnitName )
|
||||
if Client then
|
||||
self:ResetMenu(Client)
|
||||
end
|
||||
elseif (EventData.id == EVENTS.PlayerEnterAircraft) and EventData.IniCoalition == self.Coalition then
|
||||
if EventData.IniPlayerName and EventData.IniGroup then
|
||||
if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then
|
||||
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
|
||||
if not Retry then
|
||||
-- try again in 2 secs
|
||||
self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true)
|
||||
end
|
||||
return self
|
||||
end
|
||||
--self:I(self.lid.."Join event for player: "..EventData.IniPlayerName)
|
||||
local player = _DATABASE:FindClient( EventData.IniUnitName )
|
||||
self:Propagate(player)
|
||||
end
|
||||
elseif EventData.id == EVENTS.PlayerEnterUnit then
|
||||
-- special for CA slots
|
||||
local grp = GROUP:FindByName(EventData.IniGroupName)
|
||||
if grp:IsGround() then
|
||||
self:T(string.format("Player %s entered GROUND unit %s!",EventData.IniPlayerName,EventData.IniUnitName))
|
||||
local IsPlayer = EventData.IniDCSUnit:getPlayerName()
|
||||
if IsPlayer then
|
||||
|
||||
local client=_DATABASE.CLIENTS[EventData.IniDCSUnitName] --Wrapper.Client#CLIENT
|
||||
|
||||
-- Add client in case it does not exist already.
|
||||
if not client then
|
||||
|
||||
-- Debug info.
|
||||
self:I(string.format("Player '%s' joined ground unit '%s' of group '%s'", tostring(EventData.IniPlayerName), tostring(EventData.IniDCSUnitName), tostring(EventData.IniDCSGroupName)))
|
||||
|
||||
client=_DATABASE:AddClient(EventData.IniDCSUnitName)
|
||||
|
||||
-- Add player.
|
||||
client:AddPlayer(EventData.IniPlayerName)
|
||||
|
||||
-- Add player.
|
||||
if not _DATABASE.PLAYERS[EventData.IniPlayerName] then
|
||||
_DATABASE:AddPlayer( EventData.IniUnitName, EventData.IniPlayerName )
|
||||
end
|
||||
|
||||
-- Player settings.
|
||||
local Settings = SETTINGS:Set( EventData.IniPlayerName )
|
||||
Settings:SetPlayerMenu(EventData.IniUnit)
|
||||
end
|
||||
--local player = _DATABASE:FindClient( EventData.IniPlayerName )
|
||||
self:Propagate(client)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this Client Manager to auto-propagate menus **once** to newly joined players. Useful if you have **one** menu structure only. Does not automatically push follow-up changes to the client(s).
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:InitAutoPropagation()
|
||||
-- Player Events
|
||||
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.Ejection, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.Crash, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
|
||||
self:SetEventPriority(6)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new entry in the **generic** structure.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string Text Text of the F10 menu entry.
|
||||
-- @param #CLIENTMENU Parent The parent menu entry.
|
||||
-- @param #string Function (optional) Function to call when the entry is used.
|
||||
-- @param ... (optional) Arguments for the Function, comma separated.
|
||||
-- @return #CLIENTMENU Entry
|
||||
function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...)
|
||||
self:T(self.lid.."NewEntry "..Text or "None")
|
||||
self.entrycount = self.entrycount + 1
|
||||
local entry = CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg))
|
||||
if not Parent then
|
||||
self.rootentries[self.entrycount] = entry
|
||||
end
|
||||
local depth = #entry.path
|
||||
if not self.menutree[depth] then self.menutree[depth] = {} end
|
||||
table.insert(self.menutree[depth],entry.UUID)
|
||||
self.flattree[entry.UUID] = entry
|
||||
return entry
|
||||
end
|
||||
|
||||
--- Check matching entry in the generic structure by UUID.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string UUID UUID of the menu entry.
|
||||
-- @return #boolean Exists
|
||||
function CLIENTMENUMANAGER:EntryUUIDExists(UUID)
|
||||
local exists = self.flattree[UUID] and true or false
|
||||
return exists
|
||||
end
|
||||
|
||||
--- Find matching entry in the generic structure by UUID.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string UUID UUID of the menu entry.
|
||||
-- @return #CLIENTMENU Entry The #CLIENTMENU object found or nil.
|
||||
function CLIENTMENUMANAGER:FindEntryByUUID(UUID)
|
||||
self:T(self.lid.."FindEntryByUUID "..UUID or "None")
|
||||
local entry = nil
|
||||
for _gid,_entry in pairs(self.flattree) do
|
||||
local Entry = _entry -- #CLIENTMENU
|
||||
if Entry and Entry.UUID == UUID then
|
||||
entry = Entry
|
||||
end
|
||||
end
|
||||
return entry
|
||||
end
|
||||
|
||||
--- Find matching entries by text in the generic structure by UUID.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string Text Text or partial text of the menu entry to find.
|
||||
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
|
||||
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
|
||||
-- @return #table Table of matching #CLIENTMENU objects
|
||||
-- @return #number Number of matches
|
||||
function CLIENTMENUMANAGER:FindUUIDsByText(Text,Parent)
|
||||
self:T(self.lid.."FindUUIDsByText "..Text or "None")
|
||||
local matches = {}
|
||||
local entries = {}
|
||||
local n = 0
|
||||
for _uuid,_entry in pairs(self.flattree) do
|
||||
local Entry = _entry -- #CLIENTMENU
|
||||
if Parent then
|
||||
if Entry and string.find(Entry.name,Text,1,true) and string.find(Entry.UUID,Parent.UUID,1,true) then
|
||||
table.insert(matches,_uuid)
|
||||
table.insert(entries,Entry )
|
||||
n=n+1
|
||||
end
|
||||
else
|
||||
if Entry and string.find(Entry.name,Text,1,true) then
|
||||
table.insert(matches,_uuid)
|
||||
table.insert(entries,Entry )
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
end
|
||||
return matches, entries, n
|
||||
end
|
||||
|
||||
--- Find matching entries in the generic structure by the menu text.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #string Text Text or partial text of the F10 menu entry.
|
||||
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
|
||||
-- @return #table Table of matching #CLIENTMENU objects.
|
||||
-- @return #number Number of matches
|
||||
function CLIENTMENUMANAGER:FindEntriesByText(Text,Parent)
|
||||
self:T(self.lid.."FindEntriesByText "..Text or "None")
|
||||
local matches, objects, number = self:FindUUIDsByText(Text, Parent)
|
||||
return objects, number
|
||||
end
|
||||
|
||||
--- Find matching entries under a parent in the generic structure by UUID.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Parent Find entries under this parent entry.
|
||||
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
|
||||
-- @return #table Table of matching #CLIENTMENU objects
|
||||
-- @return #number Number of matches
|
||||
function CLIENTMENUMANAGER:FindUUIDsByParent(Parent)
|
||||
self:T(self.lid.."FindUUIDsByParent")
|
||||
local matches = {}
|
||||
local entries = {}
|
||||
local n = 0
|
||||
for _uuid,_entry in pairs(self.flattree) do
|
||||
local Entry = _entry -- #CLIENTMENU
|
||||
if Parent then
|
||||
if Entry and string.find(Entry.UUID,Parent.UUID,1,true) then
|
||||
table.insert(matches,_uuid)
|
||||
table.insert(entries,Entry )
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
end
|
||||
return matches, entries, n
|
||||
end
|
||||
|
||||
--- Find matching entries in the generic structure under a parent.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Parent Find entries under this parent entry.
|
||||
-- @return #table Table of matching #CLIENTMENU objects.
|
||||
-- @return #number Number of matches
|
||||
function CLIENTMENUMANAGER:FindEntriesByParent(Parent)
|
||||
self:T(self.lid.."FindEntriesByParent")
|
||||
local matches, objects, number = self:FindUUIDsByParent(Parent)
|
||||
return objects, number
|
||||
end
|
||||
|
||||
--- Alter the text of a leaf entry in the generic structure and push to one specific client's F10 menu.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The menu entry.
|
||||
-- @param #string Text New Text of the F10 menu entry.
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) The client for whom to alter the entry, if nil done for all clients.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:ChangeEntryText(Entry, Text, Client)
|
||||
self:T(self.lid.."ChangeEntryText "..Text or "None")
|
||||
local newentry = CLIENTMENU:NewEntry(nil,Text,Entry.Parent,Entry.Function,unpack(Entry.Functionargs))
|
||||
self:DeleteF10Entry(Entry,Client)
|
||||
self:DeleteGenericEntry(Entry)
|
||||
if not Entry.Parent then
|
||||
self.rootentries[self.entrycount] = newentry
|
||||
end
|
||||
local depth = #newentry.path
|
||||
if not self.menutree[depth] then self.menutree[depth] = {} end
|
||||
table.insert(self.menutree[depth],newentry.UUID)
|
||||
self.flattree[newentry.UUID] = newentry
|
||||
self:AddEntry(newentry,Client)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Push the complete menu structure to each of the clients in the set - refresh the menu tree of the clients.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, propagate only for this client.
|
||||
-- @return #CLIENTMENU Entry
|
||||
function CLIENTMENUMANAGER:Propagate(Client)
|
||||
self:T(self.lid.."Propagate")
|
||||
--self:I(UTILS.PrintTableToLog(Client,1))
|
||||
local knownunits = {} -- track so we can ID multi seated
|
||||
local Set = self.clientset.Set
|
||||
if Client then
|
||||
Set = {Client}
|
||||
end
|
||||
self:ResetMenu(Client)
|
||||
for _,_client in pairs(Set) do
|
||||
local client = _client -- Wrapper.Client#CLIENT
|
||||
if client and client:IsAlive() then
|
||||
local playerunit = client:GetName()
|
||||
--local playergroup = client:GetGroup()
|
||||
local playername = client:GetPlayerName() or "none"
|
||||
if not knownunits[playerunit] then
|
||||
knownunits[playerunit] = true
|
||||
else
|
||||
self:I("Player in multi seat unit: "..playername)
|
||||
break -- multi seat already build
|
||||
end
|
||||
if not self.playertree[playername] then
|
||||
self.playertree[playername] = {}
|
||||
end
|
||||
for level,branch in pairs (self.menutree) do
|
||||
self:T("Building branch:" .. level)
|
||||
for _,leaf in pairs(branch) do
|
||||
self:T("Building leaf:" .. leaf)
|
||||
local entry = self:FindEntryByUUID(leaf)
|
||||
if entry then
|
||||
self:T("Found generic entry:" .. entry.UUID)
|
||||
local parent = nil
|
||||
if entry.Parent and entry.Parent.UUID then
|
||||
parent = self.playertree[playername][entry.Parent.UUID] or self:FindEntryByUUID(entry.Parent.UUID)
|
||||
end
|
||||
self.playertree[playername][entry.UUID] = CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs))
|
||||
self.playertree[playername][entry.UUID].Once = entry.Once
|
||||
else
|
||||
self:T("NO generic entry for:" .. leaf)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Push a single previously created entry into the F10 menu structure of all clients.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to add.
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:AddEntry(Entry,Client)
|
||||
self:T(self.lid.."AddEntry")
|
||||
local Set = self.clientset.Set
|
||||
local knownunits = {}
|
||||
if Client then
|
||||
Set = {Client}
|
||||
end
|
||||
for _,_client in pairs(Set) do
|
||||
local client = _client -- Wrapper.Client#CLIENT
|
||||
if client and client:IsAlive() then
|
||||
local playername = client:GetPlayerName() or "None"
|
||||
local unitname = client:GetName()
|
||||
if not knownunits[unitname] then
|
||||
knownunits[unitname] = true
|
||||
else
|
||||
self:I("Player in multi seat unit: "..playername)
|
||||
break
|
||||
end
|
||||
if Entry then
|
||||
self:T("Adding generic entry:" .. Entry.UUID)
|
||||
local parent = nil
|
||||
if not self.playertree[playername] then
|
||||
self.playertree[playername] = {}
|
||||
end
|
||||
if Entry.Parent and Entry.Parent.UUID then
|
||||
parent = self.playertree[playername][Entry.Parent.UUID] or self:FindEntryByUUID(Entry.Parent.UUID)
|
||||
end
|
||||
self.playertree[playername][Entry.UUID] = CLIENTMENU:NewEntry(client,Entry.name,parent,Entry.Function,unpack(Entry.Functionargs))
|
||||
self.playertree[playername][Entry.UUID].Once = Entry.Once
|
||||
else
|
||||
self:T("NO generic entry given")
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Blank out the menu - remove **all root entries** and all entries below from the client's F10 menus, leaving the generic structure untouched.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, remove only for this client.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:ResetMenu(Client)
|
||||
self:T(self.lid.."ResetMenu")
|
||||
for _,_entry in pairs(self.rootentries) do
|
||||
--local RootEntry = self.structure.generic[_entry]
|
||||
if _entry then
|
||||
self:DeleteF10Entry(_entry,Client)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Blank out the menu - remove **all root entries** and all entries below from all clients' F10 menus, and **delete** the generic structure.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:ResetMenuComplete()
|
||||
self:T(self.lid.."ResetMenuComplete")
|
||||
for _,_entry in pairs(self.rootentries) do
|
||||
--local RootEntry = self.structure.generic[_entry]
|
||||
if _entry then
|
||||
self:DeleteF10Entry(_entry)
|
||||
end
|
||||
end
|
||||
self.playertree = nil
|
||||
self.playertree = {}
|
||||
self.rootentries = nil
|
||||
self.rootentries = {}
|
||||
self.menutree = nil
|
||||
self.menutree = {}
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the entry and all entries below the given entry from the client's F10 menus.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to remove
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client)
|
||||
self:T(self.lid.."DeleteF10Entry")
|
||||
local Set = self.clientset.Set
|
||||
if Client then
|
||||
Set = {Client}
|
||||
end
|
||||
for _,_client in pairs(Set) do
|
||||
if _client and _client:IsAlive() then
|
||||
local playername = _client:GetPlayerName()
|
||||
if self.playertree[playername] then
|
||||
local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
|
||||
if centry then
|
||||
--self:I("Match for "..Entry.UUID)
|
||||
centry:Clear()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove the entry and all entries below the given entry from the generic tree.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry to remove
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:DeleteGenericEntry(Entry)
|
||||
self:T(self.lid.."DeleteGenericEntry")
|
||||
|
||||
if Entry.Children and #Entry.Children > 0 then
|
||||
self:RemoveGenericSubEntries(Entry)
|
||||
end
|
||||
|
||||
local depth = #Entry.path
|
||||
local uuid = Entry.UUID
|
||||
|
||||
local tbl = UTILS.DeepCopy(self.menutree)
|
||||
|
||||
if tbl[depth] then
|
||||
for i=depth,#tbl do
|
||||
--self:I("Level = "..i)
|
||||
for _id,_uuid in pairs(tbl[i]) do
|
||||
self:T(_uuid)
|
||||
if string.find(_uuid,uuid,1,true) or _uuid == uuid then
|
||||
--self:I("Match for ".._uuid)
|
||||
self.menutree[i][_id] = nil
|
||||
self.flattree[_uuid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove all entries below the given entry from the generic tree.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:RemoveGenericSubEntries(Entry)
|
||||
self:T(self.lid.."RemoveGenericSubEntries")
|
||||
|
||||
local depth = #Entry.path + 1
|
||||
local uuid = Entry.UUID
|
||||
|
||||
local tbl = UTILS.DeepCopy(self.menutree)
|
||||
|
||||
if tbl[depth] then
|
||||
for i=depth,#tbl do
|
||||
self:T("Level = "..i)
|
||||
for _id,_uuid in pairs(tbl[i]) do
|
||||
self:T(_uuid)
|
||||
if string.find(_uuid,uuid,1,true) then
|
||||
self:T("Match for ".._uuid)
|
||||
self.menutree[i][_id] = nil
|
||||
self.flattree[_uuid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Remove all entries below the given entry from the client's F10 menus.
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
|
||||
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched.
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:RemoveF10SubEntries(Entry,Client)
|
||||
self:T(self.lid.."RemoveSubEntries")
|
||||
local Set = self.clientset.Set
|
||||
if Client then
|
||||
Set = {Client}
|
||||
end
|
||||
for _,_client in pairs(Set) do
|
||||
if _client and _client:IsAlive() then
|
||||
local playername = _client:GetPlayerName()
|
||||
if self.playertree[playername] then
|
||||
local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
|
||||
centry:RemoveSubEntries()
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
-- End ClientMenu
|
||||
--
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
@@ -20,7 +20,6 @@
|
||||
-- * Manage database of hits to units and statics.
|
||||
-- * Manage database of destroys of units and statics.
|
||||
-- * Manage database of @{Core.Zone#ZONE_BASE} objects.
|
||||
-- * Manage database of @{Wrapper.DynamicCargo#DYNAMICCARGO} objects alive in the mission.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -38,9 +37,6 @@
|
||||
-- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID.
|
||||
-- @field #table CLIENTS Clients.
|
||||
-- @field #table STORAGES DCS warehouse storages.
|
||||
-- @field #table STNS Used Link16 octal numbers for F16/15/18/AWACS planes.
|
||||
-- @field #table SADL Used Link16 octal numbers for A10/C-II planes.
|
||||
-- @field #table DYNAMICCARGO Dynamic Cargo objects.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
|
||||
@@ -56,7 +52,6 @@
|
||||
-- * PLAYERS
|
||||
-- * CARGOS
|
||||
-- * STORAGES (DCS warehouses)
|
||||
-- * DYNAMICCARGO
|
||||
--
|
||||
-- On top, for internal MOOSE administration purposes, the DATABASE administers the Unit and Group TEMPLATES as defined within the Mission Editor.
|
||||
--
|
||||
@@ -98,9 +93,6 @@ DATABASE = {
|
||||
OPSZONES = {},
|
||||
PATHLINES = {},
|
||||
STORAGES = {},
|
||||
STNS={},
|
||||
SADL={},
|
||||
DYNAMICCARGO={},
|
||||
}
|
||||
|
||||
local _DATABASECoalition =
|
||||
@@ -139,7 +131,7 @@ function DATABASE:New()
|
||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) -- DCS 2.7.1 for Aerial units no dead event ATM
|
||||
--self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) -- DCS 2.7.1 for Aerial units no dead event ATM
|
||||
self:HandleEvent( EVENTS.Hit, self.AccountHits )
|
||||
self:HandleEvent( EVENTS.NewCargo )
|
||||
self:HandleEvent( EVENTS.DeleteCargo )
|
||||
@@ -147,8 +139,6 @@ function DATABASE:New()
|
||||
self:HandleEvent( EVENTS.DeleteZone )
|
||||
--self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event.
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit )
|
||||
-- DCS 2.9.7 Moose own dynamic cargo events
|
||||
self:HandleEvent( EVENTS.DynamicCargoRemoved, self._EventOnDynamicCargoRemoved)
|
||||
|
||||
self:_RegisterTemplates()
|
||||
self:_RegisterGroupsAndUnits()
|
||||
@@ -176,30 +166,24 @@ end
|
||||
--- Adds a Unit based on the Unit Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string DCSUnitName Unit name.
|
||||
-- @param #boolean force
|
||||
-- @return Wrapper.Unit#UNIT The added unit.
|
||||
function DATABASE:AddUnit( DCSUnitName, force )
|
||||
|
||||
local DCSunitName = DCSUnitName
|
||||
|
||||
if type(DCSunitName) == "number" then DCSunitName = string.format("%d",DCSUnitName) end
|
||||
|
||||
if not self.UNITS[DCSunitName] or force == true then
|
||||
function DATABASE:AddUnit( DCSUnitName )
|
||||
|
||||
if not self.UNITS[DCSUnitName] then
|
||||
-- Debug info.
|
||||
self:T( { "Add UNIT:", DCSunitName } )
|
||||
self:T( { "Add UNIT:", DCSUnitName } )
|
||||
|
||||
-- Register unit
|
||||
self.UNITS[DCSunitName]=UNIT:Register(DCSunitName)
|
||||
self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName)
|
||||
end
|
||||
|
||||
return self.UNITS[DCSunitName]
|
||||
return self.UNITS[DCSUnitName]
|
||||
end
|
||||
|
||||
|
||||
--- Deletes a Unit from the DATABASE based on the Unit Name.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:DeleteUnit( DCSUnitName )
|
||||
self:T("DeleteUnit "..tostring(DCSUnitName))
|
||||
self.UNITS[DCSUnitName] = nil
|
||||
end
|
||||
|
||||
@@ -211,9 +195,10 @@ function DATABASE:AddStatic( DCSStaticName )
|
||||
|
||||
if not self.STATICS[DCSStaticName] then
|
||||
self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName )
|
||||
return self.STATICS[DCSStaticName]
|
||||
end
|
||||
|
||||
return self.STATICS[DCSStaticName]
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
@@ -223,42 +208,16 @@ function DATABASE:DeleteStatic( DCSStaticName )
|
||||
self.STATICS[DCSStaticName] = nil
|
||||
end
|
||||
|
||||
--- Finds a STATIC based on the Static Name.
|
||||
--- Finds a STATIC based on the StaticName.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string StaticName Name of the static object.
|
||||
-- @param #string StaticName
|
||||
-- @return Wrapper.Static#STATIC The found STATIC.
|
||||
function DATABASE:FindStatic( StaticName )
|
||||
|
||||
local StaticFound = self.STATICS[StaticName]
|
||||
return StaticFound
|
||||
end
|
||||
|
||||
--- Add a DynamicCargo to the database.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string Name Name of the dynamic cargo.
|
||||
-- @return Wrapper.DynamicCargo#DYNAMICCARGO The dynamic cargo object.
|
||||
function DATABASE:AddDynamicCargo( Name )
|
||||
if not self.DYNAMICCARGO[Name] then
|
||||
self.DYNAMICCARGO[Name] = DYNAMICCARGO:Register(Name)
|
||||
end
|
||||
return self.DYNAMICCARGO[Name]
|
||||
end
|
||||
|
||||
--- Finds a DYNAMICCARGO based on the Dynamic Cargo Name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string DynamicCargoName
|
||||
-- @return Wrapper.DynamicCargo#DYNAMICCARGO The found DYNAMICCARGO.
|
||||
function DATABASE:FindDynamicCargo( DynamicCargoName )
|
||||
local StaticFound = self.DYNAMICCARGO[DynamicCargoName]
|
||||
return StaticFound
|
||||
end
|
||||
|
||||
--- Deletes a DYNAMICCARGO from the DATABASE based on the Dynamic Cargo Name.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:DeleteDynamicCargo( DynamicCargoName )
|
||||
self.DYNAMICCARGO[DynamicCargoName] = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds a Airbase based on the Airbase Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string AirbaseName The name of the airbase.
|
||||
@@ -850,19 +809,14 @@ end
|
||||
--- Adds a CLIENT based on the ClientName in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client unit.
|
||||
-- @param #boolean Force (optional) Force registration of client.
|
||||
-- @return Wrapper.Client#CLIENT The client object.
|
||||
function DATABASE:AddClient( ClientName, Force )
|
||||
|
||||
local DCSUnitName = ClientName
|
||||
|
||||
if type(DCSUnitName) == "number" then DCSUnitName = string.format("%d",ClientName) end
|
||||
|
||||
if not self.CLIENTS[DCSUnitName] or Force == true then
|
||||
self.CLIENTS[DCSUnitName] = CLIENT:Register( DCSUnitName )
|
||||
function DATABASE:AddClient( ClientName )
|
||||
|
||||
if not self.CLIENTS[ClientName] then
|
||||
self.CLIENTS[ClientName] = CLIENT:Register( ClientName )
|
||||
end
|
||||
|
||||
return self.CLIENTS[DCSUnitName]
|
||||
return self.CLIENTS[ClientName]
|
||||
end
|
||||
|
||||
|
||||
@@ -872,28 +826,16 @@ end
|
||||
-- @return Wrapper.Group#GROUP The found GROUP.
|
||||
function DATABASE:FindGroup( GroupName )
|
||||
|
||||
if type(GroupName) ~= "string" or GroupName == "" then return end
|
||||
|
||||
local GroupFound = self.GROUPS[GroupName]
|
||||
|
||||
if GroupFound == nil and GroupName ~= nil and self.Templates.Groups[GroupName] == nil then
|
||||
-- see if the group exists in the API, maybe a dynamic slot
|
||||
self:_RegisterDynamicGroup(GroupName)
|
||||
return self.GROUPS[GroupName]
|
||||
end
|
||||
|
||||
return GroupFound
|
||||
end
|
||||
|
||||
|
||||
--- Adds a GROUP based on the GroupName in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName
|
||||
-- @param #boolean force
|
||||
-- @return Wrapper.Group#GROUP The Group
|
||||
function DATABASE:AddGroup( GroupName, force )
|
||||
function DATABASE:AddGroup( GroupName )
|
||||
|
||||
if not self.GROUPS[GroupName] or force == true then
|
||||
if not self.GROUPS[GroupName] then
|
||||
self:T( { "Add GROUP:", GroupName } )
|
||||
self.GROUPS[GroupName] = GROUP:Register( GroupName )
|
||||
end
|
||||
@@ -904,11 +846,9 @@ end
|
||||
--- Adds a player based on the Player Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:AddPlayer( UnitName, PlayerName )
|
||||
|
||||
if type(UnitName) == "number" then UnitName = string.format("%d",UnitName) end
|
||||
|
||||
|
||||
if PlayerName then
|
||||
self:I( { "Add player for unit:", UnitName, PlayerName } )
|
||||
self:T( { "Add player for unit:", UnitName, PlayerName } )
|
||||
self.PLAYERS[PlayerName] = UnitName
|
||||
self.PLAYERUNITS[PlayerName] = self:FindUnit( UnitName )
|
||||
self.PLAYERSJOINED[PlayerName] = PlayerName
|
||||
@@ -916,21 +856,6 @@ function DATABASE:AddPlayer( UnitName, PlayerName )
|
||||
|
||||
end
|
||||
|
||||
--- Get a PlayerName by UnitName from PLAYERS in DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @return #string PlayerName
|
||||
-- @return Wrapper.Unit#UNIT PlayerUnit
|
||||
function DATABASE:_FindPlayerNameByUnitName(UnitName)
|
||||
if UnitName then
|
||||
for playername,unitname in pairs(self.PLAYERS) do
|
||||
if unitname == UnitName and self.PLAYERUNITS[playername] and self.PLAYERUNITS[playername]:IsAlive() then
|
||||
return playername, self.PLAYERUNITS[playername]
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Deletes a player from the DATABASE based on the Player Name.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:DeletePlayer( UnitName, PlayerName )
|
||||
@@ -1003,7 +928,7 @@ function DATABASE:Spawn( SpawnTemplate )
|
||||
SpawnTemplate.CountryID = nil
|
||||
SpawnTemplate.CategoryID = nil
|
||||
|
||||
self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID, SpawnTemplate.name )
|
||||
self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID )
|
||||
|
||||
self:T3( SpawnTemplate )
|
||||
coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate )
|
||||
@@ -1080,7 +1005,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
self.Templates.Groups[GroupTemplateName].CategoryID = CategoryID
|
||||
self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionSide
|
||||
self.Templates.Groups[GroupTemplateName].CountryID = CountryID
|
||||
|
||||
|
||||
local UnitNames = {}
|
||||
|
||||
for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do
|
||||
@@ -1104,31 +1029,10 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID
|
||||
self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate
|
||||
end
|
||||
|
||||
if UnitTemplate.AddPropAircraft then
|
||||
if UnitTemplate.AddPropAircraft.STN_L16 then
|
||||
local stn = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.STN_L16)
|
||||
if stn == nil or stn < 1 then
|
||||
self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
|
||||
else
|
||||
self.STNS[stn] = UnitTemplate.name
|
||||
self:T("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
|
||||
end
|
||||
end
|
||||
if UnitTemplate.AddPropAircraft.SADL_TN then
|
||||
local sadl = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.SADL_TN)
|
||||
if sadl == nil or sadl < 1 then
|
||||
self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
|
||||
else
|
||||
self.SADL[sadl] = UnitTemplate.name
|
||||
self:T("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
|
||||
end
|
||||
|
||||
|
||||
-- Debug info.
|
||||
self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
|
||||
Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID,
|
||||
@@ -1139,92 +1043,15 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
)
|
||||
end
|
||||
|
||||
--- Get next (consecutive) free STN as octal number.
|
||||
-- @param #DATABASE self
|
||||
-- @param #number octal Starting octal.
|
||||
-- @param #string unitname Name of the associated unit.
|
||||
-- @return #number Octal
|
||||
function DATABASE:GetNextSTN(octal,unitname)
|
||||
local first = UTILS.OctalToDecimal(octal) or 0
|
||||
if self.STNS[first] == unitname then return octal end
|
||||
local nextoctal = 77777
|
||||
local found = false
|
||||
if 32767-first < 10 then
|
||||
first = 0
|
||||
end
|
||||
for i=first+1,32767 do
|
||||
if self.STNS[i] == nil then
|
||||
found = true
|
||||
nextoctal = UTILS.DecimalToOctal(i)
|
||||
self.STNS[i] = unitname
|
||||
self:T("Register STN "..tostring(nextoctal).." for ".. unitname)
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
self:E(string.format("WARNING: No next free STN past %05d found!",octal))
|
||||
-- cleanup
|
||||
local NewSTNS = {}
|
||||
for _id,_name in pairs(self.STNS) do
|
||||
if self.UNITS[_name] ~= nil then
|
||||
NewSTNS[_id] = _name
|
||||
end
|
||||
end
|
||||
self.STNS = nil
|
||||
self.STNS = NewSTNS
|
||||
end
|
||||
return nextoctal
|
||||
end
|
||||
|
||||
--- Get next (consecutive) free SADL as octal number.
|
||||
-- @param #DATABASE self
|
||||
-- @param #number octal Starting octal.
|
||||
-- @param #string unitname Name of the associated unit.
|
||||
-- @return #number Octal
|
||||
function DATABASE:GetNextSADL(octal,unitname)
|
||||
local first = UTILS.OctalToDecimal(octal) or 0
|
||||
if self.SADL[first] == unitname then return octal end
|
||||
local nextoctal = 7777
|
||||
local found = false
|
||||
if 4095-first < 10 then
|
||||
first = 0
|
||||
end
|
||||
for i=first+1,4095 do
|
||||
if self.STNS[i] == nil then
|
||||
found = true
|
||||
nextoctal = UTILS.DecimalToOctal(i)
|
||||
self.SADL[i] = unitname
|
||||
self:T("Register SADL "..tostring(nextoctal).." for ".. unitname)
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
self:E(string.format("WARNING: No next free SADL past %04d found!",octal))
|
||||
-- cleanup
|
||||
local NewSTNS = {}
|
||||
for _id,_name in pairs(self.SADL) do
|
||||
if self.UNITS[_name] ~= nil then
|
||||
NewSTNS[_id] = _name
|
||||
end
|
||||
end
|
||||
self.SADL = nil
|
||||
self.SADL = NewSTNS
|
||||
end
|
||||
return nextoctal
|
||||
end
|
||||
|
||||
--- Get group template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName Group name.
|
||||
-- @return #table Group template table.
|
||||
function DATABASE:GetGroupTemplate( GroupName )
|
||||
local GroupTemplate=nil
|
||||
if self.Templates.Groups[GroupName] then
|
||||
GroupTemplate = self.Templates.Groups[GroupName].Template
|
||||
GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID
|
||||
GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID
|
||||
GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID
|
||||
end
|
||||
local GroupTemplate = self.Templates.Groups[GroupName].Template
|
||||
GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID
|
||||
GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID
|
||||
GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID
|
||||
return GroupTemplate
|
||||
end
|
||||
|
||||
@@ -1269,43 +1096,6 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get a generic static cargo group template from scratch for dynamic cargo spawns register. Does not register the template!
|
||||
-- @param #DATABASE self
|
||||
-- @param #string Name Name of the static.
|
||||
-- @param #string Typename Typename of the static. Defaults to "container_cargo".
|
||||
-- @param #number Mass Mass of the static. Defaults to 0.
|
||||
-- @param #number Coalition Coalition of the static. Defaults to coalition.side.BLUE.
|
||||
-- @param #number Country Country of the static. Defaults to country.id.GERMANY.
|
||||
-- @return #table Static template table.
|
||||
function DATABASE:_GetGenericStaticCargoGroupTemplate(Name,Typename,Mass,Coalition,Country)
|
||||
local StaticTemplate = {}
|
||||
StaticTemplate.name = Name or "None"
|
||||
StaticTemplate.units = { [1] = {
|
||||
name = Name,
|
||||
resourcePayload = {
|
||||
["weapons"] = {},
|
||||
["aircrafts"] = {},
|
||||
["gasoline"] = 0,
|
||||
["diesel"] = 0,
|
||||
["methanol_mixture"] = 0,
|
||||
["jet_fuel"] = 0,
|
||||
},
|
||||
["mass"] = Mass or 0,
|
||||
["category"] = "Cargos",
|
||||
["canCargo"] = true,
|
||||
["type"] = Typename or "container_cargo",
|
||||
["rate"] = 100,
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["heading"] = 0,
|
||||
}}
|
||||
StaticTemplate.CategoryID = "static"
|
||||
StaticTemplate.CoalitionID = Coalition or coalition.side.BLUE
|
||||
StaticTemplate.CountryID = Country or country.id.GERMANY
|
||||
--UTILS.PrintTableToLog(StaticTemplate)
|
||||
return StaticTemplate
|
||||
end
|
||||
|
||||
--- Get static group template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string StaticName Name of the static.
|
||||
@@ -1379,11 +1169,7 @@ end
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Coalition ID.
|
||||
function DATABASE:GetCoalitionFromClientTemplate( ClientName )
|
||||
if self.Templates.ClientsByName[ClientName] then
|
||||
return self.Templates.ClientsByName[ClientName].CoalitionID
|
||||
end
|
||||
self:T("WARNING: Template does not exist for client "..tostring(ClientName))
|
||||
return nil
|
||||
return self.Templates.ClientsByName[ClientName].CoalitionID
|
||||
end
|
||||
|
||||
--- Get category ID from client name.
|
||||
@@ -1391,11 +1177,7 @@ end
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Category ID.
|
||||
function DATABASE:GetCategoryFromClientTemplate( ClientName )
|
||||
if self.Templates.ClientsByName[ClientName] then
|
||||
return self.Templates.ClientsByName[ClientName].CategoryID
|
||||
end
|
||||
self:T("WARNING: Template does not exist for client "..tostring(ClientName))
|
||||
return nil
|
||||
return self.Templates.ClientsByName[ClientName].CategoryID
|
||||
end
|
||||
|
||||
--- Get country ID from client name.
|
||||
@@ -1403,11 +1185,7 @@ end
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Country ID.
|
||||
function DATABASE:GetCountryFromClientTemplate( ClientName )
|
||||
if self.Templates.ClientsByName[ClientName] then
|
||||
return self.Templates.ClientsByName[ClientName].CountryID
|
||||
end
|
||||
self:T("WARNING: Template does not exist for client "..tostring(ClientName))
|
||||
return nil
|
||||
return self.Templates.ClientsByName[ClientName].CountryID
|
||||
end
|
||||
|
||||
--- Airbase
|
||||
@@ -1453,36 +1231,6 @@ function DATABASE:_RegisterPlayers()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Private method that registers a single dynamic slot Group and Units within in the mission.
|
||||
-- @param #DATABASE self
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterDynamicGroup(Groupname)
|
||||
local DCSGroup = Group.getByName(Groupname)
|
||||
if DCSGroup and DCSGroup:isExist() then
|
||||
|
||||
-- Group name.
|
||||
local DCSGroupName = DCSGroup:getName()
|
||||
|
||||
-- Add group.
|
||||
self:I(string.format("Register Group: %s", tostring(DCSGroupName)))
|
||||
self:AddGroup( DCSGroupName, true )
|
||||
|
||||
-- Loop over units in group.
|
||||
for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do
|
||||
|
||||
-- Get unit name.
|
||||
local DCSUnitName = DCSUnit:getName()
|
||||
|
||||
-- Add unit.
|
||||
self:I(string.format("Register Unit: %s", tostring(DCSUnitName)))
|
||||
self:AddUnit( tostring(DCSUnitName), true )
|
||||
|
||||
end
|
||||
else
|
||||
self:E({"Group does not exist: ", DCSGroup})
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Private method that registers all Groups and Units within in the mission.
|
||||
-- @param #DATABASE self
|
||||
@@ -1581,29 +1329,12 @@ end
|
||||
-- @param DCS#Airbase airbase Airbase.
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterAirbase(airbase)
|
||||
|
||||
local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false
|
||||
local countHSyria = 0
|
||||
|
||||
|
||||
if airbase then
|
||||
|
||||
-- Get the airbase name.
|
||||
local DCSAirbaseName = airbase:getName()
|
||||
|
||||
-- DCS 2.9.8.1107 added 143 helipads all named H with the same object ID ..
|
||||
if IsSyria and DCSAirbaseName == "H" and countHSyria > 0 then
|
||||
--[[
|
||||
local p = airbase:getPosition().p
|
||||
local mgrs = COORDINATE:New(p.x,p.z,p.y):ToStringMGRS()
|
||||
self:I("Airbase on Syria map named H @ "..mgrs)
|
||||
countHSyria = countHSyria + 1
|
||||
if countHSyria > 1 then return self end
|
||||
--]]
|
||||
return self
|
||||
elseif IsSyria and DCSAirbaseName == "H" and countHSyria == 0 then
|
||||
countHSyria = countHSyria + 1
|
||||
end
|
||||
|
||||
|
||||
-- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
|
||||
local airbaseID=airbase:getID()
|
||||
|
||||
@@ -1643,7 +1374,7 @@ end
|
||||
-- @param #DATABASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function DATABASE:_EventOnBirth( Event )
|
||||
self:T( { Event } )
|
||||
self:F( { Event } )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
@@ -1651,17 +1382,7 @@ function DATABASE:_EventOnBirth( Event )
|
||||
|
||||
-- Add static object to DB.
|
||||
self:AddStatic( Event.IniDCSUnitName )
|
||||
|
||||
elseif Event.IniObjectCategory == Object.Category.CARGO and string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then
|
||||
|
||||
-- Add dynamic cargo object to DB
|
||||
|
||||
local cargo = self:AddDynamicCargo(Event.IniDCSUnitName)
|
||||
|
||||
self:I(string.format("Adding dynamic cargo %s", tostring(Event.IniDCSUnitName)))
|
||||
|
||||
self:CreateEventNewDynamicCargo( cargo )
|
||||
|
||||
else
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.UNIT then
|
||||
@@ -1682,9 +1403,9 @@ function DATABASE:_EventOnBirth( Event )
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.UNIT then
|
||||
|
||||
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
|
||||
|
||||
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
|
||||
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
|
||||
|
||||
-- Client
|
||||
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
|
||||
@@ -1699,11 +1420,11 @@ function DATABASE:_EventOnBirth( Event )
|
||||
if PlayerName then
|
||||
|
||||
-- Debug info.
|
||||
self:I(string.format("Player '%s' joined unit '%s' (%s) of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniTypeName), tostring(Event.IniDCSGroupName)))
|
||||
|
||||
self:I(string.format("Player '%s' joined unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName)))
|
||||
|
||||
-- Add client in case it does not exist already.
|
||||
if client == nil or (client and client:CountPlayers() == 0) then
|
||||
client=self:AddClient(Event.IniDCSUnitName, true)
|
||||
if not client then
|
||||
client=self:AddClient(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
-- Add player.
|
||||
@@ -1713,19 +1434,14 @@ function DATABASE:_EventOnBirth( Event )
|
||||
if not self.PLAYERS[PlayerName] then
|
||||
self:AddPlayer( Event.IniUnitName, PlayerName )
|
||||
end
|
||||
|
||||
local function SetPlayerSettings(self,PlayerName,IniUnit)
|
||||
-- Player settings.
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
--Settings:SetPlayerMenu(Event.IniUnit)
|
||||
Settings:SetPlayerMenu(IniUnit)
|
||||
-- Create an event.
|
||||
self:CreateEventPlayerEnterAircraft(IniUnit)
|
||||
--self:CreateEventPlayerEnterAircraft(Event.IniUnit)
|
||||
end
|
||||
|
||||
self:ScheduleOnce(1,SetPlayerSettings,self,PlayerName,Event.IniUnit)
|
||||
|
||||
|
||||
-- Player settings.
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu(Event.IniUnit)
|
||||
|
||||
-- Create an event.
|
||||
self:CreateEventPlayerEnterAircraft(Event.IniUnit)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1739,6 +1455,7 @@ end
|
||||
-- @param #DATABASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function DATABASE:_EventOnDeadOrCrash( Event )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
local name=Event.IniDCSUnitName
|
||||
@@ -1746,7 +1463,7 @@ function DATABASE:_EventOnDeadOrCrash( Event )
|
||||
if Event.IniObjectCategory == 3 then
|
||||
|
||||
---
|
||||
-- STATICS
|
||||
-- STATICS
|
||||
---
|
||||
|
||||
if self.STATICS[Event.IniDCSUnitName] then
|
||||
@@ -1756,7 +1473,7 @@ function DATABASE:_EventOnDeadOrCrash( Event )
|
||||
---
|
||||
-- Maybe a UNIT?
|
||||
---
|
||||
|
||||
|
||||
-- Delete unit.
|
||||
if self.UNITS[Event.IniDCSUnitName] then
|
||||
self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName))
|
||||
@@ -1779,8 +1496,7 @@ function DATABASE:_EventOnDeadOrCrash( Event )
|
||||
|
||||
-- Delete unit.
|
||||
if self.UNITS[Event.IniDCSUnitName] then
|
||||
self:ScheduleOnce(1,self.DeleteUnit,self,Event.IniDCSUnitName)
|
||||
--self:DeleteUnit(Event.IniDCSUnitName)
|
||||
self:DeleteUnit(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
-- Remove client players.
|
||||
@@ -1847,15 +1563,6 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
|
||||
end
|
||||
end
|
||||
|
||||
--- Handles the OnDynamicCargoRemoved event to clean the active dynamic cargo table.
|
||||
-- @param #DATABASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function DATABASE:_EventOnDynamicCargoRemoved( Event )
|
||||
self:T( { Event } )
|
||||
if Event.IniDynamicCargoName then
|
||||
self:DeleteDynamicCargo(Event.IniDynamicCargoName)
|
||||
end
|
||||
end
|
||||
|
||||
--- Handles the OnPlayerLeaveUnit event to clean the active players table.
|
||||
-- @param #DATABASE self
|
||||
@@ -1879,7 +1586,7 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
-- Try to get the player name. This can be buggy for multicrew aircraft!
|
||||
local PlayerName = Event.IniPlayerName or Event.IniUnit:GetPlayerName() or FindPlayerName(Event.IniUnitName)
|
||||
local PlayerName = Event.IniUnit:GetPlayerName() or FindPlayerName(Event.IniUnitName)
|
||||
|
||||
if PlayerName then
|
||||
|
||||
@@ -1897,7 +1604,6 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
|
||||
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
|
||||
if client then
|
||||
client:RemovePlayer(PlayerName)
|
||||
--self.PLAYERSETTINGS[PlayerName] = nil
|
||||
end
|
||||
|
||||
end
|
||||
@@ -2276,7 +1982,7 @@ function DATABASE:_RegisterTemplates()
|
||||
for group_num, Template in pairs(obj_type_data.group) do
|
||||
|
||||
if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group
|
||||
|
||||
|
||||
self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
|
||||
|
||||
else
|
||||
|
||||
@@ -194,11 +194,6 @@ world.event.S_EVENT_NEW_ZONE_GOAL = world.event.S_EVENT_MAX + 1004
|
||||
world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1005
|
||||
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1006
|
||||
world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT = world.event.S_EVENT_MAX + 1007
|
||||
-- dynamic cargo
|
||||
world.event.S_EVENT_NEW_DYNAMIC_CARGO = world.event.S_EVENT_MAX + 1008
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_LOADED = world.event.S_EVENT_MAX + 1009
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED = world.event.S_EVENT_MAX + 1010
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_REMOVED = world.event.S_EVENT_MAX + 1011
|
||||
|
||||
|
||||
--- The different types of events supported by MOOSE.
|
||||
@@ -266,29 +261,17 @@ EVENTS = {
|
||||
SimulationStart = world.event.S_EVENT_SIMULATION_START or -1,
|
||||
WeaponRearm = world.event.S_EVENT_WEAPON_REARM or -1,
|
||||
WeaponDrop = world.event.S_EVENT_WEAPON_DROP or -1,
|
||||
-- Added with DCS 2.9.x
|
||||
--UnitTaskTimeout = world.event.S_EVENT_UNIT_TASK_TIMEOUT or -1,
|
||||
UnitTaskComplete = world.event.S_EVENT_UNIT_TASK_COMPLETE or -1,
|
||||
-- Added with DCS 2.9.0
|
||||
UnitTaskTimeout = world.event.S_EVENT_UNIT_TASK_TIMEOUT or -1,
|
||||
UnitTaskStage = world.event.S_EVENT_UNIT_TASK_STAGE or -1,
|
||||
--MacSubtaskScore = world.event.S_EVENT_MAC_SUBTASK_SCORE or -1,
|
||||
MacSubtaskScore = world.event.S_EVENT_MAC_SUBTASK_SCORE or -1,
|
||||
MacExtraScore = world.event.S_EVENT_MAC_EXTRA_SCORE or -1,
|
||||
MissionRestart = world.event.S_EVENT_MISSION_RESTART or -1,
|
||||
MissionWinner = world.event.S_EVENT_MISSION_WINNER or -1,
|
||||
RunwayTakeoff = world.event.S_EVENT_RUNWAY_TAKEOFF or -1,
|
||||
RunwayTouch = world.event.S_EVENT_RUNWAY_TOUCH or -1,
|
||||
MacLMSRestart = world.event.S_EVENT_MAC_LMS_RESTART or -1,
|
||||
SimulationFreeze = world.event.S_EVENT_SIMULATION_FREEZE or -1,
|
||||
SimulationUnfreeze = world.event.S_EVENT_SIMULATION_UNFREEZE or -1,
|
||||
HumanAircraftRepairStart = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or -1,
|
||||
HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1,
|
||||
-- dynamic cargo
|
||||
NewDynamicCargo = world.event.S_EVENT_NEW_DYNAMIC_CARGO or -1,
|
||||
DynamicCargoLoaded = world.event.S_EVENT_DYNAMIC_CARGO_LOADED or -1,
|
||||
DynamicCargoUnloaded = world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED or -1,
|
||||
DynamicCargoRemoved = world.event.S_EVENT_DYNAMIC_CARGO_REMOVED or -1,
|
||||
PostponedTakeoff = world.event.S_EVENT_POSTPONED_TAKEOFF or -1,
|
||||
PostponedLand = world.event.S_EVENT_POSTPONED_LAND or -1,
|
||||
}
|
||||
|
||||
|
||||
--- The Event structure
|
||||
-- Note that at the beginning of each field description, there is an indication which field will be populated depending on the object type involved in the Event:
|
||||
--
|
||||
@@ -344,9 +327,6 @@ EVENTS = {
|
||||
--
|
||||
-- @field Core.Zone#ZONE Zone The zone object.
|
||||
-- @field #string ZoneName The name of the zone.
|
||||
--
|
||||
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
|
||||
-- @field #string IniDynamicCargoName The dynamic cargo unit name.
|
||||
|
||||
|
||||
|
||||
@@ -666,24 +646,24 @@ local _EVENTMETA = {
|
||||
Text = "S_EVENT_WEAPON_DROP"
|
||||
},
|
||||
-- DCS 2.9
|
||||
--[EVENTS.UnitTaskTimeout] = {
|
||||
-- Order = 1,
|
||||
-- Side = "I",
|
||||
-- Event = "OnEventUnitTaskTimeout",
|
||||
-- Text = "S_EVENT_UNIT_TASK_TIMEOUT "
|
||||
--},
|
||||
[EVENTS.UnitTaskTimeout] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventUnitTaskTimeout",
|
||||
Text = "S_EVENT_UNIT_TASK_TIMEOUT "
|
||||
},
|
||||
[EVENTS.UnitTaskStage] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventUnitTaskStage",
|
||||
Text = "S_EVENT_UNIT_TASK_STAGE "
|
||||
},
|
||||
--[EVENTS.MacSubtaskScore] = {
|
||||
-- Order = 1,
|
||||
--Side = "I",
|
||||
--Event = "OnEventMacSubtaskScore",
|
||||
--Text = "S_EVENT_MAC_SUBTASK_SCORE"
|
||||
--},
|
||||
[EVENTS.MacSubtaskScore] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMacSubtaskScore",
|
||||
Text = "S_EVENT_MAC_SUBTASK_SCORE"
|
||||
},
|
||||
[EVENTS.MacExtraScore] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
@@ -702,76 +682,20 @@ local _EVENTMETA = {
|
||||
Event = "OnEventMissionWinner",
|
||||
Text = "S_EVENT_MISSION_WINNER"
|
||||
},
|
||||
[EVENTS.RunwayTakeoff] = {
|
||||
[EVENTS.PostponedTakeoff] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventRunwayTakeoff",
|
||||
Text = "S_EVENT_RUNWAY_TAKEOFF"
|
||||
Event = "OnEventPostponedTakeoff",
|
||||
Text = "S_EVENT_POSTPONED_TAKEOFF"
|
||||
},
|
||||
[EVENTS.RunwayTouch] = {
|
||||
[EVENTS.PostponedLand] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventRunwayTouch",
|
||||
Text = "S_EVENT_RUNWAY_TOUCH"
|
||||
},
|
||||
[EVENTS.MacLMSRestart] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventMacLMSRestart",
|
||||
Text = "S_EVENT_MAC_LMS_RESTART"
|
||||
},
|
||||
[EVENTS.SimulationFreeze] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventSimulationFreeze",
|
||||
Text = "S_EVENT_SIMULATION_FREEZE"
|
||||
},
|
||||
[EVENTS.SimulationUnfreeze] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventSimulationUnfreeze",
|
||||
Text = "S_EVENT_SIMULATION_UNFREEZE"
|
||||
},
|
||||
[EVENTS.HumanAircraftRepairStart] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventHumanAircraftRepairStart",
|
||||
Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_START"
|
||||
},
|
||||
[EVENTS.HumanAircraftRepairFinish] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventHumanAircraftRepairFinish",
|
||||
Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH"
|
||||
},
|
||||
-- dynamic cargo
|
||||
[EVENTS.NewDynamicCargo] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventNewDynamicCargo",
|
||||
Text = "S_EVENT_NEW_DYNAMIC_CARGO"
|
||||
},
|
||||
[EVENTS.DynamicCargoLoaded] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoLoaded",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_LOADED"
|
||||
},
|
||||
[EVENTS.DynamicCargoUnloaded] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoUnloaded",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_UNLOADED"
|
||||
},
|
||||
[EVENTS.DynamicCargoRemoved] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoRemoved",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_REMOVED"
|
||||
Event = "OnEventPostponedLand",
|
||||
Text = "S_EVENT_POSTPONED_LAND"
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
--- The Events structure
|
||||
-- @type EVENT.Events
|
||||
-- @field #number IniUnit
|
||||
@@ -1184,63 +1108,7 @@ do -- Event Creation
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventNewDynamicCargo(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.NewDynamicCargo,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_LOADED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoLoaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoLoaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_UNLOADED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoUnloaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoUnloaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_REMOVED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoRemoved(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoRemoved,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Main event function.
|
||||
@@ -1329,7 +1197,6 @@ function EVENT:onEvent( Event )
|
||||
end
|
||||
|
||||
Event.IniDCSGroupName = Event.IniUnit and Event.IniUnit.GroupName or ""
|
||||
Event.IniGroupName=Event.IniDCSGroupName --At least set the group name because group might not exist any more
|
||||
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
|
||||
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
|
||||
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
|
||||
@@ -1356,14 +1223,7 @@ function EVENT:onEvent( Event )
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
if string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then
|
||||
Event.IniDynamicCargo = DYNAMICCARGO:FindByName(Event.IniUnitName)
|
||||
Event.IniDynamicCargoName = Event.IniUnitName
|
||||
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
|
||||
else
|
||||
--Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false )
|
||||
end
|
||||
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
@@ -1373,12 +1233,11 @@ function EVENT:onEvent( Event )
|
||||
-- Scenery
|
||||
---
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = ( Event.IniDCSUnit and Event.IniDCSUnit.getName ) and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000)
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
Event.IniCategory = (Event.IniDCSUnit and Event.IniDCSUnit.getDesc ) and Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = (Event.initiator and Event.initiator.isExist
|
||||
and Event.initiator:isExist() and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName) and Event.IniDCSUnit:getTypeName() or "SCENERY"
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY"
|
||||
|
||||
elseif Event.IniObjectCategory == Object.Category.BASE then
|
||||
---
|
||||
@@ -1442,7 +1301,7 @@ function EVENT:onEvent( Event )
|
||||
-- STATIC
|
||||
---
|
||||
Event.TgtDCSUnit = Event.target
|
||||
if Event.target.isExist and Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object, check that isExist exists (Kiowa Hellfire issue, Special K)
|
||||
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
-- Workaround for borked target info on cruise missiles
|
||||
if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then
|
||||
@@ -1476,26 +1335,24 @@ function EVENT:onEvent( Event )
|
||||
-- SCENERY
|
||||
---
|
||||
Event.TgtDCSUnit = Event.target
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName() or nil
|
||||
if Event.TgtDCSUnitName~=nil then
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
end
|
||||
|
||||
-- Weapon.
|
||||
if Event.weapon and type(Event.weapon) == "table" and Event.weapon.isExist and Event.weapon:isExist() then
|
||||
if Event.weapon then
|
||||
Event.Weapon = Event.weapon
|
||||
Event.WeaponName = Event.weapon:isExist() and Event.weapon:getTypeName() or "Unknown Weapon"
|
||||
Event.WeaponName = Event.Weapon:getTypeName()
|
||||
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
|
||||
Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName()
|
||||
--Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName()
|
||||
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon.getCoalition and Event.Weapon:getCoalition()
|
||||
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon.getDesc and Event.Weapon:getDesc().category
|
||||
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon.getTypeName and Event.Weapon:getTypeName()
|
||||
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition()
|
||||
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category
|
||||
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()
|
||||
--Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
|
||||
end
|
||||
|
||||
@@ -1521,7 +1378,6 @@ function EVENT:onEvent( Event )
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
Event.MarkText=Event.text
|
||||
Event.MarkCoalition=Event.coalition
|
||||
Event.IniCoalition=Event.coalition
|
||||
Event.MarkGroupID = Event.groupID
|
||||
end
|
||||
|
||||
@@ -1530,15 +1386,6 @@ function EVENT:onEvent( Event )
|
||||
Event.Cargo = Event.cargo
|
||||
Event.CargoName = Event.cargo.Name
|
||||
end
|
||||
|
||||
-- Dynamic cargo Object
|
||||
if Event.dynamiccargo then
|
||||
Event.IniDynamicCargo = Event.dynamiccargo
|
||||
Event.IniDynamicCargoName = Event.IniDynamicCargo.StaticName
|
||||
if Event.IniDynamicCargo.Owner or Event.IniUnitName then
|
||||
Event.IniPlayerName = Event.IniDynamicCargo.Owner or string.match(Event.IniUnitName or "None|00:00|PKG00","^(.+)|%d%d:%d%d|PKG%d+")
|
||||
end
|
||||
end
|
||||
|
||||
-- Zone object.
|
||||
if Event.zone then
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
-- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes.
|
||||
-- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used,
|
||||
-- and tailored** by mission designers through **the implementation of Transition Handlers**.
|
||||
-- Each of these FSM implementation classes start either with:
|
||||
--
|
||||
-- * an acronym **AI\_**, which indicates a FSM implementation directing **AI controlled** @{Wrapper.Group#GROUP} and/or @{Wrapper.Unit#UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class.
|
||||
-- * an acronym **TASK\_**, which indicates a FSM implementation executing a @{Tasking.Task#TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class.
|
||||
-- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{Tasking.Task#TASK}, seated in a @{Wrapper.Client#CLIENT} (slot) or a @{Wrapper.Unit#UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class.
|
||||
--
|
||||
-- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections.
|
||||
--
|
||||
-- ##__Disclaimer:__
|
||||
@@ -55,6 +61,7 @@
|
||||
--
|
||||
-- The following derived classes are available in the MOOSE framework, that implement a specialized form of a FSM:
|
||||
--
|
||||
-- * @{#FSM_TASK}: Models Finite State Machines for @{Tasking.Task}s.
|
||||
-- * @{#FSM_PROCESS}: Models Finite State Machines for @{Tasking.Task} actions, which control @{Wrapper.Client}s.
|
||||
-- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Wrapper.Client}s.
|
||||
-- * @{#FSM_SET}: Models Finite State Machines for @{Core.Set}s. Note that these FSMs control multiple objects!!! So State concerns here
|
||||
@@ -71,9 +78,8 @@
|
||||
-- @image Core_Finite_State_Machine.JPG
|
||||
|
||||
do -- FSM
|
||||
|
||||
---
|
||||
-- @type FSM
|
||||
|
||||
--- @type FSM
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler.
|
||||
-- @field #table options Options.
|
||||
@@ -111,6 +117,11 @@ do -- FSM
|
||||
-- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes.
|
||||
-- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used,
|
||||
-- and tailored** by mission designers through **the implementation of Transition Handlers**.
|
||||
-- Each of these FSM implementation classes start either with:
|
||||
--
|
||||
-- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{Wrapper.Group#GROUP} and/or @{Wrapper.Unit#UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class.
|
||||
-- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{Tasking.Task#TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class.
|
||||
-- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{Tasking.Task#TASK}, seated in a @{Wrapper.Client#CLIENT} (slot) or a @{Wrapper.Unit#UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -937,9 +948,8 @@ do -- FSM
|
||||
end
|
||||
|
||||
do -- FSM_CONTROLLABLE
|
||||
|
||||
---
|
||||
-- @type FSM_CONTROLLABLE
|
||||
|
||||
--- @type FSM_CONTROLLABLE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Controllable
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
@@ -1071,9 +1081,8 @@ do -- FSM_CONTROLLABLE
|
||||
end
|
||||
|
||||
do -- FSM_PROCESS
|
||||
|
||||
---
|
||||
-- @type FSM_PROCESS
|
||||
|
||||
--- @type FSM_PROCESS
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
do -- Goal
|
||||
|
||||
-- @type 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.
|
||||
@@ -71,10 +71,10 @@ do -- Goal
|
||||
ClassName = "GOAL",
|
||||
}
|
||||
|
||||
-- @field #table GOAL.Players
|
||||
--- @field #table GOAL.Players
|
||||
GOAL.Players = {}
|
||||
|
||||
-- @field #number GOAL.TotalContributions
|
||||
--- @field #number GOAL.TotalContributions
|
||||
GOAL.TotalContributions = 0
|
||||
|
||||
--- GOAL Constructor.
|
||||
@@ -145,7 +145,7 @@ do -- Goal
|
||||
self.TotalContributions = self.TotalContributions + 1
|
||||
end
|
||||
|
||||
-- @param #GOAL self
|
||||
--- @param #GOAL self
|
||||
-- @param #number Player contribution.
|
||||
function GOAL:GetPlayerContribution( PlayerName )
|
||||
return self.Players[PlayerName] or 0
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
-- Last Update: Mar 2023
|
||||
-- Last Update: Feb 2023
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.1.4",
|
||||
version = "0.1.1",
|
||||
debug = false,
|
||||
Casesensitive = true,
|
||||
}
|
||||
@@ -108,30 +108,23 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
--- 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 #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.
|
||||
-- @param #number MarkerID Id of this marker.
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator.
|
||||
-- @param #string PlayerName Name of the player creating/changing the mark. nil if it cannot be obtained.
|
||||
-- @param Core.Event#EVENTDATA EventData the event data table.
|
||||
|
||||
--- 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 #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.
|
||||
-- @param #number MarkerID Id of this marker.
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator.
|
||||
-- @param #string PlayerName Name of the player creating/changing the mark. nil if it cannot be obtained.
|
||||
-- @param Core.Event#EVENTDATA EventData the event data table
|
||||
-- @param #number idx DCS Marker ID
|
||||
|
||||
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
@@ -140,7 +133,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
|
||||
--- "Stop" trigger. Used to stop the function an unhandle events
|
||||
--- "Stop" trigger. Used to stop the function an unhandle events
|
||||
-- @function [parent=#MARKEROPS_BASE] Stop
|
||||
|
||||
end
|
||||
@@ -154,43 +147,37 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
self:E("Skipping onEvent. Event or Event.idx unknown.")
|
||||
return true
|
||||
end
|
||||
|
||||
local coalition = Event.MarkCoalition
|
||||
--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=Event.IniGroupName, vec3=Event.pos})
|
||||
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 coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z})
|
||||
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
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
|
||||
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=Event.IniGroupName, vec3=Event.pos})
|
||||
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 coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z})
|
||||
if self.debug then
|
||||
local coordtext = coord:ToStringLLDDM()
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark changed at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord,Event.idx)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=Event.IniGroupName, vec3=Event.pos})
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Hande event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
@@ -243,10 +230,8 @@ end
|
||||
-- @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 #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber)
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
@@ -257,10 +242,8 @@ end
|
||||
-- @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 #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber)
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -75,37 +75,35 @@ MESSAGE.Type = {
|
||||
|
||||
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{#MESSAGE.ToClient} or @{#MESSAGE.ToCoalition} or @{#MESSAGE.ToAll} to send these Messages to the respective recipients.
|
||||
-- @param self
|
||||
-- @param #string Text is the text of the Message.
|
||||
-- @param #number Duration Duration in seconds how long the message text is shown.
|
||||
-- @param #string Category (Optional) String expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ".
|
||||
-- @param #string MessageText is the text of the Message.
|
||||
-- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel.
|
||||
-- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ".
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
|
||||
-- @return #MESSAGE self
|
||||
-- @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".
|
||||
-- -- 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( Text, Duration, Category, ClearScreen )
|
||||
|
||||
function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
self:F( { Text, Duration, Category } )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
|
||||
self.MessageType = nil
|
||||
|
||||
-- When no MessageCategory is given, we don't show it as a title...
|
||||
if Category and Category ~= "" then
|
||||
if Category:sub( -1 ) ~= "\n" then
|
||||
self.MessageCategory = Category .. ": "
|
||||
if MessageCategory and MessageCategory ~= "" then
|
||||
if MessageCategory:sub( -1 ) ~= "\n" then
|
||||
self.MessageCategory = MessageCategory .. ": "
|
||||
else
|
||||
self.MessageCategory = Category:sub( 1, -2 ) .. ":\n"
|
||||
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
|
||||
end
|
||||
else
|
||||
self.MessageCategory = ""
|
||||
@@ -116,9 +114,9 @@ function MESSAGE:New( Text, Duration, Category, ClearScreen )
|
||||
self.ClearScreen = ClearScreen
|
||||
end
|
||||
|
||||
self.MessageDuration = Duration or 5
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = Text:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
@@ -179,22 +177,40 @@ end
|
||||
--
|
||||
-- -- 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.
|
||||
-- Client = CLIENT:FindByName("NameOfClientUnit")
|
||||
-- ClientGroup = Group.getByName( "ClientGroup" )
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ):ToClient( Client )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score" ):ToClient( Client )
|
||||
-- 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", 25, "Score"):ToClient( Client )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score"):ToClient( Client )
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25 ):ToClient( ClientGroup )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25 ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score")
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
|
||||
-- MessageClient1:ToClient( Client )
|
||||
-- MessageClient2:ToClient( Client )
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25 )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25 )
|
||||
-- MessageClient1:ToClient( ClientGroup )
|
||||
-- MessageClient2:ToClient( ClientGroup )
|
||||
--
|
||||
function MESSAGE:ToClient( Client, Settings )
|
||||
self:F( Client )
|
||||
self:ToUnit(Client,Settings)
|
||||
|
||||
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
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
@@ -206,7 +222,7 @@ end
|
||||
function MESSAGE:ToGroup( Group, Settings )
|
||||
self:F( Group.GroupName )
|
||||
|
||||
if Group and Group:IsAlive() then
|
||||
if Group then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
@@ -231,7 +247,7 @@ end
|
||||
function MESSAGE:ToUnit( Unit, Settings )
|
||||
self:F( Unit.IdentifiableName )
|
||||
|
||||
if Unit and Unit:IsAlive() then
|
||||
if Unit then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
@@ -241,7 +257,6 @@ function MESSAGE:ToUnit( Unit, Settings )
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
local ID = Unit:GetID()
|
||||
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
@@ -290,11 +305,11 @@ end
|
||||
-- @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", 25, "Penalty"):ToBlue()
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToBlue()
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", 25, "Penalty")
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 )
|
||||
-- MessageBLUE:ToBlue()
|
||||
--
|
||||
function MESSAGE:ToBlue()
|
||||
@@ -311,11 +326,11 @@ end
|
||||
-- @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", 25, "Penalty"):ToRed()
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToRed()
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty")
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 )
|
||||
-- MessageRED:ToRed()
|
||||
--
|
||||
function MESSAGE:ToRed()
|
||||
@@ -334,11 +349,11 @@ end
|
||||
-- @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", 25, "Penalty"):ToCoalition( coalition.side.RED )
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToCoalition( coalition.side.RED )
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):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", 25, "Penalty")
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 )
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
--
|
||||
function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
@@ -380,36 +395,29 @@ end
|
||||
--- Sends a MESSAGE to all players.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
|
||||
-- @param #number Delay (Optional) Delay in seconds before the message is send. Default instantly (`nil`).
|
||||
-- @return #MESSAGE self
|
||||
-- @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!", 25, "End of Mission"):ToAll()
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25 ):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission"):ToAll()
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25 ):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission")
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25 )
|
||||
-- MessageAll:ToAll()
|
||||
--
|
||||
function MESSAGE:ToAll( Settings, Delay )
|
||||
function MESSAGE:ToAll( Settings )
|
||||
self:F()
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, MESSAGE.ToAll, self, Settings, 0)
|
||||
else
|
||||
if self.MessageType then
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings 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.outText( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
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 )
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -452,7 +460,7 @@ end
|
||||
_MESSAGESRS = {}
|
||||
|
||||
--- Set up MESSAGE generally to allow Text-To-Speech via SRS and TTS functions. `SetMSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible.
|
||||
-- @param #string PathToSRS (optional) Path to SRS TTS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio" or your configuration file setting.
|
||||
-- @param #string PathToSRS (optional) Path to SRS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" or your configuration file setting.
|
||||
-- @param #number Port Port (optional) number of SRS, defaults to 5002 or your configuration file setting.
|
||||
-- @param #string PathToCredentials (optional) Path to credentials file for Google.
|
||||
-- @param #number Frequency Frequency in MHz. Can also be given as a #table of frequencies.
|
||||
@@ -464,17 +472,16 @@ _MESSAGESRS = {}
|
||||
-- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest).
|
||||
-- @param #string Label (optional) Label, defaults to "MESSAGE" or the Message Category set.
|
||||
-- @param Core.Point#COORDINATE Coordinate (optional) Coordinate this messages originates from.
|
||||
-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
|
||||
--
|
||||
function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend)
|
||||
function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate)
|
||||
|
||||
_MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
|
||||
_MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
|
||||
_MESSAGESRS.frequency = Frequency or MSRS.frequencies or 243
|
||||
_MESSAGESRS.modulation = Modulation or MSRS.modulations or radio.modulation.AM
|
||||
@@ -490,10 +497,6 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G
|
||||
_MESSAGESRS.MSRS:SetCoordinate(Coordinate)
|
||||
end
|
||||
|
||||
if Backend then
|
||||
_MESSAGESRS.MSRS:SetBackend(Backend)
|
||||
end
|
||||
|
||||
_MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB"
|
||||
_MESSAGESRS.MSRS:SetCulture(Culture)
|
||||
|
||||
@@ -506,10 +509,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G
|
||||
end
|
||||
|
||||
_MESSAGESRS.label = Label or MSRS.Label or "MESSAGE"
|
||||
_MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label)
|
||||
_MESSAGESRS.MSRS:SetLabel(Label or "MESSAGE")
|
||||
|
||||
_MESSAGESRS.port = Port or MSRS.port or 5002
|
||||
_MESSAGESRS.MSRS:SetPort(_MESSAGESRS.port)
|
||||
_MESSAGESRS.MSRS:SetPort(Port or 5002)
|
||||
|
||||
_MESSAGESRS.volume = Volume or MSRS.volume or 1
|
||||
_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume)
|
||||
@@ -535,7 +538,7 @@ end
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
|
||||
--
|
||||
@@ -567,7 +570,7 @@ end
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSBlue()
|
||||
--
|
||||
@@ -589,7 +592,7 @@ end
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED)
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSRed()
|
||||
--
|
||||
@@ -611,7 +614,7 @@ end
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL)
|
||||
-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL)
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSAll()
|
||||
--
|
||||
|
||||
@@ -73,7 +73,7 @@ PATHLINE = {
|
||||
|
||||
--- PATHLINE class version.
|
||||
-- @field #string version
|
||||
PATHLINE.version="0.1.1"
|
||||
PATHLINE.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -237,14 +237,13 @@ end
|
||||
--- Get COORDINATES of pathline. Note that COORDINATE objects are created when calling this function. That does involve deep copy calls and can have an impact on performance if done too often.
|
||||
-- @param #PATHLINE self
|
||||
-- @return <Core.Point#COORDINATE> List of COORDINATES points.
|
||||
function PATHLINE:GetCoordinates()
|
||||
function PATHLINE:GetCoordinats()
|
||||
|
||||
local vecs={}
|
||||
|
||||
for _,_point in pairs(self.points) do
|
||||
local point=_point --#PATHLINE.Point
|
||||
local coord=COORDINATE:NewFromVec3(point.vec3)
|
||||
table.insert(vecs,coord)
|
||||
end
|
||||
|
||||
return vecs
|
||||
@@ -263,7 +262,7 @@ function PATHLINE:GetPointFromIndex(n)
|
||||
local point=nil --#PATHLINE.Point
|
||||
|
||||
if n>=1 and n<=N then
|
||||
point=self.points[n]
|
||||
point=self.point[n]
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: No point in pathline for N=%s", tostring(n)))
|
||||
end
|
||||
@@ -368,4 +367,4 @@ end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,8 +15,7 @@
|
||||
-- @module Core.Report
|
||||
-- @image Core_Report.JPG
|
||||
|
||||
---
|
||||
-- @type REPORT
|
||||
--- @type REPORT
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Provides a handy means to create messages and reports.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
---- **Core** - SCHEDULEDISPATCHER dispatches the different schedules.
|
||||
--- **Core** - SCHEDULEDISPATCHER dispatches the different schedules.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -175,7 +175,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
local Name = Info.name or "?"
|
||||
|
||||
local ErrorHandler = function( errmsg )
|
||||
env.info( "Error in timer function: " .. errmsg or "" )
|
||||
env.info( "Error in timer function: " .. errmsg )
|
||||
if BASE.Debug ~= nil then
|
||||
env.info( BASE.Debug.traceback() )
|
||||
end
|
||||
@@ -326,7 +326,7 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
|
||||
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 and Schedule.ScheduleID then
|
||||
if Schedule.ScheduleID then
|
||||
|
||||
self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,9 +29,7 @@
|
||||
-- @module Core.Settings
|
||||
-- @image Core_Settings.JPG
|
||||
|
||||
|
||||
---
|
||||
-- @type SETTINGS
|
||||
--- @type SETTINGS
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
@@ -220,8 +218,7 @@ SETTINGS = {
|
||||
|
||||
SETTINGS.__Enum = {}
|
||||
|
||||
---
|
||||
-- @type SETTINGS.__Enum.Era
|
||||
--- @type SETTINGS.__Enum.Era
|
||||
-- @field #number WWII
|
||||
-- @field #number Korea
|
||||
-- @field #number Cold
|
||||
@@ -740,8 +737,8 @@ do -- SETTINGS
|
||||
if _SETTINGS.ShowPlayerMenu == true then
|
||||
|
||||
local PlayerGroup = PlayerUnit:GetGroup()
|
||||
local PlayerName = PlayerUnit:GetPlayerName() or "None"
|
||||
--local PlayerNames = PlayerGroup:GetPlayerNames()
|
||||
local PlayerName = PlayerUnit:GetPlayerName()
|
||||
local PlayerNames = PlayerGroup:GetPlayerNames()
|
||||
|
||||
local PlayerMenu = MENU_GROUP:New( PlayerGroup, 'Settings "' .. PlayerName .. '"' )
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,36 @@
|
||||
--- **Core** - Spawn statics.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Spawn new statics from a static already defined in the mission editor.
|
||||
-- * Spawn new statics from a given template.
|
||||
-- * Spawn new statics from a given type.
|
||||
-- * Spawn with a custom heading and location.
|
||||
-- * Spawn within a zone.
|
||||
-- * Spawn statics linked to units, .e.g on aircraft carriers.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Core/SpawnStatic)
|
||||
--
|
||||
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/SpawnStatic)
|
||||
--
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
--
|
||||
-- ## No videos yet!
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.SpawnStatic
|
||||
-- @image Core_Spawnstatic.JPG
|
||||
|
||||
@@ -58,37 +58,37 @@
|
||||
|
||||
|
||||
--- Allows to spawn dynamically new @{Wrapper.Static}s into your mission.
|
||||
--
|
||||
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc),
|
||||
--
|
||||
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc),
|
||||
-- and "copy" these properties to create a new static object and place it at the desired coordinate.
|
||||
--
|
||||
-- New spawned @{Wrapper.Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
|
||||
--
|
||||
-- New spawned @{Wrapper.Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
|
||||
-- By default, spawned @{Wrapper.Static}s will follow a naming convention at run-time:
|
||||
--
|
||||
--
|
||||
-- * Spawned @{Wrapper.Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**.
|
||||
--
|
||||
--
|
||||
-- # SPAWNSTATIC Constructors
|
||||
--
|
||||
--
|
||||
-- Firstly, we need to create a SPAWNSTATIC object that will be used to spawn new statics into the mission. There are three ways to do this.
|
||||
--
|
||||
--
|
||||
-- ## Use another Static
|
||||
--
|
||||
--
|
||||
-- A new SPAWNSTATIC object can be created using another static by the @{#SPAWNSTATIC.NewFromStatic}() function. All parameters such as position, heading, country will be initialized
|
||||
-- from the static.
|
||||
--
|
||||
--
|
||||
-- ## From a Template
|
||||
--
|
||||
--
|
||||
-- A SPAWNSTATIC object can also be created from a template table using the @{#SPAWNSTATIC.NewFromTemplate}(SpawnTemplate, CountryID) function. All parameters are taken from the template.
|
||||
--
|
||||
--
|
||||
-- ## From a Type
|
||||
--
|
||||
--
|
||||
-- A very basic method is to create a SPAWNSTATIC object by just giving the type of the static. All parameters must be initialized from the InitXYZ functions described below. Otherwise default values
|
||||
-- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map.
|
||||
--
|
||||
--
|
||||
-- # Setting Parameters
|
||||
--
|
||||
--
|
||||
-- Parameters such as the spawn position, heading, country etc. can be set via :Init*XYZ* functions. Note that these functions must be given before the actual spawn command!
|
||||
--
|
||||
--
|
||||
-- * @{#SPAWNSTATIC.InitCoordinate}(Coordinate) Sets the coordinate where the static is spawned. Statics are always spawnd on the ground.
|
||||
-- * @{#SPAWNSTATIC.InitHeading}(Heading) sets the orientation of the static.
|
||||
-- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this.
|
||||
@@ -99,17 +99,17 @@
|
||||
-- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier.
|
||||
--
|
||||
-- # Spawning the Statics
|
||||
--
|
||||
--
|
||||
-- Once the SPAWNSTATIC object is created and parameters are initialized, the spawn command can be given. There are different methods where some can be used to directly set parameters
|
||||
-- such as position and heading.
|
||||
--
|
||||
--
|
||||
-- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a COORDINATE coordinate. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Core.Zone}. Optionally, heading and name can be given. The name **must be unique**!
|
||||
--
|
||||
--
|
||||
-- @field #SPAWNSTATIC SPAWNSTATIC
|
||||
--
|
||||
--
|
||||
SPAWNSTATIC = {
|
||||
ClassName = "SPAWNSTATIC",
|
||||
SpawnIndex = 0,
|
||||
@@ -139,9 +139,9 @@ SPAWNSTATIC = {
|
||||
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
|
||||
|
||||
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
|
||||
|
||||
|
||||
if TemplateStatic then
|
||||
self.SpawnTemplatePrefix = SpawnTemplateName
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
|
||||
@@ -149,7 +149,6 @@ function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
|
||||
self.CategoryID = CategoryID
|
||||
self.CoalitionID = CoalitionID
|
||||
self.SpawnIndex = 0
|
||||
self.StaticCopyFrom = SpawnTemplateName
|
||||
else
|
||||
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
|
||||
end
|
||||
@@ -167,11 +166,11 @@ end
|
||||
function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID)
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
|
||||
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate)
|
||||
self.SpawnTemplatePrefix = SpawnTemplate.name
|
||||
self.CountryID = CountryID or country.id.USA
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -190,69 +189,13 @@ function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID)
|
||||
self.InitStaticCategory=StaticCategory
|
||||
self.CountryID=CountryID or country.id.USA
|
||||
self.SpawnTemplatePrefix=self.InitStaticType
|
||||
self.TemplateStaticUnit = {}
|
||||
|
||||
|
||||
self.InitStaticCoordinate=COORDINATE:New(0, 0, 0)
|
||||
self.InitStaticHeading=0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal/Cargo) Init the resource table for STATIC object that should be spawned containing storage objects.
|
||||
-- NOTE that you have to init many other parameters as the resources.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number CombinedWeight The weight this cargo object should have (some have fixed weights!), defaults to 1kg.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:_InitResourceTable(CombinedWeight)
|
||||
if not self.TemplateStaticUnit.resourcePayload then
|
||||
self.TemplateStaticUnit.resourcePayload = {
|
||||
["weapons"] = {},
|
||||
["aircrafts"] = {},
|
||||
["gasoline"] = 0,
|
||||
["diesel"] = 0,
|
||||
["methanol_mixture"] = 0,
|
||||
["jet_fuel"] = 0,
|
||||
}
|
||||
end
|
||||
self:InitCargo(true)
|
||||
self:InitCargoMass(CombinedWeight or 1)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User/Cargo) Add to resource table for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string Type Type of cargo. Known types are: STORAGE.Type.WEAPONS, STORAGE.Type.LIQUIDS, STORAGE.Type.AIRCRAFT. Liquids are fuel.
|
||||
-- @param #string Name Name of the cargo type. Liquids can be STORAGE.LiquidName.JETFUEL, STORAGE.LiquidName.GASOLINE, STORAGE.LiquidName.MW50 and STORAGE.LiquidName.DIESEL. The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. Aircraft go by their typename.
|
||||
-- @param #number Amount of tons (liquids) or number (everything else) to add.
|
||||
-- @param #number CombinedWeight Combined weight to be set to this static cargo object. NOTE - some static cargo objects have fixed weights!
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:AddCargoResource(Type,Name,Amount,CombinedWeight)
|
||||
if not self.TemplateStaticUnit.resourcePayload then
|
||||
self:_InitResourceTable(CombinedWeight)
|
||||
end
|
||||
if Type == STORAGE.Type.LIQUIDS and type(Name) == "string" then
|
||||
self.TemplateStaticUnit.resourcePayload[Name] = Amount
|
||||
else
|
||||
self.TemplateStaticUnit.resourcePayload[Type] = {
|
||||
[Name] = {
|
||||
["amount"] = Amount,
|
||||
}
|
||||
}
|
||||
end
|
||||
UTILS.PrintTableToLog(self.TemplateStaticUnit)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User/Cargo) Resets resource table to zero for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters.
|
||||
-- Handy if you spawn from cargo statics which have resources already set.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:ResetCargoResources()
|
||||
self.TemplateStaticUnit.resourcePayload = nil
|
||||
self:_InitResourceTable()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize heading of the spawned static.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#COORDINATE Coordinate Position where the static is spawned.
|
||||
@@ -303,16 +246,12 @@ end
|
||||
-- @param #number CallsignID Callsign ID. Default 1 (="London").
|
||||
-- @param #number Frequency Frequency in MHz. Default 127.5 MHz.
|
||||
-- @param #number Modulation Modulation 0=AM, 1=FM.
|
||||
-- @param #boolean DynamicSpawns If true, allow Dynamic Spawns
|
||||
-- @param #boolean DynamicHotStarts If true, and DynamicSpawns is true, then allow Dynamic Spawns with hot starts.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation, DynamicSpawns,DynamicHotStarts)
|
||||
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation)
|
||||
self.InitFarp=true
|
||||
self.InitFarpCallsignID=CallsignID or 1
|
||||
self.InitFarpFreq=Frequency or 127.5
|
||||
self.InitFarpModu=Modulation or 0
|
||||
self.InitFarpDynamicSpawns = DynamicSpawns
|
||||
self.InitFarpDynamicHotStarts = (DynamicSpawns == true and DynamicHotStarts == true) and true or nil
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -352,7 +291,7 @@ function SPAWNSTATIC:InitCountry(CountryID)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc.
|
||||
--- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name.
|
||||
-- @return #SPAWNSTATIC self
|
||||
@@ -378,25 +317,6 @@ function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Allows to place a CallFunction hook when a new static spawns.
|
||||
-- The provided method will be called when a new group is spawned, including its given parameters.
|
||||
-- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #function SpawnCallBackFunction The function to be called when a group spawns.
|
||||
-- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:OnSpawnStatic( SpawnCallBackFunction, ... )
|
||||
self:F( "OnSpawnStatic" )
|
||||
|
||||
self.SpawnFunctionHook = SpawnCallBackFunction
|
||||
self.SpawnFunctionArguments = {}
|
||||
if arg then
|
||||
self.SpawnFunctionArguments = arg
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Spawn a new STATIC object.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template.
|
||||
@@ -407,18 +327,18 @@ function SPAWNSTATIC:Spawn(Heading, NewName)
|
||||
if Heading then
|
||||
self.InitStaticHeading=Heading
|
||||
end
|
||||
|
||||
|
||||
if NewName then
|
||||
self.InitStaticName=NewName
|
||||
end
|
||||
|
||||
return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID)
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Creates a new @{Wrapper.Static} from a COORDINATE.
|
||||
--- Creates a new @{Wrapper.Static} from a POINT_VEC2.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#COORDINATE PointVec2 The 2D coordinate where to spawn the static.
|
||||
-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static.
|
||||
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
|
||||
-- @param #string NewName (Optional) The name of the new static.
|
||||
-- @return Wrapper.Static#STATIC The static spawned.
|
||||
@@ -427,7 +347,7 @@ function SPAWNSTATIC:SpawnFromPointVec2(PointVec2, Heading, NewName)
|
||||
local vec2={x=PointVec2:GetX(), y=PointVec2:GetY()}
|
||||
|
||||
local Coordinate=COORDINATE:NewFromVec2(vec2)
|
||||
|
||||
|
||||
return self:SpawnFromCoordinate(Coordinate, Heading, NewName)
|
||||
end
|
||||
|
||||
@@ -442,11 +362,11 @@ function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName)
|
||||
|
||||
-- Set up coordinate.
|
||||
self.InitStaticCoordinate=Coordinate
|
||||
|
||||
|
||||
if Heading then
|
||||
self.InitStaticHeading=Heading
|
||||
end
|
||||
|
||||
|
||||
if NewName then
|
||||
self.InitStaticName=NewName
|
||||
end
|
||||
@@ -464,9 +384,8 @@ end
|
||||
function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName)
|
||||
|
||||
-- Spawn the new static at the center of the zone.
|
||||
--local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
|
||||
local Static = self:SpawnFromCoordinate(Zone:GetCoordinate(), Heading, NewName)
|
||||
|
||||
local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
|
||||
|
||||
return Static
|
||||
end
|
||||
|
||||
@@ -480,45 +399,45 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template=Template or {}
|
||||
|
||||
local CountryID=CountryID or self.CountryID
|
||||
|
||||
|
||||
if self.InitStaticType then
|
||||
Template.type=self.InitStaticType
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticCategory then
|
||||
Template.category=self.InitStaticCategory
|
||||
end
|
||||
|
||||
if self.InitStaticCoordinate then
|
||||
Template.x = self.InitStaticCoordinate.x
|
||||
|
||||
if self.InitStaticCoordinate then
|
||||
Template.x = self.InitStaticCoordinate.x
|
||||
Template.y = self.InitStaticCoordinate.z
|
||||
Template.alt = self.InitStaticCoordinate.y
|
||||
Template.alt = self.InitStaticCoordinate.y
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticHeading then
|
||||
Template.heading = math.rad(self.InitStaticHeading)
|
||||
Template.heading = math.rad(self.InitStaticHeading)
|
||||
end
|
||||
|
||||
if self.InitStaticShape then
|
||||
Template.shape_name=self.InitStaticShape
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticLivery then
|
||||
Template.livery_id=self.InitStaticLivery
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticDead~=nil then
|
||||
Template.dead=self.InitStaticDead
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticCargo~=nil then
|
||||
Template.canCargo=self.InitStaticCargo
|
||||
end
|
||||
|
||||
|
||||
if self.InitStaticCargoMass~=nil then
|
||||
Template.mass=self.InitStaticCargoMass
|
||||
end
|
||||
|
||||
|
||||
if self.InitLinkUnit then
|
||||
Template.linkUnit=self.InitLinkUnit:GetID()
|
||||
Template.linkOffset=true
|
||||
@@ -527,51 +446,49 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template.offsets.x=self.InitOffsetX
|
||||
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0
|
||||
end
|
||||
|
||||
|
||||
if self.InitFarp then
|
||||
Template.heliport_callsign_id = self.InitFarpCallsignID
|
||||
Template.heliport_frequency = self.InitFarpFreq
|
||||
Template.heliport_modulation = self.InitFarpModu
|
||||
Template.unitId=nil
|
||||
end
|
||||
|
||||
|
||||
-- Increase spawn index counter.
|
||||
self.SpawnIndex = self.SpawnIndex + 1
|
||||
|
||||
|
||||
-- Name of the spawned static.
|
||||
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex)
|
||||
|
||||
-- Add and register the new static.
|
||||
local mystatic=_DATABASE:AddStatic(Template.name)
|
||||
|
||||
-- Debug output.
|
||||
self:T(Template)
|
||||
|
||||
-- Add static to the game.
|
||||
local Static=nil --DCS#StaticObject
|
||||
|
||||
|
||||
if self.InitFarp then
|
||||
|
||||
local TemplateGroup={}
|
||||
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
|
||||
|
||||
TemplateGroup.visible=true
|
||||
TemplateGroup.hidden=false
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
|
||||
if self.InitFarpDynamicSpawns == true then
|
||||
TemplateGroup.units[1].dynamicSpawn = true
|
||||
if self.InitFarpDynamicHotStarts == true then
|
||||
TemplateGroup.units[1].allowHotStart = true
|
||||
end
|
||||
end
|
||||
|
||||
self:T("Spawning FARP")
|
||||
self:T("Spawning FARP")
|
||||
self:T({Template=Template})
|
||||
self:T({TemplateGroup=TemplateGroup})
|
||||
|
||||
|
||||
-- ED's dirty way to spawn FARPS.
|
||||
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
|
||||
--Static=coalition.addStaticObject(CountryID, Template)
|
||||
|
||||
-- Currently DCS 2.8 does not trigger birth events if FARPS are spawned!
|
||||
|
||||
-- Currently DCS 2.8 does not trigger birth events if FAPRS are spawned!
|
||||
-- We create such an event. The airbase is registered in Core.Event
|
||||
local Event = {
|
||||
id = EVENTS.Birth,
|
||||
@@ -582,45 +499,10 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
world.onEvent(Event)
|
||||
|
||||
else
|
||||
self:T("Spawning Static")
|
||||
self:T2({Template=Template})
|
||||
self:T("Spawning Static")
|
||||
self:T2({Template=Template})
|
||||
Static=coalition.addStaticObject(CountryID, Template)
|
||||
end
|
||||
|
||||
if Static then
|
||||
self:T(string.format("Succesfully spawned static object \"%s\" ID=%d", Static:getName(), Static:getID()))
|
||||
--[[
|
||||
local static=StaticObject.getByName(Static:getName())
|
||||
if static then
|
||||
env.info(string.format("FF got static from StaticObject.getByName"))
|
||||
else
|
||||
env.error(string.format("FF error did NOT get static from StaticObject.getByName"))
|
||||
end ]]
|
||||
else
|
||||
self:E(string.format("ERROR: DCS static object \"%s\" is nil!", tostring(Template.name)))
|
||||
end
|
||||
end
|
||||
|
||||
-- Add and register the new static.
|
||||
local mystatic=_DATABASE:AddStatic(Template.name)
|
||||
|
||||
-- If there is a SpawnFunction hook defined, call it.
|
||||
if self.SpawnFunctionHook then
|
||||
-- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group.
|
||||
self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments))
|
||||
end
|
||||
|
||||
if self.StaticCopyFrom ~= nil then
|
||||
mystatic.StaticCopyFrom = self.StaticCopyFrom
|
||||
if not _DATABASE.Templates.Statics[Template.name] then
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
_DATABASE:_RegisterStaticTemplate( TemplateGroup, self.CoalitionID, self.CategoryID, CountryID )
|
||||
end
|
||||
end
|
||||
|
||||
return mystatic
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
do -- UserFlag
|
||||
|
||||
-- @type USERFLAG
|
||||
--- @type USERFLAG
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string UserFlagName Name of the flag.
|
||||
-- @extends Core.Base#BASE
|
||||
@@ -58,7 +58,7 @@ do -- UserFlag
|
||||
|
||||
--- Set the userflag to a given Number.
|
||||
-- @param #USERFLAG self
|
||||
-- @param #number Number The number value to set the flag to.
|
||||
-- @param #number Number The number value to be checked if it is the same as the userflag.
|
||||
-- @param #number Delay Delay in seconds, before the flag is set.
|
||||
-- @return #USERFLAG The userflag instance.
|
||||
-- @usage
|
||||
@@ -104,4 +104,4 @@ do -- UserFlag
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
do -- Velocity
|
||||
|
||||
-- @type VELOCITY
|
||||
--- @type VELOCITY
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ end
|
||||
|
||||
do -- VELOCITY_POSITIONABLE
|
||||
|
||||
-- @type VELOCITY_POSITIONABLE
|
||||
--- @type VELOCITY_POSITIONABLE
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,7 @@
|
||||
-- @module Core.Zone_Detection
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
---
|
||||
-- @type ZONE_DETECTION
|
||||
--- @type ZONE_DETECTION
|
||||
-- @field DCS#Vec2 Vec2 The current location of the zone.
|
||||
-- @field DCS#Distance Radius The radius of the zone.
|
||||
-- @extends #ZONE_BASE
|
||||
@@ -107,7 +106,7 @@ function ZONE_DETECTION:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset )
|
||||
local Radial = ( Angle + AngleOffset ) * RadialBase / 360
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
COORDINATE:New( Point.x, AddHeight, Point.y):Smoke( SmokeColor )
|
||||
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor )
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -138,7 +137,7 @@ function ZONE_DETECTION:FlareZone( FlareColor, Points, Azimuth, AddHeight )
|
||||
local Radial = Angle * RadialBase / 360
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
COORDINATE:New( Point.x, AddHeight, Point.y ):Flare( FlareColor, Azimuth )
|
||||
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth )
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -202,3 +201,4 @@ function ZONE_DETECTION:IsVec3InZone( Vec3 )
|
||||
|
||||
return InZone
|
||||
end
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ do -- world
|
||||
-- @field #world.event event [https://wiki.hoggitworld.com/view/DCS_enum_world](https://wiki.hoggitworld.com/view/DCS_enum_world)
|
||||
-- @field #world.BirthPlace BirthPlace The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
|
||||
-- @field #world.VolumeType VolumeType The volumeType enumerator defines the types of 3d geometery used within the [world.searchObjects](https://wiki.hoggitworld.com/view/DCS_func_searchObjects) function.
|
||||
-- @field #world.weather weather Weather functions for fog etc.
|
||||
|
||||
--- The world singleton contains functions centered around two different but extremely useful functions.
|
||||
-- * Events and event handlers are all governed within world.
|
||||
@@ -26,68 +25,38 @@ do -- world
|
||||
|
||||
--- [https://wiki.hoggitworld.com/view/DCS_enum_world](https://wiki.hoggitworld.com/view/DCS_enum_world)
|
||||
-- @type world.event
|
||||
-- @field S_EVENT_INVALID = 0
|
||||
-- @field S_EVENT_SHOT = 1
|
||||
-- @field S_EVENT_HIT = 2
|
||||
-- @field S_EVENT_TAKEOFF = 3
|
||||
-- @field S_EVENT_LAND = 4
|
||||
-- @field S_EVENT_CRASH = 5
|
||||
-- @field S_EVENT_EJECTION = 6
|
||||
-- @field S_EVENT_REFUELING = 7
|
||||
-- @field S_EVENT_DEAD = 8
|
||||
-- @field S_EVENT_PILOT_DEAD = 9
|
||||
-- @field S_EVENT_BASE_CAPTURED = 10
|
||||
-- @field S_EVENT_MISSION_START = 11
|
||||
-- @field S_EVENT_MISSION_END = 12
|
||||
-- @field S_EVENT_TOOK_CONTROL = 13
|
||||
-- @field S_EVENT_REFUELING_STOP = 14
|
||||
-- @field S_EVENT_BIRTH = 15
|
||||
-- @field S_EVENT_HUMAN_FAILURE = 16
|
||||
-- @field S_EVENT_DETAILED_FAILURE = 17
|
||||
-- @field S_EVENT_ENGINE_STARTUP = 18
|
||||
-- @field S_EVENT_ENGINE_SHUTDOWN = 19
|
||||
-- @field S_EVENT_PLAYER_ENTER_UNIT = 20
|
||||
-- @field S_EVENT_PLAYER_LEAVE_UNIT = 21
|
||||
-- @field S_EVENT_PLAYER_COMMENT = 22
|
||||
-- @field S_EVENT_SHOOTING_START = 23
|
||||
-- @field S_EVENT_SHOOTING_END = 24
|
||||
-- @field S_EVENT_MARK_ADDED = 25
|
||||
-- @field S_EVENT_MARK_CHANGE = 26
|
||||
-- @field S_EVENT_MARK_REMOVED = 27
|
||||
-- @field S_EVENT_KILL = 28
|
||||
-- @field S_EVENT_SCORE = 29
|
||||
-- @field S_EVENT_UNIT_LOST = 30
|
||||
-- @field S_EVENT_LANDING_AFTER_EJECTION = 31
|
||||
-- @field S_EVENT_PARATROOPER_LENDING = 32 -- who's lending whom what? ;)
|
||||
-- @field S_EVENT_DISCARD_CHAIR_AFTER_EJECTION = 33
|
||||
-- @field S_EVENT_WEAPON_ADD = 34
|
||||
-- @field S_EVENT_TRIGGER_ZONE = 35
|
||||
-- @field S_EVENT_LANDING_QUALITY_MARK = 36
|
||||
-- @field S_EVENT_BDA = 37 -- battle damage assessment
|
||||
-- @field S_EVENT_AI_ABORT_MISSION = 38
|
||||
-- @field S_EVENT_DAYNIGHT = 39
|
||||
-- @field S_EVENT_FLIGHT_TIME = 40
|
||||
-- @field S_EVENT_PLAYER_SELF_KILL_PILOT = 41
|
||||
-- @field S_EVENT_PLAYER_CAPTURE_AIRFIELD = 42
|
||||
-- @field S_EVENT_EMERGENCY_LANDING = 43
|
||||
-- @field S_EVENT_UNIT_CREATE_TASK = 44
|
||||
-- @field S_EVENT_UNIT_DELETE_TASK = 45
|
||||
-- @field S_EVENT_SIMULATION_START = 46
|
||||
-- @field S_EVENT_WEAPON_REARM = 47
|
||||
-- @field S_EVENT_WEAPON_DROP = 48
|
||||
-- @field S_EVENT_UNIT_TASK_COMPLETE = 49
|
||||
-- @field S_EVENT_UNIT_TASK_STAGE = 50
|
||||
-- @field S_EVENT_MAC_EXTRA_SCORE= 51 -- not sure what this is
|
||||
-- @field S_EVENT_MISSION_RESTART= 52
|
||||
-- @field S_EVENT_MISSION_WINNER = 53
|
||||
-- @field S_EVENT_RUNWAY_TAKEOFF= 54
|
||||
-- @field S_EVENT_RUNWAY_TOUCH= 55
|
||||
-- @field S_EVENT_MAC_LMS_RESTART= 56 -- not sure what this is
|
||||
-- @field S_EVENT_SIMULATION_FREEZE = 57
|
||||
-- @field S_EVENT_SIMULATION_UNFREEZE = 58
|
||||
-- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_START = 59
|
||||
-- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH = 60
|
||||
-- @field S_EVENT_MAX = 61
|
||||
-- @field S_EVENT_INVALID
|
||||
-- @field S_EVENT_SHOT [https://wiki.hoggitworld.com/view/DCS_event_shot](https://wiki.hoggitworld.com/view/DCS_event_shot)
|
||||
-- @field S_EVENT_HIT [https://wiki.hoggitworld.com/view/DCS_event_hit](https://wiki.hoggitworld.com/view/DCS_event_hit)
|
||||
-- @field S_EVENT_TAKEOFF [https://wiki.hoggitworld.com/view/DCS_event_takeoff](https://wiki.hoggitworld.com/view/DCS_event_takeoff)
|
||||
-- @field S_EVENT_LAND [https://wiki.hoggitworld.com/view/DCS_event_land](https://wiki.hoggitworld.com/view/DCS_event_land)
|
||||
-- @field S_EVENT_CRASH [https://wiki.hoggitworld.com/view/DCS_event_crash](https://wiki.hoggitworld.com/view/DCS_event_crash)
|
||||
-- @field S_EVENT_EJECTION [https://wiki.hoggitworld.com/view/DCS_event_ejection](https://wiki.hoggitworld.com/view/DCS_event_ejection)
|
||||
-- @field S_EVENT_REFUELING [https://wiki.hoggitworld.com/view/DCS_event_refueling](https://wiki.hoggitworld.com/view/DCS_event_refueling)
|
||||
-- @field S_EVENT_DEAD [https://wiki.hoggitworld.com/view/DCS_event_dead](https://wiki.hoggitworld.com/view/DCS_event_dead)
|
||||
-- @field S_EVENT_PILOT_DEAD [https://wiki.hoggitworld.com/view/DCS_event_pilot_dead](https://wiki.hoggitworld.com/view/DCS_event_pilot_dead)
|
||||
-- @field S_EVENT_BASE_CAPTURED [https://wiki.hoggitworld.com/view/DCS_event_base_captured](https://wiki.hoggitworld.com/view/DCS_event_base_captured)
|
||||
-- @field S_EVENT_MISSION_START [https://wiki.hoggitworld.com/view/DCS_event_mission_start](https://wiki.hoggitworld.com/view/DCS_event_mission_start)
|
||||
-- @field S_EVENT_MISSION_END [https://wiki.hoggitworld.com/view/DCS_event_mission_end](https://wiki.hoggitworld.com/view/DCS_event_mission_end)
|
||||
-- @field S_EVENT_TOOK_CONTROL
|
||||
-- @field S_EVENT_REFUELING_STOP [https://wiki.hoggitworld.com/view/DCS_event_refueling_stop](https://wiki.hoggitworld.com/view/DCS_event_refueling_stop)
|
||||
-- @field S_EVENT_BIRTH [https://wiki.hoggitworld.com/view/DCS_event_birth](https://wiki.hoggitworld.com/view/DCS_event_birth)
|
||||
-- @field S_EVENT_HUMAN_FAILURE [https://wiki.hoggitworld.com/view/DCS_event_human_failure](https://wiki.hoggitworld.com/view/DCS_event_human_failure)
|
||||
-- @field S_EVENT_ENGINE_STARTUP [https://wiki.hoggitworld.com/view/DCS_event_engine_startup](https://wiki.hoggitworld.com/view/DCS_event_engine_startup)
|
||||
-- @field S_EVENT_ENGINE_SHUTDOWN [https://wiki.hoggitworld.com/view/DCS_event_engine_shutdown](https://wiki.hoggitworld.com/view/DCS_event_engine_shutdown)
|
||||
-- @field S_EVENT_PLAYER_ENTER_UNIT [https://wiki.hoggitworld.com/view/DCS_event_player_enter_unit](https://wiki.hoggitworld.com/view/DCS_event_player_enter_unit)
|
||||
-- @field S_EVENT_PLAYER_LEAVE_UNIT [https://wiki.hoggitworld.com/view/DCS_event_player_leave_unit](https://wiki.hoggitworld.com/view/DCS_event_player_leave_unit)
|
||||
-- @field S_EVENT_PLAYER_COMMENT
|
||||
-- @field S_EVENT_SHOOTING_START [https://wiki.hoggitworld.com/view/DCS_event_shooting_start](https://wiki.hoggitworld.com/view/DCS_event_shooting_start)
|
||||
-- @field S_EVENT_SHOOTING_END [https://wiki.hoggitworld.com/view/DCS_event_shooting_end](https://wiki.hoggitworld.com/view/DCS_event_shooting_end)
|
||||
-- @field S_EVENT_MARK ADDED [https://wiki.hoggitworld.com/view/DCS_event_mark_added](https://wiki.hoggitworld.com/view/DCS_event_mark_added) DCS>=2.5.1
|
||||
-- @field S_EVENT_MARK CHANGE [https://wiki.hoggitworld.com/view/DCS_event_mark_change](https://wiki.hoggitworld.com/view/DCS_event_mark_change) DCS>=2.5.1
|
||||
-- @field S_EVENT_MARK REMOVE [https://wiki.hoggitworld.com/view/DCS_event_mark_remove](https://wiki.hoggitworld.com/view/DCS_event_mark_remove) DCS>=2.5.1
|
||||
-- @field S_EVENT_KILL [https://wiki.hoggitworld.com/view/DCS_event_kill](https://wiki.hoggitworld.com/view/DCS_event_kill) DCS>=2.5.6
|
||||
-- @field S_EVENT_SCORE [https://wiki.hoggitworld.com/view/DCS_event_score](https://wiki.hoggitworld.com/view/DCS_event_score) DCS>=2.5.6
|
||||
-- @field S_EVENT_UNIT_LOST [https://wiki.hoggitworld.com/view/DCS_event_unit_lost](https://wiki.hoggitworld.com/view/DCS_event_unit_lost) DCS>=2.5.6
|
||||
-- @field S_EVENT_LANDING_AFTER_EJECTION [https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection](https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection) DCS>=2.5.6
|
||||
-- @field S_EVENT_MAX
|
||||
|
||||
--- The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
|
||||
-- @type world.BirthPlace
|
||||
@@ -133,36 +102,6 @@ do -- world
|
||||
-- @function [parent=#world] getAirbases
|
||||
-- @param #number coalitionId The coalition side number ID. Default is all airbases are returned.
|
||||
-- @return #table Table of DCS airbase objects.
|
||||
|
||||
|
||||
--- Weather functions.
|
||||
-- @type world.weather
|
||||
|
||||
--- Fog animation data structure.
|
||||
-- @type world.FogAnimation
|
||||
-- @field #number time
|
||||
-- @field #number visibility
|
||||
-- @field #number thickness
|
||||
|
||||
--- Returns the current fog thickness.
|
||||
-- @function [parent=#world.weather] getFogThickness Returns the fog thickness.
|
||||
-- @return #number Fog thickness in meters. If there is no fog, zero is returned.
|
||||
|
||||
--- Sets the fog thickness instantly. Any current fog animation is discarded.
|
||||
-- @function [parent=#world.weather] setFogThickness
|
||||
-- @param #number thickness Fog thickness in meters. Set to zero to disable fog.
|
||||
|
||||
--- Returns the current fog visibility distance.
|
||||
-- @function [parent=#world.weather] getFogVisibilityDistance Returns the current maximum visibility distance in meters. Returns zero if fog is not present.
|
||||
|
||||
--- Instantly sets the maximum visibility distance of fog at sea level when looking at the horizon. Any current fog animation is discarded. Set zero to disable the fog.
|
||||
-- @function [parent=#world.weather] setFogVisibilityDistance
|
||||
-- @param #number visibility Max fog visibility in meters. Set to zero to disable fog.
|
||||
|
||||
--- Sets fog animation keys. Time is set in seconds and relative to the current simulation time, where time=0 is the current moment.
|
||||
-- Time must be increasing. Previous animation is always discarded despite the data being correct.
|
||||
-- @function [parent=#world.weather] setFogAnimation
|
||||
-- @param #world.FogAnimation animation List of fog animations
|
||||
|
||||
end -- world
|
||||
|
||||
@@ -198,7 +137,7 @@ end -- env
|
||||
|
||||
do -- radio
|
||||
|
||||
--@type radio
|
||||
---@type radio
|
||||
-- @field #radio.modulation modulation
|
||||
|
||||
---
|
||||
@@ -438,7 +377,7 @@ do -- coalition
|
||||
-- @param #table groupData Group data table.
|
||||
-- @return DCS#Group The spawned Group object.
|
||||
|
||||
--- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addStaticObject)
|
||||
--- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup)
|
||||
-- @function [parent=#coalition] addStaticObject
|
||||
-- @param #number countryId Id of the country.
|
||||
-- @param #table groupData Group data table.
|
||||
@@ -451,7 +390,6 @@ end -- coalition
|
||||
|
||||
do -- Types
|
||||
|
||||
--- Descriptors.
|
||||
-- @type Desc
|
||||
-- @field #number speedMax0 Max speed in meters/second at zero altitude.
|
||||
-- @field #number massEmpty Empty mass in kg.
|
||||
@@ -630,13 +568,9 @@ do -- Object
|
||||
--- @function [parent=#Object] destroy
|
||||
-- @param #Object self
|
||||
|
||||
--- Returns an enumerator of the category for the specific object.
|
||||
-- The enumerator returned is dependent on the category of the object and how the function is called.
|
||||
-- As of DCS 2.9.2 when this function is called on an Object, Unit, Weapon, or Airbase a 2nd value will be returned which details the object sub-category value.
|
||||
-- @function [parent=#Object] getCategory
|
||||
--- @function [parent=#Object] getCategory
|
||||
-- @param #Object self
|
||||
-- @return #Object.Category The object category (1=UNIT, 2=WEAPON, 3=STATIC, 4=BASE, 5=SCENERY, 6=Cargo)
|
||||
-- @return #number The subcategory of the passed object, e.g. Unit.Category if a unit object was passed.
|
||||
-- @return #Object.Category
|
||||
|
||||
--- Returns type name of the Object.
|
||||
-- @function [parent=#Object] getTypeName
|
||||
@@ -1049,16 +983,14 @@ do -- Spot
|
||||
end -- Spot
|
||||
|
||||
do -- Controller
|
||||
|
||||
--- Controller is an object that performs A.I.-tasks. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C.
|
||||
--
|
||||
-- This class has 2 types of functions:
|
||||
--
|
||||
-- * Tasks
|
||||
-- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics.
|
||||
--
|
||||
-- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics.
|
||||
-- @type Controller
|
||||
-- @field #Controller.Detection Detection Enum contains identifiers of surface types.
|
||||
-- @field #Controller.Detection Detection Enum contains identifiers of surface types.
|
||||
|
||||
--- Enables and disables the controller.
|
||||
-- Note: Now it works only for ground / naval groups!
|
||||
@@ -1117,18 +1049,18 @@ do -- Controller
|
||||
|
||||
-- Detection
|
||||
|
||||
--- Enum containing detection types.
|
||||
--- Enum contains identifiers of surface types.
|
||||
-- @type Controller.Detection
|
||||
-- @field #number VISUAL Visual detection. Numeric value 1.
|
||||
-- @field #number OPTIC Optical detection. Numeric value 2.
|
||||
-- @field #number RADAR Radar detection. Numeric value 4.
|
||||
-- @field #number IRST Infra-red search and track detection. Numeric value 8.
|
||||
-- @field #number RWR Radar Warning Receiver detection. Numeric value 16.
|
||||
-- @field #number DLINK Data link detection. Numeric value 32.
|
||||
-- @field VISUAL
|
||||
-- @field OPTIC
|
||||
-- @field RADAR
|
||||
-- @field IRST
|
||||
-- @field RWR
|
||||
-- @field DLINK
|
||||
|
||||
--- Detected target.
|
||||
-- @type Controller.DetectedTarget
|
||||
-- @field DCS#Object object The target
|
||||
-- @type DetectedTarget
|
||||
-- @field Wrapper.Object#Object object The target
|
||||
-- @field #boolean visible The target is visible
|
||||
-- @field #boolean type The target type is known
|
||||
-- @field #boolean distance Distance to the target is known
|
||||
@@ -1141,9 +1073,9 @@ do -- Controller
|
||||
-- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN
|
||||
-- @return #boolean detected True if the target is detected.
|
||||
-- @return #boolean visible Has effect only if detected is true. True if the target is visible now.
|
||||
-- @return #boolean type Has effect only if detected is true. True if the target type is known.
|
||||
-- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known.
|
||||
-- @return #ModelTime lastTime Has effect only if visible is false. Last time when target was seen.
|
||||
-- @return #boolean type Has effect only if detected is true. True if the target type is known.
|
||||
-- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known.
|
||||
-- @return #Vec3 lastPos Has effect only if visible is false. Last position of the target when it was seen.
|
||||
-- @return #Vec3 lastVel Has effect only if visible is false. Last velocity of the target when it was seen.
|
||||
|
||||
@@ -1169,7 +1101,6 @@ end -- Controller
|
||||
|
||||
do -- Unit
|
||||
|
||||
--- Unit.
|
||||
-- @type Unit
|
||||
-- @extends #CoalitionObject
|
||||
-- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically.
|
||||
@@ -1734,7 +1665,6 @@ do -- AI
|
||||
-- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE}
|
||||
-- @field ENGAGE_AIR_WEAPONS
|
||||
-- @field AC_ENGAGEMENT_RANGE_RESTRICTION
|
||||
-- @field EVASION_OF_ARM
|
||||
|
||||
---
|
||||
-- @type AI.Option.Ground.mid -- Moose added
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@
|
||||
-- ### Author: FlightControl - Framework Design & Programming
|
||||
-- ### Refactoring to use the Runway auto-detection: Applevangelist
|
||||
-- @date August 2022
|
||||
-- Last Update Feb 2025
|
||||
-- Last Update Nov 2023
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -416,7 +416,7 @@ end
|
||||
-- @field #ATC_GROUND_UNIVERSAL
|
||||
ATC_GROUND_UNIVERSAL = {
|
||||
ClassName = "ATC_GROUND_UNIVERSAL",
|
||||
Version = "0.0.2",
|
||||
Version = "0.0.1",
|
||||
SetClient = nil,
|
||||
Airbases = nil,
|
||||
AirbaseList = nil,
|
||||
@@ -441,25 +441,17 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList)
|
||||
self:T( { self.ClassName } )
|
||||
|
||||
self.Airbases = {}
|
||||
|
||||
for _name,_ in pairs(_DATABASE.AIRBASES) do
|
||||
self.Airbases[_name]={}
|
||||
end
|
||||
|
||||
self.AirbaseList = AirbaseList
|
||||
|
||||
if not self.AirbaseList then
|
||||
self.AirbaseList = {}
|
||||
for _name,_base in pairs(_DATABASE.AIRBASES) do
|
||||
-- DONE exclude FARPS and Ships
|
||||
if _base and _base.isAirdrome == true then
|
||||
self.AirbaseList[_name]=_name
|
||||
self.Airbases[_name]={}
|
||||
end
|
||||
end
|
||||
else
|
||||
for _,_name in pairs(AirbaseList) do
|
||||
-- DONE exclude FARPS and Ships
|
||||
local airbase = _DATABASE:FindAirbase(_name)
|
||||
if airbase and (airbase.isAirdrome == true) then
|
||||
self.Airbases[_name]={}
|
||||
end
|
||||
for _name,_ in pairs(_DATABASE.AIRBASES) do
|
||||
self.AirbaseList[_name]=_name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -729,18 +721,14 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
|
||||
if NotInRunwayZone then
|
||||
|
||||
local Taxi = Client:GetState( self, "Taxi" )
|
||||
|
||||
if IsOnGround then
|
||||
|
||||
local Taxi = Client:GetState( self, "Taxi" )
|
||||
self:T( Taxi )
|
||||
if Taxi == false then
|
||||
local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed )
|
||||
Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " ..
|
||||
Velocity:ToString() , 20, "ATC" )
|
||||
Client:SetState( self, "Taxi", true )
|
||||
Client:SetState( self, "Speeding", false )
|
||||
Client:SetState( self, "Warnings", 0 )
|
||||
end
|
||||
|
||||
-- TODO: GetVelocityKMH function usage
|
||||
@@ -749,7 +737,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
local IsAboveRunway = Client:IsAboveRunway()
|
||||
self:T( {IsAboveRunway, IsOnGround, Velocity:Get() })
|
||||
|
||||
if IsOnGround and not Taxi then
|
||||
if IsOnGround then
|
||||
local Speeding = false
|
||||
if AirbaseMeta.MaximumKickSpeed then
|
||||
if Velocity:Get() > AirbaseMeta.MaximumKickSpeed then
|
||||
@@ -761,17 +749,15 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
end
|
||||
end
|
||||
if Speeding == true then
|
||||
--MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() ..
|
||||
-- " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
|
||||
--Client:Destroy()
|
||||
Client:SetState( self, "Speeding", true )
|
||||
local SpeedingWarnings = Client:GetState( self, "Warnings" )
|
||||
Client:SetState( self, "Warnings", SpeedingWarnings + 1 )
|
||||
Client:Message( "Warning " .. SpeedingWarnings .. "/3! Airbase traffic rule violation! Slow down now! Your speed is " ..
|
||||
Velocity:ToString(), 5, "ATC" )
|
||||
MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() ..
|
||||
" has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
|
||||
Client:Destroy()
|
||||
Client:SetState( self, "Speeding", false )
|
||||
Client:SetState( self, "Warnings", 0 )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
if IsOnGround then
|
||||
|
||||
local Speeding = false
|
||||
@@ -1049,23 +1035,23 @@ end
|
||||
-- The following airbases are monitored at the Nevada region.
|
||||
-- Use the @{Wrapper.Airbase#AIRBASE.Nevada} enumeration to select the airbases to be monitored.
|
||||
--
|
||||
-- * `AIRBASE.Nevada.Beatty`
|
||||
-- * `AIRBASE.Nevada.Boulder_City`
|
||||
-- * `AIRBASE.Nevada.Creech`
|
||||
-- * `AIRBASE.Nevada.Beatty_Airport`
|
||||
-- * `AIRBASE.Nevada.Boulder_City_Airport`
|
||||
-- * `AIRBASE.Nevada.Creech_AFB`
|
||||
-- * `AIRBASE.Nevada.Echo_Bay`
|
||||
-- * `AIRBASE.Nevada.Groom_Lake`
|
||||
-- * `AIRBASE.Nevada.Henderson_Executive`
|
||||
-- * `AIRBASE.Nevada.Jean`
|
||||
-- * `AIRBASE.Nevada.Laughlin`
|
||||
-- * `AIRBASE.Nevada.Groom_Lake_AFB`
|
||||
-- * `AIRBASE.Nevada.Henderson_Executive_Airport`
|
||||
-- * `AIRBASE.Nevada.Jean_Airport`
|
||||
-- * `AIRBASE.Nevada.Laughlin_Airport`
|
||||
-- * `AIRBASE.Nevada.Lincoln_County`
|
||||
-- * `AIRBASE.Nevada.McCarran_International`
|
||||
-- * `AIRBASE.Nevada.McCarran_International_Airport`
|
||||
-- * `AIRBASE.Nevada.Mesquite`
|
||||
-- * `AIRBASE.Nevada.Mina`
|
||||
-- * `AIRBASE.Nevada.Nellis`
|
||||
-- * `AIRBASE.Nevada.Mina_Airport`
|
||||
-- * `AIRBASE.Nevada.Nellis_AFB`
|
||||
-- * `AIRBASE.Nevada.North_Las_Vegas`
|
||||
-- * `AIRBASE.Nevada.Pahute_Mesa`
|
||||
-- * `AIRBASE.Nevada.Tonopah`
|
||||
-- * `AIRBASE.Nevada.Tonopah_Test_Range`
|
||||
-- * `AIRBASE.Nevada.Pahute_Mesa_Airstrip`
|
||||
-- * `AIRBASE.Nevada.Tonopah_Airport`
|
||||
-- * `AIRBASE.Nevada.Tonopah_Test_Range_Airfield`
|
||||
--
|
||||
-- # Installation
|
||||
--
|
||||
@@ -1102,10 +1088,10 @@ end
|
||||
--
|
||||
-- -- Monitor specific airbases.
|
||||
-- ATC_Ground = ATC_GROUND_NEVADA:New(
|
||||
-- { AIRBASE.Nevada.Laughlin,
|
||||
-- { AIRBASE.Nevada.Laughlin_Airport,
|
||||
-- AIRBASE.Nevada.Lincoln_County,
|
||||
-- AIRBASE.Nevada.North_Las_Vegas,
|
||||
-- AIRBASE.Nevada.McCarran_International
|
||||
-- AIRBASE.Nevada.McCarran_International_Airport
|
||||
-- }
|
||||
-- )
|
||||
--
|
||||
@@ -1344,33 +1330,33 @@ end
|
||||
-- The following airbases are monitored at the PersianGulf region.
|
||||
-- Use the @{Wrapper.Airbase#AIRBASE.PersianGulf} enumeration to select the airbases to be monitored.
|
||||
--
|
||||
-- * `AIRBASE.PersianGulf.Abu_Musa_Island`
|
||||
-- * `AIRBASE.PersianGulf.Al_Dhafra_AFB`
|
||||
-- * `AIRBASE.PersianGulf.Abu_Musa_Island_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Al_Dhafra_AB`
|
||||
-- * `AIRBASE.PersianGulf.Al_Maktoum_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Al_Minhad_AFB`
|
||||
-- * `AIRBASE.PersianGulf.Al_Minhad_AB`
|
||||
-- * `AIRBASE.PersianGulf.Bandar_Abbas_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Bandar_Lengeh`
|
||||
-- * `AIRBASE.PersianGulf.Dubai_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Fujairah_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Havadarya`
|
||||
-- * `AIRBASE.PersianGulf.Kerman`
|
||||
-- * `AIRBASE.PersianGulf.Kerman_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Khasab`
|
||||
-- * `AIRBASE.PersianGulf.Lar`
|
||||
-- * `AIRBASE.PersianGulf.Lar_Airbase`
|
||||
-- * `AIRBASE.PersianGulf.Qeshm_Island`
|
||||
-- * `AIRBASE.PersianGulf.Sharjah_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Shiraz_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Shiraz_International_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Sir_Abu_Nuayr`
|
||||
-- * `AIRBASE.PersianGulf.Sirri_Island`
|
||||
-- * `AIRBASE.PersianGulf.Tunb_Island_AFB`
|
||||
-- * `AIRBASE.PersianGulf.Tunb_Kochak`
|
||||
-- * `AIRBASE.PersianGulf.Sas_Al_Nakheel`
|
||||
-- * `AIRBASE.PersianGulf.Bandar_e_Jask`
|
||||
-- * `AIRBASE.PersianGulf.Abu_Dhabi_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Al_Bateen`
|
||||
-- * `AIRBASE.PersianGulf.Kish_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Al_Ain_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Lavan_Island`
|
||||
-- * `AIRBASE.PersianGulf.Jiroft`
|
||||
-- * `AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Bandar_e_Jask_airfield`
|
||||
-- * `AIRBASE.PersianGulf.Abu_Dhabi_International_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Al_Bateen_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Kish_International_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Al_Ain_International_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Lavan_Island_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Jiroft_Airport`
|
||||
--
|
||||
-- # Installation
|
||||
--
|
||||
@@ -1405,8 +1391,8 @@ end
|
||||
-- AirbasePoliceCaucasus = ATC_GROUND_PERSIANGULF:New()
|
||||
--
|
||||
-- ATC_Ground = ATC_GROUND_PERSIANGULF:New(
|
||||
-- { AIRBASE.PersianGulf.Kerman,
|
||||
-- AIRBASE.PersianGulf.Al_Minhad_AFB
|
||||
-- { AIRBASE.PersianGulf.Kerman_Airport,
|
||||
-- AIRBASE.PersianGulf.Al_Minhad_AB
|
||||
-- }
|
||||
-- )
|
||||
--
|
||||
@@ -1455,10 +1441,11 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
---
|
||||
-- @type ATC_GROUND_MARIANAISLANDS
|
||||
|
||||
-- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
|
||||
--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND}
|
||||
--
|
||||
|
||||
@@ -1,806 +0,0 @@
|
||||
--- **Functional** -- Send a truck to supply artillery groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **AMMOTRUCK** - Send a truck to supply artillery groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- Demo missions can be found on [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Functional/AmmoTruck)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author : **applevangelist**
|
||||
--
|
||||
-- @module Functional.AmmoTruck
|
||||
-- @image Artillery.JPG
|
||||
--
|
||||
-- Last update: July 2023
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **AMMOTRUCK** class, extends Core.Fsm#FSM
|
||||
-- @type AMMOTRUCK
|
||||
-- @field #string ClassName Class Name
|
||||
-- @field #string lid Lid for log entries
|
||||
-- @field #string version Version string
|
||||
-- @field #string alias Alias name
|
||||
-- @field #boolean debug Debug flag
|
||||
-- @field #table trucklist List of (alive) #AMMOTRUCK.data trucks
|
||||
-- @field #table targetlist List of (alive) #AMMOTRUCK.data artillery
|
||||
-- @field #number coalition Coalition this is for
|
||||
-- @field Core.Set#SET_GROUP truckset SET of trucks
|
||||
-- @field Core.Set#SET_GROUP targetset SET of artillery
|
||||
-- @field #table remunitionqueue List of (alive) #AMMOTRUCK.data artillery to be reloaded
|
||||
-- @field #table waitingtargets List of (alive) #AMMOTRUCK.data artillery waiting
|
||||
-- @field #number ammothreshold Threshold (min) ammo before sending a truck
|
||||
-- @field #number remunidist Max distance trucks will go
|
||||
-- @field #number monitor Monitor interval in seconds
|
||||
-- @field #number unloadtime Unload time in seconds
|
||||
-- @field #number waitingtime Max waiting time in seconds
|
||||
-- @field #boolean routeonroad Route truck on road if true (default)
|
||||
-- @field #number reloads Number of reloads a single truck can do before he must return home
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Amateurs talk about tactics, but professionals study logistics.* - General Robert H Barrow, USMC
|
||||
--
|
||||
-- Simple Class to re-arm your artillery with trucks.
|
||||
--
|
||||
-- #AMMOTRUCK
|
||||
--
|
||||
-- * Controls a SET\_GROUP of trucks which will re-arm a SET\_GROUP of artillery groups when they run out of ammunition.
|
||||
--
|
||||
-- ## 1 The AMMOTRUCK concept
|
||||
--
|
||||
-- A SET\_GROUP of trucks which will re-arm a SET\_GROUP of artillery groups when they run out of ammunition. They will be based on a
|
||||
-- homebase and drive from there to the artillery groups and then back home.
|
||||
-- Trucks are the **only known in-game mechanic** to re-arm artillery and other units in DCS. Working units are e.g.: M-939 (blue), Ural-375 and ZIL-135 (both red).
|
||||
--
|
||||
-- ## 2 Set-up
|
||||
--
|
||||
-- Define a set of trucks and a set of artillery:
|
||||
--
|
||||
-- local truckset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Ammo Truck"):FilterStart()
|
||||
-- local ariset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Artillery"):FilterStart()
|
||||
--
|
||||
-- Create an AMMOTRUCK object to take care of the artillery using the trucks, with a homezone:
|
||||
--
|
||||
-- local ammotruck = AMMOTRUCK:New(truckset,ariset,coalition.side.BLUE,"Logistics",ZONE:FindByName("HomeZone")
|
||||
--
|
||||
-- ## 2 Options and their default values
|
||||
--
|
||||
-- ammotruck.ammothreshold = 5 -- send a truck when down to this many rounds
|
||||
-- ammotruck.remunidist = 20000 -- 20km - send trucks max this far from home
|
||||
-- ammotruck.unloadtime = 600 -- 10 minutes - min time to unload ammunition
|
||||
-- ammotruck.waitingtime = 1800 -- 30 mintes - wait max this long until remunition is done
|
||||
-- ammotruck.monitor = -60 -- 1 minute - AMMOTRUCK checks run every one minute
|
||||
-- ammotruck.routeonroad = true -- Trucks will **try** to drive on roads
|
||||
-- ammotruck.usearmygroup = false -- If true, will make use of ARMYGROUP in the background (if used in DEV branch)
|
||||
-- ammotruck.reloads = 5 -- Maxn re-arms a truck can do before he needs to go home and restock. Set to -1 for unlimited
|
||||
--
|
||||
-- ## 3 FSM Events to shape mission
|
||||
--
|
||||
-- Truck has been sent off:
|
||||
--
|
||||
-- function ammotruck:OnAfterRouteTruck(From, Event, To, Truckdata, Aridata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- Truck has arrived:
|
||||
--
|
||||
-- function ammotruck:OnAfterTruckArrived(From, Event, To, Truckdata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- Truck is unloading:
|
||||
--
|
||||
-- function ammotruck:OnAfterTruckUnloading(From, Event, To, Truckdata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- Truck is returning home:
|
||||
--
|
||||
-- function ammotruck:OnAfterTruckReturning(From, Event, To, Truckdata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- Truck is arrived at home:
|
||||
--
|
||||
-- function ammotruck:OnAfterTruckHome(From, Event, To, Truckdata)
|
||||
-- ...
|
||||
-- end
|
||||
--
|
||||
-- @field #AMMOTRUCK
|
||||
AMMOTRUCK = {
|
||||
ClassName = "AMMOTRUCK",
|
||||
lid = "",
|
||||
version = "0.0.12",
|
||||
alias = "",
|
||||
debug = false,
|
||||
trucklist = {},
|
||||
targetlist = {},
|
||||
coalition = nil,
|
||||
truckset = nil,
|
||||
targetset = nil,
|
||||
remunitionqueue = {},
|
||||
waitingtargets = {},
|
||||
ammothreshold = 5,
|
||||
remunidist = 20000,
|
||||
monitor = -60,
|
||||
unloadtime = 600,
|
||||
waitingtime = 1800,
|
||||
routeonroad = true,
|
||||
reloads = 5,
|
||||
}
|
||||
|
||||
---
|
||||
-- @type AMMOTRUCK.State
|
||||
AMMOTRUCK.State = {
|
||||
IDLE = "idle",
|
||||
DRIVING = "driving",
|
||||
ARRIVED = "arrived",
|
||||
UNLOADING = "unloading",
|
||||
RETURNING = "returning",
|
||||
WAITING = "waiting",
|
||||
RELOADING = "reloading",
|
||||
OUTOFAMMO = "outofammo",
|
||||
REQUESTED = "requested",
|
||||
}
|
||||
|
||||
---
|
||||
--@type AMMOTRUCK.data
|
||||
--@field Wrapper.Group#GROUP group
|
||||
--@field #string name
|
||||
--@field #AMMOTRUCK.State statusquo
|
||||
--@field #number timestamp
|
||||
--@field #number ammo
|
||||
--@field Core.Point#COORDINATE coordinate
|
||||
--@field #string targetname
|
||||
--@field Wrapper.Group#GROUP targetgroup
|
||||
--@field Core.Point#COORDINATE targetcoordinate
|
||||
--@field #number reloads
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param Core.Set#SET_GROUP Truckset Set of truck groups
|
||||
-- @param Core.Set#SET_GROUP Targetset Set of artillery groups
|
||||
-- @param #number Coalition Coalition
|
||||
-- @param #string Alias Alias Name
|
||||
-- @param Core.Zone#ZONE Homezone Home, return zone for trucks
|
||||
-- @return #AMMOTRUCK self
|
||||
-- @usage
|
||||
-- Define a set of trucks and a set of artillery:
|
||||
-- local truckset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Ammo Truck"):FilterStart()
|
||||
-- local ariset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Artillery"):FilterStart()
|
||||
--
|
||||
-- Create an AMMOTRUCK object to take care of the artillery using the trucks, with a homezone:
|
||||
-- local ammotruck = AMMOTRUCK:New(truckset,ariset,coalition.side.BLUE,"Logistics",ZONE:FindByName("HomeZone")
|
||||
function AMMOTRUCK:New(Truckset,Targetset,Coalition,Alias,Homezone)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #AMMOTRUCK
|
||||
|
||||
self.truckset = Truckset -- Core.Set#SET_GROUP
|
||||
self.targetset = Targetset -- Core.Set#SET_GROUP
|
||||
self.coalition = Coalition -- #number
|
||||
self.alias = Alias -- #string
|
||||
self.debug = false
|
||||
self.remunitionqueue = {}
|
||||
self.trucklist = {}
|
||||
self.targetlist = {}
|
||||
self.ammothreshold = 5
|
||||
self.remunidist = 20000
|
||||
self.homezone = Homezone -- Core.Zone#ZONE
|
||||
self.waitingtime = 1800
|
||||
self.usearmygroup = false
|
||||
self.hasarmygroup = false
|
||||
|
||||
-- Log id.
|
||||
self.lid=string.format("AMMOTRUCK %s | %s | ", self.version, self.alias)
|
||||
|
||||
self:SetStartState("Stopped")
|
||||
self:AddTransition("Stopped", "Start", "Running")
|
||||
self:AddTransition("*", "Monitor", "*")
|
||||
self:AddTransition("*", "RouteTruck", "*")
|
||||
self:AddTransition("*", "TruckArrived", "*")
|
||||
self:AddTransition("*", "TruckUnloading", "*")
|
||||
self:AddTransition("*", "TruckReturning", "*")
|
||||
self:AddTransition("*", "TruckHome", "*")
|
||||
self:AddTransition("*", "Stop", "Stopped")
|
||||
|
||||
self:__Start(math.random(5,10))
|
||||
|
||||
self:I(self.lid .. "Started")
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the AMMOTRUCK and all its event handlers.
|
||||
-- @function [parent=#AMMOTRUCK] Stop
|
||||
-- @param #AMMOTRUCK self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the AMMOTRUCK and all its event handlers.
|
||||
-- @function [parent=#AMMOTRUCK] __Stop
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- On after "RouteTruck" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterRouteTruck
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
-- @param #AMMOTRUCK.data Artillery
|
||||
|
||||
--- On after "TruckUnloading" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterTruckUnloading
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
|
||||
--- On after "TruckReturning" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterTruckReturning
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
|
||||
--- On after "RouteTruck" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterRouteTruck
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
|
||||
--- On after "TruckHome" event.
|
||||
-- @function [parent=#AMMOTRUCK] OnAfterTruckHome
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckDrivingTrucks(dataset)
|
||||
self:T(self.lid .. " CheckDrivingTrucks")
|
||||
local data = dataset
|
||||
for _,_data in pairs (data) do
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
-- see if we arrived at destination
|
||||
local coord = truck.group:GetCoordinate()
|
||||
local tgtcoord = truck.targetcoordinate
|
||||
local dist = coord:Get2DDistance(tgtcoord)
|
||||
if dist <= 150 then
|
||||
-- arrived
|
||||
truck.statusquo = AMMOTRUCK.State.ARRIVED
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
truck.coordinate = coord
|
||||
self:__TruckArrived(1,truck)
|
||||
end
|
||||
-- still driving?
|
||||
local Tnow = timer.getAbsTime()
|
||||
if Tnow - truck.timestamp > 30 then
|
||||
local group = truck.group
|
||||
if self.usearmygroup then
|
||||
group = truck.group:GetGroup()
|
||||
end
|
||||
local currspeed = group:GetVelocityKMH()
|
||||
if truck.lastspeed then
|
||||
if truck.lastspeed == 0 and currspeed == 0 then
|
||||
self:T(truck.group:GetName().." Is not moving!")
|
||||
-- try and move it
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
if self.routeonroad then
|
||||
group:RouteGroundOnRoad(truck.targetcoordinate,30,2,"Vee")
|
||||
else
|
||||
group:RouteGroundTo(truck.targetcoordinate,30,"Vee",2)
|
||||
end
|
||||
end
|
||||
truck.lastspeed = currspeed
|
||||
else
|
||||
truck.lastspeed = currspeed
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
end
|
||||
self:I({truck=truck.group:GetName(),currspeed=currspeed,lastspeed=truck.lastspeed})
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:GetAmmoStatus(Group)
|
||||
local ammotot, shells, rockets, bombs, missiles, narti = Group:GetAmmunition()
|
||||
return rockets+missiles+narti
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckWaitingTargets(dataset)
|
||||
self:T(self.lid .. " CheckWaitingTargets")
|
||||
local data = dataset
|
||||
for _,_data in pairs (data) do
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
-- see how long we're waiting - maybe ammo truck is dead?
|
||||
local Tnow = timer.getAbsTime()
|
||||
local Tdiff = Tnow - truck.timestamp
|
||||
if Tdiff > self.waitingtime then
|
||||
local hasammo = self:GetAmmoStatus(truck.group)
|
||||
if hasammo <= self.ammothreshold then
|
||||
truck.statusquo = AMMOTRUCK.State.OUTOFAMMO
|
||||
else
|
||||
truck.statusquo = AMMOTRUCK.State.IDLE
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckReturningTrucks(dataset)
|
||||
self:T(self.lid .. " CheckReturningTrucks")
|
||||
local data = dataset
|
||||
local tgtcoord = self.homezone:GetCoordinate()
|
||||
local radius = self.homezone:GetRadius()
|
||||
for _,_data in pairs (data) do
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
-- see if we arrived at destination
|
||||
local coord = truck.group:GetCoordinate()
|
||||
local dist = coord:Get2DDistance(tgtcoord)
|
||||
self:T({name=truck.name,radius=radius,distance=dist})
|
||||
if dist <= radius then
|
||||
-- arrived
|
||||
truck.statusquo = AMMOTRUCK.State.IDLE
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
truck.coordinate = coord
|
||||
truck.reloads = self.reloads or 5
|
||||
self:__TruckHome(1,truck)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string name Artillery group name to find
|
||||
-- @return #AMMOTRUCK.data Data
|
||||
function AMMOTRUCK:FindTarget(name)
|
||||
self:T(self.lid .. " FindTarget")
|
||||
local data = nil
|
||||
local dataset = self.targetlist
|
||||
for _,_entry in pairs(dataset) do
|
||||
local entry = _entry -- #AMMOTRUCK.data
|
||||
if entry.name == name then
|
||||
data = entry
|
||||
break
|
||||
end
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string name Truck group name to find
|
||||
-- @return #AMMOTRUCK.data Data
|
||||
function AMMOTRUCK:FindTruck(name)
|
||||
self:T(self.lid .. " FindTruck")
|
||||
local data = nil
|
||||
local dataset = self.trucklist
|
||||
for _,_entry in pairs(dataset) do
|
||||
local entry = _entry -- #AMMOTRUCK.data
|
||||
if entry.name == name then
|
||||
data = entry
|
||||
break
|
||||
end
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckArrivedTrucks(dataset)
|
||||
self:T(self.lid .. " CheckArrivedTrucks")
|
||||
local data = dataset
|
||||
for _,_data in pairs (data) do
|
||||
-- set to unloading
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
truck.statusquo = AMMOTRUCK.State.UNLOADING
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
self:__TruckUnloading(2,truck)
|
||||
-- set target to reloading
|
||||
local aridata = self:FindTarget(truck.targetname) -- #AMMOTRUCK.data
|
||||
if aridata then
|
||||
aridata.statusquo = AMMOTRUCK.State.RELOADING
|
||||
aridata.timestamp = timer.getAbsTime()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #table dataset table of #AMMOTRUCK.data entries
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckUnloadingTrucks(dataset)
|
||||
self:T(self.lid .. " CheckUnloadingTrucks")
|
||||
local data = dataset
|
||||
for _,_data in pairs (data) do
|
||||
-- check timestamp
|
||||
local truck = _data -- #AMMOTRUCK.data
|
||||
local Tnow = timer.getAbsTime()
|
||||
local Tpassed = Tnow - truck.timestamp
|
||||
local hasammo = self:GetAmmoStatus(truck.targetgroup)
|
||||
if Tpassed > self.unloadtime and hasammo > self.ammothreshold then
|
||||
truck.statusquo = AMMOTRUCK.State.RETURNING
|
||||
truck.timestamp = timer.getAbsTime()
|
||||
self:__TruckReturning(2,truck)
|
||||
-- set target to reloaded
|
||||
local aridata = self:FindTarget(truck.targetname) -- #AMMOTRUCK.data
|
||||
if aridata then
|
||||
aridata.statusquo = AMMOTRUCK.State.IDLE
|
||||
aridata.timestamp = timer.getAbsTime()
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckTargetsAlive()
|
||||
self:T(self.lid .. " CheckTargetsAlive")
|
||||
local arilist = self.targetlist
|
||||
for _,_ari in pairs(arilist) do
|
||||
local ari = _ari -- #AMMOTRUCK.data
|
||||
if ari.group and ari.group:IsAlive() then
|
||||
-- everything fine
|
||||
else
|
||||
-- ari dead
|
||||
self.targetlist[ari.name] = nil
|
||||
end
|
||||
end
|
||||
-- new arrivals?
|
||||
local aritable = self.targetset:GetSetObjects() --#table
|
||||
for _,_ari in pairs(aritable) do
|
||||
local ari = _ari -- Wrapper.Group#GROUP
|
||||
if ari and ari:IsAlive() and not self.targetlist[ari:GetName()] then
|
||||
local name = ari:GetName()
|
||||
local newari = {} -- #AMMOTRUCK.data
|
||||
newari.name = name
|
||||
newari.group = ari
|
||||
newari.statusquo = AMMOTRUCK.State.IDLE
|
||||
newari.timestamp = timer.getAbsTime()
|
||||
newari.coordinate = ari:GetCoordinate()
|
||||
local hasammo = self:GetAmmoStatus(ari)
|
||||
--newari.ammo = ari:GetAmmunition()
|
||||
newari.ammo = hasammo
|
||||
self.targetlist[name] = newari
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:CheckTrucksAlive()
|
||||
self:T(self.lid .. " CheckTrucksAlive")
|
||||
local trucklist = self.trucklist
|
||||
for _,_truck in pairs(trucklist) do
|
||||
local truck = _truck -- #AMMOTRUCK.data
|
||||
if truck.group and truck.group:IsAlive() then
|
||||
-- everything fine
|
||||
else
|
||||
-- truck dead
|
||||
local tgtname = truck.targetname
|
||||
local targetdata = self:FindTarget(tgtname) -- #AMMOTRUCK.data
|
||||
if targetdata then
|
||||
if targetdata.statusquo ~= AMMOTRUCK.State.IDLE then
|
||||
targetdata.statusquo = AMMOTRUCK.State.IDLE
|
||||
end
|
||||
end
|
||||
self.trucklist[truck.name] = nil
|
||||
end
|
||||
end
|
||||
-- new arrivals?
|
||||
local trucktable = self.truckset:GetSetObjects() --#table
|
||||
for _,_truck in pairs(trucktable) do
|
||||
local truck = _truck -- Wrapper.Group#GROUP
|
||||
if truck and truck:IsAlive() and not self.trucklist[truck:GetName()] then
|
||||
local name = truck:GetName()
|
||||
local newtruck = {} -- #AMMOTRUCK.data
|
||||
newtruck.name = name
|
||||
newtruck.group = truck
|
||||
if self.hasarmygroup then
|
||||
-- is (not) already ARMYGROUP?
|
||||
if truck.ClassName and truck.ClassName == "GROUP" then
|
||||
local trucker = ARMYGROUP:New(truck)
|
||||
trucker:Activate()
|
||||
newtruck.group = trucker
|
||||
end
|
||||
end
|
||||
newtruck.statusquo = AMMOTRUCK.State.IDLE
|
||||
newtruck.timestamp = timer.getAbsTime()
|
||||
newtruck.coordinate = truck:GetCoordinate()
|
||||
newtruck.reloads = self.reloads or 5
|
||||
self.trucklist[name] = newtruck
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterStart(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
if ARMYGROUP and self.usearmygroup then
|
||||
self.hasarmygroup = true
|
||||
else
|
||||
self.hasarmygroup = false
|
||||
end
|
||||
if self.debug then
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("AMMOTRUCK")
|
||||
end
|
||||
self:CheckTargetsAlive()
|
||||
self:CheckTrucksAlive()
|
||||
self:__Monitor(-30)
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterMonitor(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:CheckTargetsAlive()
|
||||
self:CheckTrucksAlive()
|
||||
-- update ammo state
|
||||
local remunition = false
|
||||
local remunitionqueue = {}
|
||||
local waitingtargets = {}
|
||||
for _,_ari in pairs(self.targetlist) do
|
||||
local data = _ari -- #AMMOTRUCK.data
|
||||
if data.group and data.group:IsAlive() then
|
||||
data.ammo = self:GetAmmoStatus(data.group)
|
||||
data.timestamp = timer.getAbsTime()
|
||||
local text = string.format("Ari %s | Ammo %d | State %s",data.name,data.ammo,data.statusquo)
|
||||
self:T(text)
|
||||
if data.ammo <= self.ammothreshold and (data.statusquo == AMMOTRUCK.State.IDLE or data.statusquo == AMMOTRUCK.State.OUTOFAMMO) then
|
||||
-- add to remu queue
|
||||
data.statusquo = AMMOTRUCK.State.OUTOFAMMO
|
||||
remunitionqueue[#remunitionqueue+1] = data
|
||||
remunition = true
|
||||
elseif data.statusquo == AMMOTRUCK.State.WAITING then
|
||||
waitingtargets[#waitingtargets+1] = data
|
||||
end
|
||||
else
|
||||
self.targetlist[data.name] = nil
|
||||
end
|
||||
end
|
||||
-- sort trucks in buckets
|
||||
local idletrucks = {}
|
||||
local drivingtrucks = {}
|
||||
local unloadingtrucks = {}
|
||||
local arrivedtrucks = {}
|
||||
local returningtrucks = {}
|
||||
local found = false
|
||||
for _,_truckdata in pairs(self.trucklist) do
|
||||
local data = _truckdata -- #AMMOTRUCK.data
|
||||
if data.group and data.group:IsAlive() then
|
||||
-- check state
|
||||
local text = string.format("Truck %s | State %s",data.name,data.statusquo)
|
||||
self:T(text)
|
||||
if data.statusquo == AMMOTRUCK.State.IDLE then
|
||||
idletrucks[#idletrucks+1] = data
|
||||
found = true
|
||||
elseif data.statusquo == AMMOTRUCK.State.DRIVING then
|
||||
drivingtrucks[#drivingtrucks+1] = data
|
||||
elseif data.statusquo == AMMOTRUCK.State.ARRIVED then
|
||||
arrivedtrucks[#arrivedtrucks+1] = data
|
||||
elseif data.statusquo == AMMOTRUCK.State.UNLOADING then
|
||||
unloadingtrucks[#unloadingtrucks+1] = data
|
||||
elseif data.statusquo == AMMOTRUCK.State.RETURNING then
|
||||
returningtrucks[#returningtrucks+1] = data
|
||||
if data.reloads > 0 or data.reloads == -1 then
|
||||
idletrucks[#idletrucks+1] = data
|
||||
found = true
|
||||
end
|
||||
end
|
||||
else
|
||||
self.truckset[data.name] = nil
|
||||
end
|
||||
end
|
||||
-- see if we can/need route one
|
||||
local n=0
|
||||
if found and remunition then
|
||||
-- match
|
||||
--local match = false
|
||||
for _,_truckdata in pairs(idletrucks) do
|
||||
local truckdata = _truckdata -- #AMMOTRUCK.data
|
||||
local truckcoord = truckdata.group:GetCoordinate() -- Core.Point#COORDINATE
|
||||
for _,_aridata in pairs(remunitionqueue) do
|
||||
local aridata = _aridata -- #AMMOTRUCK.data
|
||||
local aricoord = aridata.coordinate
|
||||
local distance = truckcoord:Get2DDistance(aricoord)
|
||||
if distance <= self.remunidist and aridata.statusquo == AMMOTRUCK.State.OUTOFAMMO and n <= #idletrucks then
|
||||
n = n + 1
|
||||
aridata.statusquo = AMMOTRUCK.State.REQUESTED
|
||||
self:__RouteTruck(n*5,truckdata,aridata)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check driving trucks
|
||||
if #drivingtrucks > 0 then
|
||||
self:CheckDrivingTrucks(drivingtrucks)
|
||||
end
|
||||
|
||||
-- check arrived trucks
|
||||
if #arrivedtrucks > 0 then
|
||||
self:CheckArrivedTrucks(arrivedtrucks)
|
||||
end
|
||||
|
||||
-- check unloading trucks
|
||||
if #unloadingtrucks > 0 then
|
||||
self:CheckUnloadingTrucks(unloadingtrucks)
|
||||
end
|
||||
|
||||
-- check returningtrucks trucks
|
||||
if #returningtrucks > 0 then
|
||||
self:CheckReturningTrucks(returningtrucks)
|
||||
end
|
||||
|
||||
-- check waiting targets
|
||||
if #waitingtargets > 0 then
|
||||
self:CheckWaitingTargets(waitingtargets)
|
||||
end
|
||||
|
||||
self:__Monitor(self.monitor)
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #AMMOTRUCK.data Truckdata
|
||||
-- @param #AMMOTRUCK.data Aridata
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterRouteTruck(From, Event, To, Truckdata, Aridata)
|
||||
self:T({From, Event, To, Truckdata.name, Aridata.name})
|
||||
local truckdata = Truckdata -- #AMMOTRUCK.data
|
||||
local aridata = Aridata -- #AMMOTRUCK.data
|
||||
local tgtgrp = aridata.group
|
||||
local tgtzone = ZONE_GROUP:New(aridata.name,tgtgrp,30)
|
||||
local tgtcoord = tgtzone:GetRandomCoordinate(15)
|
||||
if self.hasarmygroup then
|
||||
local mission = AUFTRAG:NewONGUARD(tgtcoord)
|
||||
local oldmission = truckdata.group:GetMissionCurrent()
|
||||
if oldmission then oldmission:Cancel() end
|
||||
mission:SetTime(5)
|
||||
mission:SetTeleport(false)
|
||||
truckdata.group:AddMission(mission)
|
||||
elseif self.routeonroad then
|
||||
truckdata.group:RouteGroundOnRoad(tgtcoord,30)
|
||||
else
|
||||
truckdata.group:RouteGroundTo(tgtcoord,30)
|
||||
end
|
||||
truckdata.statusquo = AMMOTRUCK.State.DRIVING
|
||||
truckdata.targetgroup = tgtgrp
|
||||
truckdata.targetname = aridata.name
|
||||
truckdata.targetcoordinate = tgtcoord
|
||||
aridata.statusquo = AMMOTRUCK.State.WAITING
|
||||
aridata.timestamp = timer.getAbsTime()
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #AMMOTRUCK.data Truckdata
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterTruckUnloading(From, Event, To, Truckdata)
|
||||
local m = MESSAGE:New("Truck "..Truckdata.name.." unloading!",15,"AmmoTruck"):ToCoalitionIf(self.coalition,self.debug)
|
||||
local truck = Truckdata -- Functional.AmmoTruck#AMMOTRUCK.data
|
||||
local coord = truck.group:GetCoordinate()
|
||||
local heading = truck.group:GetHeading()
|
||||
heading = heading < 180 and (360-heading) or (heading - 180)
|
||||
local cid = self.coalition == coalition.side.BLUE and country.id.USA or country.id.RUSSIA
|
||||
cid = self.coalition == coalition.side.NEUTRAL and country.id.UN_PEACEKEEPERS or cid
|
||||
|
||||
local ammo = {}
|
||||
for i=1,5 do
|
||||
ammo[i] = SPAWNSTATIC:NewFromType("ammo_cargo","Cargos",cid)
|
||||
:InitCoordinate(coord:Translate((15+((i-1)*4)),heading))
|
||||
:Spawn(0,"AmmoCrate-"..math.random(1,10000))
|
||||
end
|
||||
|
||||
local function destroyammo(ammo)
|
||||
for _,_crate in pairs(ammo) do
|
||||
_crate:Destroy(false)
|
||||
end
|
||||
end
|
||||
|
||||
local scheduler = SCHEDULER:New(nil,destroyammo,{ammo},self.waitingtime)
|
||||
|
||||
-- one reload less
|
||||
if truck.reloads ~= -1 then
|
||||
truck.reloads = truck.reloads - 1
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #AMMOTRUCK.data Truck
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterTruckReturning(From, Event, To, Truck)
|
||||
self:T({From, Event, To, Truck.name})
|
||||
-- route home
|
||||
local truckdata = Truck -- #AMMOTRUCK.data
|
||||
local tgtzone = self.homezone
|
||||
local tgtcoord = tgtzone:GetRandomCoordinate()
|
||||
if self.hasarmygroup then
|
||||
local mission = AUFTRAG:NewONGUARD(tgtcoord)
|
||||
local oldmission = truckdata.group:GetMissionCurrent()
|
||||
if oldmission then oldmission:Cancel() end
|
||||
mission:SetTime(5)
|
||||
mission:SetTeleport(false)
|
||||
truckdata.group:AddMission(mission)
|
||||
elseif self.routeonroad then
|
||||
truckdata.group:RouteGroundOnRoad(tgtcoord,30,1,"Cone")
|
||||
else
|
||||
truckdata.group:RouteGroundTo(tgtcoord,30,"Cone",1)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #AMMOTRUCK self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AMMOTRUCK self
|
||||
function AMMOTRUCK:onafterStop(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
return self
|
||||
end
|
||||
@@ -45,7 +45,6 @@
|
||||
-- @field #table currentMove Holds the current commanded move, if there is one assigned.
|
||||
-- @field #number Nammo0 Initial amount total ammunition (shells+rockets+missiles) of the whole group.
|
||||
-- @field #number Nshells0 Initial amount of shells of the whole group.
|
||||
-- @field #number Narty0 Initial amount of artillery shells of the whole group.
|
||||
-- @field #number Nrockets0 Initial amount of rockets of the whole group.
|
||||
-- @field #number Nmissiles0 Initial amount of missiles of the whole group.
|
||||
-- @field #number Nukes0 Initial amount of tactical nukes of the whole group. Default is 0.
|
||||
@@ -416,7 +415,7 @@
|
||||
-- arty set, battery "Paladin Alpha", rearming place
|
||||
--
|
||||
-- Setting the rearming group is independent of the position of the mark. Just create one anywhere on the map and type
|
||||
-- arty set, battery "Mortar Bravo", rearming group "Ammo Truck M939"
|
||||
-- arty set, battery "Mortar Bravo", rearming group "Ammo Truck M818"
|
||||
-- Note that the name of the rearming group has to be given in quotation marks and spelt exactly as the group name defined in the mission editor.
|
||||
--
|
||||
-- ## Transporting
|
||||
@@ -454,7 +453,7 @@
|
||||
-- -- Creat a new ARTY object from a Paladin group.
|
||||
-- paladin=ARTY:New(GROUP:FindByName("Blue Paladin"))
|
||||
--
|
||||
-- -- Define a rearming group. This is a Transport M939 truck.
|
||||
-- -- Define a rearming group. This is a Transport M818 truck.
|
||||
-- paladin:SetRearmingGroup(GROUP:FindByName("Blue Ammo Truck"))
|
||||
--
|
||||
-- -- Set the max firing range. A Paladin unit has a range of 20 km.
|
||||
@@ -619,148 +618,63 @@ ARTY.WeaponType={
|
||||
}
|
||||
|
||||
--- Database of common artillery unit properties.
|
||||
-- @type ARTY.dbitem
|
||||
-- @field #string displayname Name displayed in ME.
|
||||
-- @field #number minrange Minimum firing range in meters.
|
||||
-- @field #number maxrange Maximum firing range in meters.
|
||||
-- @field #number reloadtime Reload time in seconds.
|
||||
|
||||
--- Database of common artillery unit properties.
|
||||
-- Table key is the "type name" and table value is and `ARTY.dbitem`.
|
||||
-- @type ARTY.db
|
||||
ARTY.db={
|
||||
["LeFH_18-40-105"] = {
|
||||
displayname = "FH LeFH-18 105mm", -- name displayed in the ME
|
||||
minrange = 500, -- min range (green circle) in meters
|
||||
maxrange = 10500, -- max range (red circle) in meters
|
||||
reloadtime = nil, -- reload time in seconds
|
||||
["2B11 mortar"] = { -- type "2B11 mortar"
|
||||
minrange = 500, -- correct?
|
||||
maxrange = 7000, -- 7 km
|
||||
reloadtime = 30, -- 30 sec
|
||||
},
|
||||
["M2A1-105"] = {
|
||||
displayname = "FH M2A1 105mm",
|
||||
minrange = 500,
|
||||
maxrange = 11500,
|
||||
reloadtime = nil,
|
||||
["SPH 2S1 Gvozdika"] = { -- type "SAU Gvozdika"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 15000, -- 15 km
|
||||
reloadtime = nil, -- unknown
|
||||
},
|
||||
["Pak40"] = {
|
||||
displayname = "FH Pak 40 75mm",
|
||||
minrange = 500,
|
||||
maxrange = 3000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["L118_Unit"] = {
|
||||
displayname = "L118 Light Artillery Gun",
|
||||
minrange = 500,
|
||||
maxrange = 17500,
|
||||
reloadtime = nil,
|
||||
["SPH 2S19 Msta"] = { --type "SAU Msta", alias "2S19 Msta"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 23500, -- 23.5 km
|
||||
reloadtime = nil, -- unknown
|
||||
},
|
||||
["Smerch"] = {
|
||||
displayname = "MLRS 9A52 Smerch CM 300mm",
|
||||
minrange = 20000,
|
||||
maxrange = 70000,
|
||||
reloadtime = 2160,
|
||||
["SPH 2S3 Akatsia"] = { -- type "SAU Akatsia", alias "2S3 Akatsia"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 17000, -- 17 km
|
||||
reloadtime = nil, -- unknown
|
||||
},
|
||||
["Smerch_HE"] = {
|
||||
displayname = "MLRS 9A52 Smerch HE 300mm",
|
||||
minrange = 20000,
|
||||
maxrange = 70000,
|
||||
reloadtime = 2160,
|
||||
["SPH 2S9 Nona"] = { --type "SAU 2-C9"
|
||||
minrange = 500, -- correct?
|
||||
maxrange = 7000, -- 7 km
|
||||
reloadtime = nil, -- unknown
|
||||
},
|
||||
["Uragan_BM-27"] = {
|
||||
displayname = "MLRS 9K57 Uragan BM-27 220mm",
|
||||
minrange = 11500,
|
||||
maxrange = 35800,
|
||||
reloadtime = 840,
|
||||
["SPH M109 Paladin"] = { -- type "M-109", alias "M109"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 22000, -- 22 km
|
||||
reloadtime = nil, -- unknown
|
||||
},
|
||||
["Grad-URAL"] = {
|
||||
displayname = "MLRS BM-21 Grad 122mm",
|
||||
minrange = 5000,
|
||||
maxrange = 19000,
|
||||
reloadtime = 420,
|
||||
["SpGH Dana"] = { -- type "SpGH_Dana"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 18700, -- 18.7 km
|
||||
reloadtime = nil, -- unknown
|
||||
},
|
||||
["HL_B8M1"] = {
|
||||
displayname = "MLRS HL with B8M1 80mm",
|
||||
minrange = 500,
|
||||
maxrange = 5000,
|
||||
reloadtime = nil,
|
||||
["MLRS BM-21 Grad"] = { --type "Grad-URAL", alias "MLRS BM-21 Grad"
|
||||
minrange = 5000, -- 5 km
|
||||
maxrange = 19000, -- 19 km
|
||||
reloadtime = 420, -- 7 min
|
||||
},
|
||||
["tt_B8M1"] = {
|
||||
displayname = "MLRS LC with B8M1 80mm",
|
||||
minrange = 500,
|
||||
maxrange = 5000,
|
||||
reloadtime = nil,
|
||||
["MLRS 9K57 Uragan BM-27"] = { -- type "Uragan_BM-27"
|
||||
minrange = 11500, -- 11.5 km
|
||||
maxrange = 35800, -- 35.8 km
|
||||
reloadtime = 840, -- 14 min
|
||||
},
|
||||
["MLRS"] = {
|
||||
displayname = "MLRS M270 227mm",
|
||||
minrange = 10000,
|
||||
maxrange = 32000,
|
||||
reloadtime = 540,
|
||||
["MLRS 9A52 Smerch"] = { -- type "Smerch"
|
||||
minrange = 20000, -- 20 km
|
||||
maxrange = 70000, -- 70 km
|
||||
reloadtime = 2160, -- 36 min
|
||||
},
|
||||
["2B11 mortar"] = {
|
||||
displayname = "Mortar 2B11 120mm",
|
||||
minrange = 500,
|
||||
maxrange = 7000,
|
||||
reloadtime = 30,
|
||||
["MLRS M270"] = { --type "MRLS", alias "M270 MRLS"
|
||||
minrange = 10000, -- 10 km
|
||||
maxrange = 32000, -- 32 km
|
||||
reloadtime = 540, -- 9 min
|
||||
},
|
||||
["PLZ05"] = {
|
||||
displayname = "PLZ-05",
|
||||
minrange = 500,
|
||||
maxrange = 23500,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SAU Gvozdika"] = {
|
||||
displayname = "SPH 2S1 Gvozdika 122mm",
|
||||
minrange = 300,
|
||||
maxrange = 15000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SAU Msta"] = {
|
||||
displayname = "SPH 2S19 Msta 152mm",
|
||||
minrange = 300,
|
||||
maxrange = 23500,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SAU Akatsia"] = {
|
||||
displayname = "SPH 2S3 Akatsia 152mm",
|
||||
minrange = 300,
|
||||
maxrange = 17000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SpGH_Dana"] = {
|
||||
displayname = "SPH Dana vz77 152mm",
|
||||
minrange = 300,
|
||||
maxrange = 18700,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["M-109"] = {
|
||||
displayname = "SPH M109 Paladin 155mm",
|
||||
minrange = 300,
|
||||
maxrange = 22000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["M12_GMC"] = {
|
||||
displayname = "SPH M12 GMC 155mm",
|
||||
minrange = 300,
|
||||
maxrange = 18200,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["Wespe124"] = {
|
||||
displayname = "SPH Sd.Kfz.124 Wespe 105mm",
|
||||
minrange = 300,
|
||||
maxrange = 7000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["T155_Firtina"] = {
|
||||
displayname = "SPH T155 Firtina 155mm",
|
||||
minrange = 300,
|
||||
maxrange = 41000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SAU 2-C9"] = {
|
||||
displayname = "SPM 2S9 Nona 120mm M",
|
||||
minrange = 500,
|
||||
maxrange = 7000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
}
|
||||
|
||||
--- Target.
|
||||
@@ -780,7 +694,7 @@ ARTY.db={
|
||||
|
||||
--- Arty script version.
|
||||
-- @field #string version
|
||||
ARTY.version="1.3.3"
|
||||
ARTY.version="1.3.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -793,7 +707,7 @@ ARTY.version="1.3.3"
|
||||
-- DONE: Add user defined rearm weapon types.
|
||||
-- DONE: Check if target is in range. Maybe this requires a data base with the ranges of all arty units. <solved by user function>
|
||||
-- DONE: Make ARTY move to rearming position.
|
||||
-- DONE: Check that right rearming vehicle is specified. Blue M939, Red Ural-375. Are there more? <user needs to know!>
|
||||
-- DONE: Check that right rearming vehicle is specified. Blue M818, Red Ural-375. Are there more? <user needs to know!>
|
||||
-- DONE: Check if ARTY group is still alive.
|
||||
-- DONE: Handle dead events.
|
||||
-- DONE: Abort firing task if no shooting event occured with 5(?) minutes. Something went wrong then. Min/max range for example.
|
||||
@@ -882,8 +796,8 @@ function ARTY:New(group, alias)
|
||||
-- Maximum speed in km/h.
|
||||
self.SpeedMax=group:GetSpeedMax()
|
||||
|
||||
-- Group is mobile or not (e.g. mortars). Some immobile units have a speed of 1 m/s = 3.6 km/h. So we check this number.
|
||||
if self.SpeedMax>3.6 then
|
||||
-- Group is mobile or not (e.g. mortars).
|
||||
if self.SpeedMax>1 then
|
||||
self.ismobile=true
|
||||
else
|
||||
self.ismobile=false
|
||||
@@ -1618,7 +1532,7 @@ end
|
||||
|
||||
--- Assign a group, which is responsible for rearming the ARTY group. If the group is too far away from the ARTY group it will be guided towards the ARTY group.
|
||||
-- @param #ARTY self
|
||||
-- @param Wrapper.Group#GROUP group Group that is supposed to rearm the ARTY group. For the blue coalition, this is often a unarmed M939 transport whilst for red an unarmed Ural-375 transport can be used.
|
||||
-- @param Wrapper.Group#GROUP group Group that is supposed to rearm the ARTY group. For the blue coalition, this is often a unarmed M818 transport whilst for red an unarmed Ural-375 transport can be used.
|
||||
-- @return self
|
||||
function ARTY:SetRearmingGroup(group)
|
||||
self:F({group=group})
|
||||
@@ -1973,7 +1887,7 @@ function ARTY:onafterStart(Controllable, From, Event, To)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
-- Get Ammo.
|
||||
self.Nammo0, self.Nshells0, self.Nrockets0, self.Nmissiles0, self.Narty0=self:GetAmmo(self.Debug)
|
||||
self.Nammo0, self.Nshells0, self.Nrockets0, self.Nmissiles0=self:GetAmmo(self.Debug)
|
||||
|
||||
-- Init nuclear explosion parameters if they were not set by user.
|
||||
if self.nukerange==nil then
|
||||
@@ -2008,7 +1922,7 @@ function ARTY:onafterStart(Controllable, From, Event, To)
|
||||
end
|
||||
|
||||
-- Check if we have and arty type that is in the DB.
|
||||
local _dbproperties=self:_CheckDB(self.Type)
|
||||
local _dbproperties=self:_CheckDB(self.DisplayName)
|
||||
self:T({dbproperties=_dbproperties})
|
||||
if _dbproperties~=nil then
|
||||
for property,value in pairs(_dbproperties) do
|
||||
@@ -2054,8 +1968,8 @@ function ARTY:onafterStart(Controllable, From, Event, To)
|
||||
text=text..string.format("Type = %s\n", self.Type)
|
||||
text=text..string.format("Display Name = %s\n", self.DisplayName)
|
||||
text=text..string.format("Number of units = %d\n", self.IniGroupStrength)
|
||||
text=text..string.format("Speed max = %.1f km/h\n", self.SpeedMax)
|
||||
text=text..string.format("Speed default = %.1f km/h\n", self.Speed)
|
||||
text=text..string.format("Speed max = %d km/h\n", self.SpeedMax)
|
||||
text=text..string.format("Speed default = %d km/h\n", self.Speed)
|
||||
text=text..string.format("Is mobile = %s\n", tostring(self.ismobile))
|
||||
text=text..string.format("Is cargo = %s\n", tostring(self.iscargo))
|
||||
text=text..string.format("Min range = %.1f km\n", self.minrange/1000)
|
||||
@@ -2179,7 +2093,7 @@ function ARTY:_StatusReport(display)
|
||||
end
|
||||
|
||||
-- Get Ammo.
|
||||
local Nammo, Nshells, Nrockets, Nmissiles, Narty=self:GetAmmo()
|
||||
local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo()
|
||||
local Nnukes=self.Nukes
|
||||
local Nillu=self.Nillu
|
||||
local Nsmoke=self.Nsmoke
|
||||
@@ -2192,7 +2106,7 @@ function ARTY:_StatusReport(display)
|
||||
text=text..string.format("Clock = %s\n", Clock)
|
||||
text=text..string.format("FSM state = %s\n", self:GetState())
|
||||
text=text..string.format("Total ammo count = %d\n", Nammo)
|
||||
text=text..string.format("Number of shells = %d\n", Narty)
|
||||
text=text..string.format("Number of shells = %d\n", Nshells)
|
||||
text=text..string.format("Number of rockets = %d\n", Nrockets)
|
||||
text=text..string.format("Number of missiles = %d\n", Nmissiles)
|
||||
text=text..string.format("Number of nukes = %d\n", Nnukes)
|
||||
@@ -2379,19 +2293,19 @@ function ARTY:OnEventShot(EventData)
|
||||
end
|
||||
|
||||
-- Get current ammo.
|
||||
local _nammo,_nshells,_nrockets,_nmissiles,_narty=self:GetAmmo()
|
||||
local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo()
|
||||
|
||||
-- Decrease available nukes because we just fired one.
|
||||
if self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes then
|
||||
self.Nukes=self.Nukes-1
|
||||
end
|
||||
|
||||
-- Decrease available illumination shells because we just fired one.
|
||||
-- Decrease available illuminatin shells because we just fired one.
|
||||
if self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells then
|
||||
self.Nillu=self.Nillu-1
|
||||
end
|
||||
|
||||
-- Decrease available smoke shells because we just fired one.
|
||||
-- Decrease available illuminatin shells because we just fired one.
|
||||
if self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells then
|
||||
self.Nsmoke=self.Nsmoke-1
|
||||
end
|
||||
@@ -2409,7 +2323,7 @@ function ARTY:OnEventShot(EventData)
|
||||
|
||||
-- Weapon type name for current target.
|
||||
local _weapontype=self:_WeaponTypeName(self.currentTarget.weapontype)
|
||||
self:T(self.lid..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d", self.groupname, _nammo, _narty, _nrockets, _nmissiles))
|
||||
self:T(self.lid..string.format("Group %s ammo: total=%d, shells=%d, rockets=%d, missiles=%d", self.groupname, _nammo, _nshells, _nrockets, _nmissiles))
|
||||
self:T(self.lid..string.format("Group %s uses weapontype %s for current target.", self.groupname, _weapontype))
|
||||
|
||||
-- Default switches for cease fire and relocation.
|
||||
@@ -2857,7 +2771,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
self:_EventFromTo("onafterStatus", Event, From, To)
|
||||
|
||||
-- Get ammo.
|
||||
local nammo, nshells, nrockets, nmissiles, narty=self:GetAmmo()
|
||||
local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.iscargo and self.cargogroup then
|
||||
@@ -2874,7 +2788,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, nammo, narty, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
|
||||
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, nammo, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
@@ -2957,19 +2871,20 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
if self.currentTarget then
|
||||
self:CeaseFire(self.currentTarget)
|
||||
end
|
||||
|
||||
if self:is("CombatReady") then
|
||||
-- Open fire on timed target.
|
||||
self:OpenFire(_timedTarget)
|
||||
end
|
||||
|
||||
-- Open fire on timed target.
|
||||
self:OpenFire(_timedTarget)
|
||||
|
||||
elseif _normalTarget then
|
||||
|
||||
if self:is("CombatReady") then
|
||||
-- Open fire on normal target.
|
||||
self:OpenFire(_normalTarget)
|
||||
end
|
||||
|
||||
-- Open fire on normal target.
|
||||
self:OpenFire(_normalTarget)
|
||||
|
||||
end
|
||||
|
||||
-- Get ammo.
|
||||
--local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
|
||||
-- Check if we have a target in the queue for which weapons are still available.
|
||||
local gotsome=false
|
||||
if #self.targets>0 then
|
||||
@@ -3130,14 +3045,14 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target)
|
||||
local range=Controllable:GetCoordinate():Get2DDistance(target.coord)
|
||||
|
||||
-- Get ammo.
|
||||
local Nammo, Nshells, Nrockets, Nmissiles, Narty=self:GetAmmo()
|
||||
local nfire=Narty
|
||||
local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo()
|
||||
local nfire=Nammo
|
||||
local _type="shots"
|
||||
if target.weapontype==ARTY.WeaponType.Auto then
|
||||
nfire=Nammo -- We take everything that is available
|
||||
nfire=Nammo
|
||||
_type="shots"
|
||||
elseif target.weapontype==ARTY.WeaponType.Cannon then
|
||||
nfire=Narty
|
||||
nfire=Nshells
|
||||
_type="shells"
|
||||
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
|
||||
nfire=self.Nukes
|
||||
@@ -3155,8 +3070,6 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target)
|
||||
nfire=Nmissiles
|
||||
_type="cruise missiles"
|
||||
end
|
||||
|
||||
--env.info(string.format("FF type=%s, Nrockets=%d, Nfire=%d target.nshells=%d", _type, Nrockets, nfire, target.nshells))
|
||||
|
||||
-- Adjust if less than requested ammo is left.
|
||||
target.nshells=math.min(target.nshells, nfire)
|
||||
@@ -3424,7 +3337,7 @@ function ARTY:_CheckRearmed()
|
||||
self:F2()
|
||||
|
||||
-- Get current ammo.
|
||||
local nammo,nshells,nrockets,nmissiles,narty=self:GetAmmo()
|
||||
local nammo,nshells,nrockets,nmissiles=self:GetAmmo()
|
||||
|
||||
-- Number of units still alive.
|
||||
local units=self.Controllable:GetUnits()
|
||||
@@ -3690,11 +3603,7 @@ function ARTY:_FireAtCoord(coord, radius, nshells, weapontype)
|
||||
if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then
|
||||
weapontype=ARTY.WeaponType.Cannon
|
||||
end
|
||||
|
||||
if group:HasTask() then
|
||||
group:ClearTasks()
|
||||
end
|
||||
|
||||
|
||||
-- Set ROE to weapon free.
|
||||
group:OptionROEOpenFire()
|
||||
|
||||
@@ -3705,7 +3614,7 @@ function ARTY:_FireAtCoord(coord, radius, nshells, weapontype)
|
||||
local fire=group:TaskFireAtPoint(vec2, radius, nshells, weapontype)
|
||||
|
||||
-- Execute task.
|
||||
group:SetTask(fire,1)
|
||||
group:SetTask(fire)
|
||||
end
|
||||
|
||||
--- Set task for attacking a group.
|
||||
@@ -3722,11 +3631,7 @@ function ARTY:_AttackGroup(target)
|
||||
if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then
|
||||
weapontype=ARTY.WeaponType.Cannon
|
||||
end
|
||||
|
||||
if group:HasTask() then
|
||||
group:ClearTasks()
|
||||
end
|
||||
|
||||
|
||||
-- Set ROE to weapon free.
|
||||
group:OptionROEOpenFire()
|
||||
|
||||
@@ -3737,7 +3642,7 @@ function ARTY:_AttackGroup(target)
|
||||
local fire=group:TaskAttackGroup(targetgroup, weapontype, AI.Task.WeaponExpend.ONE, 1)
|
||||
|
||||
-- Execute task.
|
||||
group:SetTask(fire,1)
|
||||
group:SetTask(fire)
|
||||
end
|
||||
|
||||
|
||||
@@ -3804,6 +3709,51 @@ function ARTY:_NuclearBlast(_coord)
|
||||
ignite(_fires)
|
||||
end
|
||||
|
||||
--[[
|
||||
local ZoneNuke=ZONE_RADIUS:New("Nukezone", _coord:GetVec2(), 2000)
|
||||
|
||||
-- Scan for Scenery objects.
|
||||
ZoneNuke:Scan(Object.Category.SCENERY)
|
||||
|
||||
-- Array with all possible hideouts, i.e. scenery objects in the vicinity of the group.
|
||||
local scenery={}
|
||||
|
||||
for SceneryTypeName, SceneryData in pairs(ZoneNuke:GetScannedScenery()) do
|
||||
for SceneryName, SceneryObject in pairs(SceneryData) do
|
||||
|
||||
local SceneryObject = SceneryObject -- Wrapper.Scenery#SCENERY
|
||||
|
||||
-- Position of the scenery object.
|
||||
local spos=SceneryObject:GetCoordinate()
|
||||
|
||||
-- Distance from group to impact point.
|
||||
local distance= spos:Get2DDistance(_coord)
|
||||
|
||||
-- Place markers on every possible scenery object.
|
||||
if self.Debug then
|
||||
local MarkerID=spos:MarkToAll(string.format("%s scenery object %s", self.Controllable:GetName(), SceneryObject:GetTypeName()))
|
||||
local text=string.format("%s scenery: %s, Coord %s", self.Controllable:GetName(), SceneryObject:GetTypeName(), SceneryObject:GetCoordinate():ToStringLLDMS())
|
||||
self:T2(SUPPRESSION.id..text)
|
||||
end
|
||||
|
||||
-- Add to table.
|
||||
table.insert(scenery, {object=SceneryObject, distance=distance})
|
||||
|
||||
--SceneryObject:Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort scenery wrt to distance from impact point.
|
||||
-- local _sort = function(a,b) return a.distance < b.distance end
|
||||
-- table.sort(scenery,_sort)
|
||||
|
||||
-- for _,object in pairs(scenery) do
|
||||
-- local sobject=object -- Wrapper.Scenery#SCENERY
|
||||
-- sobject:Destroy()
|
||||
-- end
|
||||
|
||||
]]
|
||||
|
||||
end
|
||||
|
||||
--- Route group to a certain point.
|
||||
@@ -3965,7 +3915,6 @@ end
|
||||
-- @return #number Number of shells the group has left.
|
||||
-- @return #number Number of rockets the group has left.
|
||||
-- @return #number Number of missiles the group has left.
|
||||
-- @return #number Number of artillery shells the group has left.
|
||||
function ARTY:GetAmmo(display)
|
||||
self:F3({display=display})
|
||||
|
||||
@@ -3979,7 +3928,6 @@ function ARTY:GetAmmo(display)
|
||||
local nshells=0
|
||||
local nrockets=0
|
||||
local nmissiles=0
|
||||
local nartyshells=0
|
||||
|
||||
-- Get all units.
|
||||
local units=self.Controllable:GetUnits()
|
||||
@@ -4082,8 +4030,7 @@ function ARTY:GetAmmo(display)
|
||||
|
||||
-- Add up all shells.
|
||||
nshells=nshells+Nammo
|
||||
local _,_,_,_,_,shells = unit:GetAmmunition()
|
||||
nartyshells=nartyshells+shells
|
||||
|
||||
-- Debug info.
|
||||
text=text..string.format("- %d shells of type %s\n", Nammo, _weaponName)
|
||||
|
||||
@@ -4129,7 +4076,7 @@ function ARTY:GetAmmo(display)
|
||||
-- Total amount of ammunition.
|
||||
nammo=nshells+nrockets+nmissiles
|
||||
|
||||
return nammo, nshells, nrockets, nmissiles, nartyshells
|
||||
return nammo, nshells, nrockets, nmissiles
|
||||
end
|
||||
|
||||
--- Returns a name of a missile category.
|
||||
@@ -4880,10 +4827,7 @@ function ARTY:_CheckShootingStarted()
|
||||
|
||||
-- Check if we waited long enough and no shot was fired.
|
||||
--if dt > self.WaitForShotTime and self.Nshots==0 then
|
||||
|
||||
self:T(string.format("dt = %d WaitTime = %d | shots = %d TargetShells = %d",dt,self.WaitForShotTime,self.Nshots,self.currentTarget.nshells))
|
||||
|
||||
if (dt > self.WaitForShotTime and self.Nshots==0) or (self.currentTarget.nshells <= self.Nshots) then --https://github.com/FlightControl-Master/MOOSE/issues/1356
|
||||
if dt > self.WaitForShotTime and (self.Nshots==0 or self.currentTarget.nshells >= self.Nshots) then --https://github.com/FlightControl-Master/MOOSE/issues/1356
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("%s, no shot event after %d seconds. Removing current target %s from list.", self.groupname, self.WaitForShotTime, name))
|
||||
@@ -4945,7 +4889,7 @@ end
|
||||
function ARTY:_CheckOutOfAmmo(targets)
|
||||
|
||||
-- Get current ammo.
|
||||
local _nammo,_nshells,_nrockets,_nmissiles,_narty=self:GetAmmo()
|
||||
local _nammo,_nshells,_nrockets,_nmissiles=self:GetAmmo()
|
||||
|
||||
-- Special weapon type requested ==> Check if corresponding ammo is empty.
|
||||
local _partlyoutofammo=false
|
||||
@@ -4957,7 +4901,7 @@ function ARTY:_CheckOutOfAmmo(targets)
|
||||
self:T(self.lid..string.format("Group %s, auto weapon requested for target %s but all ammo is empty.", self.groupname, Target.name))
|
||||
_partlyoutofammo=true
|
||||
|
||||
elseif Target.weapontype==ARTY.WeaponType.Cannon and _narty==0 then
|
||||
elseif Target.weapontype==ARTY.WeaponType.Cannon and _nshells==0 then
|
||||
|
||||
self:T(self.lid..string.format("Group %s, cannons requested for target %s but shells empty.", self.groupname, Target.name))
|
||||
_partlyoutofammo=true
|
||||
@@ -5001,14 +4945,14 @@ end
|
||||
function ARTY:_CheckWeaponTypeAvailable(target)
|
||||
|
||||
-- Get current ammo of group.
|
||||
local Nammo, Nshells, Nrockets, Nmissiles, Narty=self:GetAmmo()
|
||||
local Nammo, Nshells, Nrockets, Nmissiles=self:GetAmmo()
|
||||
|
||||
-- Check if enough ammo is there for the selected weapon type.
|
||||
local nfire=Nammo
|
||||
if target.weapontype==ARTY.WeaponType.Auto then
|
||||
nfire=Nammo
|
||||
elseif target.weapontype==ARTY.WeaponType.Cannon then
|
||||
nfire=Narty
|
||||
nfire=Nshells
|
||||
elseif target.weapontype==ARTY.WeaponType.TacticalNukes then
|
||||
nfire=self.Nukes
|
||||
elseif target.weapontype==ARTY.WeaponType.IlluminationShells then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,13 +52,11 @@
|
||||
-- @module Functional.CleanUp
|
||||
-- @image CleanUp_Airbases.JPG
|
||||
|
||||
---
|
||||
-- @type CLEANUP_AIRBASE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-)
|
||||
--- @type CLEANUP_AIRBASE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-)
|
||||
-- @field #map<#string,Wrapper.Airbase#AIRBASE> Airbases Map of Airbases.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
---
|
||||
-- @type CLEANUP_AIRBASE
|
||||
--- @type CLEANUP_AIRBASE
|
||||
-- @extends #CLEANUP_AIRBASE.__
|
||||
|
||||
--- Keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat.
|
||||
@@ -95,7 +93,7 @@ CLEANUP_AIRBASE = {
|
||||
-- @field #CLEANUP_AIRBASE.__
|
||||
CLEANUP_AIRBASE.__ = {}
|
||||
|
||||
-- @field #CLEANUP_AIRBASE.__.Airbases
|
||||
--- @field #CLEANUP_AIRBASE.__.Airbases
|
||||
CLEANUP_AIRBASE.__.Airbases = {}
|
||||
|
||||
--- Creates the main object which is handling the cleaning of the debris within the given Zone Names.
|
||||
@@ -242,8 +240,7 @@ function CLEANUP_AIRBASE.__:DestroyMissile( MissileObject )
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
-- @param #CLEANUP_AIRBASE self
|
||||
--- @param #CLEANUP_AIRBASE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function CLEANUP_AIRBASE.__:OnEventBirth( EventData )
|
||||
self:F( { EventData } )
|
||||
@@ -357,7 +354,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
|
||||
self:F({Event})
|
||||
|
||||
|
||||
if Event.IniDCSUnit and Event.IniUnit and Event.IniCategory == Object.Category.UNIT then
|
||||
if Event.IniDCSUnit and Event.IniCategory == Object.Category.UNIT then
|
||||
if self.CleanUpList[Event.IniDCSUnitName] == nil then
|
||||
if self:IsInAirbase( Event.IniUnit:GetVec2() ) then
|
||||
self:AddForCleanUp( Event.IniUnit, Event.IniDCSUnitName )
|
||||
@@ -365,7 +362,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
|
||||
end
|
||||
end
|
||||
|
||||
if Event.TgtDCSUnit and Event.TgtUnit and Event.TgtCategory == Object.Category.UNIT then
|
||||
if Event.TgtDCSUnit and Event.TgtCategory == Object.Category.UNIT then
|
||||
if self.CleanUpList[Event.TgtDCSUnitName] == nil then
|
||||
if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then
|
||||
self:AddForCleanUp( Event.TgtUnit, Event.TgtDCSUnitName )
|
||||
@@ -387,7 +384,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
|
||||
local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT
|
||||
local CleanUpGroupName = CleanUpListData.CleanUpGroupName
|
||||
|
||||
if CleanUpUnit and CleanUpUnit:IsAlive() ~= nil then
|
||||
if CleanUpUnit:IsAlive() ~= nil then
|
||||
|
||||
if self:IsInAirbase( CleanUpUnit:GetVec2() ) then
|
||||
|
||||
@@ -414,7 +411,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
|
||||
end
|
||||
end
|
||||
-- Clean Units which are waiting for a very long time in the CleanUpZone.
|
||||
if CleanUpUnit and (CleanUpUnit.GetPlayerName == nil or not CleanUpUnit:GetPlayerName()) then
|
||||
if CleanUpUnit and not CleanUpUnit:GetPlayerName() then
|
||||
local CleanUpUnitVelocity = CleanUpUnit:GetVelocityKMH()
|
||||
if CleanUpUnitVelocity < 1 then
|
||||
if CleanUpListData.CleanUpMoved then
|
||||
|
||||
@@ -1,687 +0,0 @@
|
||||
--- **Functional** - Manage and track client slots easily to add your own client-based menus and modules to.
|
||||
--
|
||||
-- The @{#CLIENTWATCH} class adds a simplified way to create scripts and menus for individual clients. Instead of creating large algorithms and juggling multiple event handlers, you can simply provide one or more prefixes to the class and use the callback functions on spawn, despawn, and any aircraft related events to script to your hearts content.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Find clients by prefixes or by providing a Wrapper.CLIENT object
|
||||
-- * Trigger functions when the client spawns and despawns
|
||||
-- * Create multiple client instances without overwriting event handlers between instances
|
||||
-- * More reliable aircraft lost events for when DCS thinks the aircraft id dead but a dead event fails to trigger
|
||||
-- * Easily manage clients spawned in dynamic slots
|
||||
--
|
||||
-- ====
|
||||
--
|
||||
-- ### Author: **Statua**
|
||||
--
|
||||
-- ### Contributions: **FlightControl**: Wrapper.CLIENT
|
||||
--
|
||||
-- ====
|
||||
-- @module Functional.ClientWatch
|
||||
-- @image clientwatch.jpg
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
--- CLIENTWATCH class
|
||||
-- @type CLIENTWATCH
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players.
|
||||
-- @field #string lid String for DCS log file.
|
||||
-- @field #number FilterCoalition If not nil, will only activate for aircraft of the given coalition value.
|
||||
-- @field #number FilterCategory If not nil, will only activate for aircraft of the given category value.
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
--- Manage and track client slots easily to add your own client-based menus and modules to.
|
||||
--
|
||||
-- ## Creating a new instance
|
||||
--
|
||||
-- To start, you must first create a new instance of the client manager and provide it with either a Wrapper.Client#CLIENT object, a string prefix of the unit name, or a table of string prefixes for unit names. These are used to capture the client unit when it spawns and apply your scripted functions to it. Only fixed wing and rotary wing aircraft controlled by players can be used by this class.
|
||||
-- **This will not work if the client aircraft is alive!**
|
||||
--
|
||||
-- ### Examples
|
||||
--
|
||||
-- -- Create an instance with a Wrapper.Client#CLIENT object
|
||||
-- local heliClient = CLIENT:FindByName('Rotary1-1')
|
||||
-- local clientInstance = CLIENTWATCH:New(heliClient)
|
||||
--
|
||||
-- -- Create an instance with part of the unit name in the Mission Editor
|
||||
-- local clientInstance = CLIENTWATCH:New("Rotary")
|
||||
--
|
||||
-- -- Create an instance using prefixes for a few units as well as a FARP name for any dynamic spawns coming out of it
|
||||
-- local clientInstance = CLIENTWATCH:New({"Rescue","UH-1H","FARP ALPHA"})
|
||||
--
|
||||
-- ## Applying functions and methods to client aircraft when they spawn
|
||||
--
|
||||
-- Once the instance is created, it will watch for birth events. If the unit name of the client aircraft matches the one provided in the instance, the callback method @{#CLIENTWATCH:OnAfterSpawn}() can be used to apply functions and methods to the client object.
|
||||
--
|
||||
-- In the OnAfterSpawn() callback method are four values. From, Event, To, and ClientObject. From,Event,To are standard FSM strings for the state changes. ClientObject is where the magic happens. This is a special object which you can use to access all the data of the client aircraft. The following entries in ClientObject are available for you to use:
|
||||
--
|
||||
-- * **ClientObject.Unit**: The Moose @{Wrapper.Unit#UNIT} of the client aircraft
|
||||
-- * **ClientObject.Group**: The Moose @{Wrapper.Group#GRUP} of the client aircraft
|
||||
-- * **ClientObject.Client**: The Moose @{Wrapper.Client#CLIENT} of the client aircraft
|
||||
-- * **ClientObject.PlayerName**: A #string of the player controlling the aircraft
|
||||
-- * **ClientObject.UnitName**: A #string of the client aircraft unit.
|
||||
-- * **ClientObject.GroupName**: A #string of the client aircraft group.
|
||||
--
|
||||
-- ### Examples
|
||||
--
|
||||
-- -- Create an instance with a client unit prefix and send them a message when they spawn
|
||||
-- local clientInstance = CLIENTWATCH:New("Rotary")
|
||||
-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject,EventData)
|
||||
-- MESSAGE:New("Welcome to your aircraft!",10):ToUnit(ClientObject.Unit)
|
||||
-- end
|
||||
--
|
||||
-- ## Using event callbacks
|
||||
--
|
||||
-- In a normal setting, you can only use a callback function for a specific option in one location. If you have multiple scripts that rely on the same callback from the same object, this can get quite messy. With the ClientWatch module, these callbacks are isolated t the instances and therefore open the possibility to use many instances with the same callback doing different things. ClientWatch instances subscribe to all events that are applicable to player controlled aircraft and provides callbacks for each, forwarding the EventData in the callback function.
|
||||
--
|
||||
-- The following event callbacks can be used inside the OnAfterSpawn() callback:
|
||||
--
|
||||
-- * **:OnAfterDespawn(From,Event,To)**: Triggers whenever DCS no longer sees the aircraft as 'alive'. No event data is given in this callback as it is derived from other events
|
||||
-- * **:OnAfterHit(From,Event,To,EventData)**: Triggers every time the aircraft takes damage or is struck by a weapon/explosion
|
||||
-- * **:OnAfterKill(From,Event,To,EventData)**: Triggers after the aircraft kills something with its weapons
|
||||
-- * **:OnAfterScore(From,Event,To,EventData)**: Triggers after accumulating score
|
||||
-- * **:OnAfterShot(From,Event,To,EventData)**: Triggers after a single-shot weapon is released
|
||||
-- * **:OnAfterShootingStart(From,Event,To,EventData)**: Triggers when an automatic weapon begins firing
|
||||
-- * **:OnAfterShootingEnd(From,Event,To,EventData)**: Triggers when an automatic weapon stops firing
|
||||
-- * **:OnAfterLand(From,Event,To,EventData)**: Triggers when an aircraft transitions from being airborne to on the ground
|
||||
-- * **:OnAfterTakeoff(From,Event,To,EventData)**: Triggers when an aircraft transitions from being on the ground to airborne
|
||||
-- * **:OnAfterRunwayTakeoff(From,Event,To,EventData)**: Triggers after lifting off from a runway
|
||||
-- * **:OnAfterRunwayTouch(From,Event,To,EventData)**: Triggers when an aircraft's gear makes contact with a runway
|
||||
-- * **:OnAfterRefueling(From,Event,To,EventData)**: Triggers when an aircraft begins taking on fuel
|
||||
-- * **:OnAfterRefuelingStop(From,Event,To,EventData)**: Triggers when an aircraft stops taking on fuel
|
||||
-- * **:OnAfterPlayerLeaveUnit(From,Event,To,EventData)**: Triggers when a player leaves an operational aircraft
|
||||
-- * **:OnAfterCrash(From,Event,To,EventData)**: Triggers when an aircraft is destroyed (may fail to trigger if the aircraft is only partially destroyed)
|
||||
-- * **:OnAfterDead(From,Event,To,EventData)**: Triggers when an aircraft is considered dead (may fail to trigger if the aircraft was partially destroyed first)
|
||||
-- * **:OnAfterPilotDead(From,Event,To,EventData)**: Triggers when the pilot is killed (may fail to trigger if the aircraft was partially destroyed first)
|
||||
-- * **:OnAfterUnitLost(From,Event,To,EventData)**: Triggers when an aircraft is lost for any reason (may fail to trigger if the aircraft was partially destroyed first)
|
||||
-- * **:OnAfterEjection(From,Event,To,EventData)**: Triggers when a pilot ejects from an aircraft
|
||||
-- * **:OnAfterHumanFailure(From,Event,To,EventData)**: Triggers when an aircraft or system is damaged from any source or action by the player
|
||||
-- * **:OnAfterHumanAircraftRepairStart(From,Event,To,EventData)**: Triggers when an aircraft repair is started
|
||||
-- * **:OnAfterHumanAircraftRepairFinish(From,Event,To,EventData)**: Triggers when an aircraft repair is completed
|
||||
-- * **:OnAfterEngineStartup(From,Event,To,EventData)**: Triggers when the engine enters what DCS considers to be a started state. Parameters vary by aircraft
|
||||
-- * **:OnAfterEngineShutdown(From,Event,To,EventData)**: Triggers when the engine enters what DCS considers to be a stopped state. Parameters vary by aircraft
|
||||
-- * **:OnAfterWeaponAdd(From,Event,To,EventData)**: Triggers when an item is added to an aircraft's payload
|
||||
-- * **:OnAfterWeaponDrop(From,Event,To,EventData)**: Triggers when an item is jettisoned or dropped from an aircraft (unconfirmed)
|
||||
-- * **:OnAfterWeaponRearm(From,Event,To,EventData)**: Triggers when an item with internal supply is restored (unconfirmed)
|
||||
--
|
||||
-- ### Examples
|
||||
--
|
||||
-- -- Show a message to player when they take damage from a weapon
|
||||
-- local clientInstance = CLIENTWATCH:New("Rotary")
|
||||
-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject,EventData)
|
||||
-- function ClientObject:OnAfterHit(From,Event,To,EventData)
|
||||
-- local typeShooter = EventData.IniTypeName
|
||||
-- local nameWeapon = EventData.weapon_name
|
||||
-- MESSAGE:New("A "..typeShooter.." hit you with a "..nameWeapon,20):ToUnit(ClientObject.Unit)
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- @field #CLIENTWATCH
|
||||
CLIENTWATCH = {}
|
||||
CLIENTWATCH.ClassName = "CLIENTWATCH"
|
||||
CLIENTWATCH.Debug = false
|
||||
CLIENTWATCH.DebugEventData = false
|
||||
CLIENTWATCH.lid = nil
|
||||
|
||||
-- @type CLIENTWATCHTools
|
||||
-- @field #table Unit Wrapper.UNIT of the cient object
|
||||
-- @field #table Group Wrapper.GROUP of the cient object
|
||||
-- @field #table Client Wrapper.CLIENT of the cient object
|
||||
-- @field #string PlayerName Name of the player controlling the client object
|
||||
-- @field #string UnitName Name of the unit that is the client object
|
||||
-- @field #string GroupName Name of the group the client object belongs to
|
||||
CLIENTWATCHTools = {}
|
||||
|
||||
--- CLIENTWATCH version
|
||||
-- @field #string version
|
||||
CLIENTWATCH.version="1.0.1"
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Creates a new instance of CLIENTWATCH to add scripts to. Can be used multiple times with the same client/prefixes if you need it for multiple scripts.
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #string Will watch for clients whos UNIT NAME or GROUP NAME matches part of the #string as a prefix.
|
||||
-- @param #table Put strings in a table to use multiple prefixes for the above method.
|
||||
-- @param Wrapper.Client#CLIENT Provide a Moose CLIENT object to apply to that specific aircraft slot (static slots only!)
|
||||
-- @param #nil Leave blank to activate for ALL CLIENTS
|
||||
-- @return #CLIENTWATCH self
|
||||
function CLIENTWATCH:New(client)
|
||||
--Init FSM
|
||||
local self=BASE:Inherit(self, FSM:New())
|
||||
self:SetStartState( "Idle" )
|
||||
self:AddTransition( "*", "Spawn", "*" )
|
||||
|
||||
self.FilterCoalition = nil
|
||||
self.FilterCategory = nil
|
||||
|
||||
--- User function for OnAfter "Spawn" event.
|
||||
-- @function [parent=#CLIENTWATCH] OnAfterSpawn
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #table clientObject Custom object that handles events and stores Moose object data. See top documentation for more details.
|
||||
-- @param #table eventdata Data from EVENTS.Birth.
|
||||
|
||||
--Set up spawn tracking
|
||||
if not client then
|
||||
if self.Debug then self:I({"New client instance created. ClientType = All clients"}) end
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
elseif type(client) == "table" or type(client) == "string" then
|
||||
if type(client) == "table" then
|
||||
|
||||
--CLIENT TABLE
|
||||
if client.ClassName == "CLIENT" then
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Wrapper.CLIENT",client}) end
|
||||
self.ClientName = client:GetName()
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if self.ClientName == eventdata.IniUnitName then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--STRING TABLE
|
||||
else
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Multiple Prefixes",client}) end
|
||||
local tableValid = true
|
||||
for _,entry in pairs(client) do
|
||||
if type(entry) ~= "string" then
|
||||
tableValid = false
|
||||
self:E({"The base handler failed to start because at least one entry in param1's table is not a string!",InvalidEntry = entry})
|
||||
return nil
|
||||
end
|
||||
end
|
||||
if tableValid then
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
for _,entry in pairs(client) do
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if string.match(eventdata.IniUnitName,entry) or string.match(eventdata.IniGroupName,entry) then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Single Prefix",client}) end
|
||||
|
||||
--SOLO STRING
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if string.match(eventdata.IniUnitName,client) or string.match(eventdata.IniGroupName,client) then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E({"The base handler failed to start because param1 is not a CLIENT object or a prefix string!",param1 = client})
|
||||
return nil
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Filter out all clients not belonging to the provided coalition
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #number Coalition number (1 = red, 2 = blue)
|
||||
-- @param #string Coalition string ('red' or 'blue')
|
||||
function CLIENTWATCH:FilterByCoalition(value)
|
||||
if value == 1 or value == "red" then
|
||||
self.FilterCoalition = 1
|
||||
else
|
||||
self.FilterCoalition = 2
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Filter out all clients that are not of the given category
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #number Category number (0 = airplane, 1 = helicopter)
|
||||
-- @param #string Category string ('airplane' or 'helicopter')
|
||||
function CLIENTWATCH:FilterByCategory(value)
|
||||
if value == 1 or value == "helicopter" then
|
||||
self.FilterCategory = 1
|
||||
else
|
||||
self.FilterCategory = 0
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Internal function for creating a new client on birth. Do not use!!!.
|
||||
-- @param #CLIENTWATCHTools self
|
||||
-- @param #EVENTS.Birth EventData
|
||||
-- @return #CLIENTWATCHTools self
|
||||
function CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
--Init FSM
|
||||
local self=BASE:Inherit(self, FSM:New())
|
||||
self:SetStartState( "Alive" )
|
||||
self:AddTransition( "Alive", "Despawn", "Dead" )
|
||||
|
||||
self.Unit = eventdata.IniUnit
|
||||
self.Group = self.Unit:GetGroup()
|
||||
self.Client = self.Unit:GetClient()
|
||||
self.PlayerName = self.Unit:GetPlayerName()
|
||||
self.UnitName = self.Unit:GetName()
|
||||
self.GroupName = self.Group:GetName()
|
||||
|
||||
--Event events
|
||||
self:AddTransition( "*", "Hit", "*" )
|
||||
self:AddTransition( "*", "Kill", "*" )
|
||||
self:AddTransition( "*", "Score", "*" )
|
||||
self:AddTransition( "*", "Shot", "*" )
|
||||
self:AddTransition( "*", "ShootingStart", "*" )
|
||||
self:AddTransition( "*", "ShootingEnd", "*" )
|
||||
self:AddTransition( "*", "Land", "*" )
|
||||
self:AddTransition( "*", "Takeoff", "*" )
|
||||
self:AddTransition( "*", "RunwayTakeoff", "*" )
|
||||
self:AddTransition( "*", "RunwayTouch", "*" )
|
||||
self:AddTransition( "*", "Refueling", "*" )
|
||||
self:AddTransition( "*", "RefuelingStop", "*" )
|
||||
self:AddTransition( "*", "PlayerLeaveUnit", "*" )
|
||||
self:AddTransition( "*", "Crash", "*" )
|
||||
self:AddTransition( "*", "Dead", "*" )
|
||||
self:AddTransition( "*", "PilotDead", "*" )
|
||||
self:AddTransition( "*", "UnitLost", "*" )
|
||||
self:AddTransition( "*", "Ejection", "*" )
|
||||
self:AddTransition( "*", "HumanFailure", "*" )
|
||||
self:AddTransition( "*", "HumanAircraftRepairFinish", "*" )
|
||||
self:AddTransition( "*", "HumanAircraftRepairStart", "*" )
|
||||
self:AddTransition( "*", "EngineShutdown", "*" )
|
||||
self:AddTransition( "*", "EngineStartup", "*" )
|
||||
self:AddTransition( "*", "WeaponAdd", "*" )
|
||||
self:AddTransition( "*", "WeaponDrop", "*" )
|
||||
self:AddTransition( "*", "WeaponRearm", "*" )
|
||||
|
||||
--Event Handlers
|
||||
self:HandleEvent( EVENTS.Hit )
|
||||
self:HandleEvent( EVENTS.Kill )
|
||||
self:HandleEvent( EVENTS.Score )
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
self:HandleEvent( EVENTS.ShootingStart )
|
||||
self:HandleEvent( EVENTS.ShootingEnd )
|
||||
self:HandleEvent( EVENTS.Land )
|
||||
self:HandleEvent( EVENTS.Takeoff )
|
||||
self:HandleEvent( EVENTS.RunwayTakeoff )
|
||||
self:HandleEvent( EVENTS.RunwayTouch )
|
||||
self:HandleEvent( EVENTS.Refueling )
|
||||
self:HandleEvent( EVENTS.RefuelingStop )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit )
|
||||
self:HandleEvent( EVENTS.Crash )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
self:HandleEvent( EVENTS.PilotDead )
|
||||
self:HandleEvent( EVENTS.UnitLost )
|
||||
self:HandleEvent( EVENTS.Ejection )
|
||||
self:HandleEvent( EVENTS.HumanFailure )
|
||||
self:HandleEvent( EVENTS.HumanAircraftRepairFinish )
|
||||
self:HandleEvent( EVENTS.HumanAircraftRepairStart )
|
||||
self:HandleEvent( EVENTS.EngineShutdown )
|
||||
self:HandleEvent( EVENTS.EngineStartup )
|
||||
self:HandleEvent( EVENTS.WeaponAdd )
|
||||
self:HandleEvent( EVENTS.WeaponDrop )
|
||||
self:HandleEvent( EVENTS.WeaponRearm )
|
||||
|
||||
function self:OnEventHit(EventData)
|
||||
if EventData.TgtUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered hit event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Hit(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventKill(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered kill event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Kill(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventScore(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered score event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Score(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShot(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shot event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Shot(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShootingStart(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shooting start event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:ShootingStart(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShootingEnd(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shooting end event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:ShootingEnd(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventLand(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered land event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Land(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventTakeoff(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered takeoff event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Takeoff(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRunwayTakeoff(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered runway takeoff event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RunwayTakeoff(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRunwayTouch(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered runway touch event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RunwayTouch(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRefueling(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered refueling event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Refueling(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRefuelingStop(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered refueling event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RefuelingStop(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventPlayerLeaveUnit(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered leave unit event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:PlayerLeaveUnit(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventCrash(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered crash event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Crash(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventDead(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered dead event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Dead(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventPilotDead(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered pilot dead event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:PilotDead(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventUnitLost(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered unit lost event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:UnitLost(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventEjection(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered ejection event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Ejection(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventHumanFailure(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered human failure event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanFailure(EventData)
|
||||
if not self.Unit:IsAlive() then
|
||||
self._deadRoutine()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventHumanAircraftRepairFinish(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered repair finished event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanAircraftRepairFinish(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventHumanAircraftRepairStart(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered repair start event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanAircraftRepairStart(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventEngineShutdown(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered engine shutdown event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:EngineShutdown(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventEngineStartup(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered engine startup event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:EngineStartup(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponAdd(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon add event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponAdd(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponDrop(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon drop event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponDrop(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponRearm(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon rearm event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponRearm(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--Fallback timer
|
||||
self.FallbackTimer = TIMER:New(function()
|
||||
if not self.Unit:IsAlive() then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client is registered as dead without an event trigger. Running fallback dead routine.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
end
|
||||
self._deadRoutine()
|
||||
end
|
||||
end)
|
||||
self.FallbackTimer:Start(5,5)
|
||||
|
||||
--Stop event handlers and trigger Despawn
|
||||
function self._deadRoutine()
|
||||
if clientWatchDebug then self:I({"Client dead routine triggered. Shutting down tracking...",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) end
|
||||
self:UnHandleEvent( EVENTS.Hit )
|
||||
self:UnHandleEvent( EVENTS.Kill )
|
||||
self:UnHandleEvent( EVENTS.Score )
|
||||
self:UnHandleEvent( EVENTS.Shot )
|
||||
self:UnHandleEvent( EVENTS.ShootingStart )
|
||||
self:UnHandleEvent( EVENTS.ShootingEnd )
|
||||
self:UnHandleEvent( EVENTS.Land )
|
||||
self:UnHandleEvent( EVENTS.Takeoff )
|
||||
self:UnHandleEvent( EVENTS.RunwayTakeoff )
|
||||
self:UnHandleEvent( EVENTS.RunwayTouch )
|
||||
self:UnHandleEvent( EVENTS.Refueling )
|
||||
self:UnHandleEvent( EVENTS.RefuelingStop )
|
||||
self:UnHandleEvent( EVENTS.PlayerLeaveUnit )
|
||||
self:UnHandleEvent( EVENTS.Crash )
|
||||
self:UnHandleEvent( EVENTS.Dead )
|
||||
self:UnHandleEvent( EVENTS.PilotDead )
|
||||
self:UnHandleEvent( EVENTS.UnitLost )
|
||||
self:UnHandleEvent( EVENTS.Ejection )
|
||||
self:UnHandleEvent( EVENTS.HumanFailure )
|
||||
self:UnHandleEvent( EVENTS.HumanAircraftRepairFinish )
|
||||
self:UnHandleEvent( EVENTS.HumanAircraftRepairStart )
|
||||
self:UnHandleEvent( EVENTS.EngineShutdown )
|
||||
self:UnHandleEvent( EVENTS.EngineStartup )
|
||||
self:UnHandleEvent( EVENTS.WeaponAdd )
|
||||
self:UnHandleEvent( EVENTS.WeaponDrop )
|
||||
self:UnHandleEvent( EVENTS.WeaponRearm )
|
||||
self.FallbackTimer:Stop()
|
||||
self:Despawn()
|
||||
end
|
||||
|
||||
self:I({"Detected client spawn and applied internal functions and events.", PlayerName = self.PlayerName, UnitName = self.UnitName, GroupName = self.GroupName})
|
||||
return self
|
||||
end
|
||||
@@ -184,7 +184,7 @@
|
||||
|
||||
do -- DESIGNATE
|
||||
|
||||
-- @type DESIGNATE
|
||||
--- @type DESIGNATE
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
|
||||
--- Manage the designation of detected targets.
|
||||
@@ -525,7 +525,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
self.FlashStatusMenu[AttackGroup] = FlashMenu
|
||||
end
|
||||
@@ -554,7 +554,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
self.FlashDetectionMessage[AttackGroup] = FlashDetectionMessage
|
||||
end
|
||||
@@ -826,7 +826,7 @@ do -- DESIGNATE
|
||||
-- This Detection is obsolete, remove from the designate scope
|
||||
self.Designating[DesignateIndex] = nil
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
if AttackGroup:IsAlive() == true then
|
||||
local DetectionText = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
|
||||
@@ -903,7 +903,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
-- @param Wrapper.Group#GROUP GroupReport
|
||||
--- @param Wrapper.Group#GROUP GroupReport
|
||||
function( AttackGroup )
|
||||
|
||||
if self.FlashStatusMenu[AttackGroup] or ( MenuAttackGroup and ( AttackGroup:GetName() == MenuAttackGroup:GetName() ) ) then
|
||||
@@ -1060,7 +1060,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
-- @param Wrapper.Group#GROUP GroupReport
|
||||
--- @param Wrapper.Group#GROUP GroupReport
|
||||
function( AttackGroup )
|
||||
|
||||
self:ScheduleOnce( Delay, self.SetMenu, self, AttackGroup )
|
||||
@@ -1198,7 +1198,7 @@ do -- DESIGNATE
|
||||
--local ReportTypes = REPORT:New()
|
||||
--local ReportLaserCodes = REPORT:New()
|
||||
|
||||
--TargetSetUnit:Flush( self )
|
||||
TargetSetUnit:Flush( self )
|
||||
|
||||
--self:F( { Recces = self.Recces } )
|
||||
for TargetUnit, RecceData in pairs( self.Recces ) do
|
||||
@@ -1229,12 +1229,10 @@ do -- DESIGNATE
|
||||
end
|
||||
end
|
||||
|
||||
if TargetSetUnit == nil then return end
|
||||
|
||||
if self.AutoLase or ( not self.AutoLase and ( self.LaseStart + Duration >= timer.getTime() ) ) then
|
||||
|
||||
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
|
||||
-- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( TargetUnit )
|
||||
|
||||
self:F( { TargetUnit = TargetUnit:GetName() } )
|
||||
@@ -1255,7 +1253,7 @@ do -- DESIGNATE
|
||||
|
||||
local RecceUnit = UnitData -- Wrapper.Unit#UNIT
|
||||
local RecceUnitDesc = RecceUnit:GetDesc()
|
||||
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )x
|
||||
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )
|
||||
|
||||
if RecceUnit:IsLasing() == false then
|
||||
--self:F( { IsDetected = RecceUnit:IsDetected( TargetUnit ), IsLOS = RecceUnit:IsLOS( TargetUnit ) } )
|
||||
@@ -1277,10 +1275,9 @@ do -- DESIGNATE
|
||||
local Spot = RecceUnit:LaseUnit( TargetUnit, LaserCode, Duration )
|
||||
local AttackSet = self.AttackSet
|
||||
local DesignateName = self.DesignateName
|
||||
local typename = TargetUnit:GetTypeName()
|
||||
|
||||
function Spot:OnAfterDestroyed( From, Event, To )
|
||||
self.Recce:MessageToSetGroup( "Target " ..typename .. " destroyed. " .. TargetSetUnit:CountAlive() .. " targets left.",
|
||||
self.Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() .. " destroyed. " .. TargetSetUnit:Count() .. " targets left.",
|
||||
5, AttackSet, self.DesignateName )
|
||||
end
|
||||
|
||||
@@ -1288,7 +1285,7 @@ do -- DESIGNATE
|
||||
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
|
||||
MarkingCount = MarkingCount + 1
|
||||
local TargetUnitType = TargetUnit:GetTypeName()
|
||||
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnitType .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
|
||||
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
|
||||
10, self.AttackSet, DesignateName )
|
||||
if not MarkedTypes[TargetUnitType] then
|
||||
MarkedTypes[TargetUnitType] = true
|
||||
@@ -1395,7 +1392,7 @@ do -- DESIGNATE
|
||||
local MarkedCount = 0
|
||||
|
||||
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
|
||||
-- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( SmokeUnit )
|
||||
|
||||
if MarkedCount < self.MaximumMarkings then
|
||||
@@ -1460,10 +1457,9 @@ do -- DESIGNATE
|
||||
-- @param #DESIGNATE self
|
||||
-- @return #DESIGNATE
|
||||
function DESIGNATE:onafterDoneSmoking( From, Event, To, Index )
|
||||
if self.Designating[Index] ~= nil then
|
||||
self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" )
|
||||
self:SetDesignateMenu()
|
||||
end
|
||||
|
||||
self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" )
|
||||
self:SetDesignateMenu()
|
||||
end
|
||||
|
||||
--- DoneIlluminating
|
||||
@@ -1476,3 +1472,5 @@ do -- DESIGNATE
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -545,7 +545,7 @@ do -- DETECTION_BASE
|
||||
-- @param #string To The To State string.
|
||||
function DETECTION_BASE:onafterDetect( From, Event, To )
|
||||
|
||||
local DetectDelay = 0.15
|
||||
local DetectDelay = 0.1
|
||||
self.DetectionCount = 0
|
||||
self.DetectionRun = 0
|
||||
self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table
|
||||
@@ -595,8 +595,7 @@ do -- DETECTION_BASE
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---
|
||||
|
||||
-- @param #DETECTION_BASE self
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
@@ -605,7 +604,7 @@ do -- DETECTION_BASE
|
||||
-- @param #number DetectionTimeStamp Time stamp of detection event.
|
||||
function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
|
||||
|
||||
self:T( { DetectedObjects = self.DetectedObjects } )
|
||||
-- self:F( { DetectedObjects = self.DetectedObjects } )
|
||||
|
||||
self.DetectionRun = self.DetectionRun + 1
|
||||
|
||||
@@ -613,14 +612,14 @@ do -- DETECTION_BASE
|
||||
|
||||
if Detection and Detection:IsAlive() then
|
||||
|
||||
self:T( { "DetectionGroup is Alive", Detection:GetName() } )
|
||||
-- self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } )
|
||||
|
||||
local DetectionGroupName = Detection:GetName()
|
||||
local DetectionUnit = Detection:GetFirstUnitAlive()
|
||||
local DetectionUnit = Detection:GetUnit( 1 )
|
||||
|
||||
local DetectedUnits = {}
|
||||
|
||||
local DetectedTargets = DetectionUnit:GetDetectedTargets(
|
||||
local DetectedTargets = Detection:GetDetectedTargets(
|
||||
self.DetectVisual,
|
||||
self.DetectOptical,
|
||||
self.DetectRadar,
|
||||
@@ -629,30 +628,28 @@ do -- DETECTION_BASE
|
||||
self.DetectDLINK
|
||||
)
|
||||
|
||||
--self:T( { DetectedTargets = DetectedTargets } )
|
||||
--self:T(UTILS.PrintTableToLog(DetectedTargets))
|
||||
|
||||
|
||||
for DetectionObjectID, Detection in pairs( DetectedTargets or {}) do
|
||||
self:F( { DetectedTargets = DetectedTargets } )
|
||||
|
||||
for DetectionObjectID, Detection in pairs( DetectedTargets ) do
|
||||
local DetectedObject = Detection.object -- DCS#Object
|
||||
|
||||
if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then -- and ( DetectedObject:getCategory() == Object.Category.UNIT or DetectedObject:getCategory() == Object.Category.STATIC ) then
|
||||
local DetectedObjectName = DetectedObject:getName()
|
||||
if not self.DetectedObjects[DetectedObjectName] then
|
||||
self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {}
|
||||
self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName
|
||||
self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName
|
||||
self.DetectedObjects[DetectedObjectName].Object = DetectedObject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects or {}) do
|
||||
for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects ) do
|
||||
|
||||
local DetectedObject = DetectedObjectData.Object
|
||||
|
||||
if DetectedObject:isExist() then
|
||||
|
||||
local TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected(
|
||||
local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected(
|
||||
DetectedObject,
|
||||
self.DetectVisual,
|
||||
self.DetectOptical,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
do -- DETECTION_ZONES
|
||||
|
||||
-- @type DETECTION_ZONES
|
||||
--- @type DETECTION_ZONES
|
||||
-- @field DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target.
|
||||
-- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Core.Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange.
|
||||
-- @extends Functional.Detection#DETECTION_BASE
|
||||
@@ -68,7 +68,7 @@ do -- DETECTION_ZONES
|
||||
return self
|
||||
end
|
||||
|
||||
-- @param #DETECTION_ZONES self
|
||||
--- @param #DETECTION_ZONES self
|
||||
-- @param #number The amount of alive recce.
|
||||
function DETECTION_ZONES:CountAliveRecce()
|
||||
|
||||
@@ -76,7 +76,7 @@ do -- DETECTION_ZONES
|
||||
|
||||
end
|
||||
|
||||
-- @param #DETECTION_ZONES self
|
||||
--- @param #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:ForEachAliveRecce( IteratorFunction, ... )
|
||||
self:F2( arg )
|
||||
|
||||
@@ -352,7 +352,7 @@ do -- DETECTION_ZONES
|
||||
--DetectedSet:Flush( self )
|
||||
|
||||
DetectedSet:ForEachUnit(
|
||||
-- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
--- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
function( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() then
|
||||
--self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() )
|
||||
@@ -380,7 +380,7 @@ do -- DETECTION_ZONES
|
||||
|
||||
end
|
||||
|
||||
-- @param #DETECTION_ZONES self
|
||||
--- @param #DETECTION_ZONES self
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
@@ -1154,6 +1154,8 @@ function ESCORT:_ReportTargetsScheduler()
|
||||
|
||||
if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then
|
||||
|
||||
if true then
|
||||
|
||||
local EscortGroupName = self.EscortGroup:GetName()
|
||||
|
||||
self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
|
||||
@@ -1224,6 +1226,177 @@ function ESCORT:_ReportTargetsScheduler()
|
||||
end
|
||||
|
||||
return true
|
||||
else
|
||||
-- local EscortGroupName = self.EscortGroup:GetName()
|
||||
-- local EscortTargets = self.EscortGroup:GetDetectedTargets()
|
||||
--
|
||||
-- local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets
|
||||
--
|
||||
-- local EscortTargetMessages = ""
|
||||
-- for EscortTargetID, EscortTarget in pairs( EscortTargets ) do
|
||||
-- local EscortObject = EscortTarget.object
|
||||
-- self:T( EscortObject )
|
||||
-- if EscortObject and EscortObject:isExist() and EscortObject.id_ < 50000000 then
|
||||
--
|
||||
-- local EscortTargetUnit = UNIT:Find( EscortObject )
|
||||
-- local EscortTargetUnitName = EscortTargetUnit:GetName()
|
||||
--
|
||||
--
|
||||
--
|
||||
-- -- local EscortTargetIsDetected,
|
||||
-- -- EscortTargetIsVisible,
|
||||
-- -- EscortTargetLastTime,
|
||||
-- -- EscortTargetKnowType,
|
||||
-- -- EscortTargetKnowDistance,
|
||||
-- -- EscortTargetLastPos,
|
||||
-- -- EscortTargetLastVelocity
|
||||
-- -- = self.EscortGroup:IsTargetDetected( EscortObject )
|
||||
-- --
|
||||
-- -- self:T( { EscortTargetIsDetected,
|
||||
-- -- EscortTargetIsVisible,
|
||||
-- -- EscortTargetLastTime,
|
||||
-- -- EscortTargetKnowType,
|
||||
-- -- EscortTargetKnowDistance,
|
||||
-- -- EscortTargetLastPos,
|
||||
-- -- EscortTargetLastVelocity } )
|
||||
--
|
||||
--
|
||||
-- local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3()
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 +
|
||||
-- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 +
|
||||
-- ( EscortTargetUnitVec3.z - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
--
|
||||
-- self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } )
|
||||
--
|
||||
-- if Distance <= 15 then
|
||||
--
|
||||
-- if not ClientEscortTargets[EscortTargetUnitName] then
|
||||
-- ClientEscortTargets[EscortTargetUnitName] = {}
|
||||
-- end
|
||||
-- ClientEscortTargets[EscortTargetUnitName].AttackUnit = EscortTargetUnit
|
||||
-- ClientEscortTargets[EscortTargetUnitName].visible = EscortTarget.visible
|
||||
-- ClientEscortTargets[EscortTargetUnitName].type = EscortTarget.type
|
||||
-- ClientEscortTargets[EscortTargetUnitName].distance = EscortTarget.distance
|
||||
-- else
|
||||
-- if ClientEscortTargets[EscortTargetUnitName] then
|
||||
-- ClientEscortTargets[EscortTargetUnitName] = nil
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- self:T( { "Sorting Targets Table:", ClientEscortTargets } )
|
||||
-- table.sort( ClientEscortTargets, function( a, b ) return a.Distance < b.Distance end )
|
||||
-- self:T( { "Sorted Targets Table:", ClientEscortTargets } )
|
||||
--
|
||||
-- -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup.
|
||||
-- self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
|
||||
--
|
||||
-- if self.EscortMenuTargetAssistance then
|
||||
-- self.EscortMenuTargetAssistance:RemoveSubMenus()
|
||||
-- end
|
||||
--
|
||||
-- --for MenuIndex = 1, #self.EscortMenuAttackTargets do
|
||||
-- -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } )
|
||||
-- -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove()
|
||||
-- --end
|
||||
--
|
||||
--
|
||||
-- if ClientEscortTargets then
|
||||
-- for ClientEscortTargetUnitName, ClientEscortTargetData in pairs( ClientEscortTargets ) do
|
||||
--
|
||||
-- for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do
|
||||
--
|
||||
-- if ClientEscortTargetData and ClientEscortTargetData.AttackUnit:IsAlive() then
|
||||
--
|
||||
-- local EscortTargetMessage = ""
|
||||
-- local EscortTargetCategoryName = ClientEscortTargetData.AttackUnit:GetCategoryName()
|
||||
-- local EscortTargetCategoryType = ClientEscortTargetData.AttackUnit:GetTypeName()
|
||||
-- if ClientEscortTargetData.type then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at "
|
||||
-- else
|
||||
-- EscortTargetMessage = EscortTargetMessage .. "Unknown target at "
|
||||
-- end
|
||||
--
|
||||
-- local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3()
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 +
|
||||
-- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 +
|
||||
-- ( EscortTargetUnitVec3.z - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
--
|
||||
-- self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } )
|
||||
-- if ClientEscortTargetData.visible == false then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " estimated km"
|
||||
-- else
|
||||
-- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km"
|
||||
-- end
|
||||
--
|
||||
-- if ClientEscortTargetData.visible then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. ", visual"
|
||||
-- end
|
||||
--
|
||||
-- if ClientEscortGroupName == EscortGroupName then
|
||||
--
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient,
|
||||
-- EscortTargetMessage,
|
||||
-- self.EscortMenuAttackNearbyTargets,
|
||||
-- ESCORT._AttackTarget,
|
||||
-- { ParamSelf = self,
|
||||
-- ParamUnit = ClientEscortTargetData.AttackUnit
|
||||
-- }
|
||||
-- )
|
||||
-- EscortTargetMessages = EscortTargetMessages .. "\n - " .. EscortTargetMessage
|
||||
-- else
|
||||
-- if self.EscortMenuTargetAssistance then
|
||||
-- local MenuTargetAssistance = MENU_GROUP:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance )
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient,
|
||||
-- EscortTargetMessage,
|
||||
-- MenuTargetAssistance,
|
||||
-- ESCORT._AssistTarget,
|
||||
-- self,
|
||||
-- EscortGroupData.EscortGroup,
|
||||
-- ClientEscortTargetData.AttackUnit
|
||||
-- )
|
||||
-- end
|
||||
-- end
|
||||
-- else
|
||||
-- ClientEscortTargetData = nil
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- if EscortTargetMessages ~= "" and self.ReportTargets == true then
|
||||
-- self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient )
|
||||
-- else
|
||||
-- self.EscortGroup:MessageToClient( "No targets detected!", 20, self.EscortClient )
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- if self.EscortMenuResumeMission then
|
||||
-- self.EscortMenuResumeMission:RemoveSubMenus()
|
||||
--
|
||||
-- -- if self.EscortMenuResumeWayPoints then
|
||||
-- -- for MenuIndex = 1, #self.EscortMenuResumeWayPoints do
|
||||
-- -- self:T( { "Remove Menu:", self.EscortMenuResumeWayPoints[MenuIndex] } )
|
||||
-- -- self.EscortMenuResumeWayPoints[MenuIndex] = self.EscortMenuResumeWayPoints[MenuIndex]:Remove()
|
||||
-- -- end
|
||||
-- -- end
|
||||
--
|
||||
-- local TaskPoints = self:RegisterRoute()
|
||||
-- for WayPointID, WayPoint in pairs( TaskPoints ) do
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( WayPoint.x - EscortVec3.x )^2 +
|
||||
-- ( WayPoint.y - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } )
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -53,8 +53,6 @@
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE.
|
||||
-- Therefore, this class is considered to be deprecated and superseded by the [Functional.Fox](https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Functional.Fox.html) class, which provides the same functionality.
|
||||
--
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -78,8 +78,7 @@
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Applevangelist**: Additional functionality, fixes.
|
||||
--
|
||||
-- * **Wingthor (TAW)**: Testing & Advice.
|
||||
-- * **Dutch-Baron (TAW)**: Testing & Advice.
|
||||
-- * **Whisper**: Testing and Advice.
|
||||
@@ -117,13 +116,11 @@
|
||||
-- Special targets can be set that will give extra scores to the players when these are destroyed.
|
||||
-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s.
|
||||
-- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Wrapper.Static}s.
|
||||
-- Use the method @{#SCORING.AddScoreSetGroup}() to specify a special additional score for a specific @{Wrapper.Group}s gathered in a @{Core.Set#SET_GROUP}.
|
||||
-- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s.
|
||||
--
|
||||
-- local Scoring = SCORING:New( "Scoring File" )
|
||||
-- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 )
|
||||
-- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 )
|
||||
-- local GroupSet = SET_GROUP:New():FilterPrefixes("RAT"):FilterStart()
|
||||
-- Scoring:AddScoreSetGroup( GroupSet, 100)
|
||||
--
|
||||
-- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed.
|
||||
-- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over.
|
||||
@@ -229,7 +226,7 @@ SCORING = {
|
||||
ClassID = 0,
|
||||
Players = {},
|
||||
AutoSave = true,
|
||||
version = "1.18.4"
|
||||
version = "1.17.1"
|
||||
}
|
||||
|
||||
local _SCORINGCoalition = {
|
||||
@@ -248,15 +245,13 @@ local _SCORINGCategory = {
|
||||
--- Creates a new SCORING object to administer the scoring achieved by players.
|
||||
-- @param #SCORING self
|
||||
-- @param #string GameName The name of the game. This name is also logged in the CSV score file.
|
||||
-- @param #string SavePath (Optional) Path where to save the CSV file, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder.
|
||||
-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off.
|
||||
-- @return #SCORING self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Define a new scoring object for the mission Gori Valley.
|
||||
-- ScoringObject = SCORING:New( "Gori Valley" )
|
||||
--
|
||||
function SCORING:New( GameName, SavePath, AutoSave )
|
||||
function SCORING:New( GameName )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
|
||||
@@ -319,8 +314,7 @@ function SCORING:New( GameName, SavePath, AutoSave )
|
||||
end )
|
||||
|
||||
-- Create the CSV file.
|
||||
self.AutoSavePath = SavePath
|
||||
self.AutoSave = AutoSave or true
|
||||
self.AutoSave = true
|
||||
self:OpenCSV( GameName )
|
||||
|
||||
return self
|
||||
@@ -434,31 +428,6 @@ function SCORING:AddScoreGroup( ScoreGroup, Score )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Specify a special additional score for a @{Core.Set#SET_GROUP}.
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Set#SET_GROUP Set The @{Core.Set#SET_GROUP} for which each @{Wrapper.Unit} in each Group a Score is given.
|
||||
-- @param #number Score The Score value.
|
||||
-- @return #SCORING
|
||||
function SCORING:AddScoreSetGroup(Set, Score)
|
||||
local set = Set:GetSetObjects()
|
||||
|
||||
for _,_group in pairs (set) do
|
||||
if _group and _group:IsAlive() then
|
||||
self:AddScoreGroup(_group,Score)
|
||||
end
|
||||
end
|
||||
|
||||
local function AddScore(group)
|
||||
self:AddScoreGroup(group,Score)
|
||||
end
|
||||
|
||||
function Set:OnAfterAdded(From,Event,To,ObjectName,Object)
|
||||
AddScore(Object)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone.
|
||||
-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced!
|
||||
-- This allows for a dynamic destruction zone evolution within your mission.
|
||||
@@ -985,7 +954,6 @@ function SCORING:_EventOnHit( Event )
|
||||
local TargetUnitCoalition = nil
|
||||
local TargetUnitCategory = nil
|
||||
local TargetUnitType = nil
|
||||
local TargetIsScenery = false
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
@@ -1026,12 +994,6 @@ function SCORING:_EventOnHit( Event )
|
||||
TargetCategory = Event.TgtCategory
|
||||
TargetType = Event.TgtTypeName
|
||||
|
||||
-- Scenery hit
|
||||
if (not TargetCategory) and TargetUNIT ~= nil and TargetUnit:IsInstanceOf("SCENERY") then
|
||||
TargetCategory = Unit.Category.STRUCTURE
|
||||
TargetIsScenery = true
|
||||
end
|
||||
|
||||
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
||||
TargetUnitCategory = _SCORINGCategory[TargetCategory]
|
||||
TargetUnitType = TargetType
|
||||
@@ -1068,11 +1030,11 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
|
||||
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
|
||||
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
|
||||
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth
|
||||
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
|
||||
if PlayerHit.UNIT.ThreatType == nil then
|
||||
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
|
||||
-- if this fails for some reason, set a good default value
|
||||
if PlayerHit.ThreatType == nil or PlayerHit.ThreatType == "" then
|
||||
if PlayerHit.ThreatType == nil then
|
||||
PlayerHit.ThreatLevel = 1
|
||||
PlayerHit.ThreatType = "Unknown"
|
||||
end
|
||||
@@ -1124,22 +1086,17 @@ function SCORING:_EventOnHit( Event )
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
elseif TargetIsScenery ~= true then
|
||||
else
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
|
||||
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
elseif TargetIsScenery == true then
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object." .. " Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
end
|
||||
self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
end
|
||||
else -- A scenery object was hit.
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit nothing special.",
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.",
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
@@ -1184,7 +1141,7 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
|
||||
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
|
||||
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
|
||||
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth
|
||||
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
|
||||
if PlayerHit.UNIT.ThreatType == nil then
|
||||
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
|
||||
-- if this fails for some reason, set a good default value
|
||||
@@ -1331,17 +1288,17 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1
|
||||
|
||||
|
||||
--self:OnKillPvP(PlayerName, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
|
||||
self:OnKillPvP(Player, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
|
||||
|
||||
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
|
||||
self:OnKillPvP(PlayerName, TargetPlayerName, true)
|
||||
self:OnKillPvP(Player, TargetPlayerName, true)
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
|
||||
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
|
||||
else
|
||||
self:OnKillPvE(PlayerName, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
|
||||
self:OnKillPvE(Player, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
|
||||
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
@@ -1369,14 +1326,14 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
else
|
||||
Player.PlayerKills = 1
|
||||
end
|
||||
self:OnKillPvP(PlayerName, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
|
||||
self:OnKillPvP(Player, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
|
||||
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
|
||||
else
|
||||
self:OnKillPvE(PlayerName, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
|
||||
self:OnKillPvE(Player, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
|
||||
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
@@ -1854,11 +1811,10 @@ end
|
||||
function SCORING:OpenCSV( ScoringCSV )
|
||||
self:F( ScoringCSV )
|
||||
|
||||
if lfs and io and os and self.AutoSave == true then
|
||||
if lfs and io and os and self.AutoSave then
|
||||
if ScoringCSV then
|
||||
self.ScoringCSV = ScoringCSV
|
||||
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]]
|
||||
local fdir = path .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv"
|
||||
local fdir = lfs.writedir() .. [[Logs\]] .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv"
|
||||
|
||||
self.CSVFile, self.err = io.open( fdir, "w+" )
|
||||
if not self.CSVFile then
|
||||
@@ -1979,23 +1935,23 @@ end
|
||||
|
||||
--- Handles the event when one player kill another player
|
||||
-- @param #SCORING self
|
||||
-- @param #string PlayerName The attacking player
|
||||
-- @param #string TargetPlayerName The name of the killed player
|
||||
-- @param #boolean IsTeamKill true if this kill was a team kill
|
||||
-- @param #number TargetThreatLevel Threat level of the target
|
||||
-- @param #number PlayerThreatLevel Threat level of the player
|
||||
-- @param #PLAYER Player the ataching player
|
||||
-- @param #string TargetPlayerName the name of the killed player
|
||||
-- @param #bool IsTeamKill true if this kill was a team kill
|
||||
-- @param #number TargetThreatLevel Thread level of the target
|
||||
-- @param #number PlayerThreatLevelThread level of the player
|
||||
-- @param #number Score The score based on both threat levels
|
||||
function SCORING:OnKillPvP(PlayerName, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
|
||||
function SCORING:OnKillPvP(Player, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
|
||||
|
||||
end
|
||||
--- Handles the event when one player kill another player
|
||||
-- @param #SCORING self
|
||||
-- @param #string PlayerName The attacking player
|
||||
-- @param #PLAYER Player the ataching player
|
||||
-- @param #string TargetUnitName the name of the killed unit
|
||||
-- @param #boolean IsTeamKill true if this kill was a team kill
|
||||
-- @param #number TargetThreatLevel Threat level of the target
|
||||
-- @param #number PlayerThreatLevel Threat level of the player
|
||||
-- @param #bool IsTeamKill true if this kill was a team kill
|
||||
-- @param #number TargetThreatLevel Thread level of the target
|
||||
-- @param #number PlayerThreatLevelThread level of the player
|
||||
-- @param #number Score The score based on both threat levels
|
||||
function SCORING:OnKillPvE(PlayerName, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
|
||||
function SCORING:OnKillPvE(Player, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
|
||||
|
||||
end
|
||||
end
|
||||
@@ -19,7 +19,7 @@
|
||||
--
|
||||
-- ### Authors: **applevangelist**, **FlightControl**
|
||||
--
|
||||
-- Last Update: Dec 2024
|
||||
-- Last Update: Dec 2023
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -28,16 +28,6 @@
|
||||
|
||||
---
|
||||
-- @type SEAD
|
||||
-- @field #string ClassName The Class Name.
|
||||
-- @field #table TargetSkill Table of target skills.
|
||||
-- @field #table SEADGroupPrefixes Table of SEAD prefixes.
|
||||
-- @field #table SuppressedGroups Table of currently suppressed groups.
|
||||
-- @field #number EngagementRange Engagement Range.
|
||||
-- @field #number Padding Padding in seconds.
|
||||
-- @field #function CallBack Callback function for suppression plans.
|
||||
-- @field #boolean UseCallBack Switch for callback function to be used.
|
||||
-- @field #boolean debug Debug switch.
|
||||
-- @field #boolen WeaponTrack Track switch, if true track weapon speed for 30 secs.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
@@ -66,11 +56,10 @@ SEAD = {
|
||||
SEADGroupPrefixes = {},
|
||||
SuppressedGroups = {},
|
||||
EngagementRange = 75, -- default 75% engagement range Feature Request #1355
|
||||
Padding = 15,
|
||||
Padding = 10,
|
||||
CallBack = nil,
|
||||
UseCallBack = false,
|
||||
debug = false,
|
||||
WeaponTrack = false,
|
||||
}
|
||||
|
||||
--- Missile enumerators
|
||||
@@ -80,7 +69,6 @@ SEAD = {
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_65"] = "AGM_65",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
@@ -100,7 +88,6 @@ SEAD = {
|
||||
-- km and mach
|
||||
["AGM_88"] = { 150, 3},
|
||||
["AGM_45"] = { 12, 2},
|
||||
["AGM_65"] = { 16, 0.9},
|
||||
["AGM_122"] = { 16.5, 2.3},
|
||||
["AGM_84"] = { 280, 0.8},
|
||||
["ALARM"] = { 45, 2},
|
||||
@@ -157,7 +144,7 @@ function SEAD:New( SEADGroupPrefixes, Padding )
|
||||
self:AddTransition("*", "ManageEvasion", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
self:I("*** SEAD - Started Version 0.4.9")
|
||||
self:I("*** SEAD - Started Version 0.4.6")
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -333,6 +320,9 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
|
||||
end
|
||||
|
||||
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
|
||||
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtgrp = seadset:GetRandom()
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
@@ -384,7 +374,7 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP
|
||||
reach = wpndata[1] * 1.1
|
||||
local mach = wpndata[2]
|
||||
wpnspeed = math.floor(mach * 340.29)
|
||||
if Weapon and Weapon:GetSpeed() > 0 then
|
||||
if Weapon then
|
||||
wpnspeed = Weapon:GetSpeed()
|
||||
self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed))
|
||||
end
|
||||
@@ -465,38 +455,29 @@ end
|
||||
-- @return #SEAD self
|
||||
function SEAD:HandleEventShot( EventData )
|
||||
self:T( { EventData.id } )
|
||||
|
||||
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
|
||||
local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP
|
||||
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName or "None" -- return weapon type
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
--UTILS.PrintTableToLog(EventData)
|
||||
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
|
||||
|
||||
if not SEADPlane then return self end -- case IniUnit is empty
|
||||
|
||||
local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP
|
||||
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
|
||||
local WeaponWrapper = WEAPON:New(EventData.Weapon) -- Wrapper.Weapon#WEAPON
|
||||
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
|
||||
local WeaponWrapper = WEAPON:New(EventData.Weapon)
|
||||
--local SEADWeaponSpeed = WeaponWrapper:GetSpeed() -- mps
|
||||
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
--self:T({ SEADWeapon })
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
self:T( '*** SEAD - Weapon Match' )
|
||||
if self.WeaponTrack == true then
|
||||
WeaponWrapper:SetFuncTrack(function(weapon) env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed() or -1)) end)
|
||||
WeaponWrapper:StartTrack(0.1)
|
||||
WeaponWrapper:StopTrack(30)
|
||||
end
|
||||
local _targetskill = "Random"
|
||||
local _targetgroupname = "none"
|
||||
local _target = EventData.Weapon:getTarget() -- Identify target
|
||||
if not _target or self.debug then -- AGM-88 or 154 w/o target data
|
||||
self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None"))
|
||||
if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then
|
||||
self:T("**** Tracking AGM-88/154 with no target data.")
|
||||
self:I("**** Tracking AGM-88/154 with no target data.")
|
||||
local pos0 = SEADPlane:GetCoordinate()
|
||||
local fheight = SEADPlane:GetHeight()
|
||||
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
|
||||
@@ -542,7 +523,7 @@ function SEAD:HandleEventShot( EventData )
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
if string.find(SEADWeaponName,"ADM_141",1,true) then
|
||||
self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,2,WeaponWrapper)
|
||||
self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper)
|
||||
else
|
||||
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper)
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: Nov 2021
|
||||
-- Last Update: Jan 2025
|
||||
-- Last Update: Nov 2023
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -113,7 +113,7 @@ SHORAD = {
|
||||
SkateNumber = 3,
|
||||
SkateZones = nil,
|
||||
minscootdist = 100,
|
||||
maxscootdist = 3000,
|
||||
minscootdist = 3000,
|
||||
scootrandomcoord = false,
|
||||
}
|
||||
|
||||
@@ -443,9 +443,7 @@ do
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
if _groups:IsSAM() then
|
||||
returnname = true
|
||||
end
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
@@ -472,7 +470,6 @@ do
|
||||
-- @param #number Radius Radius of the #ZONE
|
||||
-- @param #number ActiveTimer Number of seconds to stay active
|
||||
-- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC
|
||||
-- @param #boolean ShotAt If true, function is called after a shot
|
||||
-- @return #SHORAD self
|
||||
-- @usage Use this function to integrate with other systems, example
|
||||
--
|
||||
@@ -482,7 +479,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, ShotAt)
|
||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
@@ -524,27 +521,7 @@ do
|
||||
-- go through set and find the one(s) to activate
|
||||
local TDiff = 4
|
||||
for _,_group in pairs (shoradset) do
|
||||
|
||||
local groupname = _group:GetName()
|
||||
|
||||
if groupname == TargetGroup and ShotAt==true then
|
||||
-- Shot at a SHORAD group
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(false)
|
||||
end
|
||||
_group:OptionAlarmStateGreen()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(1,_group)
|
||||
end
|
||||
|
||||
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
|
||||
-- shot at a group we protect
|
||||
if _group:IsAnyInZone(targetzone) then
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
@@ -552,6 +529,7 @@ do
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
local groupname = _group:GetName()
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
@@ -629,7 +607,7 @@ do
|
||||
_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,true)
|
||||
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -758,7 +736,7 @@ do
|
||||
-- if being shot at, find closest SHORADs to activate
|
||||
if shotatsams or shotatus then
|
||||
self:T({shotatsams=shotatsams,shotatus=shotatus})
|
||||
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat, true)
|
||||
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,677 +0,0 @@
|
||||
----- **Functional** - TIRESIAS - manages AI behaviour (OPTIMIZED VERSION).
|
||||
|
||||
---- ===
|
||||
|
||||
--- The @{#TIRESIAS} class is working in the back to keep your large-scale ground units in check.
|
||||
--
|
||||
-- -- Features:
|
||||
--
|
||||
-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units.
|
||||
-- * Does not affect ships to keep the Navy guys happy.
|
||||
-- * Does not affect OpsGroup type groups.
|
||||
-- * Distinguishes between SAM groups, AAA groups and other ground groups.
|
||||
-- * Exceptions can be defined to keep certain actions going.
|
||||
-- * Works coalition-independent in the back
|
||||
-- * Easy setup.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Optimizations Applied:
|
||||
--
|
||||
-- * Cached frequently used functions and constants
|
||||
-- * Reduced string concatenations and formatting
|
||||
-- * Optimized loop structures and conditions
|
||||
-- * Pre-allocated tables where possible
|
||||
-- * Reduced function call overhead
|
||||
-- * Improved memory management
|
||||
--
|
||||
---- ===
|
||||
--
|
||||
---- #-- Author : **applevangelist ** (Optimized by AI)
|
||||
|
||||
---
|
||||
-- @module Functional.Tiresias
|
||||
-- @image Functional.Tiresias.jpg
|
||||
|
||||
--- Last Update: July 2025
|
||||
|
||||
--- **TIRESIAS** class, extends Core.Base#BASE
|
||||
-- @type TIRESIAS
|
||||
-- @field #string ClassName
|
||||
-- @field #boolean debug
|
||||
-- @field #string version
|
||||
-- @field #number Interval
|
||||
-- @field Core.Set#SET_GROUP GroundSet
|
||||
-- @field #number Coalition
|
||||
-- @field Core.Set#SET_GROUP VehicleSet
|
||||
-- @field Core.Set#SET_GROUP AAASet
|
||||
-- @field Core.Set#SET_GROUP SAMSet
|
||||
-- @field Core.Set#SET_GROUP ExceptionSet
|
||||
-- @field Core.Set#SET_OPSGROUP OpsGroupSet
|
||||
-- @field #number AAARange
|
||||
-- @field #number HeloSwitchRange
|
||||
-- @field #number PlaneSwitchRange
|
||||
-- @field Core.Set#SET_GROUP FlightSet
|
||||
-- @field #boolean SwitchAAA
|
||||
-- @field #string lid
|
||||
-- @field #table _cached_zones
|
||||
-- @field #table _cached_groupsets
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
---
|
||||
-- @type TIRESIAS.Data
|
||||
-- @field #string type
|
||||
-- @field #number range
|
||||
-- @field #boolean invisible
|
||||
-- @field #boolean AIOff
|
||||
-- @field #boolean exception
|
||||
|
||||
---
|
||||
-- *Tiresias, Greek demi-god and shapeshifter, blinded by the Gods, works as oracle for you.* (Wiki)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## TIRESIAS Concept
|
||||
--
|
||||
-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units.
|
||||
-- * Does not affect ships to keep the Navy guys happy.
|
||||
-- * Does not affect OpsGroup type groups.
|
||||
-- * Distinguishes between SAM groups, AAA groups and other ground groups.
|
||||
-- * Exceptions can be defined in SET_GROUP objects to keep certain actions going.
|
||||
-- * Works coalition-independent in the back
|
||||
-- * Easy setup.
|
||||
--
|
||||
-- ## Setup
|
||||
-- -- Setup is a one-liner:
|
||||
--
|
||||
-- local blinder = TIRESIAS:New()
|
||||
--
|
||||
-- -- Optionally you can set up exceptions, e.g. for convoys driving around
|
||||
--
|
||||
-- local exceptionset = SET_GROUP:New():FilterCoalitions(" red" ):FilterPrefixes(" Convoy" ):FilterStart()
|
||||
-- local blinder = TIRESIAS:New()
|
||||
-- blinder:AddExceptionSet(exceptionset)
|
||||
--
|
||||
-- -- Options
|
||||
--
|
||||
-- -- Setup different radius for activation around helo and airplane groups (applies to AI and humans)
|
||||
-- blinder:SetActivationRanges(10,25) -- defaults are 10, and 25
|
||||
--
|
||||
-- -- Setup engagement ranges for AAA (non-advanced SAM units like Flaks etc) and if you want them to be AIOff
|
||||
-- blinder:SetAAARanges(60,true) -- defaults are 60, and true
|
||||
--
|
||||
---
|
||||
-- @field #TIRESIAS
|
||||
TIRESIAS = {
|
||||
ClassName = "TIRESIAS",
|
||||
debug = true,
|
||||
version = " 0.0.7a-OPT" ,
|
||||
Interval = 20,
|
||||
GroundSet = nil,
|
||||
VehicleSet = nil,
|
||||
AAASet = nil,
|
||||
SAMSet = nil,
|
||||
ExceptionSet = nil,
|
||||
AAARange = 60, -- 60%
|
||||
HeloSwitchRange = 10, -- NM
|
||||
PlaneSwitchRange = 25, -- NM
|
||||
SwitchAAA = true,
|
||||
_cached_zones = {}, -- Cache for zone objects
|
||||
_cached_groupsets = {}, -- Cache for group_set objects
|
||||
}
|
||||
|
||||
---
|
||||
-- [USER] Create a new Tiresias object and start it up.
|
||||
-- @param #TIRESIAS self
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:New()
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self = BASE:Inherit(self, FSM:New()) -- #TIRESIAS
|
||||
|
||||
--- FSM Functions ---
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
|
||||
self:AddTransition("*", "Status", "*") -- TIRESIAS status update.
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
self.ExceptionSet = nil --SET_GROUP:New():Clear(false)
|
||||
self._cached_zones = {} -- Initialize zone cache
|
||||
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
||||
|
||||
-- Cache the log identifier to avoid string concatenation in loops
|
||||
self.lid = "TIRESIAS " .. self.version .. " | "
|
||||
|
||||
self:I(self.lid .. "Managing ground groups!")
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops TIRESIAS and all its event handlers.
|
||||
-- @function [parent=#TIRESIAS] Stop
|
||||
-- @param #TIRESIAS self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops TIRESIAS and all its event handlers.
|
||||
-- @function [parent=#TIRESIAS] __Stop
|
||||
-- @param #TIRESIAS self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Start". Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance.
|
||||
-- @function [parent=#TIRESIAS] Start
|
||||
-- @param #TIRESIAS self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance.
|
||||
-- @function [parent=#TIRESIAS] __Start
|
||||
-- @param #TIRESIAS self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
self:__Start(1)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-----
|
||||
|
||||
---
|
||||
-- Helper Functions
|
||||
---
|
||||
|
||||
--- [USER] Set activation radius for Helos and Planes in Nautical Miles.
|
||||
-- @param #TIRESIAS self
|
||||
-- @param #number HeloMiles Radius around a Helicopter in which AI ground units will be activated. Defaults to 10NM.
|
||||
-- @param #number PlaneMiles Radius around an Airplane in which AI ground units will be activated. Defaults to 25NM.
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:SetActivationRanges(HeloMiles, PlaneMiles)
|
||||
self.HeloSwitchRange = HeloMiles or 10
|
||||
self.PlaneSwitchRange = PlaneMiles or 25
|
||||
-- Clear zone cache when ranges change
|
||||
self._cached_zones = {}
|
||||
return self
|
||||
end
|
||||
|
||||
---[USER] Set AAA Ranges - AAA equals non-SAM systems which qualify as AAA in DCS world.
|
||||
-- @param #TIRESIAS self
|
||||
-- @param #number FiringRange The engagement range that AAA units will be set to. Can be 0 to 100 (percent). Defaults to 60.
|
||||
-- @param #boolean SwitchAAA Decide if these system will have their AI switched off, too. Defaults to true.
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:SetAAARanges(FiringRange, SwitchAAA)
|
||||
self.AAARange = FiringRange or 60
|
||||
self.SwitchAAA = (SwitchAAA == false) and false or true
|
||||
return self
|
||||
end
|
||||
|
||||
--- [USER] Add a SET_GROUP of GROUP objects as exceptions. Can be done multiple times. Does **not** work work for GROUP objects spawned into the SET after start, i.e. the groups need to exist in the game already.
|
||||
-- @param #TIRESIAS self
|
||||
-- @param Core.Set#SET_GROUP Set to add to the exception list.
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:AddExceptionSet(Set)
|
||||
self:T(self.lid .. " AddExceptionSet" )
|
||||
|
||||
if not self.ExceptionSet then
|
||||
self.ExceptionSet = SET_GROUP:New()
|
||||
end
|
||||
|
||||
local exceptions = self.ExceptionSet
|
||||
|
||||
-- Cache the exception data structure for reuse
|
||||
local exception_data = {
|
||||
type = " Exception" ,
|
||||
exception = true,
|
||||
}
|
||||
|
||||
Set:ForEachGroupAlive(
|
||||
function(grp)
|
||||
local inAAASet = self.AAASet:IsIncludeObject(grp)
|
||||
local inVehSet = self.VehicleSet:IsIncludeObject(grp)
|
||||
local inSAMSet = self.SAMSet:IsIncludeObject(grp)
|
||||
if grp:IsGround() and (not grp.Tiresias) and (not inAAASet) and (not inVehSet) and (not inSAMSet) then
|
||||
grp.Tiresias = exception_data
|
||||
exceptions:AddGroup(grp, true)
|
||||
BASE:T(" TIRESIAS: Added exception group: " .. grp:GetName())
|
||||
end
|
||||
end
|
||||
)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [INTERNAL] Filter Function - Optimized with cached calls
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return #boolean isin
|
||||
function TIRESIAS._FilterNotAAA(Group)
|
||||
local grp = Group -- Wrapper.Group#GROUP
|
||||
-- Cache method calls to reduce overhead
|
||||
local is_air = grp:IsAir()
|
||||
local is_ship = grp:IsShip()
|
||||
local is_AAA = grp:IsAAA()
|
||||
if is_air or grp:IsShip() then -- air or ship - no AAA
|
||||
return true -- keep in SET
|
||||
end
|
||||
return not is_AAA -- remove AAA, keep others
|
||||
end
|
||||
|
||||
--- [INTERNAL] Filter Function - Optimized with cached calls
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return #boolean isin
|
||||
function TIRESIAS._FilterNotSAM(Group)
|
||||
local grp = Group -- Wrapper.Group#GROUP
|
||||
-- Cache method calls to reduce overhead
|
||||
local is_air = grp:IsGround()
|
||||
local is_ship = grp:IsShip()
|
||||
local is_SAM = grp:IsSAM()
|
||||
if is_air or grp:IsShip() then
|
||||
return true -- keep in SET
|
||||
end
|
||||
return not is_SAM -- remove SAM, keep others
|
||||
end
|
||||
|
||||
--- [INTERNAL] Filter Function - Optimized with cached calls
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return #boolean isin
|
||||
function TIRESIAS._FilterAAA(Group)
|
||||
local grp = Group -- Wrapper.Group#GROUP
|
||||
-- Cache method calls to reduce overhead
|
||||
local is_ground = grp:IsGround()
|
||||
if (not is_ground) or grp:IsShip() then
|
||||
return false -- not AAA
|
||||
end
|
||||
return grp:IsAAA() -- only AAA
|
||||
end
|
||||
|
||||
--- [INTERNAL] Filter Function - Optimized with cached calls
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
-- @return #boolean isin
|
||||
function TIRESIAS._FilterSAM(Group)
|
||||
local grp = Group -- Wrapper.Group#GROUP
|
||||
-- Cache method calls to reduce overhead
|
||||
local is_ground = grp:IsGround()
|
||||
if (not is_ground) or grp:IsShip() then
|
||||
return false -- not SAM
|
||||
end
|
||||
return grp:IsSAM() -- only SAM
|
||||
end
|
||||
|
||||
--- [INTERNAL] Init Groups - Optimized with reduced function calls
|
||||
-- @param #TIRESIAS self
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:_InitGroups()
|
||||
self:T(self.lid .. " _InitGroups" )
|
||||
|
||||
-- Cache frequently used values
|
||||
local EngageRange = self.AAARange
|
||||
local SwitchAAA = self.SwitchAAA
|
||||
|
||||
-- Pre-create data structures to avoid repeated table creation
|
||||
local aaa_data_template = {
|
||||
type = " AAA" ,
|
||||
invisible = true,
|
||||
range = EngageRange,
|
||||
exception = false,
|
||||
AIOff = SwitchAAA,
|
||||
}
|
||||
|
||||
local vehicle_data_template = {
|
||||
type = " Vehicle" ,
|
||||
invisible = true,
|
||||
AIOff = true,
|
||||
exception = false,
|
||||
}
|
||||
|
||||
local sam_data_template = {
|
||||
type = " SAM" ,
|
||||
invisible = true,
|
||||
exception = false,
|
||||
}
|
||||
|
||||
--- AAA - Optimized loop
|
||||
self.AAASet:ForEachGroupAlive(
|
||||
function(grp)
|
||||
local tiresias_data = grp.Tiresias
|
||||
if not tiresias_data then
|
||||
grp:OptionEngageRange(EngageRange)
|
||||
grp:SetCommandInvisible(true)
|
||||
if SwitchAAA then
|
||||
grp:SetAIOff()
|
||||
grp:EnableEmission(false)
|
||||
end
|
||||
grp.Tiresias = aaa_data_template
|
||||
elseif not tiresias_data.exception == true then
|
||||
if not tiresias_data.invisible == true then
|
||||
grp:SetCommandInvisible(true)
|
||||
tiresias_data.invisible = true
|
||||
if SwitchAAA == true then
|
||||
grp:SetAIOff()
|
||||
grp:EnableEmission(false)
|
||||
tiresias_data.AIOff = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
--- Vehicles - Optimized loop
|
||||
self.VehicleSet:ForEachGroupAlive(
|
||||
function(grp)
|
||||
local tiresias_data = grp.Tiresias
|
||||
if not tiresias_data then
|
||||
grp:SetAIOff()
|
||||
grp:SetCommandInvisible(true)
|
||||
grp.Tiresias = vehicle_data_template
|
||||
elseif not tiresias_data.exception == true then
|
||||
if not tiresias_data.invisible then
|
||||
grp:SetCommandInvisible(true)
|
||||
grp:SetAIOff()
|
||||
tiresias_data.invisible = true
|
||||
tiresias_data.AIOff = true
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
--- SAM - Optimized loop
|
||||
self.SAMSet:ForEachGroupAlive(
|
||||
function(grp)
|
||||
local tiresias_data = grp.Tiresias
|
||||
if not tiresias_data then
|
||||
grp:SetCommandInvisible(true)
|
||||
grp.Tiresias = sam_data_template
|
||||
elseif not tiresias_data.exception == true then
|
||||
if not tiresias_data.invisible then
|
||||
grp:SetCommandInvisible(true)
|
||||
tiresias_data.invisible = true
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [INTERNAL] Event handler function - Optimized
|
||||
-- @param #TIRESIAS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:_EventHandler(EventData)
|
||||
self:T(string.format(" %s Event = %d" , self.lid, EventData.id))
|
||||
|
||||
local event = EventData -- Core.Event#EVENTDATA
|
||||
if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then
|
||||
local _group = event.IniGroup
|
||||
if _group and _group:IsAlive() then
|
||||
-- Cache the radius calculation
|
||||
local radius = _group:IsHelicopter() and self.HeloSwitchRange or self.PlaneSwitchRange
|
||||
self:_SwitchOnGroups(_group, radius)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [INTERNAL] Switch Groups Behaviour - Optimized with zone caching
|
||||
-- @param #TIRESIAS self
|
||||
-- @param Wrapper.Group#GROUP group
|
||||
-- @param #number radius Radius in NM
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:_SwitchOnGroups(group, radius)
|
||||
self:T(self.lid .. " _SwitchOnGroups " .. group:GetName() .. " Radius " .. radius .. " NM" )
|
||||
|
||||
-- Use cached zones to reduce object creation
|
||||
local group_name = group:GetName()
|
||||
local cache_key = group_name .. " _" .. radius
|
||||
local zone = self._cached_zones[cache_key] -- Core.Zone#ZONE_RADIUS
|
||||
--local ground = self._cached_groupsets[cache_key] -- Core.Set#SET_GROUP
|
||||
|
||||
if not zone then
|
||||
zone = ZONE_GROUP:New(" Zone-" .. group_name, group, UTILS.NMToMeters(radius))
|
||||
self._cached_zones[cache_key] = zone
|
||||
else
|
||||
-- Update zone center to current group position
|
||||
zone:UpdateFromGroup(group)
|
||||
end
|
||||
|
||||
--if not ground then
|
||||
--ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
|
||||
--self._cached_groupsets[cache_key] = ground
|
||||
--else
|
||||
--ground:FilterZones({zone},true):FilterOnce()
|
||||
zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
|
||||
local ground = zone:GetScannedSetGroup()
|
||||
--end
|
||||
|
||||
local count = ground:CountAlive()
|
||||
|
||||
if self.debug then
|
||||
self:I(string.format(" There are %d groups around this plane or helo!" , count))
|
||||
end
|
||||
|
||||
if count > 0 then
|
||||
-- Cache values outside the loop
|
||||
local SwitchAAA = self.SwitchAAA
|
||||
local group_coalition = group:GetCoalition()
|
||||
|
||||
ground:ForEachGroupAlive(
|
||||
function(grp)
|
||||
local tiresias_data = grp.Tiresias
|
||||
if grp:GetCoalition() ~= group_coalition
|
||||
and tiresias_data
|
||||
and tiresias_data.type
|
||||
and not tiresias_data.exception == true then
|
||||
|
||||
-- Make group visible if invisible
|
||||
if tiresias_data.invisible == true then
|
||||
grp:SetCommandInvisible(false)
|
||||
tiresias_data.invisible = false
|
||||
end
|
||||
|
||||
-- Handle AI activation based on type
|
||||
local grp_type = tiresias_data.type
|
||||
if grp_type == "Vehicle" and tiresias_data.AIOff == true then
|
||||
grp:SetAIOn()
|
||||
tiresias_data.AIOff = false
|
||||
elseif SwitchAAA == true and grp_type == "AAA" and tiresias_data.AIOff == true then
|
||||
grp:SetAIOn()
|
||||
grp:EnableEmission(true)
|
||||
tiresias_data.AIOff = false
|
||||
end
|
||||
else
|
||||
BASE:T("TIRESIAS - This group " .. tostring(grp:GetName()) .. " has not been initialized or is an exception!")
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-----
|
||||
|
||||
---
|
||||
-- FSM Functions
|
||||
----
|
||||
|
||||
--- [INTERNAL] FSM Function - Optimized initialization
|
||||
-- @param #TIRESIAS self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:onafterStart(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
|
||||
-- Create sets with optimized filters
|
||||
local VehicleSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart()
|
||||
local AAASet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart()
|
||||
local SAMSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart()
|
||||
local OpsGroupSet = SET_OPSGROUP:New():FilterActive(true):FilterStart()
|
||||
self.FlightSet = SET_GROUP:New():FilterCategories({" plane" ," helicopter" }):FilterStart()
|
||||
|
||||
-- Cache frequently used values
|
||||
local EngageRange = self.AAARange
|
||||
local SwitchAAA = self.SwitchAAA
|
||||
local ExceptionSet = self.ExceptionSet
|
||||
|
||||
-- Pre-create data templates to reduce object creation
|
||||
local exception_data = {
|
||||
type = " Exception" ,
|
||||
exception = true,
|
||||
}
|
||||
|
||||
local vehicle_data = {
|
||||
type = " Vehicle" ,
|
||||
invisible = true,
|
||||
AIOff = true,
|
||||
exception = false,
|
||||
}
|
||||
|
||||
local aaa_data = {
|
||||
type = " AAA" ,
|
||||
invisible = true,
|
||||
range = EngageRange,
|
||||
exception = false,
|
||||
AIOff = SwitchAAA,
|
||||
}
|
||||
|
||||
local sam_data = {
|
||||
type = " SAM" ,
|
||||
invisible = true,
|
||||
exception = false,
|
||||
}
|
||||
|
||||
if ExceptionSet then
|
||||
function ExceptionSet:OnAfterAdded(From, Event, To, ObjectName, Object)
|
||||
BASE:I(" TIRESIAS: EXCEPTION Object Added: " .. Object:GetName())
|
||||
if Object and Object:IsAlive() then
|
||||
Object.Tiresias = exception_data
|
||||
Object:SetAIOn()
|
||||
Object:SetCommandInvisible(false)
|
||||
Object:EnableEmission(true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Process existing OpsGroups more efficiently
|
||||
local OGS = OpsGroupSet:GetAliveSet()
|
||||
for _, _OG in pairs(OGS or {}) do
|
||||
local OG = _OG -- Ops.OpsGroup#OPSGROUP
|
||||
local grp = OG:GetGroup()
|
||||
ExceptionSet:AddGroup(grp, true)
|
||||
end
|
||||
|
||||
function OpsGroupSet:OnAfterAdded(From, Event, To, ObjectName, Object)
|
||||
local grp = Object:GetGroup()
|
||||
ExceptionSet:AddGroup(grp, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Optimized event handlers with pre-created data objects
|
||||
function VehicleSet:OnAfterAdded(From, Event, To, ObjectName, Object)
|
||||
BASE:T(" TIRESIAS: VEHICLE Object Added: " .. Object:GetName())
|
||||
if Object and Object:IsAlive() then
|
||||
Object:SetAIOff()
|
||||
Object:SetCommandInvisible(true)
|
||||
Object.Tiresias = vehicle_data
|
||||
end
|
||||
end
|
||||
|
||||
function AAASet:OnAfterAdded(From, Event, To, ObjectName, Object)
|
||||
if Object and Object:IsAlive() then
|
||||
BASE:I(" TIRESIAS: AAA Object Added: " .. Object:GetName())
|
||||
Object:OptionEngageRange(EngageRange)
|
||||
Object:SetCommandInvisible(true)
|
||||
if SwitchAAA then
|
||||
Object:SetAIOff()
|
||||
Object:EnableEmission(false)
|
||||
end
|
||||
Object.Tiresias = aaa_data
|
||||
end
|
||||
end
|
||||
|
||||
function SAMSet:OnAfterAdded(From, Event, To, ObjectName, Object)
|
||||
if Object and Object:IsAlive() then
|
||||
BASE:T(" TIRESIAS: SAM Object Added: " .. Object:GetName())
|
||||
Object:SetCommandInvisible(true)
|
||||
Object.Tiresias = sam_data
|
||||
end
|
||||
end
|
||||
|
||||
-- Store references
|
||||
self.VehicleSet = VehicleSet
|
||||
self.AAASet = AAASet
|
||||
self.SAMSet = SAMSet
|
||||
self.OpsGroupSet = OpsGroupSet
|
||||
|
||||
self:_InitGroups()
|
||||
|
||||
self:__Status(1)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [INTERNAL] FSM Function
|
||||
-- @param #TIRESIAS self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:onbeforeStatus(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
return self:GetState() ~= " Stopped"
|
||||
end
|
||||
|
||||
--- [INTERNAL] FSM Function - Optimized status processing
|
||||
-- @param #TIRESIAS self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:onafterStatus(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
|
||||
if self.debug then
|
||||
local count = self.VehicleSet:CountAlive()
|
||||
local AAAcount = self.AAASet:CountAlive()
|
||||
local SAMcount = self.SAMSet:CountAlive()
|
||||
self:I(string.format(" Overall: %d | Vehicles: %d | AAA: %d | SAM: %d" ,
|
||||
count + AAAcount + SAMcount, count, AAAcount, SAMcount))
|
||||
end
|
||||
|
||||
self:_InitGroups()
|
||||
|
||||
-- Process flight groups more efficiently
|
||||
local flight_count = self.FlightSet:CountAlive()
|
||||
if flight_count > 0 then
|
||||
local Set = self.FlightSet:GetAliveSet()
|
||||
-- Cache range values outside loop
|
||||
local helo_range = self.HeloSwitchRange
|
||||
local plane_range = self.PlaneSwitchRange
|
||||
|
||||
for _, _plane in pairs(Set or {}) do
|
||||
local plane = _plane -- Wrapper.Group#GROUP
|
||||
local radius = plane:IsHelicopter() and helo_range or plane_range
|
||||
self:_SwitchOnGroups(plane, radius)
|
||||
end
|
||||
end
|
||||
|
||||
if self:GetState() ~= " Stopped" then
|
||||
self:__Status(self.Interval)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [INTERNAL] FSM Function
|
||||
-- @param #TIRESIAS self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #TIRESIAS self
|
||||
function TIRESIAS:onafterStop(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
|
||||
-- Clear zone cache on stop to free memory
|
||||
self._cached_zones = {}
|
||||
return self
|
||||
end
|
||||
|
||||
-----
|
||||
---- End
|
||||
-----
|
||||
@@ -1629,7 +1629,7 @@ WAREHOUSE = {
|
||||
-- @field #boolean arrived If true, asset arrived at its destination.
|
||||
--
|
||||
-- @field #number damage Damage of asset group in percent.
|
||||
-- @field Ops.Airwing#AIRWING.Payload payload The payload of the asset.
|
||||
-- @field Ops.AirWing#AIRWING.Payload payload The payload of the asset.
|
||||
-- @field Ops.OpsGroup#OPSGROUP flightgroup The flightgroup object.
|
||||
-- @field Ops.Cohort#COHORT cohort The cohort this asset belongs to.
|
||||
-- @field Ops.Legion#LEGION legion The legion this asset belonts to.
|
||||
@@ -3153,7 +3153,7 @@ end
|
||||
-- @param #WAREHOUSE self
|
||||
-- @return Core.Point#COORDINATE The coordinate of the warehouse.
|
||||
function WAREHOUSE:GetCoordinate()
|
||||
return self.warehouse:GetCoord()
|
||||
return self.warehouse:GetCoordinate()
|
||||
end
|
||||
|
||||
--- Get 3D vector of warehouse static.
|
||||
@@ -3414,7 +3414,7 @@ end
|
||||
-- FSM states
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the warehouse. Adds event handlers and schedules status updates of reqests and queue.
|
||||
--- On after Start event. Starts the warehouse. Addes event handlers and schedules status updates of reqests and queue.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
@@ -3595,7 +3595,6 @@ function WAREHOUSE:onafterStatus(From, Event, To)
|
||||
local Trepair=self:GetRunwayRepairtime()
|
||||
self:I(self.lid..string.format("Runway destroyed! Will be repaired in %d sec", Trepair))
|
||||
if Trepair==0 then
|
||||
self.runwaydestroyed = nil
|
||||
self:RunwayRepaired()
|
||||
end
|
||||
end
|
||||
@@ -5393,8 +5392,7 @@ function WAREHOUSE:onafterRunwayDestroyed(From, Event, To)
|
||||
self:_InfoMessage(text)
|
||||
|
||||
self.runwaydestroyed=timer.getAbsTime()
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
--- On after "RunwayRepaired" event.
|
||||
@@ -5409,8 +5407,7 @@ function WAREHOUSE:onafterRunwayRepaired(From, Event, To)
|
||||
self:_InfoMessage(text)
|
||||
|
||||
self.runwaydestroyed=nil
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -6047,7 +6044,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
|
||||
else
|
||||
|
||||
if parking and #parking<#template.units and not airstart then
|
||||
if #parking<#template.units and not airstart 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
|
||||
@@ -6089,7 +6086,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
terminal=parking[i].TerminalID
|
||||
end
|
||||
|
||||
if self.Debug and terminal then
|
||||
if self.Debug then
|
||||
local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)
|
||||
coord:MarkToAll(text)
|
||||
env.info(text)
|
||||
@@ -6732,7 +6729,7 @@ end
|
||||
-- @param Wrapper.Group#GROUP deadgroup Group of unit that died.
|
||||
-- @param #WAREHOUSE.Pendingitem request Request that needs to be updated.
|
||||
function WAREHOUSE:_UnitDead(deadunit, deadgroup, request)
|
||||
--self:F(self.lid.."FF unit dead "..deadunit:GetName())
|
||||
self:F(self.lid.."FF unit dead "..deadunit:GetName())
|
||||
|
||||
-- Find opsgroup.
|
||||
local opsgroup=_DATABASE:FindOpsGroup(deadgroup)
|
||||
@@ -6893,7 +6890,7 @@ function WAREHOUSE:_CheckConquered()
|
||||
for _,_unit in pairs(units) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
local distance=coord:Get2DDistance(unit:GetCoord())
|
||||
local distance=coord:Get2DDistance(unit:GetCoordinate())
|
||||
|
||||
-- Filter only alive groud units. Also check distance again, because the scan routine might give some larger distances.
|
||||
if unit:IsGround() and unit:IsAlive() and distance <= radius then
|
||||
@@ -7946,12 +7943,10 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
|
||||
local clients=_DATABASE.CLIENTS
|
||||
for clientname, client in pairs(clients) do
|
||||
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
|
||||
if template then
|
||||
local units=template.units
|
||||
for i,unit in pairs(units) do
|
||||
local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
|
||||
coords[unit.name]=coord
|
||||
end
|
||||
local units=template.units
|
||||
for i,unit in pairs(units) do
|
||||
local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
|
||||
coords[unit.name]=coord
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8122,11 +8117,9 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
|
||||
-- Debug output for occupied spots.
|
||||
if self.Debug then
|
||||
local coord=problem.coord --Core.Point#COORDINATE
|
||||
if coord then
|
||||
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
|
||||
self:I(self.lid..text)
|
||||
coord:MarkToAll(text)
|
||||
end
|
||||
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
|
||||
self:I(self.lid..text)
|
||||
coord:MarkToAll(string.format(text))
|
||||
else
|
||||
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
|
||||
end
|
||||
@@ -8435,14 +8428,12 @@ function WAREHOUSE:_GetAttribute(group)
|
||||
local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN --#WAREHOUSE.Attribute
|
||||
|
||||
if group then
|
||||
|
||||
local groupCat=group:GetCategory()
|
||||
|
||||
-----------
|
||||
--- Air ---
|
||||
-----------
|
||||
-- Planes
|
||||
local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes") and groupCat==Group.Category.AIRPLANE
|
||||
local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes")
|
||||
local awacs=group:HasAttribute("AWACS")
|
||||
local fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") or (group:HasAttribute("Bombers") and not group:HasAttribute("Strategic bombers"))
|
||||
local bomber=group:HasAttribute("Strategic bombers")
|
||||
@@ -8597,6 +8588,7 @@ end
|
||||
-- @param #WAREHOUSE.Queueitem qitem Item of queue to be removed.
|
||||
-- @param #table queue The queue from which the item should be deleted.
|
||||
function WAREHOUSE:_DeleteQueueItem(qitem, queue)
|
||||
self:F({qitem=qitem, queue=queue})
|
||||
|
||||
for i=1,#queue do
|
||||
local _item=queue[i] --#WAREHOUSE.Queueitem
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
do -- ZONE_CAPTURE_COALITION
|
||||
|
||||
-- @type ZONE_CAPTURE_COALITION
|
||||
--- @type ZONE_CAPTURE_COALITION
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number MarkBlue ID of blue F10 mark.
|
||||
-- @field #number MarkRed ID of red F10 mark.
|
||||
@@ -161,7 +161,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
-- The mission designer can use these values to alter the logic.
|
||||
-- For example:
|
||||
--
|
||||
-- -- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
|
||||
-- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
|
||||
-- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To )
|
||||
-- if From ~= "Empty" then
|
||||
-- -- Display a message
|
||||
@@ -172,7 +172,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
--
|
||||
-- ## Example Event Handler.
|
||||
--
|
||||
-- -- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
|
||||
-- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
|
||||
-- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To )
|
||||
-- if From ~= To then
|
||||
-- local Coalition = self:GetCoalition()
|
||||
@@ -273,7 +273,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
-- Depending on the zone ownership, different messages are sent.
|
||||
-- Note the methods `ZoneCaptureCoalition:GetZoneName()`.
|
||||
--
|
||||
-- -- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
|
||||
-- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
|
||||
-- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To )
|
||||
-- if From ~= To then
|
||||
-- local Coalition = self:GetCoalition()
|
||||
@@ -294,7 +294,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
-- Next is the Event Handler when the **Empty** state transition is triggered.
|
||||
-- Now we smoke the ZoneCaptureCoalition with a green color, using `self:Smoke( SMOKECOLOR.Green )`.
|
||||
--
|
||||
-- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self
|
||||
-- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self
|
||||
-- function ZoneCaptureCoalition:OnEnterEmpty()
|
||||
-- self:Smoke( SMOKECOLOR.Green )
|
||||
-- US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
|
||||
@@ -304,7 +304,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
-- The next Event Handlers speak for itself.
|
||||
-- When the zone is Attacked, we smoke the zone white and send some messages to each coalition.
|
||||
--
|
||||
-- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self
|
||||
-- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self
|
||||
-- function ZoneCaptureCoalition:OnEnterAttacked()
|
||||
-- ZoneCaptureCoalition:Smoke( SMOKECOLOR.White )
|
||||
-- local Coalition = self:GetCoalition()
|
||||
@@ -321,7 +321,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
-- When the zone is Captured, we send some victory or loss messages to the correct coalition.
|
||||
-- And we add some score.
|
||||
--
|
||||
-- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self
|
||||
-- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self
|
||||
-- function ZoneCaptureCoalition:OnEnterCaptured()
|
||||
-- local Coalition = self:GetCoalition()
|
||||
-- self:E({Coalition = Coalition})
|
||||
@@ -641,7 +641,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
--
|
||||
-- @usage
|
||||
-- -- For example, one could stop the monitoring when the zone was captured!
|
||||
-- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self
|
||||
-- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self
|
||||
-- function ZoneCaptureCoalition:OnEnterCaptured()
|
||||
-- local Coalition = self:GetCoalition()
|
||||
-- self:E({Coalition = Coalition})
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
do -- Zone
|
||||
|
||||
-- @type ZONE_GOAL
|
||||
--- @type ZONE_GOAL
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field Core.Goal#GOAL Goal The goal object.
|
||||
-- @field #number SmokeTime Time stamp in seconds when the last smoke of the zone was triggered.
|
||||
@@ -178,7 +178,7 @@ do -- Zone
|
||||
|
||||
end
|
||||
|
||||
-- @param #ZONE_GOAL self
|
||||
--- @param #ZONE_GOAL self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data table.
|
||||
function ZONE_GOAL:__Destroyed( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
--
|
||||
-- # Developer Note
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE
|
||||
-- Therefore, this class is considered to be deprecated
|
||||
--
|
||||
@@ -23,7 +21,7 @@
|
||||
|
||||
do -- ZoneGoal
|
||||
|
||||
-- @type ZONE_GOAL_CARGO
|
||||
--- @type ZONE_GOAL_CARGO
|
||||
-- @extends Functional.ZoneGoal#ZONE_GOAL
|
||||
|
||||
|
||||
@@ -57,7 +55,7 @@ do -- ZoneGoal
|
||||
ClassName = "ZONE_GOAL_CARGO",
|
||||
}
|
||||
|
||||
-- @field #table ZONE_GOAL_CARGO.States
|
||||
--- @field #table ZONE_GOAL_CARGO.States
|
||||
ZONE_GOAL_CARGO.States = {}
|
||||
|
||||
--- ZONE_GOAL_CARGO Constructor.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Functional (WIP)** - Base class modeling processes to achieve goals involving coalition zones.
|
||||
--- **Functional** - Base class that models processes to achieve goals involving a Zone for a Coalition.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
do -- ZoneGoal
|
||||
|
||||
-- @type ZONE_GOAL_COALITION
|
||||
--- @type ZONE_GOAL_COALITION
|
||||
-- @field #string ClassName Name of the Class.
|
||||
-- @field #number Coalition The current coalition ID of the zone owner.
|
||||
-- @field #number PreviousCoalition The previous owner of the zone.
|
||||
@@ -48,7 +48,7 @@ do -- ZoneGoal
|
||||
ObjectCategories = nil,
|
||||
}
|
||||
|
||||
-- @field #table ZONE_GOAL_COALITION.States
|
||||
--- @field #table ZONE_GOAL_COALITION.States
|
||||
ZONE_GOAL_COALITION.States = {}
|
||||
|
||||
--- ZONE_GOAL_COALITION Constructor.
|
||||
|
||||
@@ -10,7 +10,7 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
--- Settings
|
||||
_SETTINGS = SETTINGS:Set() -- Core.Settings#SETTINGS
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
--- Register cargos.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Enums.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Utils.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Profiler.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Templates.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/STTS.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/FiFo.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' )
|
||||
|
||||
@@ -31,7 +33,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Spot.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/MarkerOps_Base.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/TextAndSound.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Pathline.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/ClientMenu.lua')
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Object.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Identifiable.lua' )
|
||||
@@ -47,7 +48,12 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Marker.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Weapon.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Net.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Storage.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/DynamicCargo.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/Cargo.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoUnit.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoSlingload.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoCrate.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoGroup.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Scoring.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/CleanUp.lua' )
|
||||
@@ -71,13 +77,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Warehouse.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Fox.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Mantis.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Shorad.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/AICSAR.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/AmmoTruck.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Autolase.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ZoneGoalCargo.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Tiresias.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua')
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' )
|
||||
@@ -85,39 +84,43 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RescueHelo.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/ATIS.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/CTLD.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/CSAR.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/AirWing.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/ArmyGroup.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Auftrag.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Awacs.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Brigade.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Chief.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Cohort.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Commander.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Fleet.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/FlightControl.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/FlightGroup.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Flotilla.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Intelligence.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Legion.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/NavyGroup.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Operation.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/OpsGroup.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/OpsTransport.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/OpsZone.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Platoon.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/PlayerTask.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/PlayerRecce.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/ShapeBase.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Cube.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Line.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Oval.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Polygon.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Triangle.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air_Patrol.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air_Engage.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Patrol.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Cap.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Gci.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Dispatcher.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_BAI.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_CAS.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_SEAD.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_Dispatcher.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Patrol.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_CAP.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_CAS.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_BAI.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Formation.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Request.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Dispatcher.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Dispatcher_Request.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_APC.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Helicopter.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Airplane.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Ship.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_APC.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assign.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Route.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Account.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assist.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/UserSound.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/SoundOutput.lua' )
|
||||
@@ -126,4 +129,21 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/RadioQueue.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/RadioSpeech.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/SRS.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/CommandCenter.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Mission.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/TaskInfo.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Manager.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/DetectionManager.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2G_Dispatcher.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2G.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2A_Dispatcher.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2A.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_CARGO.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Transport.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_CSAR.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Dispatcher.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Zone.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Dispatcher.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Globals.lua' )
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user