Compare commits

..

29 Commits

Author SHA1 Message Date
FlightControl
bd9b844909 Static 2017-07-11 16:57:53 +02:00
FlightControl
3c4f0a4b97 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-07-11 16:57:05 +02:00
FlightControl
9a40bceec5 Static 2017-07-08 14:19:43 +02:00
FlightControl
a402235da3 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-07-08 14:19:14 +02:00
FlightControl
62b156c0e8 Static 2017-07-08 09:23:01 +02:00
FlightControl
752de77a82 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-07-08 09:22:07 +02:00
FlightControl
e658a2a3bf Static 2017-07-07 08:26:33 +02:00
FlightControl
4b8b155fb8 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-07-07 08:25:57 +02:00
FlightControl
ce2b982782 Static 2017-07-06 21:51:12 +02:00
FlightControl
af61fc6c74 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-07-06 21:50:48 +02:00
FlightControl
93f7d1f013 Static 2017-07-02 23:17:59 +02:00
FlightControl
51f8c532ca Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-07-02 23:17:28 +02:00
FlightControl
f18a56ab1a Static 2017-07-02 12:59:40 +02:00
FlightControl
9dc9fcfeff Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre
# Conflicts:
#	Moose Mission Setup/Moose.lua
2017-07-02 12:58:26 +02:00
FlightControl
b217e855e9 Static 2017-06-30 10:38:30 +02:00
FlightControl
21c19bf97e Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre
# Conflicts:
#	Moose Mission Setup/Moose.lua
2017-06-30 10:38:11 +02:00
FlightControl
565237b29e Static 2017-06-29 15:00:12 +02:00
FlightControl
78085d3de2 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-06-29 14:59:32 +02:00
FlightControl
b245292d11 Static version 2017-06-29 13:53:13 +02:00
FlightControl
dd7d5d9a56 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-06-29 13:45:03 +02:00
FlightControl
ce688aabcb New static version 2017-06-29 10:40:56 +02:00
FlightControl
8e2e2e5a1e Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-06-29 10:38:52 +02:00
FlightControl
a1b31e0065 Updated message bug 2017-06-26 20:52:30 +02:00
FlightControl
8af5de6fb5 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-06-26 20:52:06 +02:00
FlightControl
572d1f2902 Pre release 2017-06-26 20:35:28 +02:00
FlightControl
4a7f8b869d Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-06-26 20:35:05 +02:00
FlightControl
967c2a5633 New version 2017-06-26 10:39:32 +02:00
FlightControl
0203e73c71 Merge remote-tracking branch 'refs/remotes/origin/master' into release-2-2-pre 2017-06-25 07:41:47 +02:00
FlightControl
05e7eb3456 Static 2017-06-24 15:48:50 +02:00
864 changed files with 276453 additions and 52260 deletions

View File

@@ -1,85 +0,0 @@
version: 2.4.a.{build}
shallow_clone: true
skip_branch_with_pr: false
skip_commits:
message: /!nobuild/
skip_tags: false
environment:
access_token_documentation:
secure: JVBVVL8uJUcLXN+48eRdELEeCGOGCCaMzCqutsUqNuaZ/KblG5ZTt7+LV4UKv/0f
LUAROCKS_VER: 2.4.1
LUA_VER: 5.1.5
LUA: lua5.3
matrix:
- LUA_VER: 5.1.5
platform:
- x64
init:
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
throw "There are newer queued builds for this pull request, failing early." }
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
install:
# Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache.
# - call choco install 7zip.commandline
# - call choco install lua51
# - call choco install luarocks
# - call refreshenv
# - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
# - cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin
# - cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua
# - cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll
# - call luarocks install luasrcdiet
# - call luarocks install checks
# - call luarocks install luadocumentor
# - call luarocks install luacheck
#cache:
# - C:\ProgramData\chocolatey\lib
# - C:\ProgramData\chocolatey\bin
build_script:
- ps: |
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
{
$apiUrl = 'https://ci.appveyor.com/api'
$token = 'qts80b5kpq0ooj4x6vvw'
$headers = @{
"Authorization" = "Bearer $token"
"Content-type" = "application/json"
}
$RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-include'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json
# Generate the new version ...
$project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody
}
- ps: |
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
{
$apiUrl = 'https://ci.appveyor.com/api'
$token = 'qts80b5kpq0ooj4x6vvw'
$headers = @{
"Authorization" = "Bearer $token"
"Content-type" = "application/json"
}
$RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-docs'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json
# get project with last build details
$project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody
}
test: off
# test_script:
# - cmd: luacheck "Moose Development\Moose\moose.lua" "Moose Mission Setup\moose.lua"
on_finish:
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))

4
.gitmodules vendored
View File

@@ -0,0 +1,4 @@
[submodule "Moose Development/Moose/Dcs"]
path = Moose Development/Moose/Dcs
url = https://github.com/FlightControl-Master/DCS-API.git
branch = master

4
DCS_Folder_Sync.bat Normal file
View File

@@ -0,0 +1,4 @@
rem This script will pull the latest changes from the remote repository, and update the submodules accordingly.
C:\Program Files (x86)\Git\bin\git pull
C:\Program Files (x86)\Git\bin\git submodule update --init

View File

@@ -1,37 +0,0 @@
--Initialization script for the Mission lua Environment (SSE)
dofile('Scripts/ScriptingSystem.lua')
-- Add LuaSocket to the LUAPATH, so that it can be found.
package.path = package.path..";.\\LuaSocket\\?.lua;"
-- Connect to the debugger, first require it.
local initconnection = require("debugger")
-- Now make the connection..
-- "127.0.0.1" is the localhost.
-- 10000 is the port. If you wanna use another port in LDT, change this number too!
-- "dcsserver" is the name of the server. If you wanna use another name, change the name here too!
-- nil (is for transport protocol, but not using this)
-- "win" don't touch. But is important to indicate that we are in a windows environment to the debugger script.
initconnection( "127.0.0.1", 10000, "dcsserver", nil, "win", "" )
--Sanitize Mission Scripting environment
--This makes unavailable some unsecure functions.
--Mission downloaded from server to client may contain potentialy harmful lua code that may use these functions.
--You can remove the code below and make availble these functions at your own risk.
local function sanitizeModule(name)
_G[name] = nil
package.loaded[name] = nil
end
do
sanitizeModule('os')
--sanitizeModule('io')
sanitizeModule('lfs')
require = nil
loadlib = nil
end

View File

@@ -1,56 +0,0 @@
-- If you want to use the debugger, add 3 lines of extra code into MissionScripting.lua of DCS world.
-- De-sanitize the io module. The debugger needs it.
---------------------------------------------------------------------------------------------------------------------------
-- MissionScripting.lua modifications
---------------------------------------------------------------------------------------------------------------------------
-- --Initialization script for the Mission lua Environment (SSE)
--
-- dofile('Scripts/ScriptingSystem.lua')
--
-- package.path = package.path..";.\\LuaSocket\\?.lua;"
-- local initconnection = require("debugger")
-- initconnection( "127.0.0.1", 10000, "dcsserver", nil, "win", "" )
--
-- --Sanitize Mission Scripting environment
-- --This makes unavailable some unsecure functions.
-- --Mission downloaded from server to client may contain potentialy harmful lua code that may use these functions.
-- --You can remove the code below and make availble these functions at your own risk.
--
-- local function sanitizeModule(name)
-- _G[name] = nil
-- package.loaded[name] = nil
-- end
--
-- do
-- sanitizeModule('os')
-- --sanitizeModule('io')
-- sanitizeModule('lfs')
-- require = nil
-- loadlib = nil
-- end
---------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
-- So for clarity, these are the three lines of code that matter!
-- Add LuaSocket to the LUAPATH, so that it can be found.
package.path = package.path..";.\\LuaSocket\\?.lua;"
-- Connect to the debugger, first require it.
local initconnection = require("debugger")
-- Now make the connection..
-- "127.0.0.1" is the localhost.
-- 10000 is the port. If you wanna use another port in LDT, change this number too!
-- "dcsserver" is the name of the server. Ensure the same name is used at the Debug Configuration panel!
-- nil (is for transport protocol, but not using this)
-- "win" don't touch. But is important to indicate that we are in a windows environment to the debugger script.
initconnection( "127.0.0.1", 10000, "dcsserver", nil, "win", "" )

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/GenerateDocumentations.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="*.lua"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Utils}"/>
</launchConfiguration>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;D&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Utils/luarocks/lua5.1.exe}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose_Create.lua&quot; &#13;&#10;&quot;S&quot;&#13;&#10;&quot;${current_date}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework//Moose Development/Moose}&quot; &#13;&#10;&quot;${workspace_loc:/Moose_Framework/Moose Mission Setup}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup}"/>
</launchConfiguration>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Moose Mission Setup/Moose Mission Update/Moose_Update_Missions.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Missions}"/>
</launchConfiguration>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
</listAttribute>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/Moose Mission Setup/Moose Mission Update/Moose_Update_Missions.bat}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;${selected_resource_loc}&quot;"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework/Moose Mission Setup/Moose Mission Update}"/>
</launchConfiguration>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,13 +1,16 @@
--- **AI** -- (R2.2) - Models the process of air operations for airplanes.
--- **AI** -- **AI A2A Air Patrolling or Staging.**
--
-- ===
-- ====
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ===
-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: 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](https://forums.eagle.ru/member.php?u=62835)**: Testing and API concept review.
--
-- @module AI.AI_A2A
-- @image AI_Air_To_Air_Dispatching.JPG
-- ====
--
-- @module AI_A2A
--BASE:TraceClass("AI_A2A")
@@ -15,7 +18,9 @@
--- @type AI_A2A
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- The AI_A2A class implements the core functions to operate an AI @{Wrapper.Group} A2A tasking.
--- # AI_A2A class, extends @{Fsm#FSM_CONTROLLABLE}
--
-- The AI_A2A class implements the core functions to operate an AI @{Group} A2A tasking.
--
--
-- ## AI_A2A constructor
@@ -65,9 +70,8 @@ function AI_A2A:New( AIGroup )
self:SetControllable( AIGroup )
self:SetFuelThreshold( .2, 60 )
self:SetDamageThreshold( 0.4 )
self:SetDisengageRadius( 70000 )
self:ManageFuel( .2, 60 )
self:ManageDamage( 0.4 )
self:SetStartState( "Stopped" )
@@ -215,37 +219,8 @@ function AI_A2A:New( AIGroup )
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "Refuel", "Refuelling" )
--- Refuel Handler OnBefore for AI_A2A
-- @function [parent=#AI_A2A] OnBeforeRefuel
-- @param #AI_A2A 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_A2A
-- @function [parent=#AI_A2A] OnAfterRefuel
-- @param #AI_A2A 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_A2A
-- @function [parent=#AI_A2A] Refuel
-- @param #AI_A2A self
--- Refuel Asynchronous Trigger for AI_A2A
-- @function [parent=#AI_A2A] __Refuel
-- @param #AI_A2A 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" )
@@ -259,13 +234,6 @@ function AI_A2A:New( AIGroup )
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_A2A:SetDispatcher( Dispatcher )
self.Dispatcher = Dispatcher
end
@@ -292,8 +260,8 @@ end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_A2A 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.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @return #AI_A2A self
function AI_A2A:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
@@ -305,8 +273,8 @@ end
--- Sets the floor and ceiling altitude of the patrol.
-- @param #AI_A2A 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.
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @return #AI_A2A self
function AI_A2A:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
@@ -326,27 +294,8 @@ function AI_A2A:SetHomeAirbase( HomeAirbase )
self.HomeAirbase = HomeAirbase
end
--- Sets to refuel at the given tanker.
-- @param #AI_A2A self
-- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned.
-- @return #AI_A2A self
function AI_A2A: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_A2A self
-- @param #number DisengageRadius The disengage range.
-- @return #AI_A2A self
function AI_A2A:SetDisengageRadius( DisengageRadius )
self:F2( { DisengageRadius } )
self.DisengageRadius = DisengageRadius
end
--- Set the status checking off.
-- @param #AI_A2A self
-- @return #AI_A2A self
@@ -362,12 +311,13 @@ end
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_A2A.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_A2A self
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number PatrolFuelTresholdPercentage The treshold 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_A2A self
function AI_A2A:SetFuelThreshold( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
function AI_A2A:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime )
self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage
self.PatrolManageFuel = true
self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
self.Controllable:OptionRTBBingoFuel( false )
@@ -382,12 +332,12 @@ end
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
-- @param #AI_A2A self
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @param #number PatrolDamageTreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_A2A self
function AI_A2A:SetDamageThreshold( PatrolDamageThreshold )
function AI_A2A:ManageDamage( PatrolDamageTreshold )
self.PatrolManageDamage = true
self.PatrolDamageThreshold = PatrolDamageThreshold
self.PatrolDamageTreshold = PatrolDamageTreshold
return self
end
@@ -422,79 +372,45 @@ end
--- @param #AI_A2A self
function AI_A2A:onafterStatus()
self:F( " Checking Status" )
self:F()
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() )
self:F({DistanceFromHomeBase=DistanceFromHomeBase})
local Fuel = self.Controllable:GetUnit(1):GetFuel()
self:F({Fuel=Fuel})
if Fuel < self.PatrolFuelTresholdPercentage then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable
local AIControllableTemplate = self.Controllable:GetTemplate()
if DistanceFromHomeBase > self.DisengageRadius then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 )
RTB = false
end
end
if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then
if DistanceFromHomeBase < 5000 then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Home( "Destroy" )
end
end
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 )
if not self:Is( "Fuel" ) and not self:Is( "Home" ) then
local Fuel = self.Controllable:GetFuelMin()
self:F({Fuel=Fuel})
if Fuel < self.PatrolFuelThresholdPercentage then
if self.TankerName then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel()
else
self:E( 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 )
self:Fuel()
RTB = true
end
else
end
self:Fuel()
RTB = true
else
end
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
local InitialLife = self.Controllable:GetLife0()
self:F( { Damage = Damage, InitialLife = InitialLife, DamageThreshold = self.PatrolDamageThreshold } )
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
self:F( { Damage = Damage, InitialLife = InitialLife, DamageTreshold = self.PatrolDamageTreshold } )
if ( Damage / InitialLife ) < self.PatrolDamageTreshold then
self:E( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:Damaged()
RTB = true
self:SetStatusOff()
end
-- Check if planes went RTB and are out of control.
if self.Controllable:HasTask() == false then
if not self:Is( "Started" ) and
not self:Is( "Stopped" ) and
not self:Is( "Home" ) then
not self:Is( "Stopped" ) then
if self.IdleCount >= 2 then
if Damage ~= InitialLife then
self:Damaged()
else
self:E( self.Controllable:GetName() .. " control lost! " )
self:LostControl()
end
self:E( self.Controllable:GetName() .. " control lost! " )
self:LostControl()
else
self.IdleCount = self.IdleCount + 1
end
@@ -502,7 +418,7 @@ function AI_A2A:onafterStatus()
else
self.IdleCount = 0
end
if RTB == true then
self:__RTB( 0.5 )
end
@@ -513,28 +429,13 @@ end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.RTBRoute( AIGroup, Fsm )
function AI_A2A.RTBRoute( AIGroup )
AIGroup:F( { "AI_A2A.RTBRoute:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
end
AIGroup:E( { "RTBRoute:", AIGroup:GetName() } )
local _AI_A2A = AIGroup:GetState( AIGroup, "AI_A2A" ) -- #AI_A2A
_AI_A2A:__RTB( 0.5 )
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.RTBHold( AIGroup, Fsm )
AIGroup:F( { "AI_A2A.RTBHold:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
Fsm:Return()
local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
AIGroup:SetTask( Task )
end
end
--- @param #AI_A2A self
@@ -547,6 +448,8 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self.CheckStatus = false
self:ClearTargetDistance()
AIGroup:ClearTasks()
@@ -563,12 +466,11 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle )
if Distance < 5000 then
self:E( "RTB and near the airbase!" )
self:Home()
return
end
--- Create a route point of type air.
local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir(
local ToPatrolRoutePoint = ToAirbaseCoord:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -579,8 +481,7 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
self:F( { Angle = ToAirbaseAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
@@ -589,11 +490,13 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
AIGroup:WayPointInitialize( EngageRoute )
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_A2A.RTBRoute", self )
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks )
Tasks[#Tasks+1] = AIGroup:TaskFunction( 1, 1, "AI_A2A.RTBRoute" )
EngageRoute[1].task = AIGroup:TaskCombo( Tasks )
AIGroup:SetState( AIGroup, "AI_A2A", self )
--- NOW ROUTE THE GROUP!
AIGroup:Route( EngageRoute, 0.5 )
AIGroup:WayPointExecute( 1, 0 )
end
@@ -610,89 +513,6 @@ function AI_A2A:onafterHome( AIGroup, From, Event, To )
end
end
--- @param #AI_A2A self
-- @param Wrapper.Group#GROUP AIGroup
function AI_A2A:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } )
self:E( "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_A2A.RTBHold", self )
local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
--AIGroup:SetState( AIGroup, "AI_A2A", self )
AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 )
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A.Resume( AIGroup, Fsm )
AIGroup:F( { "AI_A2A.Resume:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
end
end
--- @param #AI_A2A self
-- @param Wrapper.Group#GROUP AIGroup
function AI_A2A:onafterRefuel( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local Tanker = GROUP:FindByName( self.TankerName )
if Tanker:IsAlive() and Tanker:IsAirPlane() then
local RefuelRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local ToRefuelCoord = Tanker:GetCoordinate()
local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local ToRefuelRoutePoint = ToRefuelCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToRefuelSpeed,
true
)
self:F( { ToRefuelSpeed = ToRefuelSpeed } )
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskRefueling()
Tasks[#Tasks+1] = AIGroup:TaskFunction( self:GetClassName() .. ".Resume", self )
RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks )
AIGroup:Route( RefuelRoute, 0.5 )
else
self:RTB()
end
end
end

View File

@@ -1,24 +1,45 @@
--- **AI** -- (R2.2) - Models the process of Combat Air Patrol (CAP) for airplanes.
--- **AI** -- **Execute Combat Air Patrol (CAP).**
--
-- ![Banner Image](..\Presentations\AI_CAP\Dia1.JPG)
--
-- ===
--
-- ### Author: **FlightControl**
-- AI CAP classes makes AI Controllables execute a Combat Air Patrol.
--
-- ===
-- There are the following types of CAP classes defined:
--
-- * @{#AI_A2A_CAP}: Perform a CAP in a zone.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Contributions:
--
-- @module AI.AI_A2A_Cap
-- @image AI_Combat_Air_Patrol.JPG
-- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing.
-- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Concept, Advice & Testing.
-- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision.
-- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing.
-- * **[Delta99](https://forums.eagle.ru/member.php?u=125166): Testing.
--
-- ====
--
-- @module AI_A2A_Cap
--BASE:TraceClass("AI_A2A_CAP")
--- @type AI_A2A_CAP
-- @extends AI.AI_A2A_Patrol#AI_A2A_PATROL
--- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
--- # AI_A2A_CAP class, extends @{AI_CAP#AI_PATROL_ZONE}
--
-- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event.
-- The AI_A2A_CAP is assigned a @{Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia4.JPG)
--
@@ -61,15 +82,15 @@
--
-- ### 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_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_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.
-- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Unit}.
-- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
@@ -80,7 +101,7 @@
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{AI.AI_CAP#AI_A2A_CAP.SetEngageRange}() to define that range.
-- Use the method @{AI_CAP#AI_A2A_CAP.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
@@ -88,7 +109,7 @@
--
-- An optional @{Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_Cap#AI_A2A_CAP.SetEngageZone}() to define that Zone.
-- Use the method @{AI_Cap#AI_A2A_CAP.SetEngageZone}() to define that Zone.
--
-- ===
--
@@ -99,20 +120,20 @@ AI_A2A_CAP = {
--- Creates a new AI_A2A_CAP object
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap
-- @param Wrapper.Group#GROUP AIGroup
-- @param Core.Zone#ZONE_BASE PatrolZone The @{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
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed EngageMinSpeed The minimum speed of the @{Controllable} in km/h when engaging a target.
-- @param Dcs.DCSTypes#Speed EngageMaxSpeed The maximum speed of the @{Controllable} in km/h when engaging a target.
-- @param Dcs.DCSTypes#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 )
function AI_A2A_CAP:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A_PATROL:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP
local self = BASE:Inherit( self, AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP
self.Accomplished = false
self.Engaging = false
@@ -120,12 +141,12 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
self.EngageMinSpeed = EngageMinSpeed
self.EngageMaxSpeed = EngageMaxSpeed
self:AddTransition( { "Patrolling", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
self:AddTransition( { "Patrolling", "Engaging", "Returning" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_CAP] OnBeforeEngage
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -134,7 +155,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_CAP] OnAfterEngage
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -151,7 +172,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_CAP] OnLeaveEngaging
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -160,7 +181,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_CAP] OnEnterEngaging
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -170,7 +191,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_CAP] OnBeforeFired
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -179,7 +200,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_CAP] OnAfterFired
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -198,7 +219,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_CAP] OnBeforeDestroy
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -207,7 +228,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_CAP] OnAfterDestroy
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -227,7 +248,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_CAP] OnBeforeAbort
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -236,7 +257,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_CAP] OnAfterAbort
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -255,7 +276,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_CAP] OnBeforeAccomplish
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -264,7 +285,7 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_CAP] OnAfterAccomplish
-- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -281,17 +302,6 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
return self
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2A_GCI 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 )
AICap:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- Set the Engage Zone which defines where the AI will engage bogies.
-- @param #AI_A2A_CAP self
@@ -323,36 +333,33 @@ 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 Wrapper.Controllable#CONTROLLABLE 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_A2A_CAP:onafterPatrol( AICap, From, Event, To )
function AI_A2A_CAP:onafterPatrol( AIGroup, From, Event, To )
-- Call the parent Start event handler
self:GetParent(self).onafterPatrol( self, AICap, From, Event, To )
self:GetParent(self).onafterPatrol( self, AIGroup, From, Event, To )
self:HandleEvent( EVENTS.Dead )
end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AICap
function AI_A2A_CAP.AttackRoute( AICap, Fsm )
--- @param Wrapper.Group#GROUP AIGroup
function AI_A2A_CAP.AttackRoute( AIGroup )
AICap:F( { "AI_A2A_CAP.AttackRoute:", AICap:GetName() } )
if AICap:IsAlive() then
Fsm:__Engage( 0.5 )
end
local EngageZone = AIGroup:GetState( AIGroup, "AI_A2A_CAP" ) -- AI.AI_Cap#AI_A2A_CAP
EngageZone:__Engage( 0.5 )
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE AIGroup The Controllable Object 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:onbeforeEngage( AICap, From, Event, To )
function AI_A2A_CAP:onbeforeEngage( AIGroup, From, Event, To )
if self.Accomplished == true then
return false
@@ -360,43 +367,43 @@ function AI_A2A_CAP:onbeforeEngage( AICap, From, Event, To )
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE 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_A2A_CAP:onafterAbort( AICap, From, Event, To )
AICap:ClearTasks()
function AI_A2A_CAP:onafterAbort( AIGroup, From, Event, To )
AIGroup:ClearTasks()
self:__Route( 0.5 )
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The AICap Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE AIGroup The Controllable Object 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:onafterEngage( AICap, From, Event, To, AttackSetUnit )
function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
self:F( { AICap, From, Event, To, AttackSetUnit} )
self:F( { AIGroup, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local FirstAttackUnit = self.AttackSetUnit:GetFirst() -- Wrapper.Unit#UNIT
local FirstAttackUnit = self.AttackSetUnit:GetFirst()
if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement.
if FirstAttackUnit then
if AICap:IsAlive() then
if AIGroup:IsAlive() then
local EngageRoute = {}
--- Calculate the target route point.
local CurrentCoord = AICap:GetCoordinate()
local CurrentCoord = AIGroup:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
--- Create a route point of type air.
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):WaypointAir(
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -407,31 +414,40 @@ function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit )
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
AIGroup:OptionROEOpenFire()
AIGroup:OptionROTPassiveDefense()
local AttackTasks = {}
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
AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit )
AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit )
end
end
--- 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( EngageRoute )
if #AttackTasks == 0 then
self:E("No targets found -> Going back to Patrolling")
self:__Abort( 0.5 )
else
AICap:OptionROEOpenFire()
AICap:OptionROTEvadeFire()
AttackTasks[#AttackTasks+1] = AICap:TaskFunction( "AI_A2A_CAP.AttackRoute", self )
EngageRoute[#EngageRoute].task = AICap:TaskCombo( AttackTasks )
AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( 1, #AttackTasks, "AI_A2A_CAP.AttackRoute" )
AttackTasks[#AttackTasks+1] = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
EngageRoute[1].task = AIGroup:TaskCombo( AttackTasks )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
AIGroup:SetState( AIGroup, "AI_A2A_CAP", self )
end
AICap:Route( EngageRoute, 0.5 )
--- NOW ROUTE THE GROUP!
AIGroup:WayPointExecute( 1, 0 )
end
else
self:E("No targets found -> Going back to Patrolling")
@@ -440,22 +456,22 @@ function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit )
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object 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:onafterAccomplish( AICap, From, Event, To )
function AI_A2A_CAP:onafterAccomplish( Controllable, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2A_CAP self
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object 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_A2A_CAP:onafterDestroy( AICap, From, Event, To, EventData )
function AI_A2A_CAP:onafterDestroy( Controllable, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
@@ -473,15 +489,3 @@ function AI_A2A_CAP:OnEventDead( EventData )
end
end
end
--- @param Wrapper.Group#GROUP AICap
function AI_A2A_CAP.Resume( AICap )
AICap:F( { "AI_A2A_CAP.Resume:", AICap:GetName() } )
if AICap:IsAlive() then
local _AI_A2A = AICap:GetState( AICap, "AI_A2A" ) -- #AI_A2A
_AI_A2A:__Reset( 1 )
_AI_A2A:__Route( 5 )
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,40 @@
--- **AI** -- (R2.2) - Models the process of Ground Controlled Interception (GCI) for airplanes.
--- **AI** -- **Execute Ground Controlled Interception (GCI).**
--
-- This is a class used in the @{AI_A2A_Dispatcher}.
-- ![Banner Image](..\Presentations\AI_GCI\Dia1.JPG)
--
-- ===
--
-- ### Author: **FlightControl**
-- AI A2A_INTEREPT class makes AI Groups execute an Intercept.
--
-- ===
-- There are the following types of GCI classes defined:
--
-- * @{#AI_A2A_GCI}: Perform a GCI in a zone.
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Contributions:
--
-- ====
--
-- @module AI.AI_A2A_GCI
-- @image AI_Ground_Control_Intercept.JPG
-- @module AI_A2A_GCI
--BASE:TraceClass("AI_A2A_GCI")
--- @type AI_A2A_GCI
-- @extends AI.AI_A2A#AI_A2A
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--- # AI_A2A_GCI class, extends @{AI_A2A#AI_A2A}
--
-- The AI_A2A_GCI class implements the core functions to intercept intruders. The Engage function will intercept intruders.
--
-- ![Process](..\Presentations\AI_GCI\Dia3.JPG)
--
-- 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_A2A_GCI is assigned a @{Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia4.JPG)
--
@@ -64,15 +77,15 @@
--
-- ### 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_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_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.
-- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Unit}.
-- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
@@ -83,7 +96,7 @@
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{AI.AI_GCI#AI_A2A_GCI.SetEngageRange}() to define that range.
-- Use the method @{AI_GCI#AI_A2A_GCI.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
@@ -91,7 +104,7 @@
--
-- An optional @{Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_Cap#AI_A2A_GCI.SetEngageZone}() to define that Zone.
-- Use the method @{AI_Cap#AI_A2A_GCI.SetEngageZone}() to define that Zone.
--
-- ===
--
@@ -104,12 +117,12 @@ AI_A2A_GCI = {
--- Creates a new AI_A2A_GCI object
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept
-- @param Wrapper.Group#GROUP AIGroup
-- @return #AI_A2A_GCI
function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
function AI_A2A_GCI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A:New( AIIntercept ) ) -- #AI_A2A_GCI
local self = BASE:Inherit( self, AI_A2A:New( AIGroup ) ) -- #AI_A2A_GCI
self.Accomplished = false
self.Engaging = false
@@ -121,12 +134,12 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
self.PatrolAltType = "RADIO"
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
self:AddTransition( { "Started", "Engaging", "Returning" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_GCI] OnBeforeEngage
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -135,7 +148,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2A_GCI] OnAfterEngage
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -152,7 +165,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_GCI] OnLeaveEngaging
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -161,7 +174,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2A_GCI] OnEnterEngaging
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -171,7 +184,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_GCI] OnBeforeFired
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -180,7 +193,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2A_GCI] OnAfterFired
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -199,7 +212,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_GCI] OnBeforeDestroy
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -208,7 +221,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2A_GCI] OnAfterDestroy
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -228,7 +241,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_GCI] OnBeforeAbort
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -237,7 +250,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2A_GCI] OnAfterAbort
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -256,7 +269,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_GCI] OnBeforeAccomplish
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -265,7 +278,7 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2A_GCI] OnAfterAccomplish
-- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -282,27 +295,14 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
return self
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 )
AIIntercept:HandleEvent( EVENTS.Takeoff, nil, self )
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 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_A2A_GCI:onafterEngage( AIIntercept, From, Event, To )
function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To )
self:HandleEvent( EVENTS.Dead )
@@ -311,24 +311,19 @@ end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AIControllable
function AI_A2A_GCI.InterceptRoute( AIIntercept, Fsm )
function AI_A2A_GCI.InterceptRoute( AIControllable )
AIIntercept:F( { "AI_A2A_GCI.InterceptRoute:", AIIntercept:GetName() } )
if AIIntercept:IsAlive() then
Fsm:__Engage( 0.5 )
--local Task = AIIntercept:TaskOrbitCircle( 4000, 400 )
--AIIntercept:SetTask( Task )
end
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_A2A_GCI
EngageZone:__Engage( 0.5 )
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object 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:onbeforeEngage( AIIntercept, From, Event, To )
function AI_A2A_GCI:onbeforeEngage( AIGroup, From, Event, To )
if self.Accomplished == true then
return false
@@ -336,41 +331,39 @@ function AI_A2A_GCI:onbeforeEngage( AIIntercept, From, Event, To )
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
-- @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_A2A_GCI:onafterAbort( AIIntercept, From, Event, To )
AIIntercept:ClearTasks()
function AI_A2A_GCI:onafterAbort( AIGroup, From, Event, To )
AIGroup:ClearTasks()
self:Return()
self:__RTB( 0.5 )
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The GroupGroup managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object 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:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit )
function AI_A2A_GCI:onafterEngage( AIGroup, From, Event, To, AttackSetUnit )
self:F( { AIIntercept, From, Event, To, AttackSetUnit} )
self:F( { AIGroup, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local FirstAttackUnit = self.AttackSetUnit:GetFirst()
if FirstAttackUnit and FirstAttackUnit:IsAlive() then
if FirstAttackUnit then
if AIIntercept:IsAlive() then
if AIGroup:IsAlive() then
local EngageRoute = {}
local CurrentCoord = AIIntercept:GetCoordinate()
--- Calculate the target route point.
local CurrentCoord = AIIntercept:GetCoordinate()
local CurrentCoord = AIGroup:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
@@ -379,7 +372,7 @@ function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit )
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
--- Create a route point of type air.
local ToPatrolRoutePoint = CurrentCoord:Translate( 15000, ToInterceptAngle ):WaypointAir(
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -388,34 +381,42 @@ function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit )
)
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } )
self:T2( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
AIGroup:OptionROEOpenFire()
AIGroup:OptionROTPassiveDefense()
local AttackTasks = {}
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
AttackTasks[#AttackTasks+1] = AIIntercept:TaskAttackUnit( AttackUnit )
AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit )
end
end
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
AIGroup:WayPointInitialize( EngageRoute )
if #AttackTasks == 0 then
self:E("No targets found -> Going RTB")
self:Return()
self:__RTB( 0.5 )
else
AIIntercept:OptionROEOpenFire()
AIIntercept:OptionROTEvadeFire()
AttackTasks[#AttackTasks+1] = AIIntercept:TaskFunction( "AI_A2A_GCI.InterceptRoute", self )
EngageRoute[#EngageRoute].task = AIIntercept:TaskCombo( AttackTasks )
AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( 1, #AttackTasks, "AI_A2A_GCI.InterceptRoute" )
AttackTasks[#AttackTasks+1] = AIGroup:TaskOrbitCircle( 4000, self.EngageMinSpeed )
EngageRoute[1].task = AIGroup:TaskCombo( AttackTasks )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
AIGroup:SetState( AIGroup, "EngageZone", self )
end
AIIntercept:Route( EngageRoute, 0.5 )
--- NOW ROUTE THE GROUP!
AIGroup:WayPointExecute( 1, 0 )
end
else
@@ -426,22 +427,22 @@ function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit )
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object 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:onafterAccomplish( AIIntercept, From, Event, To )
function AI_A2A_GCI:onafterAccomplish( AIGroup, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2A_GCI self
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup Object 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_A2A_GCI:onafterDestroy( AIIntercept, From, Event, To, EventData )
function AI_A2A_GCI:onafterDestroy( AIGroup, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil

View File

@@ -1,23 +1,54 @@
--- **AI** -- (R2.2) - Models the process of air patrol of airplanes.
--- **AI** -- **Air Patrolling or Staging.**
--
-- ![Banner Image](..\Presentations\AI_PATROL\Dia1.JPG)
--
-- ===
--
-- ### Author: **FlightControl**
-- AI PATROL classes makes AI Controllables execute an Patrol.
--
-- ===
-- There are the following types of PATROL classes defined:
--
-- @module AI.AI_A2A_Patrol
-- @image AI_Air_Patrolling.JPG
-- * @{#AI_A2A_PATROL}: Perform a PATROL in a zone.
--
-- ====
--
-- # Demo Missions
--
-- ### [AI_PATROL Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling)
--
-- ### [AI_PATROL Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/PAT%20-%20Patrolling)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ====
--
-- # YouTube Channel
--
-- ### [AI_PATROL YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky)
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: 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](https://forums.eagle.ru/member.php?u=62835)**: Testing and API concept review.
--
-- ====
--
-- @module AI_A2A_Patrol
--- @type AI_A2A_PATROL
-- @extends AI.AI_A2A#AI_A2A
--- Implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}.
--- # AI_A2A_PATROL class, extends @{Fsm#FSM_CONTROLLABLE}
--
-- The AI_A2A_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}.
--
-- ![Process](..\Presentations\AI_PATROL\Dia3.JPG)
--
-- The AI_A2A_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event.
-- The AI_A2A_PATROL is assigned a @{Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_PATROL\Dia4.JPG)
--
@@ -89,8 +120,8 @@
-- * @{#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 frequency can be set with @{#AI_A2A_PATROL.SetDetectionInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
-- Use the method @{#AI_A2A_PATROL.GetDetectedUnits}() to obtain a list of the @{Unit}s detected by the AI.
--
-- 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.
@@ -108,7 +139,7 @@
--
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
--
-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
--
@@ -121,23 +152,23 @@ AI_A2A_PATROL = {
--- Creates a new AI_A2A_PATROL object
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol
-- @param Wrapper.Group#GROUP AIGroup
-- @param Core.Zone#ZONE_BASE PatrolZone The @{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
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @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.
-- -- Define a new AI_A2A_PATROL 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_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 )
function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A:New( AIPatrol ) ) -- #AI_A2A_PATROL
local self = BASE:Inherit( self, AI_A2A:New( AIGroup ) ) -- #AI_A2A_PATROL
self.PatrolZone = PatrolZone
self.PatrolFloorAltitude = PatrolFloorAltitude
@@ -148,12 +179,12 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
self:AddTransition( "Started", "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 Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -162,7 +193,7 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
--- 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 Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -179,7 +210,7 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
--- 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 Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -188,7 +219,7 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
--- 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 Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -198,7 +229,7 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
--- 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 Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -207,7 +238,7 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
--- 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 Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
@@ -221,8 +252,6 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
-- @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
@@ -233,8 +262,8 @@ 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.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @return #AI_A2A_PATROL self
function AI_A2A_PATROL:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
@@ -247,8 +276,8 @@ 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.
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @return #AI_A2A_PATROL self
function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
@@ -261,19 +290,20 @@ end
--- Defines a new patrol route using the @{Process_PatrolZone} 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 Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object 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 )
function AI_A2A_PATROL:onafterPatrol( Controllable, From, Event, To )
self:F2()
self:ClearTargetDistance()
self:__Route( 1 )
AIPatrol:OnReSpawn(
self.Controllable:OnReSpawn(
function( PatrolGroup )
self:E( "ReSpawn" )
self:__Reset( 1 )
self:__Route( 5 )
end
@@ -282,27 +312,23 @@ end
--- @param Wrapper.Group#GROUP AIPatrol
-- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm )
--- @param Wrapper.Group#GROUP AIGroup
-- This statis method is called from the route path within the last task at the last waaypoint of the Controllable.
-- Note that this method is required, as triggers the next route when patrolling for the Controllable.
function AI_A2A_PATROL.PatrolRoute( AIGroup )
AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
Fsm:Route()
end
local _AI_A2A_Patrol = AIGroup:GetState( AIGroup, "AI_A2A_PATROL" ) -- #AI_A2A_PATROL
_AI_A2A_Patrol:Route()
end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_A2A_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
-- @param Wrapper.Group#GROUP AIGroup The AIGroup 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 )
function AI_A2A_PATROL:onafterRoute( AIGroup, From, Event, To )
self:F2()
@@ -312,22 +338,22 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
end
if AIPatrol:IsAlive() then
if AIGroup:IsAlive() then
local PatrolRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIPatrol:GetCoordinate()
local CurrentCoord = AIGroup:GetCoordinate()
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) )
ToTargetCoord:SetAlt(math.random( self.PatrolFloorAltitude,self.PatrolCeilingAltitude ) )
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local ToPatrolRoutePoint = ToTargetCoord:WaypointAir(
local ToPatrolRoutePoint = ToTargetCoord:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -335,29 +361,22 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
true
)
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
AIGroup:WayPointInitialize( PatrolRoute )
local Tasks = {}
Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
Tasks[#Tasks+1] = AIGroup:TaskFunction( 1, 1, "AI_A2A_PATROL.PatrolRoute" )
AIPatrol:OptionROEReturnFire()
AIPatrol:OptionROTEvadeFire()
PatrolRoute[1].task = AIGroup:TaskCombo( Tasks )
--- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ...
AIGroup:SetState( AIGroup, "AI_A2A_PATROL", self )
AIPatrol:Route( PatrolRoute, 0.5 )
--- NOW ROUTE THE GROUP!
AIGroup:WayPointExecute( 1, 2 )
end
end
--- @param Wrapper.Group#GROUP AIPatrol
function AI_A2A_PATROL.Resume( AIPatrol )
AIPatrol:F( { "AI_A2A_PATROL.Resume:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
local _AI_A2A = AIPatrol:GetState( AIPatrol, "AI_A2A" ) -- AI.AI_A2A#AI_A2A
_AI_A2A:__Reset( 1 )
_AI_A2A:__Route( 5 )
end
end

View File

@@ -1,48 +1,60 @@
--- **AI** -- Peform Battlefield Area Interdiction (BAI) within an engagement zone.
--- **AI** -- **Provide Battlefield Air Interdiction (bombing).**
--
-- **Features:**
-- ![Banner Image](..\Presentations\AI_BAI\Dia1.JPG)
--
-- * 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.
-- ===
--
-- AI_BAI classes makes AI Controllables execute bombing tasks.
--
-- There are the following types of BAI classes defined:
--
-- * @{#AI_BAI_ZONE}: Perform a BAI in a zone.
--
-- ====
--
-- # Demo Missions
--
-- ### [AI_BAI Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/BOMB%20-%20Close%20Air%20Support)
--
-- ### [AI_BAI Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/BOMB%20-%20Close%20Air%20Support)
--
-- ===
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/BAI%20-%20Battlefield%20Air%20Interdiction)
-- ====
--
-- ===
-- # YouTube Channel
--
-- ### [YouTube Playlist]()
-- ### [AI_BAI YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2)
--
-- ===
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision.
--
-- ===
-- ====
--
-- @module AI.AI_Bai
-- @image AI_Battlefield_Air_Interdiction.JPG
-- @module AI_Bai
--- AI_BAI_ZONE class
-- @type AI_BAI_ZONE
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling.
-- @field Core.Zone#ZONE_BASE TargetZone The @{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 @{Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
--- # AI_BAI_ZONE class, extends @{AI_Patrol#AI_PATROL_ZONE}
--
-- AI_BAI_ZONE derives from the @{AI_Patrol#AI_PATROL_ZONE}, inheriting its methods and behaviour.
--
-- The AI_BAI_ZONE class implements the core functions to provide BattleGround Air Interdiction in an Engage @{Zone} by an AIR @{Controllable} or @{Group}.
-- The AI_BAI_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
--
-- ![HoldAndEngage](..\Presentations\AI_BAI\Dia3.JPG)
--
-- The AI_BAI_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_BAI_ZONE process can be started through the **Start** event.
-- The AI_BAI_ZONE is assigned a @{Group} and this must be done before the AI_BAI_ZONE process can be started through the **Start** event.
--
-- ![Start Event](..\Presentations\AI_BAI\Dia4.JPG)
--
@@ -108,15 +120,15 @@
--
-- ### 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_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_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.
-- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Unit}.
-- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Unit}s assigned in the BOMB task.
-- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object**
@@ -143,12 +155,12 @@ AI_BAI_ZONE = {
--- Creates a new AI_BAI_ZONE object
-- @param #AI_BAI_ZONE self
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#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.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param 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
-- @param Dcs.DCSTypes#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 )
@@ -185,24 +197,24 @@ function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude
-- @function [parent=#AI_BAI_ZONE] Engage
-- @param #AI_BAI_ZONE self
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#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.
-- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
-- Use the structure @{DCSTypes#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.
-- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
--- 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.
-- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
-- Use the structure @{DCSTypes#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.
-- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_BAI_ZONE] OnLeaveEngaging
@@ -489,10 +501,10 @@ end
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#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 Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- @param #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.
-- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
EngageSpeed,
EngageAltitude,
@@ -519,7 +531,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -576,7 +588,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -600,7 +612,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
--- NOW ROUTE THE GROUP!
Controllable:WayPointExecute( 1 )
self:SetRefreshTimeInterval( 2 )
self:SetDetectionInterval( 2 )
self:SetDetectionActivated()
self:__Target( -2 ) -- Start Targetting
end

View File

@@ -1,44 +1,50 @@
--- **AI** -- Balance player slots with AI to create an engaging simulation environment, independent of the amount of players.
--- **AI** -- **AI Balancing will replace in multi player missions
-- non-occupied human slots with AI groups, in order to provide an engaging simulation environment,
-- even when there are hardly any players in the mission.**
--
-- **Features:**
-- ![Banner Image](..\Presentations\AI_Balancer\Dia1.JPG)
--
-- ====
--
-- * Automatically spawn AI as a replacement of free player slots for a coalition.
-- * Make the AI to perform tasks.
-- * Define a maximum amount of AI to be active at the same time.
-- * Configure the behaviour of AI when a human joins a slot for which an AI is active.
-- # Demo Missions
--
-- ===
-- ### [AI_BALANCER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing)
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing)
-- ### [AI_BALANCER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AIB%20-%20AI%20Balancing)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
-- ====
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7)
-- # YouTube Channel
--
-- ===
-- ### [AI_BALANCER YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7)
--
-- ### Author: **FlightControl**
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: 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
-- @module AI_Balancer
--- @type AI_BALANCER
-- @field Core.Set#SET_CLIENT SetClient
-- @field Core.Spawn#SPAWN SpawnAI
-- @field Functional.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.
--- # AI_BALANCER class, extends @{Fsm#FSM_SET}
--
-- The AI_BALANCER class monitors and manages as many replacement AI groups as there are
-- CLIENTS in a SET_CLIENT collection, which are not occupied by human players.
-- 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 parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM).
-- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods.
-- An explanation about state and event transition methods can be found in the @{FSM} module documentation.
--
@@ -80,8 +86,8 @@
-- However, there are 2 additional options that you can use to customize the destroy behaviour.
-- When a human player joins a slot, you can configure to let the AI return to:
--
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Wrapper.Airbase#AIRBASE}.
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Wrapper.Airbase#AIRBASE}.
-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}.
-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{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.
@@ -100,7 +106,7 @@ AI_BALANCER = {
--- 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.
-- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed.
-- @return #AI_BALANCER
function AI_BALANCER:New( SetClient, SpawnAI )
@@ -143,24 +149,24 @@ function AI_BALANCER:InitSpawnInterval( Earliest, Latest )
return self
end
--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}.
--- Returns the AI to the nearest friendly @{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 )
-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to.
function AI_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet )
self.ToNearestAirbase = true
self.ReturnThresholdRange = ReturnThresholdRange
self.ReturnTresholdRange = ReturnTresholdRange
self.ReturnAirbaseSet = ReturnAirbaseSet
end
--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}.
--- Returns the AI to the home @{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 )
-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}.
function AI_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange )
self.ToHomeAirbase = true
self.ReturnThresholdRange = ReturnThresholdRange
self.ReturnTresholdRange = ReturnTresholdRange
end
--- @param #AI_BALANCER self
@@ -172,10 +178,9 @@ 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 } )
AIGroup:E( "Spawning new AIGroup" )
--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
@@ -191,9 +196,9 @@ end
function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup )
AIGroup:Destroy()
SetGroup:Flush( self )
SetGroup:Flush()
SetGroup:Remove( ClientName )
SetGroup:Flush( self )
SetGroup:Flush()
end
--- @param #AI_BALANCER self
@@ -234,24 +239,23 @@ function AI_BALANCER:onenterMonitoring( SetGroup )
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 Client:IsAlive() 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.
-- We test if there is no other CLIENT within the self.ReturnTresholdRange 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.
-- If there is no CLIENT within the self.ReturnTresholdRange, 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 )
local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange )
self:T2( RangeZone )
_DATABASE:ForEachPlayerUnit(
_DATABASE:ForEachPlayer(
--- @param Wrapper.Unit#UNIT RangeTestUnit
function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange )
self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } )
@@ -280,12 +284,11 @@ function AI_BALANCER:onenterMonitoring( SetGroup )
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 )
self:E( "New AI Spawned for Client " .. Client.UnitName )
end
end
end

View File

@@ -1,24 +1,35 @@
--- **AI** -- Perform Combat Air Patrolling (CAP) for airplanes.
--- **AI** -- **Execute Combat Air Patrol (CAP).**
--
-- **Features:**
-- ![Banner Image](..\Presentations\AI_CAP\Dia1.JPG)
--
-- * Patrol AI airplanes within a given zone.
-- * Trigger detected events when enemy airplanes are detected.
-- * Manage a fuel treshold to RTB on time.
-- * Engage the enemy when detected.
-- ===
--
-- AI CAP classes makes AI Controllables execute a Combat Air Patrol.
--
-- There are the following types of CAP classes defined:
--
-- * @{#AI_CAP_ZONE}: Perform a CAP in a zone.
--
-- ====
--
-- # Demo Missions
--
-- ### [AI_CAP Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAP%20-%20Combat%20Air%20Patrol)
--
-- ### [AI_CAP Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAP%20-%20Combat%20Air%20Patrol)
--
-- ===
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAP%20-%20Combat%20Air%20Patrol)
-- ====
--
-- ===
-- # YouTube Channel
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1YCyPxJgoZn-CfhwyeW65L)
-- ### [AI_CAP YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1YCyPxJgoZn-CfhwyeW65L)
--
-- ===
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing.
@@ -27,24 +38,25 @@
-- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing.
-- * **[Delta99](https://forums.eagle.ru/member.php?u=125166): Testing.
--
-- ===
-- ====
--
-- @module AI.AI_Cap
-- @image AI_Combat_Air_Patrol.JPG
-- @module AI_Cap
--- @type AI_CAP_ZONE
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling.
-- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed.
-- @extends AI.AI_Patrol#AI_PATROL_ZONE
--- Implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}
--- # AI_CAP_ZONE class, extends @{AI_CAP#AI_PATROL_ZONE}
--
-- The AI_CAP_ZONE class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_CAP_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event.
-- The AI_CAP_ZONE is assigned a @{Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia4.JPG)
--
@@ -87,15 +99,15 @@
--
-- ### 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_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_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.
-- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Unit}.
-- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
@@ -106,7 +118,7 @@
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{AI.AI_CAP#AI_CAP_ZONE.SetEngageRange}() to define that range.
-- Use the method @{AI_CAP#AI_CAP_ZONE.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
@@ -114,7 +126,7 @@
--
-- An optional @{Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_Cap#AI_CAP_ZONE.SetEngageZone}() to define that Zone.
-- Use the method @{AI_Cap#AI_CAP_ZONE.SetEngageZone}() to define that Zone.
--
-- ===
--
@@ -128,11 +140,11 @@ AI_CAP_ZONE = {
--- Creates a new AI_CAP_ZONE object
-- @param #AI_CAP_ZONE self
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#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
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_CAP_ZONE self
function AI_CAP_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
@@ -346,20 +358,16 @@ function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To )
end
-- todo: need to fix this global function
--- @param AI.AI_CAP#AI_CAP_ZONE
-- @param Wrapper.Group#GROUP EngageGroup
function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm )
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
function _NewEngageCapRoute( AIControllable )
EngageGroup:F( { "AI_CAP_ZONE.EngageRoute:", EngageGroup:GetName() } )
if EngageGroup:IsAlive() then
Fsm:__Engage( 1 )
end
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_CAP_ZONE
EngageZone:__Engage( 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.
@@ -432,7 +440,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -456,7 +464,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir(
local ToPatrolRoutePoint = ToTargetPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -467,7 +475,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
Controllable:OptionROEOpenFire()
Controllable:OptionROTEvadeFire()
Controllable:OptionROTPassiveDefense()
local AttackTasks = {}
@@ -495,20 +503,28 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
end
end
--- 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( EngageRoute )
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 )
--- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ...
self.Controllable:SetState( self.Controllable, "EngageZone", self )
self.Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageCapRoute" )
self:SetDetectionDeactivated()
end
Controllable:Route( EngageRoute, 0.5 )
--- NOW ROUTE THE GROUP!
self.Controllable:WayPointExecute( 1, 2 )
end
end

View File

@@ -1,48 +1,63 @@
--- **AI** -- Perform Close Air Support (CAS) near friendlies.
--- **AI** -- **Provide Close Air Support to friendly ground troops.**
--
-- **Features:**
-- ![Banner Image](..\Presentations\AI_CAS\Dia1.JPG)
--
-- * 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.
-- ===
--
-- AI CAS classes makes AI Controllables execute a Close Air Support.
--
-- There are the following types of CAS classes defined:
--
-- * @{#AI_CAS_ZONE}: Perform a CAS in a zone.
--
-- ====
--
-- # Demo Missions
--
-- ### [AI_CAS Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAS%20-%20Close%20Air%20Support)
--
-- ### [AI_CAS Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAS%20-%20Close%20Air%20Support)
--
-- ===
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAS%20-%20Close%20Air%20Support)
-- ====
--
-- ===
-- # YouTube Channel
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2)
-- ### [AI_CAS YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2)
--
-- ===
-- ====
--
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing.
-- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Concept, Advice & Testing.
-- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision.
--
-- ===
-- ====
--
-- @module AI.AI_Cas
-- @image AI_Close_Air_Support.JPG
-- @module AI_Cas
--- AI_CAS_ZONE class
-- @type AI_CAS_ZONE
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling.
-- @field Core.Zone#ZONE_BASE TargetZone The @{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 @{Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}.
--- # AI_CAS_ZONE class, extends @{AI_Patrol#AI_PATROL_ZONE}
--
-- AI_CAS_ZONE derives from the @{AI_Patrol#AI_PATROL_ZONE}, inheriting its methods and behaviour.
--
-- The AI_CAS_ZONE class implements the core functions to provide Close Air Support in an Engage @{Zone} by an AIR @{Controllable} or @{Group}.
-- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone.
--
-- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG)
--
-- The AI_CAS_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event.
-- The AI_CAS_ZONE is assigned a @{Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event.
--
-- ![Start Event](..\Presentations\AI_CAS\Dia4.JPG)
--
@@ -108,15 +123,15 @@
--
-- ### 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_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_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.
-- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Unit}.
-- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Unit}s assigned in the CAS task.
-- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ===
@@ -131,12 +146,12 @@ AI_CAS_ZONE = {
--- Creates a new AI_CAS_ZONE object
-- @param #AI_CAS_ZONE self
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#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.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param 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
-- @param Dcs.DCSTypes#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 )
@@ -172,24 +187,24 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude
-- @function [parent=#AI_CAS_ZONE] Engage
-- @param #AI_CAS_ZONE self
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#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.
-- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
-- Use the structure @{DCSTypes#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.
-- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
--- 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.
-- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack.
-- If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack.
-- Use the structure @{DCSTypes#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.
-- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging
@@ -358,15 +373,12 @@ function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To )
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 )
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable
function _NewEngageRoute( AIControllable )
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
AIControllable:T( "NewEngageRoute" )
local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cas#AI_CAS_ZONE
EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection )
end
@@ -388,6 +400,7 @@ end
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To )
self:E("onafterTarget")
if Controllable:IsAlive() then
@@ -398,7 +411,7 @@ function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To )
if DetectedUnit:IsAlive() then
if DetectedUnit:IsInZone( self.EngageZone ) then
if Detected == true then
self:F( {"Target: ", DetectedUnit } )
self:E( {"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 )
@@ -431,10 +444,10 @@ end
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone.
-- @param DCS#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 Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement.
-- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.
-- @param #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.
-- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.
function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
EngageSpeed,
EngageAltitude,
@@ -451,9 +464,6 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
if Controllable:IsAlive() then
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
local EngageRoute = {}
--- Calculate the current route point.
@@ -463,7 +473,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -475,12 +485,12 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
local AttackTasks = {}
for DetectedUnit, Detected in pairs( self.DetectedUnits ) do
for DetectedUnitID, DetectedUnit 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 } )
self:E( {"Engaging ", DetectedUnit } )
AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit,
true,
EngageWeaponExpend,
@@ -493,8 +503,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
end
end
AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAS_ZONE.EngageRoute", self )
EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks )
EngageRoute[1].task = Controllable:TaskCombo( AttackTasks )
--- Define a random point in the @{Zone}. The AI will fly to that point within the zone.
@@ -506,7 +515,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -515,10 +524,22 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
)
EngageRoute[#EngageRoute+1] = ToTargetRoutePoint
Controllable:Route( EngageRoute, 0.5 )
--- 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 )
self:SetRefreshTimeInterval( 2 )
--- 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 )
Controllable:OptionROEOpenFire()
Controllable:OptionROTVertical()
self:SetDetectionInterval( 2 )
self:SetDetectionActivated()
self:__Target( -2 ) -- Start Targetting
end

View File

@@ -1,682 +0,0 @@
--- **AI** -- (R2.3) - Models the intelligent transportation of infantry and other cargo.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_APC
-- @image AI_Cargo_Dispatching_For_APC.JPG
--- @type AI_CARGO_APC
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Brings a dynamic cargo handling capability for AI groups.
--
-- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\APC module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\APC object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
-- ## Cargo loading.
--
-- The module will load automatically cargo when the APCs are within boarding or loading range.
-- The boarding or loading range is specified when the cargo is created in the simulation, and therefore, this range depends on the type of cargo
-- and the specified boarding range.
--
-- ## Enemies nearby.
--
-- When the APCs are approaching enemy units, something special is happening.
-- The APCs will stop moving, and the loaded infantry will unboard and follow the APCs and will help to defend the group.
-- The carrier will hold the route once the unboarded infantry is further than 50 meters from the APCs,
-- to ensure that the APCs are not too far away from the following running infantry.
-- Once all enemies are cleared, the infantry will board again automatically into the APCs. Once boarded, the APCs will follow its pre-defined route.
--
-- A combat range needs to be specified in meters at the @{#AI_CARGO_APC.New}() method.
-- This combat range will trigger the unboarding of troops when enemies are within the combat range around the APCs.
-- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit range (effectiveness) versus
-- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered.
-- A combat range of 350 meters to 500 meters has been proven to be the most effective and efficient.
--
-- ## Infantry health.
--
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
-- However, infantry that was destroyed when unboarded and following the APCs, won't be respawned again. Destroyed is destroyed.
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
-- time is not so much of an issue ...
--
-- ## Control the APCs on the map.
--
-- It is possible also as a human ground commander to influence the path of the APCs, by pointing a new path using the DCS user interface on the map.
-- In this case, the APCs will change the direction towards its new indicated route. However, there is a catch!
-- Once the APCs are near the enemy, and infantry is unboarded, the APCs won't be able to hold the route until the infantry could catch up.
-- The APCs will simply drive on and won't stop! This is a limitation in ED that prevents user actions being controlled by the scripting engine.
-- No workaround is possible on this.
--
-- ## Cargo deployment.
--
-- Using the @{#AI_CARGO_APC.Deploy}() method, you are able to direct the APCs towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
--
-- ## Cargo pickup.
--
-- Using the @{#AI_CARGO_APC.Pickup}() method, you are able to direct the APCs towards a point on the battlefield to board/load the cargo at the specific coordinate.
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
--
--
--
-- @field #AI_CARGO_APC
AI_CARGO_APC = {
ClassName = "AI_CARGO_APC",
Coordinate = nil, -- Core.Point#COORDINATE,
APC_Cargo = {},
}
--- Creates a new AI_CARGO_APC object.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param Core.Set#SET_CARGO CargoSet
-- @param #number CombatRadius
-- @return #AI_CARGO_APC
function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_APC
self.CargoSet = CargoSet -- Core.Set#SET_CARGO
self.CombatRadius = CombatRadius
self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Load", "Boarding" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( { "Unboarding", "Unloaded" }, "Unloaded", "Unloaded" )
self:AddTransition( "*", "Monitor", "*" )
self:AddTransition( "*", "Follow", "Following" )
self:AddTransition( "*", "Guard", "Unloaded" )
self:AddTransition( "*", "Home", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnBeforePickup
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterPickup
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] Pickup
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Asynchronous Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] __Pickup
-- @param #AI_CARGO_APC self
-- @param #number Delay
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Handler OnBefore for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnBeforeDeploy
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterDeploy
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] Deploy
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Asynchronous Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] __Deploy
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Delay
--- Loaded Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterLoaded
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param #string From
-- @param #string Event
-- @param #string To
--- Unloaded Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterUnloaded
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param #string From
-- @param #string Event
-- @param #string To
self:__Monitor( 1 )
self:SetCarrier( APC )
self.Transporting = false
self.Relocating = false
return self
end
--- Set the Carrier.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @return #AI_CARGO_APC
function AI_CARGO_APC:SetCarrier( CargoCarrier )
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUP
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_APC", self )
CargoCarrier:HandleEvent( EVENTS.Dead )
CargoCarrier:HandleEvent( EVENTS.Hit )
function CargoCarrier:OnEventDead( EventData )
self:F({"dead"})
local AICargoTroops = self:GetState( self, "AI_CARGO_APC" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the CargoCarrier.
AICargoTroops:Destroyed()
end
end
end
function CargoCarrier:OnEventHit( EventData )
self:F({"hit"})
local AICargoTroops = self:GetState( self, "AI_CARGO_APC" )
if AICargoTroops then
self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
-- There are enemies within combat range. Unload the CargoCarrier.
AICargoTroops:Unload( false )
end
end
end
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
self.Coalition = self.CargoCarrier:GetCoalition()
self:SetControllable( CargoCarrier )
self:Guard()
return self
end
function AI_CARGO_APC:IsTransporting()
return self.Transporting == true
end
function AI_CARGO_APC:IsRelocating()
return self.Relocating == true
end
--- Find a free Carrier within a range.
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Radius
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_APC:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
CoordinateZone:Scan( { Object.Category.UNIT } )
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
local NearUnit = UNIT:Find( DCSUnit )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_APC" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute( "Trucks" ) then
return NearUnit:GetGroup()
end
end
end
return nil
end
--- Follow Infantry to the Carrier.
-- @param #AI_CARGO_APC self
-- @param #AI_CARGO_APC Me
-- @param Wrapper.Unit#UNIT APCUnit
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
-- @return #AI_CARGO_APC
function AI_CARGO_APC:FollowToCarrier( Me, APCUnit, CargoGroup )
local InfantryGroup = CargoGroup:GetGroup()
self:F( { self = self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
--if self:Is( "Following" ) then
if APCUnit:IsAlive() then
-- We check if the Cargo is near to the CargoCarrier.
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", APCUnit, 25 ) ) then
-- The Cargo does not need to follow the Carrier.
Me:Guard()
else
self:F( { InfantryGroup = InfantryGroup:GetName() } )
if InfantryGroup:IsAlive() then
self:F( { InfantryGroup = InfantryGroup:GetName() } )
local Waypoints = {}
-- Calculate the new Route.
local FromCoord = InfantryGroup:GetCoordinate()
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
self:F({FromGround=FromGround})
table.insert( Waypoints, FromGround )
local ToCoord = APCUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
self:F({ToGround=ToGround})
table.insert( Waypoints, ToGround )
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_APC.FollowToCarrier", Me, APCUnit, CargoGroup )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterMonitor( APC, From, Event, To )
self:F( { APC, From, Event, To } )
if APC and APC:IsAlive() then
if self.CarrierCoordinate then
if self:IsRelocating() == true then
local Coordinate = APC:GetCoordinate()
self.Zone:Scan( { Object.Category.UNIT } )
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
if self:Is( "Unloaded" ) or self:Is( "Following" ) then
-- There are no enemies within combat range. Load the CargoCarrier.
self:Load()
end
else
if self:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the CargoCarrier.
self:__Unload( 1, false )
else
if self:Is( "Unloaded" ) then
self:Follow()
end
if self:Is( "Following" ) then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsAlive() then
if not Cargo:IsNear( APCUnit, 40 ) then
APCUnit:RouteStop()
self.CarrierStopped = true
else
if self.CarrierStopped then
if Cargo:IsNear( APCUnit, 25 ) then
APCUnit:RouteResume()
self.CarrierStopped = nil
end
end
end
end
end
end
end
end
end
end
self.CarrierCoordinate = APC:GetCoordinate()
end
self:__Monitor( -5 )
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeLoad( APC, From, Event, To )
self:F( { APC, From, Event, To } )
local Boarding = false
self.BoardingCount = 0
if APC and APC:IsAlive() then
self.APC_Cargo = {}
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), APC:GetName() } )
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
if Cargo:IsInLoadRadius( APCUnit:GetCoordinate() ) then
self:F( { "In radius", APCUnit:GetName() } )
APC:RouteStop()
--Cargo:Ungroup()
Cargo:Board( APCUnit, 25 )
self:__Board( 1, Cargo )
Boarding = true
-- So now this APCUnit has Cargo that is being loaded.
-- This will be used further in the logic to follow and to check cargo status.
self.APC_Cargo[APCUnit] = Cargo
break
end
end
end
end
end
return Boarding
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterBoard( APC, From, Event, To, Cargo )
self:F( { APC, From, Event, To, Cargo } )
if APC and APC:IsAlive() then
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), APC:GetName() } )
if not Cargo:IsLoaded() then
self:__Board( 10, Cargo )
else
self:__Loaded( 1 )
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To )
self:F( { APC, From, Event, To } )
local Loaded = true
if APC and APC:IsAlive() then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), APC:GetName() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
Loaded = false
end
end
end
if Loaded == true then
APC:RouteResume()
end
return Loaded
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterUnload( APC, From, Event, To, Deployed )
self:F( { APC, From, Event, To, Deployed } )
if APC and APC:IsAlive() then
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
APC:RouteStop()
for _, Cargo in pairs( APCUnit:GetCargo() ) do
Cargo:UnBoard()
self:__Unboard( 10, Cargo, Deployed )
end
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterUnboard( APC, From, Event, To, Cargo, Deployed )
self:F( { APC, From, Event, To, Cargo:GetName() } )
if APC and APC:IsAlive() then
if not Cargo:IsUnLoaded() then
self:__Unboard( 10, Cargo, Deployed )
else
self:__Unloaded( 1, Cargo, Deployed )
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeUnloaded( APC, From, Event, To, Cargo, Deployed )
self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } )
local AllUnloaded = true
--Cargo:Regroup()
if APC and APC:IsAlive() then
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
local CargoCheck = self.APC_Cargo[APCUnit]
if CargoCheck then
self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } )
if CargoCheck:IsUnLoaded() == false then
AllUnloaded = false
break
end
end
end
if AllUnloaded == true then
if Deployed == true then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
Cargo:SetDeployed( true )
end
self.APC_Cargo = {}
end
self:Guard()
self.CargoCarrier = APC
end
end
self:F( { AllUnloaded = AllUnloaded } )
return AllUnloaded
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
self:F( { APC, From, Event, To } )
self:F( "Follow" )
if APC and APC:IsAlive() then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsUnLoaded() then
self:FollowToCarrier( self, APCUnit, Cargo )
APCUnit:RouteResume()
end
end
end
end
--- @param #AI_CARGO_APC
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC._Pickup( APC, self )
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
if APC:IsAlive() then
self:Load()
self.Relocating = true
end
end
--- @param #AI_CARGO_APC
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC._Deploy( APC, self )
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
if APC:IsAlive() then
self:Unload( true )
self.Transporting = false
self.Relocating = false
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate 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.
function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed )
if APC and APC:IsAlive() then
if Coordinate then
self.RoutePickup = true
local _speed=Speed or APC:GetSpeedMax()*0.5
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
else
AI_CARGO_APC._Pickup( APC, self )
end
self.Transporting = true
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed )
if APC and APC:IsAlive() then
self.RouteDeploy = true
local _speed=Speed or APC:GetSpeedMax()*0.5
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate, Speed )
if APC and APC:IsAlive() ~= nil then
self.RouteHome = true
local _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

View File

@@ -1,446 +0,0 @@
--- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo).
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Airplane
-- @image AI_Cargo_Dispatching_For_Airplanes.JPG
--- @type AI_CARGO_AIRPLANE
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Implements the transportation of cargo by airplanes.
--
-- @field #AI_CARGO_AIRPLANE
AI_CARGO_AIRPLANE = {
ClassName = "AI_CARGO_AIRPLANE",
Coordinate = nil -- Core.Point#COORDINATE,
}
--- Creates a new AI_CARGO_AIRPLANE object.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param Core.Set#SET_CARGO CargoSet
-- @return #AI_CARGO_AIRPLANE
function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_AIRPLANE
self.CargoSet = CargoSet -- Cargo.CargoGroup#CARGO_GROUP
self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "Unloaded", "Load", "Boarding" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( "Unboarding", "Unloaded", "Unloaded" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup
-- @param #AI_CARGO_AIRPLANE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup
-- @param #AI_CARGO_AIRPLANE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Pickup Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] Pickup
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] __Pickup
-- @param #AI_CARGO_AIRPLANE self
-- @param #number Delay
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Deploy Handler OnBefore for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy
-- @param #AI_CARGO_AIRPLANE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy
-- @param #AI_CARGO_AIRPLANE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Deploy Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] Deploy
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] __Deploy
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Delay
self:SetCarrier( Airplane )
return self
end
--- Set the Carrier.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @return #AI_CARGO_AIRPLANE
function AI_CARGO_AIRPLANE:SetCarrier( Airplane )
local AICargo = self
self.Airplane = Airplane -- Wrapper.Group#GROUP
self.Airplane:SetState( self.Airplane, "AI_CARGO_AIRPLANE", self )
self.RoutePickup = false
self.RouteDeploy = false
Airplane:HandleEvent( EVENTS.Dead )
Airplane:HandleEvent( EVENTS.Hit )
Airplane:HandleEvent( EVENTS.EngineShutdown )
function Airplane:OnEventDead( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the Airplane.
AICargoTroops:Destroyed()
end
end
end
function Airplane:OnEventHit( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" )
if AICargoTroops then
self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
-- There are enemies within combat range. Unload the Airplane.
AICargoTroops:Unload()
end
end
end
function Airplane:OnEventEngineShutdown( EventData )
AICargo:Landed()
end
self.Coalition = self.Airplane:GetCoalition()
self:SetControllable( Airplane )
return self
end
--- Find a free Carrier within a range.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Radius
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_AIRPLANE:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
CoordinateZone:Scan( { Object.Category.UNIT } )
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
local NearUnit = UNIT:Find( DCSUnit )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_AIRPLANE" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute( "Trucks" ) then
self:SetCarrier( NearUnit )
break
end
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param From
-- @param Event
-- @param To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Speed
function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
if self.RoutePickup == true then
self:Load( Airplane:GetPointVec2() )
self.RoutePickup = false
end
if self.RouteDeploy == true then
self:Unload()
self.RouteDeploy = false
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param From
-- @param Event
-- @param To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Speed
function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Airbase, Speed )
if Airplane and Airplane:IsAlive() then
self:Route( Airplane, Airbase, Speed )
self.RoutePickup = true
self.Airbase = Airbase
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param From
-- @param Event
-- @param To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Speed
function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Airbase, Speed )
if Airplane and Airplane:IsAlive() then
self:Route( Airplane, Airbase, Speed )
self.RouteDeploy = true
self.Airbase = Airbase
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterLoad( Airplane, From, Event, To, Coordinate )
if Airplane and Airplane:IsAlive() then
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
if Cargo:IsInLoadRadius( Coordinate ) then
self:__Board( 5 )
Cargo:Board( Airplane, 25 )
self.Cargo = Cargo
break
end
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterBoard( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
self:F({ IsLoaded = self.Cargo:IsLoaded() } )
if not self.Cargo:IsLoaded() then
self:__Board( 10 )
else
self:__Loaded( 1 )
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterLoaded( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
self.Cargo:UnBoard()
self:__Unboard( 10 )
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterUnboard( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
if not self.Cargo:IsUnLoaded() then
self:__Unboard( 10 )
else
self:__Unloaded( 1 )
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterUnloaded( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
self.Airplane = Airplane
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Speed
function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed )
if Airplane and Airplane:IsAlive() then
local PointVec3 = Airplane:GetPointVec3()
local Takeoff = SPAWN.Takeoff.Hot
local Template = Airplane:GetTemplate()
if Template then
local Points = {}
if self.Airbase then
local FromWaypoint = Template.route.points[1]
-- These are only for ships.
FromWaypoint.linkUnit = nil
FromWaypoint.helipadId = nil
FromWaypoint.airdromeId = nil
local AirbaseID = self.Airbase:GetID()
local AirbaseCategory = self.Airbase:GetDesc().category
FromWaypoint.airdromeId = AirbaseID
FromWaypoint.alt = 0
FromWaypoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
FromWaypoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #Template.units do
self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. Template.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. Template.units[UnitID].y )
-- These cause a lot of confusion.
local UnitTemplate = Template.units[UnitID]
UnitTemplate.parking = 15
UnitTemplate.parking_id = "1"
UnitTemplate.alt = 0
local SX = UnitTemplate.x
local SY = UnitTemplate.y
local BX = FromWaypoint.x
local BY = FromWaypoint.y
local TX = PointVec3.x + ( SX - BX )
local TY = PointVec3.z + ( SY - BY )
UnitTemplate.x = TX
UnitTemplate.y = TY
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
end
FromWaypoint.x = PointVec3.x
FromWaypoint.y = PointVec3.z
Points[#Points+1] = FromWaypoint
else
local GroupPoint = Airplane:GetVec2()
local GroupVelocity = Airplane:GetUnit(1):GetDesc().speedMax
local FromWaypoint = {}
FromWaypoint.x = GroupPoint.x
FromWaypoint.y = GroupPoint.y
FromWaypoint.type = "Turning Point"
FromWaypoint.action = "Turning Point"
FromWaypoint.speed = GroupVelocity
Points[#Points+1] = FromWaypoint
end
local AirbasePointVec2 = Airbase:GetPointVec2()
local ToWaypoint = AirbasePointVec2:WaypointAir(
POINT_VEC3.RoutePointAltType.BARO,
"Land",
"Landing",
Speed or Airplane:GetUnit(1):GetDesc().speedMax
)
ToWaypoint["airdromeId"] = Airbase:GetID()
ToWaypoint["speed_locked"] = true,
self:F( ToWaypoint )
Points[#Points+1] = ToWaypoint
Template.x = PointVec3.x
Template.y = PointVec3.z
self:T3( Points )
Template.route.points = Points
--self:Respawn( Template )
local GroupSpawned = Airplane:Respawn( Template )
return GroupSpawned
end
end
end

View File

@@ -1,477 +0,0 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
--- @type AI_CARGO_DISPATCHER
-- @extends Core.Fsm#FSM
--- A dynamic cargo handling capability for AI groups.
--
-- Carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\_DISPATCHER module uses the @{Cargo} capabilities within the MOOSE framework, to enable Carrier GROUP objects
-- to transport @{Cargo} towards several deploy zones.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
-- ## 1. AI\_CARGO\_DISPATCHER constructor
--
-- * @{#AI_CARGO_DISPATCHER.New}(): Creates a new AI\_CARGO\_DISPATCHER object.
--
-- ## 2. AI\_CARGO\_DISPATCHER is a FSM
--
-- ![Process](..\Presentations\AI_PATROL\Dia2.JPG)
--
-- ### 2.1. AI\_CARGO\_DISPATCHER States
--
-- * **Monitoring**: The process is dispatching.
-- * **Idle**: The process is idle.
--
-- ### 2.2. AI\_CARGO\_DISPATCHER Events
--
-- * **Monitor**: Monitor and take action.
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Pickup**: Pickup cargo.
-- * **Load**: Load the cargo.
-- * **Loaded**: Flag that the cargo is loaded.
-- * **Deploy**: Deploy cargo to a location.
-- * **Unload**: Unload the cargo.
-- * **Unloaded**: Flag that the cargo is unloaded.
-- * **Home**: A Carrier is going home.
--
-- ## 3. Set the pickup parameters.
--
-- Several parameters can be set to pickup cargo:
--
-- * @{#AI_CARGO_DISPATCHER.SetPickupRadius}(): Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius.
-- * @{#AI_CARGO_DISPATCHER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
--
-- ## 4. Set the deploy parameters.
--
-- Several parameters can be set to deploy cargo:
--
-- * @{#AI_CARGO_DISPATCHER.SetDeployRadius}(): Sets or randomizes the deploy location for the carrier around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- * @{#AI_CARGO_DISPATCHER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
--
-- ## 5. Set the home zone when there isn't any more cargo to pickup.
--
-- A home zone can be specified to where the Carriers will move when there isn't any cargo left for pickup.
-- Use @{#AI_CARGO_DISPATCHER.SetHomeZone}() to specify the home zone.
--
-- If no home zone is specified, the carriers will wait near the deploy zone for a new pickup command.
--
-- ===
--
-- @field #AI_CARGO_DISPATCHER
AI_CARGO_DISPATCHER = {
ClassName = "AI_CARGO_DISPATCHER",
SetCarrier = nil,
SetDeployZones = nil,
AI_Cargo = {},
PickupCargo = {}
}
--- @field #AI_CARGO_DISPATCHER.AI_Cargo
AI_CARGO_DISPATCHER.AI_Cargo = {}
--- @field #AI_CARGO_DISPATCHER.PickupCargo
AI_CARGO_DISPATCHER.PickupCargo = {}
--- Creates a new AI_CARGO_DISPATCHER object.
-- @param #AI_CARGO_DISPATCHER self
-- @param Core.Set#SET_GROUP SetCarrier
-- @param Core.Set#SET_CARGO SetCargo
-- @param Core.Set#SET_ZONE SetDeployZone
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- SetCarrier = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart()
-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone )
--
function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZones )
local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER
self.SetCarrier = SetCarrier -- Core.Set#SET_GROUP
self.SetCargo = SetCargo -- Core.Set#SET_CARGO
self.SetDeployZones = SetDeployZones -- Core.Set#SET_ZONE
self:SetStartState( "Idle" )
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
self:AddTransition( "Idle", "Start", "Monitoring" )
self:AddTransition( "Monitoring", "Stop", "Idle" )
self:AddTransition( "*", "Pickup", "*" )
self:AddTransition( "*", "Loading", "*" )
self:AddTransition( "*", "Loaded", "*" )
self:AddTransition( "*", "Deploy", "*" )
self:AddTransition( "*", "Unloading", "*" )
self:AddTransition( "*", "Unloaded", "*" )
self:AddTransition( "*", "Home", "*" )
self.MonitorTimeInterval = 30
self.DeployRadiusInner = 200
self.DeployRadiusOuter = 500
self.PickupCargo = {}
self.CarrierHome = {}
-- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset.
function SetCarrier.OnAfterRemoved( SetCarrier, From, Event, To, CarrierName, Carrier )
self:F( { Carrier = Carrier:GetName() } )
self.PickupCargo[Carrier] = nil
self.CarrierHome[Carrier] = nil
end
return self
end
--- Set the home zone.
-- When there is nothing anymore to pickup, the carriers will go to a random coordinate in this zone.
-- They will await here new orders.
-- @param #AI_CARGO_DISPATCHER self
-- @param Core.Zone#ZONE_BASE HomeZone
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone )
--
-- -- Set the home coordinate
-- local HomeZone = ZONE:New( "Home" )
-- AICargoDispatcher:SetHomeZone( HomeZone )
--
function AI_CARGO_DISPATCHER:SetHomeZone( HomeZone )
self.HomeZone = HomeZone
return self
end
--- Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius.
-- This radius is influencing the location where the carrier will land to pickup the cargo.
-- There are two aspects that are very important to remember and take into account:
--
-- - Ensure that the outer and inner radius are within reporting radius set by the cargo.
-- For example, if the cargo has a reporting radius of 400 meters, and the outer and inner radius is set to 500 and 450 respectively,
-- then no cargo will be loaded!!!
-- - Also take care of the potential cargo position and possible reasons to crash the carrier. This is especially important
-- for locations which are crowded with other objects, like in the middle of villages or cities.
-- So, for the best operation of cargo operations, always ensure that the cargo is located at open spaces.
--
-- The default radius is 0, so the center. In case of a polygon zone, a random location will be selected as the center in the zone.
-- @param #AI_CARGO_DISPATCHER self
-- @param #number OuterRadius The outer radius in meters around the cargo coordinate.
-- @param #number InnerRadius (optional) The inner radius in meters around the cargo coordinate.
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone )
--
-- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters!
-- AICargoDispatcher:SetPickupRadius( 500, 300 )
--
function AI_CARGO_DISPATCHER:SetPickupRadius( OuterRadius, InnerRadius )
OuterRadius = OuterRadius or 0
InnerRadius = InnerRadius or OuterRadius
self.PickupOuterRadius = OuterRadius
self.PickupInnerRadius = InnerRadius
return self
end
--- Set the speed or randomizes the speed in km/h to pickup the cargo.
-- @param #AI_CARGO_DISPATCHER self
-- @param #number MaxSpeed (optional) The maximum speed to move to the cargo pickup location.
-- @param #number MinSpeed The minimum speed to move to the cargo pickup location.
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone )
--
-- -- Set the minimum pickup speed to be 100 km/h and the maximum speed to be 200 km/h.
-- AICargoDispatcher:SetPickupSpeed( 200, 100 )
--
function AI_CARGO_DISPATCHER:SetPickupSpeed( MaxSpeed, MinSpeed )
MaxSpeed = MaxSpeed or 999
MinSpeed = MinSpeed or MaxSpeed
self.PickupMinSpeed = MinSpeed
self.PickupMaxSpeed = MaxSpeed
return self
end
--- Sets or randomizes the deploy location for the carrier around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- This radius is influencing the location where the carrier will land to deploy the cargo.
-- There is an aspect that is very important to remember and take into account:
--
-- - Take care of the potential cargo position and possible reasons to crash the carrier. This is especially important
-- for locations which are crowded with other objects, like in the middle of villages or cities.
-- So, for the best operation of cargo operations, always ensure that the cargo is located at open spaces.
--
-- The default radius is 0, so the center. In case of a polygon zone, a random location will be selected as the center in the zone.
-- @param #AI_CARGO_DISPATCHER self
-- @param #number OuterRadius The outer radius in meters around the cargo coordinate.
-- @param #number InnerRadius (optional) The inner radius in meters around the cargo coordinate.
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone )
--
-- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters!
-- AICargoDispatcher:SetDeployRadius( 500, 300 )
--
function AI_CARGO_DISPATCHER:SetDeployRadius( OuterRadius, InnerRadius )
OuterRadius = OuterRadius or 0
InnerRadius = InnerRadius or OuterRadius
self.DeployOuterRadius = OuterRadius
self.DeployInnerRadius = InnerRadius
return self
end
--- Sets or randomizes the speed in km/h to deploy the cargo.
-- @param #AI_CARGO_DISPATCHER self
-- @param #number MaxSpeed The maximum speed to move to the cargo deploy location.
-- @param #number MinSpeed (optional) The minimum speed to move to the cargo deploy location.
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone )
--
-- -- Set the minimum deploy speed to be 100 km/h and the maximum speed to be 200 km/h.
-- AICargoDispatcher:SetDeploySpeed( 200, 100 )
--
function AI_CARGO_DISPATCHER:SetDeploySpeed( MaxSpeed, MinSpeed )
MaxSpeed = MaxSpeed or 999
MinSpeed = MinSpeed or MaxSpeed
self.DeployMinSpeed = MinSpeed
self.DeployMaxSpeed = MaxSpeed
return self
end
--- The Start trigger event, which actually takes action at the specified time interval.
-- @param #AI_CARGO_DISPATCHER self
function AI_CARGO_DISPATCHER:onafterMonitor()
for CarrierGroupName, Carrier in pairs( self.SetCarrier:GetSet() ) do
local Carrier = Carrier -- Wrapper.Group#GROUP
local AI_Cargo = self.AI_Cargo[Carrier]
if not AI_Cargo then
-- ok, so this Carrier does not have yet an AI_CARGO handling object...
-- let's create one and also declare the Loaded and UnLoaded handlers.
self.AI_Cargo[Carrier] = self:AICargo( Carrier, self.SetCargo, self.CombatRadius )
AI_Cargo = self.AI_Cargo[Carrier]
function AI_Cargo.OnAfterPickup( AI_Cargo, Carrier, From, Event, To, Cargo )
self:Pickup( Carrier, Cargo )
end
function AI_Cargo.OnAfterLoad( AI_Cargo, Carrier, From, Event, To, Cargo )
self:Loading( Carrier )
end
function AI_Cargo.OnAfterLoaded( AI_Cargo, Carrier, From, Event, To, Cargo )
self:Loaded( Carrier, Cargo )
end
function AI_Cargo.OnAfterDeploy( AI_Cargo, Carrier, From, Event, To, Cargo )
self:Deploy( Carrier, Cargo )
end
function AI_Cargo.OnAfterUnload( AI_Cargo, Carrier, From, Event, To, Cargo )
self:Unloading( Carrier, Cargo )
end
function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo )
self:Unloaded( Carrier, Cargo )
end
end
-- The Pickup sequence ...
-- Check if this Carrier need to go and Pickup something...
self:I( { IsTransporting = AI_Cargo:IsTransporting() } )
if AI_Cargo:IsTransporting() == false then
-- ok, so there is a free Carrier
-- now find the first cargo that is Unloaded
local PickupCargo = nil
for CargoName, Cargo in pairs( self.SetCargo:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { Cargo = Cargo:GetName(), UnLoaded = Cargo:IsUnLoaded(), Deployed = Cargo:IsDeployed(), PickupCargo = self.PickupCargo[Carrier] ~= nil } )
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
local CargoCoordinate = Cargo:GetCoordinate()
local CoordinateFree = true
for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do
if CarrierPickup:IsAlive() == true then
if CargoCoordinate:Get2DDistance( Coordinate ) <= 25 then
CoordinateFree = false
break
end
else
self.PickupCargo[CarrierPickup] = nil
end
end
if CoordinateFree == true then
self.PickupCargo[Carrier] = CargoCoordinate
PickupCargo = Cargo
break
end
end
end
if PickupCargo then
self.CarrierHome[Carrier] = nil
local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius )
AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ) )
break
else
if self.HomeZone then
if not self.CarrierHome[Carrier] then
self.CarrierHome[Carrier] = true
AI_Cargo:__Home( 60, self.HomeZone:GetRandomPointVec2() )
end
end
end
end
end
self:__Monitor( self.MonitorTimeInterval )
end
--- Start Handler OnBefore for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] OnBeforeStart
-- @param #AI_CARGO_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Start Handler OnAfter for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] OnAfterStart
-- @param #AI_CARGO_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Start Trigger for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] Start
-- @param #AI_CARGO_DISPATCHER self
--- Start Asynchronous Trigger for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] __Start
-- @param #AI_CARGO_DISPATCHER self
-- @param #number Delay
function AI_CARGO_DISPATCHER:onafterStart( From, Event, To )
self:__Monitor( -1 )
end
--- Stop Handler OnBefore for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] OnBeforeStop
-- @param #AI_CARGO_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Stop Handler OnAfter for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] OnAfterStop
-- @param #AI_CARGO_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Stop Trigger for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] Stop
-- @param #AI_CARGO_DISPATCHER self
--- Stop Asynchronous Trigger for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] __Stop
-- @param #AI_CARGO_DISPATCHER self
-- @param #number Delay
--- Loaded Handler OnAfter for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoaded
-- @param #AI_CARGO_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Group#GROUP Carrier
-- @param Cargo.Cargo#CARGO Cargo
--- Unloaded Handler OnAfter for AI_CARGO_DISPATCHER
-- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloaded
-- @param #AI_CARGO_DISPATCHER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Group#GROUP Carrier
-- @param Cargo.Cargo#CARGO Cargo
--- Make a Carrier run for a cargo deploy action after the cargo has been loaded, by default.
-- @param #AI_CARGO_DISPATCHER self
-- @param From
-- @param Event
-- @param To
-- @param Wrapper.Group#GROUP Carrier
-- @param Cargo.Cargo#CARGO Cargo
-- @return #AI_CARGO_DISPATCHER
function AI_CARGO_DISPATCHER:OnAfterLoaded( From, Event, To, Carrier, Cargo )
local DeployZone = self.SetDeployZones:GetRandomZone()
local DeployCoordinate = DeployZone:GetCoordinate():GetRandomCoordinateInRadius( self.DeployOuterRadius, self.DeployInnerRadius )
self.AI_Cargo[Carrier]:Deploy( DeployCoordinate, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ) )
self.PickupCargo[Carrier] = nil
end

View File

@@ -1,122 +0,0 @@
--- **AI** -- Models the intelligent transportation of infantry and other cargo using APCs.
--
-- **Features:**
--
-- * Quickly transport cargo to various deploy zones using ground vehicles (APCs, trucks ...).
-- * Various @{Cargo.Cargo#CARGO} types can be transported. These are infantry groups and crates.
-- * Define a list of deploy zones of various types to transport the cargo to.
-- * The vehicles follow the roads to ensure the fastest possible cargo transportation over the ground.
-- * Multiple vehicles can transport multiple cargo as one vehicle group.
-- * Multiple vehicle groups can be enabled as one collaborating transportation process.
-- * Infantry loaded as cargo, will unboard in case enemies are nearby and will help defending the vehicles.
-- * Different ranges can be setup for enemy defenses.
-- * Different options can be setup to tweak the cargo transporation behaviour.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_APC
-- @image AI_Cargo_Dispatching_For_APC.JPG
--- @type AI_CARGO_DISPATCHER_APC
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- A dynamic cargo transportation capability for AI groups.
--
-- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\_DISPATCHER\_APC module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_APC object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
-- ## 1. AI\_CARGO\_DISPATCHER\_APC constructor
--
-- * @{#AI_CARGO_DISPATCHER\_APC.New}(): Creates a new AI\_CARGO\_DISPATCHER\_APC object.
--
-- ## 2. AI\_CARGO\_DISPATCHER\_APC is a FSM
--
-- ![Process](..\Presentations\AI_CARGO_DISPATCHER_APC\Dia3.JPG)
--
-- ### 2.1. AI\_CARGO\_DISPATCHER\_APC States
--
-- * **Monitoring**: The process is dispatching.
-- * **Idle**: The process is idle.
--
-- ### 2.2. AI\_CARGO\_DISPATCHER\_APC Events
--
-- * **Monitor**: Monitor and take action.
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Pickup**: Pickup cargo.
-- * **Load**: Load the cargo.
-- * **Loaded**: Flag that the cargo is loaded.
-- * **Deploy**: Deploy cargo to a location.
-- * **Unload**: Unload the cargo.
-- * **Unloaded**: Flag that the cargo is unloaded.
-- * **Home**: A APC is going home.
--
-- ## 3. Set the pickup parameters.
--
-- Several parameters can be set to pickup cargo:
--
-- * @{#AI_CARGO_DISPATCHER\_APC.SetPickupRadius}(): Sets or randomizes the pickup location for the APC around the cargo coordinate in a radius defined an outer and optional inner radius.
-- * @{#AI_CARGO_DISPATCHER\_APC.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
--
-- ## 4. Set the deploy parameters.
--
-- Several parameters can be set to deploy cargo:
--
-- * @{#AI_CARGO_DISPATCHER\_APC.SetDeployRadius}(): Sets or randomizes the deploy location for the APC around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- * @{#AI_CARGO_DISPATCHER\_APC.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
--
-- ## 5. Set the home zone when there isn't any more cargo to pickup.
--
-- A home zone can be specified to where the APCs will move when there isn't any cargo left for pickup.
-- Use @{#AI_CARGO_DISPATCHER\_APC.SetHomeZone}() to specify the home zone.
--
-- If no home zone is specified, the APCs will wait near the deploy zone for a new pickup command.
--
-- ===
--
-- @field #AI_CARGO_DISPATCHER_APC
AI_CARGO_DISPATCHER_APC = {
ClassName = "AI_CARGO_DISPATCHER_APC",
}
--- Creates a new AI_CARGO_DISPATCHER_APC object.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param Core.Set#SET_GROUP SetAPC The collection of APC @{Wrapper.Group}s.
-- @param Core.Set#SET_CARGO SetCargo The collection of @{Cargo} derived objects.
-- @param Core.Set#SET_ZONE SetDeployZone The collection of deploy @{Zone}s, which are used to where the cargo will be deployed by the APCs.
-- @param DCS#Distance CombatRadius The cargo will be unloaded from the APC and engage the enemy if the enemy is within CombatRadius range. The radius is in meters, the default value is 500 meters.
-- @return #AI_CARGO_DISPATCHER_APC
-- @usage
--
-- -- Create a new cargo dispatcher for the set of APCs, with a combatradius of 500.
-- SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart()
-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- AICargoDispatcher = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZone, 500 )
--
function AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZone, CombatRadius )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone ) ) -- #AI_CARGO_DISPATCHER_APC
self.CombatRadius = CombatRadius or 500
self:SetDeploySpeed( 70, 120 )
self:SetPickupSpeed( 70, 120 )
self:SetPickupRadius( 0, 0 )
self:SetDeployRadius( 0, 0 )
return self
end
function AI_CARGO_DISPATCHER_APC:AICargo( APC, SetCargo )
return AI_CARGO_APC:New( APC, SetCargo, self.CombatRadius )
end

View File

@@ -1,51 +0,0 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo using Planes.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_Airplane
-- @image AI_Cargo_Dispatching_For_Airplanes.JPG
--
--- @type AI_CARGO_DISPATCHER_AIRPLANE
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- Brings a dynamic cargo handling capability for AI groups.
--
-- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\_DISPATCHER\_AIRPLANE module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_AIRPLANE object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
--
--
-- @field #AI_CARGO_DISPATCHER_AIRPLANE
AI_CARGO_DISPATCHER_AIRPLANE = {
ClassName = "AI_CARGO_DISPATCHER_AIRPLANE",
}
--- Creates a new AI_CARGO_DISPATCHER_AIRPLANE object.
-- @param #AI_CARGO_DISPATCHER_AIRPLANE self
-- @param Core.Set#SET_GROUP SetAirplane
-- @param Core.Set#SET_CARGO SetCargo
-- @param Core.Set#SET_ZONE SetDeployZone
-- @return #AI_CARGO_DISPATCHER_AIRPLANE
-- @usage
--
-- -- Create a new cargo dispatcher
-- SetAirplane = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart()
-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- AICargoDispatcher = AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplane, SetCargo )
--
function AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplane, SetCargo, SetDeployZones )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetAirplane, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE
return self
end

View File

@@ -1,119 +0,0 @@
--- **AI** -- Models the intelligent transportation of infantry and other cargo using Helicopters.
--
-- The @{#AI_CARGO_DISPATCHER_HELICOPTER} classes implements the dynamic dispatching of cargo transportation tasks for helicopters.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_Helicopter
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
--- @type AI_CARGO_DISPATCHER_HELICOPTER
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- A dynamic cargo handling capability for AI helicopter groups.
--
-- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\_DISPATCHER\_HELICOPTER module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_HELICOPTER object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
-- ---
--
-- ## 1. AI\_CARGO\_DISPATCHER\_HELICOPTER constructor
--
-- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.New}(): Creates a new AI\_CARGO\_DISPATCHER\_HELICOPTER object.
--
-- ---
--
-- ## 2. AI\_CARGO\_DISPATCHER\_HELICOPTER is a FSM
--
-- ![Process](..\Presentations\AI_CARGO_DISPATCHER_HELICOPTER\Dia3.JPG)
--
-- ### 2.1. AI\_CARGO\_DISPATCHER\_HELICOPTER States
--
-- * **Monitoring**: The process is dispatching.
-- * **Idle**: The process is idle.
--
-- ### 2.2. AI\_CARGO\_DISPATCHER\_HELICOPTER Events
--
-- * **Monitor**: Monitor and take action.
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Pickup**: Pickup cargo.
-- * **Load**: Load the cargo.
-- * **Loaded**: Flag that the cargo is loaded.
-- * **Deploy**: Deploy cargo to a location.
-- * **Unload**: Unload the cargo.
-- * **Unloaded**: Flag that the cargo is unloaded.
-- * **Home**: A Helicopter is going home.
--
-- ---
--
-- ## 3. Set the pickup parameters.
--
-- Several parameters can be set to pickup cargo:
--
-- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.SetPickupRadius}(): Sets or randomizes the pickup location for the helicopter around the cargo coordinate in a radius defined an outer and optional inner radius.
-- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
--
-- ---
--
-- ## 4. Set the deploy parameters.
--
-- Several parameters can be set to deploy cargo:
--
-- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.SetDeployRadius}(): Sets or randomizes the deploy location for the helicopter around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
--
-- ---
--
-- ## 5. Set the home zone when there isn't any more cargo to pickup.
--
-- A home zone can be specified to where the Helicopters will move when there isn't any cargo left for pickup.
-- Use @{#AI_CARGO_DISPATCHER\_HELICOPTER.SetHomeZone}() to specify the home zone.
--
-- If no home zone is specified, the helicopters will wait near the deploy zone for a new pickup command.
--
-- ===
--
-- @field #AI_CARGO_DISPATCHER_HELICOPTER
AI_CARGO_DISPATCHER_HELICOPTER = {
ClassName = "AI_CARGO_DISPATCHER_HELICOPTER",
}
--- Creates a new AI_CARGO_DISPATCHER_HELICOPTER object.
-- @param #AI_CARGO_DISPATCHER_HELICOPTER self
-- @param Core.Set#SET_GROUP SetHelicopter The collection of Helicopter @{Wrapper.Group}s.
-- @param Core.Set#SET_CARGO SetCargo The collection of @{Cargo} derived objects.
-- @param Core.Set#SET_ZONE SetDeployZones The collection of deploy @{Zone}s, which are used to where the cargo will be deployed by the Helicopters.
-- @return #AI_CARGO_DISPATCHER_HELICOPTER
-- @usage
--
-- -- Create a new cargo dispatcher
-- SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart()
-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- AICargoDispatcher = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo )
--
function AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo, SetDeployZones )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetHelicopter, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER
self:SetDeploySpeed( 200, 150 )
self:SetPickupSpeed( 200, 150 )
self:SetPickupRadius( 0, 0 )
self:SetDeployRadius( 0, 0 )
return self
end
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, SetCargo )
return AI_CARGO_HELICOPTER:New( Helicopter, SetCargo )
end

View File

@@ -1,722 +0,0 @@
--- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo).
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Cargo_Helicopter
-- @image AI_Cargo_Dispatching_For_Helicopters.JPG
--- @type AI_CARGO_HELICOPTER
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- # AI\_CARGO\_TROOPS class, extends @{Core.Fsm#FSM_CONTROLLABLE}
--
-- ===
--
-- @field #AI_CARGO_HELICOPTER
AI_CARGO_HELICOPTER = {
ClassName = "AI_CARGO_HELICOPTER",
Coordinate = nil -- Core.Point#COORDINATE,
}
AI_CARGO_QUEUE = {}
--- Creates a new AI_CARGO_HELICOPTER object.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param Core.Set#SET_CARGO CargoSet
-- @return #AI_CARGO_HELICOPTER
function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_HELICOPTER
self.CargoSet = CargoSet -- Cargo.CargoGroup#CARGO_GROUP
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "Unloaded", "Load", "Boarding" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( "Unboarding", "Unloaded", "Unloaded" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Queue", "*" )
self:AddTransition( "*", "Orbit" , "*" )
self:AddTransition( "*", "Home" , "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #number Delay
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforeDeploy
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeploy
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Delay
-- We need to capture the Crash events for the helicopters.
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
-- So, we need to unlock this when the helo is not anymore ...
Helicopter:HandleEvent( EVENTS.Crash,
function( Helicopter, EventData )
AI_CARGO_QUEUE[Helicopter] = nil
end
)
-- We need to capture the Land events for the helicopters.
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
-- So, we need to unlock this when the helo has landed, which can be anywhere ...
-- But only free the landing coordinate after 1 minute, to ensure that all helos have left.
Helicopter:HandleEvent( EVENTS.Land,
function( Helicopter, EventData )
self:ScheduleOnce( 60,
function( Helicopter )
AI_CARGO_QUEUE[Helicopter] = nil
end, Helicopter
)
end
)
self:SetCarrier( Helicopter )
return self
end
function AI_CARGO_HELICOPTER:IsTransporting()
return self.Transporting == true
end
function AI_CARGO_HELICOPTER:IsRelocating()
return self.Relocating == true
end
--- Set the Carrier.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @return #AI_CARGO_HELICOPTER
function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
local AICargo = self
self.Helicopter = Helicopter -- Wrapper.Group#GROUP
self.Helicopter:SetState( self.Helicopter, "AI_CARGO_HELICOPTER", self )
self.RoutePickup = false
self.RouteDeploy = false
Helicopter:HandleEvent( EVENTS.Dead )
Helicopter:HandleEvent( EVENTS.Hit )
Helicopter:HandleEvent( EVENTS.Land )
function Helicopter:OnEventDead( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_HELICOPTER" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the Helicopter.
AICargoTroops:Destroyed()
end
end
end
function Helicopter:OnEventLand( EventData )
AICargo:Landed()
end
self.Coalition = self.Helicopter:GetCoalition()
self:SetControllable( Helicopter )
return self
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
Helicopter:F( { Name = Helicopter:GetName() } )
if Helicopter and Helicopter:IsAlive() then
-- S_EVENT_LAND is directly called in two situations:
-- 1 - When the helo lands normally on the ground.
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
-- For point 2, this is an issue, the infantry may not unload in this case!
-- So we check if the helo is on the ground, and velocity< 5.
-- Only then the infantry can unload (and load too, for consistency)!
self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
if self.RoutePickup == true then
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
self:Load( Helicopter:GetPointVec2() )
self.RoutePickup = false
self.Relocating = true
end
end
if self.RouteDeploy == true then
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
self:Unload( true )
self.RouteDeploy = false
self.Transporting = false
self.Relocating = false
end
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate )
local HelicopterInZone = false
if Helicopter and Helicopter:IsAlive() == true then
local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() )
if Distance > 2000 then
self:__Queue( -10, Coordinate )
else
local ZoneFree = true
for Helicopter, ZoneQueue in pairs( AI_CARGO_QUEUE ) do
local ZoneQueue = ZoneQueue -- Core.Zone#ZONE_RADIUS
if ZoneQueue:IsCoordinateInZone( Coordinate ) then
ZoneFree = false
end
end
self:F({ZoneFree=ZoneFree})
if ZoneFree == true then
local ZoneQueue = ZONE_RADIUS:New( Helicopter:GetName(), Coordinate:GetVec2(), 100 )
AI_CARGO_QUEUE[Helicopter] = ZoneQueue
local Route = {}
-- local CoordinateFrom = Helicopter:GetCoordinate()
-- local WaypointFrom = CoordinateFrom:WaypointAir(
-- "RADIO",
-- POINT_VEC3.RoutePointType.TurningPoint,
-- POINT_VEC3.RoutePointAction.TurningPoint,
-- Speed,
-- true
-- )
-- Route[#Route+1] = WaypointFrom
local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
50,
true
)
Route[#Route+1] = WaypointTo
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 0 )
else
self:__Queue( -10, Coordinate )
end
end
else
AI_CARGO_QUEUE[Helicopter] = nil
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
if Helicopter and Helicopter:IsAlive() then
if not self:IsTransporting() then
local Route = {}
-- local CoordinateFrom = Helicopter:GetCoordinate()
-- local WaypointFrom = CoordinateFrom:WaypointAir(
-- "RADIO",
-- POINT_VEC3.RoutePointType.TurningPoint,
-- POINT_VEC3.RoutePointAction.TurningPoint,
-- Speed,
-- true
-- )
-- Route[#Route+1] = WaypointFrom
local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
50,
true
)
Route[#Route+1] = WaypointTo
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 80 ), 150, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 0 )
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeLoad( Helicopter, From, Event, To, Coordinate )
local Boarding = false
if Helicopter and Helicopter:IsAlive() then
self.BoardingCount = 0
if Helicopter and Helicopter:IsAlive() then
self.Helicopter_Cargo = {}
for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do
local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsUnLoaded = Cargo:IsUnLoaded() } )
if Cargo:IsUnLoaded() then
if Cargo:IsInLoadRadius( HelicopterUnit:GetCoordinate() ) then
self:F( { "In radius", HelicopterUnit:GetName() } )
--Cargo:Ungroup()
Cargo:Board( HelicopterUnit, 25 )
self:__Board( 1, Cargo )
Boarding = true
-- So now this APCUnit has Cargo that is being loaded.
-- This will be used further in the logic to follow and to check cargo status.
self.Helicopter_Cargo[HelicopterUnit] = Cargo
break
end
end
end
end
end
end
return Boarding
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterBoard( Helicopter, From, Event, To, Cargo )
self:F( { APC, From, Event, To, Cargo } )
if Helicopter and Helicopter:IsAlive() then
self:F({ IsLoaded = Cargo:IsLoaded() } )
if not Cargo:IsLoaded() then
self:__Board( 10, Cargo )
else
self:__Loaded( 1, Cargo )
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeLoaded( Helicopter, From, Event, To, Cargo )
self:F( { APC, From, Event, To } )
local Loaded = true
if Helicopter and Helicopter:IsAlive() then
for HelicopterUnit, Cargo in pairs( self.Helicopter_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
Loaded = false
end
end
end
return Loaded
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterUnload( Helicopter, From, Event, To, Deployed )
if Helicopter and Helicopter:IsAlive() then
for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do
local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( HelicopterUnit:GetCargo() ) do
Cargo:UnBoard()
Cargo:SetDeployed( true )
self:__Unboard( 10, Cargo, Deployed )
end
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterUnboard( Helicopter, From, Event, To, Cargo, Deployed )
if Helicopter and Helicopter:IsAlive() then
if not Cargo:IsUnLoaded() then
self:__Unboard( 10, Cargo, Deployed )
else
self:__Unloaded( 1, Cargo, Deployed )
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeUnloaded( Helicopter, From, Event, To, Cargo, Deployed )
self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } )
local AllUnloaded = true
--Cargo:Regroup()
if Helicopter and Helicopter:IsAlive() then
for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do
local CargoCheck = self.Helicopter_Cargo[HelicopterUnit] -- Cargo.Cargo#CARGO
if CargoCheck then
self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } )
if CargoCheck:IsUnLoaded() == false then
AllUnloaded = false
break
end
end
end
if AllUnloaded == true then
if Deployed == true then
for HelicopterUnit, Cargo in pairs( self.Helicopter_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
end
self.Helicopter_Cargo = {}
end
self.Helicopter = Helicopter
end
end
self:F( { AllUnloaded = AllUnloaded } )
return AllUnloaded
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterUnloaded( Helicopter, From, Event, To, Cargo, Deployed )
self:Orbit( Helicopter:GetCoordinate(), 50 )
-- Free the coordinate zone after 30 seconds, so that the original helicopter can fly away first.
self:ScheduleOnce( 30,
function( Helicopter )
AI_CARGO_QUEUE[Helicopter] = nil
end, Helicopter
)
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed )
if Helicopter and Helicopter:IsAlive() ~= nil then
Helicopter:Activate()
self.RoutePickup = true
Coordinate.y = math.random( 50, 200 )
local _speed=Speed or Helicopter:GetSpeedMax()*0.5
local Route = {}
--- Calculate the target route point.
local CoordinateFrom = Helicopter:GetCoordinate()
local CoordinateTo = Coordinate
--- Create a route point of type air.
local WaypointFrom = CoordinateFrom:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
_speed,
true
)
--- Create a route point of type air.
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.Transporting = true
end
end
function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate )
AICargoHelicopter:__Queue( -10, Coordinate, 100 )
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed )
if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteDeploy = true
local Route = {}
--- Calculate the target route point.
Coordinate.y = math.random( 50, 200 )
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 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 = {}
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate )
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 )
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 Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed )
if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteHome = true
local Route = {}
--- Calculate the target route point.
Coordinate.y = math.random( 50, 200 )
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
--- Create a route point of type air.
local CoordinateTo = Coordinate
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

View File

@@ -1,34 +1,64 @@
--- **AI** -- Build large airborne formations of aircraft.
--- **AI** -- Build large **formations** of AI @{Group}s flying together.
--
-- **Features:**
-- ![Banner Image](..\Presentations\AI_FORMATION\Dia1.JPG)
--
-- ===
--
-- AI_FORMATION makes AI @{GROUP}s fly in formation of various compositions.
-- The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!!
-- The purpose of the class is to:
--
-- * Make formation building a process that can be managed while in flight, rather than a task.
-- * Human players can guide formations, consisting of larget planes.
-- * Build large formations (like a large bomber field).
-- * Form formations that DCS does not support off the shelve.
--
-- A few remarks:
--
-- * Depending on the type of plane, the change in direction by the leader may result in the formation getting disentangled while in flight and needs to be rebuild.
-- * Formations are vulnerable to collissions, but is depending on the type of plane, the distance between the planes and the speed and angle executed by the leader.
-- * Formations may take a while to build up.
--
-- As a result, the AI_FORMATION is not perfect, but is very useful to:
--
-- * Model large formations when flying straight line.
-- * Make humans guide a large formation, when the planes are wide from each other.
--
-- There are the following types of classes defined:
--
-- * @{#AI_FORMATION}: Create a formation from several @{GROUP}s.
--
-- ====
--
-- # Demo Missions
--
-- ### [AI_FORMATION Demo Missions source code]()
--
-- ### [AI_FORMATION Demo Missions, only for beta testers]()
--
-- * Build in-air formations consisting of more than 40 aircraft as one group.
-- * Build different formation types.
-- * Assign a group leader that will guide the large formation path.
-- ### [ALL Demo Missions pack of the last release]()
--
-- ====
--
-- # YouTube Channel
--
--- ### [AI_FORMATION YouTube Channel]()
--
-- ===
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/FOR%20-%20Formation)
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ===
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0bFIJ9jIdYM22uaWmIN4oz)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
-- ====
--
-- @module AI.AI_Formation
-- @image AI_Large_Formations.JPG
-- @module AI_Formation
--- AI_FORMATION class
-- @type AI_FORMATION
-- @extends Core.Fsm#FSM_SET
-- @field Wrapper.Unit#UNIT FollowUnit
-- @field Core.Set#SET_GROUP FollowGroupSet
-- @extends Fsm#FSM_SET
-- @field Unit#UNIT FollowUnit
-- @field Set#SET_GROUP FollowGroupSet
-- @field #string FollowName
-- @field #AI_FORMATION.MODE FollowMode The mode the escort is in.
-- @field Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class.
@@ -36,9 +66,12 @@
-- @field #boolean ReportTargets If true, nearby targets are reported.
-- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup.
-- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup.
-- @field Menu#MENU_CLIENT FollowMenuResumeMission
--- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader.
--- # AI_FORMATION class, extends @{Fsm#FSM_SET}
--
-- The #AI_FORMATION class allows you to build large formations, make AI follow a @{Client#CLIENT} (player) leader or a @{Unit#UNIT} (AI) leader.
--
-- AI_FORMATION makes AI @{GROUP}s fly in formation of various compositions.
-- The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!!
@@ -64,25 +97,25 @@
--
-- Create a new SPAWN object with the @{#AI_FORMATION.New} method:
--
-- * @{#AI_FORMATION.New}(): Creates a new AI_FORMATION object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT} or a @{Wrapper.Unit#UNIT}, with an optional briefing text.
-- * @{Follow#AI_FORMATION.New}(): Creates a new AI_FORMATION object from a @{Group#GROUP} for a @{Client#CLIENT} or a @{Unit#UNIT}, with an optional briefing text.
--
-- ## Formation methods
--
-- The following methods can be used to set or change the formation:
--
-- * @{#AI_FORMATION.FormationLine}(): Form a line formation (core formation function).
-- * @{#AI_FORMATION.FormationTrail}(): Form a trail formation.
-- * @{#AI_FORMATION.FormationLeftLine}(): Form a left line formation.
-- * @{#AI_FORMATION.FormationRightLine}(): Form a right line formation.
-- * @{#AI_FORMATION.FormationRightWing}(): Form a right wing formation.
-- * @{#AI_FORMATION.FormationLeftWing}(): Form a left wing formation.
-- * @{#AI_FORMATION.FormationCenterWing}(): Form a center wing formation.
-- * @{#AI_FORMATION.FormationCenterVic}(): Form a Vic formation (same as CenterWing.
-- * @{#AI_FORMATION.FormationCenterBoxed}(): Form a center boxed formation.
-- * @{AI_Formation#AI_FORMATION.FormationLine}(): Form a line formation (core formation function).
-- * @{AI_Formation#AI_FORMATION.FormationTrail}(): Form a trail formation.
-- * @{AI_Formation#AI_FORMATION.FormationLeftLine}(): Form a left line formation.
-- * @{AI_Formation#AI_FORMATION.FormationRightLine}(): Form a right line formation.
-- * @{AI_Formation#AI_FORMATION.FormationRightWing}(): Form a right wing formation.
-- * @{AI_Formation#AI_FORMATION.FormationLeftWing}(): Form a left wing formation.
-- * @{AI_Formation#AI_FORMATION.FormationCenterWing}(): Form a center wing formation.
-- * @{AI_Formation#AI_FORMATION.FormationCenterVic}(): Form a Vic formation (same as CenterWing.
-- * @{AI_Formation#AI_FORMATION.FormationCenterBoxed}(): Form a center boxed formation.
--
-- ## Randomization
--
-- Use the method @{AI.AI_Formation#AI_FORMATION.SetFlightRandomization}() to simulate the formation flying errors that pilots make while in formation. Is a range set in meters.
-- Use the method @{AI_Formation#AI_FORMATION.SetFlightRandomization}() to simulate the formation flying errors that pilots make while in formation. Is a range set in meters.
--
-- @usage
-- local FollowGroupSet = SET_GROUP:New():FilterCategories("plane"):FilterCoalitions("blue"):FilterPrefixes("Follow"):FilterStart()
@@ -122,7 +155,7 @@ AI_FORMATION = {
--- AI_FORMATION class constructor for an AI group
-- @param #AI_FORMATION self
-- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet.
-- @param Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet.
-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit.
-- @param #string FollowName Name of the escort.
-- @return #AI_FORMATION self
@@ -130,8 +163,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) )
self:F( { FollowUnit, FollowGroupSet, FollowName } )
self.FollowUnit = FollowUnit -- Wrapper.Unit#UNIT
self.FollowGroupSet = FollowGroupSet -- Core.Set#SET_GROUP
self.FollowUnit = FollowUnit -- Unit#UNIT
self.FollowGroupSet = FollowGroupSet -- Set#SET_GROUP
self:SetFlightRandomization( 2 )
@@ -646,7 +679,7 @@ end
function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
self:F( { FollowGroupSet, From , Event ,To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace } )
FollowGroupSet:Flush( self )
FollowGroupSet:Flush()
local FollowSet = FollowGroupSet:GetSet()
@@ -881,7 +914,7 @@ function AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XS
end
--- Use the method @{AI.AI_Formation#AI_FORMATION.SetFlightRandomization}() to make the air units in your formation randomize their flight a bit while in formation.
--- Use the method @{AI_Formation#AI_FORMATION.SetFlightRandomization}() to make the air units in your formation randomize their flight a bit while in formation.
-- @param #AI_FORMATION self
-- @param #number FlightRandomization The formation flying errors that pilots can make while in formation. Is a range set in meters.
-- @return #AI_FORMATION
@@ -925,7 +958,7 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
-- @param Wrapper.Unit#UNIT ClientUnit
function( FollowGroup, Formation, ClientUnit, CT1, CV1, CT2, CV2 )
FollowGroup:OptionROTEvadeFire()
FollowGroup:OptionROTPassiveDefense()
FollowGroup:OptionROEReturnFire()
local GroupUnit = FollowGroup:GetUnit( 1 )
@@ -968,7 +1001,7 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T
local Position = math.cos( Alpha_R )
local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5
local Distance = GD * Position + - CS * 0.5
local Distance = GD * Position + - CS * 0,5
-- Calculate the group direction vector
local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z }

View File

@@ -1,10 +1,6 @@
--- **AI** -- Perform Air Patrolling for airplanes.
--- **AI** -- **Air Patrolling or Staging.**
--
-- **Features:**
--
-- * Patrol AI airplanes within a given zone.
-- * Trigger detected events when enemy airplanes are detected.
-- * Manage a fuel treshold to RTB on time.
-- ![Banner Image](..\Presentations\AI_PATROL\Dia1.JPG)
--
-- ===
--
@@ -14,43 +10,53 @@
--
-- * @{#AI_PATROL_ZONE}: Perform a PATROL in a zone.
--
-- ===
-- ====
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling)
-- # Demo Missions
--
-- ===
-- ### [AI_PATROL Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling)
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky)
-- ### [AI_PATROL Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/PAT%20-%20Patrolling)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
-- ====
--
-- ### Author: **FlightControl**
-- # YouTube Channel
--
-- ### [AI_PATROL YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky)
--
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: 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](https://forums.eagle.ru/member.php?u=62835)**: Testing and API concept review.
--
-- ===
-- ====
--
-- @module AI.AI_Patrol
-- @image AI_Air_Patrolling.JPG
-- @module AI_Patrol
--- AI_PATROL_ZONE class
-- @type AI_PATROL_ZONE
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling.
-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling.
-- @field Core.Zone#ZONE_BASE PatrolZone The @{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
-- @field Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @field Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @field Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @field Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @field Functional.Spawn#SPAWN CoordTest
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}.
--- # AI_PATROL_ZONE class, extends @{Fsm#FSM_CONTROLLABLE}
--
-- The AI_PATROL_ZONE class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}.
--
-- ![Process](..\Presentations\AI_PATROL\Dia3.JPG)
--
-- The AI_PATROL_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event.
-- The AI_PATROL_ZONE is assigned a @{Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_PATROL\Dia4.JPG)
--
@@ -122,8 +128,8 @@
-- * @{#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 frequency can be set with @{#AI_PATROL_ZONE.SetDetectionInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection.
-- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Unit}s detected by the AI.
--
-- The detection can be filtered to potential targets in a specific zone.
-- Use the method @{#AI_PATROL_ZONE.SetDetectionZone}() to set the zone where targets need to be detected.
@@ -155,11 +161,11 @@ AI_PATROL_ZONE = {
--- Creates a new AI_PATROL_ZONE object
-- @param #AI_PATROL_ZONE self
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#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
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @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.
@@ -181,7 +187,7 @@ function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltit
-- defafult PatrolAltType to "RADIO" if not specified
self.PatrolAltType = PatrolAltType or "RADIO"
self:SetRefreshTimeInterval( 30 )
self:SetDetectionInterval( 30 )
self.CheckStatus = true
@@ -454,8 +460,8 @@ 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.
-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h.
-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
@@ -468,8 +474,8 @@ 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.
-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
@@ -538,7 +544,7 @@ end
-- @param #AI_PATROL_ZONE self
-- @param #number Seconds The interval in seconds.
-- @return #AI_PATROL_ZONE self
function AI_PATROL_ZONE:SetRefreshTimeInterval( Seconds )
function AI_PATROL_ZONE:SetDetectionInterval( Seconds )
self:F2()
if Seconds then
@@ -562,18 +568,18 @@ function AI_PATROL_ZONE:SetDetectionZone( DetectionZone )
end
end
--- Gets a list of @{Wrapper.Unit#UNIT}s that were detected by the AI.
--- Gets a list of @{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.
-- It is up to the mission designer to use the @{Unit} class and methods to filter the targets.
-- @param #AI_PATROL_ZONE self
-- @return #table The list of @{Wrapper.Unit#UNIT}s
-- @return #table The list of @{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.
--- Clears the list of @{Unit#UNIT}s that were detected by the AI.
-- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:ClearDetectedUnits()
self:F2()
@@ -585,12 +591,13 @@ end
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_PATROL_ZONE self
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number PatrolFuelTresholdPercentage The treshold 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 )
function AI_PATROL_ZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime )
self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage
self.PatrolManageFuel = true
self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage
self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime
return self
@@ -603,12 +610,12 @@ end
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
-- @param #AI_PATROL_ZONE self
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @param #number PatrolDamageTreshold The treshold 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 )
function AI_PATROL_ZONE:ManageDamage( PatrolDamageTreshold )
self.PatrolManageDamage = true
self.PatrolDamageThreshold = PatrolDamageThreshold
self.PatrolDamageTreshold = PatrolDamageTreshold
return self
end
@@ -741,7 +748,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TakeOffParking,
POINT_VEC3.RoutePointAction.FromParkingArea,
@@ -756,7 +763,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -782,7 +789,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y )
--- Create a route point of type air.
local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir(
local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
@@ -823,10 +830,11 @@ function AI_PATROL_ZONE:onafterStatus()
local RTB = false
local Fuel = self.Controllable:GetFuelMin()
if Fuel < self.PatrolFuelThresholdPercentage then
local Fuel = self.Controllable:GetUnit(1):GetFuel()
if Fuel < self.PatrolFuelTresholdPercentage then
self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
local OldAIControllable = self.Controllable
local AIControllableTemplate = self.Controllable:GetTemplate()
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 ) )
@@ -838,7 +846,7 @@ function AI_PATROL_ZONE:onafterStatus()
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
if Damage <= self.PatrolDamageThreshold then
if Damage <= self.PatrolDamageTreshold then
self:E( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
RTB = true
end
@@ -869,7 +877,7 @@ function AI_PATROL_ZONE:onafterRTB()
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
local CurrentRoutePoint = CurrentPointVec3:RoutePointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,

View File

@@ -1,15 +1,15 @@
--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occuring on @{Wrapper.Unit}s.
--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occuring on @{Unit}s.
--
-- ![Banner Image](..\Presentations\ACT_ACCOUNT\Dia1.JPG)
--
-- ===
--
-- @module Actions.Account
-- @image MOOSE.JPG
-- @module Account
do -- ACT_ACCOUNT
--- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS}
--- # @{#ACT_ACCOUNT} FSM class, extends @{Fsm#FSM_PROCESS}
--
-- ## ACT_ACCOUNT state machine:
--
@@ -55,7 +55,7 @@ do -- ACT_ACCOUNT
-- These state transition methods need to provide a return value, which is specified at the function description.
--
-- @type ACT_ACCOUNT
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @field Set#SET_UNIT TargetSetUnit
-- @extends Core.Fsm#FSM_PROCESS
ACT_ACCOUNT = {
ClassName = "ACT_ACCOUNT",
@@ -91,7 +91,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -107,7 +107,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -125,7 +125,7 @@ do -- ACT_ACCOUNT
--- StateMachine callback function
-- @param #ACT_ACCOUNT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -138,7 +138,7 @@ end -- ACT_ACCOUNT
do -- ACT_ACCOUNT_DEADS
--- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Core.Fsm.Account#ACT_ACCOUNT}
--- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Fsm.Account#ACT_ACCOUNT}
--
-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units.
-- The process is given a @{Set} of units that will be tracked upon successful destruction.
@@ -151,21 +151,25 @@ do -- ACT_ACCOUNT_DEADS
-- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object.
--
-- @type ACT_ACCOUNT_DEADS
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @field Set#SET_UNIT TargetSetUnit
-- @extends #ACT_ACCOUNT
ACT_ACCOUNT_DEADS = {
ClassName = "ACT_ACCOUNT_DEADS",
TargetSetUnit = nil,
}
--- Creates a new DESTROY process.
-- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param Set#SET_UNIT TargetSetUnit
-- @param #string TaskName
function ACT_ACCOUNT_DEADS:New()
function ACT_ACCOUNT_DEADS:New( TargetSetUnit, TaskName )
-- Inherits from BASE
local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS
self.TargetSetUnit = TargetSetUnit
self.TaskName = TaskName
self.DisplayInterval = 30
self.DisplayCount = 30
self.DisplayMessage = true
@@ -177,38 +181,38 @@ do -- ACT_ACCOUNT_DEADS
function ACT_ACCOUNT_DEADS:Init( FsmAccount )
self.Task = self:GetTask()
self.TaskName = self.Task:GetName()
self.TargetSetUnit = FsmAccount.TargetSetUnit
self.TaskName = FsmAccount.TaskName
end
--- Process Events
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To )
self:E( { ProcessUnit, 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 )
self:Message( "Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." )
end
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Client#CLIENT ProcessClient
-- @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 } )
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient:GetName(), Task:GetName(), From, Event, To, EventData } )
if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
local PlayerName = ProcessUnit:GetPlayerName()
if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
local PlayerName = ProcessClient:GetPlayerName()
local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
if PlayerHit == PlayerName then
self:Player( EventData )
@@ -220,26 +224,24 @@ do -- ACT_ACCOUNT_DEADS
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Client#CLIENT ProcessClient
-- @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 } )
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient:GetName(), Task:GetName(), From, Event, To, EventData } )
local TaskGroup = ProcessUnit:GetGroup()
local TaskGroup = ProcessClient: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()
self.TargetSetUnit:Remove( EventData.IniUnitName )
self:Message( "You have destroyed a target. Your group assigned with task " .. self.TaskName .. " has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." )
local PlayerName = ProcessClient:GetPlayerName()
Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 )
if Task.TargetSetUnit:Count() > 0 then
if self.TargetSetUnit:Count() > 0 then
self:__More( 1 )
else
self:__NoMore( 1 )
@@ -248,22 +250,20 @@ do -- ACT_ACCOUNT_DEADS
--- StateMachine callback function
-- @param #ACT_ACCOUNT_DEADS self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Client#CLIENT ProcessClient
-- @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 } )
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessClient, Task, From, Event, To, EventData )
self:T( { ProcessClient: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 )
local TaskGroup = ProcessClient:GetGroup()
self.TargetSetUnit:Remove( EventData.IniUnitName )
self:Message( "One of the task targets has been destroyed. Your group assigned with task " .. self.TaskName .. " has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." )
if Task.TargetSetUnit:Count() > 0 then
if self.TargetSetUnit:Count() > 0 then
self:__More( 1 )
else
self:__NoMore( 1 )
@@ -285,7 +285,7 @@ do -- ACT_ACCOUNT_DEADS
end
--- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Event#EVENTDATA EventData
-- @param Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData )
self:T( { "EventDead", EventData } )
@@ -297,7 +297,7 @@ do -- ACT_ACCOUNT_DEADS
--- DCS Events
--- @param #ACT_ACCOUNT_DEADS self
-- @param Core.Event#EVENTDATA EventData
-- @param Event#EVENTDATA EventData
function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData )
self:T( { "EventDead", EventData } )

View File

@@ -2,7 +2,7 @@
--
-- ===
--
-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS}
-- # @{#ACT_ASSIGN} FSM template class, extends @{Fsm#FSM_PROCESS}
--
-- ## ACT_ASSIGN state machine:
--
@@ -54,7 +54,7 @@
--
-- ===
--
-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Core.Fsm.Assign#ACT_ASSIGN}
-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN}
--
-- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task.
--
@@ -64,7 +64,7 @@
--
-- ===
--
-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Core.Fsm.Assign#ACT_ASSIGN}
-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN}
--
-- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option.
-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task.
@@ -77,8 +77,7 @@
--
-- ===
--
-- @module Actions.Assign
-- @image MOOSE.JPG
-- @module Assign
do -- ACT_ASSIGN
@@ -156,7 +155,8 @@ do -- ACT_ASSIGN_ACCEPT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, From, Event, To )
self:E( { ProcessUnit, From, Event, To } )
self:__Assign( 1 )
end
@@ -167,8 +167,12 @@ do -- ACT_ASSIGN_ACCEPT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To )
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, From, Event, To )
env.info( "in here" )
self:E( { ProcessUnit, From, Event, To } )
local ProcessGroup = ProcessUnit:GetGroup()
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
end
@@ -189,99 +193,99 @@ do -- ACT_ASSIGN_MENU_ACCEPT
--- Init.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param #string TaskName
-- @param #string TaskBriefing
-- @return #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:New( TaskBriefing )
function ACT_ASSIGN_MENU_ACCEPT:New( TaskName, TaskBriefing )
-- Inherits from BASE
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT
self.TaskName = TaskName
self.TaskBriefing = TaskBriefing
return self
end
function ACT_ASSIGN_MENU_ACCEPT:Init( FsmAssign )
self.TaskName = FsmAssign.TaskName
self.TaskBriefing = FsmAssign.TaskBriefing
end
--- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param #string TaskName
-- @param #string TaskBriefing
-- @return #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing )
function ACT_ASSIGN_MENU_ACCEPT:Init( TaskName, TaskBriefing )
self.TaskBriefing = TaskBriefing
self.TaskName = TaskName
return self
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, From, Event, To )
self:E( { ProcessUnit, 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 )
self:Message( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled." )
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 )
local ProcessGroup = ProcessUnit:GetGroup()
self:__Reject( 120, TaskGroup )
self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.TaskName .. " acceptance" )
self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.TaskName, self.Menu, self.MenuAssign, self )
self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.TaskName, self.Menu, self.MenuReject, self )
end
--- Menu function.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign( TaskGroup )
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign()
self:E( )
self:__Assign( -1, TaskGroup )
self:__Assign( 1 )
end
--- Menu function.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup )
function ACT_ASSIGN_MENU_ACCEPT:MenuReject()
self:E( )
self:__Reject( -1, TaskGroup )
self:__Reject( 1 )
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup )
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, From, Event, To )
self:E( { ProcessUnit.UnitNameFrom, Event, To } )
self.Menu:Remove()
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE 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 } )
function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, From, Event, To )
self:E( { ProcessUnit.UnitName, From, Event, To } )
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() )
ProcessUnit:Destroy()
end
end -- ACT_ASSIGN_MENU_ACCEPT

View File

@@ -1,5 +1,9 @@
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
--
-- ===
--
-- # @{#ACT_ASSIST} FSM class, extends @{Fsm#FSM_PROCESS}
--
-- ## ACT_ASSIST state machine:
--
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
@@ -48,7 +52,7 @@
--
-- ===
--
-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Core.Fsm.Route#ACT_ASSIST}
-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Fsm.Route#ACT_ASSIST}
--
-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}.
-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour.
@@ -60,9 +64,7 @@
--
-- ===
--
-- @module Actions.Assist
-- @image MOOSE.JPG
-- @module Smoke
do -- ACT_ASSIST
@@ -109,6 +111,7 @@ do -- ACT_ASSIST
local MissionMenu = self:GetMission():GetMenu( ProcessGroup )
local function MenuSmoke( MenuParam )
self:E( MenuParam )
local self = MenuParam.self
local SmokeColor = MenuParam.SmokeColor
self.SmokeColor = SmokeColor
@@ -140,7 +143,7 @@ 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 Set#SET_UNIT TargetSetUnit
-- @field Core.Zone#ZONE_BASE TargetZone
-- @extends #ACT_ASSIST
ACT_ASSIST_SMOKE_TARGETS_ZONE = {
@@ -156,7 +159,7 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param 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
@@ -175,7 +178,7 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param 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 )

View File

@@ -0,0 +1,198 @@
--- @module Process_JTAC
--- PROCESS_JTAC class
-- @type PROCESS_JTAC
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Core.Fsm#FSM_PROCESS
PROCESS_JTAC = {
ClassName = "PROCESS_JTAC",
Fsm = {},
TargetSetUnit = nil,
}
--- Creates a new DESTROY process.
-- @param #PROCESS_JTAC self
-- @param Tasking.Task#TASK Task
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @param Wrapper.Unit#UNIT FACUnit
-- @return #PROCESS_JTAC self
function PROCESS_JTAC:New( Task, ProcessUnit, TargetSetUnit, FACUnit )
-- Inherits from BASE
local self = BASE:Inherit( self, PROCESS:New( "JTAC", Task, ProcessUnit ) ) -- #PROCESS_JTAC
self.TargetSetUnit = TargetSetUnit
self.FACUnit = FACUnit
self.DisplayInterval = 60
self.DisplayCount = 30
self.DisplayMessage = true
self.DisplayTime = 10 -- 10 seconds is the default
self.DisplayCategory = "HQ" -- Targets is the default display category
self.Fsm = FSM_PROCESS:New( self, {
initial = 'Assigned',
events = {
{ name = 'Start', from = 'Assigned', to = 'CreatedMenu' },
{ name = 'JTACMenuUpdate', from = 'CreatedMenu', to = 'AwaitingMenu' },
{ name = 'JTACMenuAwait', from = 'AwaitingMenu', to = 'AwaitingMenu' },
{ name = 'JTACMenuSpot', from = 'AwaitingMenu', to = 'AwaitingMenu' },
{ name = 'JTACMenuCancel', from = 'AwaitingMenu', to = 'AwaitingMenu' },
{ name = 'JTACStatus', from = 'AwaitingMenu', to = 'AwaitingMenu' },
{ name = 'Fail', from = 'AwaitingMenu', to = 'Failed' },
{ name = 'Fail', from = 'CreatedMenu', to = 'Failed' },
},
callbacks = {
onStart = self.OnStart,
onJTACMenuUpdate = self.OnJTACMenuUpdate,
onJTACMenuAwait = self.OnJTACMenuAwait,
onJTACMenuSpot = self.OnJTACMenuSpot,
onJTACMenuCancel = self.OnJTACMenuCancel,
},
endstates = { 'Failed' }
} )
self:HandleEvent( EVENTS.Dead, self.EventDead )
return self
end
--- Process Events
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_JTAC self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
function PROCESS_JTAC:OnStart( Fsm, From, Event, To )
self:NextEvent( Fsm.JTACMenuUpdate )
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_JTAC self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
function PROCESS_JTAC:OnJTACMenuUpdate( Fsm, From, Event, To )
local function JTACMenuSpot( MenuParam )
self:E( MenuParam.TargetUnit.UnitName )
local self = MenuParam.self
local TargetUnit = MenuParam.TargetUnit
self:NextEvent( self.Fsm.JTACMenuSpot, TargetUnit )
end
local function JTACMenuCancel( MenuParam )
self:E( MenuParam )
local self = MenuParam.self
local TargetUnit = MenuParam.TargetUnit
self:NextEvent( self.Fsm.JTACMenuCancel, TargetUnit )
end
-- Loop each unit in the target set, and determine the threat levels map table.
local UnitThreatLevels = self.TargetSetUnit:GetUnitThreatLevels()
self:E( {"UnitThreadLevels", UnitThreatLevels } )
local JTACMenu = self.ProcessGroup:GetState( self.ProcessGroup, "JTACMenu" )
if not JTACMenu then
JTACMenu = MENU_GROUP:New( self.ProcessGroup, "JTAC", self.MissionMenu )
for ThreatLevel, ThreatLevelTable in pairs( UnitThreatLevels ) do
local JTACMenuThreatLevel = MENU_GROUP:New( self.ProcessGroup, ThreatLevelTable.UnitThreatLevelText, JTACMenu )
for ThreatUnitName, ThreatUnit in pairs( ThreatLevelTable.Units ) do
local JTACMenuUnit = MENU_GROUP:New( self.ProcessGroup, ThreatUnit:GetTypeName(), JTACMenuThreatLevel )
MENU_GROUP_COMMAND:New( self.ProcessGroup, "Lase Target", JTACMenuUnit, JTACMenuSpot, { self = self, TargetUnit = ThreatUnit } )
MENU_GROUP_COMMAND:New( self.ProcessGroup, "Cancel Target", JTACMenuUnit, JTACMenuCancel, { self = self, TargetUnit = ThreatUnit } )
end
end
end
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_JTAC self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
function PROCESS_JTAC:OnJTACMenuAwait( Fsm, From, Event, To )
if self.DisplayCount >= self.DisplayInterval then
local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC
TaskJTAC.Spots = TaskJTAC.Spots or {}
for TargetUnitName, SpotData in pairs( TaskJTAC.Spots) do
local TargetUnit = UNIT:FindByName( TargetUnitName )
self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup )
end
self.DisplayCount = 1
else
self.DisplayCount = self.DisplayCount + 1
end
self:NextEvent( Fsm.JTACMenuAwait )
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_JTAC self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT TargetUnit
function PROCESS_JTAC:OnJTACMenuSpot( Fsm, From, Event, To, TargetUnit )
local TargetUnitName = TargetUnit:GetName()
local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC
TaskJTAC.Spots = TaskJTAC.Spots or {}
TaskJTAC.Spots[TargetUnitName] = TaskJTAC.Spots[TargetUnitName] or {}
local DCSFACObject = self.FACUnit:GetDCSObject()
local TargetVec3 = TargetUnit:GetVec3()
TaskJTAC.Spots[TargetUnitName] = Spot.createInfraRed( self.FACUnit:GetDCSObject(), { x = 0, y = 1, z = 0 }, TargetUnit:GetVec3(), math.random( 1000, 9999 ) )
local SpotData = TaskJTAC.Spots[TargetUnitName]
self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup )
self:NextEvent( Fsm.JTACMenuAwait )
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_JTAC self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT TargetUnit
function PROCESS_JTAC:OnJTACMenuCancel( Fsm, From, Event, To, TargetUnit )
local TargetUnitName = TargetUnit:GetName()
local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC
TaskJTAC.Spots = TaskJTAC.Spots or {}
if TaskJTAC.Spots[TargetUnitName] then
TaskJTAC.Spots[TargetUnitName]:destroy() -- destroys the spot
TaskJTAC.Spots[TargetUnitName] = nil
end
self.FACUnit:MessageToGroup( "Stopped lasing " .. TargetUnit:GetTypeName(), 15, self.ProcessGroup )
self:NextEvent( Fsm.JTACMenuAwait )
end

View File

@@ -0,0 +1,173 @@
--- @module Process_Pickup
--- PROCESS_PICKUP class
-- @type PROCESS_PICKUP
-- @field Wrapper.Unit#UNIT ProcessUnit
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @extends Core.Fsm#FSM_PROCESS
PROCESS_PICKUP = {
ClassName = "PROCESS_PICKUP",
Fsm = {},
TargetSetUnit = nil,
}
--- Creates a new DESTROY process.
-- @param #PROCESS_PICKUP self
-- @param Tasking.Task#TASK Task
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Core.Set#SET_UNIT TargetSetUnit
-- @return #PROCESS_PICKUP self
function PROCESS_PICKUP:New( Task, ProcessName, ProcessUnit )
-- Inherits from BASE
local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_PICKUP
self.DisplayInterval = 30
self.DisplayCount = 30
self.DisplayMessage = true
self.DisplayTime = 10 -- 10 seconds is the default
self.DisplayCategory = "HQ" -- Targets is the default display category
self.Fsm = FSM_PROCESS:New( self, {
initial = 'Assigned',
events = {
{ name = 'Start', from = 'Assigned', to = 'Navigating' },
{ name = 'Start', from = 'Navigating', to = 'Navigating' },
{ name = 'Nearby', from = 'Navigating', to = 'Preparing' },
{ name = 'Pickup', from = 'Preparing', to = 'Loading' },
{ name = 'Load', from = 'Loading', to = 'Success' },
{ name = 'Fail', from = 'Assigned', to = 'Failed' },
{ name = 'Fail', from = 'Navigating', to = 'Failed' },
{ name = 'Fail', from = 'Preparing', to = 'Failed' },
},
callbacks = {
onStart = self.OnStart,
onNearby = self.OnNearby,
onPickup = self.OnPickup,
onLoad = self.OnLoad,
},
endstates = { 'Success', 'Failed' }
} )
return self
end
--- Process Events
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_PICKUP self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
function PROCESS_PICKUP:OnStart( Fsm, From, Event, To )
self:NextEvent( Fsm.Start )
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_PICKUP self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
function PROCESS_PICKUP:OnNavigating( Fsm, From, Event, To )
local TaskGroup = self.ProcessUnit:GetGroup()
if self.DisplayCount >= self.DisplayInterval then
MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup )
self.DisplayCount = 1
else
self.DisplayCount = self.DisplayCount + 1
end
return true -- Process always the event.
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_PICKUP self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Event#EVENTDATA Event
function PROCESS_PICKUP:OnHitTarget( Fsm, From, Event, To, Event )
self.TargetSetUnit:Flush()
if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then
self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName )
local TaskGroup = self.ProcessUnit:GetGroup()
MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup )
end
if self.TargetSetUnit:Count() > 0 then
self:NextEvent( Fsm.MoreTargets )
else
self:NextEvent( Fsm.Destroyed )
end
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_PICKUP self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
function PROCESS_PICKUP:OnMoreTargets( Fsm, From, Event, To )
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_PICKUP self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Event#EVENTDATA DCSEvent
function PROCESS_PICKUP:OnKilled( Fsm, From, Event, To )
self:NextEvent( Fsm.Restart )
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_PICKUP self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
function PROCESS_PICKUP:OnRestart( Fsm, From, Event, To )
self:NextEvent( Fsm.Menu )
end
--- StateMachine callback function for a PROCESS
-- @param #PROCESS_PICKUP self
-- @param Core.Fsm#FSM_PROCESS Fsm
-- @param #string Event
-- @param #string From
-- @param #string To
function PROCESS_PICKUP:OnDestroyed( Fsm, From, Event, To )
end
--- DCS Events
--- @param #PROCESS_PICKUP self
-- @param Core.Event#EVENTDATA Event
function PROCESS_PICKUP:EventDead( Event )
if Event.IniDCSUnit then
self:NextEvent( self.Fsm.HitTarget, Event )
end
end

View File

@@ -2,7 +2,7 @@
--
-- ===
--
-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS}
-- # @{#ACT_ROUTE} FSM class, extends @{Fsm#FSM_PROCESS}
--
-- ## ACT_ROUTE state machine:
--
@@ -60,9 +60,9 @@
--
-- ===
--
-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Core.Fsm.Route#ACT_ROUTE}
-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Fsm.Route#ACT_ROUTE}
--
-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{Zone}.
-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Controllable} player @{Unit} to a @{Zone}.
-- The player receives on perioding times messages with the coordinates of the route to follow.
-- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended.
--
@@ -72,8 +72,7 @@
--
-- ===
--
-- @module Actions.Route
-- @image MOOSE.JPG
-- @module Route
do -- ACT_ROUTE
@@ -124,20 +123,16 @@ do -- ACT_ROUTE
--- Set a Cancel Menu item.
-- @param #ACT_ROUTE self
-- @return #ACT_ROUTE
function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag )
function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime )
self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New(
MENU_GROUP_COMMAND:New(
MenuGroup,
MenuText,
ParentMenu,
self.MenuCancel,
self
):SetTime( MenuTime ):SetTag( MenuTag )
ParentMenu:SetTime( MenuTime )
):SetTime(MenuTime)
ParentMenu:Remove( MenuTime, MenuTag )
return self
end
@@ -159,10 +154,12 @@ do -- ACT_ROUTE
--- Get the routing text to be displayed.
-- The route mode determines the text displayed.
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT Controllable
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @return #string
function ACT_ROUTE:GetRouteText( Controllable )
self:E()
local RouteText = ""
local Coordinate = nil -- Core.Point#COORDINATE
@@ -177,7 +174,6 @@ do -- ACT_ROUTE
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
@@ -185,13 +181,13 @@ do -- ACT_ROUTE
local ShortestDistance = 0
local ShortestReferencePoint = nil
local ShortestReferenceName = ""
self:F( { CC.ReferencePoints } )
self:E( { CC.ReferencePoints } )
for ZoneName, Zone in pairs( CC.ReferencePoints ) do
self:F( { ZoneName = ZoneName } )
self:E( { ZoneName = ZoneName } )
local Zone = Zone -- Core.Zone#ZONE
local ZoneCoord = Zone:GetCoordinate()
local ZoneDistance = ZoneCoord:Get2DDistance( self.Coordinate )
self:F( { ShortestDistance, ShortestReferenceName } )
self:E( { ShortestDistance, ShortestReferenceName } )
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
ShortestDistance = ZoneDistance
ShortestReferencePoint = ZoneCoord
@@ -202,7 +198,7 @@ do -- ACT_ROUTE
RouteText = Coordinate:ToStringFromRP( ShortestReferencePoint, ShortestReferenceName, Controllable )
end
else
RouteText = Coordinate:ToString( Controllable, nil, Task )
RouteText = Coordinate:ToString( Controllable )
end
end
@@ -211,16 +207,14 @@ do -- ACT_ROUTE
function ACT_ROUTE:MenuCancel()
self:F("Cancelled")
self.CancelMenuGroupCommand:Remove()
self:__Cancel( 1 )
self:Cancel()
end
--- Task Events
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
@@ -232,7 +226,7 @@ do -- ACT_ROUTE
--- Check if the controllable has arrived.
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @return #boolean
function ACT_ROUTE:onfuncHasArrived( ProcessUnit )
return false
@@ -240,13 +234,15 @@ do -- ACT_ROUTE
--- StateMachine callback function
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To )
self:F( { "BeforeRoute 1", self.DisplayCount, self.DisplayInterval } )
if ProcessUnit:IsAlive() then
self:F( "BeforeRoute 2" )
local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic
if self.DisplayCount >= self.DisplayInterval then
self:T( { HasArrived = HasArrived } )
@@ -258,6 +254,8 @@ do -- ACT_ROUTE
self.DisplayCount = self.DisplayCount + 1
end
self:T( { DisplayCount = self.DisplayCount } )
if HasArrived then
self:__Arrive( 1 )
else
@@ -340,7 +338,7 @@ do -- ACT_ROUTE_POINT
-- @param #ACT_ROUTE_POINT self
-- @param #number Range The Range to consider the arrival. Default is 10000 meters.
function ACT_ROUTE_POINT:SetRange( Range )
self:F2( { Range } )
self:F2( { self.Range } )
self.Range = Range or 10000
end
@@ -348,13 +346,12 @@ do -- ACT_ROUTE_POINT
-- @param #ACT_ROUTE_POINT self
-- @return #number The Range to consider the arrival. Default is 10000 meters.
function ACT_ROUTE_POINT:GetRange()
self:F2( { self.Range } )
return self.Range
end
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @return #boolean
function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit )
@@ -363,7 +360,7 @@ do -- ACT_ROUTE_POINT
if Distance <= self.Range then
local RouteText = "You have arrived."
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
self:Message( RouteText )
return true
end
end
@@ -375,15 +372,14 @@ do -- ACT_ROUTE_POINT
--- StateMachine callback function
-- @param #ACT_ROUTE_POINT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To )
local RouteText = self:GetRouteText( ProcessUnit )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
self:Message( RouteText )
end
end -- ACT_ROUTE_POINT
@@ -448,13 +444,13 @@ do -- ACT_ROUTE_ZONE
--- Method override to check if the controllable has arrived.
-- @param #ACT_ROUTE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @return #boolean
function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit )
if ProcessUnit:IsInZone( self.Zone ) then
local RouteText = "You have arrived within the zone."
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
self:Message( RouteText )
end
return ProcessUnit:IsInZone( self.Zone )
@@ -464,15 +460,15 @@ do -- ACT_ROUTE_ZONE
--- StateMachine callback function
-- @param #ACT_ROUTE_ZONE self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ROUTE_ZONE:onafterReport( ProcessUnit, From, Event, To )
self:F( { ProcessUnit = ProcessUnit } )
self:E( { ProcessUnit = ProcessUnit } )
local RouteText = self:GetRouteText( ProcessUnit )
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
self:Message( RouteText )
end
end -- ACT_ROUTE_ZONE

File diff suppressed because it is too large Load Diff

View File

@@ -1,311 +0,0 @@
--- **Cargo** -- Management of single cargo crates, which are based on a @{Static} object.
--
-- ===
--
-- ### [Demo Missions]()
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module Cargo.CargoCrate
-- @image Cargo_Crates.JPG
do -- CARGO_CRATE
--- Models the behaviour of cargo crates, which can be slingloaded and boarded on helicopters.
-- @type CARGO_CRATE
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
--- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_CRATE objects to and from carriers.
--
-- ===
--
-- @field #CARGO_CRATE
CARGO_CRATE = {
ClassName = "CARGO_CRATE"
}
--- CARGO_CRATE Constructor.
-- @param #CARGO_CRATE self
-- @param Wrapper.Static#STATIC CargoStatic
-- @param #string Type
-- @param #string Name
-- @param #number LoadRadius (optional)
-- @param #number NearRadius (optional)
-- @return #CARGO_CRATE
function CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_CRATE
self:F( { Type, Name, NearRadius } )
self.CargoObject = CargoStatic -- Wrapper.Static#STATIC
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
self:SetEventPriority( 4 )
return self
end
--- @param #CARGO_CRATE self
-- @param Core.Event#EVENTDATA EventData
function CARGO_CRATE:OnEventCargoDead( EventData )
local Destroyed = false
if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() then
if self.CargoObject:GetName() == EventData.IniUnitName then
if not self.NoDestroy then
Destroyed = true
end
end
else
if self:IsLoaded() then
local CarrierName = self.CargoCarrier:GetName()
if CarrierName == EventData.IniDCSUnitName then
MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll()
Destroyed = true
self.CargoCarrier:ClearCargo()
end
end
end
if Destroyed then
self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } )
self:Destroyed()
end
end
--- Enter UnLoaded State.
-- @param #CARGO_CRATE self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2
function CARGO_CRATE:onenterUnLoaded( From, Event, To, ToPointVec2 )
--self:F( { ToPointVec2, From, Event, To } )
local Angle = 180
local Speed = 10
local Distance = 10
if From == "Loaded" then
local StartCoordinate = self.CargoCarrier:GetCoordinate()
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployCoord = StartCoordinate:Translate( Distance, CargoDeployHeading )
ToPointVec2 = ToPointVec2 or COORDINATE:NewFromVec2( { x= CargoDeployCoord.x, y = CargoDeployCoord.z } )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawnAt( ToPointVec2, 0 )
self.CargoCarrier = nil
end
end
if self.OnUnLoadedCallBack then
self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) )
self.OnUnLoadedCallBack = nil
end
end
--- Loaded State.
-- @param #CARGO_CRATE self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_CRATE:onenterLoaded( From, Event, To, CargoCarrier )
--self:F( { From, Event, To, CargoCarrier } )
self.CargoCarrier = CargoCarrier
-- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects).
if self.CargoObject then
self:T("Destroying")
self.NoDestroy = true
self.CargoObject:Destroy()
--local Coordinate = self.CargoObject:GetCoordinate():GetRandomCoordinateInRadius( 50, 20 )
--self.CargoObject:ReSpawnAt( Coordinate, 0 )
end
end
--- Check if the cargo can be Boarded.
-- @param #CARGO_CRATE self
function CARGO_CRATE:CanBoard()
return false
end
--- Check if the cargo can be Unboarded.
-- @param #CARGO_CRATE self
function CARGO_CRATE:CanUnboard()
return false
end
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
-- @param #CARGO_CRATE self
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Crate is within the report radius.
function CARGO_CRATE:IsInReportRadius( Coordinate )
--self:F( { Coordinate, LoadRadius = self.LoadRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.LoadRadius then
return true
end
end
return false
end
--- Check if Cargo Crate is in the radius for the Cargo to be Boarded or Loaded.
-- @param #CARGO_CRATE self
-- @param Core.Point#Coordinate Coordinate
-- @return #boolean true if the Cargo Crate is within the loading radius.
function CARGO_CRATE:IsInLoadRadius( Coordinate )
--self:F( { Coordinate, LoadRadius = self.NearRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.NearRadius then
return true
end
end
return false
end
--- Get the current Coordinate of the CargoGroup.
-- @param #CARGO_CRATE self
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
-- @return #nil There is no valid Cargo in the CargoGroup.
function CARGO_CRATE:GetCoordinate()
--self:F()
return self.CargoObject:GetCoordinate()
end
--- Check if the CargoGroup is alive.
-- @param #CARGO_CRATE self
-- @return #boolean true if the CargoGroup is alive.
-- @return #boolean false if the CargoGroup is dead.
function CARGO_CRATE:IsAlive()
local Alive = true
-- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive.
-- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive.
if self:IsLoaded() then
Alive = Alive == true and self.CargoCarrier:IsAlive()
else
Alive = Alive == true and self.CargoObject:IsAlive()
end
return Alive
end
--- Route Cargo to Coordinate and randomize locations.
-- @param #CARGO_CRATE self
-- @param Core.Point#COORDINATE Coordinate
function CARGO_CRATE:RouteTo( Coordinate )
self:F( {Coordinate = Coordinate } )
end
--- Check if Cargo is near to the Carrier.
-- The Cargo is near to the Carrier within NearRadius.
-- @param #CARGO_CRATE self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @param #number NearRadius
-- @return #boolean The Cargo is near to the Carrier.
-- @return #nil The Cargo is not near to the Carrier.
function CARGO_CRATE:IsNear( CargoCarrier, NearRadius )
self:F( {NearRadius = NearRadius } )
return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius )
end
--- Respawn the CargoGroup.
-- @param #CARGO_CRATE self
function CARGO_CRATE:Respawn()
self:F( { "Respawning crate " .. self:GetName() } )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event.
self:__Reset( -0.1 )
end
end
--- Respawn the CargoGroup.
-- @param #CARGO_CRATE self
function CARGO_CRATE:onafterReset()
self:F( { "Reset crate " .. self:GetName() } )
-- Respawn the group...
if self.CargoObject then
self:SetDeployed( false )
self:SetStartState( "UnLoaded" )
self.CargoCarrier = nil
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
end
end
--- Get the transportation method of the Cargo.
-- @param #CARGO_CRATE self
-- @return #string The transportation method of the Cargo.
function CARGO_CRATE:GetTransportationMethod()
if self:IsLoaded() then
return "for unloading"
else
if self:IsUnLoaded() then
return "for loading"
else
if self:IsDeployed() then
return "delivered"
end
end
end
return ""
end
end

View File

@@ -1,778 +0,0 @@
--- **Cargo** -- Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
--
-- ===
--
-- ![Banner Image](..\Presentations\CARGO\Dia1.JPG)
--
-- ===
--
-- ### [Demo Missions]()
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module Cargo.CargoGroup
-- @image Cargo_Groups.JPG
do -- CARGO_GROUP
--- @type CARGO_GROUP
-- @extends Cargo.Cargo#CARGO_REPORTABLE
-- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects.
-- @field #string GroupName The name of the CargoGroup.
--- Defines a cargo that is represented by a @{Wrapper.Group} object within the simulator.
-- The cargo can be Loaded, UnLoaded, Boarded, UnBoarded to and from Carriers.
--
-- The above cargo classes are used by the AI\_CARGO\_ classes to allow AI groups to transport cargo:
--
-- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC#AI_CARGO_APC} class.
-- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter#AI_CARGO_HELICOPTER} class.
-- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Plane#AI_CARGO_PLANE} class.
-- * AI Ships is planned.
--
-- The above cargo classes are also used by the TASK\_CARGO\_ classes to allow human players to transport cargo as part of a tasking:
--
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players.
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players.
--
-- The
--
-- @field #CARGO_GROUP CARGO_GROUP
--
CARGO_GROUP = {
ClassName = "CARGO_GROUP",
}
--- CARGO_GROUP constructor.
-- This make a new CARGO_GROUP from a @{Wrapper.Group} object.
-- It will "ungroup" the group object within the sim, and will create a @{Set} of individual Unit objects.
-- @param #CARGO_GROUP self
-- @param Wrapper.Group#GROUP CargoGroup
-- @param #string Type
-- @param #string Name
-- @param #number LoadRadius (optional)
-- @param #number NearRadius (optional)
-- @return #CARGO_GROUP
function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius )
local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius ) ) -- #CARGO_GROUP
self:F( { Type, Name, LoadRadius } )
self.CargoSet = SET_CARGO:New()
self.CargoGroup = CargoGroup
self.Grouped = true
self.CargoUnitTemplate = {}
self:SetDeployed( false )
local WeightGroup = 0
self.CargoGroup:Destroy()
local GroupName = CargoGroup:GetName()
self.CargoName = Name
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
GroupTemplate.name = self.CargoName .. "#CARGO"
GroupTemplate.groupId = nil
GroupTemplate.units = {}
for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do
UnitTemplate.name = UnitTemplate.name .. "#CARGO"
local CargoUnitName = UnitTemplate.name
self.CargoUnitTemplate[CargoUnitName] = UnitTemplate
GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
-- And we register the spawned unit as part of the CargoSet.
local Unit = UNIT:Register( CargoUnitName )
--local WeightUnit = Unit:GetDesc().massEmpty
--WeightGroup = WeightGroup + WeightUnit
local CargoUnit = CARGO_UNIT:New( Unit, Type, CargoUnitName, 10 )
self.CargoSet:Add( CargoUnitName, CargoUnit )
end
-- Then we register the new group in the database
self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
self.CargoObject = _DATABASE:Spawn( GroupTemplate )
self:SetWeight( WeightGroup )
self.CargoLimit = 10
self:T( { "Weight Cargo", WeightGroup } )
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
self:SetEventPriority( 4 )
return self
end
--- Ungroup the cargo group into individual groups with one unit.
-- This is required because by default a group will move in formation and this is really an issue for group control.
-- Therefore this method is made to be able to ungroup a group.
-- This works for ground only groups.
-- @param #CARGO_GROUP self
function CARGO_GROUP:Ungroup()
if self.Grouped == true then
self.Grouped = false
self.CargoGroup:Destroy()
for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do
local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT
if CargoUnit:IsUnLoaded() then
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
--local GroupName = env.getValueDictByKey( GroupTemplate.name )
-- We create a new group object with one unit...
-- First we prepare the template...
GroupTemplate.name = self.CargoName .. "#CARGO#" .. CargoUnitName
GroupTemplate.groupId = nil
if CargoUnit:IsUnLoaded() then
GroupTemplate.units = {}
GroupTemplate.units[1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX()
GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY()
GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading()
end
-- Then we register the new group in the database
local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
end
end
self.CargoObject = nil
end
end
--- Regroup the cargo group into one group with multiple unit.
-- This is required because by default a group will move in formation and this is really an issue for group control.
-- Therefore this method is made to be able to regroup a group.
-- This works for ground only groups.
-- @param #CARGO_GROUP self
function CARGO_GROUP:Regroup()
self:F("Regroup")
if self.Grouped == false then
self.Grouped = true
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
GroupTemplate.name = self.CargoName .. "#CARGO"
GroupTemplate.groupId = nil
GroupTemplate.units = {}
for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do
local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT
self:F( { CargoUnit:GetName(), UnLoaded = CargoUnit:IsUnLoaded() } )
if CargoUnit:IsUnLoaded() then
CargoUnit.CargoObject:Destroy()
GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX()
GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY()
GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading()
end
end
-- Then we register the new group in the database
self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID )
self:F( { "Regroup", GroupTemplate } )
-- Now we spawn the new group based on the template created.
self.CargoObject = _DATABASE:Spawn( GroupTemplate )
end
end
--- @param #CARGO_GROUP self
-- @param Core.Event#EVENTDATA EventData
function CARGO_GROUP:OnEventCargoDead( EventData )
self:I( EventData )
local Destroyed = false
if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() or self:IsUnboarding() then
Destroyed = true
for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do
local Cargo = CargoData -- Cargo.Cargo#CARGO
if Cargo:IsAlive() then
Destroyed = false
else
Cargo:Destroyed()
end
end
else
local CarrierName = self.CargoCarrier:GetName()
if CarrierName == EventData.IniDCSUnitName then
MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll()
Destroyed = true
self.CargoCarrier:ClearCargo()
end
end
if Destroyed then
self:Destroyed()
self:E( { "Cargo group destroyed" } )
end
end
--- Enter Boarding State.
-- @param #CARGO_GROUP self
-- @param Wrapper.Unit#UNIT CargoCarrier
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
--self:F( { CargoCarrier.UnitName, From, Event, To } )
local NearRadius = NearRadius or 25
if From == "UnLoaded" then
-- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2
self.CargoSet:ForEach(
function( Cargo, ... )
Cargo:__Board( 1, CargoCarrier, NearRadius, ... )
end, ...
)
self:__Boarding( 1, CargoCarrier, NearRadius, ... )
end
end
--- Enter Loaded State.
-- @param #CARGO_GROUP self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_GROUP:onenterLoaded( From, Event, To, CargoCarrier, ... )
--self:F( { From, Event, To, CargoCarrier, ...} )
if From == "UnLoaded" then
-- For each Cargo object within the CARGO_GROUP, load each cargo to the CargoCarrier.
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
Cargo:Load( CargoCarrier )
end
end
--self.CargoObject:Destroy()
self.CargoCarrier = CargoCarrier
self.CargoCarrier:AddCargo( self )
end
--- Leave Boarding State.
-- @param #CARGO_GROUP self
-- @param Wrapper.Unit#UNIT CargoCarrier
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
--self:F( { CargoCarrier.UnitName, From, Event, To } )
local NearRadius = NearRadius or 100
local Boarded = true
local Cancelled = false
local Dead = true
self.CargoSet:Flush()
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
self:T( { Cargo:GetName(), Cargo.current } )
if not Cargo:is( "Loaded" )
and (not Cargo:is( "Destroyed" )) then -- If one or more units of a group defined as CARGO_GROUP died, the CARGO_GROUP:Board() command does not trigger the CARGO_GRUOP:OnEnterLoaded() function.
Boarded = false
end
if Cargo:is( "UnLoaded" ) then
Cancelled = true
end
if not Cargo:is( "Destroyed" ) then
Dead = false
end
end
if not Dead then
if not Cancelled then
if not Boarded then
self:__Boarding( 1, CargoCarrier, NearRadius, ... )
else
self:F("Group Cargo is loaded")
self:__Load( 1, CargoCarrier, ... )
end
else
self:__CancelBoarding( 1, CargoCarrier, NearRadius, ... )
end
else
self:__Destroyed( 1, CargoCarrier, NearRadius, ... )
end
end
--- Enter UnBoarding State.
-- @param #CARGO_GROUP self
-- @param Core.Point#POINT_VEC2 ToPointVec2
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... )
self:F( {From, Event, To, ToPointVec2, NearRadius } )
NearRadius = NearRadius or 25
local Timer = 1
if From == "Loaded" then
if self.CargoObject then
self.CargoObject:Destroy()
end
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
self.CargoSet:ForEach(
--- @param Cargo.Cargo#CARGO Cargo
function( Cargo, NearRadius )
if not Cargo:IsDestroyed() then
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 + 3
end
end, { NearRadius }
)
self:__UnBoarding( 1, ToPointVec2, NearRadius, ... )
end
end
--- Leave UnBoarding State.
-- @param #CARGO_GROUP self
-- @param Core.Point#POINT_VEC2 ToPointVec2
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... )
--self:F( { From, Event, To, ToPointVec2, NearRadius } )
--local NearRadius = NearRadius or 25
local Angle = 180
local Speed = 10
local Distance = 5
if From == "UnBoarding" then
local UnBoarded = true
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
self:T( { Cargo:GetName(), Cargo.current } )
if not Cargo:is( "UnLoaded" ) and not Cargo:IsDestroyed() then
UnBoarded = false
end
end
if UnBoarded then
return true
else
self:__UnBoarding( 1, ToPointVec2, NearRadius, ... )
end
return false
end
end
--- UnBoard Event.
-- @param #CARGO_GROUP self
-- @param Core.Point#POINT_VEC2 ToPointVec2
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... )
--self:F( { From, Event, To, ToPointVec2, NearRadius } )
--local NearRadius = NearRadius or 25
self:__UnLoad( 1, ToPointVec2, ... )
end
--- Enter UnLoaded State.
-- @param #CARGO_GROUP self
-- @param Core.Point#POINT_VEC2
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... )
--self:F( { From, Event, To, ToPointVec2 } )
if From == "Loaded" then
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
self.CargoSet:ForEach(
function( Cargo )
--Cargo:UnLoad( ToPointVec2 )
local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(10)
Cargo:UnLoad( RandomVec2 )
end
)
end
self.CargoCarrier:RemoveCargo( self )
self.CargoCarrier = nil
end
--- Get the current Coordinate of the CargoGroup.
-- @param #CARGO_GROUP self
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
-- @return #nil There is no valid Cargo in the CargoGroup.
function CARGO_GROUP:GetCoordinate()
self:F()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
return Cargo.CargoObject:GetCoordinate()
end
return nil
end
--- Get the x position of the cargo.
-- @param #CARGO_GROUP self
-- @return #number
function CARGO:GetX()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
return Cargo:GetCoordinate().x
end
return nil
end
--- Get the y position of the cargo.
-- @param #CARGO_GROUP self
-- @return #number
function CARGO:GetY()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
return Cargo:GetCoordinate().z
end
return nil
end
--- Check if the CargoGroup is alive.
-- @param #CARGO_GROUP self
-- @return #boolean true if the CargoGroup is alive.
-- @return #boolean false if the CargoGroup is dead.
function CARGO_GROUP:IsAlive()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
return Cargo ~= nil
end
--- Get the first alive Cargo Unit of the Cargo Group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetFirstAlive()
local CargoFirstAlive = nil
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
if not Cargo:IsDestroyed() then
CargoFirstAlive = Cargo
break
end
end
return CargoFirstAlive
end
--- Get the amount of cargo units in the group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetCount()
return self.CargoSet:Count()
end
--- Get the amount of cargo units in the group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetGroup( Cargo )
local Cargo = Cargo or self:GetFirstAlive() -- Cargo.Cargo#CARGO
return Cargo.CargoObject:GetGroup()
end
--- Route Cargo to Coordinate and randomize locations.
-- @param #CARGO_GROUP self
-- @param Core.Point#COORDINATE Coordinate
function CARGO_GROUP:RouteTo( Coordinate )
--self:F( {Coordinate = Coordinate } )
-- For each Cargo within the CargoSet, route each object to the Coordinate
self.CargoSet:ForEach(
function( Cargo )
Cargo.CargoObject:RouteGroundTo( Coordinate, 10, "vee", 0 )
end
)
end
--- Check if Cargo is near to the Carrier.
-- The Cargo is near to the Carrier if the first unit of the Cargo Group is within NearRadius.
-- @param #CARGO_GROUP self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @param #number NearRadius
-- @return #boolean The Cargo is near to the Carrier.
-- @return #nil The Cargo is not near to the Carrier.
function CARGO_GROUP:IsNear( CargoCarrier, NearRadius )
self:F( {NearRadius = NearRadius } )
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsAlive() then
if Cargo:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) then
self:F( "Near" )
return true
end
end
end
return nil
end
--- Check if Cargo Group is in the radius for the Cargo to be Boarded.
-- @param #CARGO_GROUP self
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Group is within the load radius.
function CARGO_GROUP:IsInLoadRadius( Coordinate )
--self:F( { Coordinate } )
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
local Distance = 0
if Cargo:IsLoaded() then
Distance = Coordinate:Get2DDistance( Cargo.CargoCarrier:GetCoordinate() )
else
Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() )
end
self:F( { Distance = Distance, LoadRadius = self.LoadRadius } )
if Distance <= self.LoadRadius then
return true
else
return false
end
end
return nil
end
--- Check if Cargo Group is in the report radius.
-- @param #CARGO_GROUP self
-- @param Core.Point#Coordinate Coordinate
-- @return #boolean true if the Cargo Group is within the report radius.
function CARGO_GROUP:IsInReportRadius( Coordinate )
--self:F( { Coordinate } )
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
self:F( { Cargo } )
local Distance = 0
if Cargo:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.LoadRadius then
return true
end
end
end
return nil
end
--- Respawn the CargoGroup.
-- @param #CARGO_GROUP self
function CARGO_GROUP:Respawn()
self:F( { "Respawning" } )
for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do
local Cargo = CargoData -- Cargo.Cargo#CARGO
Cargo:Destroy()
Cargo:SetStartState( "UnLoaded" )
end
-- We iterate through the group template and for each unit in the template, we create a new group with one unit.
for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
local GroupName = env.getValueDictByKey( GroupTemplate.name )
-- We create a new group object with one unit...
-- First we prepare the template...
GroupTemplate.name = GroupName .. "#CARGO#" .. UnitID
GroupTemplate.groupId = nil
GroupTemplate.units = {}
GroupTemplate.units[1] = UnitTemplate
local UnitName = UnitTemplate.name .. "#CARGO"
GroupTemplate.units[1].name = UnitTemplate.name .. "#CARGO"
-- Then we register the new group in the database
local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
-- And we register the spawned unit as part of the CargoSet.
local Unit = UNIT:FindByName( UnitName )
--local WeightUnit = Unit:GetDesc().massEmpty
--WeightGroup = WeightGroup + WeightUnit
local CargoUnit = CARGO_UNIT:New( Unit, Type, UnitName, 10 )
self.CargoSet:Add( UnitName, CargoUnit )
end
self:SetDeployed( false )
self:SetStartState( "UnLoaded" )
end
--- Signal a flare at the position of the CargoGroup.
-- @param #CARGO_GROUP self
-- @param Utilities.Utils#FLARECOLOR FlareColor
function CARGO_GROUP:Flare( FlareColor )
local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO
if Cargo then
Cargo:Flare( FlareColor )
end
end
--- Smoke the CargoGroup.
-- @param #CARGO_GROUP self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke.
-- @param #number Radius The radius of randomization around the center of the first element of the CargoGroup.
function CARGO_GROUP:Smoke( SmokeColor, Radius )
local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO
if Cargo then
Cargo:Smoke( SmokeColor, Radius )
end
end
--- Check if the first element of the CargoGroup is the given @{Zone}.
-- @param #CARGO_GROUP self
-- @param Core.Zone#ZONE_BASE Zone
-- @return #boolean **true** if the first element of the CargoGroup is in the Zone
-- @return #boolean **false** if there is no element of the CargoGroup in the Zone.
function CARGO_GROUP:IsInZone( Zone )
--self:F( { Zone } )
local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO
if Cargo then
return Cargo:IsInZone( Zone )
end
return nil
end
--- Get the transportation method of the Cargo.
-- @param #CARGO_GROUP self
-- @return #string The transportation method of the Cargo.
function CARGO_GROUP:GetTransportationMethod()
if self:IsLoaded() then
return "for unboarding"
else
if self:IsUnLoaded() then
return "for boarding"
else
if self:IsDeployed() then
return "delivered"
end
end
end
return ""
end
end -- CARGO_GROUP

View File

@@ -1,262 +0,0 @@
--- **Cargo** -- Management of single cargo crates, which are based on a @{Static} object. The cargo can only be slingloaded.
--
-- ===
--
-- ### [Demo Missions]()
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module Cargo.CargoSlingload
-- @image Cargo_Slingload.JPG
do -- CARGO_SLINGLOAD
--- Models the behaviour of cargo crates, which can only be slingloaded.
-- @type CARGO_SLINGLOAD
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
--- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
--
-- ===
--
-- @field #CARGO_SLINGLOAD
CARGO_SLINGLOAD = {
ClassName = "CARGO_SLINGLOAD"
}
--- CARGO_SLINGLOAD Constructor.
-- @param #CARGO_SLINGLOAD self
-- @param Wrapper.Static#STATIC CargoStatic
-- @param #string Type
-- @param #string Name
-- @param #number LoadRadius (optional)
-- @param #number NearRadius (optional)
-- @return #CARGO_SLINGLOAD
function CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_SLINGLOAD
self:F( { Type, Name, NearRadius } )
self.CargoObject = CargoStatic
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
self:SetEventPriority( 4 )
return self
end
--- @param #CARGO_SLINGLOAD self
-- @param Core.Event#EVENTDATA EventData
function CARGO_SLINGLOAD:OnEventCargoDead( EventData )
local Destroyed = false
if self:IsDestroyed() or self:IsUnLoaded() then
if self.CargoObject:GetName() == EventData.IniUnitName then
if not self.NoDestroy then
Destroyed = true
end
end
end
if Destroyed then
self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } )
self:Destroyed()
end
end
--- Check if the cargo can be Slingloaded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanSlingload()
return true
end
--- Check if the cargo can be Boarded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanBoard()
return false
end
--- Check if the cargo can be Unboarded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanUnboard()
return false
end
--- Check if the cargo can be Loaded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanLoad()
return false
end
--- Check if the cargo can be Unloaded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanUnload()
return false
end
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
-- @param #CARGO_SLINGLOAD self
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Crate is within the report radius.
function CARGO_SLINGLOAD:IsInReportRadius( Coordinate )
--self:F( { Coordinate, LoadRadius = self.LoadRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
if Distance <= self.LoadRadius then
return true
end
end
return false
end
--- Check if Cargo Slingload is in the radius for the Cargo to be Boarded or Loaded.
-- @param #CARGO_SLINGLOAD self
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Slingload is within the loading radius.
function CARGO_SLINGLOAD:IsInLoadRadius( Coordinate )
--self:F( { Coordinate } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
if Distance <= self.NearRadius then
return true
end
end
return false
end
--- Get the current Coordinate of the CargoGroup.
-- @param #CARGO_SLINGLOAD self
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
-- @return #nil There is no valid Cargo in the CargoGroup.
function CARGO_SLINGLOAD:GetCoordinate()
--self:F()
return self.CargoObject:GetCoordinate()
end
--- Check if the CargoGroup is alive.
-- @param #CARGO_SLINGLOAD self
-- @return #boolean true if the CargoGroup is alive.
-- @return #boolean false if the CargoGroup is dead.
function CARGO_SLINGLOAD:IsAlive()
local Alive = true
-- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive.
-- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive.
if self:IsLoaded() then
Alive = Alive == true and self.CargoCarrier:IsAlive()
else
Alive = Alive == true and self.CargoObject:IsAlive()
end
return Alive
end
--- Route Cargo to Coordinate and randomize locations.
-- @param #CARGO_SLINGLOAD self
-- @param Core.Point#COORDINATE Coordinate
function CARGO_SLINGLOAD:RouteTo( Coordinate )
--self:F( {Coordinate = Coordinate } )
end
--- Check if Cargo is near to the Carrier.
-- The Cargo is near to the Carrier within NearRadius.
-- @param #CARGO_SLINGLOAD self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @param #number NearRadius
-- @return #boolean The Cargo is near to the Carrier.
-- @return #nil The Cargo is not near to the Carrier.
function CARGO_SLINGLOAD:IsNear( CargoCarrier, NearRadius )
--self:F( {NearRadius = NearRadius } )
return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius )
end
--- Respawn the CargoGroup.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:Respawn()
--self:F( { "Respawning slingload " .. self:GetName() } )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event.
self:__Reset( -0.1 )
end
end
--- Respawn the CargoGroup.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:onafterReset()
--self:F( { "Reset slingload " .. self:GetName() } )
-- Respawn the group...
if self.CargoObject then
self:SetDeployed( false )
self:SetStartState( "UnLoaded" )
self.CargoCarrier = nil
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
end
end
--- Get the transportation method of the Cargo.
-- @param #CARGO_SLINGLOAD self
-- @return #string The transportation method of the Cargo.
function CARGO_SLINGLOAD:GetTransportationMethod()
if self:IsLoaded() then
return "for sling loading"
else
if self:IsUnLoaded() then
return "for sling loading"
else
if self:IsDeployed() then
return "delivered"
end
end
end
return ""
end
end

View File

@@ -1,393 +0,0 @@
--- **Cargo** -- Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
--
-- ===
--
-- ### [Demo Missions]()
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module Cargo.CargoUnit
-- @image Cargo_Units.JPG
do -- CARGO_UNIT
--- Models CARGO in the form of units, which can be boarded, unboarded, loaded, unloaded.
-- @type CARGO_UNIT
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
--- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_UNIT objects to and from carriers.
--
-- ===
--
-- @field #CARGO_UNIT CARGO_UNIT
--
CARGO_UNIT = {
ClassName = "CARGO_UNIT"
}
--- CARGO_UNIT Constructor.
-- @param #CARGO_UNIT self
-- @param Wrapper.Unit#UNIT CargoUnit
-- @param #string Type
-- @param #string Name
-- @param #number Weight
-- @param #number LoadRadius (optional)
-- @param #number NearRadius (optional)
-- @return #CARGO_UNIT
function CARGO_UNIT:New( CargoUnit, Type, Name, Weight, NearRadius )
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, NearRadius ) ) -- #CARGO_UNIT
self:I( { Type, Name, Weight, NearRadius } )
self:T( CargoUnit )
self.CargoObject = CargoUnit
self:T( self.ClassName )
self:SetEventPriority( 5 )
return self
end
--- Enter UnBoarding State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2 ToPointVec2
function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius )
self:F( { From, Event, To, ToPointVec2, NearRadius } )
NearRadius = NearRadius or 25
local Angle = 180
local Speed = 60
local DeployDistance = 9
local RouteDistance = 60
if From == "Loaded" then
if not self:IsDestroyed() then
local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE
if CargoCarrier:IsAlive() then
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading )
-- if there is no ToPointVec2 given, then use the CargoRoutePointVec2
local FromDirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3( ToPointVec2 or CargoRoutePointVec2 )
local FromAngle = CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3)
local FromPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, FromAngle )
--local CargoDeployPointVec2 = CargoCarrierPointVec2:GetRandomCoordinateInRadius( 10, 5 )
ToPointVec2 = ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius( NearRadius, DeployDistance )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } )
self.CargoCarrier = nil
local Points = {}
-- From
Points[#Points+1] = FromPointVec2:WaypointGround( Speed, "Vee" )
-- To
Points[#Points+1] = ToPointVec2:WaypointGround( Speed, "Vee" )
local TaskRoute = self.CargoObject:TaskRoute( Points )
self.CargoObject:SetTask( TaskRoute, 1 )
self:__UnBoarding( 1, ToPointVec2, NearRadius )
end
else
-- the Carrier is dead. This cargo is dead too!
self:Destroyed()
end
end
end
end
--- Leave UnBoarding State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2 ToPointVec2
function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius )
self:F( { From, Event, To, ToPointVec2, NearRadius } )
NearRadius = NearRadius or 100
local Angle = 180
local Speed = 10
local Distance = 5
if From == "UnBoarding" then
--if self:IsNear( ToPointVec2, NearRadius ) then
return true
--else
--self:__UnBoarding( 1, ToPointVec2, NearRadius )
--end
--return false
end
end
--- UnBoard Event.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2 ToPointVec2
function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius )
self:F( { From, Event, To, ToPointVec2, NearRadius } )
NearRadius = NearRadius or 100
self.CargoInAir = self.CargoObject:InAir()
self:T( self.CargoInAir )
-- Only unboard the cargo when the carrier is not in the air.
-- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea).
if not self.CargoInAir then
end
self:__UnLoad( 1, ToPointVec2, NearRadius )
end
--- Enter UnLoaded State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2
function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 )
self:F( { ToPointVec2, From, Event, To } )
local Angle = 180
local Speed = 10
local Distance = 5
if From == "Loaded" then
local StartPointVec2 = self.CargoCarrier:GetPointVec2()
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployCoord = StartPointVec2:Translate( Distance, CargoDeployHeading )
ToPointVec2 = ToPointVec2 or COORDINATE:New( CargoDeployCoord.x, CargoDeployCoord.z )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawnAt( ToPointVec2, 0 )
self.CargoCarrier = nil
end
end
if self.OnUnLoadedCallBack then
self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) )
self.OnUnLoadedCallBack = nil
end
end
--- Board Event.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... )
self:F( { From, Event, To, CargoCarrier, NearRadius } )
local NearRadius = NearRadius or 25
self.CargoInAir = self.CargoObject:InAir()
local Desc = self.CargoObject:GetDesc()
local MaxSpeed = Desc.speedMaxOffRoad
local TypeName = Desc.typeName
self:T( self.CargoInAir )
-- Only move the group to the carrier when the cargo is not in the air
-- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea).
if not self.CargoInAir then
if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then
self:Load( CargoCarrier, NearRadius, ... )
else
if MaxSpeed and MaxSpeed == 0 or TypeName and TypeName == "Stinger comm" then
self:Load( CargoCarrier, NearRadius, ... )
else
local Speed = 90
local Angle = 180
local Distance = 5
NearRadius = NearRadius or 25
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading )
-- Set the CargoObject to state Green to ensure it is boarding!
self.CargoObject:OptionAlarmStateGreen()
local Points = {}
local PointStartVec2 = self.CargoObject:GetPointVec2()
Points[#Points+1] = PointStartVec2:WaypointGround( Speed )
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed )
local TaskRoute = self.CargoObject:TaskRoute( Points )
self.CargoObject:SetTask( TaskRoute, 2 )
self:__Boarding( -1, CargoCarrier, NearRadius )
self.RunCount = 0
end
end
end
end
--- Boarding Event.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Client#CLIENT CargoCarrier
-- @param #number NearRadius
function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
--self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } )
if CargoCarrier and CargoCarrier:IsAlive() and self.CargoObject and self.CargoObject:IsAlive() then
if CargoCarrier:InAir() == false then
if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then
self:__Load( 1, CargoCarrier, ... )
else
self:__Boarding( -1, CargoCarrier, NearRadius, ... )
self.RunCount = self.RunCount + 1
if self.RunCount >= 40 then
self.RunCount = 0
local Speed = 90
local Angle = 180
local Distance = 5
NearRadius = NearRadius or 25
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading )
-- Set the CargoObject to state Green to ensure it is boarding!
self.CargoObject:OptionAlarmStateGreen()
local Points = {}
local PointStartVec2 = self.CargoObject:GetPointVec2()
Points[#Points+1] = PointStartVec2:WaypointGround( Speed, "Off road" )
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed, "Off road" )
local TaskRoute = self.CargoObject:TaskRoute( Points )
self.CargoObject:SetTask( TaskRoute, 0.2 )
end
end
else
self.CargoObject:MessageToGroup( "Cancelling Boarding... Get back on the ground!", 5, CargoCarrier:GetGroup(), self:GetName() )
self:CancelBoarding( CargoCarrier, NearRadius, ... )
self.CargoObject:SetCommand( self.CargoObject:CommandStopRoute( true ) )
end
else
self:E("Something is wrong")
end
end
--- Enter Boarding State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
--self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } )
local Speed = 90
local Angle = 180
local Distance = 5
local NearRadius = NearRadius or 25
if From == "UnLoaded" or From == "Boarding" then
end
end
--- Loaded State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier )
self:F( { From, Event, To, CargoCarrier } )
self.CargoCarrier = CargoCarrier
-- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects).
if self.CargoObject then
self:T("Destroying")
self.CargoObject:Destroy()
--self.CargoObject:ReSpawnAt( COORDINATE:NewFromVec2( {x=0,y=0} ), 0 )
end
end
--- Get the transportation method of the Cargo.
-- @param #CARGO_UNIT self
-- @return #string The transportation method of the Cargo.
function CARGO_UNIT:GetTransportationMethod()
if self:IsLoaded() then
return "for unboarding"
else
if self:IsUnLoaded() then
return "for boarding"
else
if self:IsDeployed() then
return "delivered"
end
end
end
return ""
end
end -- CARGO_UNIT

View File

@@ -1,12 +1,15 @@
--- **Core** -- BASE forms **the basis of the MOOSE framework**. Each class within the MOOSE framework derives from BASE.
--
-- ### Author: **FlightControl**
-- ### Contributions:
-- ![Banner Image](..\Presentations\BASE\Dia1.JPG)
--
-- ===
--
-- @module Core.Base
-- @image Core_Base.JPG
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ====
--
-- @module Base
@@ -23,7 +26,9 @@ local _ClassID = 0
-- @field ClassID The ID number of the class.
-- @field ClassNameAndID The name of the class concatenated with the ID number of the class.
--- All classes within the MOOSE framework are derived from the BASE class.
--- # 1) #BASE class
--
-- All classes within the MOOSE framework are derived from the BASE class.
--
-- BASE provides facilities for :
--
@@ -37,8 +42,8 @@ local _ClassID = 0
--
-- ## 1.1) BASE constructor
--
-- Any class derived from BASE, will use the @{Core.Base#BASE.New} constructor embedded in the @{Core.Base#BASE.Inherit} method.
-- See an example at the @{Core.Base#BASE.New} method how this is done.
-- Any class derived from BASE, will use the @{Base#BASE.New} constructor embedded in the @{Base#BASE.Inherit} method.
-- See an example at the @{Base#BASE.New} method how this is done.
--
-- ## 1.2) Trace information for debugging
--
@@ -122,7 +127,7 @@ local _ClassID = 0
-- ### 1.3.2 Event Handling of DCS Events
--
-- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called
-- when the DCS event occurs. The Event Handling method receives an @{Core.Event#EVENTDATA} structure, which contains a lot of information
-- when the DCS event occurs. The Event Handling method receives an @{Event#EVENTDATA} structure, which contains a lot of information
-- about the event that occurred.
--
-- Find below an example of the prototype how to write an event handling function for two units:
@@ -193,19 +198,13 @@ BASE = {
ClassID = 0,
Events = {},
States = {},
Debug = debug,
Scheduler = nil,
_ = {},
}
--- @field #BASE.__
BASE.__ = {}
--- @field #BASE._
BASE._ = {
Schedules = {} --- Contains the Schedulers Active
}
--- The Formation Class
-- @type FORMATION
-- @field Cone A cone formation.
@@ -266,23 +265,6 @@ function BASE:Inherit( Child, Parent )
return Child
end
local function getParent( Child )
local Parent = nil
if Child.ClassName == 'BASE' then
Parent = nil
else
if rawget( Child, "__" ) then
Parent = getmetatable( Child.__ ).__index
else
Parent = getmetatable( Child ).__index
end
end
return Parent
end
--- This is the worker method to retrieve the Parent class.
-- Note that the Parent class must be passed to call the parent class method.
--
@@ -292,91 +274,16 @@ end
-- @param #BASE self
-- @param #BASE Child is the Child class from which the Parent class needs to be retrieved.
-- @return #BASE
function BASE:GetParent( Child, FromClass )
function BASE:GetParent( Child )
local Parent
-- BASE class has no parent
if Child.ClassName == 'BASE' then
Parent = nil
else
--self:E({FromClass = FromClass})
--self:E({Child = Child.ClassName})
if FromClass then
while( Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName ) do
Child = getParent( Child )
--self:E({Child.ClassName})
end
end
if Child.ClassName == 'BASE' then
Parent = nil
else
Parent = getParent( Child )
end
end
--self:E({Parent.ClassName})
return Parent
if rawget( Child, "__" ) then
Parent = getmetatable( Child.__ ).__index
else
Parent = getmetatable( Child ).__index
end
return Parent
end
--- This is the worker method to check if an object is an (sub)instance of a class.
--
-- ### Examples:
--
-- * ZONE:New( 'some zone' ):IsInstanceOf( ZONE ) will return true
-- * ZONE:New( 'some zone' ):IsInstanceOf( 'ZONE' ) will return true
-- * ZONE:New( 'some zone' ):IsInstanceOf( 'zone' ) will return true
-- * ZONE:New( 'some zone' ):IsInstanceOf( 'BASE' ) will return true
--
-- * ZONE:New( 'some zone' ):IsInstanceOf( 'GROUP' ) will return false
--
-- @param #BASE self
-- @param ClassName is the name of the class or the class itself to run the check against
-- @return #boolean
function BASE:IsInstanceOf( ClassName )
-- Is className NOT a string ?
if type( ClassName ) ~= 'string' then
-- Is className a Moose class ?
if type( ClassName ) == 'table' and ClassName.ClassName ~= nil then
-- Get the name of the Moose class as a string
ClassName = ClassName.ClassName
-- className is neither a string nor a Moose class, throw an error
else
-- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall
local err_str = 'className parameter should be a string; parameter received: '..type( ClassName )
self:E( err_str )
-- error( err_str )
return false
end
end
ClassName = string.upper( ClassName )
if string.upper( self.ClassName ) == ClassName then
return true
end
local Parent = getParent(self)
while Parent do
if string.upper( Parent.ClassName ) == ClassName then
return true
end
Parent = getParent( Parent )
end
return false
end
--- Get the ClassName + ClassID of the class instance.
-- The ClassName + ClassID is formatted as '%s#%09d'.
-- @param #BASE self
@@ -604,8 +511,8 @@ end
--- Creation of a Birth Event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event.
-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event.
-- @param #string IniUnitName The initiating unit name.
-- @param place
-- @param subplace
@@ -626,8 +533,8 @@ end
--- Creation of a Crash Event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event.
-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event.
function BASE:CreateEventCrash( EventTime, Initiator )
self:F( { EventTime, Initiator } )
@@ -640,42 +547,10 @@ function BASE:CreateEventCrash( EventTime, Initiator )
world.onEvent( Event )
end
--- Creation of a Dead Event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
function BASE:CreateEventDead( EventTime, Initiator )
self:F( { EventTime, Initiator } )
local Event = {
id = world.event.S_EVENT_DEAD,
time = EventTime,
initiator = Initiator,
}
world.onEvent( Event )
end
--- Creation of a Takeoff Event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
function BASE:CreateEventTakeoff( EventTime, Initiator )
self:F( { EventTime, Initiator } )
local Event = {
id = world.event.S_EVENT_TAKEOFF,
time = EventTime,
initiator = Initiator,
}
world.onEvent( Event )
end
-- TODO: Complete DCS#Event structure.
-- TODO: Complete Dcs.DCSTypes#Event structure.
--- The main event handling function... This function captures all events generated for the class.
-- @param #BASE self
-- @param DCS#Event event
-- @param Dcs.DCSTypes#Event event
function BASE:onEvent(event)
--self:F( { BaseEventCodes[event.id], event } )
@@ -702,96 +577,6 @@ function BASE:onEvent(event)
end
end
do -- Scheduling
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @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 #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
function BASE:ScheduleOnce( Start, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
local ObjectName = "-"
ObjectName = self.ClassName .. self.ClassID
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
if not self.Scheduler then
self.Scheduler = SCHEDULER:New( self )
end
self.Scheduler.SchedulerObject = self.Scheduler
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
{ ... },
Start,
nil,
nil,
nil
)
self._.Schedules[#self._.Schedules+1] = ScheduleID
return self._.Schedules[#self._.Schedules]
end
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
-- @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 #number Repeat Specifies the interval in seconds when the scheduler will call the event function.
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
-- @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 #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
function BASE:ScheduleRepeat( Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
local ObjectName = "-"
ObjectName = self.ClassName .. self.ClassID
self:F3( { "ScheduleRepeat: ", ObjectName, Start, Repeat, RandomizeFactor, Stop } )
if not self.Scheduler then
self.Scheduler = SCHEDULER:New( self )
end
self.Scheduler.SchedulerObject = self.Scheduler
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
{ ... },
Start,
Repeat,
RandomizeFactor,
Stop
)
self._.Schedules[#self._.Schedules+1] = ScheduleID
return self._.Schedules[#self._.Schedules]
end
--- Stops the Schedule.
-- @param #BASE self
-- @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.
function BASE:ScheduleStop( SchedulerFunction )
self:F3( { "ScheduleStop:" } )
_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
end
end
--- Set a state or property of the Object given a Key and a Value.
-- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone.
-- @param #BASE self
@@ -806,6 +591,7 @@ function BASE:SetState( Object, Key, Value )
self.States[ClassNameAndID] = self.States[ClassNameAndID] or {}
self.States[ClassNameAndID][Key] = Value
self:T2( { ClassNameAndID, Key, Value } )
return self.States[ClassNameAndID][Key]
end
@@ -823,6 +609,7 @@ function BASE:GetState( Object, Key )
if self.States[ClassNameAndID] then
local Value = self.States[ClassNameAndID][Key] or false
self:T2( { ClassNameAndID, Key, Value } )
return Value
end
@@ -843,7 +630,7 @@ end
-- TODO: Make trace function using variable parameters.
--- Set trace on or off
-- Note that when trace is off, no BASE.Debug statement is performed, increasing performance!
-- Note that when trace is off, no debug statement is performed, increasing performance!
-- When Moose is loaded statically, (as one file), tracing is switched off by default.
-- So tracing must be switched on manually in your mission if you are using Moose statically.
-- When moose is loading dynamically (for moose class development), tracing is switched on by default.
@@ -865,7 +652,7 @@ end
-- @return #boolean
function BASE:IsTrace()
if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
return true
else
return false
@@ -921,10 +708,10 @@ end
-- @param Arguments A #table or any field.
function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo( 3, "l" )
local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo( 2, "nl" )
local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or debug.getinfo( 3, "l" )
local Function = "function"
if DebugInfoCurrent.name then
@@ -950,9 +737,9 @@ end
-- @param Arguments A #table or any field.
function BASE:F( Arguments )
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
if debug and _TraceOnOff then
local DebugInfoCurrent = debug.getinfo( 2, "nl" )
local DebugInfoFrom = debug.getinfo( 3, "l" )
if _TraceLevel >= 1 then
self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom )
@@ -966,9 +753,9 @@ end
-- @param Arguments A #table or any field.
function BASE:F2( Arguments )
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
if debug and _TraceOnOff then
local DebugInfoCurrent = debug.getinfo( 2, "nl" )
local DebugInfoFrom = debug.getinfo( 3, "l" )
if _TraceLevel >= 2 then
self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom )
@@ -981,9 +768,9 @@ end
-- @param Arguments A #table or any field.
function BASE:F3( Arguments )
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
if debug and _TraceOnOff then
local DebugInfoCurrent = debug.getinfo( 2, "nl" )
local DebugInfoFrom = debug.getinfo( 3, "l" )
if _TraceLevel >= 3 then
self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom )
@@ -996,10 +783,10 @@ end
-- @param Arguments A #table or any field.
function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo( 3, "l" )
local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo( 2, "nl" )
local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or debug.getinfo( 3, "l" )
local Function = "function"
if DebugInfoCurrent.name then
@@ -1025,9 +812,9 @@ end
-- @param Arguments A #table or any field.
function BASE:T( Arguments )
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
if debug and _TraceOnOff then
local DebugInfoCurrent = debug.getinfo( 2, "nl" )
local DebugInfoFrom = debug.getinfo( 3, "l" )
if _TraceLevel >= 1 then
self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom )
@@ -1041,9 +828,9 @@ end
-- @param Arguments A #table or any field.
function BASE:T2( Arguments )
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
if debug and _TraceOnOff then
local DebugInfoCurrent = debug.getinfo( 2, "nl" )
local DebugInfoFrom = debug.getinfo( 3, "l" )
if _TraceLevel >= 2 then
self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom )
@@ -1056,9 +843,9 @@ end
-- @param Arguments A #table or any field.
function BASE:T3( Arguments )
if BASE.Debug and _TraceOnOff then
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
if debug and _TraceOnOff then
local DebugInfoCurrent = debug.getinfo( 2, "nl" )
local DebugInfoFrom = debug.getinfo( 3, "l" )
if _TraceLevel >= 3 then
self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom )
@@ -1071,9 +858,9 @@ end
-- @param Arguments A #table or any field.
function BASE:E( Arguments )
if BASE.Debug then
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
if debug then
local DebugInfoCurrent = debug.getinfo( 2, "nl" )
local DebugInfoFrom = debug.getinfo( 3, "l" )
local Function = "function"
if DebugInfoCurrent.name then
@@ -1087,36 +874,6 @@ function BASE:E( Arguments )
end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
else
env.info( string.format( "%1s:%20s%05d(%s)" , "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
end
end
--- Log an information which will be traced always. Can be anywhere within the function logic.
-- @param #BASE self
-- @param Arguments A #table or any field.
function BASE:I( Arguments )
if BASE.Debug then
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
local Function = "function"
if DebugInfoCurrent.name then
Function = DebugInfoCurrent.name
end
local LineCurrent = DebugInfoCurrent.currentline
local LineFrom = -1
if DebugInfoFrom then
LineFrom = DebugInfoFrom.currentline
end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
else
env.info( string.format( "%1s:%20s%05d(%s)" , "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,9 @@
--- **Core** -- DATABASE manages the database of mission objects.
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module Core.Database
-- @image Core_Database.JPG
--- @type DATABASE
-- @extends Core.Base#BASE
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
-- ====
--
-- 1) @{#DATABASE} class, extends @{Base#BASE}
-- ===================================================
-- Mission designers can use the DATABASE class to refer to:
--
-- * STATICS
@@ -29,10 +17,35 @@
--
-- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor.
--
-- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission.
-- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required.
-- Moose will automatically create one instance of the DATABASE class into the **global** object _DATABASE.
-- Moose refers to _DATABASE within the framework extensively, but you can also refer to the _DATABASE object within your missions if required.
--
-- @field #DATABASE
-- 1.1) DATABASE iterators
-- -----------------------
-- You can iterate the database with the available iterator methods.
-- The iterator methods will walk the DATABASE set, and call for each element within the set a function that you provide.
-- The following iterator methods are currently available within the DATABASE:
--
-- * @{#DATABASE.ForEachUnit}: Calls a function for each @{UNIT} it finds within the DATABASE.
-- * @{#DATABASE.ForEachGroup}: Calls a function for each @{GROUP} it finds within the DATABASE.
-- * @{#DATABASE.ForEachPlayer}: Calls a function for each alive player it finds within the DATABASE.
-- * @{#DATABASE.ForEachPlayerJoined}: Calls a function for each joined player it finds within the DATABASE.
-- * @{#DATABASE.ForEachClient}: Calls a function for each @{CLIENT} it finds within the DATABASE.
-- * @{#DATABASE.ForEachClientAlive}: Calls a function for each alive @{CLIENT} it finds within the DATABASE.
--
-- ===
--
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ====
-- @module Database
--- DATABASE class
-- @type DATABASE
-- @extends Core.Base#BASE
DATABASE = {
ClassName = "DATABASE",
Templates = {
@@ -57,16 +70,12 @@ DATABASE = {
NavPoints = {},
PLAYERSETTINGS = {},
ZONENAMES = {},
HITS = {},
DESTROYS = {},
ZONES = {},
}
local _DATABASECoalition =
{
[1] = "Red",
[2] = "Blue",
[3] = "Neutral",
}
local _DATABASECategory =
@@ -95,14 +104,11 @@ function DATABASE:New()
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Hit, self.AccountHits )
self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo )
self:HandleEvent( EVENTS.NewZone )
self:HandleEvent( EVENTS.DeleteZone )
-- Follow alive players and clients
--self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event.
self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit )
self:_RegisterTemplates()
@@ -117,7 +123,7 @@ function DATABASE:New()
--- @param #DATABASE self
local function CheckPlayers( self )
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL )}
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) }
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
--self:E( { "CoalitionData:", CoalitionData } )
for UnitId, UnitData in pairs( CoalitionData ) do
@@ -142,8 +148,8 @@ function DATABASE:New()
end
end
--self:E( "Scheduling" )
--PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 )
self:E( "Scheduling" )
PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 )
return self
end
@@ -247,178 +253,35 @@ function DATABASE:FindAirbase( AirbaseName )
return AirbaseFound
end
--- Adds a Cargo based on the Cargo Name in the DATABASE.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:AddCargo( Cargo )
do -- Zones
--- Finds a @{Zone} based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @return Core.Zone#ZONE_BASE The found ZONE.
function DATABASE:FindZone( ZoneName )
local ZoneFound = self.ZONES[ZoneName]
return ZoneFound
end
--- Adds a @{Zone} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @param Core.Zone#ZONE_BASE Zone The zone.
function DATABASE:AddZone( ZoneName, Zone )
if not self.ZONES[ZoneName] then
self.ZONES[ZoneName] = Zone
end
end
--- Deletes a @{Zone} from the DATABASE based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
function DATABASE:DeleteZone( ZoneName )
self.ZONES[ZoneName] = nil
end
--- Finds an @{Zone} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param #string ZoneName
-- @return Core.Zone#ZONE_BASE The found @{Zone}.
function DATABASE:FindZone( ZoneName )
local ZoneFound = self.ZONES[ZoneName]
return ZoneFound
if not self.CARGOS[Cargo.Name] then
self.CARGOS[Cargo.Name] = Cargo
end
end
--- Private method that registers new ZONE_BASE derived objects within the DATABASE Object.
-- @param #DATABASE self
-- @return #DATABASE self
function DATABASE:_RegisterZones()
--- Deletes a Cargo from the DATABASE based on the Cargo Name.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:DeleteCargo( CargoName )
for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do
local ZoneName = ZoneData.name
self.CARGOS[CargoName] = nil
end
self:I( { "Register ZONE:", Name = ZoneName } )
local Zone = ZONE:New( ZoneName )
self.ZONENAMES[ZoneName] = ZoneName
self:AddZone( ZoneName, Zone )
end
for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do
if ZoneGroupName:match("~ZONE_POLYGON") then
local ZoneName1 = ZoneGroupName:match("(.*)~ZONE_POLYGON")
local ZoneName2 = ZoneGroupName:match(".*~ZONE_POLYGON(.*)")
local ZoneName = ZoneName1 .. ( ZoneName2 or "" )
self:I( { "Register ZONE_POLYGON:", Name = ZoneName } )
local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup )
self.ZONENAMES[ZoneName] = ZoneName
self:AddZone( ZoneName, Zone_Polygon )
end
end
end
--- Finds an CARGO based on the CargoName.
-- @param #DATABASE self
-- @param #string CargoName
-- @return Wrapper.Cargo#CARGO The found CARGO.
function DATABASE:FindCargo( CargoName )
local CargoFound = self.CARGOS[CargoName]
return CargoFound
end
end -- zone
do -- cargo
--- Adds a Cargo based on the Cargo Name in the DATABASE.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:AddCargo( Cargo )
if not self.CARGOS[Cargo.Name] then
self.CARGOS[Cargo.Name] = Cargo
end
end
--- Deletes a Cargo from the DATABASE based on the Cargo Name.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:DeleteCargo( CargoName )
self.CARGOS[CargoName] = nil
end
--- Finds an CARGO based on the CargoName.
-- @param #DATABASE self
-- @param #string CargoName
-- @return Wrapper.Cargo#CARGO The found CARGO.
function DATABASE:FindCargo( CargoName )
local CargoFound = self.CARGOS[CargoName]
return CargoFound
end
--- Checks if the Template name has a ~CARGO tag.
-- If yes, the group is a cargo.
-- @param #DATABASE self
-- @param #string TemplateName
-- @return #boolean
function DATABASE:IsCargo( TemplateName )
TemplateName = env.getValueDictByKey( TemplateName )
local Cargo = TemplateName:match( "~(CARGO)" )
return Cargo and Cargo == "CARGO"
end
--- Private method that registers new Static Templates within the DATABASE Object.
-- @param #DATABASE self
-- @return #DATABASE self
function DATABASE:_RegisterCargos()
for CargoGroupName, CargoGroup in pairs( self.GROUPS ) do
if self:IsCargo( CargoGroupName ) then
local CargoInfo = CargoGroupName:match("~CARGO(.*)")
local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)")
local CargoName1 = CargoGroupName:match("(.*)~CARGO%(.*%)")
local CargoName2 = CargoGroupName:match(".*~CARGO%(.*%)(.*)")
self:E({CargoName1 = CargoName1, CargoName2 = CargoName2 })
local CargoName = CargoName1 .. ( CargoName2 or "" )
local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?")
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName
local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") )
local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") )
self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
end
end
for CargoStaticName, CargoStatic in pairs( self.STATICS ) do
if self:IsCargo( CargoStaticName ) then
local CargoInfo = CargoStaticName:match("~CARGO(.*)")
local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)")
local CargoName = CargoStaticName:match("(.*)~CARGO")
local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?")
local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?")
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName
local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") )
local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") )
if Category == "SLING" then
self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
else
if Category == "CRATE" then
self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
end
end
end
end
end
end -- cargo
--- Finds a CLIENT based on the ClientName.
-- @param #DATABASE self
@@ -473,7 +336,7 @@ function DATABASE:AddPlayer( UnitName, PlayerName )
if PlayerName then
self:E( { "Add player for unit:", UnitName, PlayerName } )
self.PLAYERS[PlayerName] = UnitName
self.PLAYERUNITS[PlayerName] = self:FindUnit( UnitName )
self.PLAYERUNITS[UnitName] = PlayerName
self.PLAYERSJOINED[PlayerName] = PlayerName
end
end
@@ -485,48 +348,10 @@ function DATABASE:DeletePlayer( UnitName, PlayerName )
if PlayerName then
self:E( { "Clean player:", PlayerName } )
self.PLAYERS[PlayerName] = nil
self.PLAYERUNITS[PlayerName] = nil
self.PLAYERUNITS[UnitName] = PlayerName
end
end
--- Get the player table from the DATABASE.
-- The player table contains all unit names with the key the name of the player (PlayerName).
-- @param #DATABASE self
-- @usage
-- local Players = _DATABASE:GetPlayers()
-- for PlayerName, UnitName in pairs( Players ) do
-- ..
-- end
function DATABASE:GetPlayers()
return self.PLAYERS
end
--- Get the player table from the DATABASE, which contains all UNIT objects.
-- The player table contains all UNIT objects of the player with the key the name of the player (PlayerName).
-- @param #DATABASE self
-- @usage
-- local PlayerUnits = _DATABASE:GetPlayerUnits()
-- for PlayerName, PlayerUnit in pairs( PlayerUnits ) do
-- ..
-- end
function DATABASE:GetPlayerUnits()
return self.PLAYERUNITS
end
--- Get the player table from the DATABASE which have joined in the mission historically.
-- The player table contains all UNIT objects with the key the name of the player (PlayerName).
-- @param #DATABASE self
-- @usage
-- local PlayersJoined = _DATABASE:GetPlayersJoined()
-- for PlayerName, PlayerUnit in pairs( PlayersJoined ) do
-- ..
-- end
function DATABASE:GetPlayersJoined()
return self.PLAYERSJOINED
end
--- Instantiate new Groups within the DCSRTE.
-- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined:
@@ -590,14 +415,13 @@ end
--- Private method that registers new Group Templates within the DATABASE Object.
-- @param #DATABASE self
-- @param #table GroupTemplate
-- @param DCS#coalition.side CoalitionSide The coalition.side of the object.
-- @param DCS#Object.Category CategoryID The Object.category of the object.
-- @param DCS#country.id CountryID the country.id of the object
-- @return #DATABASE self
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName )
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionID, CategoryID, CountryID )
local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name )
local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name)
local TraceTable = {}
if not self.Templates.Groups[GroupTemplateName] then
self.Templates.Groups[GroupTemplateName] = {}
self.Templates.Groups[GroupTemplateName].Status = nil
@@ -609,7 +433,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
end
GroupTemplate.CategoryID = CategoryID
GroupTemplate.CoalitionID = CoalitionSide
GroupTemplate.CoalitionID = CoalitionID
GroupTemplate.CountryID = CountryID
self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName
@@ -618,10 +442,21 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self.Templates.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units
self.Templates.Groups[GroupTemplateName].Units = GroupTemplate.units
self.Templates.Groups[GroupTemplateName].CategoryID = CategoryID
self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionSide
self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionID
self.Templates.Groups[GroupTemplateName].CountryID = CountryID
local UnitNames = {}
TraceTable[#TraceTable+1] = "Group"
TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].GroupName
TraceTable[#TraceTable+1] = "Coalition"
TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CoalitionID
TraceTable[#TraceTable+1] = "Category"
TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CategoryID
TraceTable[#TraceTable+1] = "Country"
TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CountryID
TraceTable[#TraceTable+1] = "Units"
for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do
@@ -634,27 +469,21 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self.Templates.Units[UnitTemplate.name].GroupTemplate = GroupTemplate
self.Templates.Units[UnitTemplate.name].GroupId = GroupTemplate.groupId
self.Templates.Units[UnitTemplate.name].CategoryID = CategoryID
self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionSide
self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionID
self.Templates.Units[UnitTemplate.name].CountryID = CountryID
if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then
self.Templates.ClientsByName[UnitTemplate.name] = UnitTemplate
self.Templates.ClientsByName[UnitTemplate.name].CategoryID = CategoryID
self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionSide
self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionID
self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID
self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate
end
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplate.name].UnitName
end
self:I( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID,
Category = self.Templates.Groups[GroupTemplateName].CategoryID,
Country = self.Templates.Groups[GroupTemplateName].CountryID,
Units = UnitNames
}
)
self:E( TraceTable )
end
function DATABASE:GetGroupTemplate( GroupName )
@@ -671,6 +500,8 @@ end
-- @return #DATABASE self
function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID )
local TraceTable = {}
local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name)
self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {}
@@ -686,22 +517,28 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID
self.Templates.Statics[StaticTemplateName].CountryID = CountryID
self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName,
Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID,
Category = self.Templates.Statics[StaticTemplateName].CategoryID,
Country = self.Templates.Statics[StaticTemplateName].CountryID
}
)
self:AddStatic( StaticTemplateName )
TraceTable[#TraceTable+1] = "Static"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].GroupName
TraceTable[#TraceTable+1] = "Coalition"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CoalitionID
TraceTable[#TraceTable+1] = "Category"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CategoryID
TraceTable[#TraceTable+1] = "Country"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CountryID
self:E( TraceTable )
end
--- @param #DATABASE self
function DATABASE:GetStaticUnitTemplate( StaticName )
local StaticTemplate = self.Templates.Statics[StaticName].UnitTemplate
return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
StaticTemplate.SpawnCoalitionID = self.Templates.Statics[StaticName].CoalitionID
StaticTemplate.SpawnCategoryID = self.Templates.Statics[StaticName].CategoryID
StaticTemplate.SpawnCountryID = self.Templates.Statics[StaticName].CountryID
return StaticTemplate
end
@@ -742,7 +579,7 @@ end
-- @return #DATABASE self
function DATABASE:_RegisterPlayers()
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL ) }
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) }
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
for UnitId, UnitData in pairs( CoalitionData ) do
self:T3( { "UnitData:", UnitData } )
@@ -766,7 +603,7 @@ end
-- @return #DATABASE self
function DATABASE:_RegisterGroupsAndUnits()
local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) }
local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ) }
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
for DCSGroupId, DCSGroup in pairs( CoalitionData ) do
@@ -809,7 +646,6 @@ end
function DATABASE:_RegisterStatics()
local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) }
self:E( { Statics = CoalitionsData } )
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
for DCSStaticId, DCSStatic in pairs( CoalitionData ) do
@@ -836,7 +672,7 @@ function DATABASE:_RegisterAirbases()
local DCSAirbaseName = DCSAirbase:getName()
self:E( { "Register Airbase:", DCSAirbaseName, DCSAirbase:getID() } )
self:E( { "Register Airbase:", DCSAirbaseName } )
self:AddAirbase( DCSAirbaseName )
end
end
@@ -862,21 +698,7 @@ function DATABASE:_EventOnBirth( Event )
self:AddGroup( Event.IniDCSGroupName )
end
end
if Event.IniObjectCategory == 1 then
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
local PlayerName = Event.IniUnit:GetPlayerName()
self:E( { "PlayerName:", PlayerName } )
if PlayerName then
self:E( { "Player Joined:", PlayerName } )
if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniUnitName, PlayerName )
end
local Settings = SETTINGS:Set( PlayerName )
Settings:SetPlayerMenu( Event.IniUnit )
--MENU_INDEX:Refresh( Event.IniGroup )
end
end
--self:_EventOnPlayerEnterUnit( Event )
end
end
@@ -900,8 +722,6 @@ function DATABASE:_EventOnDeadOrCrash( Event )
end
end
end
self:AccountDestroys( Event )
end
@@ -911,14 +731,13 @@ end
function DATABASE:_EventOnPlayerEnterUnit( Event )
self:F2( { Event } )
if Event.IniDCSUnit then
if Event.IniUnit then
if Event.IniObjectCategory == 1 then
self:AddUnit( Event.IniDCSUnitName )
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
self:AddGroup( Event.IniDCSGroupName )
local PlayerName = Event.IniDCSUnit:getPlayerName()
local PlayerName = Event.IniUnit:GetPlayerName()
if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniDCSUnitName, PlayerName )
self:AddPlayer( Event.IniUnitName, PlayerName )
end
local Settings = SETTINGS:Set( PlayerName )
Settings:SetPlayerMenu( Event.IniUnit )
@@ -936,8 +755,7 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
if Event.IniUnit then
if Event.IniObjectCategory == 1 then
local PlayerName = Event.IniUnit:GetPlayerName()
if PlayerName and self.PLAYERS[PlayerName] then
self:E( { "Player Left:", PlayerName } )
if self.PLAYERS[PlayerName] then
local Settings = SETTINGS:Set( PlayerName )
Settings:RemovePlayerMenu( Event.IniUnit )
self:DeletePlayer( Event.IniUnit, PlayerName )
@@ -1121,37 +939,12 @@ function DATABASE:OnEventDeleteCargo( EventData )
end
--- Handles the OnEventNewZone event.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA EventData
function DATABASE:OnEventNewZone( EventData )
self:F2( { EventData } )
if EventData.Zone then
self:AddZone( EventData.Zone )
end
end
--- Handles the OnEventDeleteZone.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA EventData
function DATABASE:OnEventDeleteZone( EventData )
self:F2( { EventData } )
if EventData.Zone then
self:DeleteZone( EventData.Zone.ZoneName )
end
end
--- Gets the player settings
-- @param #DATABASE self
-- @param #string PlayerName
-- @return Core.Settings#SETTINGS
function DATABASE:GetPlayerSettings( PlayerName )
self:F2( { PlayerName } )
self:E({PlayerName})
return self.PLAYERSETTINGS[PlayerName]
end
@@ -1162,7 +955,7 @@ end
-- @param Core.Settings#SETTINGS Settings
-- @return Core.Settings#SETTINGS
function DATABASE:SetPlayerSettings( PlayerName, Settings )
self:F2( { PlayerName, Settings } )
self:E({PlayerName, Settings})
self.PLAYERSETTINGS[PlayerName] = Settings
end
@@ -1177,20 +970,13 @@ function DATABASE:_RegisterTemplates()
self.UNITS = {}
--Build routines.db.units and self.Navpoints
for CoalitionName, coa_data in pairs(env.mission.coalition) do
self:T({CoalitionName=CoalitionName})
if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type(coa_data) == 'table' then
if (CoalitionName == 'red' or CoalitionName == 'blue') and type(coa_data) == 'table' then
--self.Units[coa_name] = {}
local CoalitionSide = coalition.side[string.upper(CoalitionName)]
if CoalitionName=="red" then
CoalitionSide=coalition.side.NEUTRAL
elseif CoalitionName=="blue" then
CoalitionSide=coalition.side.BLUE
else
CoalitionSide=coalition.side.NEUTRAL
end
----------------------------------------------
-- build nav points DB
self.Navpoints[CoalitionName] = {}
if coa_data.nav_points then --navpoints
@@ -1205,9 +991,8 @@ function DATABASE:_RegisterTemplates()
self.Navpoints[CoalitionName][nav_ind]['point']['y'] = 0
self.Navpoints[CoalitionName][nav_ind]['point']['z'] = nav_data.y
end
end
end
end
-------------------------------------------------
if coa_data.country then --there is a country table
for cntry_id, cntry_data in pairs(coa_data.country) do
@@ -1260,104 +1045,14 @@ function DATABASE:_RegisterTemplates()
end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then
end --for coa_name, coa_data in pairs(mission.coalition) do
for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do
local ZoneName = ZoneData.name
self.ZONENAMES[ZoneName] = ZoneName
end
return self
end
--- Account the Hits of the Players.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA Event
function DATABASE:AccountHits( Event )
self:F( { Event } )
if Event.IniPlayerName ~= nil then -- It is a player that is hitting something
self:T( "Hitting Something" )
-- What is he hitting?
if Event.TgtCategory then
-- A target got hit
self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {}
local Hit = self.HITS[Event.TgtUnitName]
Hit.Players = Hit.Players or {}
Hit.Players[Event.IniPlayerName] = true
end
end
-- It is a weapon initiated by a player, that is hitting something
-- This seems to occur only with scenery and static objects.
if Event.WeaponPlayerName ~= nil then
self:T( "Hitting Scenery" )
-- What is he hitting?
if Event.TgtCategory then
if Event.IniCoalition then -- A coalition object was hit, probably a static.
-- A target got hit
self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {}
local Hit = self.HITS[Event.TgtUnitName]
Hit.Players = Hit.Players or {}
Hit.Players[Event.WeaponPlayerName] = true
else -- A scenery object was hit.
end
end
end
end
--- Account the destroys.
-- @param #DATABASE self
-- @param Core.Event#EVENTDATA Event
function DATABASE:AccountDestroys( Event )
self:F( { Event } )
local TargetUnit = nil
local TargetGroup = nil
local TargetUnitName = ""
local TargetGroupName = ""
local TargetPlayerName = ""
local TargetCoalition = nil
local TargetCategory = nil
local TargetType = nil
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if Event.IniDCSUnit then
TargetUnit = Event.IniUnit
TargetUnitName = Event.IniDCSUnitName
TargetGroup = Event.IniDCSGroup
TargetGroupName = Event.IniDCSGroupName
TargetPlayerName = Event.IniPlayerName
TargetCoalition = Event.IniCoalition
--TargetCategory = TargetUnit:getCategory()
--TargetCategory = TargetUnit:getDesc().category -- Workaround
TargetCategory = Event.IniCategory
TargetType = Event.IniTypeName
TargetUnitType = TargetType
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
end
self:T( "Something got destroyed" )
local Destroyed = false
-- What is the player destroying?
if self.HITS[Event.IniUnitName] then -- Was there a hit for this unit for this player before registered???
self.DESTROYS[Event.IniUnitName] = self.DESTROYS[Event.IniUnitName] or {}
self.DESTROYS[Event.IniUnitName] = true
end
end

View File

@@ -1,5 +1,7 @@
--- **Core** -- EVENT models DCS **event dispatching** using a **publish-subscribe** model.
--
-- ![Banner Image](..\Presentations\EVENT\Dia1.JPG)
--
-- ===
--
-- # 1) Event Handling Overview
@@ -59,8 +61,8 @@
-- So, when the DCS event occurs, the class will be notified of that event.
-- There are two functions which you use to subscribe to or unsubscribe from an event.
--
-- * @{Core.Base#BASE.HandleEvent}(): Subscribe to a DCS Event.
-- * @{Core.Base#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event.
-- * @{Base#BASE.HandleEvent}(): Subscribe to a DCS Event.
-- * @{Base#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event.
--
-- Note that for a UNIT, the event will be handled **for that UNIT only**!
-- Note that for a GROUP, the event will be handled **for all the UNITs in that GROUP only**!
@@ -72,7 +74,7 @@
-- ### 1.3.2 Event Handling of DCS Events
--
-- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called
-- when the DCS event occurs. The Event Handling method receives an @{Core.Event#EVENTDATA} structure, which contains a lot of information
-- when the DCS event occurs. The Event Handling method receives an @{Event#EVENTDATA} structure, which contains a lot of information
-- about the event that occurred.
--
-- Find below an example of the prototype how to write an event handling function for two units:
@@ -110,11 +112,11 @@
-- # 2) EVENTS type
--
-- The EVENTS structure contains names for all the different DCS events that objects can subscribe to using the
-- @{Core.Base#BASE.HandleEvent}() method.
-- @{Base#BASE.HandleEvent}() method.
--
-- # 3) EVENTDATA type
--
-- The @{Core.Event#EVENTDATA} structure contains all the fields that are populated with event information before
-- The @{Event#EVENTDATA} structure contains all the fields that are populated with event information before
-- an Event Handler method is being called by the event dispatcher.
-- The Event Handler received the EVENTDATA object as a parameter, and can be used to investigate further the different events.
-- There are basically 4 main categories of information stored in the EVENTDATA structure:
@@ -157,33 +159,28 @@
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ===
-- ====
--
-- @module Core.Event
-- @image Core_Event.JPG
-- @module Event
--- The EVENT structure
--
-- @type EVENT
-- @field #EVENT.Events Events
-- @extends Core.Base#BASE
EVENT = {
ClassName = "EVENT",
ClassID = 0,
MissionEnd = false,
}
world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
world.event.S_EVENT_DELETE_CARGO = world.event.S_EVENT_MAX + 1001
world.event.S_EVENT_NEW_ZONE = world.event.S_EVENT_MAX + 1002
world.event.S_EVENT_DELETE_ZONE = world.event.S_EVENT_MAX + 1003
--- The different types of events supported by MOOSE.
-- Use this structure to subscribe to events using the @{Core.Base#BASE.HandleEvent}() method.
-- Use this structure to subscribe to events using the @{Base#BASE.HandleEvent}() method.
-- @type EVENTS
EVENTS = {
Shot = world.event.S_EVENT_SHOT,
@@ -209,13 +206,8 @@ EVENTS = {
PlayerComment = world.event.S_EVENT_PLAYER_COMMENT,
ShootingStart = world.event.S_EVENT_SHOOTING_START,
ShootingEnd = world.event.S_EVENT_SHOOTING_END,
MarkAdded = world.event.S_EVENT_MARK_ADDED,
MarkChange = world.event.S_EVENT_MARK_CHANGE,
MarkRemoved = world.event.S_EVENT_MARK_REMOVED,
NewCargo = world.event.S_EVENT_NEW_CARGO,
DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
NewZone = world.event.S_EVENT_NEW_ZONE,
DeleteZone = world.event.S_EVENT_DELETE_ZONE,
}
--- The Event structure
@@ -227,34 +219,34 @@ EVENTS = {
-- @type EVENTDATA
-- @field #number id The identifier of the event.
--
-- @field DCS#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
-- @field DCS#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ).
-- @field DCS#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
-- @field Dcs.DCSUnit#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{Dcs.DCSUnit#Unit} or @{Dcs.DCSStaticObject#StaticObject}.
-- @field Dcs.DCSObject#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ).
-- @field Dcs.DCSUnit#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCSUnit#Unit} or @{DCSStaticObject#StaticObject}.
-- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name.
-- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Wrapper.Unit#UNIT} of the initiator Unit object.
-- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Unit#UNIT} of the initiator Unit object.
-- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName).
-- @field DCS#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}.
-- @field Dcs.DCSGroup#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}.
-- @field #string IniDCSGroupName (UNIT) The initiating Group name.
-- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object.
-- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Group#GROUP} of the initiator Group object.
-- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName).
-- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot.
-- @field DCS#coalition.side IniCoalition (UNIT) The coalition of the initiator.
-- @field DCS#Unit.Category IniCategory (UNIT) The category of the initiator.
-- @field Dcs.DCScoalition#coalition.side IniCoalition (UNIT) The coalition of the initiator.
-- @field Dcs.DCSUnit#Unit.Category IniCategory (UNIT) The category of the initiator.
-- @field #string IniTypeName (UNIT) The type name of the initiator.
--
-- @field DCS#Unit target (UNIT/STATIC) The target @{DCS#Unit} or @{DCS#StaticObject}.
-- @field DCS#Object.Category TgtObjectCategory (UNIT/STATIC) The target object category ( Object.Category.UNIT or Object.Category.STATIC ).
-- @field DCS#Unit TgtDCSUnit (UNIT/STATIC) The target @{DCS#Unit} or @{DCS#StaticObject}.
-- @field Dcs.DCSUnit#Unit target (UNIT/STATIC) The target @{Dcs.DCSUnit#Unit} or @{DCSStaticObject#StaticObject}.
-- @field Dcs.DCSObject#Object.Category TgtObjectCategory (UNIT/STATIC) The target object category ( Object.Category.UNIT or Object.Category.STATIC ).
-- @field Dcs.DCSUnit#Unit TgtDCSUnit (UNIT/STATIC) The target @{DCSUnit#Unit} or @{DCSStaticObject#StaticObject}.
-- @field #string TgtDCSUnitName (UNIT/STATIC) The target Unit name.
-- @field Wrapper.Unit#UNIT TgtUnit (UNIT/STATIC) The target MOOSE wrapper @{Wrapper.Unit#UNIT} of the target Unit object.
-- @field Wrapper.Unit#UNIT TgtUnit (UNIT/STATIC) The target MOOSE wrapper @{Unit#UNIT} of the target Unit object.
-- @field #string TgtUnitName (UNIT/STATIC) The target UNIT name (same as TgtDCSUnitName).
-- @field DCS#Group TgtDCSGroup (UNIT) The target {DCSGroup#Group}.
-- @field Dcs.DCSGroup#Group TgtDCSGroup (UNIT) The target {DCSGroup#Group}.
-- @field #string TgtDCSGroupName (UNIT) The target Group name.
-- @field Wrapper.Group#GROUP TgtGroup (UNIT) The target MOOSE wrapper @{Wrapper.Group#GROUP} of the target Group object.
-- @field Wrapper.Group#GROUP TgtGroup (UNIT) The target MOOSE wrapper @{Group#GROUP} of the target Group object.
-- @field #string TgtGroupName (UNIT) The target GROUP name (same as TgtDCSGroupName).
-- @field #string TgtPlayerName (UNIT) The name of the target player in case the Unit is a client or player slot.
-- @field DCS#coalition.side TgtCoalition (UNIT) The coalition of the target.
-- @field DCS#Unit.Category TgtCategory (UNIT) The category of the target.
-- @field Dcs.DCScoalition#coalition.side TgtCoalition (UNIT) The coalition of the target.
-- @field Dcs.DCSUnit#Unit.Category TgtCategory (UNIT) The category of the target.
-- @field #string TgtTypeName (UNIT) The type name of the target.
--
-- @field weapon The weapon used during the event.
@@ -403,24 +395,6 @@ local _EVENTMETA = {
Event = "OnEventShootingEnd",
Text = "S_EVENT_SHOOTING_END"
},
[world.event.S_EVENT_MARK_ADDED] = {
Order = 1,
Side = "I",
Event = "OnEventMarkAdded",
Text = "S_EVENT_MARK_ADDED"
},
[world.event.S_EVENT_MARK_CHANGE] = {
Order = 1,
Side = "I",
Event = "OnEventMarkChange",
Text = "S_EVENT_MARK_CHANGE"
},
[world.event.S_EVENT_MARK_REMOVED] = {
Order = 1,
Side = "I",
Event = "OnEventMarkRemoved",
Text = "S_EVENT_MARK_REMOVED"
},
[EVENTS.NewCargo] = {
Order = 1,
Event = "OnEventNewCargo",
@@ -431,16 +405,6 @@ local _EVENTMETA = {
Event = "OnEventDeleteCargo",
Text = "S_EVENT_DELETE_CARGO"
},
[EVENTS.NewZone] = {
Order = 1,
Event = "OnEventNewZone",
Text = "S_EVENT_NEW_ZONE"
},
[EVENTS.DeleteZone] = {
Order = 1,
Event = "OnEventDeleteZone",
Text = "S_EVENT_DELETE_ZONE"
},
}
@@ -458,7 +422,7 @@ end
--- Initializes the Events structure for the event
-- @param #EVENT self
-- @param DCS#world.event EventID
-- @param Dcs.DCSWorld#world.event EventID
-- @param Core.Base#BASE EventClass
-- @return #EVENT.Events
function EVENT:Init( EventID, EventClass )
@@ -484,7 +448,7 @@ end
--- Removes a subscription
-- @param #EVENT self
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param DCS#world.event EventID
-- @param Dcs.DCSWorld#world.event EventID
-- @return #EVENT.Events
function EVENT:RemoveEvent( EventClass, EventID )
@@ -504,11 +468,11 @@ end
--- Resets subscriptions
-- @param #EVENT self
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param DCS#world.event EventID
-- @param Dcs.DCSWorld#world.event EventID
-- @return #EVENT.Events
function EVENT:Reset( EventObject ) --R2.1
self:F( { "Resetting subscriptions for class: ", EventObject:GetClassNameAndID() } )
self:E( { "Resetting subscriptions for class: ", EventObject:GetClassNameAndID() } )
local EventPriority = EventObject:GetEventPriority()
for EventID, EventData in pairs( self.Events ) do
@@ -527,7 +491,7 @@ end
--- Clears all event subscriptions for a @{Core.Base#BASE} derived object.
--- Clears all event subscriptions for a @{Base#BASE} derived object.
-- @param #EVENT self
-- @param Core.Base#BASE EventObject
function EVENT:RemoveAll( EventObject )
@@ -597,12 +561,12 @@ end
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
-- @param EventID
-- @return #EVENT
function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID, ... )
function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID )
self:F2( GroupName )
local Event = self:Init( EventID, EventClass )
Event.EventGroup = true
Event.EventFunction = EventFunction
Event.Params = arg
return self
end
@@ -745,36 +709,6 @@ do -- Event Creation
world.onEvent( Event )
end
--- Creation of a New Zone Event.
-- @param #EVENT self
-- @param Core.Zone#ZONE_BASE Zone The Zone created.
function EVENT:CreateEventNewZone( Zone )
self:F( { Zone } )
local Event = {
id = EVENTS.NewZone,
time = timer.getTime(),
zone = Zone,
}
world.onEvent( Event )
end
--- Creation of a Zone Deletion Event.
-- @param #EVENT self
-- @param Core.Zone#ZONE_BASE Zone The Zone created.
function EVENT:CreateEventDeleteZone( Zone )
self:F( { Zone } )
local Event = {
id = EVENTS.DeleteZone,
time = timer.getTime(),
zone = Zone,
}
world.onEvent( Event )
end
--- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event.
-- @param #EVENT self
-- @param Wrapper.Unit#UNIT PlayerUnit.
@@ -799,7 +733,7 @@ function EVENT:onEvent( Event )
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
if BASE.Debug ~= nil then
if debug ~= nil then
env.info( debug.traceback() )
end
@@ -809,20 +743,13 @@ function EVENT:onEvent( Event )
local EventMeta = _EVENTMETA[Event.id]
--self:E( { EventMeta.Text, Event } ) -- Activate the see all incoming events ...
if self and
self.Events and
self.Events[Event.id] and
self.MissionEnd == false and
( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then
if Event.id and Event.id == EVENTS.MissionEnd then
self.MissionEnd = true
end
if Event.initiator then
Event.IniObjectCategory = Event.initiator:getCategory()
if Event.IniObjectCategory == Object.Category.UNIT then
@@ -859,16 +786,6 @@ function EVENT:onEvent( Event )
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
if Event.IniObjectCategory == Object.Category.CARGO then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
if Event.IniObjectCategory == Object.Category.SCENERY then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
@@ -907,7 +824,7 @@ function EVENT:onEvent( Event )
Event.TgtDCSUnit = Event.target
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName )
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
@@ -933,28 +850,11 @@ function EVENT:onEvent( Event )
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()
--Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
end
-- @FC: something like this should be added.
--[[
if Event.idx then
Event.MarkID=Event.idx
Event.MarkVec3=Event.pos
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
Event.MarkText=Event.text
Event.MarkCoalition=Event.coalition
Event.MarkGroupID = Event.groupID
end
]]
if Event.cargo then
Event.Cargo = Event.cargo
Event.CargoName = Event.cargo.Name
end
if Event.zone then
Event.Zone = Event.zone
Event.ZoneName = Event.zone.ZoneName
end
local PriorityOrder = EventMeta.Order
local PriorityBegin = PriorityOrder == -1 and 5 or 1
@@ -970,10 +870,10 @@ function EVENT:onEvent( Event )
-- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called.
for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do
--if Event.IniObjectCategory ~= Object.Category.STATIC then
-- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
--end
if Event.IniObjectCategory ~= Object.Category.STATIC then
--self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
end
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
@@ -983,7 +883,6 @@ function EVENT:onEvent( Event )
-- So now the EventClass must be a UNIT class!!! We check if it is still "Alive".
if EventClass:IsAlive() or
Event.id == EVENTS.PlayerEnterUnit or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead then
@@ -996,7 +895,7 @@ function EVENT:onEvent( Event )
if EventData.EventFunction then
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
@@ -1012,7 +911,7 @@ function EVENT:onEvent( Event )
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
self:E( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
@@ -1033,7 +932,6 @@ function EVENT:onEvent( Event )
-- So now the EventClass must be a GROUP class!!! We check if it is still "Alive".
if EventClass:IsAlive() or
Event.id == EVENTS.PlayerEnterUnit or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead then
@@ -1047,12 +945,12 @@ function EVENT:onEvent( Event )
if EventData.EventFunction then
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
return EventData.EventFunction( EventClass, Event )
end, ErrorHandler )
else
@@ -1063,19 +961,19 @@ function EVENT:onEvent( Event )
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling " .. EventMeta.Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
self:E( { "Calling " .. EventMeta.Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
else
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
--self:RemoveEvent( EventClass, Event.id )
self:RemoveEvent( EventClass, Event.id )
end
else
@@ -1119,18 +1017,8 @@ function EVENT:onEvent( Event )
end
end
end
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
-- And this is a problem because it will remove all entries from the SET_CARGOs.
-- To prevent this from happening, the Cargo object has a flag NoDestroy.
-- When true, the SET_CARGO won't Remove the Cargo object from the set.
-- But we need to switch that flag off after the event handlers have been called.
if Event.id == EVENTS.DeleteCargo then
Event.Cargo.NoDestroy = nil
end
else
self:T( { EventMeta.Text, Event } )
self:E( { EventMeta.Text, Event } )
end
Event = nil

View File

@@ -1,6 +1,8 @@
--- **Core** -- The **FSM** (**F**inite **S**tate **M**achine) class and derived **FSM\_** classes
-- are design patterns allowing efficient (long-lasting) processes and workflows.
--
-- ![Banner Image](..\Presentations\FSM\Dia1.JPG)
--
-- ===
--
-- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**.
@@ -50,20 +52,19 @@
--
-- * @{#FSM_TASK}: Models Finite State Machines for @{Task}s.
-- * @{#FSM_PROCESS}: Models Finite State Machines for @{Task} actions, which control @{Client}s.
-- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s.
-- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Controllable}s, which are @{Group}s, @{Unit}s, @{Client}s.
-- * @{#FSM_SET}: Models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here
-- for multiple objects or the position of the state machine in the process.
--
-- ===
-- ====
--
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ===
-- ====
--
-- @module Core.Fsm
-- @image Core_Finite_State_Machine.JPG
-- @module Fsm
do -- FSM
@@ -71,7 +72,9 @@ do -- FSM
-- @extends Core.Base#BASE
--- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**.
--- # FSM class, extends @{Base#BASE}
--
-- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**.
--
-- A FSM can only be in one of a finite number of states.
-- The machine is in only one state at a time; the state it is in at any given time is called the **current state**.
@@ -334,7 +337,7 @@ do -- FSM
--- Creates a new FSM object.
-- @param #FSM self
-- @return #FSM
function FSM:New()
function FSM:New( FsmT )
-- Inherits from BASE
self = BASE:Inherit( self, BASE:New() )
@@ -407,7 +410,7 @@ do -- FSM
return self._Transitions or {}
end
--- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Wrapper.Controllable} by the task.
--- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Controllable} by the task.
-- @param #FSM self
-- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states.
-- @param #string Event The Event name.
@@ -438,8 +441,6 @@ do -- FSM
-- @return #table
function FSM:GetProcesses()
self:F( { Processes = self._Processes } )
return self._Processes or {}
end
@@ -454,18 +455,6 @@ do -- FSM
error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" )
end
function FSM:SetProcess( From, Event, Fsm )
for ProcessID, Process in pairs( self:GetProcesses() ) do
if Process.From == From and Process.Event == Event then
Process.fsm = Fsm
return true
end
end
error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" )
end
--- Adds an End state.
function FSM:AddEndState( State )
@@ -568,130 +557,95 @@ do -- FSM
end
function FSM:_call_handler( step, trigger, params, EventName )
function FSM:_call_handler( handler, params, EventName )
local handler = step .. trigger
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
if self[handler] then
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] )
self:T2( "Calling " .. handler )
self._EventSchedules[EventName] = nil
local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler )
return Value
end
end
--- @param #FSM self
function FSM._handler( self, EventName, ... )
local Can, To = self:can( EventName )
local Can, to = self:can( EventName )
if To == "*" then
To = self.current
if to == "*" then
to = self.current
end
if Can then
local From = self.current
local Params = { From, EventName, To, ... }
local from = self.current
local params = { from, EventName, to, ... }
if self["onleave".. From] or
self["OnLeave".. From] or
self["onbefore".. EventName] or
self["OnBefore".. EventName] or
self["onafter".. EventName] or
self["OnAfter".. EventName] or
self["onenter".. To] or
self["OnEnter".. To]
then
if self:_call_handler( "onbefore", EventName, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onbefore" .. EventName )
return false
else
if self:_call_handler( "OnBefore", EventName, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnBefore" .. EventName )
return false
else
if self:_call_handler( "onleave", From, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onleave" .. From )
return false
else
if self:_call_handler( "OnLeave", From, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnLeave" .. From )
return false
end
end
end
end
if self.Controllable then
self:T( "FSM Transition for " .. self.Controllable.ControllableName .. " :" .. self.current .. " --> " .. EventName .. " --> " .. to )
else
local ClassName = self:GetClassName()
if ClassName == "FSM" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To )
end
self:T( "FSM Transition:" .. self.current .. " --> " .. EventName .. " --> " .. to )
end
if ClassName == "FSM_TASK" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.TaskName )
end
if ClassName == "FSM_CONTROLLABLE" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " )
end
if ClassName == "FSM_PROCESS" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " )
end
if ( self:_call_handler("onbefore" .. EventName, params, EventName ) == false )
or ( self:_call_handler("OnBefore" .. EventName, params, EventName ) == false )
or ( self:_call_handler("onleave" .. from, params, EventName ) == false )
or ( self:_call_handler("OnLeave" .. from, params, EventName ) == false ) then
self:T( "Cancel Transition" )
return false
end
self.current = To
self.current = to
local execute = true
local subtable = self:_gosub( From, EventName )
local subtable = self:_gosub( from, EventName )
for _, sub in pairs( subtable ) do
--if sub.nextevent then
-- self:F2( "nextevent = " .. sub.nextevent )
-- self[sub.nextevent]( self )
--end
self:T( "*** FSM *** Sub *** " .. sub.StartEvent )
self:T( "calling sub start event: " .. sub.StartEvent )
sub.fsm.fsmparent = self
sub.fsm.ReturnEvents = sub.ReturnEvents
sub.fsm[sub.StartEvent]( sub.fsm )
execute = false
end
local fsmparent, Event = self:_isendstate( To )
local fsmparent, Event = self:_isendstate( to )
if fsmparent and Event then
self:T( "*** FSM *** End *** " .. Event )
self:_call_handler("onenter", To, Params, EventName )
self:_call_handler("OnEnter", To, Params, EventName )
self:_call_handler("onafter", EventName, Params, EventName )
self:_call_handler("OnAfter", EventName, Params, EventName )
self:_call_handler("onstate", "change", Params, EventName )
self:F2( { "end state: ", fsmparent, Event } )
self:_call_handler("onenter" .. to, params, EventName )
self:_call_handler("OnEnter" .. to, params, EventName )
self:_call_handler("onafter" .. EventName, params, EventName )
self:_call_handler("OnAfter" .. EventName, params, EventName )
self:_call_handler("onstatechange", params, EventName )
fsmparent[Event]( fsmparent )
execute = false
end
if execute then
self:_call_handler("onafter", EventName, Params, EventName )
self:_call_handler("OnAfter", EventName, Params, EventName )
-- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute!
--if from ~= to then
self:_call_handler("onenter", To, Params, EventName )
self:_call_handler("OnEnter", To, Params, EventName )
self:_call_handler("onenter" .. to, params, EventName )
self:_call_handler("OnEnter" .. to, params, EventName )
--end
self:_call_handler("onstate", "change", Params, EventName )
self:_call_handler("onafter" .. EventName, params, EventName )
self:_call_handler("OnAfter" .. EventName, params, EventName )
self:_call_handler("onstatechange", params, EventName )
end
else
self:T( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " )
self:T( "Cannot execute transition." )
self:T( { From = self.current, Event = EventName, To = to, Can = Can } )
end
return nil
@@ -737,16 +691,17 @@ do -- FSM
function FSM:_isendstate( Current )
local FSMParent = self.fsmparent
if FSMParent and self.endstates[Current] then
--self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } )
self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } )
FSMParent.current = Current
local ParentFrom = FSMParent.current
--self:T( { ParentFrom, self.ReturnEvents } )
self:T( ParentFrom )
self:T( self.ReturnEvents )
local Event = self.ReturnEvents[Current]
--self:T( { Event } )
self:T( { ParentFrom, Event, self.ReturnEvents } )
if Event then
return FSMParent, Event
else
--self:T( { "Could not find parent event name for state ", ParentFrom } )
self:T( { "Could not find parent event name for state ", ParentFrom } )
end
end
@@ -769,10 +724,6 @@ do -- FSM
return self.current
end
function FSM:GetCurrentState()
return self.current
end
function FSM:Is( State )
return self.current == State
@@ -801,7 +752,9 @@ do -- FSM_CONTROLLABLE
-- @field Wrapper.Controllable#CONTROLLABLE Controllable
-- @extends Core.Fsm#FSM
--- Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s.
--- # FSM_CONTROLLABLE, extends @{#FSM}
--
-- FSM_CONTROLLABLE class models Finite State Machines for @{Controllable}s, which are @{Group}s, @{Unit}s, @{Client}s.
--
-- ===
--
@@ -816,10 +769,10 @@ do -- FSM_CONTROLLABLE
-- @param #table FSMT Finite State Machine Table
-- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs.
-- @return #FSM_CONTROLLABLE
function FSM_CONTROLLABLE:New( Controllable )
function FSM_CONTROLLABLE:New( FSMT, Controllable )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE
local self = BASE:Inherit( self, FSM:New( FSMT ) ) -- Core.Fsm#FSM_CONTROLLABLE
if Controllable then
self:SetControllable( Controllable )
@@ -902,22 +855,20 @@ do -- FSM_CONTROLLABLE
return self.Controllable
end
function FSM_CONTROLLABLE:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
function FSM_CONTROLLABLE:_call_handler( handler, params, EventName )
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
if self[handler] then
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** TaskUnit: " .. self.Controllable:GetName() )
self:F3( "Calling " .. handler )
self._EventSchedules[EventName] = nil
local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler )
return Value
@@ -934,7 +885,9 @@ do -- FSM_PROCESS
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- FSM_PROCESS class models Finite State Machines for @{Task} actions, which control @{Client}s.
--- # FSM_PROCESS, extends @{#FSM}
--
-- FSM_PROCESS class models Finite State Machines for @{Task} actions, which control @{Client}s.
--
-- ===
--
@@ -952,9 +905,9 @@ do -- FSM_PROCESS
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS
--self:F( Controllable )
self:Assign( Controllable, Task )
return self
end
@@ -962,28 +915,22 @@ do -- FSM_PROCESS
self:T( "No Initialisation" )
end
function FSM_PROCESS:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
function FSM_PROCESS:_call_handler( handler, params, EventName )
local ErrorHandler = function( errmsg )
env.info( "Error in FSM_PROCESS call handler:" .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
if self[handler] then
if handler ~= "onstatechange" then
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable:GetName() )
end
self:F3( "Calling " .. handler )
self._EventSchedules[EventName] = nil
if self.Controllable and self.Controllable:IsAlive() == true then
local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler )
end
local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler )
return Value
--return self[handler]( self, self.Controllable, unpack( params ) )
end
@@ -999,7 +946,7 @@ do -- FSM_PROCESS
local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS
NewFsm:Assign( Controllable, Task )
-- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS
NewFsm:Init( self )
@@ -1090,26 +1037,26 @@ do -- FSM_PROCESS
-- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP.
--- Send a message of the @{Task} to the Group of the Unit.
-- @param #FSM_PROCESS self
function FSM_PROCESS:Message( Message )
self:F( { Message = Message } )
-- @param #FSM_PROCESS self
function FSM_PROCESS:Message( Message )
self:F( { Message = Message } )
local CC = self:GetCommandCenter()
local TaskGroup = self.Controllable:GetGroup()
local CC = self:GetCommandCenter()
local TaskGroup = self.Controllable:GetGroup()
local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit
PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets.
local Callsign = self.Controllable:GetCallsign()
local Prefix = Callsign and " @ " .. Callsign .. PlayerName or ""
Message = Prefix .. ": " .. Message
CC:MessageToGroup( Message, TaskGroup )
end
local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit
PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets.
local Callsign = self.Controllable:GetCallsign()
local Prefix = Callsign and " @ " .. Callsign .. PlayerName or ""
Message = Prefix .. ": " .. Message
CC:MessageToGroup( Message, TaskGroup )
end
--- Assign the process to a @{Wrapper.Unit} and activate the process.
--- Assign the process to a @{Unit} and activate the process.
-- @param #FSM_PROCESS self
-- @param Task.Tasking#TASK Task
-- @param Wrapper.Unit#UNIT ProcessUnit
@@ -1125,16 +1072,14 @@ do -- FSM_PROCESS
return self
end
-- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To )
--
-- if From( "Planned" ) then
-- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
-- self.Task:Assign()
-- end
-- end
function FSM_PROCESS:onenterAssigned( ProcessUnit )
self:T( "Assign" )
function FSM_PROCESS:onenterFailed( ProcessUnit, Task, From, Event, To )
self:T( "*** FSM *** Failed *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
self.Task:Assign()
end
function FSM_PROCESS:onenterFailed( ProcessUnit )
self:T( "Failed" )
self.Task:Fail()
end
@@ -1146,17 +1091,14 @@ do -- FSM_PROCESS
-- @param #string Event
-- @param #string From
-- @param #string To
function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To )
function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To, Dummy )
self:T( { ProcessUnit:GetName(), From, Event, To, Dummy, self:IsTrace() } )
if From ~= To then
self:T( "*** FSM *** Change *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
if self:IsTrace() then
--MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll()
end
-- if self:IsTrace() then
-- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll()
-- self:F2( { Scores = self._Scores, To = To } )
-- end
self:T( { Scores = self._Scores, To = To } )
-- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects...
if self._Scores[To] then
@@ -1177,7 +1119,9 @@ do -- FSM_TASK
-- @field Tasking.Task#TASK Task
-- @extends #FSM
--- Models Finite State Machines for @{Tasking.Task}s.
--- # FSM_TASK, extends @{#FSM}
--
-- FSM_TASK class models Finite State Machines for @{Task}s.
--
-- ===
--
@@ -1189,23 +1133,22 @@ do -- FSM_TASK
--- Creates a new FSM_TASK object.
-- @param #FSM_TASK self
-- @param #string TaskName The name of the task.
-- @param #table FSMT
-- @param Tasking.Task#TASK Task
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #FSM_TASK
function FSM_TASK:New( TaskName )
function FSM_TASK:New( FSMT )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_TASK
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( FSMT ) ) -- Core.Fsm#FSM_TASK
self["onstatechange"] = self.OnStateChange
self.TaskName = TaskName
return self
end
function FSM_TASK:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
function FSM_TASK:_call_handler( handler, params, EventName )
if self[handler] then
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.TaskName )
self:T( "Calling " .. handler )
self._EventSchedules[EventName] = nil
return self[handler]( self, unpack( params ) )
end
@@ -1221,7 +1164,9 @@ do -- FSM_SET
-- @extends Core.Fsm#FSM
--- FSM_SET class models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here
--- # FSM_SET, extends @{#FSM}
--
-- FSM_SET class models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here
-- for multiple objects or the position of the state machine in the process.
--
-- ===
@@ -1265,10 +1210,9 @@ do -- FSM_SET
return self.Controllable
end
function FSM_SET:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
function FSM_SET:_call_handler( handler, params, EventName )
if self[handler] then
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] )
self:T( "Calling " .. handler )
self._EventSchedules[EventName] = nil
return self[handler]( self, self.Set, unpack( params ) )
end

View File

@@ -1,145 +0,0 @@
--- **Core (WIP)** -- Base class to allow the modeling of processes to achieve Goals.
--
-- ===
--
-- GOAL models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module Core.Goal
-- @image Core_Goal.JPG
do -- Goal
--- @type GOAL
-- @extends Core.Fsm#FSM
--- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. GOAL constructor
--
-- * @{#GOAL.New}(): Creates a new GOAL object.
--
-- ## 2. GOAL is a finite state machine (FSM).
--
-- ### 2.1 GOAL States
--
-- * **Pending**: The goal object is in progress.
-- * **Achieved**: The goal objective is Achieved.
--
-- ### 2.2 GOAL Events
--
-- * **Achieved**: Set the goal objective to Achieved.
--
-- @field #GOAL
GOAL = {
ClassName = "GOAL",
}
--- @field #table GOAL.Players
GOAL.Players = {}
--- @field #number GOAL.TotalContributions
GOAL.TotalContributions = 0
--- GOAL Constructor.
-- @param #GOAL self
-- @return #GOAL
function GOAL:New()
local self = BASE:Inherit( self, FSM:New() ) -- #GOAL
self:F( {} )
--- Achieved State for GOAL
-- @field GOAL.Achieved
--- Achieved State Handler OnLeave for GOAL
-- @function [parent=#GOAL] OnLeaveAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Achieved State Handler OnEnter for GOAL
-- @function [parent=#GOAL] OnEnterAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
self:SetStartState( "Pending" )
self:AddTransition( "*", "Achieved", "Achieved" )
--- Achieved Handler OnBefore for GOAL
-- @function [parent=#GOAL] OnBeforeAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Achieved Handler OnAfter for GOAL
-- @function [parent=#GOAL] OnAfterAchieved
-- @param #GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Achieved Trigger for GOAL
-- @function [parent=#GOAL] Achieved
-- @param #GOAL self
--- Achieved Asynchronous Trigger for GOAL
-- @function [parent=#GOAL] __Achieved
-- @param #GOAL self
-- @param #number Delay
self:SetEventPriority( 5 )
return self
end
--- @param #GOAL self
-- @param #string PlayerName
function GOAL:AddPlayerContribution( PlayerName )
self.Players[PlayerName] = self.Players[PlayerName] or 0
self.Players[PlayerName] = self.Players[PlayerName] + 1
self.TotalContributions = self.TotalContributions + 1
end
--- @param #GOAL self
-- @param #number Player contribution.
function GOAL:GetPlayerContribution( PlayerName )
return self.Players[PlayerName] or 0
end
--- @param #GOAL self
function GOAL:GetPlayerContributions()
return self.Players or {}
end
--- @param #GOAL self
function GOAL:GetTotalContributions()
return self.TotalContributions or 0
end
--- @param #GOAL self
-- @return #boolean true if the goal is Achieved
function GOAL:IsAchieved()
return self:Is( "Achieved" )
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,50 @@
--- **Core** -- MESSAGE class takes are of the **real-time notifications** and **messages to players** during a simulation.
--
-- ![Banner Image](..\Presentations\MESSAGE\Dia1.JPG)
--
-- ===
--
-- @module Core.Message
-- @image Core_Message.JPG
-- @module Message
--- The MESSAGE class
-- @type MESSAGE
-- @extends Core.Base#BASE
--- Message System to display Messages to Clients, Coalitions or All.
--- # MESSAGE class, extends @{Base#BASE}
--
-- Message System to display Messages to Clients, Coalitions or All.
-- Messages are shown on the display panel for an amount of seconds, and will then disappear.
-- Messages can contain a category which is indicating the category of the message.
--
-- ## MESSAGE construction
--
-- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet.
-- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet.
-- To send messages, you need to use the To functions.
--
-- ## Send messages to an audience
--
-- Messages are sent:
--
-- * To a @{Client} using @{#MESSAGE.ToClient}().
-- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}()
-- * To a coalition using @{#MESSAGE.ToCoalition}().
-- * To the red coalition using @{#MESSAGE.ToRed}().
-- * To the blue coalition using @{#MESSAGE.ToBlue}().
-- * To all Players using @{#MESSAGE.ToAll}().
-- * To a @{Client} using @{Message#MESSAGE.ToClient}().
-- * To a @{Group} using @{Message#MESSAGE.ToGroup}()
-- * To a coalition using @{Message#MESSAGE.ToCoalition}().
-- * To the red coalition using @{Message#MESSAGE.ToRed}().
-- * To the blue coalition using @{Message#MESSAGE.ToBlue}().
-- * To all Players using @{Message#MESSAGE.ToAll}().
--
-- ## Send conditionally to an audience
--
-- Messages can be sent conditionally to an audience (when a condition is true):
--
-- * To all players using @{#MESSAGE.ToAllIf}().
-- * To a coalition using @{#MESSAGE.ToCoalitionIf}().
-- * To all players using @{Message#MESSAGE.ToAllIf}().
-- * To a coalition using @{Message#MESSAGE.ToCoalitionIf}().
--
-- ===
-- ====
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ===
-- ====
--
-- @field #MESSAGE
MESSAGE = {
@@ -50,23 +53,12 @@ MESSAGE = {
MessageID = 0,
}
--- Message Types
-- @type MESSAGE.Type
MESSAGE.Type = {
Update = "Update",
Information = "Information",
Briefing = "Briefing Report",
Overview = "Overview Report",
Detailed = "Detailed Report"
}
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- @param self
-- @param #string MessageText is the text of the Message.
-- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel.
-- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ".
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
-- @return #MESSAGE
-- @usage
-- -- Create a series of new Messages.
@@ -78,13 +70,10 @@ MESSAGE.Type = {
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen )
function MESSAGE:New( MessageText, MessageDuration, MessageCategory )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText, MessageDuration, MessageCategory } )
self.MessageType = nil
-- When no MessageCategory is given, we don't show it as a title...
if MessageCategory and MessageCategory ~= "" then
if MessageCategory:sub(-1) ~= "\n" then
@@ -95,11 +84,6 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen
else
self.MessageCategory = ""
end
self.ClearScreen=false
if ClearScreen~=nil then
self.ClearScreen=ClearScreen
end
self.MessageDuration = MessageDuration or 5
self.MessageTime = timer.getTime()
@@ -112,52 +96,6 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen
return self
end
--- Creates a new MESSAGE object of a certain type.
-- Note that these MESSAGE objects are not yet displayed on the display panel.
-- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- The message display times are automatically defined based on the timing settings in the @{Settings} menu.
-- @param self
-- @param #string MessageText is the text of the Message.
-- @param #MESSAGE.Type MessageType The type of the message.
-- @param #boolean ClearScreen (optional) Clear all previous messages.
-- @return #MESSAGE
-- @usage
-- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information )
-- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information )
-- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update )
-- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update )
function MESSAGE:NewType( MessageText, MessageType, ClearScreen )
local self = BASE:Inherit( self, BASE:New() )
self:F( { MessageText } )
self.MessageType = MessageType
self.ClearScreen=false
if ClearScreen~=nil then
self.ClearScreen=ClearScreen
end
self.MessageTime = timer.getTime()
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
return self
end
--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc.
-- @param #MESSAGE self
-- @return #MESSAGE
function MESSAGE:Clear()
self:F()
self.ClearScreen=true
return self
end
--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".
-- @param #MESSAGE self
-- @param Wrapper.Client#CLIENT Client is the Group of the Client.
@@ -177,22 +115,14 @@ end
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
-- MessageClient1:ToClient( ClientGroup )
-- MessageClient2:ToClient( ClientGroup )
function MESSAGE:ToClient( Client, Settings )
function MESSAGE:ToClient( Client )
self:F( Client )
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
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)
end
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 )
end
return self
@@ -202,21 +132,13 @@ end
-- @param #MESSAGE self
-- @param Wrapper.Group#GROUP Group is the Group.
-- @return #MESSAGE
function MESSAGE:ToGroup( Group, Settings )
function MESSAGE:ToGroup( Group )
self:F( Group.GroupName )
if Group then
if self.MessageType then
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
end
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
return self
@@ -271,20 +193,12 @@ end
-- or
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
-- MessageRED:ToCoalition( coalition.side.RED )
function MESSAGE:ToCoalition( CoalitionSide, Settings )
function MESSAGE:ToCoalition( CoalitionSide )
self:F( CoalitionSide )
if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if CoalitionSide then
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
end
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration )
end
return self
@@ -318,16 +232,8 @@ end
function MESSAGE:ToAll()
self:F()
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
self:ToCoalition( coalition.side.RED )
self:ToCoalition( coalition.side.BLUE )
return self
end
@@ -339,7 +245,8 @@ end
function MESSAGE:ToAllIf( Condition )
if Condition and Condition == true then
self:ToAll()
self:ToCoalition( coalition.side.RED )
self:ToCoalition( coalition.side.BLUE )
end
return self

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
--- **Core** -- The RADIO Module is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions...
--
-- ![Banner Image](..\Presentations\RADIO\Dia1.JPG)
--
-- ===
--
-- The Radio contains 2 classes : RADIO and BEACON
@@ -16,10 +18,10 @@
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
-- * For simplicty sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
--
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Unit#UNIT} or a @{Group#GROUP} or by any other @{Positionable#POSITIONABLE}
--
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
-- * If the transmitter is a @{Unit#UNIT} or a @{Group#GROUP}, DCS will set the power of the transmission automatically,
-- * If the transmitter is any other @{Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
--
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
@@ -30,40 +32,39 @@
--
-- ### Author: Hugues "Grey_Echo" Bousquet
--
-- @module Core.Radio
-- @image Core_Radio.JPG
-- @module Radio
--- Models the radio capabilty.
--- # RADIO class, extends @{Base#BASE}
--
-- ## RADIO usage
--
-- There are 3 steps to a successful radio transmission.
--
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
-- * First, you need to **"add a @{#RADIO} object** to your @{Positionable#POSITIONABLE}. This is done using the @{Positionable#POSITIONABLE.GetRadio}() function,
-- * Then, you will **set the relevant parameters** to the transmission (see below),
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
--
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
-- Methods to set relevant parameters for both a @{Unit#UNIT} or a @{Group#GROUP} or any other @{Positionable#POSITIONABLE}
--
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
--
-- Additional Methods to set relevant parameters if the transmiter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
-- Additional Methods to set relevant parameters if the transmiter is a @{Unit#UNIT} or a @{Group#GROUP}
--
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
--
-- Additional Methods to set relevant parameters if the transmiter is any other @{Wrapper.Positionable#POSITIONABLE}
-- Additional Methods to set relevant parameters if the transmiter is any other @{Positionable#POSITIONABLE}
--
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
--
-- What is this power thing ?
--
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
-- * If your transmission is sent by a @{Positionable#POSITIONABLE} other than a @{Unit#UNIT} or a @{Group#GROUP}, you can set the power of the antenna,
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
-- * If the player gets **too far** from the transmiter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
-- * This an automated DCS calculation you have no say on,
@@ -92,7 +93,7 @@ RADIO = {
}
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead
-- If you want to create a RADIO, you probably should use @{Positionable#POSITIONABLE.GetRadio}() instead
-- @param #RADIO self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #RADIO Radio
@@ -261,7 +262,7 @@ end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- but it will work for any @{Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self
-- @param #string FileName
@@ -275,12 +276,8 @@ function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequen
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
self:SetFileName(FileName)
local Duration = 5
if SubtitleDuration then Duration = SubtitleDuration end
-- SubtitleDuration argument was missing, adding it
if Subtitle then self:SetSubtitle(Subtitle, Duration) end
-- self:SetSubtitleDuration is non existent, removing faulty line
-- if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration) end
if Subtitle then self:SetSubtitle(Subtitle) end
if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration) end
if Frequency then self:SetFrequency(Frequency) end
if Modulation then self:SetModulation(Modulation) end
if Loop then self:SetLoop(Loop) end
@@ -290,7 +287,7 @@ end
--- Actually Broadcast the transmission
-- * The Radio has to be populated with the new transmission before broadcasting.
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
-- * Please use RADIO setters or either @{Radio#RADIO.NewGenericTransmission} or @{Radio#RADIO.NewUnitTransmission}
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
@@ -342,20 +339,22 @@ function RADIO:StopBroadcast()
end
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
--- # BEACON class, extends @{Base#BASE}
--
-- After attaching a @{#BEACON} to your @{Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
-- attach to a cargo crate, for exemple.
--
-- ## AA TACAN Beacon usage
--
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
-- This beacon only works with airborne @{Unit#UNIT} or a @{Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
-- Use @#BEACON:StopAATACAN}() to stop it.
--
-- ## General Purpose Radio Beacon usage
--
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
-- This beacon will work with any @{Positionable#POSITIONABLE}, but **it won't follow the @{Positionable#POSITIONABLE}** ! This means that you should only use it with
-- @{Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
--
-- @type BEACON
@@ -365,7 +364,7 @@ BEACON = {
}
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.AATACAN} or @{#BEACON.Generic}
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
-- If you want to create a BEACON, you probably should use @{Positionable#POSITIONABLE.GetBeacon}() instead.
-- @param #BEACON self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
-- @return #BEACON Beacon

View File

@@ -1,100 +0,0 @@
--- **Core** -- **REPORT** class provides a handy means to create messages and reports.
--
-- ===
--
-- ### Authors:
--
-- * FlightControl : Design & Programming
--
-- ### Contributions:
--
-- @module Core.Report
-- @image Core_Report.JPG
--- The REPORT class
-- @type REPORT
-- @extends Core.Base#BASE
REPORT = {
ClassName = "REPORT",
Title = "",
}
--- Create a new REPORT.
-- @param #REPORT self
-- @param #string Title
-- @return #REPORT
function REPORT:New( Title )
local self = BASE:Inherit( self, BASE:New() ) -- #REPORT
self.Report = {}
self:SetTitle( Title or "" )
self:SetIndent( 3 )
return self
end
--- Has the REPORT Text?
-- @param #REPORT self
-- @return #boolean
function REPORT:HasText() --R2.1
return #self.Report > 0
end
--- Set indent of a REPORT.
-- @param #REPORT self
-- @param #number Indent
-- @return #REPORT
function REPORT:SetIndent( Indent ) --R2.1
self.Indent = Indent
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:Add( Text )
self.Report[#self.Report+1] = Text
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:AddIndent( Text, Separator ) --R2.1
self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
return self
end
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
-- @param #REPORT self
-- @param #string Delimiter (optional) A delimiter text.
-- @return #string The report text.
function REPORT:Text( Delimiter )
Delimiter = Delimiter or "\n"
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or ""
return ReportText
end
--- Sets the title of the report.
-- @param #REPORT self
-- @param #string Title The title of the report.
-- @return #REPORT
function REPORT:SetTitle( Title )
self.Title = Title
return self
end
--- Gets the amount of report items contained in the report.
-- @param #REPORT self
-- @return #number Returns the number of report items contained in the report. 0 is returned if no report items are contained in the report. The title is not counted for.
function REPORT:GetCount()
return #self.Report
end

View File

@@ -22,7 +22,7 @@
--
-- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object.
-- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER.
-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method.
-- The SCHEDULER object plans new scheduled functions through the @{Scheduler#SCHEDULER.Schedule}() method.
-- The Schedule() method returns the CallID that is the reference ID for each planned schedule.
--
-- ===
@@ -30,8 +30,7 @@
-- ### Contributions: -
-- ### Authors: FlightControl : Design & Programming
--
-- @module Core.ScheduleDispatcher
-- @image Core_Schedule_Dispatcher.JPG
-- @module ScheduleDispatcher
--- The SCHEDULEDISPATCHER structure
-- @type SCHEDULEDISPATCHER
@@ -56,7 +55,6 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } )
self.CallID = self.CallID + 1
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
-- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
@@ -67,33 +65,33 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
if Scheduler.MasterObject then
self.ObjectSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
self.ObjectSchedulers[self.CallID] = Scheduler
self:F3( { CallID = self.CallID, ObjectScheduler = tostring(self.ObjectSchedulers[self.CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
else
self.PersistentSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
self.PersistentSchedulers[self.CallID] = Scheduler
self:F3( { CallID = self.CallID, PersistentScheduler = self.PersistentSchedulers[self.CallID] } )
end
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
self.Schedule[Scheduler][CallID] = {}
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
self.Schedule[Scheduler][CallID].Start = Start + .1
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
self.Schedule[Scheduler][CallID].Stop = Stop
self.Schedule[Scheduler][self.CallID] = {}
self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction
self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments
self.Schedule[Scheduler][self.CallID].StartTime = timer.getTime() + ( Start or 0 )
self.Schedule[Scheduler][self.CallID].Start = Start + .1
self.Schedule[Scheduler][self.CallID].Repeat = Repeat
self.Schedule[Scheduler][self.CallID].Randomize = Randomize
self.Schedule[Scheduler][self.CallID].Stop = Stop
self:T3( self.Schedule[Scheduler][CallID] )
self:T3( self.Schedule[Scheduler][self.CallID] )
self.Schedule[Scheduler][CallID].CallHandler = function( CallID )
--self:E( CallID )
self.Schedule[Scheduler][self.CallID].CallHandler = function( CallID )
self:F2( CallID )
local ErrorHandler = function( errmsg )
env.info( "Error in timer function: " .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
if debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
@@ -102,17 +100,16 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
if not Scheduler then
Scheduler = self.PersistentSchedulers[CallID]
end
--self:T3( { Scheduler = Scheduler } )
if Scheduler then
local MasterObject = tostring(Scheduler.MasterObject)
local Schedule = self.Schedule[Scheduler][CallID]
--self:T3( { Schedule = Schedule } )
local SchedulerObject = Scheduler.SchedulerObject
local ScheduleObject = Scheduler.SchedulerObject
--local ScheduleObjectName = Scheduler.SchedulerObject:GetNameAndClassID()
local ScheduleFunction = Schedule.Function
local ScheduleArguments = Schedule.Arguments
@@ -123,10 +120,9 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
local ScheduleID = Schedule.ScheduleID
local Status, Result
--self:E( { SchedulerObject = SchedulerObject } )
if SchedulerObject then
if ScheduleObject then
local function Timer()
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
return ScheduleFunction( ScheduleObject, unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
else
@@ -137,13 +133,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
end
local CurrentTime = timer.getTime()
local StartTime = Schedule.StartTime
self:F3( { Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
local StartTime = CurrentTime + Start
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
if Repeat ~= 0 and ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) then
local ScheduleTime =
CurrentTime +
Repeat +
@@ -167,9 +160,9 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
return nil
end
self:Start( Scheduler, CallID )
self:Start( Scheduler, self.CallID )
return CallID
return self.CallID
end
function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID )
@@ -189,11 +182,10 @@ function SCHEDULEDISPATCHER:Start( Scheduler, CallID )
-- Only start when there is no ScheduleID defined!
-- This prevents to "Start" the scheduler twice with the same CallID...
if not Schedule[CallID].ScheduleID then
Schedule[CallID].StartTime = timer.getTime() -- Set the StartTime field to indicate when the scheduler started.
Schedule[CallID].ScheduleID = timer.scheduleFunction(
Schedule[CallID].CallHandler,
CallID,
timer.getTime() + Schedule[CallID].Start
timer.getTime() + Schedule[CallID].Start
)
end
else

View File

@@ -1,5 +1,7 @@
--- **Core** -- SCHEDULER prepares and handles the **execution of functions over scheduled time (intervals)**.
--
-- ![Banner Image](..\Presentations\SCHEDULER\Dia1.JPG)
--
-- ===
--
-- SCHEDULER manages the **scheduling of functions**:
@@ -19,13 +21,13 @@
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
-- ====
--
-- # YouTube Channel
--
-- ### [SCHEDULER YouTube Channel (none)]()
--
-- ===
-- ====
--
-- ### Contributions:
--
@@ -37,8 +39,8 @@
--
-- ===
--
-- @module Core.Scheduler
-- @image Core_Scheduler.JPG
-- @module Scheduler
--- The SCHEDULER class
-- @type SCHEDULER
@@ -46,7 +48,9 @@
-- @extends Core.Base#BASE
--- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization.
--- # SCHEDULER class, extends @{Base#BASE}
--
-- The SCHEDULER class creates schedule.
--
-- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**.
-- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called.
@@ -206,8 +210,7 @@ SCHEDULER = {
-- @return #SCHEDULER self.
-- @return #number The ScheduleID of the planned schedule.
function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
local self = BASE:Inherit( self, BASE:New() )
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
local ScheduleID = nil

File diff suppressed because it is too large Load Diff

View File

@@ -1,193 +1,46 @@
--- **Core** -- Manages various settings for MOOSE classes.
--- **Core** -- **SETTINGS** classe defines the format settings management for measurement.
--
-- ![Banner Image](..\Presentations\SETTINGS\Dia1.JPG)
--
-- ====
--
-- # Demo Missions
--
-- ### [SETTINGS Demo Missions source code]()
--
-- ### [SETTINGS Demo Missions, only for beta testers]()
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ====
--
-- # YouTube Channel
--
-- ### [SETTINGS YouTube Channel]()
--
-- ===
--
-- The documentation of the SETTINGS class can be found further in this document.
--
-- ===
--
-- # **AUTHORS and CONTRIBUTIONS**
--
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ### Authors:
--
-- * **FlightControl**: Design & Programming
-- ====
--
-- @module Core.Settings
-- @image Core_Settings.JPG
-- @module Settings
--- @type SETTINGS
-- @field #number LL_Accuracy
-- @field #boolean LL_DMS
-- @field #number MGRS_Accuracy
-- @field #string A2GSystem
-- @field #string A2ASystem
-- @extends Core.Base#BASE
--- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
--
-- ===
--
-- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
-- SETTINGS can work on 2 levels:
--
-- - **Default settings**: A running mission has **Default settings**.
-- - **Player settings**: For each player its own **Player settings** can be defined, overriding the **Default settings**.
--
-- So, when there isn't any **Player setting** defined for a player for a specific setting, or, the player cannot be identified, the **Default setting** will be used instead.
--
-- # 1) \_SETTINGS object
--
-- MOOSE defines by default a singleton object called **\_SETTINGS**. Use this object to modify all the **Default settings** for a running mission.
-- For each player, MOOSE will automatically allocate also a **player settings** object, and will expose a radio menu to allow the player to adapt the settings to his own preferences.
--
-- # 2) SETTINGS Menu
--
-- Settings can be adapted by the Players and by the Mission Administrator through **radio menus, which are automatically available in the mission**.
-- These menus can be found **on level F10 under "Settings"**. There are two kinds of menus generated by the system.
--
-- ## 2.1) Default settings menu
--
-- A menu is created automatically per Command Center that allows to modify the **Default** settings.
-- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**!
-- Note that the **Default settings** will only be used when a player has not choosen its own settings.
--
-- ## 2.2) Player settings menu
--
-- A menu is created automatically per Player Slot (group) that allows to modify the **Player** settings.
-- So, when joining a slot, a menu wil be available that allows to change the settings parameters **FOR THE PLAYER ONLY**!
-- Note that when a player has not chosen a specific setting, the **Default settings** will be used.
--
-- ## 2.3) Show or Hide the Player Setting menus
--
-- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
-- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus.
-- Note that when this method is used, any player already in a slot will not have its menus visibility changed.
-- The option will only have effect when a player enters a new slot or changes a slot.
--
-- Example:
--
-- _SETTINGS:SetPlayerMenuOff() -- will disable the player menus.
-- _SETTINGS:SetPlayerMenuOn() -- will enable the player menus.
-- -- But only when a player exits and reenters the slot these settings will have effect!
--
--
-- # 3) Settings
--
-- There are different settings that are managed and applied within the MOOSE framework.
-- See below a comprehensive description of each.
--
-- ## 3.1) **A2G coordinates** display formatting
--
-- ### 3.1.1) A2G coordinates setting **types**
--
-- Will customize which display format is used to indicate A2G coordinates in text as part of the Command Center communications.
--
-- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)).
-- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
-- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
-- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
--
-- ### 3.1.2) A2G coordinates setting **menu**
--
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
--
-- ### 3.1.3) A2G coordinates setting **methods**
--
-- There are different methods that can be used to change the **System settings** using the \_SETTINGS object.
--
-- - @{#SETTINGS.SetA2G_BR}(): Enable the BR display formatting by default.
-- - @{#SETTINGS.SetA2G_MGRS}(): Enable the MGRS display formatting by default. Use @{SETTINGS.SetMGRS_Accuracy}() to adapt the accuracy of the MGRS formatting.
-- - @{#SETTINGS.SetA2G_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
-- - @{#SETTINGS.SetA2G_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
--
-- ### 3.1.4) A2G coordinates setting - additional notes
--
-- One additional note on BR. In a situation when a BR coordinate should be given,
-- but there isn't any player context (no player unit to reference from), the MGRS formatting will be applied!
--
-- ## 3.2) **A2A coordinates** formatting
--
-- ### 3.2.1) A2A coordinates setting **types**
--
-- Will customize which display format is used to indicate A2A coordinates in text as part of the Command Center communications.
--
-- - A2A BRAA: [Bearing Range Altitude Aspect](https://en.wikipedia.org/wiki/Bearing_(navigation)).
-- - A2A MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
-- - A2A LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
-- - A2A LL DDM: Lattitude Longitude [Decimal Degrees and Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
-- - A2A BULLS: [Bullseye](http://falcon4.wikidot.com/concepts:bullseye).
--
-- ### 3.2.2) A2A coordinates setting **menu**
--
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
--
-- ### 3.2.3) A2A coordinates setting **methods**
--
-- There are different methods that can be used to change the **System settings** using the \_SETTINGS object.
--
-- - @{#SETTINGS.SetA2A_BRAA}(): Enable the BR display formatting by default.
-- - @{#SETTINGS.SetA2A_MGRS}(): Enable the MGRS display formatting by default. Use @{SETTINGS.SetMGRS_Accuracy}() to adapt the accuracy of the MGRS formatting.
-- - @{#SETTINGS.SetA2A_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
-- - @{#SETTINGS.SetA2A_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting.
-- - @{#SETTINGS.SetA2A_BULLS}(): Enable the BULLSeye display formatting by default.
--
-- ### 3.2.4) A2A coordinates settings - additional notes
--
-- One additional note on BRAA. In a situation when a BRAA coordinate should be given,
-- but there isn't any player context (no player unit to reference from), the MGRS formatting will be applied!
--
-- ## 3.3) **Measurements** formatting
--
-- ### 3.3.1) Measurements setting **types**
--
-- Will customize the measurements system being used as part as part of the Command Center communications.
--
-- - **Metrics** system: Applies the [Metrics system](https://en.wikipedia.org/wiki/Metric_system) ...
-- - **Imperial** system: Applies the [Imperial system](https://en.wikipedia.org/wiki/Imperial_units) ...
--
-- ### 3.3.2) Measurements setting **menu**
--
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
--
-- ### 3.3.3) Measurements setting **methods**
--
-- There are different methods that can be used to change the **Default settings** using the \_SETTINGS object.
--
-- - @{#SETTINGS.SetMetric}(): Enable the Metric system.
-- - @{#SETTINGS.SetImperial}(): Enable the Imperial system.
--
-- ## 3.4) **Message** display times
--
-- ### 3.4.1) Message setting **types**
--
-- There are various **Message Types** that will influence the duration how long a message will appear as part of the Command Center communications.
--
-- - **Update** message: A short update message.
-- - **Information** message: Provides new information **while** executing a mission.
-- - **Briefing** message: Provides a complete briefing **before** executing a mission.
-- - **Overview report**: Provides a short report overview, the summary of the report.
-- - **Detailed report**: Provides a complete report.
--
-- ### 3.4.2) Message setting **menu**
--
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
--
-- Each Message Type has specific timings that will be applied when the message is displayed.
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen.
-- So the player can choose its own amount of seconds how long a message should be displayed of a certain type.
-- Note that **Update** messages can be chosen not to be displayed at all!
--
-- ### 3.4.3) Message setting **methods**
--
-- There are different methods that can be used to change the **System settings** using the \_SETTINGS object.
--
-- - @{#SETTINGS.SetMessageTime}(): Define for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds.
-- - @{#SETTINGS.GetMessageTime}(): Retrieves for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds.
--
-- ===
--
--- # SETTINGS class, extends @{Base#BASE}
--
-- @field #SETTINGS
SETTINGS = {
ClassName = "SETTINGS",
ShowPlayerMenu = true,
}
@@ -204,13 +57,9 @@ do -- SETTINGS
self:SetMetric() -- Defaults
self:SetA2G_BR() -- Defaults
self:SetA2A_BRAA() -- Defaults
self:SetLL_Accuracy( 3 ) -- Defaults
self:SetLL_Accuracy( 2 ) -- Defaults
self:SetLL_DMS( true ) -- Defaults
self:SetMGRS_Accuracy( 5 ) -- Defaults
self:SetMessageTime( MESSAGE.Type.Briefing, 180 )
self:SetMessageTime( MESSAGE.Type.Detailed, 60 )
self:SetMessageTime( MESSAGE.Type.Information, 30 )
self:SetMessageTime( MESSAGE.Type.Overview, 60 )
self:SetMessageTime( MESSAGE.Type.Update, 15 )
return self
else
local Settings = _DATABASE:GetPlayerSettings( PlayerName )
@@ -233,6 +82,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if metric.
function SETTINGS:IsMetric()
self:E( {Metric = ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) } )
return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
end
@@ -246,6 +96,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if imperial.
function SETTINGS:IsImperial()
self:E( {Metric = ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) } )
return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
end
@@ -260,10 +111,25 @@ do -- SETTINGS
--- Gets the SETTINGS LL accuracy.
-- @param #SETTINGS self
-- @return #number
function SETTINGS:GetLL_DDM_Accuracy()
return self.LL_DDM_Accuracy or _SETTINGS:GetLL_DDM_Accuracy()
function SETTINGS:GetLL_Accuracy()
return self.LL_Accuracy or _SETTINGS:GetLL_Accuracy()
end
--- Sets the SETTINGS LL DMS.
-- @param #SETTINGS self
-- @param #number LL_DMS
-- @return #SETTINGS
function SETTINGS:SetLL_DMS( LL_DMS )
self.LL_DMS = LL_DMS
end
--- Gets the SETTINGS LL DMS.
-- @param #SETTINGS self
-- @return #number
function SETTINGS:GetLL_DMS()
return self.LL_DMS or _SETTINGS:GetLL_DMS()
end
--- Sets the SETTINGS MGRS accuracy.
-- @param #SETTINGS self
-- @param #number MGRS_Accuracy
@@ -279,50 +145,21 @@ do -- SETTINGS
return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy()
end
--- Sets the SETTINGS Message Display Timing of a MessageType
-- @param #SETTINGS self
-- @param Core.Message#MESSAGE MessageType The type of the message.
-- @param #number MessageTime The display time duration in seconds of the MessageType.
function SETTINGS:SetMessageTime( MessageType, MessageTime )
self.MessageTypeTimings = self.MessageTypeTimings or {}
self.MessageTypeTimings[MessageType] = MessageTime
end
--- Gets the SETTINGS Message Display Timing of a MessageType
-- @param #SETTINGS self
-- @param Core.Message#MESSAGE MessageType The type of the message.
-- @return #number
function SETTINGS:GetMessageTime( MessageType )
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType )
end
--- Sets A2G LL DMS
--- Sets A2G LL
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2G_LL_DMS()
self.A2GSystem = "LL DMS"
function SETTINGS:SetA2G_LL()
self.A2GSystem = "LL"
end
--- Sets A2G LL DDM
--- Is LL
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2G_LL_DDM()
self.A2GSystem = "LL DDM"
end
--- Is LL DMS
-- @param #SETTINGS self
-- @return #boolean true if LL DMS
function SETTINGS:IsA2G_LL_DMS()
return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() )
end
--- Is LL DDM
-- @param #SETTINGS self
-- @return #boolean true if LL DDM
function SETTINGS:IsA2G_LL_DDM()
return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() )
-- @return #boolean true if LL
function SETTINGS:IsA2G_LL()
return ( self.A2GSystem and self.A2GSystem == "LL" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL() )
end
--- Sets A2G MGRS
@@ -350,6 +187,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if BRA
function SETTINGS:IsA2G_BR()
self:E( { BRA = ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() ) } )
return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() )
end
@@ -382,48 +220,6 @@ do -- SETTINGS
return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() )
end
--- Sets A2A LL DMS
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2A_LL_DMS()
self.A2ASystem = "LL DMS"
end
--- Sets A2A LL DDM
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2A_LL_DDM()
self.A2ASystem = "LL DDM"
end
--- Is LL DMS
-- @param #SETTINGS self
-- @return #boolean true if LL DMS
function SETTINGS:IsA2A_LL_DMS()
return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() )
end
--- Is LL DDM
-- @param #SETTINGS self
-- @return #boolean true if LL DDM
function SETTINGS:IsA2A_LL_DDM()
return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() )
end
--- Sets A2A MGRS
-- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetA2A_MGRS()
self.A2ASystem = "MGRS"
end
--- Is MGRS
-- @param #SETTINGS self
-- @return #boolean true if MGRS
function SETTINGS:IsA2A_MGRS()
return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() )
end
--- @param #SETTINGS self
-- @return #SETTINGS
function SETTINGS:SetSystemMenu( MenuGroup, RootMenu )
@@ -436,73 +232,41 @@ do -- SETTINGS
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, "A2G Coordinate System", SettingsMenu ):SetTime( MenuTime )
if not self:IsA2G_LL_DMS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Min Sec (LL DMS)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
end
if not self:IsA2G_LL_DDM() then
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
end
if self:IsA2G_LL_DDM() then
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 3", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
end
if not self:IsA2G_BR() then
if self:IsA2G_LL() then
MENU_GROUP_COMMAND:New( MenuGroup, "Bearing, Range (BR)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
end
if not self:IsA2G_MGRS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Lattitude Longitude (LL) Accuracy 1", A2GCoordinateMenu, self.MenuLL_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Lattitude Longitude (LL) Accuracy 2", A2GCoordinateMenu, self.MenuLL_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Lattitude Longitude (LL) Accuracy 3", A2GCoordinateMenu, self.MenuLL_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Lattitude Longitude (LL) Decimal On", A2GCoordinateMenu, self.MenuLL_DMS, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Lattitude Longitude (LL) Decimal Off", A2GCoordinateMenu, self.MenuLL_DMS, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
end
if self:IsA2G_MGRS() then
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 1", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 2", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 3", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 4", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 4 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Bearing, Range (BR)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Lattitude Longitude (LL)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL" ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS) Accuracy 1", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS) Accuracy 2", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS) Accuracy 3", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS) Accuracy 4", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 4 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS) Accuracy 5", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
end
if self:IsA2G_BR() then
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Lattitude Longitude (LL)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL" ):SetTime( MenuTime )
end
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, "A2A Coordinate System", SettingsMenu ):SetTime( MenuTime )
if not self:IsA2A_LL_DMS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Min Sec (LL DMS)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
if self:IsA2A_BULLS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Bearing Range Altitude Aspect (BRAA)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
end
if not self:IsA2A_LL_DDM() then
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
end
if self:IsA2A_LL_DDM() then
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 3", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
end
if not self:IsA2A_BULLS() then
if self:IsA2A_BRAA() then
MENU_GROUP_COMMAND:New( MenuGroup, "Bullseye (BULLS)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
end
if not self:IsA2A_BRAA() then
MENU_GROUP_COMMAND:New( MenuGroup, "Bearing Range Altitude Aspect (BRAA)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
end
if not self:IsA2A_MGRS() then
MENU_GROUP_COMMAND:New( MenuGroup, "Military Grid (MGRS)", A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
end
if self:IsA2A_MGRS() then
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 1", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 2", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 3", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 4", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 4 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
end
local MetricsMenu = MENU_GROUP:New( MenuGroup, "Measures and Weights System", SettingsMenu ):SetTime( MenuTime )
if self:IsMetric() then
@@ -512,225 +276,85 @@ do -- SETTINGS
if self:IsImperial() then
MENU_GROUP_COMMAND:New( MenuGroup, "Metric (Kilometers,Meters)", MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
end
local MessagesMenu = MENU_GROUP:New( MenuGroup, "Messages and Reports", SettingsMenu ):SetTime( MenuTime )
local UpdateMessagesMenu = MENU_GROUP:New( MenuGroup, "Update Messages", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "Off", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 0 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "5 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 5 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "10 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 10 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", UpdateMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Update, 60 ):SetTime( MenuTime )
local InformationMessagesMenu = MENU_GROUP:New( MenuGroup, "Information Messages", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "5 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 5 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "10 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 10 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", InformationMessagesMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Information, 120 ):SetTime( MenuTime )
local BriefingReportsMenu = MENU_GROUP:New( MenuGroup, "Briefing Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", BriefingReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Briefing, 180 ):SetTime( MenuTime )
local OverviewReportsMenu = MENU_GROUP:New( MenuGroup, "Overview Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", OverviewReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.Overview, 180 ):SetTime( MenuTime )
local DetailedReportsMenu = MENU_GROUP:New( MenuGroup, "Detailed Reports", MessagesMenu ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "15 seconds", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 15 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "30 seconds", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 30 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 60 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
SettingsMenu:Remove( MenuTime )
return self
end
--- Sets the player menus on, so that the **Player setting menus** show up for the players.
-- But only when a player exits and reenters the slot these settings will have effect!
-- It is advised to use this method at the start of the mission.
-- @param #SETTINGS self
-- @return #SETTINGS
-- @usage
-- _SETTINGS:SetPlayerMenuOn() -- will enable the player menus.
function SETTINGS:SetPlayerMenuOn()
self.ShowPlayerMenu = true
end
--- Sets the player menus off, so that the **Player setting menus** won't show up for the players.
-- But only when a player exits and reenters the slot these settings will have effect!
-- It is advised to use this method at the start of the mission.
-- @param #SETTINGS self
-- @return #SETTINGS self
-- @usage
-- _SETTINGS:SetPlayerMenuOff() -- will disable the player menus.
function SETTINGS:SetPlayerMenuOff()
self.ShowPlayerMenu = false
end
--- Updates the menu of the player seated in the PlayerUnit.
-- @param #SETTINGS self
--- @param #SETTINGS self
-- @param RootMenu
-- @param Wrapper.Client#CLIENT PlayerUnit
-- @return #SETTINGS self
-- @param #string MenuText
-- @return #SETTINGS
function SETTINGS:SetPlayerMenu( PlayerUnit )
if _SETTINGS.ShowPlayerMenu == true then
local PlayerGroup = PlayerUnit:GetGroup()
local PlayerName = PlayerUnit:GetPlayerName()
local PlayerNames = PlayerGroup:GetPlayerNames()
local PlayerMenu = MENU_GROUP:New( PlayerGroup, 'Settings "' .. PlayerName .. '"' )
local PlayerGroup = PlayerUnit:GetGroup()
local PlayerName = PlayerUnit:GetPlayerName()
local PlayerNames = PlayerGroup:GetPlayerNames()
self.PlayerMenu = PlayerMenu
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, "A2G Coordinate System", PlayerMenu )
local PlayerMenu = MENU_GROUP:New( PlayerGroup, 'Settings "' .. PlayerName .. '"' )
self.PlayerMenu = PlayerMenu
if self:IsA2G_LL() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bearing, Range (BR)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
MENU_GROUP_COMMAND:New( PlayerGroup, "Lattitude Longitude (LL) Accuracy 1", A2GCoordinateMenu, self.MenuGroupLL_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Lattitude Longitude (LL) Accuracy 2", A2GCoordinateMenu, self.MenuGroupLL_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Lattitude Longitude (LL) Accuracy 3", A2GCoordinateMenu, self.MenuGroupLL_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Lattitude Longitude (LL) Decimal On", A2GCoordinateMenu, self.MenuGroupLL_DMSSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
MENU_GROUP_COMMAND:New( PlayerGroup, "Lattitude Longitude (LL) Decimal Off", A2GCoordinateMenu, self.MenuGroupLL_DMSSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
end
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, "A2G Coordinate System", PlayerMenu )
if self:IsA2G_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bearing Range (BR)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
MENU_GROUP_COMMAND:New( PlayerGroup, "Lattitude Longitude (LL)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL" )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 1", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 2", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 3", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 4", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 5", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 5 )
end
if self:IsA2G_BR() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
MENU_GROUP_COMMAND:New( PlayerGroup, "Lattitude Longitude (LL)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL" )
end
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, "A2A Coordinate System", PlayerMenu )
if self:IsA2A_BULLS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bearing Range Altitude Aspect (BRAA)", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
end
if self:IsA2A_BRAA() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bullseye (BULLS)", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
end
local MetricsMenu = MENU_GROUP:New( PlayerGroup, "Measures and Weights System", PlayerMenu )
if not self:IsA2G_LL_DMS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Lat/Lon Degree Min Sec (LL DMS)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
end
if self:IsMetric() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Imperial (Miles,Feet)", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
end
if not self:IsA2G_LL_DDM() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
end
if self:IsA2G_LL_DDM() then
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 3", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
end
if not self:IsA2G_BR() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bearing, Range (BR)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
end
if not self:IsA2G_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
end
if self:IsA2G_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 1", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 2", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 3", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 4", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
MENU_GROUP_COMMAND:New( PlayerGroup, "MGRS Accuracy 5", A2GCoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 5 )
end
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, "A2A Coordinate System", PlayerMenu )
if not self:IsA2A_LL_DMS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Lat/Lon Degree Min Sec (LL DMS)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
end
if not self:IsA2A_LL_DDM() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
end
if self:IsA2A_LL_DDM() then
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "LL DDM Accuracy 3", A2GCoordinateMenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
end
if not self:IsA2A_BULLS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bullseye (BULLS)", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
end
if not self:IsA2A_BRAA() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Bearing Range Altitude Aspect (BRAA)", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
end
if not self:IsA2A_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS)", A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
end
if self:IsA2A_MGRS() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 1", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 2", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 3", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 4", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
MENU_GROUP_COMMAND:New( PlayerGroup, "Military Grid (MGRS) Accuracy 5", A2ACoordinateMenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 5 )
end
local MetricsMenu = MENU_GROUP:New( PlayerGroup, "Measures and Weights System", PlayerMenu )
if self:IsMetric() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Imperial (Miles,Feet)", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
end
if self:IsImperial() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Metric (Kilometers,Meters)", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
end
local MessagesMenu = MENU_GROUP:New( PlayerGroup, "Messages and Reports", PlayerMenu )
local UpdateMessagesMenu = MENU_GROUP:New( PlayerGroup, "Update Messages", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "Off", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 0 )
MENU_GROUP_COMMAND:New( PlayerGroup, "5 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 5 )
MENU_GROUP_COMMAND:New( PlayerGroup, "10 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 10 )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 60 )
local InformationMessagesMenu = MENU_GROUP:New( PlayerGroup, "Information Messages", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "5 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 5 )
MENU_GROUP_COMMAND:New( PlayerGroup, "10 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 10 )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 120 )
local BriefingReportsMenu = MENU_GROUP:New( PlayerGroup, "Briefing Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 180 )
local OverviewReportsMenu = MENU_GROUP:New( PlayerGroup, "Overview Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 180 )
local DetailedReportsMenu = MENU_GROUP:New( PlayerGroup, "Detailed Reports", MessagesMenu )
MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 15 )
MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 30 )
MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 60 )
MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 120 )
MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 180 )
end
if self:IsImperial() then
MENU_GROUP_COMMAND:New( PlayerGroup, "Metric (Kilometers,Meters)", MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
end
return self
end
--- Removes the player menu from the PlayerUnit.
-- @param #SETTINGS self
--- @param #SETTINGS self
-- @param RootMenu
-- @param Wrapper.Client#CLIENT PlayerUnit
-- @return #SETTINGS self
-- @return #SETTINGS
function SETTINGS:RemovePlayerMenu( PlayerUnit )
if self.PlayerMenu then
self.PlayerMenu:Remove()
self.PlayerMenu = nil
end
return self
@@ -740,50 +364,51 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
self.A2GSystem = A2GSystem
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!.", A2GSystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
self.A2ASystem = A2ASystem
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!.", A2ASystem ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
function SETTINGS:MenuLL_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!.", LL_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuLL_DMS( MenuGroup, RootMenu, LL_DMS )
self.LL_DMS = LL_DMS
MESSAGE:New( string.format("Settings: Default LL format set to %s for all players!.", LL_DMS or "Decimal" or "HMS" ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!.", MGRS_Accuracy ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
self.Metric = MW
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!.", MW and "Metric" or "Imperial" ), 5 ):ToAll()
self:SetSystemMenu( MenuGroup, RootMenu )
end
--- @param #SETTINGS self
function SETTINGS:MenuMessageTimingsSystem( MenuGroup, RootMenu, MessageType, MessageTime )
self:SetMessageTime( MessageType, MessageTime )
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToAll()
end
do
--- @param #SETTINGS self
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
self.A2GSystem = A2GSystem
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format("Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
@@ -791,23 +416,31 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
self.A2ASystem = A2ASystem
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format("Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
function SETTINGS:MenuGroupLL_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
self.LL_Accuracy = LL_Accuracy
MESSAGE:New( string.format( "Settings: A2G LL format accuracy set to %d for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format("Settings: A2G LL format accuracy set to %d for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupLL_DMSSystem( PlayerUnit, PlayerGroup, PlayerName, LL_DMS )
self.LL_DMS = LL_DMS
MESSAGE:New( string.format("Settings: A2G LL format mode set to %s for player %s.", LL_DMS and "Decimal" or "HMS", PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy
MESSAGE:New( string.format( "Settings: A2G MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format("Settings: A2G MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
@@ -815,16 +448,10 @@ do -- SETTINGS
--- @param #SETTINGS self
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
self.Metric = MW
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
MESSAGE:New( string.format("Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
self:RemovePlayerMenu(PlayerUnit)
self:SetPlayerMenu(PlayerUnit)
end
--- @param #SETTINGS self
function SETTINGS:MenuGroupMessageTimingsSystem( PlayerUnit, PlayerGroup, PlayerName, MessageType, MessageTime )
self:SetMessageTime( MessageType, MessageTime )
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToGroup( PlayerGroup )
end
end

View File

@@ -1,10 +1,12 @@
--- **Core** -- Spawn dynamically new STATICs in your missions.
--
-- ===
-- ![Banner Image](..\Presentations\SPAWNSTATIC\Dia1.JPG)
--
-- ====
--
-- SPAWNSTATIC spawns static structures in your missions dynamically. See below the SPAWNSTATIC class documentation.
--
-- ===
-- ====
--
-- # Demo Missions
--
@@ -14,21 +16,20 @@
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
-- ====
--
-- # YouTube Channel
--
-- ### [SPAWNSTATIC YouTube Channel]()
--
-- ===
-- ====
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ===
-- ====
--
-- @module Core.SpawnStatic
-- @image Core_Spawnstatic.JPG
-- @module SpawnStatic
@@ -36,7 +37,9 @@
-- @extends Core.Base#BASE
--- Allows to spawn dynamically new @{Static}s.
--- # SPAWNSTATIC class, extends @{Base#BASE}
--
-- The SPAWNSTATIC class allows to spawn dynamically new @{Static}s.
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME),
-- SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy"
-- these properties to create a new static object and place it at the desired coordinate.
@@ -78,16 +81,14 @@ SPAWNSTATIC = {
-- @param #SPAWNSTATIC self
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, SpawnCountryID ) --R2.1
function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, CountryID ) --R2.1
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self:F( { SpawnTemplatePrefix } )
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticUnitTemplate( SpawnTemplatePrefix )
local TemplateStatic = StaticObject.getByName( SpawnTemplatePrefix )
if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.CountryID = SpawnCountryID or CountryID
self.CategoryID = CategoryID
self.CoalitionID = CoalitionID
self.CountryID = CountryID
self.SpawnIndex = 0
else
error( "SPAWNSTATIC:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
@@ -116,40 +117,6 @@ function SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory,
end
--- Creates a new @{Static} at the original position.
-- @param #SPAWNSTATIC self
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:Spawn( Heading, NewName ) --R2.3
self:F( { Heading, NewName } )
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local CountryID = self.CountryID
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( CountryID, StaticTemplate )
self.SpawnIndex = self.SpawnIndex + 1
return Static
end
return nil
end
--- Creates a new @{Static} from a POINT_VEC2.
-- @param #SPAWNSTATIC self
-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static.
@@ -159,101 +126,27 @@ end
function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
self:F( { PointVec2, Heading, NewName } )
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local CountryID = self.CountryID
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
StaticTemplate.x = PointVec2.x
StaticTemplate.y = PointVec2.z
StaticTemplate.units = nil
StaticTemplate.route = nil
StaticTemplate.groupId = nil
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( CountryID, StaticTemplate )
self.SpawnIndex = self.SpawnIndex + 1
return Static
end
return nil
end
--- Creates the original @{Static} at a POINT_VEC2.
-- @param #SPAWNSTATIC self
-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:ReSpawn()
local CountryName = _DATABASE.COUNTRY_NAME[self.CountryID]
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local CountryID = self.CountryID
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
StaticTemplate.units = nil
StaticTemplate.route = nil
StaticTemplate.groupId = nil
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( CountryID, StaticTemplate )
return Static
end
StaticTemplate.x = PointVec2:GetLat()
StaticTemplate.y = PointVec2:GetLon()
return nil
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( self.CountryID, StaticTemplate )
self.SpawnIndex = self.SpawnIndex + 1
return Static
end
--- Creates the original @{Static} at a POINT_VEC2.
-- @param #SPAWNSTATIC self
-- @param Core.Point#COORDINATE Coordinate The 2D coordinate where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:ReSpawnAt( Coordinate, Heading )
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local CountryID = self.CountryID
StaticTemplate.x = Coordinate.x
StaticTemplate.y = Coordinate.z
StaticTemplate.heading = Heading and ( ( Heading / 180 ) * math.pi ) or StaticTemplate.heading
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( CountryID, StaticTemplate )
return Static
end
return nil
end
--- Creates a new @{Static} from a @{Zone}.
-- @param #SPAWNSTATIC self
-- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static.

View File

@@ -1,15 +1,17 @@
--- **Core** -- Management of SPOT logistics, that can be transported from and to transportation carriers.
--
-- ===
-- ![Banner Image](..\Presentations\SPOT\Dia1.JPG)
--
-- ====
--
-- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to:
--
-- * Spot for a defined duration.
-- * wiggle the spot at the target.
-- * Provide a @{Wrapper.Unit} as a target, instead of a point.
-- * Provide a @{Unit} as a target, instead of a point.
-- * Implement a status machine, LaseOn, LaseOff.
--
-- ===
-- ====
--
-- # Demo Missions
--
@@ -19,25 +21,24 @@
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
-- ====
--
-- # YouTube Channel
--
-- ### [SPOT YouTube Channel]()
--
-- ===
-- ====
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- * [**Ciribob**](https://forums.eagle.ru/member.php?u=112175): Showing the way how to lase targets + how laser codes work!!! Explained the autolase script.
-- * [**EasyEB**](https://forums.eagle.ru/member.php?u=112055): Ideas and Beta Testing
-- * [**Wingthor**](https://forums.eagle.ru/member.php?u=123698): Beta Testing
--
-- ===
-- ====
--
-- @module Core.Spot
-- @image Core_Spot.JPG
-- @module Spot
do
@@ -46,11 +47,13 @@ do
-- @extends Core.Fsm#FSM
--- Implements the target spotting or marking functionality, but adds additional luxury to be able to:
--- # SPOT class, extends @{Fsm#FSM}
--
-- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to:
--
-- * Mark targets for a defined duration.
-- * wiggle the spot at the target.
-- * Provide a @{Wrapper.Unit} as a target, instead of a point.
-- * Provide a @{Unit} as a target, instead of a point.
-- * Implement a status machine, LaseOn, LaseOff.
--
-- ## 1. SPOT constructor
@@ -197,7 +200,7 @@ do
-- @param #number LaserCode
-- @param #number Duration
function SPOT:onafterLaseOn( From, Event, To, Target, LaserCode, Duration )
self:F( { "LaseOn", Target, LaserCode, Duration } )
self:E( { "LaseOn", Target, LaserCode, Duration } )
local function StopLase( self )
self:LaseOff()
@@ -219,16 +222,16 @@ do
self:HandleEvent( EVENTS.Dead )
self:__Lasing( -1 )
self:__Lasing( -0.2 )
end
--- @param #SPOT self
-- @param Core.Event#EVENTDATA EventData
function SPOT:OnEventDead(EventData)
self:F( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
self:E( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
if self.Target then
if EventData.IniDCSUnitName == self.Target:GetName() then
self:F( {"Target dead ", self.Target:GetName() } )
self:E( {"Target dead ", self.Target:GetName() } )
self:Destroyed()
self:LaseOff()
end
@@ -246,7 +249,7 @@ do
self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() )
self:__Lasing( -0.2 )
else
self:F( { "Target is not alive", self.Target:IsAlive() } )
self:E( { "Target is not alive", self.Target:IsAlive() } )
end
end
@@ -258,7 +261,7 @@ do
-- @return #SPOT
function SPOT:onafterLaseOff( From, Event, To )
self:F( {"Stopped lasing for ", self.Target:GetName() , SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
self:E( {"Stopped lasing for ", self.Target:GetName() , SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
self.Lasing = false

View File

@@ -1,95 +0,0 @@
--- **Core (WIP)** -- Manage user flags.
--
-- ===
--
-- Management of DCS User Flags.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module Core.UserFlag
-- @image Core_Userflag.JPG
--
do -- UserFlag
--- @type USERFLAG
-- @extends Core.Base#BASE
--- Management of DCS User Flags.
--
-- ## USERFLAG constructor
--
-- * @{#USERFLAG.New}(): Creates a new USERFLAG object.
--
-- @field #USERFLAG
USERFLAG = {
ClassName = "USERFLAG",
}
--- USERFLAG Constructor.
-- @param #USERFLAG self
-- @param #string UserFlagName The name of the userflag, which is a free text string.
-- @return #USERFLAG
function USERFLAG:New( UserFlagName ) --R2.3
local self = BASE:Inherit( self, BASE:New() ) -- #USERFLAG
self.UserFlagName = UserFlagName
return self
end
--- Set the userflag to a given Number.
-- @param #USERFLAG self
-- @param #number Number The number value to be checked if it is the same as the userflag.
-- @return #USERFLAG The userflag instance.
-- @usage
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100.
--
function USERFLAG:Set( Number ) --R2.3
self:F( { Number = Number } )
trigger.action.setUserFlag( self.UserFlagName, Number )
return self
end
--- Get the userflag Number.
-- @param #USERFLAG self
-- @return #number Number The number value to be checked if it is the same as the userflag.
-- @usage
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value.
--
function USERFLAG:Get( Number ) --R2.3
return trigger.misc.getUserFlag( self.UserFlagName )
end
--- Check if the userflag has a value of Number.
-- @param #USERFLAG self
-- @param #number Number The number value to be checked if it is the same as the userflag.
-- @return #boolean true if the Number is the value of the userflag.
-- @usage
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
-- if BlueVictory:Is( 1 ) then
-- return "Blue has won"
-- end
function USERFLAG:Is( Number ) --R2.3
return trigger.misc.getUserFlag( self.UserFlagName ) == Number
end
end

View File

@@ -1,128 +0,0 @@
--- **Core (WIP)** -- Manage user sound.
--
-- ===
--
-- Management of DCS User Sound.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module Core.UserSound
-- @image Core_Usersound.JPG
do -- UserSound
--- @type USERSOUND
-- @extends Core.Base#BASE
--- Management of DCS User Sound.
--
-- ## USERSOUND constructor
--
-- * @{#USERSOUND.New}(): Creates a new USERSOUND object.
--
-- @field #USERSOUND
USERSOUND = {
ClassName = "USERSOUND",
}
--- USERSOUND Constructor.
-- @param #USERSOUND self
-- @param #string UserSoundFileName The filename of the usersound.
-- @return #USERSOUND
function USERSOUND:New( UserSoundFileName ) --R2.3
local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND
self.UserSoundFileName = UserSoundFileName
return self
end
--- Set usersound filename.
-- @param #USERSOUND self
-- @param #string UserSoundFileName The filename of the usersound.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound.
--
function USERSOUND:SetFileName( UserSoundFileName ) --R2.3
self.UserSoundFileName = UserSoundFileName
return self
end
--- Play the usersound to all players.
-- @param #USERSOUND self
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToAll() -- Play the sound that Blue has won.
--
function USERSOUND:ToAll() --R2.3
trigger.action.outSound( self.UserSoundFileName )
return self
end
--- Play the usersound to the given coalition.
-- @param #USERSOUND self
-- @param DCS#coalition Coalition The coalition to play the usersound to.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition.
--
function USERSOUND:ToCoalition( Coalition ) --R2.3
trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName )
return self
end
--- Play the usersound to the given country.
-- @param #USERSOUND self
-- @param DCS#country Country The country to play the usersound to.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country.
--
function USERSOUND:ToCountry( Country ) --R2.3
trigger.action.outSoundForCountry( Country, self.UserSoundFileName )
return self
end
--- Play the usersound to the given @{Wrapper.Group}.
-- @param #USERSOUND self
-- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} to play the usersound to.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player.
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group.
--
function USERSOUND:ToGroup( Group ) --R2.3
trigger.action.outSoundForGroup( Group:GetID(), self.UserSoundFileName )
return self
end
end

View File

@@ -1,183 +0,0 @@
--- **Core** -- VELOCITY models a speed, which can be expressed in various formats according the Settings.
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module Core.Velocity
-- @image MOOSE.JPG
do -- Velocity
--- @type VELOCITY
-- @extends Core.Base#BASE
--- VELOCITY models a speed, which can be expressed in various formats according the Settings.
--
-- ## VELOCITY constructor
--
-- * @{#VELOCITY.New}(): Creates a new VELOCITY object.
--
-- @field #VELOCITY
VELOCITY = {
ClassName = "VELOCITY",
}
--- VELOCITY Constructor.
-- @param #VELOCITY self
-- @param #number VelocityMps The velocity in meters per second.
-- @return #VELOCITY
function VELOCITY:New( VelocityMps )
local self = BASE:Inherit( self, BASE:New() ) -- #VELOCITY
self:F( {} )
self.Velocity = VelocityMps
return self
end
--- Set the velocity in Mps (meters per second).
-- @param #VELOCITY self
-- @param #number VelocityMps The velocity in meters per second.
-- @return #VELOCITY
function VELOCITY:Set( VelocityMps )
self.Velocity = VelocityMps
return self
end
--- Get the velocity in Mps (meters per second).
-- @param #VELOCITY self
-- @return #number The velocity in meters per second.
function VELOCITY:Get()
return self.Velocity
end
--- Set the velocity in Kmph (kilometers per hour).
-- @param #VELOCITY self
-- @param #number VelocityKmph The velocity in kilometers per hour.
-- @return #VELOCITY
function VELOCITY:SetKmph( VelocityKmph )
self.Velocity = UTILS.KmphToMps( VelocityKmph )
return self
end
--- Get the velocity in Kmph (kilometers per hour).
-- @param #VELOCITY self
-- @return #number The velocity in kilometers per hour.
function VELOCITY:GetKmph()
return UTILS.MpsToKmph( self.Velocity )
end
--- Set the velocity in Miph (miles per hour).
-- @param #VELOCITY self
-- @param #number VelocityMiph The velocity in miles per hour.
-- @return #VELOCITY
function VELOCITY:SetMiph( VelocityMiph )
self.Velocity = UTILS.MiphToMps( VelocityMiph )
return self
end
--- Get the velocity in Miph (miles per hour).
-- @param #VELOCITY self
-- @return #number The velocity in miles per hour.
function VELOCITY:GetMiph()
return UTILS.MpsToMiph( self.Velocity )
end
--- Get the velocity in text, according the player @{Settings}.
-- @param #VELOCITY self
-- @param Core.Settings#SETTINGS Settings
-- @return #string The velocity in text.
function VELOCITY:GetText( Settings )
local Settings = Settings or _SETTINGS
if self.Velocity ~= 0 then
if Settings:IsMetric() then
return string.format( "%d km/h", UTILS.MpsToKmph( self.Velocity ) )
else
return string.format( "%d mi/h", UTILS.MpsToMiph( self.Velocity ) )
end
else
return "stationary"
end
end
--- Get the velocity in text, according the player or default @{Settings}.
-- @param #VELOCITY self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable
-- @param Core.Settings#SETTINGS Settings
-- @return #string The velocity in text according the player or default @{Settings}
function VELOCITY:ToString( VelocityGroup, Settings ) -- R2.3
self:F( { Group = VelocityGroup and VelocityGroup:GetName() } )
local Settings = Settings or ( VelocityGroup and _DATABASE:GetPlayerSettings( VelocityGroup:GetPlayerName() ) ) or _SETTINGS
return self:GetText( Settings )
end
end
do -- VELOCITY_POSITIONABLE
--- @type VELOCITY_POSITIONABLE
-- @extends Core.Base#BASE
--- # VELOCITY_POSITIONABLE class, extends @{Core.Base#BASE}
--
-- VELOCITY_POSITIONABLE monitors the speed of an @{Positionable} in the simulation, which can be expressed in various formats according the Settings.
--
-- ## 1. VELOCITY_POSITIONABLE constructor
--
-- * @{#VELOCITY_POSITIONABLE.New}(): Creates a new VELOCITY_POSITIONABLE object.
--
-- @field #VELOCITY_POSITIONABLE
VELOCITY_POSITIONABLE = {
ClassName = "VELOCITY_POSITIONABLE",
}
--- VELOCITY_POSITIONABLE Constructor.
-- @param #VELOCITY_POSITIONABLE self
-- @param Wrapper.Positionable#POSITIONABLE Positionable The Positionable to monitor the speed.
-- @return #VELOCITY_POSITIONABLE
function VELOCITY_POSITIONABLE:New( Positionable )
local self = BASE:Inherit( self, VELOCITY:New() ) -- #VELOCITY_POSITIONABLE
self:F( {} )
self.Positionable = Positionable
return self
end
--- Get the velocity in Mps (meters per second).
-- @param #VELOCITY_POSITIONABLE self
-- @return #number The velocity in meters per second.
function VELOCITY_POSITIONABLE:Get()
return self.Positionable:GetVelocityMPS() or 0
end
--- Get the velocity in Kmph (kilometers per hour).
-- @param #VELOCITY_POSITIONABLE self
-- @return #number The velocity in kilometers per hour.
function VELOCITY_POSITIONABLE:GetKmph()
return UTILS.MpsToKmph( self.Positionable:GetVelocityMPS() or 0)
end
--- Get the velocity in Miph (miles per hour).
-- @param #VELOCITY_POSITIONABLE self
-- @return #number The velocity in miles per hour.
function VELOCITY_POSITIONABLE:GetMiph()
return UTILS.MpsToMiph( self.Positionable:GetVelocityMPS() or 0 )
end
--- Get the velocity in text, according the player or default @{Settings}.
-- @param #VELOCITY_POSITIONABLE self
-- @return #string The velocity in text according the player or default @{Settings}
function VELOCITY_POSITIONABLE:ToString() -- R2.3
self:F( { Group = self.Positionable and self.Positionable:GetName() } )
local Settings = Settings or ( self.Positionable and _DATABASE:GetPlayerSettings( self.Positionable:GetPlayerName() ) ) or _SETTINGS
self.Velocity = self.Positionable:GetVelocityMPS()
return self:GetText( Settings )
end
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,13 +2,12 @@
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
-- ### Contributions:
--
-- ===
-- ====
--
-- @module Functional.CleanUp
-- @image CleanUp_Airbases.JPG
-- @module CleanUp
--- @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.
@@ -17,8 +16,11 @@
--- @type CLEANUP_AIRBASE
-- @extends #CLEANUP_AIRBASE.__
--- Keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat.
--- # CLEANUP_AIRBASE, extends @{Base#BASE}
--
-- ![Banner Image](..\Presentations\CLEANUP_AIRBASE\Dia1.JPG)
--
-- The CLEANUP_AIRBASE class keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat.
-- Specific airbases need to be provided that need to be guarded. Each airbase registered, will be guarded within a zone of 8 km around the airbase.
-- Any unit that fires a missile, or shoots within the zone of an airbase, will be monitored by CLEANUP_AIRBASE.
-- Within the 8km zone, units cannot fire any missile, which prevents the airbase runway to receive missile or bomb hits.
@@ -171,7 +173,7 @@ end
--- Destroys a @{Wrapper.Unit} from the simulator, but checks first if it is still existing!
--- Destroys a @{Unit} from the simulator, but checks first if it is still existing!
-- @param #CLEANUP_AIRBASE self
-- @param Wrapper.Unit#UNIT CleanUpUnit The object to be destroyed.
function CLEANUP_AIRBASE.__:DestroyUnit( CleanUpUnit )
@@ -198,7 +200,7 @@ end
--- Destroys a missile from the simulator, but checks first if it is still existing!
-- @param #CLEANUP_AIRBASE self
-- @param DCS#Weapon MissileObject
-- @param Dcs.DCSTypes#Weapon MissileObject
function CLEANUP_AIRBASE.__:DestroyMissile( MissileObject )
self:F( { MissileObject } )
@@ -288,9 +290,9 @@ function CLEANUP_AIRBASE.__:OnEventHit( Event )
end
end
--- Add the @{DCS#Unit} to the CleanUpList for CleanUp.
--- Add the @{DCSWrapper.Unit#Unit} to the CleanUpList for CleanUp.
-- @param #CLEANUP_AIRBASE self
-- @param DCS#UNIT CleanUpUnit
-- @param Wrapper.Unit#UNIT CleanUpUnit
-- @oaram #string CleanUpUnitName
function CLEANUP_AIRBASE.__:AddForCleanUp( CleanUpUnit, CleanUpUnitName )
self:F( { CleanUpUnit, CleanUpUnitName } )

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,62 @@
--- **Functional** -- Taking the lead of AI escorting your flight.
--
-- ===
--
-- Allows you to interact with escorting AI on your flight and take the lead.
-- ====
--
-- @{#ESCORT} class
-- ================
-- The @{#ESCORT} class allows you to interact with escorting AI on your flight and take the lead.
-- Each escorting group can be commanded with a whole set of radio commands (radio menu in your flight, and then F10).
--
-- The radio commands will vary according the category of the group. The richest set of commands are with Helicopters and AirPlanes.
-- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts.
--
-- # RADIO MENUs that can be created:
--
-- RADIO MENUs that can be created:
-- ================================
-- Find a summary below of the current available commands:
--
-- ## Navigation ...:
--
-- Navigation ...:
-- ---------------
-- Escort group navigation functions:
--
-- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you.
-- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
-- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
--
-- ## Hold position ...:
--
-- Hold position ...:
-- ------------------
-- Escort group navigation functions:
--
-- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
-- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
--
-- ## Report targets ...:
--
-- Report targets ...:
-- -------------------
-- Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below).
--
-- * **"Report now":** Will report the current detected targets.
-- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list.
-- * **"Report targets off":** Will stop detecting targets.
--
-- ## Scan targets ...:
--
-- Scan targets ...:
-- -----------------
-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task.
--
-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
--
-- ## Attack targets ...:
--
-- Attack targets ...:
-- -------------------
-- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
--
-- ## Request assistance from ...:
--
-- Request assistance from ...:
-- ----------------------------
-- This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**.
-- This menu item allows to request attack support from other escorts supporting the current client group.
-- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles.
-- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area.
--
-- ## ROE ...:
--
-- ROE ...:
-- --------
-- Sets the Rules of Engagement (ROE) of the escort group when in flight.
--
-- * **"Hold Fire":** The escort group will hold fire.
@@ -63,8 +64,8 @@
-- * **"Open Fire":** The escort group will open fire on designated targets.
-- * **"Weapon Free":** The escort group will engage with any target.
--
-- ## Evasion ...:
--
-- Evasion ...:
-- ------------
-- Will define the evasion techniques that the escort group will perform during flight or combat.
--
-- * **"Fight until death":** The escort group will have no reaction to threats.
@@ -72,19 +73,19 @@
-- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
-- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
--
-- ## Resume Mission ...:
--
-- Resume Mission ...:
-- -------------------
-- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
-- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission.
--
-- # ESCORT construction methods.
--
-- ESCORT construction methods.
-- ============================
-- Create a new SPAWN object with the @{#ESCORT.New} method:
--
-- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text.
-- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text.
--
-- # ESCORT initialization methods.
--
-- ESCORT initialization methods.
-- ==============================
-- The following menus are created within the RADIO MENU (F10) of an active unit hosted by a player:
--
-- * @{#ESCORT.MenuFollowAt}: Creates a menu to make the escort follow the client.
@@ -113,8 +114,8 @@
--
--
--
-- @module Functional.Escort
-- @image Escorting.JPG
-- @module Escort
-- @author FlightControl
--- ESCORT class
-- @type ESCORT
@@ -126,9 +127,10 @@
-- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class.
-- @field #number FollowDistance The current follow distance.
-- @field #boolean ReportTargets If true, nearby targets are reported.
-- @Field DCS#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup.
-- @field DCS#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup.
-- @field FunctionalMENU_GROUPDETECTION_BASE Detection
-- @Field Dcs.DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup.
-- @field Dcs.DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup.
-- @field Core.Menu#MENU_CLIENT EscortMenuResumeMission
-- @field Functional.Detection#DETECTION_BASE Detection
ESCORT = {
ClassName = "ESCORT",
EscortName = nil, -- The Escort Name
@@ -205,7 +207,7 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing )
self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection = self.EscortGroup.Detection
end
self.EscortMenu = MENU_GROUP:New( self.EscortClient:GetGroup(), self.EscortName )
self.EscortMenu = MENU_CLIENT:New( self.EscortClient, self.EscortName )
self.EscortGroup:WayPointInitialize(1)
@@ -294,21 +296,21 @@ end
--- Defines a menu slot to let the escort Join and Follow you at a certain distance.
-- This menu will appear under **Navigation**.
-- @param #ESCORT self
-- @param DCS#Distance Distance The distance in meters that the escort needs to follow the client.
-- @param Dcs.DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client.
-- @return #ESCORT
function ESCORT:MenuFollowAt( Distance )
self:F(Distance)
if self.EscortGroup:IsAir() then
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation = MENU_GROUP:New( self.EscortClient:GetGroup(), "Navigation", self.EscortMenu )
self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu )
end
if not self.EscortMenuJoinUpAndFollow then
self.EscortMenuJoinUpAndFollow = {}
end
self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1] = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Join-Up and Follow at " .. Distance, self.EscortMenuReportNavigation, ESCORT._JoinUpAndFollow, self, Distance )
self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1] = MENU_CLIENT_COMMAND:New( self.EscortClient, "Join-Up and Follow at " .. Distance, self.EscortMenuReportNavigation, ESCORT._JoinUpAndFollow, self, Distance )
self.EscortMode = ESCORT.MODE.FOLLOW
end
@@ -319,8 +321,8 @@ end
--- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds.
-- This menu will appear under **Hold position**.
-- @param #ESCORT self
-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
-- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
-- @return #ESCORT
-- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function.
@@ -330,7 +332,7 @@ function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat )
if self.EscortGroup:IsAir() then
if not self.EscortMenuHold then
self.EscortMenuHold = MENU_GROUP:New( self.EscortClient:GetGroup(), "Hold position", self.EscortMenu )
self.EscortMenuHold = MENU_CLIENT:New( self.EscortClient, "Hold position", self.EscortMenu )
end
if not Height then
@@ -360,9 +362,9 @@ function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat )
self.EscortMenuHoldPosition = {}
end
self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1] = MENU_GROUP_COMMAND
self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1] = MENU_CLIENT_COMMAND
:New(
self.EscortClient:GetGroup(),
self.EscortClient,
MenuText,
self.EscortMenuHold,
ESCORT._HoldPosition,
@@ -380,8 +382,8 @@ end
--- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds.
-- This menu will appear under **Navigation**.
-- @param #ESCORT self
-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
-- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
-- @return #ESCORT
-- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function.
@@ -391,7 +393,7 @@ function ESCORT:MenuHoldAtLeaderPosition( Height, Seconds, MenuTextFormat )
if self.EscortGroup:IsAir() then
if not self.EscortMenuHold then
self.EscortMenuHold = MENU_GROUP:New( self.EscortClient:GetGroup(), "Hold position", self.EscortMenu )
self.EscortMenuHold = MENU_CLIENT:New( self.EscortClient, "Hold position", self.EscortMenu )
end
if not Height then
@@ -421,9 +423,9 @@ function ESCORT:MenuHoldAtLeaderPosition( Height, Seconds, MenuTextFormat )
self.EscortMenuHoldAtLeaderPosition = {}
end
self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1] = MENU_GROUP_COMMAND
self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1] = MENU_CLIENT_COMMAND
:New(
self.EscortClient:GetGroup(),
self.EscortClient,
MenuText,
self.EscortMenuHold,
ESCORT._HoldPosition,
@@ -441,8 +443,8 @@ end
--- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds.
-- This menu will appear under **Scan targets**.
-- @param #ESCORT self
-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
-- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters.
-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given.
-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed.
-- @return #ESCORT
function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat )
@@ -450,7 +452,7 @@ function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat )
if self.EscortGroup:IsAir() then
if not self.EscortMenuScan then
self.EscortMenuScan = MENU_GROUP:New( self.EscortClient:GetGroup(), "Scan for targets", self.EscortMenu )
self.EscortMenuScan = MENU_CLIENT:New( self.EscortClient, "Scan for targets", self.EscortMenu )
end
if not Height then
@@ -480,9 +482,9 @@ function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat )
self.EscortMenuScanForTargets = {}
end
self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_GROUP_COMMAND
self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_CLIENT_COMMAND
:New(
self.EscortClient:GetGroup(),
self.EscortClient,
MenuText,
self.EscortMenuScan,
ESCORT._ScanTargets,
@@ -506,7 +508,7 @@ function ESCORT:MenuFlare( MenuTextFormat )
self:F()
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation = MENU_GROUP:New( self.EscortClient:GetGroup(), "Navigation", self.EscortMenu )
self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu )
end
local MenuText = ""
@@ -517,11 +519,11 @@ function ESCORT:MenuFlare( MenuTextFormat )
end
if not self.EscortMenuFlare then
self.EscortMenuFlare = MENU_GROUP:New( self.EscortClient:GetGroup(), MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, self )
self.EscortMenuFlareGreen = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release green flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Green, "Released a green flare!" )
self.EscortMenuFlareRed = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release red flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Red, "Released a red flare!" )
self.EscortMenuFlareWhite = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release white flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.White, "Released a white flare!" )
self.EscortMenuFlareYellow = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Yellow, "Released a yellow flare!" )
self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, self )
self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Green, "Released a green flare!" )
self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Red, "Released a red flare!" )
self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.White, "Released a white flare!" )
self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Yellow, "Released a yellow flare!" )
end
return self
@@ -539,7 +541,7 @@ function ESCORT:MenuSmoke( MenuTextFormat )
if not self.EscortGroup:IsAir() then
if not self.EscortMenuReportNavigation then
self.EscortMenuReportNavigation = MENU_GROUP:New( self.EscortClient:GetGroup(), "Navigation", self.EscortMenu )
self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu )
end
local MenuText = ""
@@ -550,12 +552,12 @@ function ESCORT:MenuSmoke( MenuTextFormat )
end
if not self.EscortMenuSmoke then
self.EscortMenuSmoke = MENU_GROUP:New( self.EscortClient:GetGroup(), "Smoke", self.EscortMenuReportNavigation, ESCORT._Smoke, self )
self.EscortMenuSmokeGreen = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release green smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Green, "Releasing green smoke!" )
self.EscortMenuSmokeRed = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release red smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Red, "Releasing red smoke!" )
self.EscortMenuSmokeWhite = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release white smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.White, "Releasing white smoke!" )
self.EscortMenuSmokeOrange = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release orange smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Orange, "Releasing orange smoke!" )
self.EscortMenuSmokeBlue = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Release blue smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Blue, "Releasing blue smoke!" )
self.EscortMenuSmoke = MENU_CLIENT:New( self.EscortClient, "Smoke", self.EscortMenuReportNavigation, ESCORT._Smoke, self )
self.EscortMenuSmokeGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Green, "Releasing green smoke!" )
self.EscortMenuSmokeRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Red, "Releasing red smoke!" )
self.EscortMenuSmokeWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.White, "Releasing white smoke!" )
self.EscortMenuSmokeOrange = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release orange smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Orange, "Releasing orange smoke!" )
self.EscortMenuSmokeBlue = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release blue smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Blue, "Releasing blue smoke!" )
end
end
@@ -566,13 +568,13 @@ end
-- This menu will appear under **Report targets**.
-- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed.
-- @param #ESCORT self
-- @param DCS#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds.
-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds.
-- @return #ESCORT
function ESCORT:MenuReportTargets( Seconds )
self:F( { Seconds } )
if not self.EscortMenuReportNearbyTargets then
self.EscortMenuReportNearbyTargets = MENU_GROUP:New( self.EscortClient:GetGroup(), "Report targets", self.EscortMenu )
self.EscortMenuReportNearbyTargets = MENU_CLIENT:New( self.EscortClient, "Report targets", self.EscortMenu )
end
if not Seconds then
@@ -580,12 +582,12 @@ function ESCORT:MenuReportTargets( Seconds )
end
-- Report Targets
self.EscortMenuReportNearbyTargetsNow = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Report targets now!", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargetsNow, self )
self.EscortMenuReportNearbyTargetsOn = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Report targets on", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, self, true )
self.EscortMenuReportNearbyTargetsOff = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Report targets off", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, self, false )
self.EscortMenuReportNearbyTargetsNow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets now!", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargetsNow, self )
self.EscortMenuReportNearbyTargetsOn = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets on", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, self, true )
self.EscortMenuReportNearbyTargetsOff = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets off", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, self, false )
-- Attack Targets
self.EscortMenuAttackNearbyTargets = MENU_GROUP:New( self.EscortClient:GetGroup(), "Attack targets", self.EscortMenu )
self.EscortMenuAttackNearbyTargets = MENU_CLIENT:New( self.EscortClient, "Attack targets", self.EscortMenu )
self.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, Seconds )
@@ -603,7 +605,7 @@ function ESCORT:MenuAssistedAttack()
-- Request assistance from other escorts.
-- This is very useful to let f.e. an escorting ship attack a target detected by an escorting plane...
self.EscortMenuTargetAssistance = MENU_GROUP:New( self.EscortClient:GetGroup(), "Request assistance from", self.EscortMenu )
self.EscortMenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, "Request assistance from", self.EscortMenu )
return self
end
@@ -617,18 +619,18 @@ function ESCORT:MenuROE( MenuTextFormat )
if not self.EscortMenuROE then
-- Rules of Engagement
self.EscortMenuROE = MENU_GROUP:New( self.EscortClient:GetGroup(), "ROE", self.EscortMenu )
self.EscortMenuROE = MENU_CLIENT:New( self.EscortClient, "ROE", self.EscortMenu )
if self.EscortGroup:OptionROEHoldFirePossible() then
self.EscortMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Hold Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEHoldFire(), "Holding weapons!" )
self.EscortMenuROEHoldFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Hold Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEHoldFire(), "Holding weapons!" )
end
if self.EscortGroup:OptionROEReturnFirePossible() then
self.EscortMenuROEReturnFire = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Return Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEReturnFire(), "Returning fire!" )
self.EscortMenuROEReturnFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Return Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEReturnFire(), "Returning fire!" )
end
if self.EscortGroup:OptionROEOpenFirePossible() then
self.EscortMenuROEOpenFire = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Open Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEOpenFire(), "Opening fire on designated targets!!" )
self.EscortMenuROEOpenFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Open Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEOpenFire(), "Opening fire on designated targets!!" )
end
if self.EscortGroup:OptionROEWeaponFreePossible() then
self.EscortMenuROEWeaponFree = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Weapon Free", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEWeaponFree(), "Opening fire on targets of opportunity!" )
self.EscortMenuROEWeaponFree = MENU_CLIENT_COMMAND:New( self.EscortClient, "Weapon Free", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEWeaponFree(), "Opening fire on targets of opportunity!" )
end
end
@@ -646,18 +648,18 @@ function ESCORT:MenuEvasion( MenuTextFormat )
if self.EscortGroup:IsAir() then
if not self.EscortMenuEvasion then
-- Reaction to Threats
self.EscortMenuEvasion = MENU_GROUP:New( self.EscortClient:GetGroup(), "Evasion", self.EscortMenu )
self.EscortMenuEvasion = MENU_CLIENT:New( self.EscortClient, "Evasion", self.EscortMenu )
if self.EscortGroup:OptionROTNoReactionPossible() then
self.EscortMenuEvasionNoReaction = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Fight until death", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTNoReaction(), "Fighting until death!" )
self.EscortMenuEvasionNoReaction = MENU_CLIENT_COMMAND:New( self.EscortClient, "Fight until death", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTNoReaction(), "Fighting until death!" )
end
if self.EscortGroup:OptionROTPassiveDefensePossible() then
self.EscortMenuEvasionPassiveDefense = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Use flares, chaff and jammers", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTPassiveDefense(), "Defending using jammers, chaff and flares!" )
self.EscortMenuEvasionPassiveDefense = MENU_CLIENT_COMMAND:New( self.EscortClient, "Use flares, chaff and jammers", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTPassiveDefense(), "Defending using jammers, chaff and flares!" )
end
if self.EscortGroup:OptionROTEvadeFirePossible() then
self.EscortMenuEvasionEvadeFire = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Evade enemy fire", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTEvadeFire(), "Evading on enemy fire!" )
self.EscortMenuEvasionEvadeFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Evade enemy fire", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTEvadeFire(), "Evading on enemy fire!" )
end
if self.EscortGroup:OptionROTVerticalPossible() then
self.EscortMenuOptionEvasionVertical = MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(), "Go below radar and evade fire", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTVertical(), "Evading on enemy fire with vertical manoeuvres!" )
self.EscortMenuOptionEvasionVertical = MENU_CLIENT_COMMAND:New( self.EscortClient, "Go below radar and evade fire", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTVertical(), "Evading on enemy fire with vertical manoeuvres!" )
end
end
end
@@ -674,7 +676,7 @@ function ESCORT:MenuResumeMission()
if not self.EscortMenuResumeMission then
-- Mission Resume Menu Root
self.EscortMenuResumeMission = MENU_GROUP:New( self.EscortClient:GetGroup(), "Resume mission from", self.EscortMenu )
self.EscortMenuResumeMission = MENU_CLIENT:New( self.EscortClient, "Resume mission from", self.EscortMenu )
end
return self
@@ -736,7 +738,7 @@ end
-- @param Functional.Escort#ESCORT self
-- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Client#CLIENT EscortClient
-- @param DCS#Distance Distance
-- @param Dcs.DCSTypes#Distance Distance
function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance )
self:F( { EscortGroup, EscortClient, Distance } )
@@ -845,11 +847,11 @@ function _Resume( EscortGroup )
end
--- @param #ESCORT self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
function ESCORT:_AttackTarget( DetectedItem )
-- @param #number DetectedItemID
function ESCORT:_AttackTarget( DetectedItemID )
local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP
self:F( EscortGroup )
self:E( EscortGroup )
local EscortClient = self.EscortClient
@@ -860,7 +862,7 @@ function ESCORT:_AttackTarget( DetectedItem )
EscortGroup:OptionROTPassiveDefense()
EscortGroup:SetState( EscortGroup, "Escort", self )
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID )
local Tasks = {}
@@ -873,7 +875,7 @@ function ESCORT:_AttackTarget( DetectedItem )
end, Tasks
)
Tasks[#Tasks+1] = EscortGroup:TaskFunction( "_Resume", { "''" } )
Tasks[#Tasks+1] = EscortGroup:TaskFunction( 1, 2, "_Resume", { "''" } )
EscortGroup:SetTask(
EscortGroup:TaskCombo(
@@ -883,7 +885,7 @@ function ESCORT:_AttackTarget( DetectedItem )
else
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID )
local Tasks = {}
@@ -909,9 +911,8 @@ function ESCORT:_AttackTarget( DetectedItem )
end
---
--- @param #ESCORT self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
-- @param #number DetectedItemID
function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItemID )
local EscortGroup = self.EscortGroup
local EscortClient = self.EscortClient
@@ -922,7 +923,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
EscortGroupAttack:OptionROEOpenFire()
EscortGroupAttack:OptionROTVertical()
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID )
local Tasks = {}
@@ -944,7 +945,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
)
else
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID )
local Tasks = {}
@@ -1151,7 +1152,7 @@ function ESCORT:_ReportTargetsScheduler()
end
local DetectedItems = self.Detection:GetDetectedItems()
self:F( DetectedItems )
self:E( DetectedItems )
local DetectedTargets = false
@@ -1160,42 +1161,36 @@ function ESCORT:_ReportTargetsScheduler()
for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do
local ClientEscortTargets = EscortGroupData.Detection
--local EscortUnit = EscortGroupData:GetUnit( 1 )
for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do
self:F( { DetectedItemIndex, DetectedItem } )
for DetectedItemID, DetectedItem in ipairs( DetectedItems ) do
self:E( { DetectedItemID, DetectedItem } )
-- Remove the sub menus of the Attack menu of the Escort for the EscortGroup.
local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, EscortGroupData.EscortGroup, _DATABASE:GetPlayerSettings( self.EscortClient:GetPlayerName() ) )
local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItemID, EscortGroupData )
if ClientEscortGroupName == EscortGroupName then
local DetectedMsg = DetectedItemReportSummary:Text("\n")
DetectedMsgs[#DetectedMsgs+1] = DetectedMsg
self:T( DetectedMsg )
DetectedMsgs[#DetectedMsgs+1] = DetectedItemReportSummary
MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(),
DetectedMsg,
MENU_CLIENT_COMMAND:New( self.EscortClient,
DetectedItemReportSummary,
self.EscortMenuAttackNearbyTargets,
ESCORT._AttackTarget,
self,
DetectedItem
DetectedItemID
)
else
if self.EscortMenuTargetAssistance then
local DetectedMsg = DetectedItemReportSummary:Text("\n")
self:T( DetectedMsg )
local MenuTargetAssistance = MENU_GROUP:New( self.EscortClient:GetGroup(), EscortGroupData.EscortName, self.EscortMenuTargetAssistance )
MENU_GROUP_COMMAND:New( self.EscortClient:GetGroup(),
DetectedMsg,
self:T( DetectedItemReportSummary )
local MenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance )
MENU_CLIENT_COMMAND:New( self.EscortClient,
DetectedItemReportSummary,
MenuTargetAssistance,
ESCORT._AssistTarget,
self,
EscortGroupData.EscortGroup,
DetectedItem
DetectedItemID
)
end
end
@@ -1204,9 +1199,9 @@ function ESCORT:_ReportTargetsScheduler()
end
end
self:F( DetectedMsgs )
self:E( DetectedMsgs )
if DetectedTargets then
self.EscortGroup:MessageToClient( "Reporting detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), 20, self.EscortClient )
self.EscortGroup:MessageToClient( "Detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), 20, self.EscortClient )
else
self.EscortGroup:MessageToClient( "No targets detected.", 10, self.EscortClient )
end
@@ -1326,7 +1321,7 @@ function ESCORT:_ReportTargetsScheduler()
--
-- if ClientEscortGroupName == EscortGroupName then
--
-- MENU_GROUP_COMMAND:New( self.EscortClient,
-- MENU_CLIENT_COMMAND:New( self.EscortClient,
-- EscortTargetMessage,
-- self.EscortMenuAttackNearbyTargets,
-- ESCORT._AttackTarget,
@@ -1337,8 +1332,8 @@ function ESCORT:_ReportTargetsScheduler()
-- 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,
-- local MenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance )
-- MENU_CLIENT_COMMAND:New( self.EscortClient,
-- EscortTargetMessage,
-- MenuTargetAssistance,
-- ESCORT._AssistTarget,
@@ -1377,7 +1372,7 @@ function ESCORT:_ReportTargetsScheduler()
-- 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 } )
-- MENU_CLIENT_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } )
-- end
-- end
--

View File

@@ -2,6 +2,8 @@
--
-- ===
--
-- 1) @{MissileTrainer#MISSILETRAINER} class, extends @{Base#BASE}
-- ===============================================================
-- The @{#MISSILETRAINER} class uses the DCS world messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
-- It suports the following functionality:
@@ -69,14 +71,14 @@
-- ===
--
-- CREDITS
-- ===
-- =======
-- **Stuka (Danny)** Who you can search on the Eagle Dynamics Forums.
-- Working together with Danny has resulted in the MISSILETRAINER class.
-- Danny has shared his ideas and together we made a design.
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
--
-- @module Functional.MissileTrainer
-- @image Missile_Trainer.JPG
-- @module MissileTrainer
-- @author FlightControl
--- The MISSILETRAINER class
@@ -97,39 +99,39 @@ function MISSILETRAINER._Alive( Client, self )
if self.MenusOnOff == true then
Client:Message( "Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).", 15, "Trainer" )
Client.MainMenu = MENU_GROUP:New( Client:GetGroup(), "Missile Trainer", nil ) -- Menu#MENU_GROUP
Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) -- Menu#MENU_CLIENT
Client.MenuMessages = MENU_GROUP:New( Client:GetGroup(), "Messages", Client.MainMenu )
Client.MenuOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } )
Client.MenuOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } )
Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu )
Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } )
Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } )
Client.MenuTracking = MENU_GROUP:New( Client:GetGroup(), "Tracking", Client.MainMenu )
Client.MenuTrackingToAll = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } )
Client.MenuTrackingToTarget = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } )
Client.MenuTrackOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = true } )
Client.MenuTrackOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = false } )
Client.MenuTrackIncrease = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Frequency Increase", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = -1 } )
Client.MenuTrackDecrease = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Frequency Decrease", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = 1 } )
Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu )
Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } )
Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } )
Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = true } )
Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = false } )
Client.MenuTrackIncrease = MENU_CLIENT_COMMAND:New( Client, "Frequency Increase", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = -1 } )
Client.MenuTrackDecrease = MENU_CLIENT_COMMAND:New( Client, "Frequency Decrease", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = 1 } )
Client.MenuAlerts = MENU_GROUP:New( Client:GetGroup(), "Alerts", Client.MainMenu )
Client.MenuAlertsToAll = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } )
Client.MenuAlertsToTarget = MENU_GROUP_COMMAND:New( Client:GetGroup(), "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } )
Client.MenuHitsOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = true } )
Client.MenuHitsOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = false } )
Client.MenuLaunchesOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = true } )
Client.MenuLaunchesOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = false } )
Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu )
Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } )
Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } )
Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = true } )
Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = false } )
Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = true } )
Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = false } )
Client.MenuDetails = MENU_GROUP:New( Client:GetGroup(), "Details", Client.MainMenu )
Client.MenuDetailsDistanceOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = true } )
Client.MenuDetailsDistanceOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = false } )
Client.MenuDetailsBearingOn = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = true } )
Client.MenuDetailsBearingOff = MENU_GROUP_COMMAND:New( Client:GetGroup(), "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = false } )
Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu )
Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = true } )
Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = false } )
Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = true } )
Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = false } )
Client.MenuDistance = MENU_GROUP:New( Client:GetGroup(), "Set distance to plane", Client.MainMenu )
Client.MenuDistance50 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } )
Client.MenuDistance100 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } )
Client.MenuDistance150 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } )
Client.MenuDistance200 = MENU_GROUP_COMMAND:New( Client:GetGroup(), "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } )
Client.MenuDistance = MENU_CLIENT:New( Client, "Set distance to plane", Client.MainMenu )
Client.MenuDistance50 = MENU_CLIENT_COMMAND:New( Client, "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } )
Client.MenuDistance100 = MENU_CLIENT_COMMAND:New( Client, "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } )
Client.MenuDistance150 = MENU_CLIENT_COMMAND:New( Client, "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } )
Client.MenuDistance200 = MENU_CLIENT_COMMAND:New( Client, "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } )
else
if Client.MainMenu then
Client.MainMenu:Remove()
@@ -175,13 +177,13 @@ function MISSILETRAINER:New( Distance, Briefing )
-- for ClientID, Client in pairs( self.DBClients.Database ) do
-- self:F( "ForEach:" .. Client.UnitName )
-- self:E( "ForEach:" .. Client.UnitName )
-- Client:Alive( self._Alive, self )
-- end
--
self.DBClients:ForEachClient(
function( Client )
self:F( "ForEach:" .. Client.UnitName )
self:E( "ForEach:" .. Client.UnitName )
Client:Alive( self._Alive, self )
end
)
@@ -440,7 +442,7 @@ function MISSILETRAINER._MenuMessages( MenuParameters )
if MenuParameters.Distance ~= nil then
self.Distance = MenuParameters.Distance
MESSAGE:New( "Hit detection distance set to " .. ( self.Distance * 1000 ) .. " meters", 15, "Menu" ):ToAll()
MESSAGE:New( "Hit detection distance set to " .. self.Distance * 1000 .. " meters", 15, "Menu" ):ToAll()
end
end

View File

@@ -1,14 +1,13 @@
--- **Functional** -- Limit the MOVEMENT of simulaneous moving ground vehicles.
--
-- ===
-- ====
--
-- Limit the simultaneous movement of Groups within a running Mission.
-- This module is defined to improve the performance in missions, and to bring additional realism for GROUND vehicles.
-- Performance: If in a DCSRTE there are a lot of moving GROUND units, then in a multi player mission, this WILL create lag if
-- the main DCS execution core of your CPU is fully utilized. So, this class will limit the amount of simultaneous moving GROUND units
-- on defined intervals (currently every minute).
-- @module Functional.Movement
-- @image MOOSE.JPG
-- @module Movement
--- the MOVEMENT class
-- @type MOVEMENT

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,15 @@
--- **Functional** -- (R2.0) - Administer the scoring of player achievements, and create a CSV file logging the scoring events for use at team or squadron websites.
--- **Functional** -- **Administer the SCORING of player achievements,
-- and create a CSV file logging the scoring events for use at team or squadron websites.**
--
-- ![Banner Image](..\Presentations\SCORING\Dia1.JPG)
--
-- ===
--
-- Administers the scoring of player achievements,
-- The @{#SCORING} class administers the scoring of player achievements,
-- and creates a CSV file logging the scoring events and results for use at team or squadron websites.
--
-- SCORING automatically calculates the threat level of the objects hit and destroyed by players,
-- which can be @{Wrapper.Unit}, @{Static) and @{Scenery} objects.
-- which can be @{Unit}, @{Static) and @{Scenery} objects.
--
-- Positive score points are granted when enemy or neutral targets are destroyed.
-- Negative score points or penalties are given when a friendly target is hit or destroyed.
@@ -52,7 +55,7 @@
-- Use the radio menu F10 to consult the scores while running the mission.
-- Scores can be reported for your user, or an overall score can be reported of all players currently active in the mission.
--
-- # 1) @{Functional.Scoring#SCORING} class, extends @{Core.Base#BASE}
-- # 1) @{Scoring#SCORING} class, extends @{Base#BASE}
--
-- ## 1.1) Set the destroy score or penalty scale
--
@@ -70,9 +73,9 @@
-- ## 1.2) Define special targets that will give extra scores.
--
-- Special targets can be set that will give extra scores to the players when these are destroyed.
-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s.
-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Unit}s.
-- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s.
-- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s.
-- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Group}s.
--
-- local Scoring = SCORING:New( "Scoring File" )
-- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 )
@@ -89,7 +92,7 @@
-- Define zones of destruction. Any object destroyed within the zone of the given category will give extra points.
-- Use the method @{#SCORING.AddZoneScore}() to add a @{Zone} for additional scoring.
-- Use the method @{#SCORING.RemoveZoneScore}() to remove a @{Zone} for additional scoring.
-- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Core.Zone#ZONE_UNIT},
-- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Zone#ZONE_UNIT},
-- then the zone is a moving zone, and anything destroyed within that @{Zone} will generate points.
-- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone},
-- just large enough around that building.
@@ -99,9 +102,7 @@
-- A mission has goals and achievements. The scoring system provides an API to set additional scores when a goal or achievement event happens.
-- Use the method @{#SCORING.AddGoalScore}() to add a score for a Player at any time in your mission.
--
-- ## 1.5) (Decommissioned) Configure fratricide level.
--
-- **This functionality is decomissioned until the DCS bug concerning Unit:destroy() not being functional in multi player for player units has been fixed by ED**.
-- ## 1.5) Configure fratricide level.
--
-- When a player commits too much damage to friendlies, his penalty score will reach a certain level.
-- Use the method @{#SCORING.SetFratricide}() to define the level when a player gets kicked.
@@ -175,7 +176,7 @@
-- * @{#SCORING.SetMessagesToCoalition}(): Configure to send messages to only those players within the same coalition as the player.
--
--
-- ===
-- ====
--
-- # **API CHANGE HISTORY**
--
@@ -202,8 +203,7 @@
--
-- * **FlightControl**: Concept, Design & Programming.
--
-- @module Functional.Scoring
-- @image Scoring.JPG
-- @module Scoring
--- The Scoring class
@@ -258,7 +258,7 @@ function SCORING:New( GameName )
-- Configure Messages
self:SetMessagesToAll()
self:SetMessagesHit( false )
self:SetMessagesHit( true )
self:SetMessagesDestroy( true )
self:SetMessagesScore( true )
self:SetMessagesZone( true )
@@ -279,22 +279,8 @@ function SCORING:New( GameName )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Hit, self._EventOnHit )
self:HandleEvent( EVENTS.Birth )
--self:HandleEvent( EVENTS.PlayerEnterUnit )
self:HandleEvent( EVENTS.PlayerEnterUnit )
self:HandleEvent( EVENTS.PlayerLeaveUnit )
-- During mission startup, especially for single player,
-- iterate the database for the player that has joined, and add him to the scoring, and set the menu.
-- But this can only be started one second after the mission has started, so i need to schedule this ...
self.ScoringPlayerScan = BASE:ScheduleOnce( 1,
function()
for PlayerName, PlayerUnit in pairs( _DATABASE:GetPlayerUnits() ) do
self:_AddPlayerFromUnit( PlayerUnit )
self:SetScoringMenu( PlayerUnit:GetGroup() )
end
end
)
-- Create the CSV file.
self:OpenCSV( GameName )
@@ -336,11 +322,11 @@ function SCORING:SetScaleDestroyPenalty( Scale )
return self
end
--- Add a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed.
-- Note that if there was already a @{Wrapper.Unit} declared within the scoring with the same name,
-- then the old @{Wrapper.Unit} will be replaced with the new @{Wrapper.Unit}.
--- Add a @{Unit} for additional scoring when the @{Unit} is destroyed.
-- Note that if there was already a @{Unit} declared within the scoring with the same name,
-- then the old @{Unit} will be replaced with the new @{Unit}.
-- @param #SCORING self
-- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given.
-- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddUnitScore( ScoreUnit, Score )
@@ -352,9 +338,9 @@ function SCORING:AddUnitScore( ScoreUnit, Score )
return self
end
--- Removes a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed.
--- Removes a @{Unit} for additional scoring when the @{Unit} is destroyed.
-- @param #SCORING self
-- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given.
-- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given.
-- @return #SCORING
function SCORING:RemoveUnitScore( ScoreUnit )
@@ -395,9 +381,9 @@ function SCORING:RemoveStaticScore( ScoreStatic )
end
--- Specify a special additional score for a @{Wrapper.Group}.
--- Specify a special additional score for a @{Group}.
-- @param #SCORING self
-- @param Wrapper.Group#GROUP ScoreGroup The @{Wrapper.Group} for which each @{Wrapper.Unit} a Score is given.
-- @param Wrapper.Group#GROUP ScoreGroup The @{Group} for which each @{Unit} a Score is given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddScoreGroup( ScoreGroup, Score )
@@ -581,19 +567,6 @@ function SCORING:SetCoalitionChangePenalty( CoalitionChangePenalty )
end
--- Sets the scoring menu.
-- @param #SCORING self
-- @return #SCORING
function SCORING:SetScoringMenu( ScoringGroup )
local Menu = MENU_GROUP:New( ScoringGroup, 'Scoring and Statistics' )
local ReportGroupSummary = MENU_GROUP_COMMAND:New( ScoringGroup, 'Summary report players in group', Menu, SCORING.ReportScoreGroupSummary, self, ScoringGroup )
local ReportGroupDetailed = MENU_GROUP_COMMAND:New( ScoringGroup, 'Detailed report players in group', Menu, SCORING.ReportScoreGroupDetailed, self, ScoringGroup )
local ReportToAllSummary = MENU_GROUP_COMMAND:New( ScoringGroup, 'Summary report all players', Menu, SCORING.ReportScoreAllSummary, self, ScoringGroup )
self:SetState( ScoringGroup, "ScoringMenu", Menu )
return self
end
--- Add a new player entering a Unit.
-- @param #SCORING self
-- @param Wrapper.Unit#UNIT UnitData
@@ -635,15 +608,14 @@ function SCORING:_AddPlayerFromUnit( UnitData )
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.",
MESSAGE.Type.Information
2
):ToAll()
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
end
end
self.Players[PlayerName].UnitName = UnitName
self.Players[PlayerName].UnitCoalition = UnitCoalition
self.Players[PlayerName].UnitCategory = UnitCategory
@@ -652,66 +624,33 @@ function SCORING:_AddPlayerFromUnit( UnitData )
self.Players[PlayerName].ThreatLevel = UnitThreatLevel
self.Players[PlayerName].ThreatType = UnitThreatType
-- TODO: DCS bug concerning Units with skill level client don't get destroyed in multi player. This logic is deactivated until this bug gets fixed.
--[[
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then
if self.Players[PlayerName].PenaltyWarning < 1 then
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
MESSAGE.Type.Information
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
30
):ToAll()
self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1
end
end
if self.Players[PlayerName].Penalty > self.Fratricide then
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
MESSAGE.Type.Information
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
10
):ToAll()
UnitData:GetGroup():Destroy()
end
--]]
end
end
--- Add a goal score for a player.
-- The method takes the Player name for which the Goal score needs to be set.
-- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal.
-- A free text can be given that is shown to the players.
-- The Score can be both positive and negative.
-- @param #SCORING self
-- @param #string PlayerName The name of the Player.
-- @param #string GoalTag The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel).
-- @param #string Text A free text that is shown to the players.
-- @param #number Score The score can be both positive or negative ( Penalty ).
function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score )
self:F( { PlayerName, PlayerName, GoalTag, Text, Score } )
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
if PlayerName then
local PlayerData = self.Players[PlayerName]
PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 }
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
PlayerData.Score = PlayerData.Score + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, nil )
end
end
--- Add a goal score for a player.
-- The method takes the PlayerUnit for which the Goal score needs to be set.
-- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal.
-- A free text can be given that is shown to the players.
-- The Score can be both positive and negative.
-- @param #SCORING self
-- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc.
-- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc.
-- @param #string GoalTag The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel).
-- @param #string Text A free text that is shown to the players.
-- @param #number Score The score can be both positive or negative ( Penalty ).
@@ -719,7 +658,7 @@ function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score )
local PlayerName = PlayerUnit:GetPlayerName()
self:F( { PlayerUnit.UnitName, PlayerName, GoalTag, Text, Score } )
self:E( { PlayerUnit.UnitName, PlayerName, GoalTag, Text, Score } )
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
if PlayerName then
@@ -729,7 +668,7 @@ function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score )
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
PlayerData.Score = PlayerData.Score + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. Text, MESSAGE.Type.Information ):ToAll()
MESSAGE:New( self.DisplayMessagePrefix .. Text, 30 ):ToAll()
self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, PlayerUnit:GetName() )
end
@@ -747,7 +686,7 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score )
local PlayerName = PlayerUnit:GetPlayerName()
local MissionName = Mission:GetName()
self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } )
self:E( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } )
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
if PlayerName then
@@ -765,45 +704,12 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score )
PlayerData.Score = self.Players[PlayerName].Score + Score
PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. Mission:GetText() .. " : " .. Text .. " Score: " .. Score, MESSAGE.Type.Information ):ToAll()
MESSAGE:New( self.DisplayMessagePrefix .. MissionName .. " : " .. Text .. " Score: " .. Score, 30 ):ToAll()
self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() )
end
end
--- Registers Scores the players completing a Mission Task.
-- @param #SCORING self
-- @param Tasking.Mission#MISSION Mission
-- @param #string PlayerName
-- @param #string Text
-- @param #number Score
function SCORING:_AddMissionGoalScore( Mission, PlayerName, Text, Score )
local MissionName = Mission:GetName()
self:F( { Mission:GetName(), PlayerName, Text, Score } )
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
if PlayerName then
local PlayerData = self.Players[PlayerName]
if not PlayerData.Mission[MissionName] then
PlayerData.Mission[MissionName] = {}
PlayerData.Mission[MissionName].ScoreTask = 0
PlayerData.Mission[MissionName].ScoreMission = 0
end
self:T( PlayerName )
self:T( PlayerData.Mission[MissionName] )
PlayerData.Score = self.Players[PlayerName].Score + Score
PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:NewType( string.format( "%s%s: %s! Player %s receives %d score!", self.DisplayMessagePrefix, Mission:GetText(), Text, PlayerName, Score ), MESSAGE.Type.Information ):ToAll()
self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
end
--- Registers Mission Scores for possible multiple players that contributed in the Mission.
-- @param #SCORING self
@@ -815,20 +721,20 @@ function SCORING:_AddMissionScore( Mission, Text, Score )
local MissionName = Mission:GetName()
self:F( { Mission, Text, Score } )
self:F( self.Players )
self:E( { Mission, Text, Score } )
self:E( self.Players )
for PlayerName, PlayerData in pairs( self.Players ) do
self:F( PlayerData )
self:E( PlayerData )
if PlayerData.Mission[MissionName] then
PlayerData.Score = PlayerData.Score + Score
PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in " .. Mission:GetText() .. ". " ..
MESSAGE:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " ..
Score .. " mission score!",
MESSAGE.Type.Information ):ToAll()
60 ):ToAll()
self:ScoreCSV( PlayerName, "", "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
@@ -836,30 +742,17 @@ function SCORING:_AddMissionScore( Mission, Text, Score )
end
--- Handles the OnPlayerEnterUnit event for the scoring.
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
--function SCORING:OnEventPlayerEnterUnit( Event )
-- if Event.IniUnit then
-- self:_AddPlayerFromUnit( Event.IniUnit )
-- self:SetScoringMenu( Event.IniGroup )
-- end
--end
--- Handles the OnBirth event for the scoring.
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
function SCORING:OnEventBirth( Event )
function SCORING:OnEventPlayerEnterUnit( Event )
if Event.IniUnit then
if Event.IniObjectCategory == 1 then
local PlayerName = Event.IniUnit:GetPlayerName()
if PlayerName then
self:_AddPlayerFromUnit( Event.IniUnit )
self:SetScoringMenu( Event.IniGroup )
end
end
self:_AddPlayerFromUnit( Event.IniUnit )
local Menu = MENU_GROUP:New( Event.IniGroup, 'Scoring' )
local ReportGroupSummary = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Summary report players in group', Menu, SCORING.ReportScoreGroupSummary, self, Event.IniGroup )
local ReportGroupDetailed = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Detailed report players in group', Menu, SCORING.ReportScoreGroupDetailed, self, Event.IniGroup )
local ReportToAllSummary = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Summary report all players', Menu, SCORING.ReportScoreAllSummary, self, Event.IniGroup )
self:SetState( Event.IniUnit, "ScoringMenu", Menu )
end
end
@@ -868,7 +761,7 @@ end
-- @param Core.Event#EVENTDATA Event
function SCORING:OnEventPlayerLeaveUnit( Event )
if Event.IniUnit then
local Menu = self:GetState( Event.IniUnit:GetGroup(), "ScoringMenu" ) -- Core.Menu#MENU_GROUP
local Menu = self:GetState( Event.IniUnit, "ScoringMenu" ) -- Core.Menu#MENU_GROUP
if Menu then
-- TODO: Check if this fixes #281.
--Menu:Remove()
@@ -1009,19 +902,19 @@ function SCORING:_EventOnHit( Event )
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " ..
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " ..
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1033,19 +926,19 @@ function SCORING:_EventOnHit( Event )
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " ..
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " ..
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1054,8 +947,8 @@ function SCORING:_EventOnHit( Event )
end
else -- A scenery object was hit.
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.",
MESSAGE.Type.Update
:New( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.",
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1115,10 +1008,10 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1128,10 +1021,10 @@ function SCORING:_EventOnHit( Event )
PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " ..
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Score: +" .. PlayerHit.Score .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1139,8 +1032,8 @@ function SCORING:_EventOnHit( Event )
end
else -- A scenery object was hit.
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit scenery object.",
MESSAGE.Type.Update
:New( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit scenery object.",
2
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1230,7 +1123,7 @@ function SCORING:_EventOnDeadOrCrash( Event )
local ThreatTypeTarget = TargetThreatType
local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1
local ThreatPenalty = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyPenalty / 10 )
self:F( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
self:E( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
Player.Penalty = Player.Penalty + ThreatPenalty
TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty
@@ -1238,19 +1131,19 @@ function SCORING:_EventOnDeadOrCrash( Event )
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " ..
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
15
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " ..
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
15
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@@ -1265,26 +1158,26 @@ function SCORING:_EventOnDeadOrCrash( Event )
local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1
local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyScore / 10 )
self:F( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
self:E( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
Player.Score = Player.Score + ThreatScore
TargetDestroy.Score = TargetDestroy.Score + ThreatScore
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " ..
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
15
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " ..
:New( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " ..
TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
15
)
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@@ -1298,9 +1191,9 @@ function SCORING:_EventOnDeadOrCrash( Event )
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
:New( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
15
)
:ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() )
@@ -1310,17 +1203,17 @@ function SCORING:_EventOnDeadOrCrash( Event )
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
self:F( { ScoringZone = ScoreZoneData } )
self:E( { ScoringZone = ScoreZoneData } )
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
:New( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
15 )
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
@@ -1332,17 +1225,17 @@ function SCORING:_EventOnDeadOrCrash( Event )
else
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do
self:F( { ScoringZone = ScoreZoneData } )
self:E( { ScoringZone = ScoreZoneData } )
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
:New( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
15
)
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
@@ -1447,7 +1340,7 @@ function SCORING:ReportDetailedPlayerDestroys( PlayerName )
local PenaltyDestroy = 0
for UnitName, UnitData in pairs( PlayerData.Destroy[CategoryID] ) do
self:F( { UnitData = UnitData } )
self:E( { UnitData = UnitData } )
if UnitData ~= {} then
Score = Score + UnitData.Score
ScoreDestroy = ScoreDestroy + UnitData.ScoreDestroy
@@ -1601,23 +1494,23 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup )
local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName )
ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits
self:F( { ReportHits, ScoreHits, PenaltyHits } )
self:E( { ReportHits, ScoreHits, PenaltyHits } )
local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName )
ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys
self:F( { ReportDestroys, ScoreDestroys, PenaltyDestroys } )
self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } )
local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName )
ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges
self:F( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } )
self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } )
local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName )
ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals
self:F( { ReportGoals, ScoreGoals, PenaltyGoals } )
self:E( { ReportGoals, ScoreGoals, PenaltyGoals } )
local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName )
ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions
self:F( { ReportMissions, ScoreMissions, PenaltyMissions } )
self:E( { ReportMissions, ScoreMissions, PenaltyMissions } )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions
@@ -1629,7 +1522,7 @@ function SCORING:ReportScoreGroupSummary( PlayerGroup )
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Detailed ):ToGroup( PlayerGroup )
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
end
end
@@ -1653,23 +1546,23 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup )
local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName )
ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits
self:F( { ReportHits, ScoreHits, PenaltyHits } )
self:E( { ReportHits, ScoreHits, PenaltyHits } )
local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName )
ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys
self:F( { ReportDestroys, ScoreDestroys, PenaltyDestroys } )
self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } )
local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName )
ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges
self:F( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } )
self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } )
local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName )
ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals
self:F( { ReportGoals, ScoreGoals, PenaltyGoals } )
self:E( { ReportGoals, ScoreGoals, PenaltyGoals } )
local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName )
ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions
self:F( { ReportMissions, ScoreMissions, PenaltyMissions } )
self:E( { ReportMissions, ScoreMissions, PenaltyMissions } )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions
@@ -1686,7 +1579,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup )
ReportGoals,
ReportMissions
)
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Detailed ):ToGroup( PlayerGroup )
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
end
end
@@ -1699,33 +1592,31 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
local PlayerMessage = ""
self:T( { "Summary Score Report of All Players", Players = self.Players } )
self:T( "Report Score All Players" )
for PlayerName, PlayerData in pairs( self.Players ) do
self:T( { PlayerName = PlayerName, PlayerGroup = PlayerGroup } )
if PlayerName then
local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName )
ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits
self:F( { ReportHits, ScoreHits, PenaltyHits } )
self:E( { ReportHits, ScoreHits, PenaltyHits } )
local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName )
ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys
self:F( { ReportDestroys, ScoreDestroys, PenaltyDestroys } )
self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } )
local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName )
ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges
self:F( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } )
self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } )
local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName )
ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals
self:F( { ReportGoals, ScoreGoals, PenaltyGoals } )
self:E( { ReportGoals, ScoreGoals, PenaltyGoals } )
local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName )
ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions
self:F( { ReportMissions, ScoreMissions, PenaltyMissions } )
self:E( { ReportMissions, ScoreMissions, PenaltyMissions } )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions
@@ -1737,7 +1628,7 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup )
end
end
@@ -1785,7 +1676,7 @@ function SCORING:OpenCSV( ScoringCSV )
error( "A string containing the CSV file name must be given." )
end
else
self:F( "The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used..." )
self:E( "The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used..." )
end
return self
end

View File

@@ -1,9 +1,8 @@
--- **Functional** -- Provides defensive behaviour to a set of SAM sites within a running Mission.
--
-- ===
-- ====
--
-- @module Functional.Sead
-- @image SEAD.JPG
-- @module Sead
--- The SEAD class
-- @type SEAD

File diff suppressed because it is too large Load Diff

View File

@@ -1,758 +0,0 @@
--- **Functional** -- (R2.3) Models the process to zone guarding and capturing.
--
-- ===
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAZ - Capture Zones)
--
-- - CAZ-000 - Capture Zone: Demonstrates the basic concept of capturing a zone.
--
-- ===
--
-- ### [YouTube Playlist](https://www.youtube.com/watch?v=0m6K6Yxa-os&list=PL7ZUrU4zZUl0qqJsfa8DPvZWDY-OyDumE)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions: **Millertime** - Concept
--
-- ===
--
-- @module Functional.ZoneCaptureCoalition
-- @image Capture_Zones.JPG
do -- ZONE_CAPTURE_COALITION
--- @type ZONE_CAPTURE_COALITION
-- @extends Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION
--- Models the process to capture a Zone for a Coalition, which is guarded by another Coalition.
-- This is a powerful concept that allows to create very dynamic missions based on the different state transitions of various zones.
--
-- ---
--
-- ![Banner Image](..\Presentations\ZONE_CAPTURE_COALITION\Dia1.JPG)
--
-- ---
--
-- # 0. Player Experience
--
-- ![States](..\Presentations\ZONE_CAPTURE_COALITION\Dia3.JPG)
--
-- The above models the possible state transitions between the **Guarded**, **Attacked**, **Empty** and **Captured** states.
-- A zone has an __owning coalition__, that means that at a specific point in time, a zone can be owned by the red or blue coalition.
--
-- The Zone can be in the state **Guarded** by the __owning coalition__, which is the coalition that initially occupies the zone with units of its coalition.
-- Once units of an other coalition are entering the Zone, the state will change to **Attacked**. As long as these units remain in the zone, the state keeps set to Attacked.
-- When all units are destroyed in the Zone, the state will change to **Empty**, which expresses that the Zone is empty, and can be captured.
-- When units of the other coalition are in the Zone, and no other units of the owning coalition is in the Zone, the Zone is captured, and its state will change to **Captured**.
--
-- The zone needs to be monitored regularly for the presence of units to interprete the correct state transition required.
-- This monitoring process MUST be started using the @{#ZONE_CAPTURE_COALITION.Start}() method.
-- Otherwise no monitoring will be active and the zone will stay in the current state forever.
-- See further in chapter 3.3 for more information about this.
--
-- ## 1. ZONE\_CAPTURE\_COALITION constructor
--
-- * @{#ZONE_CAPTURE_COALITION.New}(): Creates a new ZONE\_CAPTURE\_COALITION object.
--
-- In order to use ZONE\_CAPTURE\_COALITION, you need to:
--
-- - Create a @{Zone} object from one of the ZONE\_ classes. Note that ZONE\_POLYGON\_ classes are not yet functional. The only functional ZONE\_ classses are those derived from a ZONE\_RADIUS.
--
-- ![New](..\Presentations\ZONE_CAPTURE_COALITION\Dia5.JPG)
--
-- Ensure that during the life cycle of the ZONE\_CAPTURE\_COALITION object, the object keeps alive.
-- It is best to declare the object globally within your script.
--
-- ## 2. ZONE\_CAPTURE\_COALITION is a finite state machine (FSM).
--
-- ![States](..\Presentations\ZONE_CAPTURE_COALITION\Dia4.JPG)
--
-- ### 2.1 ZONE\_CAPTURE\_COALITION States
--
-- * **Captured**: The Zone has been captured by an other coalition.
-- * **Attacked**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
-- * **Guarded**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
-- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
--
-- ### 2.2 ZONE\_CAPTURE\_COALITION Events
--
-- * **Capture**: The Zone has been captured by an other coalition.
-- * **Attack**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
-- * **Guard**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
-- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
--
-- ## 3. "Script It"
--
-- ZONE\_CAPTURE\_COALITION allows to take action on the various state transitions and add your custom code and logic.
--
-- ### 3.1. Take action using state- and event handlers.
--
-- ![States](..\Presentations\ZONE_CAPTURE_COALITION\Dia6.JPG)
--
-- The most important to understand is how states and events can be tailored.
-- Carefully study the diagram and the explanations.
--
-- **State Handlers** capture the moment:
--
-- - On Leave from the old state. Return false to cancel the transition.
-- - On Enter to the new state.
--
-- **Event Handlers** capture the moment:
--
-- - On Before the event is triggered. Return false to cancel the transition.
-- - On After the event is triggered.
--
--
--
-- ![States](..\Presentations\ZONE_CAPTURE_COALITION\Dia7.JPG)
--
-- Each handler can receive optionally 3 parameters:
--
-- - **From**: A string containing the From State.
-- - **Event**: A string containing the Event.
-- - **To**: A string containing the To State.
--
-- The mission designer can use these values to alter the logic.
-- For example:
--
-- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
-- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To )
-- if From ~= "Empty" then
-- -- Display a message
-- end
-- end
--
-- This code checks that when the __Guarded__ state has been reached, that if the **From** state was __Empty__, then display a message.
--
-- ### 3.2. Example Event Handler.
--
-- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
-- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To )
-- if From ~= To then
-- local Coalition = self:GetCoalition()
-- self:E( { Coalition = Coalition } )
-- if Coalition == coalition.side.BLUE then
-- ZoneCaptureCoalition:Smoke( SMOKECOLOR.Blue )
-- US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- else
-- ZoneCaptureCoalition:Smoke( SMOKECOLOR.Red )
-- RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- US_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- end
-- end
-- end
--
-- ### 3.3. Stop and Start the zone monitoring process.
--
-- At regular intervals, the state of the zone needs to be monitored.
-- The zone needs to be scanned for the presence of units within the zone boundaries.
-- Depending on the owning coalition of the zone and the presence of units (of the owning and/or other coalition(s)), the zone will transition to another state.
--
-- However, ... this scanning process is rather CPU intensive. Imagine you have 10 of these capture zone objects setup within your mission.
-- That would mean that your mission would check 10 capture zones simultaneously, each checking for the presence of units.
-- It would be highly **CPU inefficient**, as some of these zones are not required to be monitored (yet).
--
-- Therefore, the mission designer is given 2 methods that allow to take control of the CPU utilization efficiency:
--
-- - @{#ZONE_CAPTURE_COALITION.Start()}(): This starts the monitoring process.
-- - @{#ZONE_CAPTURE_COALITION.Stop()}(): This stops the monitoring process.
--
-- ### IMPORTANT
--
-- **Each capture zone object must have the monitoring process started specifically.
-- The monitoring process is NOT started by default!!!**
--
--
-- ## 4. Full Example
--
-- The following annotated code shows a real example of how ZONE\_CAPTURE\_COALITION can be applied.
--
-- The concept is simple.
--
-- The USA (US), blue coalition, needs to capture the Russian (RU), red coalition, zone, which is near groom lake.
--
-- A capture zone has been setup that guards the presence of the troops.
-- Troops are guarded by red forces. Blue is required to destroy the red forces and capture the zones.
--
-- At first, we setup the Command Centers
--
-- do
--
-- RU_CC = COMMANDCENTER:New( GROUP:FindByName( "REDHQ" ), "Russia HQ" )
-- US_CC = COMMANDCENTER:New( GROUP:FindByName( "BLUEHQ" ), "USA HQ" )
--
-- end
--
-- Next, we define the mission, and add some scoring to it.
--
-- do -- Missions
--
-- US_Mission_EchoBay = MISSION:New( US_CC, "Echo Bay", "Primary",
-- "Welcome trainee. The airport Groom Lake in Echo Bay needs to be captured.\n" ..
-- "There are five random capture zones located at the airbase.\n" ..
-- "Move to one of the capture zones, destroy the fuel tanks in the capture zone, " ..
-- "and occupy each capture zone with a platoon.\n " ..
-- "Your orders are to hold position until all capture zones are taken.\n" ..
-- "Use the map (F10) for a clear indication of the location of each capture zone.\n" ..
-- "Note that heavy resistance can be expected at the airbase!\n" ..
-- "Mission 'Echo Bay' is complete when all five capture zones are taken, and held for at least 5 minutes!"
-- , coalition.side.RED )
--
-- US_Mission_EchoBay:Start()
--
-- end
--
--
-- Now the real work starts.
-- We define a **CaptureZone** object, which is a ZONE object.
-- Within the mission, a trigger zone is created with the name __CaptureZone__, with the defined radius within the mission editor.
--
-- CaptureZone = ZONE:New( "CaptureZone" )
--
-- Next, we define the **ZoneCaptureCoalition** object, as explained above.
--
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( CaptureZone, coalition.side.RED )
--
-- Of course, we want to let the **ZoneCaptureCoalition** object do something when the state transitions.
-- Do accomodate this, it is very simple, as explained above.
-- We use **Event Handlers** to tailor the logic.
--
-- Here we place an Event Handler at the Guarded event. So when the **Guarded** event is triggered, then this method is called!
-- With the variables **From**, **Event**, **To**. Each of these variables containing a string.
--
-- We check if the previous state wasn't Guarded also.
-- If not, we retrieve the owning Coalition of the **ZoneCaptureCoalition**, using `self:GetCoalition()`.
-- So **Coalition** will contain the current owning coalition of the zone.
--
-- Depending on the zone ownership, different messages are sent.
-- Note the methods `ZoneCaptureCoalition:GetZoneName()`.
--
-- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self
-- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To )
-- if From ~= To then
-- local Coalition = self:GetCoalition()
-- self:E( { Coalition = Coalition } )
-- if Coalition == coalition.side.BLUE then
-- ZoneCaptureCoalition:Smoke( SMOKECOLOR.Blue )
-- US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- else
-- ZoneCaptureCoalition:Smoke( SMOKECOLOR.Red )
-- RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- US_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- end
-- end
-- end
--
-- As you can see, not a rocket science.
-- 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
-- function ZoneCaptureCoalition:OnEnterEmpty()
-- self:Smoke( SMOKECOLOR.Green )
-- US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- RU_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- end
--
-- 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
-- function ZoneCaptureCoalition:OnEnterAttacked()
-- ZoneCaptureCoalition:Smoke( SMOKECOLOR.White )
-- local Coalition = self:GetCoalition()
-- self:E({Coalition = Coalition})
-- if Coalition == coalition.side.BLUE then
-- US_CC:MessageTypeToCoalition( string.format( "%s is under attack by Russia", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- RU_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- else
-- RU_CC:MessageTypeToCoalition( string.format( "%s is under attack by the USA", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- US_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- end
-- end
--
-- 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
-- function ZoneCaptureCoalition:OnEnterCaptured()
-- local Coalition = self:GetCoalition()
-- self:E({Coalition = Coalition})
-- if Coalition == coalition.side.BLUE then
-- RU_CC:MessageTypeToCoalition( string.format( "%s is captured by the USA, we lost it!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- US_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- else
-- US_CC:MessageTypeToCoalition( string.format( "%s is captured by Russia, we lost it!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- RU_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- end
--
-- self:__Guard( 30 )
-- end
--
-- And this call is the most important of all!
-- In the context of the mission, we need to start the zone capture monitoring process.
-- Or nothing will be monitored and the zone won't change states.
-- We start the monitoring after 5 seconds, and will repeat every 30 seconds a check.
--
-- ZoneCaptureCoalition:Start( 5, 30 )
--
--
-- @field #ZONE_CAPTURE_COALITION
ZONE_CAPTURE_COALITION = {
ClassName = "ZONE_CAPTURE_COALITION",
}
--- @field #table ZONE_CAPTURE_COALITION.States
ZONE_CAPTURE_COALITION.States = {}
--- ZONE_CAPTURE_COALITION Constructor.
-- @param #ZONE_CAPTURE_COALITION self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
-- @return #ZONE_CAPTURE_COALITION
-- @usage
--
-- AttackZone = ZONE:New( "AttackZone" )
--
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( AttackZone, coalition.side.RED ) -- Create a new ZONE_CAPTURE_COALITION object of zone AttackZone with ownership RED coalition.
-- ZoneCaptureCoalition:__Guard( 1 ) -- Start the Guarding of the AttackZone.
--
function ZONE_CAPTURE_COALITION:New( Zone, Coalition )
local self = BASE:Inherit( self, ZONE_GOAL_COALITION:New( Zone, Coalition ) ) -- #ZONE_CAPTURE_COALITION
self:F( { Zone = Zone, Coalition = Coalition } )
do
--- Captured State Handler OnLeave for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnLeaveCaptured
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Captured State Handler OnEnter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnEnterCaptured
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Attacked State Handler OnLeave for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnLeaveAttacked
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Attacked State Handler OnEnter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnEnterAttacked
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Guarded State Handler OnLeave for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnLeaveGuarded
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Guarded State Handler OnEnter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnEnterGuarded
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Empty State Handler OnLeave for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnLeaveEmpty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Empty State Handler OnEnter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnEnterEmpty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
end
self:AddTransition( "*", "Guard", "Guarded" )
--- Guard Handler OnBefore for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnBeforeGuard
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Guard Handler OnAfter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnAfterGuard
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Guard Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] Guard
-- @param #ZONE_CAPTURE_COALITION self
--- Guard Asynchronous Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] __Guard
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
self:AddTransition( "*", "Empty", "Empty" )
--- Empty Handler OnBefore for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnBeforeEmpty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Empty Handler OnAfter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnAfterEmpty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Empty Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] Empty
-- @param #ZONE_CAPTURE_COALITION self
--- Empty Asynchronous Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] __Empty
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
self:AddTransition( { "Guarded", "Empty" }, "Attack", "Attacked" )
--- Attack Handler OnBefore for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnBeforeAttack
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Attack Handler OnAfter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnAfterAttack
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Attack Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] Attack
-- @param #ZONE_CAPTURE_COALITION self
--- Attack Asynchronous Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] __Attack
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
self:AddTransition( { "Guarded", "Attacked", "Empty" }, "Capture", "Captured" )
--- Capture Handler OnBefore for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnBeforeCapture
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Capture Handler OnAfter for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] OnAfterCapture
-- @param #ZONE_CAPTURE_COALITION self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Capture Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] Capture
-- @param #ZONE_CAPTURE_COALITION self
--- Capture Asynchronous Trigger for ZONE\_CAPTURE\_COALITION
-- @function [parent=#ZONE_CAPTURE_COALITION] __Capture
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay
return self
end
--- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterCaptured()
self:GetParent( self, ZONE_CAPTURE_COALITION ).onenterCaptured( self )
self.Goal:Achieved()
end
function ZONE_CAPTURE_COALITION:IsGuarded()
local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition )
self:F( { IsGuarded = IsGuarded } )
return IsGuarded
end
function ZONE_CAPTURE_COALITION:IsEmpty()
local IsEmpty = self.Zone:IsNoneInZone()
self:F( { IsEmpty = IsEmpty } )
return IsEmpty
end
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Mark.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:Mark()
local Coord = self.Zone:GetCoordinate()
local ZoneName = self:GetZoneName()
local State = self:GetState()
if self.MarkRed and self.MarkBlue then
self:F( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
Coord:RemoveMark( self.MarkRed )
Coord:RemoveMark( self.MarkBlue )
end
if self.Coalition == coalition.side.BLUE then
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
else
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
end
end
--- Bound.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onenterGuarded()
--self:GetParent( self ):onenterGuarded()
if self.Coalition == coalition.side.BLUE then
--elf.ProtectZone:BoundZone( 12, country.id.USA )
else
--self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
end
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterCaptured()
--self:GetParent( self ):onenterCaptured()
local NewCoalition = self.Zone:GetScannedCoalition()
self:F( { NewCoalition = NewCoalition } )
self:SetCoalition( NewCoalition )
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterEmpty()
--self:GetParent( self ):onenterEmpty()
self:Mark()
end
function ZONE_CAPTURE_COALITION:onenterAttacked()
--self:GetParent( self ):onenterAttacked()
self:Mark()
end
--- When started, check the Coalition status.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:onafterGuard()
--self:F({BASE:GetParent( self )})
--BASE:GetParent( self ).onafterGuard( self )
if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
end
end
function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Check status Coalition ownership.
-- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:StatusZone()
local State = self:GetState()
self:F( { State = self:GetState() } )
self:GetParent( self, ZONE_CAPTURE_COALITION ).StatusZone( self )
if State ~= "Guarded" and self:IsGuarded() then
self:Guard()
end
if State ~= "Empty" and self:IsEmpty() then
self:Empty()
end
if State ~= "Attacked" and self:IsAttacked() then
self:Attack()
end
if State ~= "Captured" and self:IsCaptured() then
self:Capture()
end
end
--- Starts the zone capturing monitoring process.
-- This process can be CPU intensive, ensure that you specify reasonable time intervals for the monitoring process.
-- Note that the monitoring process is NOT started automatically during the `:New()` constructor.
-- It is advised that the zone monitoring process is only started when the monitoring is of relevance in context of the current mission goals.
-- When the zone is of no relevance, it is advised NOT to start the monitoring process, or to stop the monitoring process to save CPU resources.
-- Therefore, the mission designer will need to use the `:Start()` method within his script to start the monitoring process specifically.
-- @param #ZONE_CAPTURE_COALITION self
-- @param #number StartInterval (optional) Specifies the start time interval in seconds when the zone state will be checked for the first time.
-- @param #number RepeatInterval (optional) Specifies the repeat time interval in seconds when the zone state will be checked repeatedly.
-- @usage
--
-- -- Setup the zone.
-- CaptureZone = ZONE:New( "CaptureZone" )
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( CaptureZone, coalition.side.RED )
--
-- -- This starts the monitoring process within 15 seconds, repeating every 15 seconds.
-- ZoneCaptureCoalition:Start()
--
-- -- This starts the monitoring process immediately, but repeats every 30 seconds.
-- ZoneCaptureCoalition:Start( 0, 30 )
--
function ZONE_CAPTURE_COALITION:Start( StartInterval, RepeatInterval )
StartInterval = StartInterval or 15
RepeatInterval = RepeatInterval or 15
if self.ScheduleStatusZone then
self:ScheduleStop( self.ScheduleStatusZone )
end
self.ScheduleStatusZone = self:ScheduleRepeat( StartInterval, RepeatInterval, 0.1, nil, self.StatusZone, self )
end
--- Stops the zone capturing monitoring process.
-- When the zone capturing monitor process is stopped, there won't be any changes anymore in the state and the owning coalition of the zone.
-- This method becomes really useful when the zone is of no relevance anymore within a long lasting mission.
-- In this case, it is advised to stop the monitoring process, not to consume unnecessary the CPU intensive scanning of units presence within the zone.
-- @param #ZONE_CAPTURE_COALITION self
-- @usage
-- -- Setup the zone.
-- CaptureZone = ZONE:New( "CaptureZone" )
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( CaptureZone, coalition.side.RED )
--
-- -- This starts the monitoring process within 15 seconds, repeating every 15 seconds.
-- ZoneCaptureCoalition:Start()
--
-- -- When the zone capturing is of no relevance anymore, stop the monitoring!
-- ZoneCaptureCoalition:Stop()
--
-- @usage
-- -- For example, one could stop the monitoring when the zone was captured!
-- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self
-- function ZoneCaptureCoalition:OnEnterCaptured()
-- local Coalition = self:GetCoalition()
-- self:E({Coalition = Coalition})
-- if Coalition == coalition.side.BLUE then
-- RU_CC:MessageTypeToCoalition( string.format( "%s is captured by the USA, we lost it!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- US_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- else
-- US_CC:MessageTypeToCoalition( string.format( "%s is captured by Russia, we lost it!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- RU_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information )
-- end
--
-- self:AddScore( "Captured", "Zone captured: Extra points granted.", 200 )
--
-- self:Stop()
-- end
--
function ZONE_CAPTURE_COALITION:Stop()
if self.ScheduleStatusZone then
self:ScheduleStop( self.ScheduleStatusZone )
end
end
end

View File

@@ -1,176 +0,0 @@
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone.
--
-- ===
--
-- ZONE_GOAL models processes that have a Goal with a defined achievement involving a Zone.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module Functional.ZoneGoal
-- @image MOOSE.JPG
do -- Zone
--- @type ZONE_GOAL
-- @extends Core.Fsm#FSM
-- Models processes that have a Goal with a defined achievement involving a Zone.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. ZONE_GOAL constructor
--
-- * @{#ZONE_GOAL.New}(): Creates a new ZONE_GOAL object.
--
-- ## 2. ZONE_GOAL is a finite state machine (FSM).
--
-- ### 2.1 ZONE_GOAL States
--
-- * None: Initial State
--
-- ### 2.2 ZONE_GOAL Events
--
-- * DestroyedUnit: A @{Wrapper.Unit} is destroyed in the Zone. The event will only get triggered if the method @{#ZONE_GOAL.MonitorDestroyedUnits}() is used.
--
-- @field #ZONE_GOAL
ZONE_GOAL = {
ClassName = "ZONE_GOAL",
}
--- ZONE_GOAL Constructor.
-- @param #ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE Zone A @{Zone} object with the goal to be achieved.
-- @return #ZONE_GOAL
function ZONE_GOAL:New( Zone )
local self = BASE:Inherit( self, FSM:New() ) -- #ZONE_GOAL
self:F( { Zone = Zone } )
self.Zone = Zone -- Core.Zone#ZONE_BASE
self.Goal = GOAL:New()
self.SmokeTime = nil
self:AddTransition( "*", "DestroyedUnit", "*" )
--- DestroyedUnit Handler OnAfter for ZONE_GOAL
-- @function [parent=#ZONE_GOAL] OnAfterDestroyedUnit
-- @param #ZONE_GOAL self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT DestroyedUnit The destroyed unit.
-- @param #string PlayerName The name of the player.
return self
end
--- Get the Zone
-- @param #ZONE_GOAL self
-- @return Core.Zone#ZONE_BASE
function ZONE_GOAL:GetZone()
return self.Zone
end
--- Get the name of the ProtectZone
-- @param #ZONE_GOAL self
-- @return #string
function ZONE_GOAL:GetZoneName()
return self.Zone:GetName()
end
--- Smoke the center of theh zone.
-- @param #ZONE_GOAL self
-- @param #SMOKECOLOR.Color SmokeColor
function ZONE_GOAL:Smoke( SmokeColor )
self:F( { SmokeColor = SmokeColor} )
self.SmokeColor = SmokeColor
end
--- Flare the center of the zone.
-- @param #ZONE_GOAL self
-- @param #SMOKECOLOR.Color FlareColor
function ZONE_GOAL:Flare( FlareColor )
self.Zone:FlareZone( FlareColor, math.random( 1, 360 ) )
end
--- When started, check the Smoke and the Zone status.
-- @param #ZONE_GOAL self
function ZONE_GOAL:onafterGuard()
--self:GetParent( self ):onafterStart()
self:F("Guard")
--self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
end
end
--- Check status Smoke.
-- @param #ZONE_GOAL self
function ZONE_GOAL:StatusSmoke()
self:F({self.SmokeTime, self.SmokeColor})
local CurrentTime = timer.getTime()
if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
if self.SmokeColor then
self.Zone:GetCoordinate():Smoke( self.SmokeColor )
--self.SmokeColor = nil
self.SmokeTime = CurrentTime
end
end
end
--- @param #ZONE_GOAL self
-- @param Core.Event#EVENTDATA EventData
function ZONE_GOAL:__Destroyed( EventData )
self:F( { "EventDead", EventData } )
self:F( { EventData.IniUnit } )
local Vec3 = EventData.IniDCSUnit:getPosition().p
self:F( { Vec3 = Vec3 } )
local ZoneGoal = self:GetZone()
self:F({ZoneGoal})
if EventData.IniDCSUnit then
if ZoneGoal:IsVec3InZone(Vec3) then
local PlayerHits = _DATABASE.HITS[EventData.IniUnitName]
if PlayerHits then
for PlayerName, PlayerHit in pairs( PlayerHits.Players or {} ) do
self.Goal:AddPlayerContribution( PlayerName )
self:DestroyedUnit( EventData.IniUnitName, PlayerName )
end
end
end
end
end
--- Activate the event UnitDestroyed to be fired when a unit is destroyed in the zone.
-- @param #ZONE_GOAL self
function ZONE_GOAL:MonitorDestroyedUnits()
self:HandleEvent( EVENTS.Dead, self.__Destroyed )
self:HandleEvent( EVENTS.Crash, self.__Destroyed )
end
end

View File

@@ -1,451 +0,0 @@
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone and Cargo.
--
-- ===
--
-- ZONE_GOAL_CARGO models processes that have a Goal with a defined achievement involving a Zone and Cargo.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module Functional.ZoneGoalCargo
-- @image MOOSE.JPG
do -- ZoneGoal
--- @type ZONE_GOAL_CARGO
-- @extends Functional.ZoneGoal#ZONE_GOAL
--- Models processes that have a Goal with a defined achievement involving a Zone and Cargo.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. ZONE_GOAL_CARGO constructor
--
-- * @{#ZONE_GOAL_CARGO.New}(): Creates a new ZONE_GOAL_CARGO object.
--
-- ## 2. ZONE_GOAL_CARGO is a finite state machine (FSM).
--
-- ### 2.1 ZONE_GOAL_CARGO States
--
-- * **Deployed**: The Zone has been captured by an other coalition.
-- * **Airborne**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
-- * **Loaded**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
-- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
--
-- ### 2.2 ZONE_GOAL_CARGO Events
--
-- * **Capture**: The Zone has been captured by an other coalition.
-- * **Attack**: The Zone is currently intruded by an other coalition. There are units of the owning coalition and an other coalition in the Zone.
-- * **Guard**: The Zone is guarded by the owning coalition. There is no other unit of an other coalition in the Zone.
-- * **Empty**: The Zone is empty. There is not valid unit in the Zone.
--
-- ### 2.3 ZONE_GOAL_CARGO State Machine
--
-- @field #ZONE_GOAL_CARGO
ZONE_GOAL_CARGO = {
ClassName = "ZONE_GOAL_CARGO",
}
--- @field #table ZONE_GOAL_CARGO.States
ZONE_GOAL_CARGO.States = {}
--- ZONE_GOAL_CARGO Constructor.
-- @param #ZONE_GOAL_CARGO self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
-- @return #ZONE_GOAL_CARGO
function ZONE_GOAL_CARGO:New( Zone, Coalition )
local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_CARGO
self:F( { Zone = Zone, Coalition = Coalition } )
self:SetCoalition( Coalition )
do
--- Captured State Handler OnLeave for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnLeaveCaptured
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Captured State Handler OnEnter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnEnterCaptured
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Attacked State Handler OnLeave for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnLeaveAttacked
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Attacked State Handler OnEnter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnEnterAttacked
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Guarded State Handler OnLeave for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnLeaveGuarded
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Guarded State Handler OnEnter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnEnterGuarded
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
end
do
--- Empty State Handler OnLeave for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnLeaveEmpty
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Empty State Handler OnEnter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnEnterEmpty
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
end
self:AddTransition( "*", "Guard", "Guarded" )
--- Guard Handler OnBefore for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnBeforeGuard
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Guard Handler OnAfter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnAfterGuard
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Guard Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] Guard
-- @param #ZONE_GOAL_CARGO self
--- Guard Asynchronous Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] __Guard
-- @param #ZONE_GOAL_CARGO self
-- @param #number Delay
self:AddTransition( "*", "Empty", "Empty" )
--- Empty Handler OnBefore for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnBeforeEmpty
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Empty Handler OnAfter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnAfterEmpty
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Empty Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] Empty
-- @param #ZONE_GOAL_CARGO self
--- Empty Asynchronous Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] __Empty
-- @param #ZONE_GOAL_CARGO self
-- @param #number Delay
self:AddTransition( { "Guarded", "Empty" }, "Attack", "Attacked" )
--- Attack Handler OnBefore for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnBeforeAttack
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Attack Handler OnAfter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnAfterAttack
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Attack Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] Attack
-- @param #ZONE_GOAL_CARGO self
--- Attack Asynchronous Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] __Attack
-- @param #ZONE_GOAL_CARGO self
-- @param #number Delay
self:AddTransition( { "Guarded", "Attacked", "Empty" }, "Capture", "Captured" )
--- Capture Handler OnBefore for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnBeforeCapture
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Capture Handler OnAfter for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] OnAfterCapture
-- @param #ZONE_GOAL_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Capture Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] Capture
-- @param #ZONE_GOAL_CARGO self
--- Capture Asynchronous Trigger for ZONE_GOAL_CARGO
-- @function [parent=#ZONE_GOAL_CARGO] __Capture
-- @param #ZONE_GOAL_CARGO self
-- @param #number Delay
return self
end
--- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_CARGO self
-- @param DCSCoalition.DCSCoalition#coalition Coalition
function ZONE_GOAL_CARGO:SetCoalition( Coalition )
self.Coalition = Coalition
end
--- Get the owning coalition of the zone.
-- @param #ZONE_GOAL_CARGO self
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
function ZONE_GOAL_CARGO:GetCoalition()
return self.Coalition
end
--- Get the owning coalition name of the zone.
-- @param #ZONE_GOAL_CARGO self
-- @return #string Coalition name.
function ZONE_GOAL_CARGO:GetCoalitionName()
if self.Coalition == coalition.side.BLUE then
return "Blue"
end
if self.Coalition == coalition.side.RED then
return "Red"
end
if self.Coalition == coalition.side.NEUTRAL then
return "Neutral"
end
return ""
end
function ZONE_GOAL_CARGO:IsGuarded()
local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition )
self:F( { IsGuarded = IsGuarded } )
return IsGuarded
end
function ZONE_GOAL_CARGO:IsEmpty()
local IsEmpty = self.Zone:IsNoneInZone()
self:F( { IsEmpty = IsEmpty } )
return IsEmpty
end
function ZONE_GOAL_CARGO:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_GOAL_CARGO:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Mark.
-- @param #ZONE_GOAL_CARGO self
function ZONE_GOAL_CARGO:Mark()
local Coord = self.Zone:GetCoordinate()
local ZoneName = self:GetZoneName()
local State = self:GetState()
if self.MarkRed and self.MarkBlue then
self:F( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
Coord:RemoveMark( self.MarkRed )
Coord:RemoveMark( self.MarkBlue )
end
if self.Coalition == coalition.side.BLUE then
self.MarkBlue = Coord:MarkToCoalitionBlue( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkRed = Coord:MarkToCoalitionRed( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
else
self.MarkRed = Coord:MarkToCoalitionRed( "Guard Zone: " .. ZoneName .. "\nStatus: " .. State )
self.MarkBlue = Coord:MarkToCoalitionBlue( "Capture Zone: " .. ZoneName .. "\nStatus: " .. State )
end
end
--- Bound.
-- @param #ZONE_GOAL_CARGO self
function ZONE_GOAL_CARGO:onenterGuarded()
--self:GetParent( self ):onenterGuarded()
if self.Coalition == coalition.side.BLUE then
--elf.ProtectZone:BoundZone( 12, country.id.USA )
else
--self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
end
self:Mark()
end
function ZONE_GOAL_CARGO:onenterCaptured()
--self:GetParent( self ):onenterCaptured()
local NewCoalition = self.Zone:GetCoalition()
self:F( { NewCoalition = NewCoalition } )
self:SetCoalition( NewCoalition )
self:Mark()
end
function ZONE_GOAL_CARGO:onenterEmpty()
--self:GetParent( self ):onenterEmpty()
self:Mark()
end
function ZONE_GOAL_CARGO:onenterAttacked()
--self:GetParent( self ):onenterAttacked()
self:Mark()
end
--- When started, check the Coalition status.
-- @param #ZONE_GOAL_CARGO self
function ZONE_GOAL_CARGO:onafterGuard()
--self:F({BASE:GetParent( self )})
--BASE:GetParent( self ).onafterGuard( self )
if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
end
if not self.ScheduleStatusZone then
self.ScheduleStatusZone = self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
end
end
function ZONE_GOAL_CARGO:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } )
return IsCaptured
end
function ZONE_GOAL_CARGO:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } )
return IsAttacked
end
--- Check status Coalition ownership.
-- @param #ZONE_GOAL_CARGO self
function ZONE_GOAL_CARGO:StatusZone()
local State = self:GetState()
self:F( { State = self:GetState() } )
self.Zone:Scan()
if State ~= "Guarded" and self:IsGuarded() then
self:Guard()
end
if State ~= "Empty" and self:IsEmpty() then
self:Empty()
end
if State ~= "Attacked" and self:IsAttacked() then
self:Attack()
end
if State ~= "Captured" and self:IsCaptured() then
self:Capture()
end
end
end

View File

@@ -1,112 +0,0 @@
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone for a Coalition.
--
-- ===
--
-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module Functional.ZoneGoalCoalition
-- @image MOOSE.JPG
do -- ZoneGoal
--- @type ZONE_GOAL_COALITION
-- @extends Functional.ZoneGoal#ZONE_GOAL
--- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
-- Derived classes implement the ways how the achievements can be realized.
--
-- ## 1. ZONE_GOAL_COALITION constructor
--
-- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object.
--
-- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM).
--
-- ### 2.1 ZONE_GOAL_COALITION States
--
-- ### 2.2 ZONE_GOAL_COALITION Events
--
-- ### 2.3 ZONE_GOAL_COALITION State Machine
--
-- @field #ZONE_GOAL_COALITION
ZONE_GOAL_COALITION = {
ClassName = "ZONE_GOAL_COALITION",
}
--- @field #table ZONE_GOAL_COALITION.States
ZONE_GOAL_COALITION.States = {}
--- ZONE_GOAL_COALITION Constructor.
-- @param #ZONE_GOAL_COALITION self
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
-- @return #ZONE_GOAL_COALITION
function ZONE_GOAL_COALITION:New( Zone, Coalition )
local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION
self:F( { Zone = Zone, Coalition = Coalition } )
self:SetCoalition( Coalition )
return self
end
--- Set the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @param DCSCoalition.DCSCoalition#coalition Coalition
function ZONE_GOAL_COALITION:SetCoalition( Coalition )
self.Coalition = Coalition
end
--- Get the owning coalition of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
function ZONE_GOAL_COALITION:GetCoalition()
return self.Coalition
end
--- Get the owning coalition name of the zone.
-- @param #ZONE_GOAL_COALITION self
-- @return #string Coalition name.
function ZONE_GOAL_COALITION:GetCoalitionName()
if self.Coalition == coalition.side.BLUE then
return "Blue"
end
if self.Coalition == coalition.side.RED then
return "Red"
end
if self.Coalition == coalition.side.NEUTRAL then
return "Neutral"
end
return ""
end
--- Check status Coalition ownership.
-- @param #ZONE_GOAL_COALITION self
function ZONE_GOAL_COALITION:StatusZone()
local State = self:GetState()
self:F( { State = self:GetState() } )
self.Zone:Scan( { Object.Category.UNIT, Object.Category.STATIC } )
end
end

View File

@@ -1,6 +1,5 @@
-- The order of the declarations is important here. Don't touch it.
--- Declare the event dispatcher based on the EVENT class
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
@@ -11,8 +10,4 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
_SETTINGS = SETTINGS:Set()
_SETTINGS:SetPlayerMenuOn()
_DATABASE:_RegisterCargos()
_DATABASE:_RegisterZones()

View File

@@ -1,122 +1,118 @@
--- **Tasking** -- A command center governs multiple missions, and takes care of the reporting and communications.
--
-- **Features:**
--
-- * Govern multiple missions.
-- * Communicate to coalitions, groups.
-- * Assign tasks.
-- * Manage the menus.
-- * Manage reference zones.
--- **Tasking** -- A COMMANDCENTER is the owner of multiple missions within MOOSE.
-- A COMMANDCENTER governs multiple missions, the tasking and the reporting.
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Contributions:
--
-- ===
-- ====
--
-- @module Tasking.CommandCenter
-- @image Task_Command_Center.JPG
-- @module CommandCenter
--- The REPORT class
-- @type REPORT
-- @extends Core.Base#BASE
REPORT = {
ClassName = "REPORT",
Title = "",
}
--- Create a new REPORT.
-- @param #REPORT self
-- @param #string Title
-- @return #REPORT
function REPORT:New( Title )
local self = BASE:Inherit( self, BASE:New() ) -- #REPORT
self.Report = {}
Title = Title or ""
if Title then
self.Title = Title
end
self:SetIndent( 3 )
return self
end
--- Has the REPORT Text?
-- @param #REPORT self
-- @return #boolean
function REPORT:HasText() --R2.1
return #self.Report > 0
end
--- Set indent of a REPORT.
-- @param #REPORT self
-- @param #number Indent
-- @return #REPORT
function REPORT:SetIndent( Indent ) --R2.1
self.Indent = Indent
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:Add( Text )
self.Report[#self.Report+1] = Text
return self
end
--- Add a new line to a REPORT.
-- @param #REPORT self
-- @param #string Text
-- @return #REPORT
function REPORT:AddIndent( Text ) --R2.1
self.Report[#self.Report+1] = string.rep(" ", self.Indent ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
return self
end
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
-- @param #REPORT self
-- @param #string Delimiter (optional) A delimiter text.
-- @return #string The report text.
function REPORT:Text( Delimiter )
Delimiter = Delimiter or "\n"
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or ""
return ReportText
end
--- The COMMANDCENTER class
-- @type COMMANDCENTER
-- @field Wrapper.Group#GROUP HQ
-- @field DCS#coalition CommandCenterCoalition
-- @field Dcs.DCSCoalitionWrapper.Object#coalition CommandCenterCoalition
-- @list<Tasking.Mission#MISSION> Missions
-- @extends Core.Base#BASE
--- Governs multiple missions, the tasking and the reporting.
--- # COMMANDCENTER class, extends @{Base#BASE}
--
-- The COMMANDCENTER class governs multiple missions, the tasking and the reporting.
--
-- Command centers govern missions, communicates the task assignments between human players of the coalition, and manages the menu flow.
-- It can assign a random task to a player when requested.
-- The commandcenter provides the facilitites to communicate between human players online, executing a task.
-- The commandcenter communicates important messages between the various groups of human players executing tasks in missions.
--
-- ## 1. Create a command center object.
-- ## COMMANDCENTER constructor
--
-- * @{#COMMANDCENTER.New}(): Creates a new COMMANDCENTER object.
--
-- ## 2. Command center mission management.
--
-- Command centers manage missions. These can be added, removed and provides means to retrieve missions.
-- These methods are heavily used by the task dispatcher classes.
-- ## Mission Management
--
-- * @{#COMMANDCENTER.AddMission}(): Adds a mission to the commandcenter control.
-- * @{#COMMANDCENTER.RemoveMission}(): Removes a mission to the commandcenter control.
-- * @{#COMMANDCENTER.GetMissions}(): Retrieves the missions table controlled by the commandcenter.
--
-- ## 3. Communication management between players.
--
-- Command center provide means of communication between players.
-- Because a command center is a central object governing multiple missions,
-- there are several levels at which communication needs to be done.
-- Within MOOSE, communication is facilitated using the message system within the DCS simulator.
--
-- Messages can be sent between players at various levels:
--
-- - On a global level, to all players.
-- - On a coalition level, only to the players belonging to the same coalition.
-- - On a group level, to the players belonging to the same group.
--
-- Messages can be sent to **all players** by the command center using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToAll}().
--
-- To send messages to **the coalition of the command center**, there are two methods available:
--
-- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToCoalition}() to send a specific message to the coalition, with a given message display duration.
-- - You can send a specific type of message using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageTypeToCoalition}().
-- This will send a message of a specific type to the coalition, and as a result its display duration will be flexible according the message display time selection by the human player.
--
-- To send messages **to the group** of human players, there are also two methods available:
--
-- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToGroup}() to send a specific message to a group, with a given message display duration.
-- - You can send a specific type of message using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageTypeToGroup}().
-- This will send a message of a specific type to the group, and as a result its display duration will be flexible according the message display time selection by the human player .
--
-- Messages are considered to be sometimes disturbing for human players, therefore, the settings menu provides the means to activate or deactivate messages.
-- For more information on the message types and display timings that can be selected and configured using the menu, refer to the @{Core.Settings} menu description.
--
-- ## 4. Command center detailed methods.
--
-- Various methods are added to manage command centers.
--
-- ### 4.1. Naming and description.
--
-- There are 3 methods that can be used to retrieve the description of a command center:
--
-- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.GetName}() to retrieve the name of the command center.
-- This is the name given as part of the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor.
-- The returned name using this method, is not to be used for message communication.
--
-- A textual description can be retrieved that provides the command center name to be used within message communication:
--
-- - @{Tasking.CommandCenter#COMMANDCENTER.GetShortText}() returns the command center name as `CC [CommandCenterName]`.
-- - @{Tasking.CommandCenter#COMMANDCENTER.GetText}() returns the command center name as `Command Center [CommandCenterName]`.
--
-- ### 4.2. The coalition of the command center.
--
-- The method @{Tasking.CommandCenter#COMMANDCENTER.GetCoalition}() returns the coalition of the command center.
-- The return value is an enumeration of the type @{DCS#coalition.side}, which contains the RED, BLUE and NEUTRAL coalition.
--
-- ### 4.3. The command center is a real object.
--
-- The command center must be represented by a live object within the DCS simulator. As a result, the command center
-- can be a @{Wrapper.Unit}, a @{Wrapper.Group}, an @{Wrapper.Airbase} or a @{Wrapper.Static} object.
--
-- Using the method @{Tasking.CommandCenter#COMMANDCENTER.GetPositionable}() you retrieve the polymorphic positionable object representing
-- the command center, but just be aware that you should be able to use the representable object derivation methods.
--
-- ### 5. Command center reports.
--
-- Because a command center giverns multiple missions, there are several reports available that are generated by command centers.
-- These reports are generated using the following methods:
--
-- - @{Tasking.CommandCenter#COMMANDCENTER.ReportSummary}(): Creates a summary report of all missions governed by the command center.
-- - @{Tasking.CommandCenter#COMMANDCENTER.ReportDetails}(): Creates a detailed report of all missions governed by the command center.
-- - @{Tasking.CommandCenter#COMMANDCENTER.ReportMissionPlayers}(): Creates a report listing the players active at the missions governed by the command center.
--
-- ## 6. Reference Zones.
-- ## Reference Zones
--
-- Command Centers may be aware of certain Reference Zones within the battleground. These Reference Zones can refer to
-- known areas, recognizable buildings or sites, or any other point of interest.
@@ -163,13 +159,11 @@ COMMANDCENTER = {
-- @return #COMMANDCENTER
function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
local self = BASE:Inherit( self, BASE:New() ) -- #COMMANDCENTER
local self = BASE:Inherit( self, BASE:New() )
self.CommandCenterPositionable = CommandCenterPositionable
self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName()
self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition()
self.AutoAssignTasks = false
self.Missions = {}
@@ -179,10 +173,8 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
function( self, EventData )
if EventData.IniObjectCategory == 1 then
local EventGroup = GROUP:Find( EventData.IniDCSGroup )
self:E( { CommandCenter = self:GetName(), EventGroup = EventGroup:GetName(), HasGroup = self:HasGroup( EventGroup ), EventData = EventData } )
if EventGroup and self:HasGroup( EventGroup ) then
local CommandCenterMenu = MENU_GROUP:New( EventGroup, self:GetText() )
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", self.CommandCenterMenu )
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportMissionsStatus, self, EventGroup )
local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup )
self:ReportSummary( EventGroup )
@@ -193,30 +185,30 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
Mission:JoinUnit( PlayerUnit, PlayerGroup )
end
self:SetMenu()
_DATABASE:PlayerSettingsMenu( PlayerUnit )
end
end
end
)
-- -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do.
-- -- For these elements, it will=
-- -- - Set the correct menu.
-- -- - Assign the PlayerUnit to the Task if required.
-- -- - Send a message to the other players in the group that this player has joined.
-- self:HandleEvent( EVENTS.PlayerEnterUnit,
-- --- @param #COMMANDCENTER self
-- -- @param Core.Event#EVENTDATA EventData
-- function( self, EventData )
-- local PlayerUnit = EventData.IniUnit
-- for MissionID, Mission in pairs( self:GetMissions() ) do
-- local Mission = Mission -- Tasking.Mission#MISSION
-- local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled!
-- Mission:JoinUnit( PlayerUnit, PlayerGroup )
-- end
-- self:SetMenu()
-- end
-- )
-- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do.
-- For these elements, it will=
-- - Set the correct menu.
-- - Assign the PlayerUnit to the Task if required.
-- - Send a message to the other players in the group that this player has joined.
self:HandleEvent( EVENTS.PlayerEnterUnit,
--- @param #COMMANDCENTER self
-- @param Core.Event#EVENTDATA EventData
function( self, EventData )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled!
Mission:JoinUnit( PlayerUnit, PlayerGroup )
end
end
)
-- Handle when a player leaves a slot and goes back to spectators ...
-- The PlayerUnit will be UnAssigned from the Task.
@@ -250,7 +242,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
end
)
-- Handle when a player crashes ...
-- Handle when a player leaves a slot and goes back to spectators ...
-- The PlayerUnit will be UnAssigned from the Task.
-- When there is no Unit left running the Task, the Task goes into Abort...
self:HandleEvent( EVENTS.Crash,
@@ -282,32 +274,6 @@ function COMMANDCENTER:GetName()
return self.CommandCenterName
end
--- Gets the text string of the HQ command center.
-- @param #COMMANDCENTER self
-- @return #string
function COMMANDCENTER:GetText()
return "Command Center [" .. self.CommandCenterName .. "]"
end
--- Gets the short text string of the HQ command center.
-- @param #COMMANDCENTER self
-- @return #string
function COMMANDCENTER:GetShortText()
return "CC [" .. self.CommandCenterName .. "]"
end
--- Gets the coalition of the command center.
-- @param #COMMANDCENTER self
-- @return DCScoalition#coalition
function COMMANDCENTER:GetCoalition()
return self.CommandCenterCoalition
end
--- Gets the POSITIONABLE of the HQ command center.
-- @param #COMMANDCENTER self
-- @return Wrapper.Positionable#POSITIONABLE
@@ -320,7 +286,7 @@ end
-- @return #list<Tasking.Mission#MISSION>
function COMMANDCENTER:GetMissions()
return self.Missions or {}
return self.Missions
end
--- Add a MISSION to be governed by the HQ command center.
@@ -409,7 +375,9 @@ end
--- Sets the menu structure of the Missions governed by the HQ command center.
-- @param #COMMANDCENTER self
function COMMANDCENTER:SetMenu()
self:F2()
self:F()
self.CommandCenterMenu = self.CommandCenterMenu or MENU_COALITION:New( self.CommandCenterCoalition, "Command Center (" .. self:GetName() .. ")" )
local MenuTime = timer.getTime()
for MissionID, Mission in pairs( self:GetMissions() or {} ) do
@@ -418,7 +386,7 @@ function COMMANDCENTER:SetMenu()
end
for MissionID, Mission in pairs( self:GetMissions() or {} ) do
Mission = Mission -- Tasking.Mission#MISSION
local Mission = Mission -- Tasking.Mission#MISSION
Mission:RemoveMenu( MenuTime )
end
@@ -427,133 +395,10 @@ end
--- Gets the commandcenter menu structure governed by the HQ command center.
-- @param #COMMANDCENTER self
-- @return Core.Menu#MENU_COALITION
function COMMANDCENTER:GetMenu( TaskGroup )
local MenuTime = timer.getTime()
self.CommandCenterMenus = self.CommandCenterMenus or {}
local CommandCenterMenu
local CommandCenterText = self:GetText()
CommandCenterMenu = MENU_GROUP:New( TaskGroup, CommandCenterText ):SetTime(MenuTime)
self.CommandCenterMenus[TaskGroup] = CommandCenterMenu
if self.AutoAssignTasks == false then
local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task On", CommandCenterMenu, self.SetAutoAssignTasks, self, true ):SetTime(MenuTime):SetTag("AutoTask")
local AssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task", CommandCenterMenu, self.AssignRandomTask, self, TaskGroup ):SetTime(MenuTime):SetTag("AutoTask")
else
local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task Off", CommandCenterMenu, self.SetAutoAssignTasks, self, false ):SetTime(MenuTime):SetTag("AutoTask")
end
CommandCenterMenu:Remove( MenuTime, "AutoTask" )
return self.CommandCenterMenus[TaskGroup]
function COMMANDCENTER:GetMenu()
return self.CommandCenterMenu
end
--- Assigns a random task to a TaskGroup.
-- @param #COMMANDCENTER self
-- @return #COMMANDCENTER
function COMMANDCENTER:AssignRandomTask( TaskGroup )
local Tasks = {}
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
local MissionTasks = Mission:GetGroupTasks( TaskGroup )
for MissionTaskName, MissionTask in pairs( MissionTasks or {} ) do
Tasks[#Tasks+1] = MissionTask
end
end
local Task = Tasks[ math.random( 1, #Tasks ) ] -- Tasking.Task#TASK
Task:SetAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) )
Task:AssignToGroup( TaskGroup )
end
--- Automatically assigns tasks to all TaskGroups.
-- @param #COMMANDCENTER self
-- @param #boolean AutoAssign true for ON and false or nil for OFF.
-- @return #COMMANDCENTER
function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
self.AutoAssignTasks = AutoAssign or false
local GroupSet = self:AddGroups()
for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
self:GetMenu( TaskGroup )
end
if self.AutoAssignTasks == true then
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
else
self:ScheduleStop( self.AssignTasks )
end
end
--- Automatically assigns tasks to all TaskGroups.
-- @param #COMMANDCENTER self
function COMMANDCENTER:AssignTasks()
local GroupSet = self:AddGroups()
for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
if self:IsGroupAssigned( TaskGroup ) then
else
-- Only groups with planes or helicopters will receive automatic tasks.
-- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696
if TaskGroup:IsAir() then
self:AssignRandomTask( TaskGroup )
end
end
end
end
--- Get all the Groups active within the command center.
-- @param #COMMANDCENTER self
-- @return Core.Set#SET_GROUP
function COMMANDCENTER:AddGroups()
local GroupSet = SET_GROUP:New()
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
GroupSet = Mission:AddGroups( GroupSet )
end
return GroupSet
end
--- Checks of the TaskGroup has a Task.
-- @param #COMMANDCENTER self
-- @return #boolean
function COMMANDCENTER:IsGroupAssigned( TaskGroup )
local Assigned = false
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
if Mission:IsGroupAssigned( TaskGroup ) then
Assigned = true
break
end
end
return Assigned
end
--- Checks of the COMMANDCENTER has a GROUP.
-- @param #COMMANDCENTER self
-- @param Wrapper.Group#GROUP
@@ -585,20 +430,10 @@ end
-- @param #COMMANDCENTER self
-- @param #string Message
-- @param Wrapper.Group#GROUP TaskGroup
-- @param #sring Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown.
function COMMANDCENTER:MessageToGroup( Message, TaskGroup )
self:GetPositionable():MessageToGroup( Message, 15, TaskGroup, self:GetShortText() )
end
--- Send a CC message of a specified type to a GROUP.
-- @param #COMMANDCENTER self
-- @param #string Message
-- @param Wrapper.Group#GROUP TaskGroup
-- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message.
function COMMANDCENTER:MessageTypeToGroup( Message, TaskGroup, MessageType )
self:GetPositionable():MessageTypeToGroup( Message, MessageType, TaskGroup, self:GetShortText() )
self:GetPositionable():MessageToGroup( Message , 15, TaskGroup, self:GetName() )
end
@@ -609,21 +444,7 @@ function COMMANDCENTER:MessageToCoalition( Message )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() )
end
--- Send a CC message of a specified type to the coalition of the CC.
-- @param #COMMANDCENTER self
-- @param #string Message The message.
-- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message.
function COMMANDCENTER:MessageTypeToCoalition( Message, MessageType )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition, self:GetShortText() )
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition )
end
@@ -631,18 +452,16 @@ end
--- Report the status of all MISSIONs to a GROUP.
-- Each Mission is listed, with an indication how many Tasks are still to be completed.
-- @param #COMMANDCENTER self
function COMMANDCENTER:ReportSummary( ReportGroup )
self:F( ReportGroup )
function COMMANDCENTER:ReportMissionsStatus( ReportGroup )
self:E( ReportGroup )
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetName()
Report:Add( string.format( '%s - Report Summary Missions', Name ) )
Report:Add( "Status report of all missions." )
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
Report:Add( " - " .. Mission:ReportSummary() )
Report:Add( " - " .. Mission:ReportStatus() )
end
self:MessageToGroup( Report:Text(), ReportGroup )
@@ -652,7 +471,7 @@ end
-- Each Mission is listed, with an indication how many Tasks are still to be completed.
-- @param #COMMANDCENTER self
function COMMANDCENTER:ReportMissionsPlayers( ReportGroup )
self:F( ReportGroup )
self:E( ReportGroup )
local Report = REPORT:New()
@@ -670,7 +489,7 @@ end
-- Report the details of a Mission, listing the Mission, and all the Task details.
-- @param #COMMANDCENTER self
function COMMANDCENTER:ReportDetails( ReportGroup, Task )
self:F( ReportGroup )
self:E( ReportGroup )
local Report = REPORT:New()

View File

@@ -1,36 +1,38 @@
--- **Tasking** - This module contains the DETECTION_MANAGER class and derived classes.
--- This module contains the DETECTION_MANAGER class and derived classes.
--
-- ===
--
-- The @{#DETECTION_MANAGER} class defines the core functions to report detected objects to groups.
-- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Fsm#FSM}
-- ====================================================================
-- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups.
-- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour.
--
-- 1.1) DETECTION_MANAGER constructor:
-- -----------------------------------
-- * @{#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance.
-- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance.
--
-- 1.2) DETECTION_MANAGER reporting:
-- ---------------------------------
-- Derived DETECTION_MANAGER classes will reports detected units using the method @{#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour.
-- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour.
--
-- The time interval in seconds of the reporting can be changed using the methods @{#DETECTION_MANAGER.SetRefreshTimeInterval}().
-- To control how long a reporting message is displayed, use @{#DETECTION_MANAGER.SetReportDisplayTime}().
-- Derived classes need to implement the method @{#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report.
-- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}().
-- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}().
-- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report.
--
-- Reporting can be started and stopped using the methods @{#DETECTION_MANAGER.StartReporting}() and @{#DETECTION_MANAGER.StopReporting}() respectively.
-- If an ad-hoc report is requested, use the method @{#DETECTION_MANAGER#ReportNow}().
-- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively.
-- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}().
--
-- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds.
--
-- ===
--
-- 2) @{#DETECTION_REPORTING} class, extends @{#DETECTION_MANAGER}
-- ===
-- The @{#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Tasking.DetectionManager#DETECTION_MANAGER} class.
-- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER}
-- =========================================================================================
-- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class.
--
-- 2.1) DETECTION_REPORTING constructor:
-- -------------------------------
-- The @{#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance.
-- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance.
--
--
-- ===
@@ -38,14 +40,13 @@
-- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing
-- ### Author: FlightControl - Framework Design & Programming
--
-- @module Tasking.DetectionManager
-- @image Task_Detection_Manager.JPG
-- @module DetectionManager
do -- DETECTION MANAGER
--- DETECTION_MANAGER class.
-- @type DETECTION_MANAGER
-- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to.
-- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to.
-- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects.
-- @extends Core.Fsm#FSM
DETECTION_MANAGER = {
@@ -56,7 +57,7 @@ do -- DETECTION MANAGER
--- FAC constructor.
-- @param #DETECTION_MANAGER self
-- @param Core.Set#SET_GROUP SetGroup
-- @param Set#SET_GROUP SetGroup
-- @param Functional.Detection#DETECTION_BASE Detection
-- @return #DETECTION_MANAGER self
function DETECTION_MANAGER:New( SetGroup, Detection )
@@ -121,55 +122,14 @@ do -- DETECTION MANAGER
-- @function [parent=#DETECTION_MANAGER] __Stop
-- @param #DETECTION_MANAGER self
-- @param #number Delay
self:AddTransition( "Started", "Success", "Started" )
--- Success Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterSuccess
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Tasking.Task#TASK Task
self:AddTransition( "Started", "Failed", "Started" )
--- Failed Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterFailed
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Tasking.Task#TASK Task
self:AddTransition( "Started", "Aborted", "Started" )
--- Aborted Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterAborted
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Tasking.Task#TASK Task
self:AddTransition( "Started", "Cancelled", "Started" )
--- Cancelled Handler OnAfter for DETECTION_MANAGER
-- @function [parent=#DETECTION_MANAGER] OnAfterCancelled
-- @param #DETECTION_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Tasking.Task#TASK Task
self:AddTransition( "Started", "Report", "Started" )
self:SetRefreshTimeInterval( 30 )
self:SetReportInterval( 30 )
self:SetReportDisplayTime( 25 )
self:E( { Detection = Detection } )
Detection:__Start( 3 )
return self
@@ -181,19 +141,21 @@ do -- DETECTION MANAGER
function DETECTION_MANAGER:onafterReport( From, Event, To )
self:__Report( -self._RefreshTimeInterval )
self:E( "onafterReport" )
self:__Report( -self._ReportInterval )
self:ProcessDetected( self.Detection )
end
--- Set the reporting time interval.
-- @param #DETECTION_MANAGER self
-- @param #number RefreshTimeInterval The interval in seconds when a report needs to be done.
-- @param #number ReportInterval The interval in seconds when a report needs to be done.
-- @return #DETECTION_MANAGER self
function DETECTION_MANAGER:SetRefreshTimeInterval( RefreshTimeInterval )
function DETECTION_MANAGER:SetReportInterval( ReportInterval )
self:F2()
self._RefreshTimeInterval = RefreshTimeInterval
self._ReportInterval = ReportInterval
end
@@ -216,7 +178,7 @@ do -- DETECTION MANAGER
return self._ReportDisplayTime
end
--- Reports the detected items to the @{Core.Set#SET_GROUP}.
--- Reports the detected items to the @{Set#SET_GROUP}.
-- @param #DETECTION_MANAGER self
-- @param Functional.Detection#DETECTION_BASE Detection
-- @return #DETECTION_MANAGER self
@@ -232,7 +194,7 @@ do -- DETECTION_REPORTING
--- DETECTION_REPORTING class.
-- @type DETECTION_REPORTING
-- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to.
-- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to.
-- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects.
-- @extends #DETECTION_MANAGER
DETECTION_REPORTING = {
@@ -242,7 +204,7 @@ do -- DETECTION_REPORTING
--- DETECTION_REPORTING constructor.
-- @param #DETECTION_REPORTING self
-- @param Core.Set#SET_GROUP SetGroup
-- @param Set#SET_GROUP SetGroup
-- @param Functional.Detection#DETECTION_AREAS Detection
-- @return #DETECTION_REPORTING self
function DETECTION_REPORTING:New( SetGroup, Detection )
@@ -256,7 +218,7 @@ do -- DETECTION_REPORTING
--- Creates a string of the detected items in a @{Detection}.
-- @param #DETECTION_MANAGER self
-- @param Core.Set#SET_UNIT DetectedSet The detected Set created by the @{Functional.Detection#DETECTION_BASE} object.
-- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object.
-- @return #DETECTION_MANAGER self
function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet )
self:F2()
@@ -286,14 +248,15 @@ do -- DETECTION_REPORTING
--- Reports the detected items to the @{Core.Set#SET_GROUP}.
--- Reports the detected items to the @{Set#SET_GROUP}.
-- @param #DETECTION_REPORTING self
-- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} object to where the report needs to go.
-- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_BASE} object.
-- @param Wrapper.Group#GROUP Group The @{Group} object to where the report needs to go.
-- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object.
-- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop.
function DETECTION_REPORTING:ProcessDetected( Group, Detection )
self:F2( Group )
self:E( Group )
local DetectedMsg = {}
for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do
local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea

View File

@@ -1,124 +1,21 @@
--- **Tasking** -- A mission models a goal to be achieved through the execution and completion of tasks by human players.
--- **Tasking** -- A MISSION is the main owner of a Mission orchestration within MOOSE.
--
-- **Features:**
-- ====
--
-- * A mission has a goal to be achieved, through the execution and completion of tasks of different categories by human players.
-- * A mission manages these tasks.
-- * A mission has a state, that indicates the fase of the mission.
-- * A mission has a menu structure, that facilitates mission reports and tasking menus.
-- * A mission can assign a task to a player.
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Contributions:
--
-- ===
--
-- @module Tasking.Mission
-- @image Task_Mission.JPG
-- @module Mission
--- @type MISSION
--- The MISSION class
-- @type MISSION
-- @field #MISSION.Clients _Clients
-- @field Core.Menu#MENU_COALITION MissionMenu
-- @field #string MissionBriefing
-- @extends Core.Fsm#FSM
--- Models goals to be achieved and can contain multiple tasks to be executed to achieve the goals.
--
-- A mission contains multiple tasks and can be of different task types.
-- These tasks need to be assigned to human players to be executed.
--
-- A mission can have multiple states, which will evolve as the mission progresses during the DCS simulation.
--
-- - **IDLE**: The mission is defined, but not started yet. No task has yet been joined by a human player as part of the mission.
-- - **ENGAGED**: The mission is ongoing, players have joined tasks to be executed.
-- - **COMPLETED**: The goals of the mission has been successfully reached, and the mission is flagged as completed.
-- - **FAILED**: For a certain reason, the goals of the mission has not been reached, and the mission is flagged as failed.
-- - **HOLD**: The mission was enaged, but for some reason it has been put on hold.
--
-- Note that a mission goals need to be checked by a goal check trigger: @{#MISSION.OnBeforeMissionGoals}(), which may return false if the goal has not been reached.
-- This goal is checked automatically by the mission object every x seconds.
--
-- - @{#MISSION.Start}() or @{#MISSION.__Start}() will start the mission, and will bring it from **IDLE** state to **ENGAGED** state.
-- - @{#MISSION.Stop}() or @{#MISSION.__Stop}() will stop the mission, and will bring it from **ENGAGED** state to **IDLE** state.
-- - @{#MISSION.Complete}() or @{#MISSION.__Complete}() will complete the mission, and will bring the mission state to **COMPLETED**.
-- Note that the mission must be in state **ENGAGED** to be able to complete the mission.
-- - @{#MISSION.Fail}() or @{#MISSION.__Fail}() will fail the mission, and will bring the mission state to **FAILED**.
-- Note that the mission must be in state **ENGAGED** to be able to fail the mission.
-- - @{#MISSION.Hold}() or @{#MISSION.__Hold}() will hold the mission, and will bring the mission state to **HOLD**.
-- Note that the mission must be in state **ENGAGED** to be able to hold the mission.
-- Re-engage the mission using the engage trigger.
--
-- The following sections provide an overview of the most important methods that can be used as part of a mission object.
-- Note that the @{Tasking.CommandCenter} system is using most of these methods to manage the missions in its system.
--
-- ## 1. Create a mission object.
--
-- - @{#MISSION.New}(): Creates a new MISSION object.
--
-- ## 2. Mission task management.
--
-- Missions maintain tasks, which can be added or removed, or enquired.
--
-- - @{#MISSION.AddTask}(): Adds a task to the mission.
-- - @{#MISSION.RemoveTask}(): Removes a task from the mission.
--
-- ## 3. Mission detailed methods.
--
-- Various methods are added to manage missions.
--
-- ### 3.1. Naming and description.
--
-- There are several methods that can be used to retrieve the properties of a mission:
--
-- - Use the method @{#MISSION.GetName}() to retrieve the name of the mission.
-- This is the name given as part of the @{#MISSION.New}() constructor.
--
-- A textual description can be retrieved that provides the mission name to be used within message communication:
--
-- - @{#MISSION.GetShortText}() returns the mission name as `Mission "MissionName"`.
-- - @{#MISSION.GetText}() returns the mission name as `Mission "MissionName (MissionPriority)"`. A longer version including the priority text of the mission.
--
-- ### 3.2. Get task information.
--
-- - @{#MISSION.GetTasks}(): Retrieves a list of the tasks controlled by the mission.
-- - @{#MISSION.GetTask}(): Retrieves a specific task controlled by the mission.
-- - @{#MISSION.GetTasksRemaining}(): Retrieve a list of the tasks that aren't finished or failed, and are governed by the mission.
-- - @{#MISSION.GetGroupTasks}(): Retrieve a list of the tasks that can be asigned to a @{Wrapper.Group}.
-- - @{#MISSION.GetTaskTypes}(): Retrieve a list of the different task types governed by the mission.
--
-- ### 3.3. Get the command center.
--
-- - @{#MISSION.GetCommandCenter}(): Retrieves the @{Tasking.CommandCenter} governing the mission.
--
-- ### 3.4. Get the groups active in the mission as a @{Core.Set}.
--
-- - @{#MISSION.GetGroups}(): Retrieves a @{Core.Set#SET_GROUP} of all the groups active in the mission (as part of the tasks).
--
-- ### 3.5. Get the names of the players.
--
-- - @{#MISSION.GetPlayerNames}(): Retrieves the list of the players that were active within th mission..
--
-- ## 4. Menu management.
--
-- A mission object is able to manage its own menu structure. Use the @{#MISSION.GetMenu}() and @{#MISSION.SetMenu}() to manage the underlying submenu
-- structure managing the tasks of the mission.
--
-- ## 5. Reporting management.
--
-- Several reports can be generated for a mission, and will return a text string that can be used to display using the @{Core.Message} system.
--
-- - @{#MISSION.ReportBriefing}(): Generates the briefing for the mission.
-- - @{#MISSION.ReportOverview}(): Generates an overview of the tasks and status of the mission.
-- - @{#MISSION.ReportDetails}(): Generates a detailed report of the tasks of the mission.
-- - @{#MISSION.ReportSummary}(): Generates a summary report of the tasks of the mission.
-- - @{#MISSION.ReportPlayersPerTask}(): Generates a report showing the active players per task.
-- - @{#MISSION.ReportPlayersProgress}(): Generates a report showing the task progress per player.
--
--
-- @field #MISSION
MISSION = {
ClassName = "MISSION",
Name = "",
@@ -132,7 +29,7 @@ MISSION = {
-- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players.
-- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field.
-- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}.
-- @param #string MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"...
-- @param Dcs.DCSCoalitionWrapper.Object#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"...
-- @return #MISSION self
function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition )
@@ -149,7 +46,6 @@ function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefi
self.MissionCoalition = MissionCoalition
self.Tasks = {}
self.TaskNumber = 0
self.PlayerNames = {} -- These are the players that achieved progress in the mission.
self:SetStartState( "IDLE" )
@@ -368,42 +264,23 @@ function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefi
end
--- FSM function for a MISSION
-- FSM function for a MISSION
-- @param #MISSION self
-- @param #string From
-- @param #string Event
-- @param #string To
function MISSION:onenterCOMPLETED( From, Event, To )
self:GetCommandCenter():MessageTypeToCoalition( self:GetText() .. " has been completed! Good job guys!", MESSAGE.Type.Information )
self:GetCommandCenter():MessageToCoalition( self:GetName() .. " has been completed! Good job guys!" )
end
--- Gets the mission name.
-- @param #MISSION self
-- @return #MISSION self
function MISSION:GetName()
return self.Name
end
--- Gets the mission text.
-- @param #MISSION self
-- @return #MISSION self
function MISSION:GetText()
return string.format( 'Mission "%s (%s)"', self.Name, self.MissionPriority )
end
--- Gets the short mission text.
-- @param #MISSION self
-- @return #MISSION self
function MISSION:GetShortText()
return string.format( 'Mission "%s"', self.Name )
end
--- Add a Unit to join the Mission.
-- For each Task within the Mission, the Unit is joined with the Task.
-- If the Unit was not part of a Task in the Mission, false is returned.
@@ -413,7 +290,7 @@ end
-- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission.
-- @return #boolean true if Unit is part of a Task in the Mission.
function MISSION:JoinUnit( PlayerUnit, PlayerGroup )
self:I( { Mission = self:GetName(), PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } )
self:F( { PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } )
local PlayerUnitAdded = false
@@ -480,30 +357,24 @@ function MISSION:GetScoring()
return self.Scoring
end
--- Gets the groups for which TASKS are given in the mission
--- Get the groups for which TASKS are given in the mission
-- @param #MISSION self
-- @param Core.Set#SET_GROUP GroupSet
-- @return Core.Set#SET_GROUP
function MISSION:GetGroups()
return self:AddGroups()
end
--- Adds the groups for which TASKS are given in the mission
-- @param #MISSION self
-- @param Core.Set#SET_GROUP GroupSet
-- @return Core.Set#SET_GROUP
function MISSION:AddGroups( GroupSet )
GroupSet = GroupSet or SET_GROUP:New()
local SetGroup = SET_GROUP:New()
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
GroupSet = Task:AddGroups( GroupSet )
local GroupSet = Task:GetGroups()
GroupSet:ForEachGroup(
function( TaskGroup )
SetGroup:Add( TaskGroup, TaskGroup )
end
)
end
return GroupSet
return SetGroup
end
@@ -514,16 +385,9 @@ end
function MISSION:SetMenu( MenuTime )
self:F( { self:GetName(), MenuTime } )
local MenuCount = {}
--for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
local TaskType = Task:GetType()
MenuCount[TaskType] = MenuCount[TaskType] or 1
if MenuCount[TaskType] <= 10 then
Task:SetMenu( MenuTime )
MenuCount[TaskType] = MenuCount[TaskType] + 1
end
for _, TaskData in pairs( self:GetTasks() ) do
local Task = TaskData -- Tasking.Task#TASK
Task:SetMenu( MenuTime )
end
end
@@ -552,16 +416,16 @@ do -- Group Assignment
local MissionGroupName = MissionGroup:GetName()
if self.AssignedGroups[MissionGroupName] == MissionGroup then
self:T2( { "Mission is assigned to:", MissionGroup:GetName() } )
self:T( { "Mission is assigned to:", MissionGroup:GetName() } )
return true
end
self:T2( { "Mission is not assigned to:", MissionGroup:GetName() } )
self:T( { "Mission is not assigned to:", MissionGroup:GetName() } )
return false
end
--- Set @{Wrapper.Group} assigned to the @{Mission}.
--- Set @{Group} assigned to the @{Mission}.
-- @param #MISSION self
-- @param Wrapper.Group#GROUP MissionGroup
-- @return #MISSION
@@ -571,12 +435,12 @@ do -- Group Assignment
local MissionGroupName = MissionGroup:GetName()
self.AssignedGroups[MissionGroupName] = MissionGroup
self:I( string.format( "Mission %s is assigned to %s", MissionName, MissionGroupName ) )
self:E( string.format( "Mission %s is assigned to %s", MissionName, MissionGroupName ) )
return self
end
--- Clear the @{Wrapper.Group} assignment from the @{Mission}.
--- Clear the @{Group} assignment from the @{Mission}.
-- @param #MISSION self
-- @param Wrapper.Group#GROUP MissionGroup
-- @return #MISSION
@@ -611,54 +475,38 @@ function MISSION:RemoveTaskMenu( Task )
end
--- Gets the root mission menu for the TaskGroup.
-- @param #MISSION self
-- @return Core.Menu#MENU_COALITION self
function MISSION:GetRootMenu( TaskGroup ) -- R2.2
local CommandCenter = self:GetCommandCenter()
local CommandCenterMenu = CommandCenter:GetMenu()
local MissionName = self:GetText()
--local MissionMenu = CommandCenterMenu:GetMenu( MissionName )
self.MissionMenu = MENU_COALITION:New( self.MissionCoalition, MissionName, CommandCenterMenu )
return self.MissionMenu
end
--- Gets the mission menu for the TaskGroup.
--- Gets the mission menu for the coalition.
-- @param #MISSION self
-- @return Core.Menu#MENU_COALITION self
function MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure
local CommandCenter = self:GetCommandCenter()
local CommandCenterMenu = CommandCenter:GetMenu( TaskGroup )
local CommandCenterMenu = CommandCenter:GetMenu()
self.MissionGroupMenu = self.MissionGroupMenu or {}
self.MissionGroupMenu[TaskGroup] = self.MissionGroupMenu[TaskGroup] or {}
local MissionName = self:GetName()
--local MissionMenu = CommandCenterMenu:GetMenu( MissionName )
local GroupMenu = self.MissionGroupMenu[TaskGroup]
self.MissionMenu = self.MissionMenu or {}
self.MissionMenu[TaskGroup] = self.MissionMenu[TaskGroup] or {}
local MissionText = self:GetText()
self.MissionMenu = MENU_GROUP:New( TaskGroup, MissionText, CommandCenterMenu )
local Menu = self.MissionMenu[TaskGroup]
GroupMenu.BriefingMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Mission Briefing", self.MissionMenu, self.MenuReportBriefing, self, TaskGroup )
Menu.MainMenu = Menu.MainMenu or MENU_GROUP:New( TaskGroup, self:GetName(), CommandCenterMenu )
Menu.BriefingMenu = Menu.BriefingMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Mission Briefing", Menu.MainMenu, self.MenuReportBriefing, self, TaskGroup )
GroupMenu.MarkTasks = MENU_GROUP_COMMAND:New( TaskGroup, "Mark Task Locations on Map", self.MissionMenu, self.MarkTargetLocations, self, TaskGroup )
GroupMenu.TaskReportsMenu = MENU_GROUP:New( TaskGroup, "Task Reports", self.MissionMenu )
GroupMenu.ReportTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Tasks Summary", GroupMenu.TaskReportsMenu, self.MenuReportTasksSummary, self, TaskGroup )
GroupMenu.ReportPlannedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Planned Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Planned" )
GroupMenu.ReportAssignedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Assigned Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Assigned" )
GroupMenu.ReportSuccessTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Successful Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Success" )
GroupMenu.ReportFailedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Failed Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Failed" )
GroupMenu.ReportHeldTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Held Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Hold" )
Menu.TaskReportsMenu = Menu.TaskReportsMenu or MENU_GROUP:New( TaskGroup, "Task Reports", Menu.MainMenu )
Menu.ReportTasksMenu = Menu.ReportTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Tasks", Menu.TaskReportsMenu, self.MenuReportTasksSummary, self, TaskGroup )
Menu.ReportPlannedTasksMenu = Menu.ReportPlannedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Planned Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Planned" )
Menu.ReportAssignedTasksMenu = Menu.ReportAssignedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Assigned Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Assigned" )
Menu.ReportSuccessTasksMenu = Menu.ReportSuccessTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Successful Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Success" )
Menu.ReportFailedTasksMenu = Menu.ReportFailedTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Failed Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Failed" )
Menu.ReportHeldTasksMenu = Menu.ReportHeldTasksMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Held Tasks", Menu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Hold" )
GroupMenu.PlayerReportsMenu = MENU_GROUP:New( TaskGroup, "Statistics Reports", self.MissionMenu )
GroupMenu.ReportMissionHistory = MENU_GROUP_COMMAND:New( TaskGroup, "Report Mission Progress", GroupMenu.PlayerReportsMenu, self.MenuReportPlayersProgress, self, TaskGroup )
GroupMenu.ReportPlayersPerTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Players per Task", GroupMenu.PlayerReportsMenu, self.MenuReportPlayersPerTask, self, TaskGroup )
Menu.PlayerReportsMenu = Menu.PlayerReportsMenu or MENU_GROUP:New( TaskGroup, "Statistics Reports", Menu.MainMenu )
Menu.ReportMissionHistory = Menu.ReportPlayersHistory or MENU_GROUP_COMMAND:New( TaskGroup, "Report Mission Progress", Menu.PlayerReportsMenu, self.MenuReportPlayersProgress, self, TaskGroup )
Menu.ReportPlayersPerTaskMenu = Menu.ReportPlayersPerTaskMenu or MENU_GROUP_COMMAND:New( TaskGroup, "Report Players per Task", Menu.PlayerReportsMenu, self.MenuReportPlayersPerTask, self, TaskGroup )
return self.MissionMenu
return Menu.MainMenu
end
@@ -668,25 +516,13 @@ end
-- @param #string TaskName The Name of the @{Task} within the @{Mission}.
-- @return Tasking.Task#TASK The Task
-- @return #nil Returns nil if no task was found.
function MISSION:GetTask( TaskName )
function MISSION:GetTask( TaskName )
self:F( { TaskName } )
return self.Tasks[TaskName]
end
--- Return the next @{Task} ID to be completed within the @{Mission}.
-- @param #MISSION self
-- @param Tasking.Task#TASK Task is the @{Task} object.
-- @return Tasking.Task#TASK The task added.
function MISSION:GetNextTaskID( Task )
self.TaskNumber = self.TaskNumber + 1
return self.TaskNumber
end
--- Register a @{Task} to be completed within the @{Mission}.
-- Note that there can be multiple @{Task}s registered to be completed.
-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached.
@@ -696,7 +532,9 @@ end
function MISSION:AddTask( Task )
local TaskName = Task:GetTaskName()
self:I( { "==> Adding TASK ", MissionName = self:GetName(), TaskName = TaskName } )
self:F( TaskName )
self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 }
self.Tasks[TaskName] = Task
@@ -705,7 +543,6 @@ function MISSION:AddTask( Task )
return Task
end
--- Removes a @{Task} to be completed within the @{Mission}.
-- Note that there can be multiple @{Task}s registered to be completed.
-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached.
@@ -715,7 +552,6 @@ end
function MISSION:RemoveTask( Task )
local TaskName = Task:GetTaskName()
self:I( { "<== Removing TASK ", MissionName = self:GetName(), TaskName = TaskName } )
self:F( TaskName )
self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 }
@@ -731,6 +567,21 @@ function MISSION:RemoveTask( Task )
return nil
end
--- Return the next @{Task} ID to be completed within the @{Mission}.
-- @param #MISSION self
-- @param Tasking.Task#TASK Task is the @{Task} object.
-- @return Tasking.Task#TASK The task added.
function MISSION:GetNextTaskID( Task )
local TaskName = Task:GetTaskName()
self:F( TaskName )
self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 }
self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1
return self.Tasks[TaskName].n
end
--- Is the @{Mission} **COMPLETED**.
-- @param #MISSION self
-- @return #boolean
@@ -832,7 +683,7 @@ function MISSION:ReportBriefing()
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetText()
local Name = self:GetName()
-- Determine the status of the mission.
local Status = "<" .. self:GetState() .. ">"
@@ -845,57 +696,57 @@ function MISSION:ReportBriefing()
end
----- Create a status report of the Mission.
---- This reports provides a one liner of the mission status. It indicates how many players and how many Tasks.
----
---- Mission "<MissionName>" - Status "<MissionStatus>"
---- - Task Types: <TaskType>, <TaskType>
---- - <xx> Planned Tasks (xp)
---- - <xx> Assigned Tasks(xp)
---- - <xx> Success Tasks (xp)
---- - <xx> Hold Tasks (xp)
---- - <xx> Cancelled Tasks (xp)
---- - <xx> Aborted Tasks (xp)
---- - <xx> Failed Tasks (xp)
----
---- @param #MISSION self
---- @return #string
--function MISSION:ReportSummary()
--
-- local Report = REPORT:New()
--
-- -- List the name of the mission.
-- local Name = self:GetText()
--
-- -- Determine the status of the mission.
-- local Status = "<" .. self:GetState() .. ">"
--
-- Report:Add( string.format( '%s - Status "%s"', Name, Status ) )
--
-- local TaskTypes = self:GetTaskTypes()
--
-- Report:Add( string.format( " - Task Types: %s", table.concat(TaskTypes, ", " ) ) )
--
-- local TaskStatusList = { "Planned", "Assigned", "Success", "Hold", "Cancelled", "Aborted", "Failed" }
--
-- for TaskStatusID, TaskStatus in pairs( TaskStatusList ) do
-- local TaskCount = 0
-- local TaskPlayerCount = 0
-- -- Determine how many tasks are remaining.
-- for TaskID, Task in pairs( self:GetTasks() ) do
-- local Task = Task -- Tasking.Task#TASK
-- if Task:Is( TaskStatus ) then
-- TaskCount = TaskCount + 1
-- TaskPlayerCount = TaskPlayerCount + Task:GetPlayerCount()
-- end
-- end
-- if TaskCount > 0 then
-- Report:Add( string.format( " - %02d %s Tasks (%dp)", TaskCount, TaskStatus, TaskPlayerCount ) )
-- end
-- end
--
-- return Report:Text()
--end
--- Create a status report of the Mission.
-- This reports provides a one liner of the mission status. It indicates how many players and how many Tasks.
--
-- Mission "<MissionName>" - Status "<MissionStatus>"
-- - Task Types: <TaskType>, <TaskType>
-- - <xx> Planned Tasks (xp)
-- - <xx> Assigned Tasks(xp)
-- - <xx> Success Tasks (xp)
-- - <xx> Hold Tasks (xp)
-- - <xx> Cancelled Tasks (xp)
-- - <xx> Aborted Tasks (xp)
-- - <xx> Failed Tasks (xp)
--
-- @param #MISSION self
-- @return #string
function MISSION:ReportStatus()
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetName()
-- Determine the status of the mission.
local Status = "<" .. self:GetState() .. ">"
Report:Add( string.format( '%s - Status "%s"', Name, Status ) )
local TaskTypes = self:GetTaskTypes()
Report:Add( string.format( " - Task Types: %s", table.concat(TaskTypes, ", " ) ) )
local TaskStatusList = { "Planned", "Assigned", "Success", "Hold", "Cancelled", "Aborted", "Failed" }
for TaskStatusID, TaskStatus in pairs( TaskStatusList ) do
local TaskCount = 0
local TaskPlayerCount = 0
-- Determine how many tasks are remaining.
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
if Task:Is( TaskStatus ) then
TaskCount = TaskCount + 1
TaskPlayerCount = TaskPlayerCount + Task:GetPlayerCount()
end
end
if TaskCount > 0 then
Report:Add( string.format( " - %02d %s Tasks (%dp)", TaskCount, TaskStatus, TaskPlayerCount ) )
end
end
return Report:Text()
end
--- Create an active player report of the Mission.
@@ -913,7 +764,7 @@ function MISSION:ReportPlayersPerTask( ReportGroup )
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetText()
local Name = self:GetName()
-- Determine the status of the mission.
local Status = "<" .. self:GetState() .. ">"
@@ -954,7 +805,7 @@ function MISSION:ReportPlayersProgress( ReportGroup )
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetText()
local Name = self:GetName()
-- Determine the status of the mission.
local Status = "<" .. self:GetState() .. ">"
@@ -990,42 +841,15 @@ function MISSION:ReportPlayersProgress( ReportGroup )
end
--- Mark all the target locations on the Map.
-- @param #MISSION self
-- @param Wrapper.Group#GROUP ReportGroup
-- @return #string
function MISSION:MarkTargetLocations( ReportGroup )
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetText()
-- Determine the status of the mission.
local Status = "<" .. self:GetState() .. ">"
Report:Add( string.format( '%s - %s - All Tasks are marked on the map. Select a Task from the Mission Menu and Join the Task!!!', Name, Status ) )
-- Determine how many tasks are remaining.
for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do
local Task = Task -- Tasking.Task#TASK
Task:MenuMarkToGroup( ReportGroup )
end
return Report:Text()
end
--- Create a summary report of the Mission (one line).
-- @param #MISSION self
-- @param Wrapper.Group#GROUP ReportGroup
-- @return #string
function MISSION:ReportSummary( ReportGroup )
function MISSION:ReportSummary()
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetText()
local Name = self:GetName()
-- Determine the status of the mission.
local Status = "<" .. self:GetState() .. ">"
@@ -1033,9 +857,9 @@ function MISSION:ReportSummary( ReportGroup )
Report:Add( string.format( '%s - %s - Task Overview Report', Name, Status ) )
-- Determine how many tasks are remaining.
for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
Report:Add( "- " .. Task:ReportSummary( ReportGroup ) )
Report:Add( "- " .. Task:ReportSummary() )
end
return Report:Text()
@@ -1046,12 +870,10 @@ end
-- @return #string
function MISSION:ReportOverview( ReportGroup, TaskStatus )
self:F( { TaskStatus = TaskStatus } )
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetText()
local Name = self:GetName()
-- Determine the status of the mission.
local Status = "<" .. self:GetState() .. ">"
@@ -1059,16 +881,11 @@ function MISSION:ReportOverview( ReportGroup, TaskStatus )
Report:Add( string.format( '%s - %s - %s Tasks Report', Name, Status, TaskStatus ) )
-- Determine how many tasks are remaining.
local Tasks = 0
local TasksRemaining = 0
for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do
local Task = Task -- Tasking.Task#TASK
if Task:Is( TaskStatus ) then
Report:Add( string.rep( "-", 140 ) )
Report:Add( Task:ReportOverview( ReportGroup ) )
end
Tasks = Tasks + 1
if Tasks >= 8 then
break
Report:Add( " - " .. Task:ReportOverview( ReportGroup ) )
end
end
@@ -1083,7 +900,7 @@ function MISSION:ReportDetails( ReportGroup )
local Report = REPORT:New()
-- List the name of the mission.
local Name = self:GetText()
local Name = self:GetName()
-- Determine the status of the mission.
local Status = "<" .. self:GetState() .. ">"
@@ -1094,7 +911,6 @@ function MISSION:ReportDetails( ReportGroup )
local TasksRemaining = 0
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
Report:Add( string.rep( "-", 140 ) )
Report:Add( Task:ReportDetails( ReportGroup ) )
end
@@ -1109,28 +925,9 @@ end
-- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" )
function MISSION:GetTasks()
return self.Tasks or {}
return self.Tasks
end
--- Get the relevant tasks of a TaskGroup.
-- @param #MISSION
-- @param Wrapper.Group#GROUP TaskGroup
-- @return #list<Tasking.Task#TASK>
function MISSION:GetGroupTasks( TaskGroup )
local Tasks = {}
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
if Task:HasGroup( TaskGroup ) then
Tasks[#Tasks+1] = Task
end
end
return Tasks
end
--- Reports the briefing.
-- @param #MISSION self
-- @param Wrapper.Group#GROUP ReportGroup The group to which the report needs to be sent.
@@ -1138,18 +935,7 @@ function MISSION:MenuReportBriefing( ReportGroup )
local Report = self:ReportBriefing()
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Briefing )
end
--- Mark all the targets of the Mission on the Map.
-- @param #MISSION self
-- @param Wrapper.Group#GROUP ReportGroup
function MISSION:MenuMarkTargetLocations( ReportGroup )
local Report = self:MarkTargetLocations( ReportGroup )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
end
@@ -1159,9 +945,9 @@ end
-- @param Wrapper.Group#GROUP ReportGroup
function MISSION:MenuReportTasksSummary( ReportGroup )
local Report = self:ReportSummary( ReportGroup )
local Report = self:ReportSummary()
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
end
@@ -1174,7 +960,7 @@ function MISSION:MenuReportTasksPerStatus( ReportGroup, TaskStatus )
local Report = self:ReportOverview( ReportGroup, TaskStatus )
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
end
@@ -1184,7 +970,7 @@ function MISSION:MenuReportPlayersPerTask( ReportGroup )
local Report = self:ReportPlayersPerTask()
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
end
--- @param #MISSION self
@@ -1193,7 +979,7 @@ function MISSION:MenuReportPlayersProgress( ReportGroup )
local Report = self:ReportPlayersProgress()
self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview )
self:GetCommandCenter():MessageToGroup( Report, ReportGroup )
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,356 +0,0 @@
--- **Tasking** -- Controls the information of a Task.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
-- ===
--
-- @module Tasking.TaskInfo
-- @image MOOSE.JPG
--- @type TASKINFO
-- @extends Core.Base#BASE
---
-- # TASKINFO class, extends @{Core.Base#BASE}
--
-- ## The TASKINFO class implements the methods to contain information and display information of a task.
--
-- @field #TASKINFO
TASKINFO = {
ClassName = "TASKINFO",
}
--- @type #TASKINFO.Detail #string A string that flags to document which level of detail needs to be shown in the report.
--
-- - "M" for Markings on the Map (F10).
-- - "S" for Summary Reports.
-- - "O" for Overview Reports.
-- - "D" for Detailed Reports.
TASKINFO.Detail = ""
--- Instantiates a new TASKINFO.
-- @param #TASKINFO self
-- @param Tasking.Task#TASK Task The task owning the information.
-- @return #TASKINFO self
function TASKINFO:New( Task )
local self = BASE:Inherit( self, BASE:New() ) -- Core.Base#BASE
self.Task = Task
self.VolatileInfo = SET_BASE:New()
self.PersistentInfo = SET_BASE:New()
self.Info = self.VolatileInfo
return self
end
--- Add taskinfo.
-- @param #TASKINFO self
-- @param #string The info key.
-- @param Data The data of the info.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddInfo( Key, Data, Order, Detail, Keep )
self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail } )
if Keep == true then
self.PersistentInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail } )
end
return self
end
--- Get taskinfo.
-- @param #TASKINFO self
-- @param #string The info key.
-- @return Data The data of the info.
-- @return #number Order The display order, which is a number from 0 to 100.
-- @return #TASKINFO.Detail Detail The detail Level.
function TASKINFO:GetInfo( Key )
local Object = self:Get( Key )
return Object.Data, Object.Order, Object.Detail
end
--- Get data.
-- @param #TASKINFO self
-- @param #string The info key.
-- @return Data The data of the info.
function TASKINFO:GetData( Key )
local Object = self.Info:Get( Key )
return Object.Data
end
--- Add Text.
-- @param #TASKINFO self
-- @param #string Key The key.
-- @param #string Text The text.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddText( Key, Text, Order, Detail, Keep )
self:AddInfo( Key, Text, Order, Detail, Keep )
return self
end
--- Add the task name.
-- @param #TASKINFO self
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddTaskName( Order, Detail, Keep )
self:AddInfo( "TaskName", self.Task:GetName(), Order, Detail, Keep )
return self
end
--- Add a Coordinate.
-- @param #TASKINFO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddCoordinate( Coordinate, Order, Detail, Keep )
self:AddInfo( "Coordinate", Coordinate, Order, Detail, Keep )
return self
end
--- Add Threat.
-- @param #TASKINFO self
-- @param #string ThreatText The text of the Threat.
-- @param #string ThreatLevel The level of the Threat.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddThreat( ThreatText, ThreatLevel, Order, Detail, Keep )
self:AddInfo( "Threat", ThreatText .. " [" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]", Order, Detail, Keep )
return self
end
--- Get Threat.
-- @param #TASKINFO self
-- @return #string The threat
function TASKINFO:GetThreat()
self:GetInfo( "Threat" )
return self
end
--- Add the Target count.
-- @param #TASKINFO self
-- @param #number TargetCount The amount of targets.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddTargetCount( TargetCount, Order, Detail, Keep )
self:AddInfo( "Counting", string.format( "%d", TargetCount ), Order, Detail, Keep )
return self
end
--- Add the Targets.
-- @param #TASKINFO self
-- @param #number TargetCount The amount of targets.
-- @param #string TargetTypes The text containing the target types.
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddTargets( TargetCount, TargetTypes, Order, Detail, Keep )
self:AddInfo( "Targets", string.format( "%d of %s", TargetCount, TargetTypes ), Order, Detail, Keep )
return self
end
--- Get Targets.
-- @param #TASKINFO self
-- @return #string The targets
function TASKINFO:GetTargets()
self:GetInfo( "Targets" )
return self
end
--- Add the QFE at a Coordinate.
-- @param #TASKINFO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddQFEAtCoordinate( Coordinate, Order, Detail, Keep )
self:AddInfo( "QFE", Coordinate, Order, Detail, Keep )
return self
end
--- Add the Temperature at a Coordinate.
-- @param #TASKINFO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddTemperatureAtCoordinate( Coordinate, Order, Detail, Keep )
self:AddInfo( "Temperature", Coordinate, Order, Detail, Keep )
return self
end
--- Add the Wind at a Coordinate.
-- @param #TASKINFO self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddWindAtCoordinate( Coordinate, Order, Detail, Keep )
self:AddInfo( "Wind", Coordinate, Order, Detail, Keep )
return self
end
--- Add Cargo.
-- @param #TASKINFO self
-- @param Core.Cargo#CARGO Cargo
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddCargo( Cargo, Order, Detail, Keep )
self:AddInfo( "Cargo", Cargo, Order, Detail, Keep )
return self
end
--- Add Cargo set.
-- @param #TASKINFO self
-- @param Core.Set#SET_CARGO SetCargo
-- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self
function TASKINFO:AddCargoSet( SetCargo, Order, Detail, Keep )
local CargoReport = REPORT:New()
CargoReport:Add( "" )
SetCargo:ForEachCargo(
--- @param Cargo.Cargo#CARGO Cargo
function( Cargo )
CargoReport:Add( string.format( ' - %s (%s) %s - status %s ', Cargo:GetName(), Cargo:GetType(), Cargo:GetTransportationMethod(), Cargo:GetCurrentState() ) )
end
)
self:AddInfo( "Cargo", CargoReport:Text(), Order, Detail, Keep )
return self
end
--- Create the taskinfo Report
-- @param #TASKINFO self
-- @param Core.Report#REPORT Report
-- @param #TASKINFO.Detail Detail The detail Level.
-- @param Wrapper.Group#GROUP ReportGroup
-- @return #TASKINFO self
function TASKINFO:Report( Report, Detail, ReportGroup )
local Line = 0
local LineReport = REPORT:New()
if not self.Task:IsStatePlanned() and not self.Task:IsStateAssigned() then
self.Info = self.PersistentInfo
end
for Key, Data in UTILS.spairs( self.Info.Set, function( t, a, b ) return t[a].Order < t[b].Order end ) do
self:F( { Key = Key, Detail = Detail, Data = Data } )
if Data.Detail:find( Detail ) then
local Text = ""
if Key == "TaskName" then
Key = nil
Text = Data.Data
end
if Key == "Coordinate" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToString( ReportGroup:GetUnit(1), nil, self )
end
if Key == "Threat" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "Counting" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "Targets" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "QFE" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringPressure( ReportGroup:GetUnit(1), nil, self )
end
if Key == "Temperature" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringTemperature( ReportGroup:GetUnit(1), nil, self )
end
if Key == "Wind" then
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringWind( ReportGroup:GetUnit(1), nil, self )
end
if Key == "Cargo" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "Friendlies" then
local DataText = Data.Data -- #string
Text = DataText
end
if Key == "Players" then
local DataText = Data.Data -- #string
Text = DataText
end
if Line < math.floor( Data.Order / 10 ) then
if Line == 0 then
if Text ~= "" then
Report:AddIndent( LineReport:Text( ", " ), "-" )
end
else
if Text ~= "" then
Report:AddIndent( LineReport:Text( ", " ) )
end
end
LineReport = REPORT:New()
Line = math.floor( Data.Order / 10 )
end
if Text ~= "" then
LineReport:Add( ( Key and ( Key .. ":" ) or "" ) .. Text )
end
end
end
Report:AddIndent( LineReport:Text( ", " ) )
end

View File

@@ -1,261 +0,0 @@
--- **Tasking** - The TASK_Protect models tasks for players to protect or capture specific zones.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions: MillerTime
--
-- ===
--
-- @module Tasking.TaskZoneCapture
-- @image MOOSE.JPG
do -- TASK_ZONE_GOAL
--- The TASK_ZONE_GOAL class
-- @type TASK_ZONE_GOAL
-- @field Core.ZoneGoal#ZONE_GOAL ZoneGoal
-- @extends Tasking.Task#TASK
--- # TASK_ZONE_GOAL class, extends @{Tasking.Task#TASK}
--
-- The TASK_ZONE_GOAL class defines the task to protect or capture a protection zone.
-- The TASK_ZONE_GOAL is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses:
--
-- * **None**: Start of the process
-- * **Planned**: The A2G task is planned.
-- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}.
-- * **Success**: The A2G task is successfully completed.
-- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
--
-- ## Set the scoring of achievements in an A2G attack.
--
-- Scoring or penalties can be given in the following circumstances:
--
-- * @{#TASK_ZONE_GOAL.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed.
-- * @{#TASK_ZONE_GOAL.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed.
-- * @{#TASK_ZONE_GOAL.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed.
--
-- @field #TASK_ZONE_GOAL
TASK_ZONE_GOAL = {
ClassName = "TASK_ZONE_GOAL",
}
--- Instantiates a new TASK_ZONE_GOAL.
-- @param #TASK_ZONE_GOAL self
-- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal
-- @return #TASK_ZONE_GOAL self
function TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing )
local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- #TASK_ZONE_GOAL
self:F()
self.ZoneGoal = ZoneGoal
self.TaskType = TaskType
local Fsm = self:GetUnitProcess()
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "StartMonitoring", Rejected = "Reject" } )
Fsm:AddTransition( "Assigned", "StartMonitoring", "Monitoring" )
Fsm:AddTransition( "Monitoring", "Monitor", "Monitoring", {} )
Fsm:AddTransition( "Monitoring", "RouteTo", "Monitoring" )
Fsm:AddProcess( "Monitoring", "RouteToZone", ACT_ROUTE_ZONE:New(), {} )
--Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
--Fsm:AddTransition( "Accounted", "Success", "Success" )
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" )
self:SetTargetZone( self.ZoneGoal:GetZone() )
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_ZONE_GOAL Task
function Fsm:onafterStartMonitoring( TaskUnit, Task )
self:F( { self } )
self:__Monitor( 0.1 )
self:__RouteTo( 0.1 )
end
--- Monitor Loop
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_ZONE_GOAL Task
function Fsm:onafterMonitor( TaskUnit, Task )
self:F( { self } )
self:__Monitor( 15 )
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_ZONE_GOAL Task
function Fsm:onafterRouteTo( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit
if Task:GetTargetZone( TaskUnit ) then
self:__RouteTo( 0.1 )
end
end
return self
end
--- @param #TASK_ZONE_GOAL self
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal Engine.
function TASK_ZONE_GOAL:SetProtect( ZoneGoal )
self.ZoneGoal = ZoneGoal -- Core.ZoneGoal#ZONE_GOAL
end
--- @param #TASK_ZONE_GOAL self
function TASK_ZONE_GOAL:GetPlannedMenuText()
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.ZoneGoal:GetZoneName() .. " )"
end
--- @param #TASK_ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_ZONE_GOAL:SetTargetZone( TargetZone, TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteZone:SetZone( TargetZone )
end
--- @param #TASK_ZONE_GOAL self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map.
function TASK_ZONE_GOAL:GetTargetZone( TaskUnit )
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
return ActRouteZone:GetZone()
end
function TASK_ZONE_GOAL:SetGoalTotal( GoalTotal )
self.GoalTotal = GoalTotal
end
function TASK_ZONE_GOAL:GetGoalTotal()
return self.GoalTotal
end
end
do -- TASK_ZONE_CAPTURE
--- The TASK_ZONE_CAPTURE class
-- @type TASK_ZONE_CAPTURE
-- @field Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal
-- @extends #TASK_ZONE_GOAL
--- # TASK_ZONE_CAPTURE class, extends @{Tasking.TaskZoneGoal#TASK_ZONE_GOAL}
--
-- The TASK_ZONE_CAPTURE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed.
-- These tasks are important to be executed as they will help to achieve air superiority at the vicinity.
--
-- The TASK_ZONE_CAPTURE is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks
-- based on detected enemy ground targets.
--
-- @field #TASK_ZONE_CAPTURE
TASK_ZONE_CAPTURE = {
ClassName = "TASK_ZONE_CAPTURE",
}
--- Instantiates a new TASK_ZONE_CAPTURE.
-- @param #TASK_ZONE_CAPTURE self
-- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoalCoalition
-- @param #string TaskBriefing The briefing of the task.
-- @return #TASK_ZONE_CAPTURE self
function TASK_ZONE_CAPTURE:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing)
local self = BASE:Inherit( self, TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, "CAPTURE", TaskBriefing ) ) -- #TASK_ZONE_CAPTURE
self:F()
Mission:AddTask( self )
self.TaskCoalition = ZoneGoalCoalition:GetCoalition()
self.TaskCoalitionName = ZoneGoalCoalition:GetCoalitionName()
self.TaskZoneName = ZoneGoalCoalition:GetZoneName()
ZoneGoalCoalition:MonitorDestroyedUnits()
self:SetBriefing(
TaskBriefing or
"Capture Zone " .. self.TaskZoneName
)
self:UpdateTaskInfo()
return self
end
--- Instantiates a new TASK_ZONE_CAPTURE.
-- @param #TASK_ZONE_CAPTURE self
function TASK_ZONE_CAPTURE:UpdateTaskInfo()
local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate()
self.TaskInfo:AddCoordinate( ZoneCoordinate, 0, "SOD" )
self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD" )
self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD" )
end
function TASK_ZONE_CAPTURE:ReportOrder( ReportGroup )
local Coordinate = self:GetData( "Coordinate" )
--local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_ZONE_CAPTURE self
-- @param Wrapper.Unit#UNIT TaskUnit
function TASK_ZONE_CAPTURE:OnAfterGoal( From, Event, To, PlayerUnit, PlayerName )
self:F( { PlayerUnit = PlayerUnit } )
if self.ZoneGoal then
if self.ZoneGoal.Goal:IsAchieved() then
self:Success()
local TotalContributions = self.ZoneGoal.Goal:GetTotalContributions()
local PlayerContributions = self.ZoneGoal.Goal:GetPlayerContributions()
self:F( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } )
for PlayerName, PlayerContribution in pairs( PlayerContributions ) do
local Scoring = self:GetScoring()
if Scoring then
Scoring:_AddMissionGoalScore( self.Mission, PlayerName, "Zone " .. self.ZoneGoal:GetZoneName() .." captured", PlayerContribution * 200 / TotalContributions )
end
end
end
end
self:__Goal( -10, PlayerUnit, PlayerName )
end
end

View File

@@ -1,34 +1,37 @@
--- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements.
--
-- ===
-- ![Banner Image](..\Presentations\TASK_A2A\Dia1.JPG)
--
-- ### Author: **FlightControl**
-- ====
--
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Contributions:
--
-- ===
-- ====
--
-- @module Tasking.Task_A2A
-- @image MOOSE.JPG
-- @module Task_A2A
do -- TASK_A2A
--- The TASK_A2A class
-- @type TASK_A2A
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @field Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK
--- Defines Air To Air tasks for a @{Set} of Target Units,
-- based on the tasking capabilities defined in @{Tasking.Task#TASK}.
-- The TASK_A2A is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses:
--- # TASK_A2A class, extends @{Task#TASK}
--
-- The TASK_A2A class defines Air To Air tasks for a @{Set} of Target Units,
-- based on the tasking capabilities defined in @{Task#TASK}.
-- The TASK_A2A is implemented using a @{Fsm#FSM_TASK}, and has the following statuses:
--
-- * **None**: Start of the process
-- * **Planned**: The A2A task is planned.
-- * **Assigned**: The A2A task is assigned to a @{Wrapper.Group#GROUP}.
-- * **Assigned**: The A2A task is assigned to a @{Group#GROUP}.
-- * **Success**: The A2A task is successfully completed.
-- * **Failed**: The A2A task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
--
-- # 1) Set the scoring of achievements in an A2A attack.
-- # 1.1) Set the scoring of achievements in an A2A attack.
--
-- Scoring or penalties can be given in the following circumstances:
--
@@ -44,9 +47,9 @@ do -- TASK_A2A
--- Instantiates a new TASK_A2A.
-- @param #TASK_A2A self
-- @param Tasking.Mission#MISSION Mission
-- @param Core.Set#SET_GROUP SetAttack The set of groups for which the Task can be assigned.
-- @param Set#SET_GROUP SetAttack The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_UNIT UnitSetTargets
-- @param Set#SET_UNIT UnitSetTargets
-- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range.
-- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known.
-- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be.
@@ -61,6 +64,8 @@ do -- TASK_A2A
local Fsm = self:GetUnitProcess()
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "RouteToRendezVous", Rejected = "Reject" } )
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
@@ -70,7 +75,7 @@ do -- TASK_A2A
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, self.TaskType ), {} )
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
@@ -81,22 +86,13 @@ do -- TASK_A2A
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
Fsm:AddTransition( "Failed", "Fail", "Failed" )
---- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #TASK_CARGO Task
function Fsm:OnLeaveAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:SelectAction()
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task
function Fsm:onafterRouteToRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.RendezVousSetUnit
if Task:GetRendezVousZone( TaskUnit ) then
@@ -115,7 +111,7 @@ do -- TASK_A2A
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2A Task
function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit
self:__Engage( 0.1 )
@@ -126,7 +122,7 @@ do -- TASK_A2A
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task#TASK_A2A Task
function Fsm:onafterEngage( TaskUnit, Task )
self:F( { self } )
self:E( { self } )
self:__Account( 0.1 )
self:__RouteToTarget(0.1 )
self:__RouteToTargets( -10 )
@@ -137,7 +133,7 @@ do -- TASK_A2A
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task
function Fsm:onafterRouteToTarget( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.TargetSetUnit
if Task:GetTargetZone( TaskUnit ) then
@@ -145,9 +141,9 @@ do -- TASK_A2A
else
local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if TargetUnit then
local Coordinate = TargetUnit:GetPointVec3()
local Coordinate = TargetUnit:GetCoordinate()
self:T( { TargetCoordinate = Coordinate, Coordinate:GetX(), Coordinate:GetAlt(), Coordinate:GetZ() } )
Task:SetTargetCoordinate( Coordinate, TaskUnit )
Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit )
end
self:__RouteToTargetPoint( 0.1 )
end
@@ -158,7 +154,7 @@ do -- TASK_A2A
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2A#TASK_A2A Task
function Fsm:onafterRouteToTargets( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if TargetUnit then
Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit )
@@ -170,20 +166,11 @@ do -- TASK_A2A
end
--- @param #TASK_A2A self
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
function TASK_A2A:SetTargetSetUnit( TargetSetUnit )
self.TargetSetUnit = TargetSetUnit
end
--- @param #TASK_A2A self
function TASK_A2A:GetPlannedMenuText()
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
end
--- @param #TASK_A2A self
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
@@ -290,67 +277,8 @@ do -- TASK_A2A
return self.GoalTotal
end
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats.
-- @param #TASK_A2A self
function TASK_A2A:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- This method checks every 10 seconds if the goal has been reached of the task.
-- @param #TASK_A2A self
function TASK_A2A:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then
self:Success()
end
self:__Goal( -10 )
end
--- @param #TASK_A2A self
function TASK_A2A:UpdateTaskInfo( DetectedItem )
if self:IsStatePlanned() or self:IsStateAssigned() then
local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate()
self.TaskInfo:AddTaskName( 0, "MSOD" )
self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" )
local ThreatLevel, ThreatText
if DetectedItem then
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem )
else
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
end
self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true )
if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New()
local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit )
if not TargetTypes[TargetType] then
TargetTypes[TargetType] = TargetType
ReportTypes:Add( TargetType )
end
end
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
else
local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
end
end
end
end
@@ -358,16 +286,18 @@ do -- TASK_A2A_INTERCEPT
--- The TASK_A2A_INTERCEPT class
-- @type TASK_A2A_INTERCEPT
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @field Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK
--- Defines an intercept task for a human player to be executed.
--- # TASK_A2A_INTERCEPT class, extends @{Task_A2A#TASK_A2A}
--
-- The TASK_A2A_INTERCEPT class defines an intercept task for a human player to be executed.
-- When enemy planes need to be intercepted by human players, use this task type to urgen the players to get out there!
--
-- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks
-- The TASK_A2A_INTERCEPT is used by the @{Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks
-- based on detected airborne enemy targets intruding friendly airspace.
--
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets.
-- The task is defined for a @{Mission#MISSION}, where a friendly @{Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
--
-- @field #TASK_A2A_INTERCEPT
@@ -391,14 +321,46 @@ do -- TASK_A2A_INTERCEPT
Mission:AddTask( self )
--TODO: Add BR, Altitude, type of planes...
self:SetBriefing(
TaskBriefing or
"Intercept incoming intruders.\n"
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate, 10 )
self:SetInfo( "Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 0 )
return self
end
--- @param #TASK_A2A_INTERCEPT self
-- @param Wrapper.Group#GROUP ReportGroup
function TASK_A2A_INTERCEPT:ReportOrder( ReportGroup )
self:F( { TaskInfo = self.TaskInfo } )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2A_INTERCEPT self
function TASK_A2A_INTERCEPT:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then
self:Success()
end
self:__Goal( -10 )
end
--- Set a score when a target in scope of the A2A attack, has been destroyed .
-- @param #TASK_A2A_INTERCEPT self
-- @param #string PlayerName The name of the player.
@@ -455,18 +417,20 @@ do -- TASK_A2A_SWEEP
--- The TASK_A2A_SWEEP class
-- @type TASK_A2A_SWEEP
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @field Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK
--- Defines a sweep task for a human player to be executed.
--- # TASK_A2A_SWEEP class, extends @{Task_A2A#TASK_A2A}
--
-- The TASK_A2A_SWEEP class defines a sweep task for a human player to be executed.
-- A sweep task needs to be given when targets were detected but somehow the detection was lost.
-- Most likely, these enemy planes are hidden in the mountains or are flying under radar.
-- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters.
--
-- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks
-- The TASK_A2A_SWEEP is used by the @{Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks
-- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds.
--
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets.
-- The task is defined for a @{Mission#MISSION}, where a friendly @{Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
--
-- @field #TASK_A2A_SWEEP
@@ -490,14 +454,31 @@ do -- TASK_A2A_SWEEP
Mission:AddTask( self )
--TODO: Add BR, Altitude, type of planes...
self:SetBriefing(
TaskBriefing or
"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n"
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate, 10 )
self:SetInfo( "Assumed Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Lost Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 0 )
return self
end
function TASK_A2A_SWEEP:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2A_SWEEP self
function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
@@ -564,16 +545,18 @@ do -- TASK_A2A_ENGAGE
--- The TASK_A2A_ENGAGE class
-- @type TASK_A2A_ENGAGE
-- @field Core.Set#SET_UNIT TargetSetUnit
-- @field Set#SET_UNIT TargetSetUnit
-- @extends Tasking.Task#TASK
--- Defines an engage task for a human player to be executed.
--- # TASK_A2A_ENGAGE class, extends @{Task_A2A#TASK_A2A}
--
-- The TASK_A2A_ENGAGE class defines an engage task for a human player to be executed.
-- When enemy planes are close to human players, use this task type is used urge the players to get out there!
--
-- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks
-- The TASK_A2A_ENGAGE is used by the @{Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks
-- based on detected airborne enemy targets intruding friendly airspace.
--
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets.
-- The task is defined for a @{Mission#MISSION}, where a friendly @{Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets.
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
--
-- @field #TASK_A2A_ENGAGE
@@ -597,13 +580,41 @@ do -- TASK_A2A_ENGAGE
Mission:AddTask( self )
--TODO: Add BR, Altitude, type of planes...
self:SetBriefing(
TaskBriefing or
"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n"
)
local TargetCoordinate = TargetSetUnit:GetFirst():GetCoordinate()
self:SetInfo( "Coordinates", TargetCoordinate, 10 )
self:SetInfo( "Threat", "[" .. string.rep( "", TargetSetUnit:CalculateThreatLevelA2G() ) .. "]", 11 )
local DetectedItemsCount = TargetSetUnit:Count()
local DetectedItemsTypes = TargetSetUnit:GetTypeNames()
self:SetInfo( "Targets", string.format( "%d of %s", DetectedItemsCount, DetectedItemsTypes ), 0 )
return self
end
function TASK_A2A_ENGAGE:ReportOrder( ReportGroup )
local Coordinate = self.TaskInfo.Coordinates.TaskInfoText
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
return Distance
end
--- @param #TASK_A2A_ENGAGE self
function TASK_A2A_ENGAGE:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:Count() == 0 then
self:Success()
end
self:__Goal( -10 )
end
--- Set a score when a target in scope of the A2A attack, has been destroyed .
-- @param #TASK_A2A_ENGAGE self

View File

@@ -1,27 +1,16 @@
--- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network.
--- **Tasking** - The TASK_A2A_DISPATCHER creates and manages player TASK_A2A tasks based on detected targets.
--
-- **Features:**
-- The @{#TASK_A2A_DISPATCHER} classes implement the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups.
--
-- * Dynamically assign tasks to human players based on detected targets.
-- * Dynamically change the tasks as the tactical situation evolves during the mission.
-- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP.
-- * Dynamically assign (GCI) Ground Control Intercept tasks for human players to perform GCI.
-- * Dynamically assign Engage tasks for human players to engage on close-by airborne bogeys.
-- * Define and use an EWR (Early Warning Radar) network.
-- * Define different ranges to engage upon intruders.
-- * Keep task achievements.
-- * Score task achievements.
--
-- ===
-- ====
--
-- ### Author: **FlightControl**
-- ### Author: **Sven Van de Velde (FlightControl)**
--
-- ### Contributions:
--
-- ===
-- ====
--
-- @module Tasking.Task_A2A_Dispatcher
-- @image Task_A2A_Dispatcher.JPG
-- @module Task_A2A_Dispatcher
do -- TASK_A2A_DISPATCHER
@@ -29,7 +18,11 @@ do -- TASK_A2A_DISPATCHER
-- @type TASK_A2A_DISPATCHER
-- @extends Tasking.DetectionManager#DETECTION_MANAGER
--- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups.
--- # TASK_A2A_DISPATCHER class, extends @{Tasking#DETECTION_MANAGER}
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia1.JPG)
--
-- The @{#TASK_A2A_DISPATCHER} class implements the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups.
--
-- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG)
--
@@ -90,7 +83,7 @@ do -- TASK_A2A_DISPATCHER
-- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map.
-- It all depends on what the desired effect is.
--
-- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class.
-- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class.
-- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network,
-- increasing or decreasing the radar coverage of the Early Warning System.
--
@@ -100,7 +93,7 @@ do -- TASK_A2A_DISPATCHER
--
-- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 )
-- EWRDetection:SetFriendliesRange( 10000 )
-- EWRDetection:SetRefreshTimeInterval(30)
-- EWRDetection:SetDetectionInterval(30)
--
-- -- Setup the A2A dispatcher, and initialize it.
-- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection )
@@ -189,7 +182,7 @@ do -- TASK_A2A_DISPATCHER
--- TASK_A2A_DISPATCHER constructor.
-- @param #TASK_A2A_DISPATCHER self
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
-- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission.
-- @param Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission.
-- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players.
-- @return #TASK_A2A_DISPATCHER self
function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection )
@@ -204,10 +197,9 @@ do -- TASK_A2A_DISPATCHER
-- TODO: Check detection through radar.
self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER )
self.Detection:InitDetectRadar( true )
self.Detection:SetRefreshTimeInterval( 30 )
self.Detection:SetDetectionInterval( 30 )
self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign.
-- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign
@@ -218,7 +210,7 @@ do -- TASK_A2A_DISPATCHER
-- @param Tasking.Task_A2A#TASK_A2A Task
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #string PlayerName
self:__Start( 5 )
return self
@@ -258,7 +250,7 @@ do -- TASK_A2A_DISPATCHER
--- Creates an INTERCEPT task when there are targets for it.
-- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem )
self:F( { DetectedItem.ItemID } )
@@ -285,7 +277,7 @@ do -- TASK_A2A_DISPATCHER
--- Creates an SWEEP task when there are targets for it.
-- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem )
self:F( { DetectedItem.ItemID } )
@@ -311,7 +303,7 @@ do -- TASK_A2A_DISPATCHER
--- Creates an ENGAGE task when there are human friendlies airborne near the targets.
-- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
-- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return Set#SET_UNIT TargetSetUnit: The target set of units.
-- @return #nil If there are no targets to be set.
function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem )
self:F( { DetectedItem.ItemID } )
@@ -344,7 +336,7 @@ do -- TASK_A2A_DISPATCHER
-- @param #TASK_A2A_DISPATCHER self
-- @param Tasking.Mission#MISSION Mission
-- @param Tasking.Task#TASK Task
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Detection#DETECTION_BASE} derived object.
-- @param #boolean DetectedItemID
-- @param #boolean DetectedItemChange
-- @return Tasking.Task#TASK
@@ -383,14 +375,16 @@ do -- TASK_A2A_DISPATCHER
end
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
--DetectedSet:Flush( self )
--self:F( { DetectedSetCount = DetectedSet:Count() } )
--DetectedSet:Flush()
--self:E( { DetectedSetCount = DetectedSet:Count() } )
if DetectedSet:Count() == 0 then
Remove = true
end
if DetectedItemChanged == true or Remove then
Task = self:RemoveTask( DetectedItemIndex )
--self:E( "Removing Tasking: " .. Task:GetTaskName() )
Mission:RemoveTask( Task )
self.Tasks[DetectedItemIndex] = nil
end
end
end
@@ -405,7 +399,7 @@ do -- TASK_A2A_DISPATCHER
function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem )
local DetectedSet = DetectedItem.Set
local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE )
local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem )
local FriendlyTypes = {}
local FriendliesCount = 0
@@ -426,7 +420,7 @@ do -- TASK_A2A_DISPATCHER
end
--self:F( { FriendliesCount = FriendliesCount } )
--self:E( { FriendliesCount = FriendliesCount } )
local FriendlyTypesReport = REPORT:New()
@@ -459,7 +453,7 @@ do -- TASK_A2A_DISPATCHER
for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do
local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT
local PlayerName = PlayerUnit:GetPlayerName()
--self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
--self:E( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
if PlayerUnit:IsAirPlane() and PlayerName ~= nil then
local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel()
PlayersCount = PlayersCount + 1
@@ -472,6 +466,8 @@ do -- TASK_A2A_DISPATCHER
end
--self:E( { PlayersCount = PlayersCount } )
local PlayerTypesReport = REPORT:New()
if PlayersCount > 0 then
@@ -486,18 +482,13 @@ do -- TASK_A2A_DISPATCHER
return PlayersCount, PlayerTypesReport
end
function TASK_A2A_DISPATCHER:RemoveTask( TaskIndex )
self.Mission:RemoveTask( self.Tasks[TaskIndex] )
self.Tasks[TaskIndex] = nil
end
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
--- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}.
-- @param #TASK_A2A_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Detection#DETECTION_BASE} derived object.
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_A2A_DISPATCHER:ProcessDetected( Detection )
self:F()
self:E()
local AreaMsg = {}
local TaskMsg = {}
@@ -513,13 +504,14 @@ do -- TASK_A2A_DISPATCHER
for TaskIndex, TaskData in pairs( self.Tasks ) do
local Task = TaskData -- Tasking.Task#TASK
if Task:IsStatePlanned() then
local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex )
local DetectedItem = Detection:GetDetectedItem( TaskIndex )
if not DetectedItem then
local TaskText = Task:GetName()
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup )
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetName() ), TaskGroup )
end
Task = self:RemoveTask( TaskIndex )
Mission:RemoveTask( Task )
self.Tasks[TaskIndex] = nil
end
end
end
@@ -531,8 +523,8 @@ do -- TASK_A2A_DISPATCHER
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
local DetectedCount = DetectedSet:Count()
local DetectedZone = DetectedItem.Zone
--self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
--DetectedSet:Flush( self )
--self:E( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
--DetectedSet:Flush()
local DetectedID = DetectedItem.ID
local TaskIndex = DetectedItem.Index
@@ -546,58 +538,36 @@ do -- TASK_A2A_DISPATCHER
local TargetSetUnit = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed...
if TargetSetUnit then
Task = TASK_A2A_ENGAGE:New( Mission, self.SetGroup, string.format( "ENGAGE.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
else
local TargetSetUnit = self:EvaluateINTERCEPT( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed...
if TargetSetUnit then
Task = TASK_A2A_INTERCEPT:New( Mission, self.SetGroup, string.format( "INTERCEPT.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
else
local TargetSetUnit = self:EvaluateSWEEP( DetectedItem ) -- Returns a SetUnit
if TargetSetUnit then
Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit )
Task:SetDetection( Detection, DetectedItem )
Task:UpdateTaskInfo( DetectedItem )
end
end
end
if Task then
self.Tasks[TaskIndex] = Task
Task:SetTargetZone( DetectedZone, DetectedItem.Coordinate.y, DetectedItem.Coordinate.Heading )
Task:SetTargetZone( DetectedZone, DetectedSet:GetFirst():GetAltitude(), DetectedSet:GetFirst():GetHeading() )
Task:SetDispatcher( self )
Mission:AddTask( Task )
function Task.OnEnterSuccess( Task, From, Event, To )
self:Success( Task )
end
function Task.onenterCancelled( Task, From, Event, To )
self:Cancelled( Task )
end
function Task.onenterFailed( Task, From, Event, To )
self:Failed( Task )
end
function Task.onenterAborted( Task, From, Event, To )
self:Aborted( Task )
end
TaskReport:Add( Task:GetName() )
else
self:F("This should not happen")
self:E("This should not happen")
end
end
if Task then
local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE )
Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" )
local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem )
Task:SetInfo( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 30 )
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" )
Task:SetInfo( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 31 )
end
-- OK, so the tasking has been done, now delete the changes reported for the area.
@@ -611,7 +581,7 @@ do -- TASK_A2A_DISPATCHER
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetName(), TaskText ), TaskGroup )
end
end

Some files were not shown because too many files have changed in this diff Show More