mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Merge branch 'FF/Ops' into FF/OpsDev
This commit is contained in:
commit
a1c9c1538d
@ -26,24 +26,25 @@ init:
|
||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
||||
install:
|
||||
- cmd:
|
||||
# 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
|
||||
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
|
||||
cache:
|
||||
C:\ProgramData\chocolatey\lib
|
||||
C:\ProgramData\chocolatey\bin
|
||||
|
||||
|
||||
|
||||
@ -51,8 +52,9 @@ build_script:
|
||||
- ps: |
|
||||
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
|
||||
{
|
||||
echo "Hello World!"
|
||||
$apiUrl = 'https://ci.appveyor.com/api'
|
||||
$token = 'qts80b5kpq0ooj4x6vvw'
|
||||
$token = 'v2.6hcv3ige78kg3yvg4ge8'
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-type" = "application/json"
|
||||
@ -65,7 +67,7 @@ build_script:
|
||||
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
|
||||
{
|
||||
$apiUrl = 'https://ci.appveyor.com/api'
|
||||
$token = 'qts80b5kpq0ooj4x6vvw'
|
||||
$token = 'v2.6hcv3ige78kg3yvg4ge8'
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-type" = "application/json"
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@ -18,6 +18,8 @@ local.properties
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# AppVeyor
|
||||
.appveyor/
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
@ -26,6 +28,13 @@ local.properties
|
||||
.buildpath
|
||||
|
||||
|
||||
#####################
|
||||
## Visual Studio Code
|
||||
#####################
|
||||
*.code-workspace
|
||||
.vscode/
|
||||
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
54
.luacheckrc
Normal file
54
.luacheckrc
Normal file
@ -0,0 +1,54 @@
|
||||
ignore = {
|
||||
"011", -- A syntax error.
|
||||
"021", -- An invalid inline option.
|
||||
"022", -- An unpaired inline push directive.
|
||||
"023", -- An unpaired inline pop directive.
|
||||
"111", -- Setting an undefined global variable.
|
||||
"112", -- Mutating an undefined global variable.
|
||||
"113", -- Accessing an undefined global variable.
|
||||
"121", -- Setting a read-only global variable.
|
||||
"122", -- Setting a read-only field of a global variable.
|
||||
"131", -- Unused implicitly defined global variable.
|
||||
"142", -- Setting an undefined field of a global variable.
|
||||
"143", -- Accessing an undefined field of a global variable.
|
||||
"211", -- Unused local variable.
|
||||
"212", -- Unused argument.
|
||||
"213", -- Unused loop variable.
|
||||
"221", -- Local variable is accessed but never set.
|
||||
"231", -- Local variable is set but never accessed.
|
||||
"232", -- An argument is set but never accessed.
|
||||
"233", -- Loop variable is set but never accessed.
|
||||
"241", -- Local variable is mutated but never accessed.
|
||||
"311", -- Value assigned to a local variable is unused.
|
||||
"312", -- Value of an argument is unused.
|
||||
"313", -- Value of a loop variable is unused.
|
||||
"314", -- Value of a field in a table literal is unused.
|
||||
"321", -- Accessing uninitialized local variable.
|
||||
"331", -- Value assigned to a local variable is mutated but never accessed.
|
||||
"341", -- Mutating uninitialized local variable.
|
||||
"411", -- Redefining a local variable.
|
||||
"412", -- Redefining an argument.
|
||||
"413", -- Redefining a loop variable.
|
||||
"421", -- Shadowing a local variable.
|
||||
"422", -- Shadowing an argument.
|
||||
"423", -- Shadowing a loop variable.
|
||||
"431", -- Shadowing an upvalue.
|
||||
"432", -- Shadowing an upvalue argument.
|
||||
"433", -- Shadowing an upvalue loop variable.
|
||||
"511", -- Unreachable code.
|
||||
"512", -- Loop can be executed at most once.
|
||||
"521", -- Unused label.
|
||||
"531", -- Left-hand side of an assignment is too short.
|
||||
"532", -- Left-hand side of an assignment is too long.
|
||||
"541", -- An empty do end block.
|
||||
"542", -- An empty if branch.
|
||||
"551", -- An empty statement.
|
||||
"561", -- Cyclomatic complexity of a function is too high.
|
||||
"571", -- A numeric for loop goes from #(expr) down to 1 or less without negative step.
|
||||
"611", -- A line consists of nothing but whitespace.
|
||||
"612", -- A line contains trailing whitespace.
|
||||
"613", -- Trailing whitespace in a string.
|
||||
"614", -- Trailing whitespace in a comment.
|
||||
"621", -- Inconsistent indentation (SPACE followed by TAB).
|
||||
"631", -- Line is too long.
|
||||
}
|
||||
@ -143,7 +143,7 @@ end
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolAltType )
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -658,7 +658,9 @@ do -- AI_A2A_DISPATCHER
|
||||
-- of the race track will randomly selected between 90 (West to East) and 180 (North to South) degrees.
|
||||
-- After a random duration between 10 and 20 minutes, the flight will get a new random orbit location.
|
||||
--
|
||||
-- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the
|
||||
-- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the CAP command used earlier on, e.g.
|
||||
--
|
||||
-- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 )
|
||||
--
|
||||
-- Also note that the center of the race track pattern is chosen randomly within the patrol zone and can be close the the boarder of the zone. Hence, it cannot be guaranteed that the
|
||||
-- whole pattern lies within the patrol zone.
|
||||
@ -929,6 +931,8 @@ do -- AI_A2A_DISPATCHER
|
||||
self.DefenderTasks = {} -- The Defenders Tasks.
|
||||
self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons.
|
||||
|
||||
self.SetSendPlayerMessages = false --#boolean Flash messages to player
|
||||
|
||||
-- TODO: Check detection through radar.
|
||||
self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } )
|
||||
--self.Detection:InitDetectRadar( true )
|
||||
@ -949,7 +953,6 @@ do -- AI_A2A_DISPATCHER
|
||||
self:SetDefaultCapTimeInterval( 180, 600 ) -- Between 180 and 600 seconds.
|
||||
self:SetDefaultCapLimit( 1 ) -- Maximum one CAP per squadron.
|
||||
|
||||
|
||||
self:AddTransition( "Started", "Assign", "Started" )
|
||||
|
||||
--- OnAfter Transition Handler for Event Assign.
|
||||
@ -2356,6 +2359,12 @@ do -- AI_A2A_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
-- @param #AI_A2G_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function AI_A2A_DISPATCHER:SetSendMessages( onoff )
|
||||
self.SetSendPlayerMessages = onoff
|
||||
end
|
||||
|
||||
--- Sets flights to take-off in the air, as part of the defense system.
|
||||
-- @param #AI_A2A_DISPATCHER self
|
||||
@ -2919,9 +2928,13 @@ do -- AI_A2A_DISPATCHER
|
||||
-- @return #AI_A2A_DISPATCHER.Squadron Squadron The squadron.
|
||||
function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender )
|
||||
self.Defenders = self.Defenders or {}
|
||||
local DefenderName = Defender:GetName()
|
||||
self:F( { DefenderName = DefenderName } )
|
||||
return self.Defenders[ DefenderName ]
|
||||
if Defender ~= nil then
|
||||
local DefenderName = Defender:GetName()
|
||||
self:F( { DefenderName = DefenderName } )
|
||||
return self.Defenders[ DefenderName ]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -3240,7 +3253,9 @@ do -- AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
|
||||
if Squadron then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup )
|
||||
end
|
||||
AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling
|
||||
end
|
||||
end
|
||||
@ -3252,10 +3267,10 @@ do -- AI_A2A_DISPATCHER
|
||||
self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To )
|
||||
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
if Squadron then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
|
||||
if Squadron and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
|
||||
end
|
||||
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
@ -3271,7 +3286,7 @@ do -- AI_A2A_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
if Squadron then
|
||||
if Squadron and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
|
||||
end
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
@ -3459,10 +3474,10 @@ do -- AI_A2A_DISPATCHER
|
||||
local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup )
|
||||
|
||||
if DefenderTarget then
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеса вверх.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеÑ<EFBFBD>а вверх.", DefenderGroup )
|
||||
end
|
||||
--Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit
|
||||
Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
|
||||
@ -3480,11 +3495,11 @@ do -- AI_A2A_DISPATCHER
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват самолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "DE" then
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват Ñ<>амолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "DE" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
@ -3502,10 +3517,10 @@ do -- AI_A2A_DISPATCHER
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие Ñ<>амолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
@ -3520,10 +3535,10 @@ do -- AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
|
||||
if Squadron then
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаясь на базу.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаÑ<EFBFBD>Ñ<EFBFBD>ÑŒ на базу.", DefenderGroup )
|
||||
end
|
||||
end
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
@ -3551,10 +3566,10 @@ do -- AI_A2A_DISPATCHER
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в посадка на базу.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие Ñ<>амолеты в поÑ<C2BE>адка на базу.", DefenderGroup )
|
||||
end
|
||||
|
||||
if Action and Action == "Destroy" then
|
||||
@ -4523,5 +4538,5 @@ do
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
@ -431,7 +431,7 @@ do -- AI_A2G_DISPATCHER
|
||||
--
|
||||
-- ### 2.2. The **Defense Reactivity**.
|
||||
--
|
||||
-- There are 5 levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is
|
||||
-- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is
|
||||
-- also determined by the distance of the enemy ground target to the defense coordinate.
|
||||
-- If you want to have a **low** defense reactivity, that is, the probability that an A2G defense will engage to the enemy ground target, then
|
||||
-- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods
|
||||
@ -999,7 +999,9 @@ do -- AI_A2G_DISPATCHER
|
||||
-- self.Detection:InitDetectRadar( false )
|
||||
-- self.Detection:InitDetectVisual( true )
|
||||
-- self.Detection:SetRefreshTimeInterval( 30 )
|
||||
|
||||
|
||||
self.SetSendPlayerMessages = false --flash messages to players
|
||||
|
||||
self:SetDefenseRadius()
|
||||
self:SetDefenseLimit( nil )
|
||||
self:SetDefenseApproach( AI_A2G_DISPATCHER.DefenseApproach.Random )
|
||||
@ -3699,7 +3701,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
|
||||
if Squadron then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
|
||||
end
|
||||
AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit
|
||||
end
|
||||
end
|
||||
@ -3711,7 +3715,7 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
if Squadron then
|
||||
if Squadron and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
|
||||
end
|
||||
|
||||
@ -3730,8 +3734,9 @@ do -- AI_A2G_DISPATCHER
|
||||
if Squadron and AttackSetUnit:Count() > 0 then
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -3745,8 +3750,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
if FirstUnit then
|
||||
local Coordinate = FirstUnit:GetCoordinate()
|
||||
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -3757,8 +3763,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
|
||||
end
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
end
|
||||
|
||||
@ -3770,7 +3777,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." )
|
||||
end
|
||||
if DefenderGroup:IsAboveRunway() then
|
||||
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
|
||||
DefenderGroup:Destroy()
|
||||
@ -3785,8 +3794,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
|
||||
end
|
||||
if Action and Action == "Destroy" then
|
||||
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
|
||||
DefenderGroup:Destroy()
|
||||
@ -3842,7 +3852,9 @@ do -- AI_A2G_DISPATCHER
|
||||
self:F( { DefenderTarget = DefenderTarget } )
|
||||
|
||||
if DefenderTarget then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
|
||||
end
|
||||
AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
|
||||
end
|
||||
end
|
||||
@ -3857,8 +3869,9 @@ do -- AI_A2G_DISPATCHER
|
||||
if Squadron then
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
end
|
||||
@ -3873,8 +3886,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
if FirstUnit then
|
||||
local Coordinate = FirstUnit:GetCoordinate()
|
||||
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -3884,8 +3898,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
|
||||
end
|
||||
self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To )
|
||||
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
@ -3899,8 +3914,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
--Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
|
||||
end
|
||||
if DefenderGroup:IsAboveRunway() then
|
||||
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
|
||||
DefenderGroup:Destroy()
|
||||
@ -3915,8 +3931,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
|
||||
end
|
||||
if Action and Action == "Destroy" then
|
||||
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
|
||||
DefenderGroup:Destroy()
|
||||
@ -3956,7 +3973,7 @@ do -- AI_A2G_DISPATCHER
|
||||
local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate )
|
||||
|
||||
-- Now check if this coordinate is not in a danger zone, meaning, that the attack line is not crossing other coordinates.
|
||||
-- (y1 – y2)x + (x2 – x1)y + (x1y2 – x2y1) = 0
|
||||
-- (y1 - y2)x + (x2 - x1)y + (x1y2 - x2y1) = 0
|
||||
|
||||
local c1 = DefenseCoordinate
|
||||
local c2 = AttackCoordinate
|
||||
@ -4017,7 +4034,7 @@ do -- AI_A2G_DISPATCHER
|
||||
for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do
|
||||
|
||||
-- Here we check if the defenders have a defense line to the attackers.
|
||||
-- If the attackers are behind enemy lines or too close to an other defense line; then don´t engage.
|
||||
-- If the attackers are behind enemy lines or too close to an other defense line; then don't engage.
|
||||
local DefenseCoordinate = DefenderGroup:GetCoordinate()
|
||||
local HasDefenseLine = self:HasDefenseLine( DefenseCoordinate, DetectedItem )
|
||||
|
||||
@ -4320,7 +4337,7 @@ do -- AI_A2G_DISPATCHER
|
||||
|
||||
-- Show tactical situation
|
||||
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
|
||||
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) )
|
||||
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) )
|
||||
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
|
||||
local Defender = Defender -- Wrapper.Group#GROUP
|
||||
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
|
||||
@ -4537,7 +4554,7 @@ do -- AI_A2G_DISPATCHER
|
||||
if self.TacticalDisplay then
|
||||
-- Show tactical situation
|
||||
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
|
||||
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) )
|
||||
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) )
|
||||
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
|
||||
local Defender = Defender -- Wrapper.Group#GROUP
|
||||
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
|
||||
@ -4706,6 +4723,12 @@ do
|
||||
local PatrolTaskType = PatrolTaskTypes[math.random(1,3)]
|
||||
self:Patrol( SquadronName, PatrolTaskType )
|
||||
end
|
||||
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
-- @param #AI_A2G_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function AI_A2G_DISPATCHER:SetSendMessages( onoff )
|
||||
self.SetSendPlayerMessages = onoff
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -595,19 +595,24 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromCoord = AIGroup:GetCoordinate()
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate()
|
||||
|
||||
if not self.RTBMinSpeed and not self.RTBMaxSpeed then
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
|
||||
local ToTargetVec3 = ToTargetCoord:GetVec3()
|
||||
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+1000 -- let's set this 1000m/3000 feet above ground
|
||||
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
|
||||
|
||||
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
|
||||
local RTBSpeedMax = AIGroup:GetSpeedMax()
|
||||
self:SetRTBSpeed( RTBSpeedMax * 0.25, RTBSpeedMax * 0.25 )
|
||||
self:SetRTBSpeed( RTBSpeedMax * 0.5, RTBSpeedMax * 0.6 )
|
||||
end
|
||||
|
||||
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
|
||||
local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) )
|
||||
--local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) )
|
||||
|
||||
local Distance = FromCoord:Get2DDistance( ToTargetCoord )
|
||||
local Distance = FromCoord:Get2DDistance( ToTargetCoord2 )
|
||||
|
||||
local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
|
||||
--local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
|
||||
local ToAirbaseCoord = ToTargetCoord2
|
||||
|
||||
if Distance < 5000 then
|
||||
self:I( "RTB and near the airbase!" )
|
||||
self:Home()
|
||||
|
||||
@ -418,6 +418,7 @@ end
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
|
||||
|
||||
@ -425,7 +426,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:Count()
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
@ -510,6 +511,7 @@ end
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
|
||||
|
||||
@ -517,8 +519,8 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:Count()
|
||||
self:I({AttackCount = AttackCount})
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
self:T({AttackCount = AttackCount})
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -35,10 +35,9 @@ AI_CARGO = {
|
||||
|
||||
--- Creates a new AI_CARGO object.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param Core.Set#SET_CARGO CargoSet
|
||||
-- @param #number CombatRadius
|
||||
-- @return #AI_CARGO
|
||||
-- @param Wrapper.Group#GROUP Carrier Cargo carrier group.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport.
|
||||
-- @return #AI_CARGO self
|
||||
function AI_CARGO:New( Carrier, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO
|
||||
@ -52,7 +51,8 @@ function AI_CARGO:New( Carrier, CargoSet )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
|
||||
self:AddTransition( "*", "Load", "Boarding" )
|
||||
self:AddTransition( { "Boarding", "Loaded" }, "Board", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Board", "Boarding" )
|
||||
self:AddTransition( "Loaded", "Board", "Loaded" )
|
||||
self:AddTransition( "Boarding", "Loaded", "Boarding" )
|
||||
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
|
||||
|
||||
@ -393,7 +393,7 @@ end
|
||||
function AI_CARGO:onafterBoard( Carrier, From, Event, To, Cargo, CarrierUnit, PickupZone )
|
||||
self:F( { Carrier, From, Event, To, Cargo, CarrierUnit:GetName() } )
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
if Carrier and Carrier:IsAlive() and From == "Boarding" then
|
||||
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } )
|
||||
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
|
||||
self:__Board( -10, Cargo, CarrierUnit, PickupZone )
|
||||
@ -509,7 +509,7 @@ end
|
||||
function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
if Carrier and Carrier:IsAlive() and From == "Unboarding" then
|
||||
if not Cargo:IsUnLoaded() then
|
||||
self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
return
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of cargo using ground vehicles.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -157,6 +157,45 @@ function AI_CARGO_APC:SetCarrier( CargoCarrier )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Find a free Carrier within a radius.
|
||||
-- @param #AI_CARGO_APC self
|
||||
@ -350,10 +389,13 @@ function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CARGO_APC
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
--- Pickup task function. Triggers Load event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC sel `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param #number Speed Speed (not used).
|
||||
-- @param Core.Zone#ZONE PickupZone Pickup zone.
|
||||
function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
|
||||
|
||||
@ -362,8 +404,12 @@ function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_APC._Deploy( APC, self, Coordinate, DeployZone )
|
||||
--- Deploy task function. Triggers Unload event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC self `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param Core.Zone#ZONE DeployZone Deploy zone.
|
||||
function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
|
||||
|
||||
@ -392,12 +438,20 @@ function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, He
|
||||
self.RoutePickup = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.pickupOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
|
||||
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
@ -428,18 +482,34 @@ function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, He
|
||||
|
||||
self.RouteDeploy = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
|
||||
-- Set speed in km/h.
|
||||
local speedmax=APC:GetSpeedMax()
|
||||
local _speed=Speed or speedmax*0.5
|
||||
_speed=math.min(_speed, speedmax)
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.deployOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
-- Task function
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
-- Last waypoint
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Set task function
|
||||
APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Route group
|
||||
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
|
||||
-- Call parent function.
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
end
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo).
|
||||
--- **AI** - Models the intelligent transportation of cargo using airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -408,9 +408,6 @@ function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Route the airplane from one airport or it's current position to another airbase.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed.
|
||||
@ -438,14 +435,10 @@ function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled
|
||||
|
||||
-- To point.
|
||||
local AirbasePointVec2 = Airbase:GetPointVec2()
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(
|
||||
POINT_VEC3.RoutePointAltType.BARO,
|
||||
"Land",
|
||||
"Landing",
|
||||
Speed or Airplane:GetSpeedMax()*0.8
|
||||
)
|
||||
ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
ToWaypoint["speed_locked"] = true
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase)
|
||||
|
||||
--ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
--ToWaypoint["speed_locked"] = true
|
||||
|
||||
|
||||
-- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
@ -1104,7 +1104,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor()
|
||||
-- The Pickup sequence ...
|
||||
-- Check if this Carrier need to go and Pickup something...
|
||||
-- So, if the cargo bay is not full yet with cargo to be loaded ...
|
||||
self:I( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
|
||||
self:T( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
|
||||
if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then
|
||||
-- ok, so there is a free Carrier
|
||||
-- now find the first cargo that is Unloaded
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--- **AI** -- (2.4) - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
@ -181,25 +181,36 @@ function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZon
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- AI cargo
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param Wrapper.Group#GROUP APC The APC carrier.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Cargo set.
|
||||
-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object.
|
||||
function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet )
|
||||
|
||||
return AI_CARGO_APC:New( APC, CargoSet, self.CombatRadius )
|
||||
local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius)
|
||||
|
||||
aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation)
|
||||
aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation)
|
||||
|
||||
return aicargoapc
|
||||
end
|
||||
|
||||
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
|
||||
-- This is only valid for APCs and trucks etc, thus ground vehicles.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
|
||||
-- When the combat radius is 0, no defense will happen of the carrier.
|
||||
-- When the combat radius is 0 (default), no defense will happen of the carrier.
|
||||
-- When the combat radius is not provided, no defense will happen!
|
||||
-- @return #AI_CARGO_DISPATCHER_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- Disembark the infantry when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( true )
|
||||
-- AICargoDispatcher:SetCombatRadius( 500 )
|
||||
--
|
||||
-- -- Keep the cargo in the carrier when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( false )
|
||||
-- AICargoDispatcher:SetCombatRadius( 0 )
|
||||
function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
@ -207,3 +218,41 @@ function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
193
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua
Normal file
193
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua
Normal file
@ -0,0 +1,193 @@
|
||||
--- **AI** -- (2.5.1) - Models the intelligent transportation of infantry and other cargo using Ships
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Transport cargo to various deploy zones using naval vehicles.
|
||||
-- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates.
|
||||
-- * Define a deploy zone of various types to determine the destination of the cargo.
|
||||
-- * Ships will follow shipping lanes as defined in the Mission Editor.
|
||||
-- * Multiple ships can transport multiple cargo as a single group.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- NEED TO DO
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_SHIP
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo transportation capability for AI groups.
|
||||
--
|
||||
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
|
||||
--
|
||||
-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framwork.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_SHIP constructor.
|
||||
--
|
||||
-- * @{AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 3) Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
--
|
||||
-- # 4) Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
--
|
||||
-- # 5) Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_SHIP
|
||||
AI_CARGO_DISPATCHER_SHIP = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_SHIP"
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
-- @param #AI_CARGO_DISPATCHER_SHIP self
|
||||
-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_DISPATCHER_SHIP
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart()
|
||||
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
-- NEED MORE THOUGHT - ShippingLane is part of Warehouse.......
|
||||
-- local ShippingLane = GROUP:New():FilterPrefixes( "ShippingLane" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane )
|
||||
-- AICargoDispatcherShip:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) )
|
||||
|
||||
self:SetPickupSpeed( 60, 10 )
|
||||
self:SetDeploySpeed( 60, 10 )
|
||||
|
||||
self:SetPickupRadius( 500, 6000 )
|
||||
self:SetDeployRadius( 500, 6000 )
|
||||
|
||||
self:SetPickupHeight( 0, 0 )
|
||||
self:SetDeployHeight( 0, 0 )
|
||||
|
||||
self:SetShippingLane( ShippingLane )
|
||||
|
||||
self:SetMonitorTimeInterval( 600 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet )
|
||||
|
||||
return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane )
|
||||
end
|
||||
@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo).
|
||||
--- **AI** - Models the intelligent transportation of cargo using helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -33,7 +33,7 @@
|
||||
--
|
||||
-- ## Infantry health.
|
||||
--
|
||||
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
|
||||
-- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
|
||||
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
|
||||
@ -67,16 +67,11 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
|
||||
self:AddTransition( "Unloaded", "Pickup", "*" )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
|
||||
self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Board", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Loaded", "Boarding" )
|
||||
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
|
||||
self:AddTransition( "Loaded", "Unload", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
|
||||
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Unboarding", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Unboard", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "*", "PickedUp", "*" )
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
@ -100,13 +95,31 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string 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.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object.
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
|
||||
@ -122,7 +135,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
@ -132,21 +145,42 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string 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.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deployed Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Home Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Home
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
--- Home Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Home
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
-- We need to capture the Crash events for the helicopters.
|
||||
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
|
||||
@ -228,7 +262,7 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
|
||||
self:F({From, Event, To})
|
||||
Helicopter:F( { Name = Helicopter:GetName() } )
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
@ -243,7 +277,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
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
|
||||
if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then
|
||||
--self:Load( Helicopter:GetPointVec2() )
|
||||
self:Load( self.PickupZone )
|
||||
self.RoutePickup = false
|
||||
@ -251,7 +285,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
end
|
||||
|
||||
if self.RouteDeploy == true then
|
||||
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
|
||||
if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then
|
||||
self:Unload( self.DeployZone )
|
||||
self.RouteDeploy = false
|
||||
end
|
||||
@ -269,7 +303,7 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, DeployZone})
|
||||
local HelicopterInZone = false
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() == true then
|
||||
@ -308,7 +342,11 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina
|
||||
-- true
|
||||
-- )
|
||||
-- Route[#Route+1] = WaypointFrom
|
||||
local CoordinateTo = Coordinate
|
||||
local CoordinateTo = Coordinate
|
||||
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
@ -348,28 +386,17 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
|
||||
|
||||
self:F({From, Event, To, Coordinate})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
|
||||
local Route = {}
|
||||
|
||||
-- local 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
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true)
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
local Tasks = {}
|
||||
@ -379,7 +406,7 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
end
|
||||
|
||||
@ -395,7 +422,7 @@ end
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @return #boolean True if all cargo has been unloaded.
|
||||
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
|
||||
self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } )
|
||||
self:F( { From, Event, To, DeployZone = DeployZone } )
|
||||
|
||||
self:Orbit( Helicopter:GetCoordinate(), 50 )
|
||||
|
||||
@ -408,7 +435,6 @@ function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, Deplo
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone )
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- On after Pickup event.
|
||||
@ -418,11 +444,12 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Pickup place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
self:F({Coordinate, Speed, Height, PickupZone })
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
Helicopter:Activate()
|
||||
@ -436,25 +463,16 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin
|
||||
|
||||
--- 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
|
||||
)
|
||||
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
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointTo
|
||||
@ -495,10 +513,10 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate.
|
||||
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteDeploy = true
|
||||
@ -514,25 +532,17 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
|
||||
|
||||
--- 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
|
||||
)
|
||||
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
|
||||
)
|
||||
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
Route[#Route+1] = WaypointTo
|
||||
@ -542,7 +552,9 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
-- The _Deploy function does not exist.
|
||||
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
|
||||
|
||||
--Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
@ -566,11 +578,12 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, Height})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteHome = true
|
||||
@ -579,30 +592,23 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
Coordinate.y = Height
|
||||
--Coordinate.y = Height
|
||||
Height = Height or 50
|
||||
|
||||
Speed = Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
Speed ,
|
||||
true
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
@ -613,12 +619,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
|
||||
|
||||
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 )
|
||||
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
397
Moose Development/Moose/AI/AI_Cargo_Ship.lua
Normal file
397
Moose Development/Moose/AI/AI_Cargo_Ship.lua
Normal file
@ -0,0 +1,397 @@
|
||||
--- **AI** -- (R2.5.1) - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_SHIP
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI naval group.
|
||||
--
|
||||
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
|
||||
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo loading.
|
||||
--
|
||||
-- The module will automatically load cargo when the Ship is within boarding or loading radius.
|
||||
-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of
|
||||
-- cargo and the specified boarding radius.
|
||||
--
|
||||
-- ## Defending the Ship when enemies are nearby
|
||||
-- This is not supported for naval cargo because most tanks don't float. Protect your transports...
|
||||
--
|
||||
-- ## Infantry or cargo **health**.
|
||||
-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding cargo is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter.
|
||||
-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for
|
||||
-- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed.
|
||||
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance
|
||||
-- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat
|
||||
-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting
|
||||
-- between warehouses.
|
||||
--
|
||||
-- ## Control the Ships on the map.
|
||||
--
|
||||
-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission
|
||||
-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module.
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the
|
||||
-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified
|
||||
-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
--
|
||||
-- @field #AI_CARGO_SHIP
|
||||
AI_CARGO_SHIP = {
|
||||
ClassName = "AI_CARGO_SHIP",
|
||||
Coordinate = nil -- Core.Point#COORDINATE
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_SHIP object.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship The carrier Ship group
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP
|
||||
|
||||
self:AddTransition( "*", "Monitor", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
|
||||
self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0
|
||||
self:SetShippingLane ( ShippingLane )
|
||||
|
||||
self:SetCarrier( Ship )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:SetCarrier( CargoCarrier )
|
||||
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP
|
||||
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self )
|
||||
|
||||
CargoCarrier:HandleEvent( EVENTS.Dead )
|
||||
|
||||
function CargoCarrier:OnEventDead( EventData )
|
||||
self:F({"dead"})
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- Better hope they can swim!
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
|
||||
self.Coalition = self.CargoCarrier:GetCoalition()
|
||||
|
||||
self:SetControllable( CargoCarrier )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- FInd a free Carrier within a radius
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Radius
|
||||
-- @return Wrapper.Group#GROUP NewCarrier
|
||||
function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius )
|
||||
|
||||
local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius )
|
||||
CoordinateZone:Scan( { Object.Category.UNIT } )
|
||||
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
|
||||
local NearUnit = UNIT:Find( DCSUnit )
|
||||
self:F({NearUnit=NearUnit})
|
||||
if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then
|
||||
local Attributes = NearUnit:GetDesc()
|
||||
self:F({Desc=Attributes})
|
||||
if NearUnit:HasAttributes( "Trucks" ) then
|
||||
return NearUnit:GetGroup()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetCombatRadius( CombatRadius )
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Follow Infantry to the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param #AI_CARGO_SHIP Me
|
||||
-- @param Wrapper.Unit#UNIT ShipUnit
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup )
|
||||
|
||||
local InfantryGroup = CargoGroup:GetGroup()
|
||||
|
||||
self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
if ShipUnit:IsAlive() then
|
||||
-- Check if the Cargo is near the CargoCarrier
|
||||
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then
|
||||
|
||||
-- Cargo does not need to navigate to Carrier
|
||||
Me:Guard()
|
||||
else
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
if InfantryGroup:IsAlive() then
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
local Waypoints = {}
|
||||
|
||||
-- Calculate new route
|
||||
local FromCoord = InfantryGroup:GetCoordinate()
|
||||
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({FromGround=FromGround})
|
||||
table.insert( Waypoints, FromGround )
|
||||
|
||||
local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
|
||||
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({ToGround=ToGround})
|
||||
table.insert( Waypoints, ToGround )
|
||||
|
||||
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup )
|
||||
|
||||
self:F({Waypoints=Waypoints})
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone
|
||||
|
||||
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To )
|
||||
self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } )
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
-- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0.
|
||||
-- This is to avoid Unloading the Ship in the middle of the sea.
|
||||
if Ship and Ship:IsAlive() then
|
||||
if self.CarrierCoordinate then
|
||||
if self:IsTransporting() == true then
|
||||
local Coordinate = Ship:GetCoordinate()
|
||||
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
if self:Is( "Unloaded" ) then
|
||||
-- There are no enemies within combat radius. Reload the CargoCarrier.
|
||||
self:Reload()
|
||||
end
|
||||
else
|
||||
if self:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
|
||||
else
|
||||
if self:Is( "Unloaded" ) then
|
||||
--self:Follow()
|
||||
end
|
||||
self:F( "I am here" .. self:GetCurrentState() )
|
||||
if self:Is( "Following" ) then
|
||||
for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
if Cargo:IsAlive() then
|
||||
if not Cargo:IsNear( ShipUnit, 40 ) then
|
||||
ShipUnit:RouteStop()
|
||||
self.CarrierStopped = true
|
||||
else
|
||||
if self.CarrierStopped then
|
||||
if Cargo:IsNear( ShipUnit, 25 ) then
|
||||
ShipUnit:RouteResume()
|
||||
self.CarrierStopped = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.CarrierCoordinate = Ship:GetCoordinate()
|
||||
end
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Load event
|
||||
-- @param Wrapper.Group#Group Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
|
||||
Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Load( PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone )
|
||||
Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Unload( DeployZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- on after Pickup event.
|
||||
-- @param AI_CARGO_SHIP Ship
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point
|
||||
-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided
|
||||
function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP SHIP
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point
|
||||
-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
for i=1, #lane do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone )
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:SetTaskWaypoint( Waypoint, TaskFunction )
|
||||
Ship:Route(Waypoints, 1)
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Unload event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } )
|
||||
|
||||
local UnboardInterval = 5
|
||||
local UnboardDelay = 5
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
for _, ShipUnit in pairs( Ship:GetUnits() ) do
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
Ship:RouteStop()
|
||||
for _, Cargo in pairs( ShipUnit:GetCargo() ) do
|
||||
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
|
||||
if Cargo:IsLoaded() then
|
||||
local unboardCoord = DeployZone:GetRandomPointVec2()
|
||||
Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000)
|
||||
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
|
||||
self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend )
|
||||
if not Defend == true then
|
||||
Cargo:SetDeployed( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
self.RouteHome = true
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
-- Need to find a more generalized way to do this instead of reversing the shipping lane.
|
||||
-- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1
|
||||
for i=#lane, 1, -1 do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:Route(Waypoints, 1)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1140,8 +1140,8 @@ end
|
||||
-- @param DCS#Vec3 CV2 Vec3
|
||||
function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
|
||||
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then
|
||||
|
||||
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then
|
||||
|
||||
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
|
||||
|
||||
FollowGroup:OptionROTEvadeFire()
|
||||
|
||||
@ -190,7 +190,7 @@ do -- ACT_ROUTE
|
||||
self:F( { ZoneName = ZoneName } )
|
||||
local Zone = Zone -- Core.Zone#ZONE
|
||||
local ZoneCoord = Zone:GetCoordinate()
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( self.Coordinate )
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate )
|
||||
self:F( { ShortestDistance, ShortestReferenceName } )
|
||||
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
|
||||
ShortestDistance = ZoneDistance
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--- **Core** -- Management of CARGO logistics, that can be transported from and to transportation carriers.
|
||||
--- **Cargo** - Management of CARGO logistics, that can be transported from and to transportation carriers.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -1066,18 +1066,26 @@ do -- CARGO_REPRESENTABLE
|
||||
|
||||
--- CARGO_REPRESENTABLE Constructor.
|
||||
-- @param #CARGO_REPRESENTABLE self
|
||||
-- @param #string Type
|
||||
-- @param #string Name
|
||||
-- @param #number LoadRadius (optional)
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @param Wrapper.Positionable#POSITIONABLE CargoObject The cargo object.
|
||||
-- @param #string Type Type name
|
||||
-- @param #string Name Name.
|
||||
-- @param #number LoadRadius (optional) Radius in meters.
|
||||
-- @param #number NearRadius (optional) Radius in meters when the cargo is loaded into the carrier.
|
||||
-- @return #CARGO_REPRESENTABLE
|
||||
function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CARGO.
|
||||
local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE
|
||||
self:F( { Type, Name, LoadRadius, NearRadius } )
|
||||
|
||||
local Desc = CargoObject:GetDesc()
|
||||
self:I( { Desc = Desc } )
|
||||
-- Descriptors.
|
||||
local Desc=CargoObject:GetDesc()
|
||||
self:T({Desc=Desc})
|
||||
|
||||
-- Weight.
|
||||
local Weight = math.random( 80, 120 )
|
||||
|
||||
-- Adjust weight..
|
||||
if Desc then
|
||||
if Desc.typeName == "2B11 mortar" then
|
||||
Weight = 210
|
||||
@ -1086,13 +1094,8 @@ do -- CARGO_REPRESENTABLE
|
||||
end
|
||||
end
|
||||
|
||||
-- Set weight.
|
||||
self:SetWeight( Weight )
|
||||
|
||||
-- local Box = CargoUnit:GetBoundingBox()
|
||||
-- local VolumeUnit = ( Box.max.x - Box.min.x ) * ( Box.max.y - Box.min.y ) * ( Box.max.z - Box.min.z )
|
||||
-- self:I( { VolumeUnit = VolumeUnit, WeightUnit = WeightUnit } )
|
||||
--self:SetVolume( VolumeUnit )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--- **Cargo** -- Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
|
||||
--- **Cargo** - Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -56,6 +56,8 @@ do -- CARGO_GROUP
|
||||
-- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene.
|
||||
-- @return #CARGO_GROUP Cargo group object.
|
||||
function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CAROG_REPORTABLE
|
||||
local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP
|
||||
self:F( { Type, Name, LoadRadius } )
|
||||
|
||||
@ -76,6 +78,9 @@ do -- CARGO_GROUP
|
||||
local GroupName = CargoGroup:GetName()
|
||||
self.CargoName = Name
|
||||
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
|
||||
|
||||
-- Deactivate late activation.
|
||||
self.CargoTemplate.lateActivation=false
|
||||
|
||||
self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
|
||||
self.GroupTemplate.name = self.CargoName .. "#CARGO"
|
||||
@ -481,7 +486,7 @@ do -- CARGO_GROUP
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... )
|
||||
--self:F( { From, Event, To, ToPointVec2 } )
|
||||
|
||||
@ -491,7 +496,10 @@ do -- CARGO_GROUP
|
||||
self.CargoSet:ForEach(
|
||||
function( Cargo )
|
||||
--Cargo:UnLoad( ToPointVec2 )
|
||||
local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
|
||||
local RandomVec2=nil
|
||||
if ToPointVec2 then
|
||||
RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
|
||||
end
|
||||
Cargo:UnBoard( RandomVec2 )
|
||||
end
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--- **Cargo** -- Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
|
||||
--- **Cargo** - Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -46,14 +46,17 @@ do -- CARGO_UNIT
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @return #CARGO_UNIT
|
||||
function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius )
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
|
||||
self:I( { Type, Name, LoadRadius, NearRadius } )
|
||||
|
||||
self:T( CargoUnit )
|
||||
-- Inherit CARGO_REPRESENTABLE.
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
|
||||
|
||||
-- Debug info.
|
||||
self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius})
|
||||
|
||||
-- Set cargo object.
|
||||
self.CargoObject = CargoUnit
|
||||
|
||||
self:T( self.ClassName )
|
||||
|
||||
-- Set event prio.
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
@ -100,7 +103,12 @@ do -- CARGO_UNIT
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
|
||||
if CargoCarrier:IsShip() then
|
||||
-- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead.
|
||||
self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading )
|
||||
else
|
||||
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
|
||||
end
|
||||
self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } )
|
||||
self.CargoCarrier = nil
|
||||
|
||||
|
||||
@ -676,6 +676,44 @@ do -- Event Handling
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Paratrooper landing.
|
||||
-- @function [parent=#BASE] OnEventParatrooperLanding
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Discard chair after ejection.
|
||||
-- @function [parent=#BASE] OnEventDiscardChairAfterEjection
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Weapon add. Fires when entering a mission per pylon with the name of the weapon (double pylons not counted, infinite wep reload not counted.
|
||||
-- @function [parent=#BASE] OnEventParatrooperLanding
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Trigger zone.
|
||||
-- @function [parent=#BASE] OnEventTriggerZone
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Landing quality mark.
|
||||
-- @function [parent=#BASE] OnEventLandingQualityMark
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- BDA.
|
||||
-- @function [parent=#BASE] OnEventBDA
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
|
||||
--- Occurs when a player enters a slot and takes control of an aircraft.
|
||||
-- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event.
|
||||
-- initiator : The unit that is being taken control of.
|
||||
-- @function [parent=#BASE] OnEventPlayerEnterAircraft
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -781,31 +819,48 @@ function BASE:CreateEventTakeoff( EventTime, Initiator )
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a `S_EVENT_PLAYER_ENTER_AIRCRAFT` event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered.
|
||||
function BASE:CreateEventPlayerEnterAircraft( PlayerUnit )
|
||||
self:F( { PlayerUnit } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.PlayerEnterAircraft,
|
||||
time = timer.getTime(),
|
||||
initiator = PlayerUnit:GetDCSObject()
|
||||
}
|
||||
|
||||
world.onEvent(Event)
|
||||
end
|
||||
|
||||
-- TODO: Complete DCS#Event structure.
|
||||
--- The main event handling function... This function captures all events generated for the class.
|
||||
-- @param #BASE self
|
||||
-- @param DCS#Event event
|
||||
function BASE:onEvent(event)
|
||||
--self:F( { BaseEventCodes[event.id], event } )
|
||||
|
||||
if self then
|
||||
for EventID, EventObject in pairs( self.Events ) do
|
||||
|
||||
for EventID, EventObject in pairs(self.Events) do
|
||||
if EventObject.EventEnabled then
|
||||
--env.info( 'onEvent Table EventObject.Self = ' .. tostring(EventObject.Self) )
|
||||
--env.info( 'onEvent event.id = ' .. tostring(event.id) )
|
||||
--env.info( 'onEvent EventObject.Event = ' .. tostring(EventObject.Event) )
|
||||
|
||||
if event.id == EventObject.Event then
|
||||
|
||||
if self == EventObject.Self then
|
||||
|
||||
if event.initiator and event.initiator:isExist() then
|
||||
event.IniUnitName = event.initiator:getName()
|
||||
end
|
||||
|
||||
if event.target and event.target:isExist() then
|
||||
event.TgtUnitName = event.target:getName()
|
||||
end
|
||||
--self:T( { BaseEventCodes[event.id], event } )
|
||||
--EventObject.EventFunction( self, event )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
442
Moose Development/Moose/Core/Beacon.lua
Normal file
442
Moose Development/Moose/Core/Beacon.lua
Normal file
@ -0,0 +1,442 @@
|
||||
--- **Core** - TACAN and other beacons.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provide beacon functionality to assist pilots.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Beacon
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon
|
||||
--
|
||||
-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
||||
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
|
||||
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
|
||||
-- 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.
|
||||
-- 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.
|
||||
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name = nil,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
-- @type BEACON.Type
|
||||
-- @field #number NULL
|
||||
-- @field #number VOR
|
||||
-- @field #number DME
|
||||
-- @field #number VOR_DME
|
||||
-- @field #number TACAN TACtical Air Navigation system.
|
||||
-- @field #number VORTAC
|
||||
-- @field #number RSBN
|
||||
-- @field #number BROADCAST_STATION
|
||||
-- @field #number HOMER
|
||||
-- @field #number AIRPORT_HOMER
|
||||
-- @field #number AIRPORT_HOMER_WITH_MARKER
|
||||
-- @field #number ILS_FAR_HOMER
|
||||
-- @field #number ILS_NEAR_HOMER
|
||||
-- @field #number ILS_LOCALIZER
|
||||
-- @field #number ILS_GLIDESLOPE
|
||||
-- @field #number PRMG_LOCALIZER
|
||||
-- @field #number PRMG_GLIDESLOPE
|
||||
-- @field #number ICLS Same as ICLS glideslope.
|
||||
-- @field #number ICLS_LOCALIZER
|
||||
-- @field #number ICLS_GLIDESLOPE
|
||||
-- @field #number NAUTICAL_HOMER
|
||||
BEACON.Type={
|
||||
NULL = 0,
|
||||
VOR = 1,
|
||||
DME = 2,
|
||||
VOR_DME = 3,
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
RSBN = 128,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
PRMG_LOCALIZER = 33024,
|
||||
PRMG_GLIDESLOPE = 33280,
|
||||
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
|
||||
ICLS_LOCALIZER = 131328,
|
||||
ICLS_GLIDESLOPE = 131584,
|
||||
NAUTICAL_HOMER = 65536,
|
||||
}
|
||||
|
||||
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
|
||||
-- @type BEACON.System
|
||||
-- @field #number PAR_10 ?
|
||||
-- @field #number RSBN_5 Russian VOR/DME system.
|
||||
-- @field #number TACAN TACtical Air Navigation system on ground.
|
||||
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
|
||||
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glideslope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
|
||||
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
|
||||
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
|
||||
-- @field #number ICLS_LOCALIZER Carrier landing system.
|
||||
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER_X = 4,
|
||||
TACAN_TANKER_Y = 5,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
ILS_GLIDESLOPE = 8,
|
||||
PRMG_LOCALIZER = 9,
|
||||
PRMG_GLIDESLOPE = 10,
|
||||
BROADCAST_STATION = 11,
|
||||
VORTAC = 12,
|
||||
TACAN_AA_MODE_X = 13,
|
||||
TACAN_AA_MODE_Y = 14,
|
||||
VORDME = 15,
|
||||
ICLS_LOCALIZER = 16,
|
||||
ICLS_GLIDESLOPE = 17,
|
||||
}
|
||||
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
|
||||
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
|
||||
-- @param #BEACON self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
|
||||
function BEACON:New(Positionable)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
-- Debug.
|
||||
self:F(Positionable)
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
self.name=Positionable:GetName()
|
||||
self:I(string.format("New BEACON %s", tostring(self.name)))
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
|
||||
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
|
||||
|
||||
-- Get frequency.
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
return self
|
||||
end
|
||||
|
||||
-- Beacon type.
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
-- Beacon system.
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA=self.Positionable:IsAir()
|
||||
if AA then
|
||||
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
-- Check if "Y" mode is selected for aircraft.
|
||||
if Mode~="Y" then
|
||||
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
|
||||
end
|
||||
end
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug.
|
||||
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop sheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel ICLS channel.
|
||||
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
-- @param #boolean Bearing Can the BEACON be homed on ?
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
|
||||
local IsValid = true
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
|
||||
-- or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
System = 5
|
||||
else
|
||||
System = 14
|
||||
end
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = 4,
|
||||
system = System,
|
||||
callsign = Message,
|
||||
frequency = Frequency,
|
||||
}
|
||||
})
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
-- * ARK9 -> 150-1300KHz
|
||||
-- * **Huey**
|
||||
-- * AN/ARC-131 -> 30-76 Mhz FM
|
||||
-- @param #BEACON self
|
||||
-- @param #string FileName The name of the audio file
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a beacon for a unit in distress.
|
||||
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
|
||||
-- -- The beacon they use is battery-powered, and only lasts for 5 min
|
||||
-- local UnitInDistress = UNIT:FindByName("Unit1")
|
||||
-- local UnitBeacon = UnitInDistress:GetBeacon()
|
||||
--
|
||||
-- -- Set the beacon and start it
|
||||
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
IsValid = true
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type(TACANChannel) ~= "number" then
|
||||
if TACANMode ~= "X" and TACANMode ~= "Y" then
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
A = 1088
|
||||
end
|
||||
else -- 'X'
|
||||
if TACANChannel < 64 then
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
@ -24,7 +24,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -33,6 +33,9 @@
|
||||
|
||||
|
||||
--- @type DATABASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID.
|
||||
-- @field #table CLIENTS Clients.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
|
||||
@ -126,8 +129,6 @@ function DATABASE:New()
|
||||
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.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit )
|
||||
|
||||
@ -140,37 +141,6 @@ function DATABASE:New()
|
||||
|
||||
self.UNITS_Position = 0
|
||||
|
||||
--- @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 )}
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
--self:E( { "CoalitionData:", CoalitionData } )
|
||||
for UnitId, UnitData in pairs( CoalitionData ) do
|
||||
if UnitData and UnitData:isExist() then
|
||||
|
||||
local UnitName = UnitData:getName()
|
||||
local PlayerName = UnitData:getPlayerName()
|
||||
local PlayerUnit = UNIT:Find( UnitData )
|
||||
--self:T( { "UnitData:", UnitData, UnitName, PlayerName, PlayerUnit } )
|
||||
|
||||
if PlayerName and PlayerName ~= "" then
|
||||
if self.PLAYERS[PlayerName] == nil or self.PLAYERS[PlayerName] ~= UnitName then
|
||||
--self:E( { "Add player for unit:", UnitName, PlayerName } )
|
||||
self:AddPlayer( UnitName, PlayerName )
|
||||
--_EVENTDISPATCHER:CreateEventPlayerEnterUnit( PlayerUnit )
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--self:E( "Scheduling" )
|
||||
--PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@ -187,14 +157,22 @@ end
|
||||
|
||||
--- Adds a Unit based on the Unit Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string DCSUnitName Unit name.
|
||||
-- @return Wrapper.Unit#UNIT The added unit.
|
||||
function DATABASE:AddUnit( DCSUnitName )
|
||||
|
||||
if not self.UNITS[DCSUnitName] then
|
||||
self:T( { "Add UNIT:", DCSUnitName } )
|
||||
local UnitRegister = UNIT:Register( DCSUnitName )
|
||||
self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName )
|
||||
if not self.UNITS[DCSUnitName] then
|
||||
|
||||
table.insert( self.UNITS_Index, DCSUnitName )
|
||||
-- Debug info.
|
||||
self:T( { "Add UNIT:", DCSUnitName } )
|
||||
|
||||
--local UnitRegister = UNIT:Register( DCSUnitName )
|
||||
|
||||
-- Register unit
|
||||
self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName)
|
||||
|
||||
-- This is not used anywhere in MOOSE as far as I can see so I remove it until there comes an error somewhere.
|
||||
--table.insert(self.UNITS_Index, DCSUnitName )
|
||||
end
|
||||
|
||||
return self.UNITS[DCSUnitName]
|
||||
@ -210,6 +188,8 @@ end
|
||||
|
||||
--- Adds a Static based on the Static Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string DCSStaticName Name of the static.
|
||||
-- @return Wrapper.Static#STATIC The static object.
|
||||
function DATABASE:AddStatic( DCSStaticName )
|
||||
|
||||
if not self.STATICS[DCSStaticName] then
|
||||
@ -224,8 +204,7 @@ end
|
||||
--- Deletes a Static from the DATABASE based on the Static Name.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:DeleteStatic( DCSStaticName )
|
||||
|
||||
--self.STATICS[DCSStaticName] = nil
|
||||
self.STATICS[DCSStaticName] = nil
|
||||
end
|
||||
|
||||
--- Finds a STATIC based on the StaticName.
|
||||
@ -319,24 +298,78 @@ do -- Zones
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterZones()
|
||||
|
||||
for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do
|
||||
for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do
|
||||
local ZoneName = ZoneData.name
|
||||
|
||||
-- Color
|
||||
local color=ZoneData.color or {1, 0, 0, 0.15}
|
||||
|
||||
-- Create new Zone
|
||||
local Zone=nil --Core.Zone#ZONE_BASE
|
||||
|
||||
if ZoneData.type==0 then
|
||||
|
||||
---
|
||||
-- Circular zone
|
||||
---
|
||||
|
||||
self:I(string.format("Register ZONE: %s (Circular)", ZoneName))
|
||||
|
||||
Zone=ZONE:New(ZoneName)
|
||||
|
||||
else
|
||||
|
||||
self:I( { "Register ZONE:", Name = ZoneName } )
|
||||
local Zone = ZONE:New( ZoneName )
|
||||
self.ZONENAMES[ZoneName] = ZoneName
|
||||
self:AddZone( ZoneName, Zone )
|
||||
---
|
||||
-- Quad-point zone
|
||||
---
|
||||
|
||||
self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName))
|
||||
|
||||
Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies)
|
||||
|
||||
--for i,vec2 in pairs(ZoneData.verticies) do
|
||||
-- local coord=COORDINATE:NewFromVec2(vec2)
|
||||
-- coord:MarkToAll(string.format("%s Point %d", ZoneName, i))
|
||||
--end
|
||||
|
||||
end
|
||||
|
||||
if Zone then
|
||||
|
||||
-- Store color of zone.
|
||||
Zone.Color=color
|
||||
|
||||
-- Store in DB.
|
||||
self.ZONENAMES[ZoneName] = ZoneName
|
||||
|
||||
-- Add zone.
|
||||
self:AddZone(ZoneName, Zone)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Polygon zones defined by late activated groups.
|
||||
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 } )
|
||||
-- Debug output
|
||||
self:I(string.format("Register ZONE: %s (Polygon)", ZoneName))
|
||||
|
||||
-- Create a new polygon zone.
|
||||
local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup )
|
||||
|
||||
-- Set color.
|
||||
Zone_Polygon:SetColor({1, 0, 0}, 0.15)
|
||||
|
||||
-- Store name in DB.
|
||||
self.ZONENAMES[ZoneName] = ZoneName
|
||||
|
||||
-- Add zone to DB.
|
||||
self:AddZone( ZoneName, Zone_Polygon )
|
||||
end
|
||||
end
|
||||
@ -432,7 +465,6 @@ do -- cargo
|
||||
local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug!
|
||||
|
||||
for CargoGroupName, CargoGroup in pairs( Groups ) do
|
||||
self:I( { Cargo = CargoGroupName } )
|
||||
if self:IsCargo( CargoGroupName ) then
|
||||
local CargoInfo = CargoGroupName:match("#CARGO(.*)")
|
||||
local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)")
|
||||
@ -489,6 +521,8 @@ end
|
||||
|
||||
--- Adds a CLIENT based on the ClientName in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client unit.
|
||||
-- @return Wrapper.Client#CLIENT The client object.
|
||||
function DATABASE:AddClient( ClientName )
|
||||
|
||||
if not self.CLIENTS[ClientName] then
|
||||
@ -626,6 +660,9 @@ function DATABASE:Spawn( SpawnTemplate )
|
||||
end
|
||||
|
||||
--- Set a status to a Group within the Database, this to check crossing events for example.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName Group name.
|
||||
-- @param #string Status Status.
|
||||
function DATABASE:SetStatusGroup( GroupName, Status )
|
||||
self:F2( Status )
|
||||
|
||||
@ -633,8 +670,11 @@ function DATABASE:SetStatusGroup( GroupName, Status )
|
||||
end
|
||||
|
||||
--- Get a status to a Group within the Database, this to check crossing events for example.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName Group name.
|
||||
-- @return #string Status or an empty string "".
|
||||
function DATABASE:GetStatusGroup( GroupName )
|
||||
self:F2( Status )
|
||||
self:F2( GroupName )
|
||||
|
||||
if self.Templates.Groups[GroupName] then
|
||||
return self.Templates.Groups[GroupName].Status
|
||||
@ -648,7 +688,8 @@ end
|
||||
-- @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
|
||||
-- @param DCS#country.id CountryID the country ID of the object.
|
||||
-- @param #string GroupName (Optional) The name of the group. Default is `GroupTemplate.name`.
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName )
|
||||
|
||||
@ -704,6 +745,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
|
||||
Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID,
|
||||
Category = self.Templates.Groups[GroupTemplateName].CategoryID,
|
||||
@ -713,6 +755,10 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
)
|
||||
end
|
||||
|
||||
--- Get group template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName Group name.
|
||||
-- @return #table Group template table.
|
||||
function DATABASE:GetGroupTemplate( GroupName )
|
||||
local GroupTemplate = self.Templates.Groups[GroupName].Template
|
||||
GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID
|
||||
@ -723,7 +769,10 @@ end
|
||||
|
||||
--- Private method that registers new Static Templates within the DATABASE Object.
|
||||
-- @param #DATABASE self
|
||||
-- @param #table StaticTemplate
|
||||
-- @param #table StaticTemplate Template table.
|
||||
-- @param #number CoalitionID Coalition ID.
|
||||
-- @param #number CategoryID Category ID.
|
||||
-- @param #number CountryID Country ID.
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID )
|
||||
|
||||
@ -744,7 +793,8 @@ 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,
|
||||
-- Debug info.
|
||||
self:T( { Static = self.Templates.Statics[StaticTemplateName].StaticName,
|
||||
Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID,
|
||||
Category = self.Templates.Statics[StaticTemplateName].CategoryID,
|
||||
Country = self.Templates.Statics[StaticTemplateName].CountryID
|
||||
@ -753,48 +803,101 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
|
||||
|
||||
self:AddStatic( StaticTemplateName )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #DATABASE self
|
||||
--- Get static group template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string StaticName Name of the static.
|
||||
-- @return #table Static template table.
|
||||
function DATABASE:GetStaticGroupTemplate( StaticName )
|
||||
local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate
|
||||
return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
|
||||
if self.Templates.Statics[StaticName] then
|
||||
local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate
|
||||
return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
|
||||
else
|
||||
self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #DATABASE self
|
||||
--- Get static unit template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string StaticName Name of the static.
|
||||
-- @return #table Static template table.
|
||||
function DATABASE:GetStaticUnitTemplate( StaticName )
|
||||
local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate
|
||||
return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
|
||||
if self.Templates.Statics[StaticName] then
|
||||
local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate
|
||||
return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
|
||||
else
|
||||
self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Get group name from unit name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string UnitName Name of the unit.
|
||||
-- @return #string Group name.
|
||||
function DATABASE:GetGroupNameFromUnitName( UnitName )
|
||||
return self.Templates.Units[UnitName].GroupName
|
||||
if self.Templates.Units[UnitName] then
|
||||
return self.Templates.Units[UnitName].GroupName
|
||||
else
|
||||
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get group template from unit name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string UnitName Name of the unit.
|
||||
-- @return #table Group template.
|
||||
function DATABASE:GetGroupTemplateFromUnitName( UnitName )
|
||||
return self.Templates.Units[UnitName].GroupTemplate
|
||||
if self.Templates.Units[UnitName] then
|
||||
return self.Templates.Units[UnitName].GroupTemplate
|
||||
else
|
||||
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get coalition ID from client name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Coalition ID.
|
||||
function DATABASE:GetCoalitionFromClientTemplate( ClientName )
|
||||
return self.Templates.ClientsByName[ClientName].CoalitionID
|
||||
end
|
||||
|
||||
--- Get category ID from client name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Category ID.
|
||||
function DATABASE:GetCategoryFromClientTemplate( ClientName )
|
||||
return self.Templates.ClientsByName[ClientName].CategoryID
|
||||
end
|
||||
|
||||
--- Get country ID from client name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Country ID.
|
||||
function DATABASE:GetCountryFromClientTemplate( ClientName )
|
||||
return self.Templates.ClientsByName[ClientName].CountryID
|
||||
end
|
||||
|
||||
--- Airbase
|
||||
|
||||
--- Get coalition ID from airbase name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string AirbaseName Name of the airbase.
|
||||
-- @return #number Coalition ID.
|
||||
function DATABASE:GetCoalitionFromAirbase( AirbaseName )
|
||||
return self.AIRBASES[AirbaseName]:GetCoalition()
|
||||
end
|
||||
|
||||
--- Get category from airbase name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string AirbaseName Name of the airbase.
|
||||
-- @return #number Category.
|
||||
function DATABASE:GetCategoryFromAirbase( AirbaseName )
|
||||
return self.AIRBASES[AirbaseName]:GetCategory()
|
||||
end
|
||||
@ -831,33 +934,38 @@ end
|
||||
function DATABASE:_RegisterGroupsAndUnits()
|
||||
|
||||
local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) }
|
||||
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
|
||||
for DCSGroupId, DCSGroup in pairs( CoalitionData ) do
|
||||
|
||||
if DCSGroup:isExist() then
|
||||
|
||||
-- Group name.
|
||||
local DCSGroupName = DCSGroup:getName()
|
||||
|
||||
self:I( { "Register Group:", DCSGroupName } )
|
||||
-- Add group.
|
||||
self:I(string.format("Register Group: %s", tostring(DCSGroupName)))
|
||||
self:AddGroup( DCSGroupName )
|
||||
|
||||
-- Loop over units in group.
|
||||
for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do
|
||||
|
||||
-- Get unit name.
|
||||
local DCSUnitName = DCSUnit:getName()
|
||||
self:I( { "Register Unit:", DCSUnitName } )
|
||||
|
||||
-- Add unit.
|
||||
self:I(string.format("Register Unit: %s", tostring(DCSUnitName)))
|
||||
self:AddUnit( DCSUnitName )
|
||||
|
||||
end
|
||||
else
|
||||
self:E( { "Group does not exist: ", DCSGroup } )
|
||||
self:E({"Group does not exist: ", DCSGroup})
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
self:T("Groups:")
|
||||
for GroupName, Group in pairs( self.GROUPS ) do
|
||||
self:T( { "Group:", GroupName } )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@ -867,7 +975,7 @@ end
|
||||
function DATABASE:_RegisterClients()
|
||||
|
||||
for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do
|
||||
self:T( { "Register Client:", ClientName } )
|
||||
self:I(string.format("Register Client: %s", tostring(ClientName)))
|
||||
self:AddClient( ClientName )
|
||||
end
|
||||
|
||||
@ -877,15 +985,15 @@ end
|
||||
--- @param #DATABASE self
|
||||
function DATABASE:_RegisterStatics()
|
||||
|
||||
local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) }
|
||||
self:I( { Statics = CoalitionsData } )
|
||||
local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED), GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE), GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)}
|
||||
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
for DCSStaticId, DCSStatic in pairs( CoalitionData ) do
|
||||
|
||||
if DCSStatic:isExist() then
|
||||
local DCSStaticName = DCSStatic:getName()
|
||||
|
||||
self:T( { "Register Static:", DCSStaticName } )
|
||||
self:I(string.format("Register Static: %s", tostring(DCSStaticName)))
|
||||
self:AddStatic( DCSStaticName )
|
||||
else
|
||||
self:E( { "Static does not exist: ", DCSStatic } )
|
||||
@ -912,8 +1020,23 @@ function DATABASE:_RegisterAirbases()
|
||||
-- Add and register airbase.
|
||||
local airbase=self:AddAirbase( DCSAirbaseName )
|
||||
|
||||
-- Unique ID.
|
||||
local airbaseUID=airbase:GetID(true)
|
||||
|
||||
-- Debug output.
|
||||
self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true)))
|
||||
local text=string.format("Register %s: %s (ID=%d UID=%d), parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseID, airbaseUID, airbase.NparkingTotal)
|
||||
for _,terminalType in pairs(AIRBASE.TerminalType) do
|
||||
if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then
|
||||
text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType])
|
||||
end
|
||||
end
|
||||
text=text.."]"
|
||||
self:I(text)
|
||||
|
||||
-- Check for DCS bug IDs.
|
||||
if airbaseID~=airbase:GetID() then
|
||||
--self:E("WARNING: :getID does NOT match :GetID!")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -930,36 +1053,74 @@ function DATABASE:_EventOnBirth( Event )
|
||||
self:F( { Event } )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
if Event.IniObjectCategory == 3 then
|
||||
|
||||
self:AddStatic( Event.IniDCSUnitName )
|
||||
|
||||
else
|
||||
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
self:AddUnit( Event.IniDCSUnitName )
|
||||
self:AddGroup( Event.IniDCSGroupName )
|
||||
|
||||
-- Add airbase if it was spawned later in the mission.
|
||||
local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName)
|
||||
if DCSAirbase then
|
||||
self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName)))
|
||||
self:AddAirbase(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
|
||||
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
|
||||
|
||||
-- Client
|
||||
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
|
||||
|
||||
if client then
|
||||
-- TODO: create event ClientAlive
|
||||
end
|
||||
|
||||
-- Get player name.
|
||||
local PlayerName = Event.IniUnit:GetPlayerName()
|
||||
|
||||
if PlayerName then
|
||||
self:I( { "Player Joined:", PlayerName } )
|
||||
self:AddClient( Event.IniDCSUnitName )
|
||||
|
||||
-- Debug info.
|
||||
self:I(string.format("Player '%s' joint unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName)))
|
||||
|
||||
-- Add client in case it does not exist already.
|
||||
if not client then
|
||||
client=self:AddClient(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
-- Add player.
|
||||
client:AddPlayer(PlayerName)
|
||||
|
||||
-- Add player.
|
||||
if not self.PLAYERS[PlayerName] then
|
||||
self:AddPlayer( Event.IniUnitName, PlayerName )
|
||||
end
|
||||
|
||||
-- Player settings.
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu( Event.IniUnit )
|
||||
--MENU_INDEX:Refresh( Event.IniGroup )
|
||||
Settings:SetPlayerMenu(Event.IniUnit)
|
||||
|
||||
-- Create an event.
|
||||
self:CreateEventPlayerEnterAircraft(Event.IniUnit)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -967,22 +1128,53 @@ end
|
||||
-- @param #DATABASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function DATABASE:_EventOnDeadOrCrash( Event )
|
||||
self:F2( { Event } )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
local name=Event.IniDCSUnitName
|
||||
|
||||
if Event.IniObjectCategory == 3 then
|
||||
|
||||
---
|
||||
-- STATICS
|
||||
---
|
||||
|
||||
if self.STATICS[Event.IniDCSUnitName] then
|
||||
self:DeleteStatic( Event.IniDCSUnitName )
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
---
|
||||
-- UNITS
|
||||
---
|
||||
|
||||
-- Delete unit.
|
||||
if self.UNITS[Event.IniDCSUnitName] then
|
||||
self:DeleteUnit( Event.IniDCSUnitName )
|
||||
self:DeleteUnit(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
-- Remove client players.
|
||||
local client=self.CLIENTS[name] --Wrapper.Client#CLIENT
|
||||
|
||||
if client then
|
||||
client:RemovePlayers()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Add airbase if it was spawned later in the mission.
|
||||
local airbase=self.AIRBASES[Event.IniDCSUnitName] --Wrapper.Airbase#AIRBASE
|
||||
if airbase and (airbase:IsHelipad() or airbase:IsShip()) then
|
||||
self:DeleteAirbase(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Account destroys.
|
||||
self:AccountDestroys( Event )
|
||||
end
|
||||
|
||||
@ -995,15 +1187,31 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
-- Add unit.
|
||||
self:AddUnit( Event.IniDCSUnitName )
|
||||
|
||||
-- Ini unit.
|
||||
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
|
||||
|
||||
-- Add group.
|
||||
self:AddGroup( Event.IniDCSGroupName )
|
||||
|
||||
-- Get player unit.
|
||||
local PlayerName = Event.IniDCSUnit:getPlayerName()
|
||||
if not self.PLAYERS[PlayerName] then
|
||||
self:AddPlayer( Event.IniDCSUnitName, PlayerName )
|
||||
|
||||
if PlayerName then
|
||||
|
||||
if not self.PLAYERS[PlayerName] then
|
||||
self:AddPlayer( Event.IniDCSUnitName, PlayerName )
|
||||
end
|
||||
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu( Event.IniUnit )
|
||||
|
||||
else
|
||||
self:E("ERROR: getPlayerName() returned nil for event PlayerEnterUnit")
|
||||
end
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu( Event.IniUnit )
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1016,13 +1224,30 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
|
||||
self:F2( { Event } )
|
||||
|
||||
if Event.IniUnit then
|
||||
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
-- Try to get the player name. This can be buggy for multicrew aircraft!
|
||||
local PlayerName = Event.IniUnit:GetPlayerName()
|
||||
if PlayerName and self.PLAYERS[PlayerName] then
|
||||
self:I( { "Player Left:", PlayerName } )
|
||||
|
||||
if PlayerName then --and self.PLAYERS[PlayerName] then
|
||||
|
||||
-- Debug info.
|
||||
self:I(string.format("Player '%s' left unit %s", tostring(PlayerName), tostring(Event.IniUnitName)))
|
||||
|
||||
-- Remove player menu.
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:RemovePlayerMenu( Event.IniUnit )
|
||||
self:DeletePlayer( Event.IniUnit, PlayerName )
|
||||
Settings:RemovePlayerMenu(Event.IniUnit)
|
||||
|
||||
-- Delete player.
|
||||
self:DeletePlayer(Event.IniUnit, PlayerName)
|
||||
|
||||
-- Client stuff.
|
||||
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
|
||||
if client then
|
||||
client:RemovePlayer(PlayerName)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1249,19 +1474,19 @@ function DATABASE:SetPlayerSettings( PlayerName, Settings )
|
||||
self.PLAYERSETTINGS[PlayerName] = Settings
|
||||
end
|
||||
|
||||
--- Add a flight group to the data base.
|
||||
--- Add an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) to the data base.
|
||||
-- @param #DATABASE self
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP flightgroup
|
||||
function DATABASE:AddFlightGroup(flightgroup)
|
||||
self:T({NewFlightGroup=flightgroup.groupname})
|
||||
self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup
|
||||
-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB.
|
||||
function DATABASE:AddOpsGroup(opsgroup)
|
||||
--env.info("Adding OPSGROUP "..tostring(opsgroup.groupname))
|
||||
self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup
|
||||
end
|
||||
|
||||
--- Get a flight group from the data base.
|
||||
--- Get an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) from the data base.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string groupname Group name of the flight group. Can also be passed as GROUP object.
|
||||
-- @return Ops.FlightGroup#FLIGHTGROUP Flight group object.
|
||||
function DATABASE:GetFlightGroup(groupname)
|
||||
-- @param #string groupname Group name of the group. Can also be passed as GROUP object.
|
||||
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
|
||||
function DATABASE:GetOpsGroup(groupname)
|
||||
|
||||
-- Get group and group name.
|
||||
if type(groupname)=="string" then
|
||||
@ -1269,6 +1494,23 @@ function DATABASE:GetFlightGroup(groupname)
|
||||
groupname=groupname:GetName()
|
||||
end
|
||||
|
||||
--env.info("Getting OPSGROUP "..tostring(groupname))
|
||||
return self.FLIGHTGROUPS[groupname]
|
||||
end
|
||||
|
||||
--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string groupname Group name of the group. Can also be passed as GROUP object.
|
||||
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
|
||||
function DATABASE:FindOpsGroup(groupname)
|
||||
|
||||
-- Get group and group name.
|
||||
if type(groupname)=="string" then
|
||||
else
|
||||
groupname=groupname:GetName()
|
||||
end
|
||||
|
||||
--env.info("Getting OPSGROUP "..tostring(groupname))
|
||||
return self.FLIGHTGROUPS[groupname]
|
||||
end
|
||||
|
||||
@ -1355,19 +1597,13 @@ function DATABASE:_RegisterTemplates()
|
||||
for group_num, Template in pairs(obj_type_data.group) do
|
||||
|
||||
if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group
|
||||
self:_RegisterGroupTemplate(
|
||||
Template,
|
||||
CoalitionSide,
|
||||
_DATABASECategory[string.lower(CategoryName)],
|
||||
CountryID
|
||||
)
|
||||
|
||||
self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
|
||||
|
||||
else
|
||||
self:_RegisterStaticTemplate(
|
||||
Template,
|
||||
CoalitionSide,
|
||||
_DATABASECategory[string.lower(CategoryName)],
|
||||
CountryID
|
||||
)
|
||||
|
||||
self:_RegisterStaticTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
|
||||
|
||||
end --if GroupTemplate and GroupTemplate.units then
|
||||
end --for group_num, GroupTemplate in pairs(obj_type_data.group) do
|
||||
end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -410,7 +410,7 @@ do -- FSM
|
||||
Transition.To = To
|
||||
|
||||
-- Debug message.
|
||||
self:T2( Transition )
|
||||
self:T3( Transition )
|
||||
|
||||
self._Transitions[Transition] = Transition
|
||||
self:_eventmap( self.Events, Transition )
|
||||
@ -432,7 +432,7 @@ do -- FSM
|
||||
-- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM.
|
||||
-- @return Core.Fsm#FSM_PROCESS The SubFSM.
|
||||
function FSM:AddProcess( From, Event, Process, ReturnEvents )
|
||||
self:T( { From, Event } )
|
||||
self:T3( { From, Event } )
|
||||
|
||||
local Sub = {}
|
||||
Sub.From = From
|
||||
@ -533,14 +533,14 @@ do -- FSM
|
||||
Process._Scores[State].ScoreText = ScoreText
|
||||
Process._Scores[State].Score = Score
|
||||
|
||||
self:T( Process._Scores )
|
||||
self:T3( Process._Scores )
|
||||
|
||||
return Process
|
||||
end
|
||||
|
||||
--- Returns a table with the scores defined.
|
||||
-- @param #FSM self
|
||||
-- @param #table Scores.
|
||||
-- @return #table Scores.
|
||||
function FSM:GetScores()
|
||||
return self._Scores or {}
|
||||
end
|
||||
@ -576,7 +576,7 @@ do -- FSM
|
||||
self[__Event] = self[__Event] or self:_delayed_transition(Event)
|
||||
|
||||
-- Debug message.
|
||||
self:T2( "Added methods: " .. Event .. ", " .. __Event )
|
||||
self:T3( "Added methods: " .. Event .. ", " .. __Event )
|
||||
|
||||
Events[Event] = self.Events[Event] or { map = {} }
|
||||
self:_add_to_map( Events[Event].map, EventStructure )
|
||||
@ -825,7 +825,7 @@ do -- FSM
|
||||
end
|
||||
|
||||
-- Debug.
|
||||
self:T2( { CallID = CallID } )
|
||||
self:T3( { CallID = CallID } )
|
||||
end
|
||||
|
||||
end
|
||||
@ -846,7 +846,7 @@ do -- FSM
|
||||
function FSM:_gosub( ParentFrom, ParentEvent )
|
||||
local fsmtable = {}
|
||||
if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then
|
||||
self:T( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } )
|
||||
self:T3( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } )
|
||||
return self.subs[ParentFrom][ParentEvent]
|
||||
else
|
||||
return {}
|
||||
@ -1150,7 +1150,7 @@ do -- FSM_PROCESS
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @return #FSM_PROCESS
|
||||
function FSM_PROCESS:Copy( Controllable, Task )
|
||||
self:T( { self:GetClassNameAndID() } )
|
||||
self:T3( { self:GetClassNameAndID() } )
|
||||
|
||||
|
||||
local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS
|
||||
@ -1176,13 +1176,13 @@ do -- FSM_PROCESS
|
||||
|
||||
-- Copy End States
|
||||
for EndStateID, EndState in pairs( self:GetEndStates() ) do
|
||||
self:T( EndState )
|
||||
self:T3( EndState )
|
||||
NewFsm:AddEndState( EndState )
|
||||
end
|
||||
|
||||
-- Copy the score tables
|
||||
for ScoreID, Score in pairs( self:GetScores() ) do
|
||||
self:T( Score )
|
||||
self:T3( Score )
|
||||
NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score )
|
||||
end
|
||||
|
||||
|
||||
251
Moose Development/Moose/Core/MarkerOps_Base.lua
Normal file
251
Moose Development/Moose/Core/MarkerOps_Base.lua
Normal file
@ -0,0 +1,251 @@
|
||||
--- **Core** - MarkerOps_Base.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Create an easy way to tap into markers added to the F10 map by users.
|
||||
-- * Recognize own tag and list of keywords.
|
||||
-- * Matched keywords are handed down to functions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
-- @module Core.MarkerOps_Base
|
||||
-- @image MOOSE_Core.JPG
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
--- MARKEROPS_BASE class.
|
||||
-- @type MARKEROPS_BASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string Tag Tag to identify commands.
|
||||
-- @field #table Keywords Table of keywords to recognize.
|
||||
-- @field #string version Version of #MARKEROPS_BASE.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Fiat lux.* -- Latin proverb.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The MARKEROPS_BASE Concept
|
||||
--
|
||||
-- This class enable scripting text-based actions from markers.
|
||||
--
|
||||
-- @field #MARKEROPS_BASE
|
||||
MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.0.1",
|
||||
debug = false,
|
||||
}
|
||||
|
||||
--- Function to instantiate a new #MARKEROPS_BASE object.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Tagname Name to identify us from the event text.
|
||||
-- @param #table Keywords Table of keywords recognized from the event text.
|
||||
-- @return #MARKEROPS_BASE self
|
||||
function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||
-- Inherit FSM
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version))
|
||||
|
||||
self.Tag = Tagname or "mytag"-- #string
|
||||
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
|
||||
self.debug = false
|
||||
|
||||
-----------------------
|
||||
--- FSM Transitions ---
|
||||
-----------------------
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkAdded", "*") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkChanged", "*") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM.
|
||||
self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM.
|
||||
|
||||
self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark)
|
||||
self:HandleEvent(EVENTS.MarkChange, self.OnEventMark)
|
||||
self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark)
|
||||
|
||||
-- start
|
||||
self:I(self.lid..string.format("started for %s",self.Tag))
|
||||
self:__Start(1)
|
||||
return self
|
||||
|
||||
-------------------
|
||||
-- PSEUDO Functions
|
||||
-------------------
|
||||
|
||||
--- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
|
||||
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
|
||||
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
|
||||
--- "Stop" trigger. Used to stop the function an unhandle events
|
||||
-- @function [parent=#MARKEROPS_BASE] Stop
|
||||
|
||||
end
|
||||
|
||||
--- (internal) Handle events.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function MARKEROPS_BASE:OnEventMark(Event)
|
||||
self:T({Event})
|
||||
if Event == nil or Event.idx == nil then
|
||||
self:E("Skipping onEvent. Event or Event.idx unknown.")
|
||||
return true
|
||||
end
|
||||
--position
|
||||
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
|
||||
local coord=COORDINATE:NewFromVec3(vec3)
|
||||
if self.debug then
|
||||
local coordtext = coord:ToStringLLDDM()
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
-- decision
|
||||
if Event.id==world.event.S_EVENT_MARK_ADDED then
|
||||
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
|
||||
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Hande event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
self:MarkDeleted()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- (internal) Match tag.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #boolean
|
||||
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
local matches = false
|
||||
local type = string.lower(self.Tag) -- #string
|
||||
if string.find(string.lower(Eventtext),type) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
--- (internal) Match keywords table.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #table
|
||||
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
|
||||
local matchtable = {}
|
||||
local keytable = self.Keywords
|
||||
for _index,_word in pairs (keytable) do
|
||||
if string.find(string.lower(Eventtext),string.lower(_word))then
|
||||
table.insert(matchtable,_word)
|
||||
end
|
||||
end
|
||||
return matchtable
|
||||
end
|
||||
|
||||
--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
|
||||
self:T({self.lid,From,Event,To})
|
||||
end
|
||||
|
||||
--- On enter "Stopped" event. Unsubscribe events.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
function MARKEROPS_BASE:onenterStopped(From,Event,To)
|
||||
self:T({self.lid,From,Event,To})
|
||||
-- unsubscribe from events
|
||||
self:UnHandleEvent(EVENTS.MarkAdded)
|
||||
self:UnHandleEvent(EVENTS.MarkChange)
|
||||
self:UnHandleEvent(EVENTS.MarkRemoved)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition End.
|
||||
--------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@ -122,7 +122,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
|
||||
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.1
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.001
|
||||
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
|
||||
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
|
||||
self.Schedule[Scheduler][CallID].Stop = Stop
|
||||
|
||||
@ -233,7 +233,9 @@ do -- SET_BASE
|
||||
-- @param Core.Base#BASE Object The object itself.
|
||||
-- @return Core.Base#BASE The added BASE Object.
|
||||
function SET_BASE:Add( ObjectName, Object )
|
||||
self:F2( { ObjectName = ObjectName, Object = Object } )
|
||||
|
||||
-- Debug info.
|
||||
self:T( { ObjectName = ObjectName, Object = Object } )
|
||||
|
||||
-- Ensure that the existing element is removed from the Set before a new one is inserted to the Set
|
||||
if self.Set[ObjectName] then
|
||||
@ -880,8 +882,8 @@ do -- SET_GROUP
|
||||
--
|
||||
-- * @{#SET_GROUP.FilterCoalitions}: Builds the SET_GROUP with the groups belonging to the coalition(s).
|
||||
-- * @{#SET_GROUP.FilterCategories}: Builds the SET_GROUP with the groups belonging to the category(ies).
|
||||
-- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the gruops belonging to the country(ies).
|
||||
-- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups starting with the same prefix string(s).
|
||||
-- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the groups belonging to the country(ies).
|
||||
-- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups *containing* the given string in the group name. **Attention!** Bad naming convention, as this not really filtering *prefixes*.
|
||||
-- * @{#SET_GROUP.FilterActive}: Builds the SET_GROUP with the groups that are only active. Groups that are inactive (late activation) won't be included in the set!
|
||||
--
|
||||
-- For the Category Filter, extra methods have been added:
|
||||
@ -1241,10 +1243,10 @@ do -- SET_GROUP
|
||||
end
|
||||
|
||||
|
||||
--- Builds a set of groups of defined GROUP prefixes.
|
||||
-- All the groups starting with the given prefixes will be included within the set.
|
||||
--- Builds a set of groups that contain the given string in their group name.
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #string Prefixes The prefix of which the group name starts with.
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterPrefixes( Prefixes )
|
||||
if not self.Filter.GroupPrefixes then
|
||||
@ -1400,6 +1402,22 @@ do -- SET_GROUP
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activate late activated groups.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:Activate(Delay)
|
||||
local Set = self:GetSet()
|
||||
for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP
|
||||
local group=GroupData --Wrapper.Group#GROUP
|
||||
if group and group:IsAlive()==false then
|
||||
group:Activate(Delay)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for.
|
||||
@ -1843,7 +1861,7 @@ do -- SET_UNIT
|
||||
-- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies).
|
||||
-- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s).
|
||||
-- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies).
|
||||
-- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s).
|
||||
-- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units sharing the same string(s) in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*.
|
||||
-- * @{#SET_UNIT.FilterActive}: Builds the SET_UNIT with the units that are only active. Units that are inactive (late activation) won't be included in the set!
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_UNIT, you can start filtering using:
|
||||
@ -2105,10 +2123,10 @@ do -- SET_UNIT
|
||||
end
|
||||
|
||||
|
||||
--- Builds a set of units of defined unit prefixes.
|
||||
-- All the units starting with the given prefixes will be included within the set.
|
||||
--- Builds a set of UNITs that contain a given string in their unit name.
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all units that **contain** the string.
|
||||
-- @param #SET_UNIT self
|
||||
-- @param #string Prefixes The prefix of which the unit name starts with.
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the unit name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_UNIT self
|
||||
function SET_UNIT:FilterPrefixes( Prefixes )
|
||||
if not self.Filter.UnitPrefixes then
|
||||
@ -2963,7 +2981,7 @@ do -- SET_STATIC
|
||||
-- * @{#SET_STATIC.FilterCategories}: Builds the SET_STATIC with the units belonging to the category(ies).
|
||||
-- * @{#SET_STATIC.FilterTypes}: Builds the SET_STATIC with the units belonging to the unit type(s).
|
||||
-- * @{#SET_STATIC.FilterCountries}: Builds the SET_STATIC with the units belonging to the country(ies).
|
||||
-- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units starting with the same prefix string(s).
|
||||
-- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units containing the same string(s) in their name. **ATTENTION** bad naming convention as this *does not** only filter *prefixes*.
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_STATIC, you can start filtering using:
|
||||
--
|
||||
@ -3176,10 +3194,10 @@ do -- SET_STATIC
|
||||
end
|
||||
|
||||
|
||||
--- Builds a set of units of defined unit prefixes.
|
||||
-- All the units starting with the given prefixes will be included within the set.
|
||||
--- Builds a set of STATICs that contain the given string in their name.
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all statics that **contain** the string.
|
||||
-- @param #SET_STATIC self
|
||||
-- @param #string Prefixes The prefix of which the unit name starts with.
|
||||
-- @param #string Prefixes The string pattern(s) that need to be contained in the static name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_STATIC self
|
||||
function SET_STATIC:FilterPrefixes( Prefixes )
|
||||
if not self.Filter.StaticPrefixes then
|
||||
@ -3267,7 +3285,7 @@ do -- SET_STATIC
|
||||
-- @param #SET_STATIC self
|
||||
-- @param Core.Zone#ZONE Zone The Zone to be tested for.
|
||||
-- @return #boolean
|
||||
function SET_STATIC:IsPatriallyInZone( Zone )
|
||||
function SET_STATIC:IsPartiallyInZone( Zone )
|
||||
|
||||
local IsPartiallyInZone = false
|
||||
|
||||
@ -3675,7 +3693,7 @@ do -- SET_CLIENT
|
||||
-- * @{#SET_CLIENT.FilterCategories}: Builds the SET_CLIENT with the clients belonging to the category(ies).
|
||||
-- * @{#SET_CLIENT.FilterTypes}: Builds the SET_CLIENT with the clients belonging to the client type(s).
|
||||
-- * @{#SET_CLIENT.FilterCountries}: Builds the SET_CLIENT with the clients belonging to the country(ies).
|
||||
-- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients starting with the same prefix string(s).
|
||||
-- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients containing the same string(s) in their unit/pilot name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*.
|
||||
-- * @{#SET_CLIENT.FilterActive}: Builds the SET_CLIENT with the units that are only active. Units that are inactive (late activation) won't be included in the set!
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_CLIENT, you can start filtering using:
|
||||
@ -3858,10 +3876,10 @@ do -- SET_CLIENT
|
||||
end
|
||||
|
||||
|
||||
--- Builds a set of clients of defined client prefixes.
|
||||
-- All the clients starting with the given prefixes will be included within the set.
|
||||
--- Builds a set of CLIENTs that contain the given string in their unit/pilot name.
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string.
|
||||
-- @param #SET_CLIENT self
|
||||
-- @param #string Prefixes The prefix of which the client name starts with.
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the unit/pilot name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_CLIENT self
|
||||
function SET_CLIENT:FilterPrefixes( Prefixes )
|
||||
if not self.Filter.ClientPrefixes then
|
||||
@ -4114,7 +4132,7 @@ do -- SET_PLAYER
|
||||
-- * @{#SET_PLAYER.FilterCategories}: Builds the SET_PLAYER with the clients belonging to the category(ies).
|
||||
-- * @{#SET_PLAYER.FilterTypes}: Builds the SET_PLAYER with the clients belonging to the client type(s).
|
||||
-- * @{#SET_PLAYER.FilterCountries}: Builds the SET_PLAYER with the clients belonging to the country(ies).
|
||||
-- * @{#SET_PLAYER.FilterPrefixes}: Builds the SET_PLAYER with the clients starting with the same prefix string(s).
|
||||
-- * @{#SET_PLAYER.FilterPrefixes}: Builds the SET_PLAYER with the clients sharing the same string(s) in their unit/pilot name. **ATTENTION** Bad naming convention as this *does not* only filter prefixes.
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_PLAYER, you can start filtering using:
|
||||
--
|
||||
@ -4293,10 +4311,10 @@ do -- SET_PLAYER
|
||||
end
|
||||
|
||||
|
||||
--- Builds a set of clients of defined client prefixes.
|
||||
-- All the clients starting with the given prefixes will be included within the set.
|
||||
--- Builds a set of PLAYERs that contain the given string in their unit/pilot name.
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all player clients that **contain** the string.
|
||||
-- @param #SET_PLAYER self
|
||||
-- @param #string Prefixes The prefix of which the client name starts with.
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the unit/pilot name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_PLAYER self
|
||||
function SET_PLAYER:FilterPrefixes( Prefixes )
|
||||
if not self.Filter.ClientPrefixes then
|
||||
@ -4705,7 +4723,8 @@ do -- SET_AIRBASE
|
||||
if _DATABASE then
|
||||
|
||||
-- We use the BaseCaptured event, which is generated by DCS when a base got captured.
|
||||
self:HandleEvent( EVENTS.BaseCaptured )
|
||||
self:HandleEvent(EVENTS.BaseCaptured)
|
||||
self:HandleEvent(EVENTS.Dead)
|
||||
|
||||
-- We initialize the first set.
|
||||
for ObjectName, Object in pairs( self.Database ) do
|
||||
@ -4720,10 +4739,9 @@ do -- SET_AIRBASE
|
||||
return self
|
||||
end
|
||||
|
||||
--- Starts the filtering.
|
||||
--- Base capturing event.
|
||||
-- @param #SET_AIRBASE self
|
||||
-- @param Core.Event#EVENT EventData
|
||||
-- @return #SET_AIRBASE self
|
||||
function SET_AIRBASE:OnEventBaseCaptured(EventData)
|
||||
|
||||
-- When a base got captured, we reevaluate the set.
|
||||
@ -4739,24 +4757,36 @@ do -- SET_AIRBASE
|
||||
|
||||
end
|
||||
|
||||
--- Dead event.
|
||||
-- @param #SET_AIRBASE self
|
||||
-- @param Core.Event#EVENT EventData
|
||||
function SET_AIRBASE:OnEventDead(EventData)
|
||||
|
||||
local airbaseName, airbase=self:FindInDatabase(EventData)
|
||||
|
||||
if airbase and airbase:IsShip() or airbase:IsHelipad() then
|
||||
self:RemoveAirbasesByName(airbaseName)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Handles the Database to check on an event (birth) that the Object was added in the Database.
|
||||
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
|
||||
-- @param #SET_AIRBASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
-- @return #string The name of the AIRBASE
|
||||
-- @return #table The AIRBASE
|
||||
-- @param Core.Event#EVENTDATA Event Event data.
|
||||
-- @return #string The name of the AIRBASE.
|
||||
-- @return Wrapper.Airbase#AIRBASE The AIRBASE object.
|
||||
function SET_AIRBASE:AddInDatabase( Event )
|
||||
self:F3( { Event } )
|
||||
|
||||
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
|
||||
end
|
||||
|
||||
--- Handles the Database to check on any event that Object exists in the Database.
|
||||
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
|
||||
-- @param #SET_AIRBASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
-- @return #string The name of the AIRBASE
|
||||
-- @return #table The AIRBASE
|
||||
-- @param Core.Event#EVENTDATA Event Event data.
|
||||
-- @return #string The name of the AIRBASE.
|
||||
-- @return Wrapper.Airbase#AIRBASE The AIRBASE object.
|
||||
function SET_AIRBASE:FindInDatabase( Event )
|
||||
self:F3( { Event } )
|
||||
|
||||
@ -4862,7 +4892,7 @@ do -- SET_CARGO
|
||||
-- Filter criteria are defined by:
|
||||
--
|
||||
-- * @{#SET_CARGO.FilterCoalitions}: Builds the SET_CARGO with the cargos belonging to the coalition(s).
|
||||
-- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the prefix string(s).
|
||||
-- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the same string(s). **ATTENTION** Bad naming convention as this *does not* only filter *prefixes*.
|
||||
-- * @{#SET_CARGO.FilterTypes}: Builds the SET_CARGO with the cargos belonging to the cargo type(s).
|
||||
-- * @{#SET_CARGO.FilterCountries}: Builds the SET_CARGO with the cargos belonging to the country(ies).
|
||||
--
|
||||
@ -4916,7 +4946,7 @@ do -- SET_CARGO
|
||||
--- (R2.1) Add CARGO to SET_CARGO.
|
||||
-- @param Core.Set#SET_CARGO self
|
||||
-- @param Cargo.Cargo#CARGO Cargo A single cargo.
|
||||
-- @return self
|
||||
-- @return Core.Set#SET_CARGO self
|
||||
function SET_CARGO:AddCargo( Cargo ) --R2.4
|
||||
|
||||
self:Add( Cargo:GetName(), Cargo )
|
||||
@ -4928,7 +4958,7 @@ do -- SET_CARGO
|
||||
--- (R2.1) Add CARGOs to SET_CARGO.
|
||||
-- @param Core.Set#SET_CARGO self
|
||||
-- @param #string AddCargoNames A single name or an array of CARGO names.
|
||||
-- @return self
|
||||
-- @return Core.Set#SET_CARGO self
|
||||
function SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1
|
||||
|
||||
local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames }
|
||||
@ -4943,7 +4973,7 @@ do -- SET_CARGO
|
||||
--- (R2.1) Remove CARGOs from SET_CARGO.
|
||||
-- @param Core.Set#SET_CARGO self
|
||||
-- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names.
|
||||
-- @return self
|
||||
-- @return Core.Set#SET_CARGO self
|
||||
function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1
|
||||
|
||||
local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames }
|
||||
@ -5024,10 +5054,10 @@ do -- SET_CARGO
|
||||
end
|
||||
|
||||
|
||||
--- (R2.1) Builds a set of cargos of defined cargo prefixes.
|
||||
-- All the cargos starting with the given prefixes will be included within the set.
|
||||
--- Builds a set of CARGOs that contain a given string in their name.
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all cargos that **contain** the string.
|
||||
-- @param #SET_CARGO self
|
||||
-- @param #string Prefixes The prefix of which the cargo name starts with.
|
||||
-- @param #string Prefixes The string pattern(s) that need to be in the cargo name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_CARGO self
|
||||
function SET_CARGO:FilterPrefixes( Prefixes ) --R2.1
|
||||
if not self.Filter.CargoPrefixes then
|
||||
@ -5303,7 +5333,7 @@ do -- SET_ZONE
|
||||
-- You can set filter criteria to build the collection of zones in SET_ZONE.
|
||||
-- Filter criteria are defined by:
|
||||
--
|
||||
-- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern of prefix.
|
||||
-- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*.
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_ZONE, you can start filtering using:
|
||||
--
|
||||
@ -5399,10 +5429,12 @@ do -- SET_ZONE
|
||||
|
||||
--- Get a random zone from the set.
|
||||
-- @param #SET_ZONE self
|
||||
-- @param #number margin Number of tries to find a zone
|
||||
-- @return Core.Zone#ZONE_BASE The random Zone.
|
||||
-- @return #nil if no zone in the collection.
|
||||
function SET_ZONE:GetRandomZone()
|
||||
|
||||
function SET_ZONE:GetRandomZone(margin)
|
||||
|
||||
local margin = margin or 100
|
||||
if self:Count() ~= 0 then
|
||||
|
||||
local Index = self.Index
|
||||
@ -5411,9 +5443,11 @@ do -- SET_ZONE
|
||||
-- Loop until a zone has been found.
|
||||
-- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected.
|
||||
-- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues!
|
||||
while not ZoneFound do
|
||||
local counter = 0
|
||||
while (not ZoneFound) or (counter < margin) do
|
||||
local ZoneRandom = math.random( 1, #Index )
|
||||
ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe()
|
||||
counter = counter + 1
|
||||
end
|
||||
|
||||
return ZoneFound
|
||||
@ -5434,10 +5468,10 @@ do -- SET_ZONE
|
||||
|
||||
|
||||
|
||||
--- Builds a set of zones of defined zone prefixes.
|
||||
-- All the zones starting with the given prefixes will be included within the set.
|
||||
--- Builds a set of ZONEs that contain the given string in their name.
|
||||
-- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string.
|
||||
-- @param #SET_ZONE self
|
||||
-- @param #string Prefixes The prefix of which the zone name starts with.
|
||||
-- @param #string Prefixes The string pattern(s) that need to be contained in the zone name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_ZONE self
|
||||
function SET_ZONE:FilterPrefixes( Prefixes )
|
||||
if not self.Filter.Prefixes then
|
||||
@ -5636,7 +5670,7 @@ do -- SET_ZONE_GOAL
|
||||
-- You can set filter criteria to build the collection of zones in SET_ZONE_GOAL.
|
||||
-- Filter criteria are defined by:
|
||||
--
|
||||
-- * @{#SET_ZONE_GOAL.FilterPrefixes}: Builds the SET_ZONE_GOAL with the zones having a certain text pattern of prefix.
|
||||
-- * @{#SET_ZONE_GOAL.FilterPrefixes}: Builds the SET_ZONE_GOAL with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*.
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_ZONE_GOAL, you can start filtering using:
|
||||
--
|
||||
@ -5752,10 +5786,10 @@ do -- SET_ZONE_GOAL
|
||||
|
||||
|
||||
|
||||
--- Builds a set of zones of defined zone prefixes.
|
||||
-- All the zones starting with the given prefixes will be included within the set.
|
||||
--- Builds a set of ZONE_GOALs that contain the given string in their name.
|
||||
-- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string.
|
||||
-- @param #SET_ZONE_GOAL self
|
||||
-- @param #string Prefixes The prefix of which the zone name starts with.
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the zone name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_ZONE_GOAL self
|
||||
function SET_ZONE_GOAL:FilterPrefixes( Prefixes )
|
||||
if not self.Filter.Prefixes then
|
||||
@ -5876,12 +5910,13 @@ do -- SET_ZONE_GOAL
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SET_ZONE_GOAL:OnEventNewZoneGoal( EventData )
|
||||
|
||||
self:I( { "New Zone Capture Coalition", EventData } )
|
||||
self:I( { "Zone Capture Coalition", EventData.ZoneGoal } )
|
||||
-- Debug info.
|
||||
self:T( { "New Zone Capture Coalition", EventData } )
|
||||
self:T( { "Zone Capture Coalition", EventData.ZoneGoal } )
|
||||
|
||||
if EventData.ZoneGoal then
|
||||
if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then
|
||||
self:I( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } )
|
||||
self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } )
|
||||
self:Add( EventData.ZoneGoal.ZoneName , EventData.ZoneGoal )
|
||||
end
|
||||
end
|
||||
@ -5932,3 +5967,618 @@ do -- SET_ZONE_GOAL
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- SET_OPSGROUP
|
||||
|
||||
--- @type SET_OPSGROUP
|
||||
-- @extends Core.Set#SET_BASE
|
||||
|
||||
--- Mission designers can use the @{Core.Set#SET_OPSGROUP} class to build sets of OPS groups belonging to certain:
|
||||
--
|
||||
-- * Coalitions
|
||||
-- * Categories
|
||||
-- * Countries
|
||||
-- * Contain a certain string pattern
|
||||
--
|
||||
-- ## SET_OPSGROUP constructor
|
||||
--
|
||||
-- Create a new SET_OPSGROUP object with the @{#SET_OPSGROUP.New} method:
|
||||
--
|
||||
-- * @{#SET_OPSGROUP.New}: Creates a new SET_OPSGROUP object.
|
||||
--
|
||||
-- ## Add or Remove GROUP(s) from SET_OPSGROUP
|
||||
--
|
||||
-- GROUPS can be added and removed using the @{Core.Set#SET_OPSGROUP.AddGroupsByName} and @{Core.Set#SET_OPSGROUP.RemoveGroupsByName} respectively.
|
||||
-- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_OPSGROUP.
|
||||
--
|
||||
-- ## SET_OPSGROUP filter criteria
|
||||
--
|
||||
-- You can set filter criteria to define the set of groups within the SET_OPSGROUP.
|
||||
-- Filter criteria are defined by:
|
||||
--
|
||||
-- * @{#SET_OPSGROUP.FilterCoalitions}: Builds the SET_OPSGROUP with the groups belonging to the coalition(s).
|
||||
-- * @{#SET_OPSGROUP.FilterCategories}: Builds the SET_OPSGROUP with the groups belonging to the category(ies).
|
||||
-- * @{#SET_OPSGROUP.FilterCountries}: Builds the SET_OPSGROUP with the groups belonging to the country(ies).
|
||||
-- * @{#SET_OPSGROUP.FilterPrefixes}: Builds the SET_OPSGROUP with the groups *containing* the given string in the group name. **Attention!** Bad naming convention, as this not really filtering *prefixes*.
|
||||
-- * @{#SET_OPSGROUP.FilterActive}: Builds the SET_OPSGROUP with the groups that are only active. Groups that are inactive (late activation) won't be included in the set!
|
||||
--
|
||||
-- For the Category Filter, extra methods have been added:
|
||||
--
|
||||
-- * @{#SET_OPSGROUP.FilterCategoryAirplane}: Builds the SET_OPSGROUP from airplanes.
|
||||
-- * @{#SET_OPSGROUP.FilterCategoryHelicopter}: Builds the SET_OPSGROUP from helicopters.
|
||||
-- * @{#SET_OPSGROUP.FilterCategoryGround}: Builds the SET_OPSGROUP from ground vehicles or infantry.
|
||||
-- * @{#SET_OPSGROUP.FilterCategoryShip}: Builds the SET_OPSGROUP from ships.
|
||||
--
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_OPSGROUP, you can start filtering using:
|
||||
--
|
||||
-- * @{#SET_OPSGROUP.FilterStart}: Starts the filtering of the groups within the SET_OPSGROUP and add or remove GROUP objects **dynamically**.
|
||||
-- * @{#SET_OPSGROUP.FilterOnce}: Filters of the groups **once**.
|
||||
--
|
||||
--
|
||||
-- ## SET_OPSGROUP iterators
|
||||
--
|
||||
-- Once the filters have been defined and the SET_OPSGROUP has been built, you can iterate the SET_OPSGROUP with the available iterator methods.
|
||||
-- The iterator methods will walk the SET_OPSGROUP set, and call for each element within the set a function that you provide.
|
||||
-- The following iterator methods are currently available within the SET_OPSGROUP:
|
||||
--
|
||||
-- * @{#SET_OPSGROUP.ForEachGroup}: Calls a function for each alive group it finds within the SET_OPSGROUP.
|
||||
--
|
||||
-- ## SET_OPSGROUP trigger events on the GROUP objects.
|
||||
--
|
||||
-- The SET is derived from the FSM class, which provides extra capabilities to track the contents of the GROUP objects in the SET_OPSGROUP.
|
||||
--
|
||||
-- ### When a GROUP object crashes or is dead, the SET_OPSGROUP will trigger a **Dead** event.
|
||||
--
|
||||
-- You can handle the event using the OnBefore and OnAfter event handlers.
|
||||
-- The event handlers need to have the paramters From, Event, To, GroupObject.
|
||||
-- The GroupObject is the GROUP object that is dead and within the SET_OPSGROUP, and is passed as a parameter to the event handler.
|
||||
-- See the following example:
|
||||
--
|
||||
-- -- Create the SetCarrier SET_OPSGROUP collection.
|
||||
--
|
||||
-- local SetHelicopter = SET_OPSGROUP:New():FilterPrefixes( "Helicopter" ):FilterStart()
|
||||
--
|
||||
-- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset.
|
||||
--
|
||||
-- function SetHelicopter:OnAfterDead( From, Event, To, GroupObject )
|
||||
-- self:F( { GroupObject = GroupObject:GetName() } )
|
||||
-- end
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #SET_OPSGROUP SET_OPSGROUP
|
||||
--
|
||||
SET_OPSGROUP = {
|
||||
ClassName = "SET_OPSGROUP",
|
||||
Filter = {
|
||||
Coalitions = nil,
|
||||
Categories = nil,
|
||||
Countries = nil,
|
||||
GroupPrefixes = nil,
|
||||
},
|
||||
FilterMeta = {
|
||||
Coalitions = {
|
||||
red = coalition.side.RED,
|
||||
blue = coalition.side.BLUE,
|
||||
neutral = coalition.side.NEUTRAL,
|
||||
},
|
||||
Categories = {
|
||||
plane = Group.Category.AIRPLANE,
|
||||
helicopter = Group.Category.HELICOPTER,
|
||||
ground = Group.Category.GROUND,
|
||||
ship = Group.Category.SHIP,
|
||||
},
|
||||
}, -- FilterMeta
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new SET_OPSGROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP
|
||||
function SET_OPSGROUP:New()
|
||||
|
||||
-- Inherit SET_BASE.
|
||||
local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.GROUPS)) -- #SET_OPSGROUP
|
||||
|
||||
-- Include non activated
|
||||
self:FilterActive( false )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the Set.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:GetAliveSet()
|
||||
|
||||
local AliveSet = SET_OPSGROUP:New()
|
||||
|
||||
-- Clean the Set before returning with only the alive Groups.
|
||||
for GroupName, GroupObject in pairs(self.Set) do
|
||||
local GroupObject=GroupObject --Wrapper.Group#GROUP
|
||||
|
||||
if GroupObject and GroupObject:IsAlive() then
|
||||
AliveSet:Add(GroupName, GroupObject)
|
||||
end
|
||||
end
|
||||
|
||||
return AliveSet.Set or {}
|
||||
end
|
||||
|
||||
--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index.
|
||||
-- @param #SET_BASE self
|
||||
-- @param #string ObjectName The name of the object.
|
||||
-- @param Core.Base#BASE Object The object itself.
|
||||
-- @return Core.Base#BASE The added BASE Object.
|
||||
function SET_OPSGROUP:Add(ObjectName, Object)
|
||||
self:T( { ObjectName = ObjectName, Object = Object } )
|
||||
|
||||
-- Ensure that the existing element is removed from the Set before a new one is inserted to the Set
|
||||
if self.Set[ObjectName] then
|
||||
self:Remove(ObjectName, true)
|
||||
end
|
||||
|
||||
local object=nil --Ops.OpsGroup#OPSGROUP
|
||||
if Object:IsInstanceOf("GROUP") then
|
||||
|
||||
---
|
||||
-- GROUP Object
|
||||
---
|
||||
|
||||
-- Fist, look up in the DATABASE if an OPSGROUP already exists.
|
||||
object=_DATABASE:FindOpsGroup(ObjectName)
|
||||
|
||||
if not object then
|
||||
|
||||
if Object:IsShip() then
|
||||
object=NAVYGROUP:New(Object)
|
||||
elseif Object:IsGround() then
|
||||
object=ARMYGROUP:New(Object)
|
||||
elseif Object:IsAir() then
|
||||
object=FLIGHTGROUP:New(Object)
|
||||
else
|
||||
env.error("ERROR: Unknown category of group object!")
|
||||
end
|
||||
end
|
||||
|
||||
elseif Object:IsInstanceOf("OPSGROUP") then
|
||||
-- We already have an OPSGROUP.
|
||||
object=Object
|
||||
else
|
||||
env.error("ERROR: Object must be a GROUP or OPSGROUP!")
|
||||
end
|
||||
|
||||
-- Add object to set.
|
||||
self.Set[ObjectName]=object
|
||||
|
||||
-- Add Object name to Index.
|
||||
table.insert(self.Index, ObjectName)
|
||||
|
||||
-- Trigger Added event.
|
||||
self:Added(ObjectName, object)
|
||||
end
|
||||
|
||||
--- Add a GROUP or OPSGROUP object to the set.
|
||||
-- **NOTE** that an OPSGROUP is automatically created from the GROUP if it does not exist already.
|
||||
-- @param Core.Set#SET_OPSGROUP self
|
||||
-- @param Wrapper.Group#GROUP group The GROUP which should be added to the set. Can also be given as an #OPSGROUP object.
|
||||
-- @return Core.Set#SET_OPSGROUP self
|
||||
function SET_OPSGROUP:AddGroup(group)
|
||||
|
||||
local groupname=group:GetName()
|
||||
|
||||
self:Add(groupname, group )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add GROUP(s) or OPSGROUP(s) to the set.
|
||||
-- @param Core.Set#SET_OPSGROUP self
|
||||
-- @param #string AddGroupNames A single name or an array of GROUP names.
|
||||
-- @return Core.Set#SET_OPSGROUP self
|
||||
function SET_OPSGROUP:AddGroupsByName( AddGroupNames )
|
||||
|
||||
local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames }
|
||||
|
||||
for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do
|
||||
self:Add(AddGroupName, GROUP:FindByName(AddGroupName))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove GROUP(s) or OPSGROUP(s) from the set.
|
||||
-- @param Core.Set#SET_OPSGROUP self
|
||||
-- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names.
|
||||
-- @return Core.Set#SET_OPSGROUP self
|
||||
function SET_OPSGROUP:RemoveGroupsByName( RemoveGroupNames )
|
||||
|
||||
local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames }
|
||||
|
||||
for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do
|
||||
self:Remove( RemoveGroupName )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Finds an OPSGROUP based on the group name.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string GroupName Name of the group.
|
||||
-- @return Ops.OpsGroup#OPSGROUP The found OPSGROUP (FLIGHTGROUP, ARMYGROUP or NAVYGROUP) or `#nil` if the group is not in the set.
|
||||
function SET_OPSGROUP:FindGroup(GroupName)
|
||||
local GroupFound = self.Set[GroupName]
|
||||
return GroupFound
|
||||
end
|
||||
|
||||
--- Finds a FLIGHTGROUP based on the group name.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string GroupName Name of the group.
|
||||
-- @return Ops.FlightGroup#FLIGHTGROUP The found FLIGHTGROUP or `#nil` if the group is not in the set.
|
||||
function SET_OPSGROUP:FindFlightGroup(GroupName)
|
||||
local GroupFound = self:FindGroup(GroupName)
|
||||
return GroupFound
|
||||
end
|
||||
|
||||
--- Finds a ARMYGROUP based on the group name.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string GroupName Name of the group.
|
||||
-- @return Ops.ArmyGroup#ARMYGROUP The found ARMYGROUP or `#nil` if the group is not in the set.
|
||||
function SET_OPSGROUP:FindArmyGroup(GroupName)
|
||||
local GroupFound = self:FindGroup(GroupName)
|
||||
return GroupFound
|
||||
end
|
||||
|
||||
|
||||
--- Finds a NAVYGROUP based on the group name.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string GroupName Name of the group.
|
||||
-- @return Ops.NavyGroup#NAVYGROUP The found NAVYGROUP or `#nil` if the group is not in the set.
|
||||
function SET_OPSGROUP:FindNavyGroup(GroupName)
|
||||
local GroupFound = self:FindGroup(GroupName)
|
||||
return GroupFound
|
||||
end
|
||||
|
||||
--- Builds a set of groups of coalitions.
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCoalitions(Coalitions)
|
||||
|
||||
-- Create an empty set.
|
||||
if not self.Filter.Coalitions then
|
||||
self.Filter.Coalitions={}
|
||||
end
|
||||
|
||||
-- Ensure we got a table.
|
||||
if type(Coalitions)~="table" then
|
||||
Coalitions = {Coalitions}
|
||||
end
|
||||
|
||||
-- Set filter.
|
||||
for CoalitionID, Coalition in pairs( Coalitions ) do
|
||||
self.Filter.Coalitions[Coalition] = Coalition
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Builds a set of groups out of categories.
|
||||
--
|
||||
-- Possible current categories are:
|
||||
--
|
||||
-- * "plane" for fixed wing groups
|
||||
-- * "helicopter" for rotary wing groups
|
||||
-- * "ground" for ground groups
|
||||
-- * "ship" for naval groups
|
||||
--
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCategories( Categories )
|
||||
|
||||
if not self.Filter.Categories then
|
||||
self.Filter.Categories={}
|
||||
end
|
||||
|
||||
if type(Categories)~="table" then
|
||||
Categories={Categories}
|
||||
end
|
||||
|
||||
for CategoryID, Category in pairs( Categories ) do
|
||||
self.Filter.Categories[Category] = Category
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups out of ground category.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCategoryGround()
|
||||
self:FilterCategories("ground")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups out of airplane category.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCategoryAirplane()
|
||||
self:FilterCategories("plane")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups out of aicraft category (planes and helicopters).
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCategoryAircraft()
|
||||
self:FilterCategories({"plane", "helicopter"})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups out of helicopter category.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCategoryHelicopter()
|
||||
self:FilterCategories("helicopter")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups out of ship category.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCategoryShip()
|
||||
self:FilterCategories("ship")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups of defined countries.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Countries Can take those country strings known within DCS world.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCountries(Countries)
|
||||
|
||||
-- Create empty table if necessary.
|
||||
if not self.Filter.Countries then
|
||||
self.Filter.Countries = {}
|
||||
end
|
||||
|
||||
-- Ensure input is a table.
|
||||
if type(Countries)~="table" then
|
||||
Countries={Countries}
|
||||
end
|
||||
|
||||
-- Set filter.
|
||||
for CountryID, Country in pairs( Countries ) do
|
||||
self.Filter.Countries[Country] = Country
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Builds a set of groups that contain the given string in their group name.
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterPrefixes(Prefixes)
|
||||
|
||||
-- Create emtpy table if necessary.
|
||||
if not self.Filter.GroupPrefixes then
|
||||
self.Filter.GroupPrefixes={}
|
||||
end
|
||||
|
||||
-- Ensure we have a table.
|
||||
if type(Prefixes)~="table" then
|
||||
Prefixes={Prefixes}
|
||||
end
|
||||
|
||||
-- Set group prefixes.
|
||||
for PrefixID, Prefix in pairs(Prefixes) do
|
||||
self.Filter.GroupPrefixes[Prefix]=Prefix
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups that are only active.
|
||||
-- Only the groups that are active will be included within the set.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #boolean Active (optional) Include only active groups to the set.
|
||||
-- Include inactive groups if you provide false.
|
||||
-- @return #SET_OPSGROUP self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Include only active groups to the set.
|
||||
-- GroupSet = SET_OPSGROUP:New():FilterActive():FilterStart()
|
||||
--
|
||||
-- -- Include only active groups to the set of the blue coalition, and filter one time.
|
||||
-- GroupSet = SET_OPSGROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce()
|
||||
--
|
||||
-- -- Include only active groups to the set of the blue coalition, and filter one time.
|
||||
-- -- Later, reset to include back inactive groups to the set.
|
||||
-- GroupSet = SET_OPSGROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce()
|
||||
-- ... logic ...
|
||||
-- GroupSet = SET_OPSGROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce()
|
||||
--
|
||||
function SET_OPSGROUP:FilterActive( Active )
|
||||
Active = Active or not ( Active == false )
|
||||
self.Filter.Active = Active
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Starts the filtering.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterStart()
|
||||
|
||||
if _DATABASE then
|
||||
self:_FilterStart()
|
||||
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
|
||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activate late activated groups in the set.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:Activate(Delay)
|
||||
local Set = self:GetSet()
|
||||
for GroupID, GroupData in pairs(Set) do
|
||||
local group=GroupData --Ops.OpsGroup#OPSGROUP
|
||||
if group and group:IsAlive()==false then
|
||||
group:Activate(Delay)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Handles the OnDead or OnCrash event for alive groups set.
|
||||
-- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function SET_OPSGROUP:_EventOnDeadOrCrash( Event )
|
||||
self:F( { Event } )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
local ObjectName, Object = self:FindInDatabase( Event )
|
||||
if ObjectName then
|
||||
if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed.
|
||||
self:Remove( ObjectName )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Handles the Database to check on an event (birth) that the Object was added in the Database.
|
||||
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
-- @return #string The name of the GROUP
|
||||
-- @return #table The GROUP
|
||||
function SET_OPSGROUP:AddInDatabase( Event )
|
||||
|
||||
if Event.IniObjectCategory==1 then
|
||||
|
||||
if not self.Database[Event.IniDCSGroupName] then
|
||||
self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName]
|
||||
end
|
||||
|
||||
--- Handles the Database to check on any event that Object exists in the Database.
|
||||
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param Core.Event#EVENTDATA Event Event data table.
|
||||
-- @return #string The name of the GROUP
|
||||
-- @return #table The GROUP
|
||||
function SET_OPSGROUP:FindInDatabase(Event)
|
||||
return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName]
|
||||
end
|
||||
|
||||
--- Iterate the set and call an iterator function for each OPSGROUP object.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #function IteratorFunction The function that will be called for all OPSGROUPs in the set. **NOTE** that the function must have the OPSGROUP as first parameter!
|
||||
-- @param ... (Optional) arguments passed to the `IteratorFunction`.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:ForEachGroup( IteratorFunction, ... )
|
||||
|
||||
self:ForEach(IteratorFunction, arg, self:GetSet())
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check include object.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param Wrapper.Group#GROUP MGroup The group that is checked for inclusion.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:IsIncludeObject(MGroup)
|
||||
|
||||
-- Assume it is and check later if not.
|
||||
local MGroupInclude=true
|
||||
|
||||
-- Filter active.
|
||||
if self.Filter.Active~=nil then
|
||||
|
||||
local MGroupActive = false
|
||||
|
||||
if self.Filter.Active==false or (self.Filter.Active==true and MGroup:IsActive()==true) then
|
||||
MGroupActive = true
|
||||
end
|
||||
|
||||
MGroupInclude = MGroupInclude and MGroupActive
|
||||
end
|
||||
|
||||
-- Filter coalitions.
|
||||
if self.Filter.Coalitions then
|
||||
|
||||
local MGroupCoalition = false
|
||||
|
||||
for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do
|
||||
if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition() then
|
||||
MGroupCoalition = true
|
||||
end
|
||||
end
|
||||
|
||||
MGroupInclude = MGroupInclude and MGroupCoalition
|
||||
end
|
||||
|
||||
-- Filter categories.
|
||||
if self.Filter.Categories then
|
||||
|
||||
local MGroupCategory = false
|
||||
|
||||
for CategoryID, CategoryName in pairs( self.Filter.Categories ) do
|
||||
if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory() then
|
||||
MGroupCategory = true
|
||||
end
|
||||
end
|
||||
|
||||
MGroupInclude = MGroupInclude and MGroupCategory
|
||||
end
|
||||
|
||||
-- Filter countries.
|
||||
if self.Filter.Countries then
|
||||
local MGroupCountry = false
|
||||
for CountryID, CountryName in pairs( self.Filter.Countries ) do
|
||||
if country.id[CountryName] == MGroup:GetCountry() then
|
||||
MGroupCountry = true
|
||||
end
|
||||
end
|
||||
MGroupInclude = MGroupInclude and MGroupCountry
|
||||
end
|
||||
|
||||
-- Filter "prefixes".
|
||||
if self.Filter.GroupPrefixes then
|
||||
|
||||
local MGroupPrefix = false
|
||||
|
||||
for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do
|
||||
if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then --Not sure why "-" is replaced by "%-" ?!
|
||||
MGroupPrefix = true
|
||||
end
|
||||
end
|
||||
|
||||
MGroupInclude = MGroupInclude and MGroupPrefix
|
||||
end
|
||||
|
||||
return MGroupInclude
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -236,6 +236,7 @@ do -- SETTINGS
|
||||
|
||||
--- SETTINGS constructor.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string PlayerName (Optional) Set settings for this player.
|
||||
-- @return #SETTINGS
|
||||
function SETTINGS:Set( PlayerName )
|
||||
|
||||
|
||||
@ -1718,27 +1718,29 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName()))
|
||||
nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true)
|
||||
spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true)
|
||||
--[[
|
||||
elseif Parkingdata~=nil then
|
||||
-- Parking data explicitly set by user as input parameter.
|
||||
nfree=#Parkingdata
|
||||
spots=Parkingdata
|
||||
]]
|
||||
else
|
||||
if ishelo then
|
||||
if termtype==nil then
|
||||
-- Helo is spawned. Try exclusive helo spots first.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
if nfree<nunits then
|
||||
-- Not enough helo ports. Let's try also other terminal types.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
-- No terminal type specified. We try all spots except shelters.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
@ -1747,29 +1749,30 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
if isbomber or istransport or istanker or isawacs then
|
||||
-- First we fill the potentially bigger spots.
|
||||
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
if nfree<nunits then
|
||||
-- Now we try the smaller ones.
|
||||
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
self:T(string.format("Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
-- Terminal type explicitly given.
|
||||
self:T(string.format("Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring(termtype)))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get parking data.
|
||||
-- Debug: Get parking data.
|
||||
--[[
|
||||
local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype)
|
||||
self:T(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype)))
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
@ -1777,6 +1780,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy))
|
||||
end
|
||||
self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits))
|
||||
]]
|
||||
|
||||
-- Set this to true if not enough spots are available for emergency air start.
|
||||
local _notenough=false
|
||||
@ -2144,27 +2148,29 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
|
||||
self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName()))
|
||||
nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true)
|
||||
spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true)
|
||||
--[[
|
||||
elseif Parkingdata~=nil then
|
||||
-- Parking data explicitly set by user as input parameter.
|
||||
nfree=#Parkingdata
|
||||
spots=Parkingdata
|
||||
]]
|
||||
else
|
||||
if ishelo then
|
||||
if termtype==nil then
|
||||
-- Helo is spawned. Try exclusive helo spots first.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
if nfree<nunits then
|
||||
-- Not enough helo ports. Let's try also other terminal types.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
-- No terminal type specified. We try all spots except shelters.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
@ -2176,29 +2182,30 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
|
||||
if isbomber or istransport then
|
||||
-- First we fill the potentially bigger spots.
|
||||
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
if nfree<nunits then
|
||||
-- Now we try the smaller ones.
|
||||
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
self:T(string.format("Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
-- Terminal type explicitly given.
|
||||
self:T(string.format("Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring(termtype)))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get parking data.
|
||||
-- Debug: Get parking data.
|
||||
--[[
|
||||
local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype)
|
||||
self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype)))
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
@ -2206,6 +2213,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
|
||||
SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy))
|
||||
end
|
||||
self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits))
|
||||
]]
|
||||
|
||||
-- Set this to true if not enough spots are available for emergency air start.
|
||||
local _notenough=false
|
||||
@ -2415,8 +2423,7 @@ end
|
||||
-- @param #SPAWN self
|
||||
-- @param DCS#Vec3 Vec3 The Vec3 coordinates where to spawn the group.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
|
||||
self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } )
|
||||
|
||||
@ -2485,8 +2492,7 @@ end
|
||||
-- @param #SPAWN self
|
||||
-- @param Core.Point#Coordinate Coordinate The Coordinate coordinates where to spawn the group.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex )
|
||||
self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
|
||||
|
||||
@ -2502,8 +2508,7 @@ end
|
||||
-- @param #SPAWN self
|
||||
-- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground.
|
||||
@ -2527,8 +2532,7 @@ end
|
||||
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
|
||||
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2()
|
||||
@ -2561,8 +2565,7 @@ end
|
||||
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
|
||||
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2()
|
||||
@ -2618,8 +2621,7 @@ end
|
||||
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
|
||||
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnStatic = STATIC:FindByName( StaticName )
|
||||
@ -2650,8 +2652,7 @@ end
|
||||
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
|
||||
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil when nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnZone = ZONE:New( ZoneName )
|
||||
@ -3400,8 +3401,8 @@ function SPAWN:_SpawnCleanUpScheduler()
|
||||
if Stamp.Vec2 then
|
||||
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
|
||||
local NewVec2 = SpawnUnit:GetVec2()
|
||||
if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
|
||||
-- If the plane is not moving, and is on the ground, assign it with a timestamp...
|
||||
if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then
|
||||
-- If the plane is not moving or dead , and is on the ground, assign it with a timestamp...
|
||||
if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
|
||||
self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
|
||||
self:ReSpawn( SpawnCursor )
|
||||
@ -3419,7 +3420,7 @@ function SPAWN:_SpawnCleanUpScheduler()
|
||||
else
|
||||
if SpawnUnit:InAir() == false then
|
||||
Stamp.Vec2 = SpawnUnit:GetVec2()
|
||||
if SpawnUnit:GetVelocityKMH() < 1 then
|
||||
if (SpawnUnit:GetVelocityKMH() < 1) then
|
||||
Stamp.Time = timer.getTime()
|
||||
end
|
||||
else
|
||||
|
||||
@ -241,6 +241,20 @@ function SPAWNSTATIC:InitShape(StaticShape)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize parameters for spawning FARPs.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number CallsignID Callsign ID. Default 1 (="London").
|
||||
-- @param #number Frequency Frequency in MHz. Default 127.5 MHz.
|
||||
-- @param #number Modulation Modulation 0=AM, 1=FM.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation)
|
||||
self.InitFarp=true
|
||||
self.InitFarpCallsignID=CallsignID or 1
|
||||
self.InitFarpFreq=Frequency or 127.5
|
||||
self.InitFarpModu=Modulation or 0
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize cargo mass.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number Mass Mass of the cargo in kg.
|
||||
@ -420,22 +434,49 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0
|
||||
end
|
||||
|
||||
if self.InitFarp then
|
||||
Template.heliport_callsign_id = self.InitFarpCallsignID
|
||||
Template.heliport_frequency = self.InitFarpFreq
|
||||
Template.heliport_modulation = self.InitFarpModu
|
||||
Template.unitId=nil
|
||||
end
|
||||
|
||||
-- Increase spawn index counter.
|
||||
self.SpawnIndex = self.SpawnIndex + 1
|
||||
|
||||
-- Name of the spawned static.
|
||||
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex)
|
||||
|
||||
-- Register the new static.
|
||||
--_DATABASE:_RegisterStaticTemplate(Template, self.CoalitionID, self.CategoryID, CountryID)
|
||||
_DATABASE:AddStatic(Template.name)
|
||||
-- Add and register the new static.
|
||||
local mystatic=_DATABASE:AddStatic(Template.name)
|
||||
|
||||
-- Debug output.
|
||||
self:T(Template)
|
||||
|
||||
-- Add static to the game.
|
||||
local Static=coalition.addStaticObject(CountryID, Template)
|
||||
|
||||
local Static=nil
|
||||
|
||||
if self.InitFarp then
|
||||
|
||||
return _DATABASE:FindStatic(Static:getName())
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
|
||||
TemplateGroup.visible=true
|
||||
TemplateGroup.hidden=false
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
|
||||
self:T("Spawning FARP")
|
||||
self:T({Template=Template})
|
||||
self:T({TemplateGroup=TemplateGroup})
|
||||
|
||||
-- ED's dirty way to spawn FARPS.
|
||||
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
|
||||
else
|
||||
Static=coalition.addStaticObject(CountryID, Template)
|
||||
end
|
||||
|
||||
return mystatic
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -295,6 +295,17 @@ do -- country
|
||||
-- @field QATAR
|
||||
-- @field OMAN
|
||||
-- @field UNITED_ARAB_EMIRATES
|
||||
-- @field SOUTH_AFRICA
|
||||
-- @field CUBA
|
||||
-- @field PORTUGAL
|
||||
-- @field GDR
|
||||
-- @field LEBANON
|
||||
-- @field CJTF_BLUE
|
||||
-- @field CJTF_RED
|
||||
-- @field UN_PEACEKEEPERS
|
||||
-- @field Argentinia
|
||||
-- @field Cyprus
|
||||
-- @field Slovenia
|
||||
|
||||
country = {} --#country
|
||||
|
||||
@ -460,6 +471,22 @@ do -- Types
|
||||
--@field #boolean lateActivated
|
||||
--@field #boolean uncontrolled
|
||||
|
||||
--- DCS template data structure.
|
||||
-- @type Template
|
||||
-- @field #boolean uncontrolled Aircraft is uncontrolled.
|
||||
-- @field #boolean lateActivation Group is late activated.
|
||||
-- @field #number x 2D Position on x-axis in meters.
|
||||
-- @field #number y 2D Position on y-axis in meters.
|
||||
-- @field #table units Unit list.
|
||||
--
|
||||
|
||||
--- Unit data structure.
|
||||
--@type Template.Unit
|
||||
--@field #string name Name of the unit.
|
||||
--@field #number x
|
||||
--@field #number y
|
||||
--@field #number alt
|
||||
|
||||
end --
|
||||
|
||||
|
||||
@ -1152,6 +1179,10 @@ do -- Unit
|
||||
-- @param #Unit self
|
||||
-- @return #Unit.Desc
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
-- @function [parent=#Unit] enableEmission
|
||||
-- @param #Unit self
|
||||
-- @param #boolean switch
|
||||
|
||||
Unit = {} --#Unit
|
||||
|
||||
@ -1222,7 +1253,7 @@ do -- Group
|
||||
-- @param #Group self
|
||||
-- @return #number
|
||||
|
||||
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function.
|
||||
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed; Initial size limits the unitNumber parameter for Group.getUnit() function.
|
||||
-- @function [parent=#Group] getInitialSize
|
||||
-- @param #Group self
|
||||
-- @return #number
|
||||
@ -1237,6 +1268,11 @@ do -- Group
|
||||
-- @param #Group self
|
||||
-- @return #Controller
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
-- @function [parent=#Group] enableEmission
|
||||
-- @param #Group self
|
||||
-- @param #boolean switch
|
||||
|
||||
Group = {} --#Group
|
||||
|
||||
end -- Group
|
||||
@ -1452,4 +1488,4 @@ do -- AI
|
||||
|
||||
AI = {} --#AI
|
||||
|
||||
end -- AI
|
||||
end -- AI
|
||||
|
||||
@ -59,7 +59,13 @@ function ATC_GROUND:New( Airbases, AirbaseList )
|
||||
|
||||
|
||||
for AirbaseID, Airbase in pairs( self.Airbases ) do
|
||||
Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone()
|
||||
-- Specified ZoneBoundary is used if setted or Airbase radius by default
|
||||
if Airbase.ZoneBoundary then
|
||||
Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary " .. AirbaseID, Airbase.ZoneBoundary )
|
||||
else
|
||||
Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone()
|
||||
end
|
||||
|
||||
Airbase.ZoneRunways = {}
|
||||
for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do
|
||||
Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway )
|
||||
@ -3263,5 +3269,310 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds )
|
||||
end
|
||||
|
||||
|
||||
--- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
|
||||
--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND}
|
||||
--
|
||||
-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi.
|
||||
-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- The default maximum speed for the airbases at Persian Gulf is **50 km/h**. Warnings are given if this speed limit is trespassed.
|
||||
-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way.
|
||||
--
|
||||
-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi.
|
||||
-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.
|
||||
--
|
||||
-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving
|
||||
-- faster than the maximum allowed speed, the pilot will be kicked.
|
||||
--
|
||||
-- Different airbases have different maximum speeds, according safety regulations.
|
||||
--
|
||||
-- # Airbases monitored
|
||||
--
|
||||
-- The following airbases are monitored at the Mariana Island region.
|
||||
-- Use the @{Wrapper.Airbase#AIRBASE.MarianaIslands} enumeration to select the airbases to be monitored.
|
||||
--
|
||||
-- * AIRBASE.MarianaIslands.Rota_Intl
|
||||
-- * AIRBASE.MarianaIslands.Andersen_AFB
|
||||
-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl
|
||||
-- * AIRBASE.MarianaIslands.Saipan_Intl
|
||||
-- * AIRBASE.MarianaIslands.Tinian_Intl
|
||||
-- * AIRBASE.MarianaIslands.Olf_Orote
|
||||
--
|
||||
-- # Installation
|
||||
--
|
||||
-- ## In Single Player Missions
|
||||
--
|
||||
-- ATC\_GROUND is fully functional in single player.
|
||||
--
|
||||
-- ## In Multi Player Missions
|
||||
--
|
||||
-- ATC\_GROUND is functional in multi player, however ...
|
||||
--
|
||||
-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player.
|
||||
-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed
|
||||
-- by Ciribob.
|
||||
--
|
||||
-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight.
|
||||
-- ATC\_GROUND is communicating with this modified script to kick players!
|
||||
--
|
||||
-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob.
|
||||
--
|
||||
-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock)
|
||||
--
|
||||
-- # Script it!
|
||||
--
|
||||
-- ## 1. ATC_GROUND_MARIANAISLANDS Constructor
|
||||
--
|
||||
-- Creates a new ATC_GROUND_MARIANAISLANDS object that will monitor pilots taxiing behaviour.
|
||||
--
|
||||
-- -- This creates a new ATC_GROUND_MARIANAISLANDS object.
|
||||
--
|
||||
-- -- Monitor for these clients the airbases.
|
||||
-- AirbasePoliceCaucasus = ATC_GROUND_MARIANAISLANDS:New()
|
||||
--
|
||||
-- ATC_Ground = ATC_GROUND_MARIANAISLANDS:New(
|
||||
-- { AIRBASE.MarianaIslands.Andersen_AFB,
|
||||
-- AIRBASE.MarianaIslands.Saipan_Intl
|
||||
-- }
|
||||
-- )
|
||||
--
|
||||
--
|
||||
-- ## 2. Set various options
|
||||
--
|
||||
-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes.
|
||||
--
|
||||
-- ### 2.1 Speed limit at an airbase.
|
||||
--
|
||||
-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second.
|
||||
-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour.
|
||||
-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour.
|
||||
--
|
||||
-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately.
|
||||
--
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second.
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour.
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour.
|
||||
--
|
||||
---- @field #ATC_GROUND_MARIANAISLANDS
|
||||
ATC_GROUND_MARIANAISLANDS = {
|
||||
ClassName = "ATC_GROUND_MARIANAISLANDS",
|
||||
Airbases = {
|
||||
|
||||
[AIRBASE.MarianaIslands.Andersen_AFB] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=16534.138036037,["x"]=11357.42159178,},
|
||||
[2]={["y"]=16193.406442738,["x"]=12080.012957533,},
|
||||
[3]={["y"]=13846.966851869,["x"]=12017.348398727,},
|
||||
[4]={["y"]=13085.815989171,["x"]=11686.317876875,},
|
||||
[5]={["y"]=13157.991797443,["x"]=11307.826209991,},
|
||||
[6]={["y"]=12055.725179065,["x"]=10795.955695916,},
|
||||
[7]={["y"]=12762.455491112,["x"]=8890.9830441032,},
|
||||
[8]={["y"]=15955.829493693,["x"]=10333.527220132,},
|
||||
[9]={["y"]=16537.500532414,["x"]=11302.009499603,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=12586.683049611,["x"]=10224.374497932,},
|
||||
[2]={["y"]=16191.720475696,["x"]=11791.299100017,},
|
||||
[3]={["y"]=16126.93956642,["x"]=11938.855615591,},
|
||||
[4]={["y"]=12520.758127164,["x"]=10385.177131701,},
|
||||
[5]={["y"]=12584.654720512,["x"]=10227.416991581,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=12663.030391743,["x"]=9661.9623015306,},
|
||||
[2]={["y"]=16478.347303358,["x"]=11328.665745976,},
|
||||
[3]={["y"]=16405.4731048,["x"]=11479.11570429,},
|
||||
[4]={["y"]=12597.277684174,["x"]=9817.9733769647,},
|
||||
[5]={["y"]=12661.894752524,["x"]=9674.4462086962,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=2288.5182403943,["x"]=1469.0170841716,},
|
||||
[2]={["y"]=1126.2025877996,["x"]=1174.37135631,},
|
||||
[3]={["y"]=-2015.6461924287,["x"]=-484.62000718931,},
|
||||
[4]={["y"]=-2102.1292389114,["x"]=-988.03393750566,},
|
||||
[5]={["y"]=476.03853524366,["x"]=-1220.1783269883,},
|
||||
[6]={["y"]=2059.2220058047,["x"]=78.889693514402,},
|
||||
[7]={["y"]=1898.1396965104,["x"]=705.67531284795,},
|
||||
[8]={["y"]=2760.1768681934,["x"]=1026.0681119777,},
|
||||
[9]={["y"]=2317.2278959994,["x"]=1460.8143254273,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=-1872.6620108821,["x"]=-924.3572605835,},
|
||||
[2]={["y"]=1763.4754603305,["x"]=735.35988877983,},
|
||||
[3]={["y"]=1700.6941677961,["x"]=866.32615476157,},
|
||||
[4]={["y"]=-1934.0078007732,["x"]=-779.8149298453,},
|
||||
[5]={["y"]=-1875.0113982627,["x"]=-914.95971106094,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=-1512.9403660377,["x"]=-1005.5903386188,},
|
||||
[2]={["y"]=1577.9055714735,["x"]=413.22750176368,},
|
||||
[3]={["y"]=1523.1182807849,["x"]=543.89726442232,},
|
||||
[4]={["y"]=-1572.5102998047,["x"]=-867.04004322806,},
|
||||
[5]={["y"]=-1514.2790162347,["x"]=-1003.5823633233,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Rota_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=47237.615412849,["x"]=76048.890408862,},
|
||||
[2]={["y"]=49938.030053628,["x"]=75921.721582932,},
|
||||
[3]={["y"]=49931.24873272,["x"]=75735.184004851,},
|
||||
[4]={["y"]=49295.999227075,["x"]=75754.716414519,},
|
||||
[5]={["y"]=49286.963307515,["x"]=75510.037806569,},
|
||||
[6]={["y"]=48774.280745707,["x"]=75513.331990155,},
|
||||
[7]={["y"]=48785.021396773,["x"]=75795.691662161,},
|
||||
[8]={["y"]=47232.749278491,["x"]=75839.239059146,},
|
||||
[9]={["y"]=47236.687866223,["x"]=76042.706764692,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=49741.295228062,["x"]=75901.50955922,},
|
||||
[2]={["y"]=49739.033213305,["x"]=75768.333440425,},
|
||||
[3]={["y"]=47448.460520408,["x"]=75857.400271466,},
|
||||
[4]={["y"]=47452.270177742,["x"]=75999.965448133,},
|
||||
[5]={["y"]=49738.502011054,["x"]=75905.338915708,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Saipan_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=100489.08491445,["x"]=179799.05158855,},
|
||||
[2]={["y"]=100869.73415313,["x"]=179948.98719903,},
|
||||
[3]={["y"]=101364.78967515,["x"]=180831.98517043,},
|
||||
[4]={["y"]=101563.85713359,["x"]=180885.21496237,},
|
||||
[5]={["y"]=101733.92591034,["x"]=180457.73296886,},
|
||||
[6]={["y"]=103340.30228775,["x"]=180990.08362622,},
|
||||
[7]={["y"]=103459.55080438,["x"]=180453.77747027,},
|
||||
[8]={["y"]=100406.63048095,["x"]=179266.60983762,},
|
||||
[9]={["y"]=100225.55027532,["x"]=179423.9380961,},
|
||||
[10]={["y"]=100477.48558937,["x"]=179791.9827288,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=103170.38882002,["x"]=180654.56630524,},
|
||||
[2]={["y"]=103235.37868835,["x"]=180497.25368418,},
|
||||
[3]={["y"]=100564.72969504,["x"]=179435.41443498,},
|
||||
[4]={["y"]=100509.30718722,["x"]=179584.65394733,},
|
||||
[5]={["y"]=103163.53918905,["x"]=180651.82645285,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=103048.83223261,["x"]=180819.94107128,},
|
||||
[2]={["y"]=103087.60579257,["x"]=180720.06315265,},
|
||||
[3]={["y"]=101037.52694966,["x"]=179899.50061624,},
|
||||
[4]={["y"]=100994.61708907,["x"]=180009.33151758,},
|
||||
[5]={["y"]=103043.26643227,["x"]=180820.40488798,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Tinian_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=88393.477575413,["x"]=166704.16076438,},
|
||||
[2]={["y"]=91581.732441809,["x"]=167402.54409276,},
|
||||
[3]={["y"]=91533.451647402,["x"]=166826.23670062,},
|
||||
[4]={["y"]=90827.604136952,["x"]=166699.75590414,},
|
||||
[5]={["y"]=90894.853975623,["x"]=166375.37836304,},
|
||||
[6]={["y"]=89995.027922869,["x"]=166224.92495935,},
|
||||
[7]={["y"]=88937.62899352,["x"]=166244.48573911,},
|
||||
[8]={["y"]=88408.916178231,["x"]=166480.39896864,},
|
||||
[9]={["y"]=88387.745481732,["x"]=166685.82715656,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=91329.480937912,["x"]=167204.44064529,},
|
||||
[2]={["y"]=91363.95475433,["x"]=167038.15603429,},
|
||||
[3]={["y"]=88585.849307337,["x"]=166520.3807647,},
|
||||
[4]={["y"]=88554.422227212,["x"]=166686.49505251,},
|
||||
[5]={["y"]=91318.8152578,["x"]=167203.31794212,},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
--- Creates a new ATC_GROUND_MARIANAISLANDS object.
|
||||
-- @param #ATC_GROUND_MARIANAISLANDS self
|
||||
-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.MarianaIslands enumerator).
|
||||
-- @return #ATC_GROUND_MARIANAISLANDS self
|
||||
function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) )
|
||||
|
||||
self:SetKickSpeedKmph( 50 )
|
||||
self:SetMaximumKickSpeedKmph( 150 )
|
||||
|
||||
-- -- Andersen
|
||||
-- local AndersenBoundary = GROUP:FindByName( "Andersen Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneBoundary = ZONE_POLYGON:New( "Andersen Boundary", AndersenBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local AndersenRunway1 = GROUP:FindByName( "Andersen Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[1] = ZONE_POLYGON:New( "Andersen Runway 1", AndersenRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local AndersenRunway2 = GROUP:FindByName( "Andersen Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[2] = ZONE_POLYGON:New( "Andersen Runway 2", AndersenRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Antonio_B_Won_Pat_International_Airport
|
||||
-- local AntonioBoundary = GROUP:FindByName( "Antonio Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneBoundary = ZONE_POLYGON:New( "Antonio Boundary", AntonioBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local AntonioRunway1 = GROUP:FindByName( "Antonio Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Antonio Runway 1", AntonioRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local AntonioRunway2 = GROUP:FindByName( "Antonio Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Antonio Runway 2", AntonioRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Rota_International_Airport
|
||||
-- local RotaBoundary = GROUP:FindByName( "Rota Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneBoundary = ZONE_POLYGON:New( "Rota Boundary", RotaBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local RotaRunway1 = GROUP:FindByName( "Rota Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Rota Runway 1", RotaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Saipan_International_Airport
|
||||
-- local SaipanBoundary = GROUP:FindByName( "Saipan Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneBoundary = ZONE_POLYGON:New( "Saipan Boundary", SaipanBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local SaipanRunway1 = GROUP:FindByName( "Saipan Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Saipan Runway 1", SaipanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local SaipanRunway2 = GROUP:FindByName( "Saipan Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Saipan Runway 2", SaipanRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Tinian_International_Airport
|
||||
-- local TinianBoundary = GROUP:FindByName( "Tinian Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneBoundary = ZONE_POLYGON:New( "Tinian Boundary", TinianBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local TinianRunway1 = GROUP:FindByName( "Tinian Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Tinian Runway 1", TinianRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Start SCHEDULER for ATC_GROUND_MARIANAISLANDS object.
|
||||
-- @param #ATC_GROUND_MARIANAISLANDS self
|
||||
-- @param RepeatScanSeconds Time in second for defining occurency of alerts.
|
||||
-- @return nothing
|
||||
function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
@ -693,7 +693,7 @@ ARTY.db={
|
||||
|
||||
--- Arty script version.
|
||||
-- @field #string version
|
||||
ARTY.version="1.1.8"
|
||||
ARTY.version="1.2.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -1146,12 +1146,11 @@ end
|
||||
-- @param alias (Optional) Alias name the group will be calling itself when sending messages. Default is the group name.
|
||||
-- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group.
|
||||
function ARTY:NewFromCargoGroup(cargogroup, alias)
|
||||
BASE:F2({cargogroup=cargogroup, alias=alias})
|
||||
|
||||
if cargogroup then
|
||||
BASE:T(self.lid..string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName()))
|
||||
BASE:T(string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName()))
|
||||
else
|
||||
BASE:E(self.lid.."ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
|
||||
BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
|
||||
return nil
|
||||
end
|
||||
|
||||
@ -2766,38 +2765,32 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
self:_EventFromTo("onafterStatus", Event, From, To)
|
||||
|
||||
-- Get ammo.
|
||||
local ntot, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.iscargo and self.cargogroup then
|
||||
if self.cargogroup:IsLoaded() and not self:is("InTransit") then
|
||||
-- Group is now InTransit state. Current target is canceled.
|
||||
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias))
|
||||
self:Loaded()
|
||||
elseif self.cargogroup:IsUnLoaded() then
|
||||
-- Group has been unloaded and is combat ready again.
|
||||
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias))
|
||||
self:UnLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, ntot, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
|
||||
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, nammo, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.cargogroup then
|
||||
if self.cargogroup:IsLoaded() and not self:is("InTransit") then
|
||||
-- Group is now InTransit state. Current target is canceled.
|
||||
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias))
|
||||
self:Loaded()
|
||||
elseif self.cargogroup:IsUnLoaded() then
|
||||
-- Group has been unloaded and is combat ready again.
|
||||
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias))
|
||||
self:UnLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug current status info.
|
||||
if self.Debug then
|
||||
self:_StatusReport()
|
||||
end
|
||||
|
||||
-- Group is being transported as cargo ==> skip everything and check again in 5 seconds.
|
||||
if self:is("InTransit") then
|
||||
self:__Status(-5)
|
||||
return
|
||||
end
|
||||
|
||||
-- Group on the move.
|
||||
if self:is("Moving") then
|
||||
self:T2(self.lid..string.format("%s: Moving", Controllable:GetName()))
|
||||
@ -2884,7 +2877,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
end
|
||||
|
||||
-- Get ammo.
|
||||
local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
--local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
|
||||
-- Check if we have a target in the queue for which weapons are still available.
|
||||
local gotsome=false
|
||||
@ -2914,9 +2907,23 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
-- Call status again in ~10 sec.
|
||||
self:__Status(self.StatusInterval)
|
||||
|
||||
elseif self.iscargo then
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.cargogroup and self.cargogroup:IsAlive() then
|
||||
|
||||
-- Group is being transported as cargo ==> skip everything and check again in 5 seconds.
|
||||
if self:is("InTransit") then
|
||||
self:__Status(-5)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid..string.format("Arty group %s is not alive!", self.groupname))
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -1001,7 +1001,13 @@ do -- DESIGNATE
|
||||
local ID = self.Detection:GetDetectedItemID( DetectedItem )
|
||||
local MenuText = ID --.. ", " .. Coord:ToStringA2G( AttackGroup )
|
||||
|
||||
MenuText = string.format( "(%3s) %s", Designating, MenuText )
|
||||
-- Use injected MenuName from TaskA2GDispatcher if using same Detection Object
|
||||
if DetectedItem.DesignateMenuName then
|
||||
MenuText = string.format( "(%3s) %s", Designating, DetectedItem.DesignateMenuName )
|
||||
else
|
||||
MenuText = string.format( "(%3s) %s", Designating, MenuText )
|
||||
end
|
||||
|
||||
local DetectedMenu = MENU_GROUP_DELAYED:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
|
||||
-- Build the Lasing menu.
|
||||
|
||||
1267
Moose Development/Moose/Functional/Mantis.lua
Normal file
1267
Moose Development/Moose/Functional/Mantis.lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -5786,6 +5786,8 @@ end
|
||||
-- If desired, the @{#RATMANAGER} can be stopped by the @{#RATMANAGER.Stop}(stoptime) function. The parameter "stoptime" specifies the time delay in seconds after which the manager stops.
|
||||
-- When this happens, no new aircraft will be spawned and the population will eventually decrease to zero.
|
||||
--
|
||||
-- When you are using a time intervall like @{#RATMANAGER.dTspawn}(delay), @{#RATMANAGER} will ignore the amount set with @{#RATMANAGER.New}(). @{#RATMANAGER.dTspawn}(delay) will spawn infinite groups.
|
||||
--
|
||||
-- ## Example
|
||||
-- In this example, three different @{#RAT} objects are created (but not spawned manually). The @{#RATMANAGER} takes care that at least five aircraft of each type are alive and that the total number of aircraft
|
||||
-- spawned is 25. The @{#RATMANAGER} is started after 30 seconds and stopped after two hours.
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Sound files: Check out the pinned messages in the Moose discord *#func-range* channel.
|
||||
-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -53,6 +53,7 @@
|
||||
-- @type RANGE
|
||||
-- @field #string ClassName Name of the Class.
|
||||
-- @field #boolean Debug If true, debug info is send as messages on the screen.
|
||||
-- @field #boolean verbose Verbosity level. Higher means more output to DCS log file.
|
||||
-- @field #string id String id of range for output in DCS log.
|
||||
-- @field #string rangename Name of the range.
|
||||
-- @field Core.Point#COORDINATE location Coordinate of the range location.
|
||||
@ -90,9 +91,9 @@
|
||||
-- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb.
|
||||
-- @field #boolean autosave If true, automatically save results every X seconds.
|
||||
-- @field #number instructorfreq Frequency on which the range control transmitts.
|
||||
-- @field Core.RadioQueue#RADIOQUEUE instructor Instructor radio queue.
|
||||
-- @field Sound.RadioQueue#RADIOQUEUE instructor Instructor radio queue.
|
||||
-- @field #number rangecontrolfreq Frequency on which the range control transmitts.
|
||||
-- @field Core.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue.
|
||||
-- @field Sound.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue.
|
||||
-- @field #string rangecontrolrelayname Name of relay unit.
|
||||
-- @field #string instructorrelayname Name of relay unit.
|
||||
-- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/".
|
||||
@ -289,6 +290,7 @@
|
||||
RANGE={
|
||||
ClassName = "RANGE",
|
||||
Debug = false,
|
||||
verbose = 0,
|
||||
id = nil,
|
||||
rangename = nil,
|
||||
location = nil,
|
||||
@ -518,7 +520,7 @@ RANGE.MenuF10Root=nil
|
||||
|
||||
--- Range script version.
|
||||
-- @field #string version
|
||||
RANGE.version="2.2.3"
|
||||
RANGE.version="2.3.0"
|
||||
|
||||
--TODO list:
|
||||
--TODO: Verbosity level for messages.
|
||||
@ -556,7 +558,6 @@ function RANGE:New(rangename)
|
||||
-- Debug info.
|
||||
local text=string.format("Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename)
|
||||
self:I(self.id..text)
|
||||
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
||||
|
||||
-- Defaults
|
||||
self:SetDefaultPlayerSmokeBomb()
|
||||
@ -723,7 +724,6 @@ function RANGE:onafterStart()
|
||||
-- Starting range.
|
||||
local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets)
|
||||
self:I(self.id..text)
|
||||
MESSAGE:New(text,10):ToAllIf(self.Debug)
|
||||
|
||||
-- Event handling.
|
||||
if self.eventmoose then
|
||||
@ -757,6 +757,7 @@ function RANGE:onafterStart()
|
||||
|
||||
-- Radio queue.
|
||||
self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq, nil, self.rangename)
|
||||
self.rangecontrol.schedonce=true
|
||||
|
||||
-- Init numbers.
|
||||
self.rangecontrol:SetDigit(0, RANGE.Sound.RC0.filename, RANGE.Sound.RC0.duration, self.soundpath)
|
||||
@ -781,7 +782,8 @@ function RANGE:onafterStart()
|
||||
if self.instructorfreq then
|
||||
|
||||
-- Radio queue.
|
||||
self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename)
|
||||
self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename)
|
||||
self.instructor.schedonce=true
|
||||
|
||||
-- Init numbers.
|
||||
self.instructor:SetDigit(0, RANGE.Sound.IR0.filename, RANGE.Sound.IR0.duration, self.soundpath)
|
||||
@ -1148,7 +1150,6 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe
|
||||
-- Neither unit nor static object with this name could be found.
|
||||
local text=string.format("ERROR! Could not find ANY strafe target object with name %s.", _name)
|
||||
self:E(self.id..text)
|
||||
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
||||
|
||||
end
|
||||
|
||||
@ -1168,7 +1169,6 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe
|
||||
if ntargets==0 then
|
||||
local text=string.format("ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s", self.rangename)
|
||||
self:E(self.id..text)
|
||||
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
||||
return
|
||||
end
|
||||
|
||||
@ -1238,7 +1238,6 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe
|
||||
-- Debug info
|
||||
local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline)
|
||||
self:T(self.id..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
return self
|
||||
end
|
||||
@ -1567,13 +1566,13 @@ function RANGE:OnEventBirth(EventData)
|
||||
-- Debug output.
|
||||
local text=string.format("Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)", _playername, _callsign, _unitName, _uid, _group:GetName(), _gid)
|
||||
self:T(self.id..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
-- Reset current strafe status.
|
||||
self.strafeStatus[_uid] = nil
|
||||
|
||||
-- Add Menu commands after a delay of 0.1 seconds.
|
||||
SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
|
||||
--SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
|
||||
self:ScheduleOnce(0.1, self._AddF10Commands, self, _unitName)
|
||||
|
||||
-- By default, some bomb impact points and do not flare each hit on target.
|
||||
self.PlayerSettings[_playername]={} --#RANGE.PlayerData
|
||||
@ -1591,7 +1590,8 @@ function RANGE:OnEventBirth(EventData)
|
||||
|
||||
-- Start check in zone timer.
|
||||
if self.planes[_uid] ~= true then
|
||||
SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1)
|
||||
--SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1)
|
||||
self.timerCheckZone=TIMER:New(self._CheckInZone, self, EventData.IniUnitName):Start(1, 1)
|
||||
self.planes[_uid] = true
|
||||
end
|
||||
|
||||
@ -1674,15 +1674,12 @@ function RANGE:OnEventHit(EventData)
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
-- Position of target.
|
||||
local targetPos = _target:GetCoordinate()
|
||||
|
||||
-- Message to player.
|
||||
--local text=string.format("%s, direct hit on target %s.", self:_myname(_unitName), targetname)
|
||||
--self:DisplayMessageToGroup(_unit, text, 10, true)
|
||||
|
||||
-- Flare target.
|
||||
if self.PlayerSettings[_playername].flaredirecthits then
|
||||
|
||||
-- Position of target.
|
||||
local targetPos = _target:GetCoordinate()
|
||||
|
||||
targetPos:Flare(self.PlayerSettings[_playername].flarecolor)
|
||||
end
|
||||
|
||||
@ -1724,9 +1721,6 @@ function RANGE:OnEventShot(EventData)
|
||||
self:T(self.id.."EVENT SHOT: Weapon name = ".._weaponName)
|
||||
self:T(self.id.."EVENT SHOT: Weapon cate = "..weaponcategory)
|
||||
|
||||
-- Special cases:
|
||||
--local _viggen=string.match(_weapon, "ROBOT") or string.match(_weapon, "RB75") or string.match(_weapon, "BK90") or string.match(_weapon, "RB15") or string.match(_weapon, "RB04")
|
||||
|
||||
-- Tracking conditions for bombs, rockets and missiles.
|
||||
local _bombs = weaponcategory==Weapon.Category.BOMB --string.match(_weapon, "weapons.bombs")
|
||||
local _rockets = weaponcategory==Weapon.Category.ROCKET --string.match(_weapon, "weapons.nurs")
|
||||
@ -1760,7 +1754,7 @@ function RANGE:OnEventShot(EventData)
|
||||
self:T(self.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName()))
|
||||
|
||||
-- Init bomb position.
|
||||
local _lastBombPos = {x=0,y=0,z=0}
|
||||
local _lastBombPos = {x=0,y=0,z=0} --DCS#Vec3
|
||||
|
||||
-- Function monitoring the position of a bomb until impact.
|
||||
local function trackBomb(_ordnance)
|
||||
@ -1916,35 +1910,39 @@ end
|
||||
-- @param #string To To state.
|
||||
function RANGE:onafterStatus(From, Event, To)
|
||||
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
local text=string.format("Range status: %s", fsmstate)
|
||||
|
||||
if self.instructor then
|
||||
local alive="N/A"
|
||||
if self.instructorrelayname then
|
||||
local relay=UNIT:FindByName(self.instructorrelayname)
|
||||
if relay then
|
||||
alive=tostring(relay:IsAlive())
|
||||
end
|
||||
end
|
||||
text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)", self.instructorfreq, tostring(self.instructorrelayname), alive)
|
||||
end
|
||||
|
||||
if self.rangecontrol then
|
||||
local alive="N/A"
|
||||
if self.rangecontrolrelayname then
|
||||
local relay=UNIT:FindByName(self.rangecontrolrelayname)
|
||||
if relay then
|
||||
alive=tostring(relay:IsAlive())
|
||||
end
|
||||
end
|
||||
text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive)
|
||||
end
|
||||
if self.verbose>0 then
|
||||
|
||||
|
||||
-- Check range status.
|
||||
self:I(self.id..text)
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
local text=string.format("Range status: %s", fsmstate)
|
||||
|
||||
if self.instructor then
|
||||
local alive="N/A"
|
||||
if self.instructorrelayname then
|
||||
local relay=UNIT:FindByName(self.instructorrelayname)
|
||||
if relay then
|
||||
alive=tostring(relay:IsAlive())
|
||||
end
|
||||
end
|
||||
text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)", self.instructorfreq, tostring(self.instructorrelayname), alive)
|
||||
end
|
||||
|
||||
if self.rangecontrol then
|
||||
local alive="N/A"
|
||||
if self.rangecontrolrelayname then
|
||||
local relay=UNIT:FindByName(self.rangecontrolrelayname)
|
||||
if relay then
|
||||
alive=tostring(relay:IsAlive())
|
||||
end
|
||||
end
|
||||
text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive)
|
||||
end
|
||||
|
||||
|
||||
-- Check range status.
|
||||
self:I(self.id..text)
|
||||
|
||||
end
|
||||
|
||||
-- Check player status.
|
||||
self:_CheckPlayers()
|
||||
@ -2736,6 +2734,32 @@ function RANGE:_CheckInZone(_unitName)
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
--- Function to check if unit is in zone and facing in the right direction and is below the max alt.
|
||||
local function checkme(targetheading, _zone)
|
||||
local zone=_zone --Core.Zone#ZONE
|
||||
|
||||
-- Heading check.
|
||||
local unitheading = _unit:GetHeading()
|
||||
local pitheading = targetheading-180
|
||||
local deltaheading = unitheading-pitheading
|
||||
local towardspit = math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90
|
||||
|
||||
if towardspit then
|
||||
|
||||
local vec3=_unit:GetVec3()
|
||||
local vec2={x=vec3.x, y=vec3.z} --DCS#Vec2
|
||||
local landheight=land.getHeight(vec2)
|
||||
local unitalt=vec3.y-landheight
|
||||
|
||||
if unitalt<=self.strafemaxalt then
|
||||
local unitinzone=zone:IsVec2InZone(vec2)
|
||||
return unitinzone
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Current position of player unit.
|
||||
local _unitID = _unit:GetID()
|
||||
|
||||
@ -2747,18 +2771,8 @@ function RANGE:_CheckInZone(_unitName)
|
||||
-- Get the current approach zone and check if player is inside.
|
||||
local zone=_currentStrafeRun.zone.polygon --Core.Zone#ZONE_POLYGON_BASE
|
||||
|
||||
local unitheading = _unit:GetHeading()
|
||||
local pitheading = _currentStrafeRun.zone.heading - 180
|
||||
local deltaheading = unitheading-pitheading
|
||||
local towardspit = math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90
|
||||
local unitalt=_unit:GetHeight()-_unit:GetCoordinate():GetLandHeight()
|
||||
|
||||
-- Check if unit is inside zone and below max height AGL.
|
||||
local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit
|
||||
|
||||
-- Debug output
|
||||
local text=string.format("Checking still in zone. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _unitName, _playername, tostring(unitinzone), unitalt, deltaheading)
|
||||
self:T2(self.id..text)
|
||||
-- Check if unit in zone and facing the right direction.
|
||||
local unitinzone=checkme(_currentStrafeRun.zone.heading, zone)
|
||||
|
||||
-- Check if player is in strafe zone and below max alt.
|
||||
if unitinzone then
|
||||
@ -2858,22 +2872,10 @@ function RANGE:_CheckInZone(_unitName)
|
||||
for _,_targetZone in pairs(self.strafeTargets) do
|
||||
|
||||
-- Get the current approach zone and check if player is inside.
|
||||
local zonenname=_targetZone.name
|
||||
local zone=_targetZone.polygon --Core.Zone#ZONE_POLYGON_BASE
|
||||
|
||||
-- Check if player is in zone and below max alt and flying towards the target.
|
||||
local unitheading = _unit:GetHeading()
|
||||
local pitheading = _targetZone.heading - 180
|
||||
local deltaheading = unitheading-pitheading
|
||||
local towardspit = math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90
|
||||
local unitalt =_unit:GetHeight()-_unit:GetCoordinate():GetLandHeight()
|
||||
|
||||
-- Check if unit is inside zone and below max height AGL.
|
||||
local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("Checking zone %s. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _targetZone.name, _unitName, _playername, tostring(unitinzone), unitalt, deltaheading)
|
||||
self:T2(self.id..text)
|
||||
-- Check if unit in zone and facing the right direction.
|
||||
local unitinzone=checkme(_targetZone.heading, zone)
|
||||
|
||||
-- Player is inside zone.
|
||||
if unitinzone then
|
||||
@ -2882,7 +2884,7 @@ function RANGE:_CheckInZone(_unitName)
|
||||
local _ammo=self:_GetAmmo(_unitName)
|
||||
|
||||
-- Init strafe status for this player.
|
||||
self.strafeStatus[_unitID] = {hits = 0, zone = _targetZone, time = 1, ammo=_ammo, pastfoulline=false }
|
||||
self.strafeStatus[_unitID] = {hits = 0, zone = _targetZone, time = 1, ammo=_ammo, pastfoulline=false}
|
||||
|
||||
-- Rolling in!
|
||||
local _msg=string.format("%s, rolling in on strafe pit %s.", self:_myname(_unitName), _targetZone.name)
|
||||
@ -3089,11 +3091,9 @@ function RANGE:_GetAmmo(unitname)
|
||||
|
||||
local text=string.format("Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo)
|
||||
self:T(self.id..text)
|
||||
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
||||
else
|
||||
local text=string.format("Player %s has %d ammo of type %s", playername, Nammo, Tammo)
|
||||
self:T(self.id..text)
|
||||
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -667,8 +667,6 @@ 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,
|
||||
@ -684,8 +682,6 @@ function SCORING:_AddPlayerFromUnit( UnitData )
|
||||
):ToAll()
|
||||
UnitData:GetGroup():Destroy()
|
||||
end
|
||||
--]]
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -15,7 +15,9 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
-- ### Authors: **FlightControl**, **applevangelist**
|
||||
--
|
||||
-- Last Update: July 2021
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -37,20 +39,40 @@
|
||||
--
|
||||
-- @field #SEAD
|
||||
SEAD = {
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } ,
|
||||
Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } ,
|
||||
High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } ,
|
||||
Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } }
|
||||
},
|
||||
SEADGroupPrefixes = {}
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
|
||||
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
|
||||
High = { Evade = 15, DelayOn = { 20, 40 } } ,
|
||||
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
|
||||
},
|
||||
SEADGroupPrefixes = {},
|
||||
SuppressedGroups = {},
|
||||
EngagementRange = 75 -- default 75% engagement range Feature Request #1355
|
||||
}
|
||||
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SEAD.Harms = {
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARN"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
["X_25"] = "X_25",
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
}
|
||||
|
||||
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
|
||||
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
|
||||
-- Chances are big that the missile will miss.
|
||||
-- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken.
|
||||
-- @param #SEAD self
|
||||
-- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken.
|
||||
-- @return SEAD
|
||||
-- @usage
|
||||
-- -- CCCP SEAD Defenses
|
||||
@ -58,144 +80,150 @@ SEAD = {
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
function SEAD:New( SEADGroupPrefixes )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
|
||||
return self
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
|
||||
self:I("*** SEAD - Started Version 0.2.8")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
--- Update the active SEAD Set
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string
|
||||
-- @return #SEAD self
|
||||
function SEAD:UpdateSet( SEADGroupPrefixes )
|
||||
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355
|
||||
-- @param #SEAD self
|
||||
-- @param #number range Set the engagement range in percent, e.g. 50
|
||||
-- @return self
|
||||
function SEAD:SetEngagementRange(range)
|
||||
self:F( { range } )
|
||||
range = range or 75
|
||||
if range < 0 or range > 100 then
|
||||
range = 75
|
||||
end
|
||||
self.EngagementRange = range
|
||||
self:T(string.format("*** SEAD - Engagement range set to %s",range))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a known HARM was fired
|
||||
-- @param #SEAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SEAD:_CheckHarms(WeaponName)
|
||||
self:F( { WeaponName } )
|
||||
local hit = false
|
||||
for _,_name in pairs (SEAD.Harms) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
-- @see SEAD
|
||||
-- @param #SEAD
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SEAD:OnEventShot( EventData )
|
||||
self:F( { EventData } )
|
||||
function SEAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- Start of the 2nd loop
|
||||
self:T( "Missile Launched = " .. SEADWeaponName )
|
||||
|
||||
--if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD
|
||||
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired
|
||||
then
|
||||
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
local _targetMim = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetMimname = Unit.getName(_targetMim)
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimgroupName = _targetMimgroup:getName()
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill
|
||||
self:T( self.SEADGroupPrefixes )
|
||||
self:T( _targetMimgroupName )
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( 'Group Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
|
||||
self:T( string.format("Evading, target skill " ..string.format(_targetskill)) )
|
||||
|
||||
local _targetMim = Weapon.getTarget(SEADWeapon)
|
||||
local _targetMimname = Unit.getName(_targetMim)
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
|
||||
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
|
||||
|
||||
local SuppressedGroups1 = {} -- unit suppressed radar off for a random time
|
||||
|
||||
local function SuppressionEnd1(id)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
SuppressedGroups1[id.groupName] = nil
|
||||
end
|
||||
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
|
||||
local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2])
|
||||
|
||||
if SuppressedGroups1[id.groupName] == nil then
|
||||
|
||||
SuppressedGroups1[id.groupName] = {
|
||||
SuppressionEndTime1 = timer.getTime() + delay1,
|
||||
SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function
|
||||
}
|
||||
|
||||
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function
|
||||
--trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20)
|
||||
end
|
||||
|
||||
local SuppressedGroups = {}
|
||||
|
||||
local function SuppressionEnd(id)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
||||
SuppressedGroups[id.groupName] = nil
|
||||
end
|
||||
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
|
||||
if SuppressedGroups[id.groupName] == nil then
|
||||
SuppressedGroups[id.groupName] = {
|
||||
SuppressionEndTime = timer.getTime() + delay,
|
||||
SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function
|
||||
}
|
||||
|
||||
timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function
|
||||
--trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
self:T({ SEADWeapon })
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
local _targetskill = "Random"
|
||||
local _targetMimgroupName = "none"
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
local _targetMim = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object
|
||||
if _targetUnit and _targetUnit:IsAlive() then
|
||||
local _targetMimgroup = _targetUnit:GetGroup()
|
||||
local _targetMimgroupName = _targetMimgroup:GetName() -- group name
|
||||
--local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill
|
||||
self:T( self.SEADGroupPrefixes )
|
||||
self:T( _targetMimgroupName )
|
||||
end
|
||||
-- see if we are shot at
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( '*** SEAD - Group Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
|
||||
self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) )
|
||||
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
|
||||
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
|
||||
|
||||
--tracker ID table to switch groups off and on again
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
|
||||
local function SuppressionEnd(id) --switch group back on
|
||||
local range = self.EngagementRange -- Feature Request #1355
|
||||
self:T(string.format("*** SEAD - Engagement Range is %d", range))
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
||||
--id.groupName:enableEmission(true)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355
|
||||
self.SuppressedGroups[id.groupName] = nil --delete group id from table when done
|
||||
end
|
||||
-- randomize switch-on time
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
local SuppressionEndTime = timer.getTime() + delay
|
||||
--create entry
|
||||
if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet
|
||||
self.SuppressedGroups[id.groupName] = {
|
||||
SuppressionEndTime = delay
|
||||
}
|
||||
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
--_targetMimgroup:enableEmission(false)
|
||||
timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
548
Moose Development/Moose/Functional/Shorad.lua
Normal file
548
Moose Development/Moose/Functional/Shorad.lua
Normal file
@ -0,0 +1,548 @@
|
||||
--- **Functional** -- Short Range Air Defense System
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **SHORAD** - Short Range Air Defense System
|
||||
-- Controls a network of short range air/missile defense groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author : **applevangelist **
|
||||
--
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: July 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
-- @type SHORAD
|
||||
-- @field #string ClassName
|
||||
-- @field #string name Name of this Shorad
|
||||
-- @field #boolean debug Set the debug state
|
||||
-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP}
|
||||
-- @field #number Radius Shorad defense radius in meters
|
||||
-- @field Core.Set#SET_GROUP Groupset The set of Shorad groups
|
||||
-- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend
|
||||
-- @field #string Coalition The coalition of this Shorad
|
||||
-- @field #number ActiveTimer How long a Shorad stays active after wake-up in seconds
|
||||
-- @field #table ActiveGroups Table for the timer function
|
||||
-- @field #string lid The log ID for the dcs.log
|
||||
-- @field #boolean DefendHarms Default true, intercept incoming HARMS
|
||||
-- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles
|
||||
-- @field #number DefenseLowProb Default 70, minimum detection limit
|
||||
-- @field #number DefenseHighProb Default 90, maximim detection limit
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
|
||||
--
|
||||
-- Simple Class for a more intelligent Short Range Air Defense System
|
||||
--
|
||||
-- #SHORAD
|
||||
-- Moose derived missile intercepting short range defense system.
|
||||
-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy.
|
||||
-- Easily integrated with @{Functional.Mantis#MANTIS} to complete the defensive system setup.
|
||||
--
|
||||
-- ## Usage
|
||||
--
|
||||
-- Set up a #SET_GROUP for the SAM sites to be protected:
|
||||
--
|
||||
-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()`
|
||||
--
|
||||
-- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%.
|
||||
-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire.
|
||||
--
|
||||
-- ### Start a new SHORAD system, parameters are:
|
||||
--
|
||||
-- * Name: Name of this SHORAD.
|
||||
-- * ShoradPrefix: Filter for the Shorad #SET_GROUP.
|
||||
-- * Samset: The #SET_GROUP of SAM sites to defend.
|
||||
-- * Radius: Defense radius in meters.
|
||||
-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call.
|
||||
-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".*
|
||||
--
|
||||
-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")`
|
||||
--
|
||||
-- ## Customize options
|
||||
--
|
||||
-- * SHORAD:SwitchDebug(debug)
|
||||
-- * SHORAD:SwitchHARMDefense(onoff)
|
||||
-- * SHORAD:SwitchAGMDefense(onoff)
|
||||
-- * SHORAD:SetDefenseLimits(low,high)
|
||||
-- * SHORAD:SetActiveTimer(seconds)
|
||||
-- * SHORAD:SetDefenseRadius(meters)
|
||||
--
|
||||
-- @field #SHORAD
|
||||
SHORAD = {
|
||||
ClassName = "SHORAD",
|
||||
name = "MyShorad",
|
||||
debug = false,
|
||||
Prefixes = "",
|
||||
Radius = 20000,
|
||||
Groupset = nil,
|
||||
Samset = nil,
|
||||
Coalition = nil,
|
||||
ActiveTimer = 600, --stay on 10 mins
|
||||
ActiveGroups = {},
|
||||
lid = "",
|
||||
DefendHarms = true,
|
||||
DefendMavs = true,
|
||||
DefenseLowProb = 70,
|
||||
DefenseHighProb = 90,
|
||||
UseEmOnOff = false,
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD System
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
do
|
||||
-- TODO Complete list?
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SHORAD.Harms = {
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARN"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
["X_25"] = "X_25",
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
}
|
||||
|
||||
--- TODO complete list?
|
||||
-- @field Mavs
|
||||
SHORAD.Mavs = {
|
||||
["AGM"] = "AGM",
|
||||
["C-701"] = "C-701",
|
||||
["Kh25"] = "Kh25",
|
||||
["Kh29"] = "Kh29",
|
||||
["Kh31"] = "Kh31",
|
||||
["Kh66"] = "Kh66",
|
||||
}
|
||||
|
||||
--- Instantiates a new SHORAD object
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Name Name of this SHORAD
|
||||
-- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP
|
||||
-- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend
|
||||
-- @param #number Radius Defense radius in meters, used to switch on groups
|
||||
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
|
||||
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
|
||||
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
|
||||
-- @retunr #SHORAD self
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
|
||||
|
||||
self.name = Name or "MyShorad"
|
||||
self.Prefixes = ShoradPrefix or "SAM SHORAD"
|
||||
self.Radius = Radius or 20000
|
||||
self.Coalition = Coalition or "blue"
|
||||
self.Samset = Samset or GroupSet
|
||||
self.ActiveTimer = ActiveTimer or 600
|
||||
self.ActiveGroups = {}
|
||||
self.Groupset = GroupSet
|
||||
self.DefendHarms = true
|
||||
self.DefendMavs = true
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.2.8")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initially set all groups to alarm state GREEN
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:_InitState()
|
||||
self:T(self.lid .. " _InitState")
|
||||
local table = {}
|
||||
local set = self.Groupset
|
||||
self:T({set = set})
|
||||
local aliveset = set:GetAliveSet() --#table
|
||||
for _,_group in pairs (aliveset) do
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOff()
|
||||
_group:EnableEmission(false)
|
||||
_group:OptionAlarmStateRed() --Wrapper.Group#GROUP
|
||||
else
|
||||
_group:OptionAlarmStateGreen() --Wrapper.Group#GROUP
|
||||
end
|
||||
_group:OptionDisperseOnAttack(30)
|
||||
end
|
||||
-- gather entropy
|
||||
for i=1,100 do
|
||||
math.random()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean debug Switch debug on (true) or off (false)
|
||||
function SHORAD:SwitchDebug(onoff)
|
||||
self:T( { onoff } )
|
||||
if onoff then
|
||||
self:SwitchDebugOn()
|
||||
else
|
||||
self.SwitchDebugOff()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOn()
|
||||
self.debug = true
|
||||
--tracing
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SHORAD")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state off
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOff()
|
||||
self.debug = false
|
||||
BASE:TraceOff()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for HARMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
function SHORAD:SwitchHARMDefense(onoff)
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendHarms = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for AGMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
function SHORAD:SwitchAGMDefense(onoff)
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendMavs = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set defense probability limits
|
||||
-- @param #SHORAD self
|
||||
-- @param #number low Minimum detection limit, integer 1-100
|
||||
-- @param #number high Maximum detection limit integer 1-100
|
||||
function SHORAD:SetDefenseLimits(low,high)
|
||||
self:T( { low, high } )
|
||||
local low = low or 70
|
||||
local high = high or 90
|
||||
if (low < 0) or (low > 100) or (low > high) then
|
||||
low = 70
|
||||
end
|
||||
if (high < 0) or (high > 100) or (high < low ) then
|
||||
high = 90
|
||||
end
|
||||
self.DefenseLowProb = low
|
||||
self.DefenseHighProb = high
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of seconds a SHORAD site will stay active
|
||||
-- @param #SHORAD self
|
||||
-- @param #number seconds Number of seconds systems stay active
|
||||
function SHORAD:SetActiveTimer(seconds)
|
||||
self:T(self.lid .. " SetActiveTimer")
|
||||
local timer = seconds or 600
|
||||
if timer < 0 then
|
||||
timer = 600
|
||||
end
|
||||
self.ActiveTimer = timer
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of meters for the SHORAD defense zone
|
||||
-- @param #SHORAD self
|
||||
-- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active
|
||||
function SHORAD:SetDefenseRadius(meters)
|
||||
self:T(self.lid .. " SetDefenseRadius")
|
||||
local radius = meters or 20000
|
||||
if radius < 0 then
|
||||
radius = 20000
|
||||
end
|
||||
self.Radius = radius
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set using Emission on/off instead of changing alarm state
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean switch Decide if we are changing alarm state or AI state
|
||||
function SHORAD:SetUsingEmOnOff(switch)
|
||||
self:T(self.lid .. " SetUsingEmOnOff")
|
||||
self.UseEmOnOff = switch or false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a HARM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckHarms(WeaponName)
|
||||
self:T(self.lid .. " _CheckHarms")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
for _,_name in pairs (SHORAD.Harms) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Check if an AGM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckMavs(WeaponName)
|
||||
self:T(self.lid .. " _CheckMavs")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
for _,_name in pairs (SHORAD.Mavs) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Check the coalition of the attacker
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Coalition name
|
||||
-- @return #boolean Returns false for a match
|
||||
function SHORAD:_CheckCoalition(Coalition)
|
||||
self:T(self.lid .. " _CheckCoalition")
|
||||
local owncoalition = self.Coalition
|
||||
local othercoalition = ""
|
||||
if Coalition == 0 then
|
||||
othercoalition = "neutral"
|
||||
elseif Coalition == 1 then
|
||||
othercoalition = "red"
|
||||
else
|
||||
othercoalition = "blue"
|
||||
end
|
||||
self:T({owncoalition = owncoalition, othercoalition = othercoalition})
|
||||
if owncoalition ~= othercoalition then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the missile is aimed at a SHORAD
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtShorad(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtShorad")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Groupset
|
||||
local shoradset = shorad:GetAliveSet() --#table
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
returnname = true
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Check if the missile is aimed at a SAM site
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtSams(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtSams")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Samset
|
||||
--local shoradset = shorad:GetAliveSet() --#table
|
||||
local shoradset = shorad:GetSet() --#table
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Calculate if the missile shot is detected
|
||||
-- @param #SHORAD self
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
function SHORAD:_ShotIsDetected()
|
||||
self:T(self.lid .. " _ShotIsDetected")
|
||||
local IsDetected = false
|
||||
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
|
||||
local ActualDetection = math.random(1,100) -- value for this shot
|
||||
if ActualDetection <= DetectionProb then
|
||||
IsDetected = true
|
||||
end
|
||||
return IsDetected
|
||||
end
|
||||
|
||||
--- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroup Name of the target group used to build the #ZONE
|
||||
-- @param #number Radius Radius of the #ZONE
|
||||
-- @param #number ActiveTimer Number of seconds to stay active
|
||||
-- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC
|
||||
-- @usage Use this function to integrate with other systems, example
|
||||
--
|
||||
-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
|
||||
-- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")
|
||||
-- myshorad:SwitchDebug(true)
|
||||
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mymantis:AddShorad(myshorad,720)
|
||||
-- mymantis:Start()
|
||||
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
local targetgroup = TargetGroup
|
||||
local targetvec2 = nil
|
||||
if targetcat == Object.Category.UNIT then
|
||||
targetvec2 = GROUP:FindByName(targetgroup):GetVec2()
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetvec2 = STATIC:FindByName(targetgroup,false):GetVec2()
|
||||
else
|
||||
local samset = self.Samset
|
||||
local sam = samset:GetRandom()
|
||||
targetvec2 = sam:GetVec2()
|
||||
end
|
||||
local targetzone = ZONE_RADIUS:New("Shorad",targetvec2,Radius) -- create a defense zone to check
|
||||
local groupset = self.Groupset --Core.Set#SET_GROUP
|
||||
local shoradset = groupset:GetAliveSet() --#table
|
||||
-- local function to switch off shorad again
|
||||
local function SleepShorad(group)
|
||||
local groupname = group:GetName()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
if self.UseEmOnOff then
|
||||
group:EnableEmission(false)
|
||||
--group:SetAIOff()
|
||||
else
|
||||
group:OptionAlarmStateGreen()
|
||||
end
|
||||
local text = string.format("Sleeping SHORAD %s", group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
end
|
||||
-- go through set and find the one(s) to activate
|
||||
for _,_group in pairs (shoradset) do
|
||||
if _group:IsAnyInZone(targetzone) then
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOn()
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
local groupname = _group:GetName()
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
timer.scheduleFunction(SleepShorad, _group, endtime)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Main function - work on the EventData
|
||||
-- @param #SHORAD self
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
function SHORAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
self:T(self.lid .. " HandleEventShot")
|
||||
--local ShootingUnit = EventData.IniDCSUnit
|
||||
--local ShootingUnitName = EventData.IniDCSUnitName
|
||||
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local ShootingWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- get firing coalition
|
||||
local weaponcoalition = EventData.IniGroup:GetCoalition()
|
||||
-- get detection probability
|
||||
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
|
||||
local IsDetected = self:_ShotIsDetected()
|
||||
-- convert to text
|
||||
local DetectedText = "false"
|
||||
if IsDetected then
|
||||
DetectedText = "true"
|
||||
end
|
||||
local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText)
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
--
|
||||
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
|
||||
-- get target data
|
||||
local targetdata = EventData.Weapon:getTarget() -- Identify target
|
||||
local targetcat = targetdata:getCategory() -- Identify category
|
||||
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
|
||||
local targetunit = nil
|
||||
if targetcat == Object.Category.UNIT then -- UNIT
|
||||
targetunit = UNIT:Find(targetdata)
|
||||
elseif targetcat == Object.Category.STATIC then -- STATIC
|
||||
targetunit = STATIC:Find(targetdata)
|
||||
end
|
||||
--local targetunitname = Unit.getName(targetdata) -- Unit name
|
||||
if targetunit and targetunit:IsAlive() then
|
||||
local targetunitname = targetunit:GetName()
|
||||
--local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group
|
||||
local targetgroup = nil
|
||||
local targetgroupname = "none"
|
||||
if targetcat == Object.Category.UNIT then
|
||||
targetgroup = targetunit:GetGroup()
|
||||
targetgroupname = targetgroup:GetName() -- group name
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetgroup = targetunit
|
||||
targetgroupname = targetunitname
|
||||
end
|
||||
local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname))
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
-- check if we or a SAM site are the target
|
||||
--local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP
|
||||
local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean
|
||||
local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean
|
||||
-- if being shot at, find closest SHORADs to activate
|
||||
if shotatsams or shotatus then
|
||||
self:T({shotatsams=shotatsams,shotatus=shotatus})
|
||||
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--
|
||||
end
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD end
|
||||
-----------------------------------------------------------------------
|
||||
@ -199,7 +199,13 @@
|
||||
-- warehouseBatumi:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO)
|
||||
--
|
||||
-- This becomes important when assets are requested from other warehouses as described below. In this case, the five Hueys are now marked as transport helicopters and
|
||||
-- not attack helicopters.
|
||||
-- not attack helicopters. This is also particularly useful when adding assets to a warehouse with the intention of using them to transport other units that are part of
|
||||
-- a subsequent request (see below). Setting the attribute will help to ensure that warehouse module can find the correct unit when attempting to service a request in its
|
||||
-- queue. For example, if we want to add an Amphibious Landing Ship, even though most are indeed armed, it's recommended to do the following:
|
||||
--
|
||||
-- warehouseBatumi:AddAsset("Landing Ship", 1, WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP)
|
||||
--
|
||||
-- Then when adding the request, you can simply specify WAREHOUSE.TransportType.SHIP (which corresponds to NAVAL_UNARMEDSHIP) as the TransportType.
|
||||
--
|
||||
-- ### Setting the Cargo Bay Weight Limit
|
||||
-- You can ajust the cargo bay weight limit, in case it is not calculated correctly automatically. For example, the cargo bay of a C-17A is much smaller in DCS than that of a C-130, which is
|
||||
@ -1575,6 +1581,7 @@ WAREHOUSE = {
|
||||
delivered = {},
|
||||
defending = {},
|
||||
portzone = nil,
|
||||
harborzone = nil,
|
||||
shippinglanes = {},
|
||||
offroadpaths = {},
|
||||
autodefence = false,
|
||||
@ -1733,12 +1740,15 @@ WAREHOUSE.Attribute = {
|
||||
-- @field #string TRAIN Transports are conducted by trains. Not implemented yet. Also trains are buggy in DCS.
|
||||
-- @field #string SELFPROPELLED Assets go to their destination by themselves. No transport carrier needed.
|
||||
WAREHOUSE.TransportType = {
|
||||
AIRPLANE = "Air_TransportPlane",
|
||||
HELICOPTER = "Air_TransportHelo",
|
||||
APC = "Ground_APC",
|
||||
TRAIN = "Ground_Train",
|
||||
SHIP = "Naval_UnarmedShip",
|
||||
SELFPROPELLED = "Selfpropelled",
|
||||
AIRPLANE = "Air_TransportPlane",
|
||||
HELICOPTER = "Air_TransportHelo",
|
||||
APC = "Ground_APC",
|
||||
TRAIN = "Ground_Train",
|
||||
SHIP = "Naval_UnarmedShip",
|
||||
AIRCRAFTCARRIER = "Naval_AircraftCarrier",
|
||||
WARSHIP = "Naval_WarShip",
|
||||
ARMEDSHIP = "Naval_ArmedShip",
|
||||
SELFPROPELLED = "Selfpropelled",
|
||||
}
|
||||
|
||||
--- Warehouse quantity enumerator for selecting number of assets, e.g. all, half etc. of what is in stock rather than an absolute number.
|
||||
@ -1783,7 +1793,7 @@ WAREHOUSE.version="1.0.2"
|
||||
-- TODO: Make more examples: ARTY, CAP, ...
|
||||
-- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport.
|
||||
-- TODO: Handle the case when units of a group die during the transfer.
|
||||
-- TODO: Added habours as interface for transport to from warehouses? Could make a rudimentary shipping dispatcher.
|
||||
-- DONE: Added harbours as interface for transport to/from warehouses. Simplifies process of spawning units near the ship, especially if cargo not self-propelled.
|
||||
-- DONE: Test capturing a neutral warehouse.
|
||||
-- DONE: Add save/load capability of warehouse <==> persistance after mission restart. Difficult in lua!
|
||||
-- DONE: Get cargo bay and weight from CARGO_GROUP and GROUP. No necessary any more!
|
||||
@ -2623,6 +2633,7 @@ end
|
||||
-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot.
|
||||
-- @return #boolean If true, parking is valid.
|
||||
function WAREHOUSE:_CheckParkingValid(spot)
|
||||
|
||||
if self.parkingIDs==nil then
|
||||
return true
|
||||
end
|
||||
@ -2636,6 +2647,25 @@ function WAREHOUSE:_CheckParkingValid(spot)
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check parking ID for an asset.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot.
|
||||
-- @return #boolean If true, parking is valid.
|
||||
function WAREHOUSE:_CheckParkingAsset(spot, asset)
|
||||
|
||||
if asset.parkingIDs==nil then
|
||||
return true
|
||||
end
|
||||
|
||||
for _,id in pairs(asset.parkingIDs or {}) do
|
||||
if spot.TerminalID==id then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Enable auto save of warehouse assets at mission end event.
|
||||
-- @param #WAREHOUSE self
|
||||
@ -2728,6 +2758,18 @@ function WAREHOUSE:SetPortZone(zone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a Harbor Zone for this warehouse where naval cargo units will spawn and be received.
|
||||
-- Both warehouses must have the harbor zone defined for units to properly spawn on both the
|
||||
-- sending and receiving side. The harbor zone should be within 3km of the port zone used for
|
||||
-- warehouse in order to facilitate the boarding process.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @param Core.Zone#ZONE zone The zone defining the naval embarcation/debarcation point for cargo units
|
||||
-- @return #WAREHOUSE self
|
||||
function WAREHOUSE:SetHarborZone(zone)
|
||||
self.harborzone=zone
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a shipping lane from this warehouse to another remote warehouse.
|
||||
-- Note that both warehouses must have a port zone defined before a shipping lane can be added!
|
||||
-- Shipping lane is taken from the waypoints of a (late activated) template group. So set up a group, e.g. a ship or a helicopter, and place its
|
||||
@ -3060,6 +3102,21 @@ function WAREHOUSE:GetCoordinate()
|
||||
return self.warehouse:GetCoordinate()
|
||||
end
|
||||
|
||||
--- Get 3D vector of warehouse static.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @return DCS#Vec3 The 3D vector of the warehouse.
|
||||
function WAREHOUSE:GetVec3()
|
||||
return self.warehouse:GetVec3()
|
||||
end
|
||||
|
||||
--- Get 2D vector of warehouse static.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @return DCS#Vec2 The 2D vector of the warehouse.
|
||||
function WAREHOUSE:GetVec2()
|
||||
return self.warehouse:GetVec2()
|
||||
end
|
||||
|
||||
|
||||
--- Get coalition side of warehouse static.
|
||||
-- @param #WAREHOUSE self
|
||||
-- @return #number Coalition side, i.e. number of @{DCS#coalition.side}.
|
||||
@ -4413,10 +4470,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request)
|
||||
self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!")
|
||||
return
|
||||
|
||||
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then
|
||||
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER then
|
||||
|
||||
self:_ErrorMessage("ERROR: Cargo transport by ship not supported yet!")
|
||||
return
|
||||
-- Spawn Ship in port zone
|
||||
spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.portzone)
|
||||
|
||||
elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
|
||||
|
||||
@ -4544,6 +4601,9 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
|
||||
--_boardradius=nil
|
||||
elseif Request.transporttype==WAREHOUSE.TransportType.APC then
|
||||
--_boardradius=nil
|
||||
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
|
||||
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
|
||||
_boardradius=6000
|
||||
end
|
||||
|
||||
-- Empty cargo group set.
|
||||
@ -4554,7 +4614,6 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
|
||||
|
||||
-- Find asset belonging to this group.
|
||||
local asset=self:FindAssetInDB(_group)
|
||||
|
||||
-- New cargo group object.
|
||||
local cargogroup=CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_boardradius, asset.loadradius)
|
||||
|
||||
@ -4563,6 +4622,7 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
|
||||
|
||||
-- Add group to group set.
|
||||
CargoGroups:AddCargo(cargogroup)
|
||||
|
||||
end
|
||||
|
||||
------------------------
|
||||
@ -4608,23 +4668,54 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
|
||||
-- Set home zone.
|
||||
CargoTransport:SetHomeZone(self.spawnzone)
|
||||
|
||||
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
|
||||
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
|
||||
|
||||
-- Pickup and deploy zones.
|
||||
local PickupZoneSet = SET_ZONE:New():AddZone(self.portzone)
|
||||
PickupZoneSet:AddZone(self.harborzone)
|
||||
local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.harborzone)
|
||||
|
||||
|
||||
-- Get the shipping lane to use and pass it to the Dispatcher
|
||||
local remotename = Request.warehouse.warehouse:GetName()
|
||||
local ShippingLane = self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])]
|
||||
|
||||
-- Define dispatcher for this task.
|
||||
CargoTransport = AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet, ShippingLane)
|
||||
|
||||
-- Set home zone
|
||||
CargoTransport:SetHomeZone(self.portzone)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Unknown transporttype!")
|
||||
end
|
||||
|
||||
-- Set pickup and deploy radii.
|
||||
-- The 20 m inner radius are to ensure that the helo does not land on the warehouse itself in the middle of the default spawn zone.
|
||||
local pickupouter=200
|
||||
local pickupinner=0
|
||||
if self.spawnzone.Radius~=nil then
|
||||
pickupouter=self.spawnzone.Radius
|
||||
local pickupouter = 200
|
||||
local pickupinner = 0
|
||||
local deployouter = 200
|
||||
local deployinner = 0
|
||||
if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
|
||||
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
|
||||
pickupouter=1000
|
||||
pickupinner=20
|
||||
end
|
||||
local deployouter=200
|
||||
local deployinner=0
|
||||
if self.spawnzone.Radius~=nil then
|
||||
deployouter=Request.warehouse.spawnzone.Radius
|
||||
deployinner=20
|
||||
deployouter=1000
|
||||
deployinner=0
|
||||
else
|
||||
pickupouter=200
|
||||
pickupinner=0
|
||||
if self.spawnzone.Radius~=nil then
|
||||
pickupouter=self.spawnzone.Radius
|
||||
pickupinner=20
|
||||
end
|
||||
deployouter=200
|
||||
deployinner=0
|
||||
if self.spawnzone.Radius~=nil then
|
||||
deployouter=Request.warehouse.spawnzone.Radius
|
||||
deployinner=20
|
||||
end
|
||||
end
|
||||
CargoTransport:SetPickupRadius(pickupouter, pickupinner)
|
||||
CargoTransport:SetDeployRadius(deployouter, deployinner)
|
||||
@ -4703,7 +4794,7 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet
|
||||
-- Get cargo group object.
|
||||
local group=Cargo:GetObject() --Wrapper.Group#GROUP
|
||||
|
||||
-- Get request.
|
||||
-- Get request.
|
||||
local request=warehouse:_GetRequestOfGroup(group, warehouse.pending)
|
||||
|
||||
-- Add cargo group to this carrier.
|
||||
@ -4810,6 +4901,13 @@ function WAREHOUSE:onbeforeArrived(From, Event, To, group)
|
||||
local asset=self:FindAssetInDB(group)
|
||||
|
||||
if asset then
|
||||
|
||||
if asset.flightgroup and not asset.arrived then
|
||||
--env.info("FF asset has a flightgroup. arrival will be handled there!")
|
||||
asset.arrived=true
|
||||
return false
|
||||
end
|
||||
|
||||
if asset.arrived==true then
|
||||
-- Asset already arrived (e.g. if multiple units trigger the event via landing).
|
||||
return false
|
||||
@ -4817,6 +4915,7 @@ function WAREHOUSE:onbeforeArrived(From, Event, To, group)
|
||||
asset.arrived=true --ensure this is not called again from the same asset group.
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@ -5717,21 +5816,32 @@ end
|
||||
-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias.
|
||||
-- @param #table parking Parking data for this asset.
|
||||
-- @param #boolean uncontrolled Spawn aircraft in uncontrolled state.
|
||||
-- @param #boolean hotstart Spawn aircraft with engines already on. Default is a cold start with engines off.
|
||||
-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned.
|
||||
function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled, hotstart)
|
||||
function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled)
|
||||
|
||||
if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
|
||||
|
||||
-- Prepare the spawn template.
|
||||
local template=self:_SpawnAssetPrepareTemplate(asset, alias)
|
||||
|
||||
-- Cold start (default).
|
||||
local _type=COORDINATE.WaypointType.TakeOffParking
|
||||
local _action=COORDINATE.WaypointAction.FromParkingArea
|
||||
|
||||
-- Hot start.
|
||||
if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then
|
||||
_type=COORDINATE.WaypointType.TakeOffParkingHot
|
||||
_action=COORDINATE.WaypointAction.FromParkingAreaHot
|
||||
uncontrolled=false
|
||||
end
|
||||
|
||||
|
||||
-- Set route points.
|
||||
if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then
|
||||
|
||||
-- Get flight path if the group goes to another warehouse by itself.
|
||||
if request.toself then
|
||||
local wp=self.airbase:GetCoordinate():WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, 0, false, self.airbase, {}, "Parking")
|
||||
local wp=self.airbase:GetCoordinate():WaypointAir("RADIO", _type, _action, 0, false, self.airbase, {}, "Parking")
|
||||
template.route.points={wp}
|
||||
else
|
||||
template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase)
|
||||
@ -5739,18 +5849,8 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
|
||||
else
|
||||
|
||||
-- Cold start (default).
|
||||
local _type=COORDINATE.WaypointType.TakeOffParking
|
||||
local _action=COORDINATE.WaypointAction.FromParkingArea
|
||||
|
||||
-- Hot start.
|
||||
if hotstart then
|
||||
_type=COORDINATE.WaypointType.TakeOffParkingHot
|
||||
_action=COORDINATE.WaypointAction.FromParkingAreaHot
|
||||
end
|
||||
|
||||
-- First route point is the warehouse airbase.
|
||||
template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action, 0, true, self.airbase, nil, "Spawnpoint")
|
||||
template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO", _type, _action, 0, true, self.airbase, nil, "Spawnpoint")
|
||||
|
||||
end
|
||||
|
||||
@ -5969,7 +6069,7 @@ function WAREHOUSE:_RouteGround(group, request)
|
||||
end
|
||||
|
||||
for n,wp in ipairs(Waypoints) do
|
||||
env.info(n)
|
||||
--env.info(n)
|
||||
local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group, n, #Waypoints)
|
||||
group:SetTaskWaypoint(wp, tf)
|
||||
end
|
||||
@ -7035,11 +7135,16 @@ function WAREHOUSE:_CheckRequestValid(request)
|
||||
valid=false
|
||||
end
|
||||
|
||||
elseif request.transporttype==WAREHOUSE.TransportType.SHIP then
|
||||
elseif request.transporttype==WAREHOUSE.TransportType.SHIP or request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER
|
||||
or request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or request.transporttype==WAREHOUSE.TransportType.WARSHIP then
|
||||
|
||||
-- Transport by ship.
|
||||
self:E("ERROR: Incorrect request. Transport by SHIP not implemented yet!")
|
||||
valid=false
|
||||
local shippinglane=self:HasConnectionNaval(request.warehouse)
|
||||
|
||||
if not shippinglane then
|
||||
self:E("ERROR: Incorrect request. No shipping lane has been defined between warehouses!")
|
||||
valid=false
|
||||
end
|
||||
|
||||
elseif request.transporttype==WAREHOUSE.TransportType.TRAIN then
|
||||
|
||||
@ -7164,24 +7269,35 @@ function WAREHOUSE:_CheckRequestNow(request)
|
||||
_assetcategory=_assets[1].category
|
||||
|
||||
-- Check available parking for air asset units.
|
||||
if self.airbase and (_assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER) then
|
||||
if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
|
||||
|
||||
if self:IsRunwayOperational() then
|
||||
|
||||
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
|
||||
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then
|
||||
|
||||
if self:IsRunwayOperational() then
|
||||
|
||||
--if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then
|
||||
if Parking==nil then
|
||||
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias)
|
||||
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
|
||||
|
||||
--if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then
|
||||
if Parking==nil then
|
||||
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
end
|
||||
|
||||
else
|
||||
-- Runway destroyed.
|
||||
local text=string.format("Warehouse %s: Request denied! Runway is still destroyed", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
return false
|
||||
end
|
||||
|
||||
else
|
||||
-- Runway destroyed.
|
||||
local text=string.format("Warehouse %s: Request denied! Runway is still destroyed", self.alias)
|
||||
|
||||
-- No airbase!
|
||||
local text=string.format("Warehouse %s: Request denied! No airbase", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@ -7205,14 +7321,37 @@ function WAREHOUSE:_CheckRequestNow(request)
|
||||
local _transportcategory=_transports[1].category
|
||||
|
||||
-- Check available parking for transport units.
|
||||
if self.airbase and (_transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER) then
|
||||
local Parking=self:_FindParkingForAssets(self.airbase,_transports)
|
||||
if Parking==nil then
|
||||
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
|
||||
return false
|
||||
if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then
|
||||
|
||||
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then
|
||||
|
||||
if self:IsRunwayOperational() then
|
||||
|
||||
local Parking=self:_FindParkingForAssets(self.airbase,_transports)
|
||||
|
||||
-- No parking ==> return false
|
||||
if Parking==nil then
|
||||
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Runway destroyed.
|
||||
local text=string.format("Warehouse %s: Request denied! Runway is still destroyed", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
-- No airbase
|
||||
local text=string.format("Warehouse %s: Request denied! No airbase currently!", self.alias)
|
||||
self:_InfoMessage(text, 5)
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
@ -7488,7 +7627,7 @@ function WAREHOUSE:_CheckQueue()
|
||||
|
||||
-- Check if request is possible now.
|
||||
local okay=false
|
||||
if valid then
|
||||
if valid then
|
||||
okay=self:_CheckRequestNow(qitem)
|
||||
else
|
||||
-- Remember invalid request and delete later in order not to confuse the loop.
|
||||
@ -7728,7 +7867,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
|
||||
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
-- Check correct terminal type for asset. We don't want helos in shelters etc.
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) then
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and self:_CheckParkingAsset(parkingspot, asset) and airbase:_CheckParkingLists(parkingspot.TerminalID) then
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
@ -8163,10 +8302,9 @@ function WAREHOUSE:_GetAttribute(group)
|
||||
-- Ships
|
||||
local aircraftcarrier=group:HasAttribute("Aircraft Carriers")
|
||||
local warship=group:HasAttribute("Heavy armed ships")
|
||||
local armedship=group:HasAttribute("Armed ships")
|
||||
local armedship=group:HasAttribute("Armed ships") or group:HasAttribute("Armed Ship")
|
||||
local unarmedship=group:HasAttribute("Unarmed ships")
|
||||
|
||||
|
||||
-- Define attribute. Order is important.
|
||||
if transportplane then
|
||||
attribute=WAREHOUSE.Attribute.AIR_TRANSPORTPLANE
|
||||
@ -8929,9 +9067,23 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination)
|
||||
local wp={}
|
||||
local c={}
|
||||
|
||||
-- Cold start (default).
|
||||
local _type=COORDINATE.WaypointType.TakeOffParking
|
||||
local _action=COORDINATE.WaypointAction.FromParkingArea
|
||||
|
||||
-- Hot start.
|
||||
if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then
|
||||
--env.info("FF hot")
|
||||
_type=COORDINATE.WaypointType.TakeOffParkingHot
|
||||
_action=COORDINATE.WaypointAction.FromParkingAreaHot
|
||||
else
|
||||
--env.info("FF cold")
|
||||
end
|
||||
|
||||
|
||||
--- Departure/Take-off
|
||||
c[#c+1]=Pdeparture
|
||||
wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb*3.6, true, departure, nil, "Departure")
|
||||
wp[#wp+1]=Pdeparture:WaypointAir("RADIO", _type, _action, VxClimb*3.6, true, departure, nil, "Departure")
|
||||
|
||||
--- Begin of Cruise
|
||||
local Pcruise=Pdeparture:Translate(d_climb, heading)
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
-- The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- GLOBALS: 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
|
||||
@ -10,10 +9,38 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
|
||||
--- Declare the main database object, which is used internally by the MOOSE classes.
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
--- Settings
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
--- Register cargos.
|
||||
_DATABASE:_RegisterCargos()
|
||||
|
||||
--- Register zones.
|
||||
_DATABASE:_RegisterZones()
|
||||
_DATABASE:_RegisterAirbases()
|
||||
|
||||
--- Check if os etc is available.
|
||||
BASE:I("Checking de-sanitization of os, io and lfs:")
|
||||
local __na=false
|
||||
if os then
|
||||
BASE:I("- os available")
|
||||
else
|
||||
BASE:I("- os NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if io then
|
||||
BASE:I("- io available")
|
||||
else
|
||||
BASE:I("- io NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if lfs then
|
||||
BASE:I("- lfs available")
|
||||
else
|
||||
BASE:I("- lfs NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if __na then
|
||||
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
|
||||
end
|
||||
|
||||
@ -2,10 +2,12 @@ __Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
|
||||
@ -20,15 +22,13 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Message.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
|
||||
@ -68,6 +68,8 @@ __Moose.Include( 'Scripts/Moose/Functional/Suppression.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Fox.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
|
||||
@ -85,6 +87,9 @@ __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Army.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
|
||||
@ -111,16 +116,25 @@ __Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Ship.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_APC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -27,14 +27,19 @@
|
||||
-- **Supported Carriers:**
|
||||
--
|
||||
-- * [USS John C. Stennis](https://en.wikipedia.org/wiki/USS_John_C._Stennis) (CVN-74)
|
||||
-- * [USS Theodore Roosevelt](https://en.wikipedia.org/wiki/USS_Theodore_Roosevelt_(CVN-71)) (CVN-71) [Super Carrier Module]
|
||||
-- * [USS Abraham Lincoln](https://en.wikipedia.org/wiki/USS_Abraham_Lincoln_(CVN-72)) (CVN-72) [Super Carrier Module]
|
||||
-- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73)) (CVN-73) [Super Carrier Module]
|
||||
-- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module]
|
||||
-- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**]
|
||||
--
|
||||
-- **Supported Aircraft:**
|
||||
--
|
||||
-- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI)
|
||||
-- * [F-14B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
||||
-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
||||
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
|
||||
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**]
|
||||
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**]
|
||||
-- * F/A-18C Hornet (AI)
|
||||
-- * F-14A Tomcat (AI)
|
||||
-- * E-2D Hawkeye (AI)
|
||||
@ -47,7 +52,9 @@
|
||||
-- the no other fixed wing aircraft (human or AI controlled) are supposed to land on the Tarawa. Currently only Case I is supported. Case II/III take slightly steps from the CVN carrier.
|
||||
-- However, the two Case II/III pattern are very similar so this is not a big drawback.
|
||||
--
|
||||
-- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well.
|
||||
-- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version.
|
||||
--
|
||||
-- The [DCS Supercarriers](https://forums.eagle.ru/forum/151-dcs-supercarrier/) are also supported.
|
||||
--
|
||||
-- ## Discussion
|
||||
--
|
||||
@ -294,7 +301,7 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- A Case III recovery is conducted during nighttime. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above.
|
||||
-- A Case III recovery is conducted during nighttime or when the visibility is below CASE II minima during the day. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above.
|
||||
--
|
||||
-- The first holding zone starts 21 NM astern the carrier at angels 6. The separation between the stacks is 1000 ft just like in Case I. However, the distance to the boat
|
||||
-- increases by 1 NM with each stack. The general form can be written as D=15+6+(N-1), where D is the distance to the boat in NM and N the number of the stack starting at N=1.
|
||||
@ -566,7 +573,10 @@
|
||||
-- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR
|
||||
-- * Too **H**igh or too **LO**w: H, LO
|
||||
-- * Too **F**ast or too **SLO**w: F, SLO
|
||||
-- * **Fly through** glideslope **down** or **up**: \\ , /
|
||||
-- * **O**ver**S**hoot: OS, only referenced during **X**
|
||||
-- * **Fly through** glideslope **down** or **up**: \\ , /, advisory only
|
||||
-- * **D**rift **L**eft or **R**ight:DL, DR, advisory only
|
||||
-- * **A**ngled **A**pproach: Angled approach (wings level and LUL): AA, advisory only
|
||||
--
|
||||
-- Each grading, x, is subdivided by
|
||||
--
|
||||
@ -627,7 +637,7 @@
|
||||
--
|
||||
-- ## Foul Deck Waveoff
|
||||
--
|
||||
-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is crossing the ship's wake during Case I/II operations,
|
||||
-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is at position IM-IC during Case I/II operations,
|
||||
-- or with an aircraft approaching the 3/4 NM during Case III operations.
|
||||
--
|
||||
-- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**.
|
||||
@ -1257,6 +1267,7 @@ AIRBOSS = {
|
||||
-- @field #string S3BTANKER Lockheed S-3B Viking tanker.
|
||||
-- @field #string E2D Grumman E-2D Hawkeye AWACS.
|
||||
-- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod.
|
||||
-- @field #string T45C T-45C by VNAO
|
||||
AIRBOSS.AircraftCarrier={
|
||||
AV8B="AV8BNA",
|
||||
HORNET="FA-18C_hornet",
|
||||
@ -1265,6 +1276,7 @@ AIRBOSS.AircraftCarrier={
|
||||
F14B="F-14B",
|
||||
F14A_AI="F-14A",
|
||||
FA18C="F/A-18C",
|
||||
T45C="T-45",
|
||||
S3B="S-3B",
|
||||
S3BTANKER="S-3B Tanker",
|
||||
E2D="E-2C",
|
||||
@ -1332,6 +1344,8 @@ AIRBOSS.CarrierType={
|
||||
-- @field #number _min Min _OK_ value. Default -0.5 deg.
|
||||
-- @field #number Left (LUR) threshold. Default -1.0 deg.
|
||||
-- @field #number Right (LUL) threshold. Default 1.0 deg.
|
||||
-- @field #number LeftMed threshold for AA/OS measuring. Default -2.0 deg.
|
||||
-- @field #number RightMed threshold for AA/OS measuring. Default 2.0 deg.
|
||||
-- @field #number LEFT LUR threshold. Default -3.0 deg.
|
||||
-- @field #number RIGHT LUL threshold. Default 3.0 deg.
|
||||
|
||||
@ -1699,7 +1713,7 @@ AIRBOSS.MenuF10Root=nil
|
||||
|
||||
--- Airboss class version.
|
||||
-- @field #string version
|
||||
AIRBOSS.version="1.1.5"
|
||||
AIRBOSS.version="1.1.6"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@ -1933,7 +1947,7 @@ function AIRBOSS:New(carriername, alias)
|
||||
|
||||
-- Welcome players.
|
||||
self:SetWelcomePlayers(true)
|
||||
|
||||
|
||||
-- Coordinates
|
||||
self.landingcoord=COORDINATE:New(0,0,0) --Core.Point#COORDINATE
|
||||
self.sterncoord=COORDINATE:New(0, 0, 0) --Core.Point#COORDINATE
|
||||
@ -1945,11 +1959,11 @@ function AIRBOSS:New(carriername, alias)
|
||||
elseif self.carriertype==AIRBOSS.CarrierType.ROOSEVELT then
|
||||
self:_InitNimitz()
|
||||
elseif self.carriertype==AIRBOSS.CarrierType.LINCOLN then
|
||||
self:_InitNimitz()
|
||||
elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then
|
||||
self:_InitNimitz()
|
||||
elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then
|
||||
self:_InitNimitz()
|
||||
elseif self.carriertype==AIRBOSS.CarrierType.WASHINGTON then
|
||||
self:_InitNimitz()
|
||||
elseif self.carriertype==AIRBOSS.CarrierType.TRUMAN then
|
||||
self:_InitNimitz()
|
||||
elseif self.carriertype==AIRBOSS.CarrierType.VINSON then
|
||||
-- TODO: Carl Vinson parameters.
|
||||
self:_InitStennis()
|
||||
@ -1987,7 +2001,7 @@ function AIRBOSS:New(carriername, alias)
|
||||
self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5)
|
||||
self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green, 5)
|
||||
self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White, 45)
|
||||
self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45)
|
||||
self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45)
|
||||
self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue, 45)
|
||||
self:_GetZoneArcOut(case):SmokeZone(SMOKECOLOR.Blue, 45)
|
||||
self:_GetZonePlatform(case):SmokeZone(SMOKECOLOR.Blue, 45)
|
||||
@ -2027,7 +2041,7 @@ function AIRBOSS:New(carriername, alias)
|
||||
|
||||
-- Bow
|
||||
bow:FlareYellow()
|
||||
|
||||
|
||||
-- Runway half width = 10 m.
|
||||
local r1=stern:Translate(self.carrierparam.rwywidth*0.5, FB+90)
|
||||
local r2=stern:Translate(self.carrierparam.rwywidth*0.5, FB-90)
|
||||
@ -2640,6 +2654,15 @@ function AIRBOSS:SetRecoveryTurnTime(interval)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set multiplayer environment wire correction.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number Dcorr Correction distance in meters. Default 12 m.
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetMPWireCorrection(Dcorr)
|
||||
self.mpWireCorrection=Dcorr or 12
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set time interval for updating queues and other stuff.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number interval Time interval in seconds. Default 30 sec.
|
||||
@ -2798,16 +2821,20 @@ end
|
||||
-- @param #number _max
|
||||
-- @param #number _min
|
||||
-- @param #number Left
|
||||
-- @param #number LeftMed
|
||||
-- @param #number LEFT
|
||||
-- @param #number Right
|
||||
-- @param #number RightMed
|
||||
-- @param #number RIGHT
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT)
|
||||
function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT)
|
||||
self.lue._max=_max or 0.5
|
||||
self.lue._min=_min or -0.5
|
||||
self.lue.Left=Left or -1.0
|
||||
self.lue.LeftMed=LeftMed or -2.0
|
||||
self.lue.LEFT=LEFT or -3.0
|
||||
self.lue.Right=Right or 1.0
|
||||
self.lue.RightMed=RightMed or 2.0
|
||||
self.lue.RIGHT=RIGHT or 3.0
|
||||
return self
|
||||
end
|
||||
@ -3278,7 +3305,7 @@ function AIRBOSS:GetNextRecoveryTime(InSeconds)
|
||||
if InSeconds then
|
||||
return self.recoverywindow.START, self.recoverywindow.STOP
|
||||
else
|
||||
return UTILS.SecondsToClock(self.recoverywindow.START), UTILS.SecondsToClock(self.recoverywindow.STOP)
|
||||
return UTILS.SecondsToClock(self.recoverywindow.START), UTILS.SecondsToClock(self.recoverywindow.STOP)
|
||||
end
|
||||
else
|
||||
if InSeconds then
|
||||
@ -3392,7 +3419,7 @@ function AIRBOSS:onafterStart(From, Event, To)
|
||||
|
||||
--self.StatusScheduler=SCHEDULER:New(self)
|
||||
--self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5)
|
||||
|
||||
|
||||
self.StatusTimer=TIMER:New(self._Status, self):Start(2, 0.5)
|
||||
|
||||
-- Start status check in 1 second.
|
||||
@ -3478,9 +3505,9 @@ function AIRBOSS:onafterStatus(From, Event, To)
|
||||
-- Disable turn into the wind for this window so that we do not do this all over again.
|
||||
self.recoverywindow.WIND=false
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -3574,36 +3601,39 @@ function AIRBOSS:_CheckAIStatus()
|
||||
-- Unit
|
||||
local unit=element.unit
|
||||
|
||||
-- Get lineup and distance to carrier.
|
||||
local lineup=self:_Lineup(unit, true)
|
||||
|
||||
local unitcoord=unit:GetCoord()
|
||||
|
||||
local dist=unitcoord:Get2DDistance(self:GetCoord())
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
-- Distance in NM.
|
||||
local distance=UTILS.MetersToNM(dist)
|
||||
-- Get lineup and distance to carrier.
|
||||
local lineup=self:_Lineup(unit, true)
|
||||
|
||||
-- Altitude in ft.
|
||||
local alt=UTILS.MetersToFeet(unitcoord.y)
|
||||
local unitcoord=unit:GetCoord()
|
||||
|
||||
-- Check if parameters are right and flight is in the groove.
|
||||
if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then
|
||||
local dist=unitcoord:Get2DDistance(self:GetCoord())
|
||||
|
||||
-- Paddles: Call the ball!
|
||||
self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true)
|
||||
-- Distance in NM.
|
||||
local distance=UTILS.MetersToNM(dist)
|
||||
|
||||
-- Pilot: "405, Hornet Ball, 3.2"
|
||||
self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000)
|
||||
-- Altitude in ft.
|
||||
local alt=UTILS.MetersToFeet(unitcoord.y)
|
||||
|
||||
-- Paddles: Roger ball after 0.5 seconds.
|
||||
self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, nil, nil, 0.5, true)
|
||||
-- Check if parameters are right and flight is in the groove.
|
||||
if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then
|
||||
|
||||
-- Flight element called the ball.
|
||||
element.ballcall=true
|
||||
-- Paddles: Call the ball!
|
||||
self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true)
|
||||
|
||||
-- This is for the whole flight. Maybe we need it.
|
||||
flight.ballcall=true
|
||||
-- Pilot: "405, Hornet Ball, 3.2"
|
||||
self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000)
|
||||
|
||||
-- Paddles: Roger ball after 0.5 seconds.
|
||||
self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, nil, nil, 0.5, true)
|
||||
|
||||
-- Flight element called the ball.
|
||||
element.ballcall=true
|
||||
|
||||
-- This is for the whole flight. Maybe we need it.
|
||||
flight.ballcall=true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@ -4139,7 +4169,7 @@ end
|
||||
-- @param #string To To state.
|
||||
function AIRBOSS:onafterStop(From, Event, To)
|
||||
self:I(self.lid..string.format("Stopping airboss script."))
|
||||
|
||||
|
||||
-- Unhandle events.
|
||||
self:UnHandleEvent(EVENTS.Birth)
|
||||
self:UnHandleEvent(EVENTS.Land)
|
||||
@ -4163,7 +4193,7 @@ function AIRBOSS:_InitStennis()
|
||||
|
||||
-- Carrier Parameters.
|
||||
self.carrierparam.sterndist =-153
|
||||
self.carrierparam.deckheight = 19
|
||||
self.carrierparam.deckheight = 19.06
|
||||
|
||||
-- Total size of the carrier (approx as rectangle).
|
||||
self.carrierparam.totlength=310 -- Wiki says 332.8 meters overall length.
|
||||
@ -4171,7 +4201,7 @@ function AIRBOSS:_InitStennis()
|
||||
self.carrierparam.totwidthstarboard=30
|
||||
|
||||
-- Landing runway.
|
||||
self.carrierparam.rwyangle = -9
|
||||
self.carrierparam.rwyangle = -9.1359
|
||||
self.carrierparam.rwylength = 225
|
||||
self.carrierparam.rwywidth = 20
|
||||
|
||||
@ -4314,7 +4344,7 @@ function AIRBOSS:_InitNimitz()
|
||||
|
||||
-- Carrier Parameters.
|
||||
self.carrierparam.sterndist =-164
|
||||
self.carrierparam.deckheight = 20
|
||||
self.carrierparam.deckheight = 20.1494 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\Database\USS_CVN_7X.lua
|
||||
|
||||
-- Total size of the carrier (approx as rectangle).
|
||||
self.carrierparam.totlength=332.8 -- Wiki says 332.8 meters overall length.
|
||||
@ -4322,7 +4352,7 @@ function AIRBOSS:_InitNimitz()
|
||||
self.carrierparam.totwidthstarboard=35
|
||||
|
||||
-- Landing runway.
|
||||
self.carrierparam.rwyangle = -9
|
||||
self.carrierparam.rwyangle = -9.1359 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\scripts\USS_Nimitz_RunwaysAndRoutes.lua
|
||||
self.carrierparam.rwylength = 250
|
||||
self.carrierparam.rwywidth = 25
|
||||
|
||||
@ -5465,6 +5495,7 @@ function AIRBOSS:_GetAircraftAoA(playerData)
|
||||
|
||||
-- Get AC type.
|
||||
local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET
|
||||
local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C
|
||||
local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC
|
||||
local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
|
||||
local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B
|
||||
@ -5491,6 +5522,15 @@ function AIRBOSS:_GetAircraftAoA(playerData)
|
||||
aoa.OnSpeedMin = self:_AoAUnit2Deg(playerData, 14.5) --14.17 --14.5 units
|
||||
aoa.Fast = self:_AoAUnit2Deg(playerData, 14.0) --13.33 --14.0 units
|
||||
aoa.FAST = self:_AoAUnit2Deg(playerData, 13.0) --11.67 --13.0 units
|
||||
elseif goshawk then
|
||||
-- T-45C Goshawk parameters.
|
||||
aoa.SLOW = 8.00 --19
|
||||
aoa.Slow = 7.75 --18
|
||||
aoa.OnSpeedMax = 7.25 --17.5
|
||||
aoa.OnSpeed = 7.00 --17
|
||||
aoa.OnSpeedMin = 6.75 --16.5
|
||||
aoa.Fast = 6.25 --16
|
||||
aoa.FAST = 6.00 --15
|
||||
elseif skyhawk then
|
||||
-- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390
|
||||
-- Note that these are arbitrary UNITS and not degrees. We need a conversion formula!
|
||||
@ -5675,6 +5715,9 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
|
||||
elseif skyhawk then
|
||||
alt=UTILS.FeetToMeters(600)
|
||||
speed=UTILS.KnotsToMps(250)
|
||||
elseif goshawk then
|
||||
alt=UTILS.FeetToMeters(800)
|
||||
speed=UTILS.KnotsToMps(300)
|
||||
end
|
||||
|
||||
elseif step==AIRBOSS.PatternStep.BREAKENTRY then
|
||||
@ -5685,11 +5728,14 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
|
||||
elseif skyhawk then
|
||||
alt=UTILS.FeetToMeters(600)
|
||||
speed=UTILS.KnotsToMps(250)
|
||||
elseif goshawk then
|
||||
alt=UTILS.FeetToMeters(800)
|
||||
speed=UTILS.KnotsToMps(300)
|
||||
end
|
||||
|
||||
elseif step==AIRBOSS.PatternStep.EARLYBREAK then
|
||||
|
||||
if hornet or tomcat or harrier then
|
||||
if hornet or tomcat or harrier or goshawk then
|
||||
alt=UTILS.FeetToMeters(800)
|
||||
elseif skyhawk then
|
||||
alt=UTILS.FeetToMeters(600)
|
||||
@ -5697,7 +5743,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
|
||||
|
||||
elseif step==AIRBOSS.PatternStep.LATEBREAK then
|
||||
|
||||
if hornet or tomcat or harrier then
|
||||
if hornet or tomcat or harrier or goshawk then
|
||||
alt=UTILS.FeetToMeters(800)
|
||||
elseif skyhawk then
|
||||
alt=UTILS.FeetToMeters(600)
|
||||
@ -5705,7 +5751,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
|
||||
|
||||
elseif step==AIRBOSS.PatternStep.ABEAM then
|
||||
|
||||
if hornet or tomcat or harrier then
|
||||
if hornet or tomcat or harrier or goshawk then
|
||||
alt=UTILS.FeetToMeters(600)
|
||||
elseif skyhawk then
|
||||
alt=UTILS.FeetToMeters(500)
|
||||
@ -5720,10 +5766,19 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
|
||||
dist=UTILS.NMToMeters(1.2)
|
||||
end
|
||||
|
||||
if goshawk then
|
||||
-- 0.9 to 1.1 NM per natops ch.4 page 48
|
||||
dist=UTILS.NMToMeters(0.9)
|
||||
else
|
||||
dist=UTILS.NMToMeters(1.1)
|
||||
end
|
||||
|
||||
elseif step==AIRBOSS.PatternStep.NINETY then
|
||||
|
||||
if hornet or tomcat then
|
||||
alt=UTILS.FeetToMeters(500)
|
||||
elseif goshawk then
|
||||
alt=UTILS.FeetToMeters(450)
|
||||
elseif skyhawk then
|
||||
alt=UTILS.FeetToMeters(500)
|
||||
elseif harrier then
|
||||
@ -5734,7 +5789,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
|
||||
|
||||
elseif step==AIRBOSS.PatternStep.WAKE then
|
||||
|
||||
if hornet then
|
||||
if hornet or goshawk then
|
||||
alt=UTILS.FeetToMeters(370)
|
||||
elseif tomcat then
|
||||
alt=UTILS.FeetToMeters(430) -- Tomcat should be a bit higher as it intercepts the GS a bit higher.
|
||||
@ -5747,7 +5802,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step)
|
||||
|
||||
elseif step==AIRBOSS.PatternStep.FINAL then
|
||||
|
||||
if hornet then
|
||||
if hornet or goshawk then
|
||||
alt=UTILS.FeetToMeters(300)
|
||||
elseif tomcat then
|
||||
alt=UTILS.FeetToMeters(360)
|
||||
@ -6066,6 +6121,11 @@ function AIRBOSS:_ScanCarrierZone()
|
||||
|
||||
-- Get flight group if possible.
|
||||
local knownflight=self:_GetFlightFromGroupInQueue(group, self.flights)
|
||||
|
||||
-- Unknown new AI flight. Create a new flight group.
|
||||
if not knownflight and not self:_IsHuman(group) then
|
||||
knownflight=self:_CreateFlightGroup(group)
|
||||
end
|
||||
|
||||
-- Get aircraft type name.
|
||||
local actype=group:GetTypeName()
|
||||
@ -6079,10 +6139,10 @@ function AIRBOSS:_ScanCarrierZone()
|
||||
local putintomarshal=false
|
||||
|
||||
-- Get flight group.
|
||||
local flight=_DATABASE:GetFlightGroup(groupname)
|
||||
|
||||
local flight=_DATABASE:GetOpsGroup(groupname)
|
||||
|
||||
if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then
|
||||
if flight.ishelo then
|
||||
if flight.isHelo then
|
||||
else
|
||||
putintomarshal=true
|
||||
end
|
||||
@ -6117,16 +6177,13 @@ function AIRBOSS:_ScanCarrierZone()
|
||||
-- Break the loop to not have all flights at once! Spams the message screen.
|
||||
break
|
||||
|
||||
end -- Closed in or tanker/AWACS
|
||||
end -- Closed in or tanker/AWACS
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Unknown new AI flight. Create a new flight group.
|
||||
if not self:_IsHuman(group) then
|
||||
self:_CreateFlightGroup(group)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
@ -6223,7 +6280,7 @@ function AIRBOSS:_MarshalPlayer(playerData, stack)
|
||||
|
||||
-- Set stack flag.
|
||||
flight.flag=stack
|
||||
|
||||
|
||||
-- Trigger Marshal event.
|
||||
self:Marshal(flight)
|
||||
end
|
||||
@ -6482,7 +6539,7 @@ function AIRBOSS:_MarshalAI(flight, nstack, respawn)
|
||||
|
||||
-- Route group.
|
||||
flight.group:Route(wp, 1)
|
||||
|
||||
|
||||
-- Trigger Marshal event.
|
||||
self:Marshal(flight)
|
||||
|
||||
@ -7018,7 +7075,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty)
|
||||
|
||||
-- Recovery case.
|
||||
case=case or self.case
|
||||
|
||||
|
||||
if case==1 then
|
||||
return self:_GetFreeStack_Old(ai, case, empty)
|
||||
end
|
||||
@ -7034,7 +7091,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty)
|
||||
for i=1,nmaxstacks do
|
||||
stack[i]=self.NmaxStack -- Number of human flights per stack.
|
||||
end
|
||||
|
||||
|
||||
local nmax=1
|
||||
|
||||
-- Loop over all flights in marshal stack.
|
||||
@ -7046,7 +7103,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty)
|
||||
|
||||
-- Get stack of flight.
|
||||
local n=flight.flag
|
||||
|
||||
|
||||
if n>nmax then
|
||||
nmax=n
|
||||
end
|
||||
@ -7063,7 +7120,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local nfree=nil
|
||||
if stack[nmax]==0 then
|
||||
-- Max occupied stack is completely full!
|
||||
@ -7079,7 +7136,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty)
|
||||
-- Case II/III return next stack
|
||||
nfree=nmax+1
|
||||
end
|
||||
|
||||
|
||||
elseif stack[nmax]==self.NmaxStack then
|
||||
-- Max occupied stack is completely empty! This should happen only when there is no other flight in the marshal queue.
|
||||
self:E(self.lid..string.format("ERROR: Max occupied stack is empty. Should not happen! Nmax=%d, stack[nmax]=%d", nmax, stack[nmax]))
|
||||
@ -7091,7 +7148,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty)
|
||||
else
|
||||
nfree=nmax
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
self:I(self.lid..string.format("Returning free stack %s", tostring(nfree)))
|
||||
@ -10054,6 +10111,27 @@ function AIRBOSS:_Groove(playerData)
|
||||
-- Distance in NM.
|
||||
local d=UTILS.MetersToNM(rho)
|
||||
|
||||
-- Drift on lineup.
|
||||
if rho>=RAR and rho<=RIM then
|
||||
if gd.LUE>0.22 and lineupError<-0.22 then
|
||||
env.info" Drift Right across centre ==> DR-"
|
||||
gd.Drift=" DR"
|
||||
self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
|
||||
elseif gd.LUE<-0.22 and lineupError>0.22 then
|
||||
env.info" Drift Left ==> DL-"
|
||||
gd.Drift=" DL"
|
||||
self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
|
||||
elseif gd.LUE>0.13 and lineupError<-0.14 then
|
||||
env.info" Little Drift Right across centre ==> (DR-)"
|
||||
gd.Drift=" (DR)"
|
||||
self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
|
||||
elseif gd.LUE<-0.13 and lineupError>0.14 then
|
||||
env.info" Little Drift Left across centre ==> (DL-)"
|
||||
gd.Drift=" (DL)"
|
||||
self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError))
|
||||
end
|
||||
end
|
||||
|
||||
-- Update max deviation of line up error.
|
||||
if math.abs(lineupError)>math.abs(gd.LUE) then
|
||||
self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f", gs, d, lineupError, gd.LUE))
|
||||
@ -10364,7 +10442,7 @@ function AIRBOSS:_GetSternCoord()
|
||||
self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(7, FB+90, true, true)
|
||||
else
|
||||
-- Nimitz SC: translate 8 meters starboard wrt Final bearing.
|
||||
self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8.5, FB+90, true, true)
|
||||
self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(9.5, FB+90, true, true)
|
||||
end
|
||||
|
||||
-- Set altitude.
|
||||
@ -10394,6 +10472,11 @@ function AIRBOSS:_GetWire(Lcoord, dc)
|
||||
|
||||
-- Corrected landing distance wrt to stern. Landing distance needs to be reduced due to delayed landing event for human players.
|
||||
local d=Ldist-dc
|
||||
|
||||
-- Multiplayer wire correction.
|
||||
if self.mpWireCorrection then
|
||||
d=d-self.mpWireCorrection
|
||||
end
|
||||
|
||||
-- Shift wires from stern to their correct position.
|
||||
local w1=self.carrierparam.wire1
|
||||
@ -10485,6 +10568,9 @@ function AIRBOSS:_Trapped(playerData)
|
||||
elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then
|
||||
-- A-4E gets slowed down much faster the the F/A-18C!
|
||||
dcorr=56
|
||||
elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then
|
||||
-- T-45 also gets slowed down much faster the the F/A-18C.
|
||||
dcorr=56
|
||||
end
|
||||
|
||||
-- Get wire.
|
||||
@ -10611,7 +10697,7 @@ function AIRBOSS:_GetZoneInitial(case)
|
||||
|
||||
-- Polygon zone.
|
||||
--local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2)
|
||||
|
||||
|
||||
self.zoneInitial:UpdateFromVec2(vec2)
|
||||
|
||||
--return zone
|
||||
@ -10640,13 +10726,13 @@ function AIRBOSS:_GetZoneLineup()
|
||||
|
||||
-- Vec2 array.
|
||||
local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()}
|
||||
|
||||
|
||||
self.zoneLineup:UpdateFromVec2(vec2)
|
||||
|
||||
-- Polygon zone.
|
||||
--local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2)
|
||||
--return zone
|
||||
|
||||
|
||||
return self.zoneLineup
|
||||
end
|
||||
|
||||
@ -10681,13 +10767,13 @@ function AIRBOSS:_GetZoneGroove(l, w, b)
|
||||
|
||||
-- Vec2 array.
|
||||
local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()}
|
||||
|
||||
|
||||
self.zoneGroove:UpdateFromVec2(vec2)
|
||||
|
||||
-- Polygon zone.
|
||||
--local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2)
|
||||
--return zone
|
||||
|
||||
|
||||
return self.zoneGroove
|
||||
end
|
||||
|
||||
@ -10713,7 +10799,7 @@ function AIRBOSS:_GetZoneBullseye(case)
|
||||
-- Create zone.
|
||||
local zone=ZONE_RADIUS:New("Zone Bullseye", vec2, radius)
|
||||
return zone
|
||||
|
||||
|
||||
--self.zoneBullseye=self.zoneBullseye or ZONE_RADIUS:New("Zone Bullseye", vec2, radius)
|
||||
end
|
||||
|
||||
@ -10942,9 +11028,9 @@ function AIRBOSS:_GetZoneCarrierBox()
|
||||
-- Create polygon zone.
|
||||
--local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2)
|
||||
--return zone
|
||||
|
||||
|
||||
self.zoneCarrierbox:UpdateFromVec2(vec2)
|
||||
|
||||
|
||||
return self.zoneCarrierbox
|
||||
end
|
||||
|
||||
@ -10979,9 +11065,9 @@ function AIRBOSS:_GetZoneRunwayBox()
|
||||
-- Create polygon zone.
|
||||
--local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2)
|
||||
--return zone
|
||||
|
||||
|
||||
self.zoneRunwaybox:UpdateFromVec2(vec2)
|
||||
|
||||
|
||||
return self.zoneRunwaybox
|
||||
end
|
||||
|
||||
@ -11112,7 +11198,7 @@ function AIRBOSS:_GetZoneHolding(case, stack)
|
||||
-- Square zone length=7NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier.
|
||||
-- So stay 0-5 NM (+1 NM error margin) port of carrier.
|
||||
self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone")
|
||||
|
||||
|
||||
self.zoneHolding:UpdateFromVec2(p)
|
||||
end
|
||||
|
||||
@ -11158,12 +11244,12 @@ function AIRBOSS:_GetZoneCommence(case, stack)
|
||||
|
||||
-- Create holding zone.
|
||||
self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone")
|
||||
|
||||
|
||||
self.zoneCommence:UpdateFromVec2(Three:GetVec2(), R)
|
||||
|
||||
else
|
||||
-- Case II/III
|
||||
|
||||
|
||||
stack=stack or 1
|
||||
|
||||
-- Start point at 21 NM for stack=1.
|
||||
@ -11191,7 +11277,7 @@ function AIRBOSS:_GetZoneCommence(case, stack)
|
||||
|
||||
-- Zone polygon.
|
||||
self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone")
|
||||
|
||||
|
||||
self.zoneCommence:UpdateFromVec2(p)
|
||||
|
||||
end
|
||||
@ -11439,7 +11525,7 @@ end
|
||||
-- @param #AIRBOSS self
|
||||
-- @return Core.Point#COORDINATE Optimal landing coordinate.
|
||||
function AIRBOSS:_GetOptLandingCoordinate()
|
||||
|
||||
|
||||
-- Start with stern coordiante.
|
||||
self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord())
|
||||
|
||||
@ -11558,7 +11644,7 @@ end
|
||||
|
||||
--- Get wind speed on carrier deck parallel and perpendicular to runway.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number alt Altitude in meters. Default 50 m.
|
||||
-- @param #number alt Altitude in meters. Default 15 m. (change made from 50m from Discord discussion from Sickdog)
|
||||
-- @return #number Wind component parallel to runway im m/s.
|
||||
-- @return #number Wind component perpendicular to runway in m/s.
|
||||
-- @return #number Total wind strength in m/s.
|
||||
@ -11581,7 +11667,7 @@ function AIRBOSS:GetWindOnDeck(alt)
|
||||
zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle)
|
||||
|
||||
-- Wind (from) vector
|
||||
local vw=cv:GetWindWithTurbulenceVec3(alt or 50)
|
||||
local vw=cv:GetWindWithTurbulenceVec3(alt or 15)
|
||||
|
||||
-- Total wind velocity vector.
|
||||
-- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total.
|
||||
@ -11995,15 +12081,15 @@ function AIRBOSS:_EvalGrooveTime(playerData)
|
||||
|
||||
local grade=""
|
||||
if t<9 then
|
||||
grade="--"
|
||||
elseif t<12 then
|
||||
grade="(OK)"
|
||||
elseif t<22 then
|
||||
grade="OK"
|
||||
grade="_NESA_"
|
||||
elseif t<15 then
|
||||
grade="NESA"
|
||||
elseif t<19 then
|
||||
grade="OK Groove"
|
||||
elseif t<=24 then
|
||||
grade="(OK)"
|
||||
grade="(LIG)"
|
||||
else
|
||||
grade="--"
|
||||
grade="LIG"
|
||||
end
|
||||
|
||||
-- The unicorn!
|
||||
@ -12042,9 +12128,9 @@ function AIRBOSS:_LSOgrade(playerData)
|
||||
local nS=count(G, '%(')
|
||||
local nN=N-nS-nL
|
||||
|
||||
-- Groove time 16-18 sec for a unicorn.
|
||||
-- Groove time 15-18.99 sec for a unicorn.
|
||||
local Tgroove=playerData.Tgroove
|
||||
local TgrooveUnicorn=Tgroove and (Tgroove>=16.0 and Tgroove<=18.0) or false
|
||||
local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false
|
||||
|
||||
local grade
|
||||
local points
|
||||
@ -12177,7 +12263,35 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
|
||||
|
||||
-- Aircraft specific AoA values.
|
||||
local acaoa=self:_GetAircraftAoA(playerData)
|
||||
|
||||
|
||||
--Angled Approach.
|
||||
local P=nil
|
||||
if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then
|
||||
if LUE>self.lue.RIGHT then
|
||||
P=underline("AA")
|
||||
elseif
|
||||
LUE>self.lue.RightMed then
|
||||
P="AA "
|
||||
elseif
|
||||
LUE>self.lue.Right then
|
||||
P=little("AA")
|
||||
end
|
||||
end
|
||||
|
||||
--Overshoot Start.
|
||||
local O=nil
|
||||
if step==AIRBOSS.PatternStep.GROOVE_XX then
|
||||
if LUE<self.lue.LEFT then
|
||||
O=underline("OS")
|
||||
elseif
|
||||
LUE<self.lue.Left then
|
||||
O="OS"
|
||||
elseif
|
||||
LUE<self.lue._min then
|
||||
O=little("OS")
|
||||
end
|
||||
end
|
||||
|
||||
-- Speed via AoA. Depends on aircraft type.
|
||||
local S=nil
|
||||
if AOA>acaoa.SLOW then
|
||||
@ -12210,7 +12324,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
|
||||
A=little("LO")
|
||||
end
|
||||
|
||||
-- Line up. Good [-0.5, 0.5]
|
||||
-- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5]
|
||||
local D=nil
|
||||
if LUE>self.lue.RIGHT then
|
||||
D=underline("LUL")
|
||||
@ -12218,12 +12332,22 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
|
||||
D="LUL"
|
||||
elseif LUE>self.lue._max then
|
||||
D=little("LUL")
|
||||
elseif LUE<self.lue.LEFT then
|
||||
D=underline("LUR")
|
||||
elseif LUE<self.lue.Left then
|
||||
D="LUR"
|
||||
elseif LUE<self.lue._min then
|
||||
D=little("LUR")
|
||||
elseif playerData.case<3 then
|
||||
if LUE<self.lue.LEFT and step~=AIRBOSS.PatternStep.GROOVE_XX then
|
||||
D=underline("LUR")
|
||||
elseif LUE<self.lue.Left and step~=AIRBOSS.PatternStep.GROOVE_XX then
|
||||
D="LUR"
|
||||
elseif LUE<self.lue._min and step~=AIRBOSS.PatternStep.GROOVE_XX then
|
||||
D=little("LUR")
|
||||
end
|
||||
elseif playerData.case==3 then
|
||||
if LUE<self.lue.LEFT then
|
||||
D=underline("LUR")
|
||||
elseif LUE<self.lue.Left then
|
||||
D="LUR"
|
||||
elseif LUE<self.lue._min then
|
||||
D=little("LUR")
|
||||
end
|
||||
end
|
||||
|
||||
-- Compile.
|
||||
@ -12233,6 +12357,11 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
|
||||
if fdata.FlyThrough then
|
||||
G=G..fdata.FlyThrough
|
||||
end
|
||||
-- Angled Approach - doesn't affect score, advisory only.
|
||||
if P then
|
||||
G=G..P
|
||||
n=n
|
||||
end
|
||||
-- Speed.
|
||||
if S then
|
||||
G=G..S
|
||||
@ -12248,7 +12377,17 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep)
|
||||
G=G..D
|
||||
n=n+1
|
||||
end
|
||||
|
||||
--Drift in Lineup
|
||||
if fdata.Drift then
|
||||
G=G..fdata.Drift
|
||||
n=n -- Drift doesn't affect score, advisory only.
|
||||
end
|
||||
-- Overshoot.
|
||||
if O then
|
||||
G=G..O
|
||||
n=n+1
|
||||
end
|
||||
|
||||
-- Add current step.
|
||||
local step=self:_GS(step)
|
||||
step=step:gsub("XX","X")
|
||||
@ -14074,6 +14213,8 @@ function AIRBOSS:_GetACNickname(actype)
|
||||
local nickname="unknown"
|
||||
if actype==AIRBOSS.AircraftCarrier.A4EC then
|
||||
nickname="Skyhawk"
|
||||
elseif actype==AIRBOSS.AircraftCarrier.T45C then
|
||||
nickname="Goshawk"
|
||||
elseif actype==AIRBOSS.AircraftCarrier.AV8B then
|
||||
nickname="Harrier"
|
||||
elseif actype==AIRBOSS.AircraftCarrier.E2D then
|
||||
@ -15581,8 +15722,12 @@ function AIRBOSS:_MarshalCallRecoveryStart(case)
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Starting aircraft recovery Case %d ops.", case)
|
||||
if case>1 then
|
||||
text=text..string.format(" Marshal radial %03d°.", radial)
|
||||
if case==1 then
|
||||
text=text..string.format(" BRC %03d°.", self:GetBRC())
|
||||
elseif case==2 then
|
||||
text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC())
|
||||
elseif case==3 then
|
||||
text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false))
|
||||
end
|
||||
self:T(self.lid..text)
|
||||
|
||||
@ -17794,7 +17939,7 @@ function AIRBOSS:onbeforeSave(From, Event, To, path, filename)
|
||||
|
||||
-- Check default path.
|
||||
if path==nil and not lfs then
|
||||
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\DCS\" folder.")
|
||||
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
|
||||
end
|
||||
|
||||
return true
|
||||
@ -17901,7 +18046,7 @@ function AIRBOSS:onbeforeLoad(From, Event, To, path, filename)
|
||||
|
||||
-- Check default path.
|
||||
if path==nil and not lfs then
|
||||
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\DCS\" folder.")
|
||||
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
-- @field Core.Set#SET_ZONE retreatZones Set of retreat zones.
|
||||
-- @extends Ops.OpsGroup#OPSGROUP
|
||||
|
||||
--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge
|
||||
--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B Sledge
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -55,16 +55,6 @@ ARMYGROUP = {
|
||||
engage = {},
|
||||
}
|
||||
|
||||
--- Army group element.
|
||||
-- @type ARMYGROUP.Element
|
||||
-- @field #string name Name of the element, i.e. the unit.
|
||||
-- @field Wrapper.Unit#UNIT unit The UNIT object.
|
||||
-- @field #string status The element status.
|
||||
-- @field #string typename Type name.
|
||||
-- @field #number length Length of element in meters.
|
||||
-- @field #number width Width of element in meters.
|
||||
-- @field #number height Height of element in meters.
|
||||
|
||||
--- Target
|
||||
-- @type ARMYGROUP.Target
|
||||
-- @field Ops.Target#TARGET Target The target.
|
||||
@ -72,16 +62,16 @@ ARMYGROUP = {
|
||||
|
||||
--- Army Group version.
|
||||
-- @field #string version
|
||||
ARMYGROUP.version="0.4.0"
|
||||
ARMYGROUP.version="0.7.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Retreat.
|
||||
-- TODO: Suppression of fire.
|
||||
-- TODO: Check if group is mobile.
|
||||
-- TODO: F10 menu.
|
||||
-- DONE: Retreat.
|
||||
-- DONE: Rearm. Specify a point where to go and wait until ammo is full.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -90,17 +80,25 @@ ARMYGROUP.version="0.4.0"
|
||||
|
||||
--- Create a new ARMYGROUP class object.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param Wrapper.Group#GROUP Group The group object. Can also be given by its group name as `#string`.
|
||||
-- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`.
|
||||
-- @return #ARMYGROUP self
|
||||
function ARMYGROUP:New(Group)
|
||||
function ARMYGROUP:New(group)
|
||||
|
||||
-- First check if we already have an OPS group for this group.
|
||||
local og=_DATABASE:GetOpsGroup(group)
|
||||
if og then
|
||||
og:I(og.lid..string.format("WARNING: OPS group already exists in data base!"))
|
||||
return og
|
||||
end
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, OPSGROUP:New(Group)) -- #ARMYGROUP
|
||||
local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #ARMYGROUP
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("ARMYGROUP %s | ", self.groupname)
|
||||
|
||||
-- Defaults
|
||||
self.isArmygroup=true
|
||||
self:SetDefaultROE()
|
||||
self:SetDefaultAlarmstate()
|
||||
self:SetDetection()
|
||||
@ -115,13 +113,13 @@ function ARMYGROUP:New(Group)
|
||||
self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards.
|
||||
self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate.
|
||||
|
||||
self:AddTransition("*", "Retreat", "Retreating") --
|
||||
self:AddTransition("Retreating", "Retreated", "Retreated") --
|
||||
self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat.
|
||||
self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated.
|
||||
|
||||
self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target
|
||||
self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target
|
||||
self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target
|
||||
self:AddTransition("Engaging", "Disengage", "Cruising") -- Engage a target
|
||||
self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state
|
||||
self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state
|
||||
self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state
|
||||
self:AddTransition("Engaging", "Disengage", "Cruising") -- Disengage and back to cruising.
|
||||
|
||||
self:AddTransition("*", "Rearm", "Rearm") -- Group is send to a coordinate and waits until ammo is refilled.
|
||||
self:AddTransition("Rearm", "Rearming", "Rearming") -- Group has arrived at the rearming coodinate and is waiting to be fully rearmed.
|
||||
@ -143,7 +141,7 @@ function ARMYGROUP:New(Group)
|
||||
|
||||
|
||||
-- Init waypoints.
|
||||
self:InitWaypoints()
|
||||
self:_InitWaypoints()
|
||||
|
||||
-- Initialize the group.
|
||||
self:_InitGroup()
|
||||
@ -151,8 +149,7 @@ function ARMYGROUP:New(Group)
|
||||
-- Handle events:
|
||||
self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
|
||||
self:HandleEvent(EVENTS.Dead, self.OnEventDead)
|
||||
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
|
||||
|
||||
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
|
||||
--self:HandleEvent(EVENTS.Hit, self.OnEventHit)
|
||||
|
||||
-- Start the status monitoring.
|
||||
@ -163,6 +160,9 @@ function ARMYGROUP:New(Group)
|
||||
|
||||
-- Start check zone timer.
|
||||
self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 30)
|
||||
|
||||
-- Add OPSGROUP to _DATABASE.
|
||||
_DATABASE:AddOpsGroup(self)
|
||||
|
||||
return self
|
||||
end
|
||||
@ -263,6 +263,26 @@ function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clo
|
||||
return task
|
||||
end
|
||||
|
||||
--- Add a *scheduled* task to transport group(s).
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param Core.Set#SET_GROUP GroupSet Set of cargo groups. Can also be a singe @{Wrapper.Group#GROUP} object.
|
||||
-- @param Core.Zone#ZONE PickupZone Zone where the cargo is picked up.
|
||||
-- @param Core.Zone#ZONE DeployZone Zone where the cargo is delivered to.
|
||||
-- @param #string Clock Time when to start the attack.
|
||||
-- @param #number Prio Priority of the task.
|
||||
-- @return Ops.OpsGroup#OPSGROUP.Task The task table.
|
||||
function ARMYGROUP:AddTaskCargoGroup(GroupSet, PickupZone, DeployZone, Clock, Prio)
|
||||
|
||||
local DCStask={}
|
||||
DCStask.id="CargoTransport"
|
||||
DCStask.params={}
|
||||
DCStask.params.cargoqueu=1
|
||||
|
||||
local task=self:AddTask(DCStask, Clock, nil, Prio)
|
||||
|
||||
return task
|
||||
end
|
||||
|
||||
--- Define a set of possible retreat zones.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param Core.Set#SET_ZONE RetreatZoneSet The retreat zone set. Default is an empty set.
|
||||
@ -342,7 +362,9 @@ function ARMYGROUP:onafterStatus(From, Event, To)
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
if self:IsAlive() then
|
||||
local alive=self:IsAlive()
|
||||
|
||||
if alive then
|
||||
|
||||
---
|
||||
-- Detection
|
||||
@ -370,6 +392,21 @@ function ARMYGROUP:onafterStatus(From, Event, To)
|
||||
self:_UpdateEngageTarget()
|
||||
end
|
||||
|
||||
-- Check if group is waiting.
|
||||
if self:IsWaiting() then
|
||||
if self.Twaiting and self.dTwait then
|
||||
if timer.getAbsTime()>self.Twaiting+self.dTwait then
|
||||
self.Twaiting=nil
|
||||
self.dTwait=nil
|
||||
self:Cruise()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if alive~=nil then
|
||||
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- Get number of tasks and missions.
|
||||
@ -378,14 +415,20 @@ function ARMYGROUP:onafterStatus(From, Event, To)
|
||||
|
||||
local roe=self:GetROE()
|
||||
local alarm=self:GetAlarmstate()
|
||||
local speed=UTILS.MpsToKnots(self.velocity)
|
||||
local speed=UTILS.MpsToKnots(self.velocity or 0)
|
||||
local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed())
|
||||
local formation=self.option.Formation or "unknown"
|
||||
local ammo=self:GetAmmoTot()
|
||||
|
||||
local cargo=0
|
||||
for _,_element in pairs(self.elements) do
|
||||
local element=_element --Ops.OpsGroup#OPSGROUP.Element
|
||||
cargo=cargo+element.weightCargo
|
||||
end
|
||||
|
||||
-- Info text.
|
||||
local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d",
|
||||
fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), self.life or 0, speed, speedEx, self.heading, ammo.Total)
|
||||
local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d, Cargo=%.1f",
|
||||
fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), self.life or 0, speed, speedEx, self.heading or 0, ammo.Total, cargo)
|
||||
self:I(self.lid..text)
|
||||
|
||||
end
|
||||
@ -393,11 +436,50 @@ function ARMYGROUP:onafterStatus(From, Event, To)
|
||||
else
|
||||
|
||||
-- Info text.
|
||||
local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive()))
|
||||
self:T2(self.lid..text)
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive()))
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
---
|
||||
-- Elements
|
||||
---
|
||||
|
||||
if self.verbose>=2 then
|
||||
local text="Elements:"
|
||||
for i,_element in pairs(self.elements) do
|
||||
local element=_element --Ops.OpsGroup#OPSGROUP.Element
|
||||
|
||||
local name=element.name
|
||||
local status=element.status
|
||||
local unit=element.unit
|
||||
--local life=unit:GetLifeRelative() or 0
|
||||
local life,life0=self:GetLifePoints(element)
|
||||
|
||||
local life0=element.life0
|
||||
|
||||
-- Get ammo.
|
||||
local ammo=self:GetAmmoElement(element)
|
||||
|
||||
-- Output text for element.
|
||||
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg",
|
||||
i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
|
||||
end
|
||||
if #self.elements==0 then
|
||||
text=text.." none!"
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- Cargo
|
||||
---
|
||||
|
||||
self:_CheckCargoTransport()
|
||||
|
||||
|
||||
---
|
||||
-- Tasks & Missions
|
||||
@ -410,6 +492,26 @@ function ARMYGROUP:onafterStatus(From, Event, To)
|
||||
self:__Status(-30)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- DCS Events ==> See OPSGROUP
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Event function handling when a unit is hit.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function ARMYGROUP:OnEventHit(EventData)
|
||||
|
||||
-- Check that this is the right group.
|
||||
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
|
||||
local unit=EventData.IniUnit
|
||||
local group=EventData.IniGroup
|
||||
local unitname=EventData.IniUnitName
|
||||
|
||||
-- TODO: suppression
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Events
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -419,7 +521,7 @@ end
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #ARMYGROUP.Element Element The group element.
|
||||
-- @param Ops.OpsGroup#OPSGROUP.Element Element The group element.
|
||||
function ARMYGROUP:onafterElementSpawned(From, Event, To, Element)
|
||||
self:T(self.lid..string.format("Element spawned %s", Element.name))
|
||||
|
||||
@ -436,8 +538,30 @@ end
|
||||
function ARMYGROUP:onafterSpawned(From, Event, To)
|
||||
self:T(self.lid..string.format("Group spawned!"))
|
||||
|
||||
-- Debug info.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("Initialized Army Group %s:\n", self.groupname)
|
||||
text=text..string.format("Unit type = %s\n", self.actype)
|
||||
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
|
||||
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
|
||||
text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal())
|
||||
text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay())
|
||||
text=text..string.format("Elements = %d\n", #self.elements)
|
||||
text=text..string.format("Waypoints = %d\n", #self.waypoints)
|
||||
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
|
||||
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles)
|
||||
text=text..string.format("FSM state = %s\n", self:GetState())
|
||||
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
|
||||
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Update position.
|
||||
self:_UpdatePosition()
|
||||
|
||||
-- Not dead or destroyed yet.
|
||||
self.isDead=false
|
||||
self.isDestroyed=false
|
||||
|
||||
if self.isAI then
|
||||
|
||||
@ -461,16 +585,34 @@ function ARMYGROUP:onafterSpawned(From, Event, To)
|
||||
if not self.option.Formation then
|
||||
self.option.Formation=self.optionDefault.Formation
|
||||
end
|
||||
|
||||
-- Update route.
|
||||
if #self.waypoints>1 then
|
||||
self:Cruise(nil, self.option.Formation or self.optionDefault.Formation)
|
||||
else
|
||||
self:FullStop()
|
||||
end
|
||||
|
||||
-- Update status.
|
||||
self:__Status(-0.1)
|
||||
|
||||
end
|
||||
|
||||
-- Update route.
|
||||
if #self.waypoints>1 then
|
||||
self:Cruise(nil, self.option.Formation or self.optionDefault.Formation)
|
||||
else
|
||||
self:FullStop()
|
||||
end
|
||||
|
||||
--- On before "UpdateRoute" event.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #number n Waypoint number. Default is next waypoint.
|
||||
-- @param #number Speed Speed in knots. Default cruise speed.
|
||||
-- @param #number Formation Formation of the group.
|
||||
function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Formation)
|
||||
if self:IsWaiting() then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- On after "UpdateRoute" event.
|
||||
@ -830,7 +972,7 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target)
|
||||
|
||||
end
|
||||
|
||||
--- On after "EngageTarget" event.
|
||||
--- Update engage target.
|
||||
-- @param #ARMYGROUP self
|
||||
function ARMYGROUP:_UpdateEngageTarget()
|
||||
|
||||
@ -926,131 +1068,14 @@ end
|
||||
-- @param #number Formation Formation.
|
||||
function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation)
|
||||
|
||||
-- Not waiting anymore.
|
||||
self.Twaiting=nil
|
||||
self.dTwait=nil
|
||||
|
||||
self:__UpdateRoute(-1, nil, Speed, Formation)
|
||||
|
||||
end
|
||||
|
||||
--- On after "Stop" event.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function ARMYGROUP:onafterStop(From, Event, To)
|
||||
|
||||
-- Handle events:
|
||||
self:UnHandleEvent(EVENTS.Birth)
|
||||
self:UnHandleEvent(EVENTS.Dead)
|
||||
self:UnHandleEvent(EVENTS.RemoveUnit)
|
||||
|
||||
-- Call OPSGROUP function.
|
||||
self:GetParent(self).onafterStop(self, From, Event, To)
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Events DCS
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Event function handling the birth of a unit.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function ARMYGROUP:OnEventBirth(EventData)
|
||||
|
||||
-- Check that this is the right group.
|
||||
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
|
||||
local unit=EventData.IniUnit
|
||||
local group=EventData.IniGroup
|
||||
local unitname=EventData.IniUnitName
|
||||
|
||||
if self.respawning then
|
||||
|
||||
local function reset()
|
||||
self.respawning=nil
|
||||
end
|
||||
|
||||
-- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed.
|
||||
-- TODO: Can I do this more rigorously?
|
||||
self:ScheduleOnce(1, reset)
|
||||
|
||||
else
|
||||
|
||||
-- Get element.
|
||||
local element=self:GetElementByName(unitname)
|
||||
|
||||
-- Set element to spawned state.
|
||||
self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name))
|
||||
self:ElementSpawned(element)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Event function handling the crash of a unit.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function ARMYGROUP:OnEventDead(EventData)
|
||||
|
||||
-- Check that this is the right group.
|
||||
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
|
||||
self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName))
|
||||
|
||||
local unit=EventData.IniUnit
|
||||
local group=EventData.IniGroup
|
||||
local unitname=EventData.IniUnitName
|
||||
|
||||
-- Get element.
|
||||
local element=self:GetElementByName(unitname)
|
||||
|
||||
if element then
|
||||
self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name))
|
||||
self:ElementDestroyed(element)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Event function handling when a unit is removed from the game.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function ARMYGROUP:OnEventRemoveUnit(EventData)
|
||||
|
||||
-- Check that this is the right group.
|
||||
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
|
||||
local unit=EventData.IniUnit
|
||||
local group=EventData.IniGroup
|
||||
local unitname=EventData.IniUnitName
|
||||
|
||||
-- Get element.
|
||||
local element=self:GetElementByName(unitname)
|
||||
|
||||
if element then
|
||||
self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name))
|
||||
self:ElementDead(element)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Event function handling when a unit is hit.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function ARMYGROUP:OnEventHit(EventData)
|
||||
|
||||
-- Check that this is the right group.
|
||||
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
|
||||
local unit=EventData.IniUnit
|
||||
local group=EventData.IniGroup
|
||||
local unitname=EventData.IniUnitName
|
||||
|
||||
-- TODO: suppression
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Routing
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -1108,17 +1133,18 @@ end
|
||||
|
||||
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param #table Template Template used to init the group. Default is `self.template`.
|
||||
-- @return #ARMYGROUP self
|
||||
function ARMYGROUP:_InitGroup()
|
||||
function ARMYGROUP:_InitGroup(Template)
|
||||
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:E(self.lid.."WARNING: Group was already initialized!")
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get template of group.
|
||||
self.template=self.group:GetTemplate()
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Define category.
|
||||
self.isAircraft=false
|
||||
@ -1129,7 +1155,7 @@ function ARMYGROUP:_InitGroup()
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=self.template.lateActivation
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Ground groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
@ -1161,64 +1187,16 @@ function ARMYGROUP:_InitGroup()
|
||||
-- Units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
|
||||
for _,_unit in pairs(units) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
-- TODO: this is wrong when grouping is used!
|
||||
local unittemplate=unit:GetTemplate()
|
||||
|
||||
local element={} --#ARMYGROUP.Element
|
||||
element.name=unit:GetName()
|
||||
element.unit=unit
|
||||
element.status=OPSGROUP.ElementStatus.INUTERO
|
||||
element.typename=unit:GetTypeName()
|
||||
element.skill=unittemplate.skill or "Unknown"
|
||||
element.ai=true
|
||||
element.category=element.unit:GetUnitCategory()
|
||||
element.categoryname=element.unit:GetCategoryName()
|
||||
element.size, element.length, element.height, element.width=unit:GetObjectSize()
|
||||
element.ammo0=self:GetAmmoUnit(unit, false)
|
||||
element.life0=unit:GetLife0()
|
||||
element.life=element.life0
|
||||
|
||||
-- Debug text.
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Adding element %s: status=%s, skill=%s, life=%.3f category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)",
|
||||
element.name, element.status, element.skill, element.life, element.categoryname, element.category, element.size, element.length, element.height, element.width)
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
-- Add elemets.
|
||||
for _,unit in pairs(units) do
|
||||
self:_AddElementByName(unit:GetName())
|
||||
end
|
||||
|
||||
-- Get Descriptors.
|
||||
self.descriptors=units[1]:GetDesc()
|
||||
|
||||
-- Add element to table.
|
||||
table.insert(self.elements, element)
|
||||
|
||||
-- Get Descriptors.
|
||||
self.descriptors=self.descriptors or unit:GetDesc()
|
||||
|
||||
-- Set type name.
|
||||
self.actype=self.actype or unit:GetTypeName()
|
||||
|
||||
if unit:IsAlive() then
|
||||
-- Trigger spawned event.
|
||||
self:ElementSpawned(element)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("Initialized Army Group %s:\n", self.groupname)
|
||||
text=text..string.format("Unit type = %s\n", self.actype)
|
||||
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
|
||||
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
|
||||
text=text..string.format("Elements = %d\n", #self.elements)
|
||||
text=text..string.format("Waypoints = %d\n", #self.waypoints)
|
||||
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
|
||||
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles)
|
||||
text=text..string.format("FSM state = %s\n", self:GetState())
|
||||
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
|
||||
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
-- Set type name.
|
||||
self.actype=units[1]:GetTypeName()
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
-- * Set mission start/stop times
|
||||
-- * Set mission priority and urgency (can cancel running missions)
|
||||
-- * Specific mission options for ROE, ROT, formation, etc.
|
||||
-- * Compatible with FLIGHTGROUP, NAVYGROUP, ARMYGROUP, AIRWING, WINGCOMMANDER and CHIEF classes
|
||||
-- * Compatible with OPS classes like FLIGHTGROUP, NAVYGROUP, ARMYGROUP, AIRWING, etc.
|
||||
-- * FSM events when a mission is done, successful or failed
|
||||
--
|
||||
-- ===
|
||||
@ -39,9 +39,10 @@
|
||||
-- @field #number prio Mission priority.
|
||||
-- @field #boolean urgent Mission is urgent. Running missions with lower prio might be cancelled.
|
||||
-- @field #number importance Importance.
|
||||
-- @field #number Tstart Mission start time in seconds.
|
||||
-- @field #number Tstop Mission stop time in seconds.
|
||||
-- @field #number Tstart Mission start time in abs. seconds.
|
||||
-- @field #number Tstop Mission stop time in abs. seconds.
|
||||
-- @field #number duration Mission duration in seconds.
|
||||
-- @field #number Tpush Mission push/execute time in abs. seconds.
|
||||
-- @field Wrapper.Marker#MARKER marker F10 map marker.
|
||||
-- @field #boolean markerOn If true, display marker on F10 map with the AUFTRAG status.
|
||||
-- @field #number markerCoaliton Coalition to which the marker is dispayed.
|
||||
@ -52,8 +53,9 @@
|
||||
-- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over.
|
||||
-- @field #number Tover Mission abs. time stamp, when mission was over.
|
||||
-- @field #table conditionStart Condition(s) that have to be true, before the mission will be started.
|
||||
-- @field #table conditionSuccess If all stop conditions are true, the mission is cancelled.
|
||||
-- @field #table conditionFailure If all stop conditions are true, the mission is cancelled.
|
||||
-- @field #table conditionSuccess If all conditions are true, the mission is cancelled.
|
||||
-- @field #table conditionFailure If all conditions are true, the mission is cancelled.
|
||||
-- @field #table conditionPush If all conditions are true, the mission is executed. Before, the group(s) wait at the mission execution waypoint.
|
||||
--
|
||||
-- @field #number orbitSpeed Orbit speed in m/s.
|
||||
-- @field #number orbitAltitude Orbit altitude in meters.
|
||||
@ -101,9 +103,11 @@
|
||||
--
|
||||
-- @field #string missionTask Mission task. See `ENUMS.MissionTask`.
|
||||
-- @field #number missionAltitude Mission altitude in meters.
|
||||
-- @field #number missionSpeed Mission speed in km/h.
|
||||
-- @field #number missionFraction Mission coordiante fraction. Default is 0.5.
|
||||
-- @field #number missionRange Mission range in meters. Used in AIRWING class.
|
||||
-- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate.
|
||||
-- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate.
|
||||
--
|
||||
-- @field #table enrouteTasks Mission enroute tasks.
|
||||
--
|
||||
@ -285,6 +289,7 @@ AUFTRAG = {
|
||||
conditionStart = {},
|
||||
conditionSuccess = {},
|
||||
conditionFailure = {},
|
||||
conditionPush = {},
|
||||
}
|
||||
|
||||
--- Global mission counter.
|
||||
@ -315,6 +320,7 @@ _AUFTRAGSNR=0
|
||||
-- @field #string TANKER Tanker mission.
|
||||
-- @field #string TROOPTRANSPORT Troop transport mission.
|
||||
-- @field #string ARTY Fire at point.
|
||||
-- @field #string PATROLZONE Patrol a zone.
|
||||
AUFTRAG.Type={
|
||||
ANTISHIP="Anti Ship",
|
||||
AWACS="AWACS",
|
||||
@ -338,6 +344,7 @@ AUFTRAG.Type={
|
||||
TANKER="Tanker",
|
||||
TROOPTRANSPORT="Troop Transport",
|
||||
ARTY="Fire At Point",
|
||||
PATROLZONE="Patrol Zone",
|
||||
}
|
||||
|
||||
--- Mission status.
|
||||
@ -430,8 +437,9 @@ AUFTRAG.TargetType={
|
||||
--- Group specific data. Each ops group subscribed to this mission has different data for this.
|
||||
-- @type AUFTRAG.GroupData
|
||||
-- @field Ops.OpsGroup#OPSGROUP opsgroup The OPS group.
|
||||
-- @field Core.Point#COORDINATE waypointcoordinate Waypoint coordinate.
|
||||
-- @field Core.Point#COORDINATE waypointcoordinate Ingress waypoint coordinate.
|
||||
-- @field #number waypointindex Waypoint index.
|
||||
-- @field Core.Point#COORDINATE wpegresscoordinate Egress waypoint coordinate.
|
||||
-- @field Ops.OpsGroup#OPSGROUP.Task waypointtask Waypoint task.
|
||||
-- @field #string status Group mission status.
|
||||
-- @field Ops.AirWing#AIRWING.SquadronAsset asset The squadron asset.
|
||||
@ -439,7 +447,7 @@ AUFTRAG.TargetType={
|
||||
|
||||
--- AUFTRAG class version.
|
||||
-- @field #string version
|
||||
AUFTRAG.version="0.5.0"
|
||||
AUFTRAG.version="0.7.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@ -934,7 +942,7 @@ end
|
||||
|
||||
--- Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT or STATIC object.
|
||||
-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 2000 ft.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewSTRIKE(Target, Altitude)
|
||||
@ -962,7 +970,7 @@ end
|
||||
|
||||
--- Create a BOMBING mission. Flight will drop bombs a specified coordinate.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT or STATIC object.
|
||||
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 25000 ft.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewBOMBING(Target, Altitude)
|
||||
@ -1002,10 +1010,6 @@ function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude)
|
||||
if type(Airdrome)=="string" then
|
||||
Airdrome=AIRBASE:FindByName(Airdrome)
|
||||
end
|
||||
|
||||
if Airdrome:IsInstanceOf("AIRBASE") then
|
||||
|
||||
end
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBRUNWAY)
|
||||
|
||||
@ -1106,9 +1110,7 @@ end
|
||||
function AUFTRAG:NewRESCUEHELO(Carrier)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO)
|
||||
|
||||
--mission.carrier=Carrier
|
||||
|
||||
|
||||
mission:_TargetFromObject(Carrier)
|
||||
|
||||
-- Mission options:
|
||||
@ -1191,6 +1193,32 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius)
|
||||
return mission
|
||||
end
|
||||
|
||||
--- Create a PATROLZONE mission. Group(s) will go to the zone and patrol it randomly.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Zone#ZONE Zone The patrol zone.
|
||||
-- @param #number Speed Speed in knots.
|
||||
-- @param #number Altitude Altitude in feet. Only for airborne units. Default 2000 feet ASL.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewPATROLZONE(Zone, Speed, Altitude)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.PATROLZONE)
|
||||
|
||||
mission:_TargetFromObject(Zone)
|
||||
|
||||
mission.optionROE=ENUMS.ROE.OpenFire
|
||||
mission.optionROT=ENUMS.ROT.PassiveDefense
|
||||
mission.optionAlarm=ENUMS.AlarmState.Auto
|
||||
|
||||
mission.missionFraction=1.0
|
||||
mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil
|
||||
mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude) or nil
|
||||
|
||||
mission.DCStask=mission:GetDCSMissionTask()
|
||||
|
||||
return mission
|
||||
end
|
||||
|
||||
|
||||
--- Create a mission to attack a group. Mission type is automatically chosen from the group category.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Ops.Target#TARGET Target The target.
|
||||
@ -1407,6 +1435,24 @@ function AUFTRAG:SetTime(ClockStart, ClockStop)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set mission push time. This is the time the mission is executed. If the push time is not passed, the group will wait at the mission execution waypoint.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #string ClockPush Time the mission is executed, e.g. "05:00" for 5 am. Can also be given as a `#number`, where it is interpreted as relative push time in seconds.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetPushTime(ClockPush)
|
||||
|
||||
if ClockPush then
|
||||
if type(ClockPush)=="string" then
|
||||
self.Tpush=UTILS.ClockToSeconds(ClockPush)
|
||||
elseif type(ClockPush)=="number" then
|
||||
self.Tpush=timer.getAbsTime()+ClockPush
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set mission priority and (optional) urgency. Urgent missions can cancel other running missions.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #number Prio Priority 1=high, 100=low. Default 50.
|
||||
@ -1781,6 +1827,26 @@ function AUFTRAG:AddConditionFailure(ConditionFunction, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add push condition.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #function ConditionFunction If this function returns `true`, the mission is executed.
|
||||
-- @param ... Condition function arguments if any.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:AddConditionPush(ConditionFunction, ...)
|
||||
|
||||
local condition={} --#AUFTRAG.Condition
|
||||
|
||||
condition.func=ConditionFunction
|
||||
condition.arg={}
|
||||
if arg then
|
||||
condition.arg=arg
|
||||
end
|
||||
|
||||
table.insert(self.conditionPush, condition)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Assign airwing squadron(s) to the mission. Only these squads will be considered for the job.
|
||||
-- @param #AUFTRAG self
|
||||
@ -1794,6 +1860,8 @@ function AUFTRAG:AssignSquadrons(Squadrons)
|
||||
end
|
||||
|
||||
self.squadrons=Squadrons
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a required payload for this mission. Only these payloads will be used for this mission. If they are not available, the mission cannot start. Only available for use with an AIRWING.
|
||||
@ -1806,12 +1874,14 @@ function AUFTRAG:AddRequiredPayload(Payload)
|
||||
|
||||
table.insert(self.payloads, Payload)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add a Ops group to the mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:AddOpsGroup(OpsGroup)
|
||||
self:T(self.lid..string.format("Adding Ops group %s", OpsGroup.groupname))
|
||||
|
||||
@ -1824,11 +1894,13 @@ function AUFTRAG:AddOpsGroup(OpsGroup)
|
||||
|
||||
self.groupdata[OpsGroup.groupname]=groupdata
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove an Ops group from the mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:DelOpsGroup(OpsGroup)
|
||||
self:T(self.lid..string.format("Removing OPS group %s", OpsGroup and OpsGroup.groupname or "nil (ERROR)!"))
|
||||
|
||||
@ -1841,6 +1913,7 @@ function AUFTRAG:DelOpsGroup(OpsGroup)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if mission is PLANNED.
|
||||
@ -1932,12 +2005,12 @@ function AUFTRAG:IsReadyToGo()
|
||||
local Tnow=timer.getAbsTime()
|
||||
|
||||
-- Start time did not pass yet.
|
||||
if self.Tstart and Tnow<self.Tstart or false then
|
||||
if self.Tstart and Tnow<self.Tstart then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Stop time already passed.
|
||||
if self.Tstop and Tnow>self.Tstop or false then
|
||||
if self.Tstop and Tnow>self.Tstop then
|
||||
return false
|
||||
end
|
||||
|
||||
@ -1963,7 +2036,7 @@ function AUFTRAG:IsReadyToCancel()
|
||||
local Tnow=timer.getAbsTime()
|
||||
|
||||
-- Stop time already passed.
|
||||
if self.Tstop and Tnow>self.Tstop then
|
||||
if self.Tstop and Tnow>=self.Tstop then
|
||||
return true
|
||||
end
|
||||
|
||||
@ -1987,6 +2060,26 @@ function AUFTRAG:IsReadyToCancel()
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if mission is ready to be pushed.
|
||||
-- * Mission push time already passed.
|
||||
-- * All push conditions are true.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return #boolean If true, mission groups can push.
|
||||
function AUFTRAG:IsReadyToPush()
|
||||
|
||||
local Tnow=timer.getAbsTime()
|
||||
|
||||
-- Push time passed?
|
||||
if self.Tpush and Tnow<self.Tpush then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Evaluate push condition(s) if any. All need to be true.
|
||||
local push=self:EvalConditionsAll(self.conditionPush)
|
||||
|
||||
return push
|
||||
end
|
||||
|
||||
--- Check if all given condition are true.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #table Conditions Table of conditions.
|
||||
@ -2926,7 +3019,7 @@ end
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Wrapper.Positionable#POSITIONABLE The target object. Could be many things.
|
||||
function AUFTRAG:GetObjective()
|
||||
return self:GetTargetData().Target
|
||||
return self:GetTargetData():GetObject()
|
||||
end
|
||||
|
||||
--- Get type of target.
|
||||
@ -3083,19 +3176,53 @@ function AUFTRAG:GetMissionTypesText(MissionTypes)
|
||||
return text
|
||||
end
|
||||
|
||||
--- Set the mission waypoint coordinate where the mission is executed.
|
||||
--- Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Core.Point#COORDINATE Coordinate where the mission is executed.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetMissionWaypointCoord(Coordinate)
|
||||
|
||||
-- Obviously a zone was passed. We get the coordinate.
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
Coordinate=Coordinate:GetCoordinate()
|
||||
end
|
||||
|
||||
self.missionWaypointCoord=Coordinate
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the mission egress coordinate. This is the coordinate where the assigned group will go once the mission is finished.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Egrees coordinate.
|
||||
-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude)
|
||||
|
||||
-- Obviously a zone was passed. We get the coordinate.
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
Coordinate=Coordinate:GetCoordinate()
|
||||
end
|
||||
|
||||
self.missionEgressCoord=Coordinate
|
||||
|
||||
if Altitude then
|
||||
self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude)
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the mission egress coordinate if this was defined.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Core.Point#COORDINATE Coordinate Coordinate or nil.
|
||||
function AUFTRAG:GetMissionEgressCoord()
|
||||
return self.missionEgressCoord
|
||||
end
|
||||
|
||||
--- Get coordinate of target. First unit/group of the set is used.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Wrapper.Group#GROUP group Group.
|
||||
-- @param #number randomradius Random radius in meters.
|
||||
-- @return Core.Point#COORDINATE Coordinate where the mission is executed.
|
||||
function AUFTRAG:GetMissionWaypointCoord(group)
|
||||
function AUFTRAG:GetMissionWaypointCoord(group, randomradius)
|
||||
|
||||
-- Check if a coord has been explicitly set.
|
||||
if self.missionWaypointCoord then
|
||||
@ -3111,7 +3238,9 @@ function AUFTRAG:GetMissionWaypointCoord(group)
|
||||
local alt=waypointcoord.y
|
||||
|
||||
-- Add some randomization.
|
||||
waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), 1000):GetRandomCoordinate():SetAltitude(alt, false)
|
||||
if randomradius then
|
||||
waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), randomradius):GetRandomCoordinate():SetAltitude(alt, false)
|
||||
end
|
||||
|
||||
-- Set altitude of mission waypoint.
|
||||
if self.missionAltitude then
|
||||
@ -3388,6 +3517,26 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable)
|
||||
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, self:GetTargetVec2(), self.artyRadius, self.artyShots, self.engageWeaponType)
|
||||
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
elseif self.type==AUFTRAG.Type.PATROLZONE then
|
||||
|
||||
-------------------------
|
||||
-- PATROL ZONE Mission --
|
||||
-------------------------
|
||||
|
||||
local DCStask={}
|
||||
|
||||
DCStask.id="PatrolZone"
|
||||
|
||||
-- We create a "fake" DCS task and pass the parameters to the FLIGHTGROUP.
|
||||
local param={}
|
||||
param.zone=self:GetObjective()
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: Unknown mission task!"))
|
||||
|
||||
2080
Moose Development/Moose/Ops/CSAR.lua
Normal file
2080
Moose Development/Moose/Ops/CSAR.lua
Normal file
File diff suppressed because it is too large
Load Diff
2806
Moose Development/Moose/Ops/CTLD.lua
Normal file
2806
Moose Development/Moose/Ops/CTLD.lua
Normal file
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
@ -65,12 +65,7 @@ NAVYGROUP = {
|
||||
pathCorridor = 400,
|
||||
}
|
||||
|
||||
--- Navy group element.
|
||||
-- @type NAVYGROUP.Element
|
||||
-- @field #string name Name of the element, i.e. the unit.
|
||||
-- @field #string typename Type name.
|
||||
|
||||
--- Navy group element.
|
||||
--- Turn into wind parameters.
|
||||
-- @type NAVYGROUP.IntoWind
|
||||
-- @field #number Tstart Time to start.
|
||||
-- @field #number Tstop Time to stop.
|
||||
@ -87,7 +82,7 @@ NAVYGROUP = {
|
||||
|
||||
--- NavyGroup version.
|
||||
-- @field #string version
|
||||
NAVYGROUP.version="0.5.0"
|
||||
NAVYGROUP.version="0.7.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@ -107,12 +102,19 @@ NAVYGROUP.version="0.5.0"
|
||||
|
||||
--- Create a new NAVYGROUP class object.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #string GroupName Name of the group.
|
||||
-- @param Wrapper.Group#GROUP group The group object. Can also be given by its group name as `#string`.
|
||||
-- @return #NAVYGROUP self
|
||||
function NAVYGROUP:New(GroupName)
|
||||
function NAVYGROUP:New(group)
|
||||
|
||||
-- First check if we already have an OPS group for this group.
|
||||
local og=_DATABASE:GetOpsGroup(group)
|
||||
if og then
|
||||
og:I(og.lid..string.format("WARNING: OPS group already exists in data base!"))
|
||||
return og
|
||||
end
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, OPSGROUP:New(GroupName)) -- #NAVYGROUP
|
||||
local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #NAVYGROUP
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("NAVYGROUP %s | ", self.groupname)
|
||||
@ -123,6 +125,7 @@ function NAVYGROUP:New(GroupName)
|
||||
self:SetDefaultAlarmstate()
|
||||
self:SetPatrolAdInfinitum(true)
|
||||
self:SetPathfinding(false)
|
||||
self.isNavygroup=true
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
@ -162,7 +165,7 @@ function NAVYGROUP:New(GroupName)
|
||||
|
||||
|
||||
-- Init waypoints.
|
||||
self:InitWaypoints()
|
||||
self:_InitWaypoints()
|
||||
|
||||
-- Initialize the group.
|
||||
self:_InitGroup()
|
||||
@ -180,6 +183,9 @@ function NAVYGROUP:New(GroupName)
|
||||
|
||||
-- Start check zone timer.
|
||||
self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 60)
|
||||
|
||||
-- Add OPSGROUP to _DATABASE.
|
||||
_DATABASE:AddOpsGroup(self)
|
||||
|
||||
return self
|
||||
end
|
||||
@ -487,7 +493,8 @@ function NAVYGROUP:onafterStatus(From, Event, To)
|
||||
-- Check if group started or stopped turning.
|
||||
self:_CheckTurning()
|
||||
|
||||
local freepath=UTILS.NMToMeters(10)
|
||||
local disttoWP=math.min(self:GetDistanceToWaypoint(), UTILS.NMToMeters(10))
|
||||
local freepath=disttoWP
|
||||
|
||||
-- Only check if not currently turning.
|
||||
if not self:IsTurning() then
|
||||
@ -495,7 +502,7 @@ function NAVYGROUP:onafterStatus(From, Event, To)
|
||||
-- Check free path ahead.
|
||||
freepath=self:_CheckFreePath(freepath, 100)
|
||||
|
||||
if freepath<5000 then
|
||||
if disttoWP>1 and freepath<disttoWP then
|
||||
|
||||
if not self.collisionwarning then
|
||||
-- Issue a collision warning event.
|
||||
@ -515,6 +522,17 @@ function NAVYGROUP:onafterStatus(From, Event, To)
|
||||
|
||||
-- Check if group got stuck.
|
||||
self:_CheckStuck()
|
||||
|
||||
-- Check if group is waiting.
|
||||
if self:IsWaiting() then
|
||||
if self.Twaiting and self.dTwait then
|
||||
if timer.getAbsTime()>self.Twaiting+self.dTwait then
|
||||
self.Twaiting=nil
|
||||
self.dTwait=nil
|
||||
self:Cruise()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.verbose>=1 then
|
||||
|
||||
@ -571,7 +589,7 @@ function NAVYGROUP:onafterStatus(From, Event, To)
|
||||
-- Recovery Windows
|
||||
---
|
||||
|
||||
if self.verbose>=2 then
|
||||
if self.verbose>=2 and #self.Qintowind>0 then
|
||||
|
||||
-- Debug output:
|
||||
local text=string.format(self.lid.."Turn into wind time windows:")
|
||||
@ -598,6 +616,11 @@ function NAVYGROUP:onafterStatus(From, Event, To)
|
||||
|
||||
end
|
||||
|
||||
---
|
||||
-- Cargo
|
||||
---
|
||||
|
||||
self:_CheckCargoTransport()
|
||||
|
||||
---
|
||||
-- Tasks & Missions
|
||||
@ -610,6 +633,12 @@ function NAVYGROUP:onafterStatus(From, Event, To)
|
||||
self:__Status(-30)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- DCS Events ==> See OPSGROUP
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- See OPSGROUP!
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Events
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -619,7 +648,7 @@ end
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #NAVYGROUP.Element Element The group element.
|
||||
-- @param Ops.OpsGroup#OPSGROUP.Element Element The group element.
|
||||
function NAVYGROUP:onafterElementSpawned(From, Event, To, Element)
|
||||
self:T(self.lid..string.format("Element spawned %s", Element.name))
|
||||
|
||||
@ -636,8 +665,30 @@ end
|
||||
function NAVYGROUP:onafterSpawned(From, Event, To)
|
||||
self:T(self.lid..string.format("Group spawned!"))
|
||||
|
||||
-- Debug info.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("Initialized Navy Group %s:\n", self.groupname)
|
||||
text=text..string.format("Unit type = %s\n", self.actype)
|
||||
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
|
||||
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
|
||||
text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal())
|
||||
text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay())
|
||||
text=text..string.format("Elements = %d\n", #self.elements)
|
||||
text=text..string.format("Waypoints = %d\n", #self.waypoints)
|
||||
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
|
||||
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
|
||||
text=text..string.format("FSM state = %s\n", self:GetState())
|
||||
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
|
||||
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Update position.
|
||||
self:_UpdatePosition()
|
||||
|
||||
-- Not dead or destroyed yet.
|
||||
self.isDead=false
|
||||
self.isDestroyed=false
|
||||
|
||||
if self.isAI then
|
||||
|
||||
@ -655,20 +706,39 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
|
||||
|
||||
-- Set radio.
|
||||
if self.radioDefault then
|
||||
self:SwitchRadio()
|
||||
-- CAREFUL: This makes DCS crash for some ships like speed boats or Higgins boats! (On a respawn for example). Looks like the command SetFrequency is causing this.
|
||||
--self:SwitchRadio()
|
||||
else
|
||||
self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, false)
|
||||
end
|
||||
|
||||
-- Update route.
|
||||
if #self.waypoints>1 then
|
||||
self:Cruise()
|
||||
else
|
||||
self:FullStop()
|
||||
end
|
||||
|
||||
-- Update status.
|
||||
self:__Status(-0.1)
|
||||
|
||||
end
|
||||
|
||||
-- Update route.
|
||||
if #self.waypoints>1 then
|
||||
self:Cruise()
|
||||
else
|
||||
self:FullStop()
|
||||
end
|
||||
|
||||
--- On before "UpdateRoute" event.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #number n Waypoint number. Default is next waypoint.
|
||||
-- @param #number Speed Speed in knots to the next waypoint.
|
||||
-- @param #number Depth Depth in meters to the next waypoint.
|
||||
function NAVYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Depth)
|
||||
if self:IsWaiting() then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- On after "UpdateRoute" event.
|
||||
@ -710,6 +780,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth)
|
||||
wp.alt=-self.depth
|
||||
else
|
||||
-- Take default waypoint alt.
|
||||
wp.alt=wp.alt or 0
|
||||
end
|
||||
|
||||
-- Current set speed in m/s.
|
||||
@ -939,6 +1010,10 @@ end
|
||||
-- @param #number Speed Speed in knots until next waypoint is reached. Default is speed set for waypoint.
|
||||
function NAVYGROUP:onafterCruise(From, Event, To, Speed)
|
||||
|
||||
-- Not waiting anymore.
|
||||
self.Twaiting=nil
|
||||
self.dTwait=nil
|
||||
|
||||
-- No set depth.
|
||||
self.depth=nil
|
||||
|
||||
@ -957,7 +1032,7 @@ function NAVYGROUP:onafterDive(From, Event, To, Depth, Speed)
|
||||
|
||||
Depth=Depth or 50
|
||||
|
||||
self:T(self.lid..string.format("Diving to %d meters", Depth))
|
||||
self:I(self.lid..string.format("Diving to %d meters", Depth))
|
||||
|
||||
self.depth=Depth
|
||||
|
||||
@ -1014,110 +1089,6 @@ function NAVYGROUP:onafterCollisionWarning(From, Event, To, Distance)
|
||||
self.collisionwarning=true
|
||||
end
|
||||
|
||||
--- On after Start event. Starts the NAVYGROUP FSM and event handlers.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function NAVYGROUP:onafterStop(From, Event, To)
|
||||
|
||||
-- Handle events:
|
||||
self:UnHandleEvent(EVENTS.Birth)
|
||||
self:UnHandleEvent(EVENTS.Dead)
|
||||
self:UnHandleEvent(EVENTS.RemoveUnit)
|
||||
|
||||
-- Call OPSGROUP function.
|
||||
self:GetParent(self).onafterStop(self, From, Event, To)
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Events DCS
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Event function handling the birth of a unit.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function NAVYGROUP:OnEventBirth(EventData)
|
||||
|
||||
-- Check that this is the right group.
|
||||
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
|
||||
local unit=EventData.IniUnit
|
||||
local group=EventData.IniGroup
|
||||
local unitname=EventData.IniUnitName
|
||||
|
||||
if self.respawning then
|
||||
|
||||
local function reset()
|
||||
self.respawning=nil
|
||||
end
|
||||
|
||||
-- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed.
|
||||
-- TODO: Can I do this more rigorously?
|
||||
self:ScheduleOnce(1, reset)
|
||||
|
||||
else
|
||||
|
||||
-- Get element.
|
||||
local element=self:GetElementByName(unitname)
|
||||
|
||||
-- Set element to spawned state.
|
||||
self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name))
|
||||
self:ElementSpawned(element)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Flightgroup event function handling the crash of a unit.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function NAVYGROUP:OnEventDead(EventData)
|
||||
|
||||
-- Check that this is the right group.
|
||||
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
|
||||
self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName))
|
||||
|
||||
local unit=EventData.IniUnit
|
||||
local group=EventData.IniGroup
|
||||
local unitname=EventData.IniUnitName
|
||||
|
||||
-- Get element.
|
||||
local element=self:GetElementByName(unitname)
|
||||
|
||||
if element then
|
||||
self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name))
|
||||
self:ElementDestroyed(element)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Flightgroup event function handling the crash of a unit.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param Core.Event#EVENTDATA EventData Event data.
|
||||
function NAVYGROUP:OnEventRemoveUnit(EventData)
|
||||
|
||||
-- Check that this is the right group.
|
||||
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
|
||||
local unit=EventData.IniUnit
|
||||
local group=EventData.IniGroup
|
||||
local unitname=EventData.IniUnitName
|
||||
|
||||
-- Get element.
|
||||
local element=self:GetElementByName(unitname)
|
||||
|
||||
if element then
|
||||
self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name))
|
||||
self:ElementDead(element)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Routing
|
||||
@ -1177,17 +1148,18 @@ end
|
||||
|
||||
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #table Template Template used to init the group. Default is `self.template`.
|
||||
-- @return #NAVYGROUP self
|
||||
function NAVYGROUP:_InitGroup()
|
||||
function NAVYGROUP:_InitGroup(Template)
|
||||
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:E(self.lid.."WARNING: Group was already initialized!")
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get template of group.
|
||||
self.template=self.group:GetTemplate()
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Define category.
|
||||
self.isAircraft=false
|
||||
@ -1201,7 +1173,7 @@ function NAVYGROUP:_InitGroup()
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=self.template.lateActivation
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Naval groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
@ -1217,8 +1189,8 @@ function NAVYGROUP:_InitGroup()
|
||||
|
||||
-- Radio parameters from template. Default is set on spawn if not modified by the user.
|
||||
self.radio.On=true -- Radio is always on for ships.
|
||||
self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000
|
||||
self.radio.Modu=tonumber(self.template.units[1].modulation)
|
||||
self.radio.Freq=tonumber(template.units[1].frequency)/1000000
|
||||
self.radio.Modu=tonumber(template.units[1].modulation)
|
||||
|
||||
-- Set default formation. No really applicable for ships.
|
||||
self.optionDefault.Formation="Off Road"
|
||||
@ -1235,64 +1207,17 @@ function NAVYGROUP:_InitGroup()
|
||||
-- Get all units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
|
||||
for _,_unit in pairs(units) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
-- Get unit template.
|
||||
local unittemplate=unit:GetTemplate()
|
||||
|
||||
local element={} --#NAVYGROUP.Element
|
||||
element.name=unit:GetName()
|
||||
element.unit=unit
|
||||
element.status=OPSGROUP.ElementStatus.INUTERO
|
||||
element.typename=unit:GetTypeName()
|
||||
element.skill=unittemplate.skill or "Unknown"
|
||||
element.ai=true
|
||||
element.category=element.unit:GetUnitCategory()
|
||||
element.categoryname=element.unit:GetCategoryName()
|
||||
element.size, element.length, element.height, element.width=unit:GetObjectSize()
|
||||
element.ammo0=self:GetAmmoUnit(unit, false)
|
||||
|
||||
-- Debug text.
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Adding element %s: status=%s, skill=%s, category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)",
|
||||
element.name, element.status, element.skill, element.categoryname, element.category, element.size, element.length, element.height, element.width)
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Add element to table.
|
||||
table.insert(self.elements, element)
|
||||
|
||||
-- Get Descriptors.
|
||||
self.descriptors=self.descriptors or unit:GetDesc()
|
||||
|
||||
-- Set type name.
|
||||
self.actype=self.actype or unit:GetTypeName()
|
||||
|
||||
if unit:IsAlive() then
|
||||
-- Trigger spawned event.
|
||||
self:ElementSpawned(element)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- Debug info.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("Initialized Navy Group %s:\n", self.groupname)
|
||||
text=text..string.format("Unit type = %s\n", self.actype)
|
||||
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
|
||||
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
|
||||
text=text..string.format("Elements = %d\n", #self.elements)
|
||||
text=text..string.format("Waypoints = %d\n", #self.waypoints)
|
||||
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
|
||||
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
|
||||
text=text..string.format("FSM state = %s\n", self:GetState())
|
||||
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
|
||||
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
|
||||
self:I(self.lid..text)
|
||||
-- Add elemets.
|
||||
for _,unit in pairs(units) do
|
||||
self:_AddElementByName(unit:GetName())
|
||||
end
|
||||
|
||||
-- Get Descriptors.
|
||||
self.descriptors=units[1]:GetDesc()
|
||||
|
||||
-- Set type name.
|
||||
self.actype=units[1]:GetTypeName()
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
|
||||
@ -1345,8 +1270,6 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx)
|
||||
--coordinate=coordinate:Translate(500, heading, true)
|
||||
|
||||
local function LoS(dist)
|
||||
--local checkcoord=coordinate:Translate(dist, heading, true)
|
||||
--return coordinate:IsLOS(checkcoord, offsetY)
|
||||
local checkvec3=UTILS.VecTranslate(vec3, dist, heading)
|
||||
local los=land.isVisible(vec3, checkvec3)
|
||||
return los
|
||||
@ -1374,7 +1297,7 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx)
|
||||
local los=LoS(x)
|
||||
|
||||
-- Debug message.
|
||||
self:T2(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los)))
|
||||
self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los)))
|
||||
|
||||
if los and d<=eps then
|
||||
return x
|
||||
@ -1548,6 +1471,7 @@ end
|
||||
-- @param #NAVYGROUP self
|
||||
-- @return #boolean If true, a path was found.
|
||||
function NAVYGROUP:_FindPathToNextWaypoint()
|
||||
env.info("FF Path finding")
|
||||
|
||||
-- Pathfinding A*
|
||||
local astar=ASTAR:New()
|
||||
@ -1558,6 +1482,11 @@ function NAVYGROUP:_FindPathToNextWaypoint()
|
||||
-- Next waypoint.
|
||||
local wpnext=self:GetWaypointNext()
|
||||
|
||||
-- No next waypoint.
|
||||
if wpnext==nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Next waypoint coordinate.
|
||||
local nextwp=wpnext.coordinate
|
||||
|
||||
@ -1574,16 +1503,21 @@ function NAVYGROUP:_FindPathToNextWaypoint()
|
||||
|
||||
-- Set end coordinate.
|
||||
astar:SetEndCoordinate(nextwp)
|
||||
|
||||
|
||||
-- Distance to next waypoint.
|
||||
local dist=position:Get2DDistance(nextwp)
|
||||
|
||||
-- Check distance >= 5 meters.
|
||||
if dist<5 then
|
||||
return
|
||||
end
|
||||
|
||||
local boxwidth=dist*2
|
||||
local spacex=dist*0.1
|
||||
local delta=dist/10
|
||||
|
||||
-- Create a grid of nodes. We only want nodes of surface type water.
|
||||
astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta*2, self.Debug)
|
||||
astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta, self.verbose>10)
|
||||
|
||||
-- Valid neighbour nodes need to have line of sight.
|
||||
astar:SetValidNeighbourLoS(self.pathCorridor)
|
||||
@ -1610,7 +1544,9 @@ function NAVYGROUP:_FindPathToNextWaypoint()
|
||||
uid=wp.uid
|
||||
|
||||
-- Debug: smoke and mark path.
|
||||
--node.coordinate:MarkToAll(string.format("Path node #%d", i))
|
||||
if self.verbose>=10 then
|
||||
node.coordinate:MarkToAll(string.format("Path node #%d", i))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1180
Moose Development/Moose/Ops/OpsTransport.lua
Normal file
1180
Moose Development/Moose/Ops/OpsTransport.lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -45,6 +45,8 @@
|
||||
-- @field #table tacanChannel List of TACAN channels available to the squadron.
|
||||
-- @field #number radioFreq Radio frequency in MHz the squad uses.
|
||||
-- @field #number radioModu Radio modulation the squad uses.
|
||||
-- @field #number takeoffType Take of type.
|
||||
-- @field #table parkingIDs Parking IDs for this squadron.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *It is unbelievable what a squadron of twelve aircraft did to tip the balance.* -- Adolf Galland
|
||||
@ -87,12 +89,13 @@ SQUADRON = {
|
||||
|
||||
--- SQUADRON class version.
|
||||
-- @field #string version
|
||||
SQUADRON.version="0.5.0"
|
||||
SQUADRON.version="0.7.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Parking spots for squadrons?
|
||||
-- DONE: Engage radius.
|
||||
-- DONE: Modex.
|
||||
-- DONE: Call signs.
|
||||
@ -134,7 +137,6 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
|
||||
self.Ngroups=Ngroups or 3
|
||||
self:SetMissionRange()
|
||||
self:SetSkill(AI.Skill.GOOD)
|
||||
--self:SetVerbosity(0)
|
||||
|
||||
-- Everyone can ORBIT.
|
||||
self:AddMissionCapability(AUFTRAG.Type.ORBIT)
|
||||
@ -282,6 +284,53 @@ function SQUADRON:SetGrouping(nunits)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid parking spot IDs. Assets of this squad are only allowed to be spawned at these parking spots. **Note** that the IDs are different from the ones displayed in the mission editor!
|
||||
-- @param #SQUADRON self
|
||||
-- @param #table ParkingIDs Table of parking ID numbers or a single `#number`.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetParkingIDs(ParkingIDs)
|
||||
if type(ParkingIDs)~="table" then
|
||||
ParkingIDs={ParkingIDs}
|
||||
end
|
||||
self.parkingIDs=ParkingIDs
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set takeoff type. All assets of this squadron will be spawned with cold (default) or hot engines.
|
||||
-- Spawning on runways is not supported.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string TakeoffType Take off type: "Cold" (default) or "Hot" with engines on.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffType(TakeoffType)
|
||||
TakeoffType=TakeoffType or "Cold"
|
||||
if TakeoffType:lower()=="hot" then
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot
|
||||
elseif TakeoffType:lower()=="cold" then
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
|
||||
else
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type cold (default). All assets of this squadron will be spawned with engines off (cold).
|
||||
-- @param #SQUADRON self
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffCold()
|
||||
self:SetTakeoffType("Cold")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type hot. All assets of this squadron will be spawned with engines on (hot).
|
||||
-- @param #SQUADRON self
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffHot()
|
||||
self:SetTakeoffType("Hot")
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set mission types this squadron is able to perform.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type.
|
||||
@ -571,15 +620,22 @@ end
|
||||
-- @return #number TACAN channel or *nil* if no channel is free.
|
||||
function SQUADRON:FetchTacan()
|
||||
|
||||
-- Get the smallest free channel if there is one.
|
||||
local freechannel=nil
|
||||
for channel,free in pairs(self.tacanChannel) do
|
||||
if free then
|
||||
self:T(self.lid..string.format("Checking out Tacan channel %d", channel))
|
||||
self.tacanChannel[channel]=false
|
||||
return channel
|
||||
if free then
|
||||
if freechannel==nil or channel<freechannel then
|
||||
freechannel=channel
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if freechannel then
|
||||
self:T(self.lid..string.format("Checking out Tacan channel %d", freechannel))
|
||||
self.tacanChannel[freechannel]=false
|
||||
end
|
||||
|
||||
return nil
|
||||
return freechannel
|
||||
end
|
||||
|
||||
--- "Return" a used TACAN channel.
|
||||
@ -764,7 +820,8 @@ end
|
||||
-- @param #string To To state.
|
||||
function SQUADRON:onafterStop(From, Event, To)
|
||||
|
||||
self:I(self.lid.."STOPPING Squadron!")
|
||||
-- Debug info.
|
||||
self:I(self.lid.."STOPPING Squadron and removing all assets!")
|
||||
|
||||
-- Remove all assets.
|
||||
for i=#self.assets,1,-1 do
|
||||
@ -772,6 +829,7 @@ function SQUADRON:onafterStop(From, Event, To)
|
||||
self:DelAsset(asset)
|
||||
end
|
||||
|
||||
-- Clear call scheduler.
|
||||
self.CallScheduler:Clear()
|
||||
|
||||
end
|
||||
@ -830,8 +888,9 @@ end
|
||||
|
||||
--- Count assets in airwing (warehous) stock.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types.
|
||||
-- @return #number Assets not spawned.
|
||||
function SQUADRON:CountAssetsInStock()
|
||||
function SQUADRON:CountAssetsInStock(MissionTypes)
|
||||
|
||||
local N=0
|
||||
for _,_asset in pairs(self.assets) do
|
||||
@ -839,7 +898,9 @@ function SQUADRON:CountAssetsInStock()
|
||||
if asset.spawned then
|
||||
|
||||
else
|
||||
N=N+1
|
||||
if MissionTypes==nil or self:CheckMissionCapability(MissionTypes, self.missiontypes) then
|
||||
N=N+1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1017,16 +1078,22 @@ end
|
||||
|
||||
--- Check if a mission type is contained in a list of possible capabilities.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string MissionType The requested mission type.
|
||||
-- @param #table MissionTypes The requested mission type. Can also be passed as a single mission type `#string`.
|
||||
-- @param #table Capabilities A table with possible capabilities.
|
||||
-- @return #boolean If true, the requested mission type is part of the possible mission types.
|
||||
function SQUADRON:CheckMissionCapability(MissionType, Capabilities)
|
||||
function SQUADRON:CheckMissionCapability(MissionTypes, Capabilities)
|
||||
|
||||
if type(MissionTypes)~="table" then
|
||||
MissionTypes={MissionTypes}
|
||||
end
|
||||
|
||||
for _,cap in pairs(Capabilities) do
|
||||
local capability=cap --Ops.Auftrag#AUFTRAG.Capability
|
||||
if capability.MissionType==MissionType then
|
||||
return true
|
||||
end
|
||||
for _,MissionType in pairs(MissionTypes) do
|
||||
if capability.MissionType==MissionType then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
@ -72,6 +72,7 @@ TARGET = {
|
||||
-- @field #string SCENERY Target is a SCENERY object.
|
||||
-- @field #string COORDINATE Target is a COORDINATE.
|
||||
-- @field #string AIRBASE Target is an AIRBASE.
|
||||
-- @field #string ZONE Target is a ZONE object.
|
||||
TARGET.ObjectType={
|
||||
GROUP="Group",
|
||||
UNIT="Unit",
|
||||
@ -79,6 +80,7 @@ TARGET.ObjectType={
|
||||
SCENERY="Scenery",
|
||||
COORDINATE="Coordinate",
|
||||
AIRBASE="Airbase",
|
||||
ZONE="Zone",
|
||||
}
|
||||
|
||||
|
||||
@ -89,12 +91,14 @@ TARGET.ObjectType={
|
||||
-- @field #string NAVAL
|
||||
-- @field #string AIRBASE
|
||||
-- @field #string COORDINATE
|
||||
-- @field #string ZONE
|
||||
TARGET.Category={
|
||||
AIRCRAFT="Aircraft",
|
||||
GROUND="Ground",
|
||||
NAVAL="Naval",
|
||||
AIRBASE="Airbase",
|
||||
COORDINATE="Coordinate",
|
||||
ZONE="Zone",
|
||||
}
|
||||
|
||||
--- Object status.
|
||||
@ -124,7 +128,7 @@ _TARGETID=0
|
||||
|
||||
--- TARGET class version.
|
||||
-- @field #string version
|
||||
TARGET.version="0.3.0"
|
||||
TARGET.version="0.5.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@ -227,12 +231,24 @@ end
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create target data from a given object.
|
||||
--- Create target data from a given object. Valid objects are:
|
||||
--
|
||||
-- * GROUP
|
||||
-- * UNIT
|
||||
-- * STATIC
|
||||
-- * AIRBASE
|
||||
-- * COORDINATE
|
||||
-- * ZONE
|
||||
-- * SET_GROUP
|
||||
-- * SET_UNIT
|
||||
-- * SET_STATIC
|
||||
-- * SET_OPSGROUP
|
||||
--
|
||||
-- @param #TARGET self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC, AIRBASE or COORDINATE.
|
||||
function TARGET:AddObject(Object)
|
||||
|
||||
if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") then
|
||||
if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") or Object:IsInstanceOf("SET_STATIC") or Object:IsInstanceOf("SET_OPSGROUP") then
|
||||
|
||||
---
|
||||
-- Sets
|
||||
@ -243,14 +259,18 @@ function TARGET:AddObject(Object)
|
||||
for _,object in pairs(set.Set) do
|
||||
self:AddObject(object)
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Groups, Units, Statics, Airbases, Coordinates
|
||||
---
|
||||
|
||||
self:_AddObject(Object)
|
||||
if Object:IsInstanceOf("OPSGROUP") then
|
||||
self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object.
|
||||
else
|
||||
self:_AddObject(Object)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -658,12 +678,12 @@ function TARGET:_AddObject(Object)
|
||||
elseif Object:IsInstanceOf("ZONE_BASE") then
|
||||
|
||||
local zone=Object --Core.Zone#ZONE_BASE
|
||||
Object=zone:GetCoordinate()
|
||||
Object=zone --:GetCoordinate()
|
||||
|
||||
target.Type=TARGET.ObjectType.COORDINATE
|
||||
target.Type=TARGET.ObjectType.ZONE
|
||||
target.Name=zone:GetName()
|
||||
|
||||
target.Coordinate=Object
|
||||
target.Coordinate=zone:GetCoordinate()
|
||||
|
||||
target.Life0=1
|
||||
target.Life=1
|
||||
@ -773,7 +793,13 @@ function TARGET:GetTargetLife(Target)
|
||||
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
||||
|
||||
return 1
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
return 1
|
||||
|
||||
else
|
||||
self:E("ERROR: unknown target object type in GetTargetLife!")
|
||||
end
|
||||
|
||||
end
|
||||
@ -865,6 +891,13 @@ function TARGET:GetTargetVec3(Target)
|
||||
local vec3={x=object.x, y=object.y, z=object.z}
|
||||
return vec3
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
local object=Target.Object --Core.Zone#ZONE
|
||||
|
||||
local vec3=object:GetVec3()
|
||||
return vec3
|
||||
|
||||
end
|
||||
|
||||
self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get Vec3")
|
||||
@ -1032,7 +1065,13 @@ function TARGET:GetTargetCategory(Target)
|
||||
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
||||
|
||||
return TARGET.Category.COORDINATE
|
||||
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
return TARGET.Category.ZONE
|
||||
|
||||
else
|
||||
self:E("ERROR: unknown target category!")
|
||||
end
|
||||
|
||||
return category
|
||||
@ -1141,6 +1180,10 @@ function TARGET:CountObjectives(Target)
|
||||
|
||||
-- No target we can check!
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
-- No target we can check!
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Unknown target type! Cannot count targets")
|
||||
end
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
--- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions.
|
||||
--- **Sound** - Radio transmissions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provide radio functionality to broadcast radio transmissions.
|
||||
-- * Provide beacon functionality to assist pilots.
|
||||
--
|
||||
-- The Radio contains 2 classes : RADIO and BEACON
|
||||
--
|
||||
-- What are radio communications in DCS?
|
||||
--
|
||||
@ -35,13 +32,13 @@
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Radio
|
||||
-- @module Sound.Radio
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
|
||||
--- Models the radio capability.
|
||||
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
|
||||
--
|
||||
-- ## RADIO usage
|
||||
-- # RADIO usage
|
||||
--
|
||||
-- There are 3 steps to a successful radio transmission.
|
||||
--
|
||||
@ -87,15 +84,15 @@
|
||||
-- @field #string alias Name of the radio transmitter.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIO = {
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
SubtitleDuration = 0,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias=nil,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias = nil,
|
||||
}
|
||||
|
||||
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
|
||||
@ -395,438 +392,3 @@ function RADIO:StopBroadcast()
|
||||
end
|
||||
return self
|
||||
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.
|
||||
-- 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.
|
||||
-- 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.
|
||||
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name=nil,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
-- @type BEACON.Type
|
||||
-- @field #number NULL
|
||||
-- @field #number VOR
|
||||
-- @field #number DME
|
||||
-- @field #number VOR_DME
|
||||
-- @field #number TACAN TACtical Air Navigation system.
|
||||
-- @field #number VORTAC
|
||||
-- @field #number RSBN
|
||||
-- @field #number BROADCAST_STATION
|
||||
-- @field #number HOMER
|
||||
-- @field #number AIRPORT_HOMER
|
||||
-- @field #number AIRPORT_HOMER_WITH_MARKER
|
||||
-- @field #number ILS_FAR_HOMER
|
||||
-- @field #number ILS_NEAR_HOMER
|
||||
-- @field #number ILS_LOCALIZER
|
||||
-- @field #number ILS_GLIDESLOPE
|
||||
-- @field #number PRMG_LOCALIZER
|
||||
-- @field #number PRMG_GLIDESLOPE
|
||||
-- @field #number ICLS Same as ICLS glideslope.
|
||||
-- @field #number ICLS_LOCALIZER
|
||||
-- @field #number ICLS_GLIDESLOPE
|
||||
-- @field #number NAUTICAL_HOMER
|
||||
BEACON.Type={
|
||||
NULL = 0,
|
||||
VOR = 1,
|
||||
DME = 2,
|
||||
VOR_DME = 3,
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
RSBN = 128,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
PRMG_LOCALIZER = 33024,
|
||||
PRMG_GLIDESLOPE = 33280,
|
||||
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
|
||||
ICLS_LOCALIZER = 131328,
|
||||
ICLS_GLIDESLOPE = 131584,
|
||||
NAUTICAL_HOMER = 65536,
|
||||
|
||||
}
|
||||
|
||||
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
|
||||
-- @type BEACON.System
|
||||
-- @field #number PAR_10 ?
|
||||
-- @field #number RSBN_5 Russian VOR/DME system.
|
||||
-- @field #number TACAN TACtical Air Navigation system on ground.
|
||||
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
|
||||
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glideslope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
|
||||
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
|
||||
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
|
||||
-- @field #number ICLS_LOCALIZER Carrier landing system.
|
||||
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER_X = 4,
|
||||
TACAN_TANKER_Y = 5,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
ILS_GLIDESLOPE = 8,
|
||||
PRMG_LOCALIZER = 9,
|
||||
PRMG_GLIDESLOPE = 10,
|
||||
BROADCAST_STATION = 11,
|
||||
VORTAC = 12,
|
||||
TACAN_AA_MODE_X = 13,
|
||||
TACAN_AA_MODE_Y = 14,
|
||||
VORDME = 15,
|
||||
ICLS_LOCALIZER = 16,
|
||||
ICLS_GLIDESLOPE = 17,
|
||||
}
|
||||
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
|
||||
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
|
||||
-- @param #BEACON self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
|
||||
function BEACON:New(Positionable)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
-- Debug.
|
||||
self:F(Positionable)
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
self.name=Positionable:GetName()
|
||||
self:I(string.format("New BEACON %s", tostring(self.name)))
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
|
||||
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
|
||||
|
||||
-- Get frequency.
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
return self
|
||||
end
|
||||
|
||||
-- Beacon type.
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
-- Beacon system.
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA=self.Positionable:IsAir()
|
||||
if AA then
|
||||
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
-- Check if "Y" mode is selected for aircraft.
|
||||
if Mode~="Y" then
|
||||
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
|
||||
end
|
||||
end
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug.
|
||||
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop sheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel ICLS channel.
|
||||
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
-- @param #boolean Bearing Can the BEACON be homed on ?
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
|
||||
local IsValid = true
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
|
||||
-- or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
System = 5
|
||||
else
|
||||
System = 14
|
||||
end
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = 4,
|
||||
system = System,
|
||||
callsign = Message,
|
||||
frequency = Frequency,
|
||||
}
|
||||
})
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
-- * ARK9 -> 150-1300KHz
|
||||
-- * **Huey**
|
||||
-- * AN/ARC-131 -> 30-76 Mhz FM
|
||||
-- @param #BEACON self
|
||||
-- @param #string FileName The name of the audio file
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a beacon for a unit in distress.
|
||||
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
|
||||
-- -- The beacon they use is battery-powered, and only lasts for 5 min
|
||||
-- local UnitInDistress = UNIT:FindByName("Unit1")
|
||||
-- local UnitBeacon = UnitInDistress:GetBeacon()
|
||||
--
|
||||
-- -- Set the beacon and start it
|
||||
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
IsValid = true
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type(TACANChannel) ~= "number" then
|
||||
if TACANMode ~= "X" and TACANMode ~= "Y" then
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
A = 1088
|
||||
end
|
||||
else -- 'X'
|
||||
if TACANChannel < 64 then
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
|
||||
|
||||
@ -1,20 +1,24 @@
|
||||
--- **Core** - Queues Radio Transmissions.
|
||||
--- **Sound** - Queues Radio Transmissions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Managed Radio Transmissions.
|
||||
-- * Manage Radio Transmissions
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: funkyfranky
|
||||
--
|
||||
-- @module Core.RadioQueue
|
||||
-- @module Sound.RadioQueue
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Manages radio transmissions.
|
||||
--
|
||||
-- The main goal of the RADIOQUEUE class is to string together multiple sound files to play a complete sentence.
|
||||
-- The underlying problem is that radio transmissions in DCS are not queued but played "on top" of each other.
|
||||
-- Therefore, to achive the goal, it is vital to know the precise duration how long it takes to play the sound file.
|
||||
--
|
||||
-- @type RADIOQUEUE
|
||||
-- @field #string ClassName Name of the class "RADIOQUEUE".
|
||||
-- @field #boolean Debugmode Debug mode. More info.
|
||||
@ -35,6 +39,7 @@
|
||||
-- @field #table numbers Table of number transmission parameters.
|
||||
-- @field #boolean checking Scheduler is checking the radio queue.
|
||||
-- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler.
|
||||
-- @field Sound.SRS#MSRS msrs Moose SRS class.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIOQUEUE = {
|
||||
ClassName = "RADIOQUEUE",
|
||||
@ -69,12 +74,14 @@ RADIOQUEUE = {
|
||||
-- @field #boolean isplaying If true, transmission is currently playing.
|
||||
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
|
||||
-- @field #number interval Interval in seconds before next transmission.
|
||||
-- @field Sound.SoundOutput#SOUNDFILE soundfile Sound file object to play via SRS.
|
||||
-- @field Sound.SoundOutput#SOUNDTEXT soundtext Sound TTS object to play via SRS.
|
||||
|
||||
|
||||
--- Create a new RADIOQUEUE object for a given radio frequency/modulation.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number frequency The radio frequency in MHz.
|
||||
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM.
|
||||
-- @param #number modulation (Optional) The radio modulation. Default `radio.modulation.AM` (=0).
|
||||
-- @param #string alias (Optional) Name of the radio queue.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
function RADIOQUEUE:New(frequency, modulation, alias)
|
||||
@ -125,9 +132,9 @@ function RADIOQUEUE:Start(delay, dt)
|
||||
|
||||
-- Start Scheduler.
|
||||
if self.schedonce then
|
||||
self:_CheckRadioQueueDelayed(delay)
|
||||
self:_CheckRadioQueueDelayed(self.delay)
|
||||
else
|
||||
self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, delay, dt)
|
||||
self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, self.delay, self.dt)
|
||||
end
|
||||
|
||||
return self
|
||||
@ -170,6 +177,17 @@ function RADIOQUEUE:SetRadioPower(power)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set SRS.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #string PathToSRS Path to SRS.
|
||||
-- @param #number Port SRS port. Default 5002.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
function RADIOQUEUE:SetSRS(PathToSRS, Port)
|
||||
self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation)
|
||||
self.msrs:SetPort(Port)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set parameters of a digit.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number digit The digit 0-9.
|
||||
@ -202,7 +220,7 @@ end
|
||||
--- Add a transmission to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission data table.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddTransmission(transmission)
|
||||
self:F({transmission=transmission})
|
||||
|
||||
@ -221,7 +239,7 @@ function RADIOQUEUE:AddTransmission(transmission)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a transmission to the radio queue.
|
||||
--- Create a new transmission and add it to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #string filename Name of the sound file. Usually an ogg or wav file type.
|
||||
-- @param #number duration Duration in seconds the file lasts.
|
||||
@ -230,7 +248,7 @@ end
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @param #string subtitle Subtitle of the transmission.
|
||||
-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
-- @return #RADIOQUEUE.Transmission Radio transmission table.
|
||||
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration)
|
||||
|
||||
-- Sanity checks.
|
||||
@ -269,9 +287,36 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval,
|
||||
-- Add transmission to queue.
|
||||
self:AddTransmission(transmission)
|
||||
|
||||
return transmission
|
||||
end
|
||||
|
||||
--- Add a SOUNDFILE to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added.
|
||||
-- @param #number tstart Start time (abs) seconds. Default now.
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval)
|
||||
--env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName()))
|
||||
local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration)
|
||||
transmission.soundfile=soundfile
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a SOUNDTEXT to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param Sound.SoundOutput#SOUNDTEXT soundtext Text-to-speech text.
|
||||
-- @param #number tstart Start time (abs) seconds. Default now.
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddSoundText(soundtext, tstart, interval)
|
||||
|
||||
local transmission=self:NewTransmission("SoundText.ogg", soundtext.duration, nil, tstart, interval, soundtext.subtitle, soundtext.subduration)
|
||||
transmission.soundtext=soundtext
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Convert a number (as string) into a radio transmission.
|
||||
-- E.g. for board number or headings.
|
||||
-- @param #RADIOQUEUE self
|
||||
@ -280,19 +325,9 @@ end
|
||||
-- @param #number interval Interval between the next call.
|
||||
-- @return #number Duration of the call in seconds.
|
||||
function RADIOQUEUE:Number2Transmission(number, delay, interval)
|
||||
|
||||
--- Split string into characters.
|
||||
local function _split(str)
|
||||
local chars={}
|
||||
for i=1,#str do
|
||||
local c=str:sub(i,i)
|
||||
table.insert(chars, c)
|
||||
end
|
||||
return chars
|
||||
end
|
||||
|
||||
-- Split string into characters.
|
||||
local numbers=_split(number)
|
||||
local numbers=UTILS.GetCharacters(number)
|
||||
|
||||
local wait=0
|
||||
for i=1,#numbers do
|
||||
@ -325,6 +360,11 @@ end
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission.
|
||||
function RADIOQUEUE:Broadcast(transmission)
|
||||
|
||||
if ((transmission.soundfile and transmission.soundfile.useSRS) or transmission.soundtext) and self.msrs then
|
||||
self:_BroadcastSRS(transmission)
|
||||
return
|
||||
end
|
||||
|
||||
-- Get unit sending the transmission.
|
||||
local sender=self:_GetRadioSender()
|
||||
|
||||
@ -416,6 +456,19 @@ function RADIOQUEUE:Broadcast(transmission)
|
||||
end
|
||||
end
|
||||
|
||||
--- Broadcast radio message.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission.
|
||||
function RADIOQUEUE:_BroadcastSRS(transmission)
|
||||
|
||||
if transmission.soundfile and transmission.soundfile.useSRS then
|
||||
self.msrs:PlaySoundFile(transmission.soundfile)
|
||||
elseif transmission.soundtext then
|
||||
self.msrs:PlaySoundText(transmission.soundtext)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Start checking the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number delay Delay in seconds before checking.
|
||||
@ -547,7 +600,7 @@ function RADIOQUEUE:_GetRadioSender()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
|
||||
--- Get unit from which we want to transmit a radio message. This has to be an aircraft or ground unit for subtitles to work.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @return DCS#Vec3 Vector 3D.
|
||||
function RADIOQUEUE:_GetRadioSenderCoord()
|
||||
@ -11,7 +11,7 @@
|
||||
--
|
||||
-- ### Authors: FlightControl
|
||||
--
|
||||
-- @module Core.RadioSpeech
|
||||
-- @module Sound.RadioSpeech
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Makes the radio speak.
|
||||
@ -162,31 +162,31 @@ RADIOSPEECH.Vocabulary.RU = {
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
|
||||
["степени"] = { "degrees", 0.5 },
|
||||
["километров"] = { "kilometers", 0.65 },
|
||||
["Ñ<EFBFBD>тепени"] = { "degrees", 0.5 },
|
||||
["километров"] = { "kilometers", 0.65 },
|
||||
["km"] = { "kilometers", 0.65 },
|
||||
["миль"] = { "miles", 0.45 },
|
||||
["миль"] = { "miles", 0.45 },
|
||||
["mi"] = { "miles", 0.45 },
|
||||
["метры"] = { "meters", 0.41 },
|
||||
["метры"] = { "meters", 0.41 },
|
||||
["m"] = { "meters", 0.41 },
|
||||
["ноги"] = { "feet", 0.37 },
|
||||
["ноги"] = { "feet", 0.37 },
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
["возвращаясь на базу"] = { "returning_to_base", 1.40 },
|
||||
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
|
||||
["перехват самолетов"] = { "intercepting_bogeys", 1.22 },
|
||||
["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
|
||||
["захватывающие самолеты"] = { "engaging_bogeys", 1.68 },
|
||||
["колеса вверх"] = { "wheels_up", 0.92 },
|
||||
["посадка на базу"] = { "landing at base", 1.04 },
|
||||
["патрулирующий"] = { "patrolling", 0.96 },
|
||||
["возвращаÑ<EFBFBD>Ñ<EFBFBD>ÑŒ на базу"] = { "returning_to_base", 1.40 },
|
||||
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
|
||||
["перехват Ñ<>амолетов"] = { "intercepting_bogeys", 1.22 },
|
||||
["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
|
||||
["захватывающие Ñ<>амолеты"] = { "engaging_bogeys", 1.68 },
|
||||
["колеÑ<EFBFBD>а вверх"] = { "wheels_up", 0.92 },
|
||||
["поÑ<EFBFBD>адка на базу"] = { "landing at base", 1.04 },
|
||||
["патрулирующий"] = { "patrolling", 0.96 },
|
||||
|
||||
["за"] = { "for", 0.27 },
|
||||
["и"] = { "and", 0.17 },
|
||||
["в"] = { "at", 0.19 },
|
||||
["за"] = { "for", 0.27 },
|
||||
["и"] = { "and", 0.17 },
|
||||
["в"] = { "at", 0.19 },
|
||||
["dot"] = { "dot", 0.51 },
|
||||
["defender"] = { "defender", 0.45 },
|
||||
}
|
||||
692
Moose Development/Moose/Sound/SRS.lua
Normal file
692
Moose Development/Moose/Sound/SRS.lua
Normal file
@ -0,0 +1,692 @@
|
||||
--- **Sound** - Simple Radio Standalone (SRS) Integration.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Play sound files via SRS
|
||||
-- * Play text-to-speach via SRS
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Youtube Videos: None yet
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions: None yet
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- The goal of the [SRS](https://github.com/ciribob/DCS-SimpleRadioStandalone) project is to bring VoIP communication into DCS and to make communication as frictionless as possible.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Sound.MSRS
|
||||
-- @image Sound_MSRS.png
|
||||
|
||||
--- MSRS class.
|
||||
-- @type MSRS
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table frequencies Frequencies used in the transmissions.
|
||||
-- @field #table modulations Modulations used in the transmissions.
|
||||
-- @field #number coalition Coalition of the transmission.
|
||||
-- @field #number port Port. Default 5002.
|
||||
-- @field #string name Name. Default "DCS-STTS".
|
||||
-- @field #number volume Volume between 0 (min) and 1 (max). Default 1.
|
||||
-- @field #string culture Culture. Default "en-GB".
|
||||
-- @field #string gender Gender. Default "female".
|
||||
-- @field #string voice Specifc voce.
|
||||
-- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send.
|
||||
-- @field #string path Path to the SRS exe. This includes the final slash "/".
|
||||
-- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json".
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The MSRS Concept
|
||||
--
|
||||
-- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS).
|
||||
--
|
||||
-- ## Prerequisites
|
||||
--
|
||||
-- This script needs SRS version >= 1.9.6.
|
||||
--
|
||||
-- # Play Sound Files
|
||||
--
|
||||
-- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "D:\\Sounds For DCS")
|
||||
-- local msrs=MSRS:New("C:\\Path To SRS", 251, radio.modulation.AM)
|
||||
-- msrs:PlaySoundFile(soundfile)
|
||||
--
|
||||
-- # Play Text-To-Speech
|
||||
--
|
||||
-- Basic example:
|
||||
--
|
||||
-- -- Create a SOUNDTEXT object.
|
||||
-- local text=SOUNDTEXT:New("All Enemies destroyed")
|
||||
--
|
||||
-- -- MOOSE SRS
|
||||
-- local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, radio.modulation.AM)
|
||||
--
|
||||
-- -- Text-to speech with default voice after 2 seconds.
|
||||
-- msrs:PlaySoundText(text, 2)
|
||||
--
|
||||
-- ## Set Gender
|
||||
--
|
||||
-- Use a specific gender with the @{#MSRS.SetGender} function, e.g. `SetGender("male")` or `:SetGender("female")`.
|
||||
--
|
||||
-- ## Set Culture
|
||||
--
|
||||
-- Use a specific "culture" with the @{#MSRS.SetCulture} function, e.g. `:SetCulture("en-US")` or `:SetCulture("de-DE")`.
|
||||
--
|
||||
-- ## Set Voice
|
||||
--
|
||||
-- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`.
|
||||
-- Note that this must be installed on your windows system.
|
||||
--
|
||||
-- ## Set Coordinate
|
||||
--
|
||||
-- Use @{#MSRS.SetCoordinate} to define the origin from where the transmission is broadcasted.
|
||||
--
|
||||
-- @field #MSRS
|
||||
MSRS = {
|
||||
ClassName = "MSRS",
|
||||
lid = nil,
|
||||
port = 5002,
|
||||
name = "MSRS",
|
||||
frequencies = {},
|
||||
modulations = {},
|
||||
coalition = 0,
|
||||
gender = "female",
|
||||
culture = nil,
|
||||
voice = nil,
|
||||
volume = 1,
|
||||
speed = 1,
|
||||
coordinate = nil,
|
||||
}
|
||||
|
||||
--- MSRS class version.
|
||||
-- @field #string version
|
||||
MSRS.version="0.0.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add functions to add/remove freqs and modulations.
|
||||
-- DONE: Add coordinate.
|
||||
-- DONE: Add google.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new MSRS object.
|
||||
-- @param #MSRS self
|
||||
-- @param #string PathToSRS Path to the directory, where SRS is located.
|
||||
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies.
|
||||
-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations.
|
||||
-- @return #MSRS self
|
||||
function MSRS:New(PathToSRS, Frequency, Modulation)
|
||||
|
||||
-- Defaults.
|
||||
Frequency =Frequency or 143
|
||||
Modulation= Modulation or radio.modulation.AM
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #MSRS
|
||||
|
||||
self:SetPath(PathToSRS)
|
||||
self:SetPort()
|
||||
self:SetFrequencies(Frequency)
|
||||
self:SetModulations(Modulation)
|
||||
self:SetGender()
|
||||
self:SetCoalition()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set path to SRS install directory. More precisely, path to where the DCS-
|
||||
-- @param #MSRS self
|
||||
-- @param #string Path Path to the directory, where the sound file is located. This does **not** contain a final backslash or slash.
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetPath(Path)
|
||||
|
||||
if Path==nil then
|
||||
self:E("ERROR: No path to SRS directory specified!")
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Set path.
|
||||
self.path=Path
|
||||
|
||||
-- Remove (back)slashes.
|
||||
local n=1 ; local nmax=1000
|
||||
while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do
|
||||
self.path=self.path:sub(1,#self.path-1)
|
||||
n=n+1
|
||||
end
|
||||
|
||||
-- Debug output.
|
||||
self:T(string.format("SRS path=%s", self:GetPath()))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get path to SRS directory.
|
||||
-- @param #MSRS self
|
||||
-- @return #string Path to the directory. This includes the final slash "/".
|
||||
function MSRS:GetPath()
|
||||
return self.path
|
||||
end
|
||||
|
||||
--- Set port.
|
||||
-- @param #MSRS self
|
||||
-- @param #number Port Port. Default 5002.
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetPort(Port)
|
||||
self.port=Port or 5002
|
||||
end
|
||||
|
||||
--- Get port.
|
||||
-- @param #MSRS self
|
||||
-- @return #number Port.
|
||||
function MSRS:GetPort()
|
||||
return self.port
|
||||
end
|
||||
|
||||
--- Set coalition.
|
||||
-- @param #MSRS self
|
||||
-- @param #number Coalition Coalition. Default 0.
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetCoalition(Coalition)
|
||||
self.coalition=Coalition or 0
|
||||
end
|
||||
|
||||
--- Get coalition.
|
||||
-- @param #MSRS self
|
||||
-- @return #number Coalition.
|
||||
function MSRS:GetCoalition()
|
||||
return self.coalition
|
||||
end
|
||||
|
||||
|
||||
--- Set frequencies.
|
||||
-- @param #MSRS self
|
||||
-- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used.
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetFrequencies(Frequencies)
|
||||
|
||||
-- Ensure table.
|
||||
if type(Frequencies)~="table" then
|
||||
Frequencies={Frequencies}
|
||||
end
|
||||
|
||||
self.frequencies=Frequencies
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get frequencies.
|
||||
-- @param #MSRS self
|
||||
-- @param #table Frequencies in MHz.
|
||||
function MSRS:GetFrequencies()
|
||||
return self.frequencies
|
||||
end
|
||||
|
||||
|
||||
--- Set modulations.
|
||||
-- @param #MSRS self
|
||||
-- @param #table Modulations Modulations. Can also be given as a #number if only one modulation should be used.
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetModulations(Modulations)
|
||||
|
||||
-- Ensure table.
|
||||
if type(Modulations)~="table" then
|
||||
Modulations={Modulations}
|
||||
end
|
||||
|
||||
self.modulations=Modulations
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get modulations.
|
||||
-- @param #MSRS self
|
||||
-- @param #table Modulations.
|
||||
function MSRS:GetModulations()
|
||||
return self.modulations
|
||||
end
|
||||
|
||||
--- Set gender.
|
||||
-- @param #MSRS self
|
||||
-- @param #string Gender Gender: "male" or "female" (default).
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetGender(Gender)
|
||||
|
||||
Gender=Gender or "female"
|
||||
|
||||
self.gender=Gender:lower()
|
||||
|
||||
-- Debug output.
|
||||
self:T("Setting gender to "..tostring(self.gender))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set culture.
|
||||
-- @param #MSRS self
|
||||
-- @param #string Culture Culture, e.g. "en-GB" (default).
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetCulture(Culture)
|
||||
|
||||
self.culture=Culture
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set to use a specific voice. Will override gender and culture settings.
|
||||
-- @param #MSRS self
|
||||
-- @param #string Voice Voice.
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetVoice(Voice)
|
||||
|
||||
self.voice=Voice
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the coordinate from which the transmissions will be broadcasted.
|
||||
-- @param #MSRS self
|
||||
-- @param Core.Point#COORDINATE Coordinate Origin of the transmission.
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetCoordinate(Coordinate)
|
||||
|
||||
self.coordinate=Coordinate
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Use google text-to-speech.
|
||||
-- @param #MSRS self
|
||||
-- @param PathToCredentials Full path to the google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json".
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetGoogle(PathToCredentials)
|
||||
|
||||
self.google=PathToCredentials
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Print SRS STTS help to DCS log file.
|
||||
-- @param #MSRS self
|
||||
-- @return #MSRS self
|
||||
function MSRS:Help()
|
||||
|
||||
-- Path and exe.
|
||||
local path=self:GetPath() or STTS.DIRECTORY
|
||||
local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe"
|
||||
|
||||
-- Text file for output.
|
||||
local filename = os.getenv('TMP') .. "\\MSRS-help-"..STTS.uuid()..".txt"
|
||||
|
||||
-- Print help.
|
||||
local command=string.format("%s/%s --help > %s", path, exe, filename)
|
||||
os.execute(command)
|
||||
|
||||
local f=assert(io.open(filename, "rb"))
|
||||
local data=f:read("*all")
|
||||
f:close()
|
||||
|
||||
-- Print to log file.
|
||||
env.info("SRS STTS help output:")
|
||||
env.info("======================================================================")
|
||||
env.info(data)
|
||||
env.info("======================================================================")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Transmission Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Play sound file (ogg or mp3) via SRS.
|
||||
-- @param #MSRS self
|
||||
-- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play.
|
||||
-- @param #number Delay Delay in seconds, before the sound file is played.
|
||||
-- @return #MSRS self
|
||||
function MSRS:PlaySoundFile(Soundfile, Delay)
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, MSRS.PlaySoundFile, self, Soundfile, 0)
|
||||
else
|
||||
|
||||
-- Sound file name.
|
||||
local soundfile=Soundfile:GetName()
|
||||
|
||||
-- Get command.
|
||||
local command=self:_GetCommand()
|
||||
|
||||
-- Append file.
|
||||
command=command.." --file="..tostring(soundfile)
|
||||
|
||||
self:_ExecCommand(command)
|
||||
|
||||
--[[
|
||||
|
||||
command=command.." > bla.txt"
|
||||
|
||||
-- Debug output.
|
||||
self:I(string.format("MSRS PlaySoundfile command=%s", command))
|
||||
|
||||
-- Execute SRS command.
|
||||
local x=os.execute(command)
|
||||
|
||||
]]
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Play a SOUNDTEXT text-to-speech object.
|
||||
-- @param #MSRS self
|
||||
-- @param Sound.SoundFile#SOUNDTEXT SoundText Sound text.
|
||||
-- @param #number Delay Delay in seconds, before the sound file is played.
|
||||
-- @return #MSRS self
|
||||
function MSRS:PlaySoundText(SoundText, Delay)
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0)
|
||||
else
|
||||
|
||||
-- Get command.
|
||||
local command=self:_GetCommand(nil, nil, nil, SoundText.gender, SoundText.voice, SoundText.culture, SoundText.volume, SoundText.speed)
|
||||
|
||||
-- Append text.
|
||||
command=command..string.format(" --text=\"%s\"", tostring(SoundText.text))
|
||||
|
||||
-- Execute command.
|
||||
self:_ExecCommand(command)
|
||||
|
||||
--[[
|
||||
command=command.." > bla.txt"
|
||||
|
||||
-- Debug putput.
|
||||
self:I(string.format("MSRS PlaySoundfile command=%s", command))
|
||||
|
||||
-- Execute SRS command.
|
||||
local x=os.execute(command)
|
||||
]]
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Play text message via STTS.
|
||||
-- @param #MSRS self
|
||||
-- @param #string Text Text message.
|
||||
-- @param #number Delay Delay in seconds, before the message is played.
|
||||
-- @return #MSRS self
|
||||
function MSRS:PlayText(Text, Delay)
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0)
|
||||
else
|
||||
|
||||
-- Get command line.
|
||||
local command=self:_GetCommand()
|
||||
|
||||
-- Append text.
|
||||
command=command..string.format(" --text=\"%s\"", tostring(Text))
|
||||
|
||||
-- Execute command.
|
||||
self:_ExecCommand(command)
|
||||
|
||||
--[[
|
||||
|
||||
-- Check that length of command is max 255 chars or os.execute() will not work!
|
||||
if string.len(command)>255 then
|
||||
|
||||
-- Create a tmp file.
|
||||
local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat"
|
||||
|
||||
local script = io.open(filename, "w+")
|
||||
script:write(command.." && exit")
|
||||
script:close()
|
||||
|
||||
-- Play command.
|
||||
command=string.format("\"%s\"", filename)
|
||||
|
||||
-- Play file in 0.05 seconds
|
||||
timer.scheduleFunction(os.execute, command, timer.getTime()+0.05)
|
||||
|
||||
-- Remove file in 1 second.
|
||||
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
|
||||
else
|
||||
|
||||
-- Debug output.
|
||||
self:I(string.format("MSRS Text command=%s", command))
|
||||
|
||||
-- Execute SRS command.
|
||||
local x=os.execute(command)
|
||||
|
||||
end
|
||||
|
||||
]]
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Play text file via STTS.
|
||||
-- @param #MSRS self
|
||||
-- @param #string TextFile Full path to the file.
|
||||
-- @param #number Delay Delay in seconds, before the message is played.
|
||||
-- @return #MSRS self
|
||||
function MSRS:PlayTextFile(TextFile, Delay)
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, MSRS.PlayTextFile, self, TextFile, 0)
|
||||
else
|
||||
|
||||
-- First check if text file exists!
|
||||
local exists=UTILS.FileExists(TextFile)
|
||||
if not exists then
|
||||
self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile))
|
||||
return self
|
||||
end
|
||||
|
||||
-- Get command line.
|
||||
local command=self:_GetCommand()
|
||||
|
||||
-- Append text file.
|
||||
command=command..string.format(" --textFile=\"%s\"", tostring(TextFile))
|
||||
|
||||
-- Debug output.
|
||||
self:T(string.format("MSRS TextFile command=%s", command))
|
||||
|
||||
-- Count length of command.
|
||||
local l=string.len(command)
|
||||
|
||||
-- Execute command.
|
||||
self:_ExecCommand(command)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`.
|
||||
-- @param #MSRS self
|
||||
-- @param #string command Command to executer
|
||||
-- @return #number Return value of os.execute() command.
|
||||
function MSRS:_ExecCommand(command)
|
||||
|
||||
-- Create a tmp file.
|
||||
local filename=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".bat"
|
||||
|
||||
local script=io.open(filename, "w+")
|
||||
script:write(command.." && exit")
|
||||
script:close()
|
||||
|
||||
-- Play command.
|
||||
command=string.format('start /b "" "%s"', filename)
|
||||
|
||||
local res=nil
|
||||
if true then
|
||||
|
||||
-- Create a tmp file.
|
||||
local filenvbs = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".vbs"
|
||||
|
||||
-- VBS script
|
||||
local script = io.open(filenvbs, "w+")
|
||||
script:write(string.format('Dim WinScriptHost\n'))
|
||||
script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n'))
|
||||
script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n', filename))
|
||||
script:write(string.format('Set WinScriptHost = Nothing'))
|
||||
script:close()
|
||||
|
||||
-- Run visual basic script. This still pops up a window but very briefly and does not put the DCS window out of focus.
|
||||
local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs)
|
||||
|
||||
-- Debug output.
|
||||
self:T("MSRS execute command="..command)
|
||||
self:T("MSRS execute VBS command="..runvbs)
|
||||
|
||||
-- Play file in 0.01 seconds
|
||||
res=os.execute(runvbs)
|
||||
|
||||
-- Remove file in 1 second.
|
||||
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
|
||||
timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1)
|
||||
|
||||
|
||||
else
|
||||
|
||||
-- Debug output.
|
||||
self:T("MSRS execute command="..command)
|
||||
|
||||
-- Execute command
|
||||
res=os.execute(command)
|
||||
|
||||
-- Remove file in 1 second.
|
||||
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
|
||||
|
||||
end
|
||||
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
--- Get lat, long and alt from coordinate.
|
||||
-- @param #MSRS self
|
||||
-- @param Core.Point#Coordinate Coordinate Coordinate. Can also be a DCS#Vec3.
|
||||
-- @return #number Latitude.
|
||||
-- @return #number Longitude.
|
||||
-- @return #number Altitude.
|
||||
function MSRS:_GetLatLongAlt(Coordinate)
|
||||
|
||||
local lat, lon, alt=coord.LOtoLL(Coordinate)
|
||||
|
||||
return lat, lon, math.floor(alt)
|
||||
end
|
||||
|
||||
|
||||
--- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`.
|
||||
-- @param #MSRS self
|
||||
-- @param #table freqs Frequencies in MHz.
|
||||
-- @param #table modus Modulations.
|
||||
-- @param #number coal Coalition.
|
||||
-- @param #string gender Gender.
|
||||
-- @param #string voice Voice.
|
||||
-- @param #string culture Culture.
|
||||
-- @param #number volume Volume.
|
||||
-- @param #number speed Speed.
|
||||
-- @param #number port Port.
|
||||
-- @return #string Command.
|
||||
function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port)
|
||||
|
||||
local path=self:GetPath() or STTS.DIRECTORY
|
||||
local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe"
|
||||
freqs=table.concat(freqs or self.frequencies, ",")
|
||||
modus=table.concat(modus or self.modulations, ",")
|
||||
coal=coal or self.coalition
|
||||
gender=gender or self.gender
|
||||
voice=voice or self.voice
|
||||
culture=culture or self.culture
|
||||
volume=volume or self.volume
|
||||
speed=speed or self.speed
|
||||
port=port or self.port
|
||||
|
||||
-- Replace modulation
|
||||
modus=modus:gsub("0", "AM")
|
||||
modus=modus:gsub("1", "FM")
|
||||
|
||||
-- This did not work well. Stopped if the transmission was a bit longer with no apparent error.
|
||||
--local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed)
|
||||
|
||||
-- Command from orig STTS script. Works better for some unknown reason!
|
||||
local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT")
|
||||
|
||||
--local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT")
|
||||
|
||||
-- Command.
|
||||
local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT")
|
||||
|
||||
-- Set voice or gender/culture.
|
||||
if voice then
|
||||
-- Use a specific voice (no need for gender and/or culture.
|
||||
command=command..string.format(" --voice=\"%s\"", tostring(voice))
|
||||
else
|
||||
-- Add gender.
|
||||
if gender and gender~="female" then
|
||||
command=command..string.format(" -g %s", tostring(gender))
|
||||
end
|
||||
-- Add culture.
|
||||
if culture and culture~="en-GB" then
|
||||
command=command..string.format(" -l %s", tostring(culture))
|
||||
end
|
||||
end
|
||||
|
||||
-- Set coordinate.
|
||||
if self.coordinate then
|
||||
local lat,lon,alt=self:_GetLatLongAlt(self.coordinate)
|
||||
command=command..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt)
|
||||
end
|
||||
|
||||
-- Set google.
|
||||
if self.google then
|
||||
command=command..string.format(' -G "%s"', self.google)
|
||||
end
|
||||
|
||||
-- Debug output.
|
||||
self:T("MSRS command="..command)
|
||||
|
||||
return command
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
408
Moose Development/Moose/Sound/SoundOutput.lua
Normal file
408
Moose Development/Moose/Sound/SoundOutput.lua
Normal file
@ -0,0 +1,408 @@
|
||||
--- **Sound** - Sound output classes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions
|
||||
-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (STTS)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- There are two classes, SOUNDFILE and SOUNDTEXT, defined in this section that deal with playing
|
||||
-- sound files or arbitrary text (via SRS Simple-Text-To-Speech), respectively.
|
||||
--
|
||||
-- The SOUNDFILE and SOUNDTEXT objects can be defined and used in other MOOSE classes.
|
||||
--
|
||||
--
|
||||
-- @module Sound.SoundOutput
|
||||
-- @image Sound_SoundOutput.png
|
||||
|
||||
do -- Sound Base
|
||||
|
||||
--- @type SOUNDBASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Basic sound output inherited by other classes suche as SOUNDFILE and SOUNDTEXT.
|
||||
--
|
||||
-- This class is **not** meant to be used by "ordinary" users.
|
||||
--
|
||||
-- @field #SOUNDBASE
|
||||
SOUNDBASE={
|
||||
ClassName = "SOUNDBASE",
|
||||
}
|
||||
|
||||
--- Constructor to create a new SOUNDBASE object.
|
||||
-- @param #SOUNDBASE self
|
||||
-- @return #SOUNDBASE self
|
||||
function SOUNDBASE:New()
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDBASE
|
||||
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function returns estimated speech time in seconds.
|
||||
-- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so
|
||||
--
|
||||
-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second
|
||||
--
|
||||
-- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function:
|
||||
--
|
||||
-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||
--
|
||||
-- @param #string Text The text string to analyze.
|
||||
-- @param #number Speed Speed factor. Default 1.
|
||||
-- @param #boolean isGoogle If true, google text-to-speech is used.
|
||||
function SOUNDBASE:GetSpeechTime(length,speed,isGoogle)
|
||||
|
||||
local maxRateRatio = 3
|
||||
|
||||
speed = speed or 1.0
|
||||
isGoogle = isGoogle or false
|
||||
|
||||
local speedFactor = 1.0
|
||||
if isGoogle then
|
||||
speedFactor = speed
|
||||
else
|
||||
if speed ~= 0 then
|
||||
speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1
|
||||
end
|
||||
if speed < 0 then
|
||||
speedFactor = 1/speedFactor
|
||||
end
|
||||
end
|
||||
|
||||
-- Words per minute.
|
||||
local wpm = math.ceil(100 * speedFactor)
|
||||
|
||||
-- Characters per second.
|
||||
local cps = math.floor((wpm * 5)/60)
|
||||
|
||||
if type(length) == "string" then
|
||||
length = string.len(length)
|
||||
end
|
||||
|
||||
return math.ceil(length/cps)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- Sound File
|
||||
|
||||
--- @type SOUNDFILE
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string filename Name of the flag.
|
||||
-- @field #string path Directory path, where the sound file is located. This includes the final slash "/".
|
||||
-- @field #string duration Duration of the sound file in seconds.
|
||||
-- @field #string subtitle Subtitle of the transmission.
|
||||
-- @field #number subduration Duration in seconds how long the subtitle is displayed.
|
||||
-- @field #boolean useSRS If true, sound file is played via SRS. Sound file needs to be on local disk not inside the miz file!
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Sound files used by other classes.
|
||||
--
|
||||
-- # The SOUNDFILE Concept
|
||||
--
|
||||
-- A SOUNDFILE object hold the important properties that are necessary to play the sound file, e.g. its file name, path, duration.
|
||||
--
|
||||
-- It can be created with the @{#SOUNDFILE.New}(*FileName*, *Path*, *Duration*) function:
|
||||
--
|
||||
-- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "Sound File/", 3.5)
|
||||
--
|
||||
-- ## SRS
|
||||
--
|
||||
-- If sound files are supposed to be played via SRS, you need to use the @{#SOUNDFILE.SetPlayWithSRS}() function.
|
||||
--
|
||||
-- # Location/Path
|
||||
--
|
||||
-- ## DCS
|
||||
--
|
||||
-- DCS can only play sound files that are located inside the mission (.miz) file. In particular, DCS cannot make use of files that are stored on
|
||||
-- your hard drive.
|
||||
--
|
||||
-- The default location where sound files are stored in DCS is the directory "l10n/DEFAULT/". This is where sound files are placed, if they are
|
||||
-- added via the mission editor (TRIGGERS-->ACTIONS-->SOUND TO ALL). Note however, that sound files which are not added with a trigger command,
|
||||
-- will be deleted each time the mission is saved! Therefore, this directory is not ideal to be used especially if many sound files are to
|
||||
-- be included since for each file a trigger action needs to be created. Which is cumbersome, to say the least.
|
||||
--
|
||||
-- The recommended way is to create a new folder inside the mission (.miz) file (a miz file is essentially zip file and can be opened, e.g., with 7-Zip)
|
||||
-- and to place the sound files in there. Sound files in these folders are not wiped out by DCS on the next save.
|
||||
--
|
||||
-- ## SRS
|
||||
--
|
||||
-- SRS sound files need to be located on your local drive (not inside the miz). Therefore, you need to specify the full path.
|
||||
--
|
||||
-- @field #SOUNDFILE
|
||||
SOUNDFILE={
|
||||
ClassName = "SOUNDFILE",
|
||||
filename = nil,
|
||||
path = "l10n/DEFAULT/",
|
||||
duration = 3,
|
||||
subtitle = nil,
|
||||
subduration = 0,
|
||||
useSRS = false,
|
||||
}
|
||||
|
||||
--- Constructor to create a new SOUNDFILE object.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string FileName The name of the sound file, e.g. "Hello World.ogg".
|
||||
-- @param #string Path The path of the directory, where the sound file is located. Default is "l10n/DEFAULT/" within the miz file.
|
||||
-- @param #number Duration Duration in seconds, how long it takes to play the sound file. Default is 3 seconds.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:New(FileName, Path, Duration)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE
|
||||
|
||||
-- Set file name.
|
||||
self:SetFileName(FileName)
|
||||
|
||||
-- Set path.
|
||||
self:SetPath(Path)
|
||||
|
||||
-- Set duration.
|
||||
self:SetDuration(Duration)
|
||||
|
||||
-- Debug info:
|
||||
self:T(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set path, where the sound file is located.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string Path Path to the directory, where the sound file is located.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetPath(Path)
|
||||
|
||||
-- Init path.
|
||||
self.path=Path or "l10n/DEFAULT/"
|
||||
|
||||
-- Remove (back)slashes.
|
||||
local nmax=1000 ; local n=1
|
||||
while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do
|
||||
self.path=self.path:sub(1,#self.path-1)
|
||||
n=n+1
|
||||
end
|
||||
|
||||
-- Append slash.
|
||||
self.path=self.path.."/"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get path of the directory, where the sound file is located.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @return #string Path.
|
||||
function SOUNDFILE:GetPath()
|
||||
local path=self.path or "l10n/DEFAULT/"
|
||||
return path
|
||||
end
|
||||
|
||||
--- Set sound file name. This must be a .ogg or .mp3 file!
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string FileName Name of the file. Default is "Hello World.mp3".
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetFileName(FileName)
|
||||
--TODO: check that sound file is really .ogg or .mp3
|
||||
self.filename=FileName or "Hello World.mp3"
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the sound file name.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @return #string Name of the soud file. This does *not* include its path.
|
||||
function SOUNDFILE:GetFileName()
|
||||
return self.filename
|
||||
end
|
||||
|
||||
|
||||
--- Set duration how long it takes to play the sound file.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string Duration Duration in seconds. Default 3 seconds.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetDuration(Duration)
|
||||
self.duration=Duration or 3
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get duration how long the sound file takes to play.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @return #number Duration in seconds.
|
||||
function SOUNDFILE:GetDuration()
|
||||
return self.duration or 3
|
||||
end
|
||||
|
||||
--- Get the complete sound file name inlcuding its path.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @return #string Name of the sound file.
|
||||
function SOUNDFILE:GetName()
|
||||
local path=self:GetPath()
|
||||
local filename=self:GetFileName()
|
||||
local name=string.format("%s%s", path, filename)
|
||||
return name
|
||||
end
|
||||
|
||||
--- Set whether sound files should be played via SRS.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #boolean Switch If true or nil, use SRS. If false, use DCS transmission.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetPlayWithSRS(Switch)
|
||||
if Switch==true or Switch==nil then
|
||||
self.useSRS=true
|
||||
else
|
||||
self.useSRS=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- Text-To-Speech
|
||||
|
||||
--- @type SOUNDTEXT
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string text Text to speak.
|
||||
-- @field #number duration Duration in seconds.
|
||||
-- @field #string gender Gender: "male", "female".
|
||||
-- @field #string culture Culture, e.g. "en-GB".
|
||||
-- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Text-to-speech objects for other classes.
|
||||
--
|
||||
-- # The SOUNDTEXT Concept
|
||||
--
|
||||
-- A SOUNDTEXT object holds all necessary information to play a general text via SRS Simple-Text-To-Speech.
|
||||
--
|
||||
-- It can be created with the @{#SOUNDTEXT.New}(*Text*, *Duration*) function.
|
||||
--
|
||||
-- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object.
|
||||
--
|
||||
-- # Options
|
||||
--
|
||||
-- ## Gender
|
||||
--
|
||||
-- You can choose a gender ("male" or "femal") with the @{#SOUNDTEXT.SetGender}(*Gender*) function.
|
||||
-- Note that the gender voice needs to be installed on your windows machine for the used culture (see below).
|
||||
--
|
||||
-- ## Culture
|
||||
--
|
||||
-- You can choose a "culture" (accent) with the @{#SOUNDTEXT.SetCulture}(*Culture*) function, where the default (SRS) culture is "en-GB".
|
||||
--
|
||||
-- Other examples for culture are: "en-US" (US accent), "de-DE" (German), "it-IT" (Italian), "ru-RU" (Russian), "zh-CN" (Chinese).
|
||||
--
|
||||
-- Note that the chosen culture needs to be installed on your windows machine.
|
||||
--
|
||||
-- ## Specific Voice
|
||||
--
|
||||
-- You can use a specific voice for the transmission with the @{SOUNDTEXT.SetVoice}(*VoiceName*) function. Here are some examples
|
||||
--
|
||||
-- * Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain)
|
||||
-- * Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States)
|
||||
-- * Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States)
|
||||
-- * Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German
|
||||
-- * Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain)
|
||||
-- * Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French
|
||||
-- * Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy)
|
||||
-- * Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian
|
||||
-- * Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified)
|
||||
--
|
||||
-- Note that this must be installed on your windos machine. Also note that this overrides any culture and gender settings.
|
||||
--
|
||||
-- @field #SOUNDTEXT
|
||||
SOUNDTEXT={
|
||||
ClassName = "SOUNDTEXT",
|
||||
}
|
||||
|
||||
--- Constructor to create a new SOUNDTEXT object.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Text The text to speak.
|
||||
-- @param #number Duration Duration in seconds, how long it takes to play the text. Default is 3 seconds.
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:New(Text, Duration)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDTEXT
|
||||
|
||||
self:SetText(Text)
|
||||
self:SetDuration(Duration or STTS.getSpeechTime(Text))
|
||||
--self:SetGender()
|
||||
--self:SetCulture()
|
||||
|
||||
-- Debug info:
|
||||
self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set text.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Text Text to speak. Default "Hello World!".
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetText(Text)
|
||||
|
||||
self.text=Text or "Hello World!"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set duration, how long it takes to speak the text.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #number Duration Duration in seconds. Default 3 seconds.
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetDuration(Duration)
|
||||
|
||||
self.duration=Duration or 3
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set gender.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Gender Gender: "male" or "female" (default).
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetGender(Gender)
|
||||
|
||||
self.gender=Gender or "female"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set TTS culture - local for the voice.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Culture TTS culture. Default "en-GB".
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetCulture(Culture)
|
||||
|
||||
self.culture=Culture or "en-GB"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set to use a specific voice name.
|
||||
-- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see [google voices](https://cloud.google.com/text-to-speech/docs/voices).
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string VoiceName Voice name. Note that this will overrule `Gender` and `Culture`.
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetVoice(VoiceName)
|
||||
|
||||
self.voice=VoiceName
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
@ -1,4 +1,4 @@
|
||||
--- **Core** - Manage user sound.
|
||||
--- **Sound** - Manage user sound.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -16,7 +16,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.UserSound
|
||||
-- @module Sound.UserSound
|
||||
-- @image Core_Usersound.JPG
|
||||
|
||||
do -- UserSound
|
||||
@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
|
||||
self:SetAutoAcceptTasks( true )
|
||||
self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance )
|
||||
self:SetFlashStatus( false )
|
||||
self:SetMessageDuration(10)
|
||||
|
||||
self:HandleEvent( EVENTS.Birth,
|
||||
--- @param #COMMANDCENTER self
|
||||
@ -682,7 +683,7 @@ end
|
||||
-- @param #string Message The message text.
|
||||
function COMMANDCENTER:MessageToAll( Message )
|
||||
|
||||
self:GetPositionable():MessageToAll( Message, 20, self:GetName() )
|
||||
self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() )
|
||||
|
||||
end
|
||||
|
||||
@ -692,7 +693,7 @@ end
|
||||
-- @param Wrapper.Group#GROUP MessageGroup The group to receive the message.
|
||||
function COMMANDCENTER:MessageToGroup( Message, MessageGroup )
|
||||
|
||||
self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() )
|
||||
self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() )
|
||||
|
||||
end
|
||||
|
||||
@ -715,7 +716,7 @@ function COMMANDCENTER:MessageToCoalition( Message )
|
||||
local CCCoalition = self:GetPositionable():GetCoalition()
|
||||
--TODO: Fix coalition bug!
|
||||
|
||||
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() )
|
||||
self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() )
|
||||
|
||||
end
|
||||
|
||||
@ -795,9 +796,18 @@ end
|
||||
|
||||
--- Let the command center flash a report of the status of the subscribed task to a group.
|
||||
-- @param #COMMANDCENTER self
|
||||
-- @param Flash #boolean
|
||||
function COMMANDCENTER:SetFlashStatus( Flash )
|
||||
self:F()
|
||||
|
||||
self.FlashStatus = Flash or true
|
||||
|
||||
self.FlashStatus = Flash and true
|
||||
end
|
||||
|
||||
--- Duration a command center message is shown.
|
||||
-- @param #COMMANDCENTER self
|
||||
-- @param seconds #number
|
||||
function COMMANDCENTER:SetMessageDuration(seconds)
|
||||
self:F()
|
||||
|
||||
self.MessageDuration = 10 or seconds
|
||||
end
|
||||
|
||||
@ -881,6 +881,9 @@ do -- Group Assignment
|
||||
local Mission = self:GetMission()
|
||||
local CommandCenter = Mission:GetCommandCenter()
|
||||
CommandCenter:SetMenu()
|
||||
|
||||
self:MenuFlashTaskStatus( TaskGroup, false ) -- stop message flashing, if any #1383 & #1312
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -1252,7 +1255,7 @@ function TASK:MenuFlashTaskStatus( TaskGroup, Flash )
|
||||
self.FlashTaskStatus = Flash
|
||||
|
||||
if self.FlashTaskStatus then
|
||||
self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60 )
|
||||
self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60) --Issue #1383 never ending flash messages
|
||||
else
|
||||
if self.FlashTaskScheduler then
|
||||
self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID )
|
||||
|
||||
@ -344,7 +344,9 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task )
|
||||
Text = DataText
|
||||
else
|
||||
local DataText = Data.Data -- #string
|
||||
Text = DataText
|
||||
if type(DataText) == "string" then --Issue #1388 - don't just assume this is a string
|
||||
Text = DataText
|
||||
end
|
||||
end
|
||||
|
||||
if Line < math.floor( Data.Order / 10 ) then
|
||||
|
||||
@ -253,7 +253,12 @@ do -- TASK_A2A_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function TASK_A2A_DISPATCHER:SetSendMessages( onoff )
|
||||
self.FlashNewTask = onoff
|
||||
end
|
||||
|
||||
--- Creates an INTERCEPT task when there are targets for it.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
@ -610,7 +615,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
local TaskText = TaskReport:Text(", ")
|
||||
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and ( not not self.FlashNewTask) then
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and (self.FlashNewTask) then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
|
||||
end
|
||||
end
|
||||
|
||||
@ -451,6 +451,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
|
||||
self.Detection = Detection
|
||||
self.Mission = Mission
|
||||
self.FlashNewTask = true --set to false to suppress flash messages
|
||||
|
||||
self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } )
|
||||
|
||||
@ -471,6 +472,12 @@ do -- TASK_A2G_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function TASK_A2G_DISPATCHER:SetSendMessages( onoff )
|
||||
self.FlashNewTask = onoff
|
||||
end
|
||||
|
||||
--- Creates a SEAD task when there are targets for it.
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
@ -616,7 +623,9 @@ do -- TASK_A2G_DISPATCHER
|
||||
if not DetectedItem then
|
||||
local TaskText = Task:GetName()
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup )
|
||||
if self.FlashNewTask then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup )
|
||||
end
|
||||
end
|
||||
Task = self:RemoveTask( TaskIndex )
|
||||
end
|
||||
@ -686,7 +695,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- Now we send to each group the changes, if any.
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
local TargetsText = TargetsReport:Text(", ")
|
||||
if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" then
|
||||
if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" and self.FlashNewTask then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "Task %s has change of targets:\n %s", Task:GetName(), TargetsText ), TaskGroup )
|
||||
end
|
||||
end
|
||||
@ -744,6 +753,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed...
|
||||
if TargetSetUnit then
|
||||
Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit )
|
||||
DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
|
||||
@ -752,6 +762,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed...
|
||||
if TargetSetUnit then
|
||||
Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit )
|
||||
DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
|
||||
@ -760,6 +771,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed...
|
||||
if TargetSetUnit then
|
||||
Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit )
|
||||
DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
end
|
||||
@ -805,7 +817,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
|
||||
local TaskText = TaskReport:Text(", ")
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and self.FlashNewTask then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
|
||||
end
|
||||
end
|
||||
@ -815,4 +827,4 @@ do -- TASK_A2G_DISPATCHER
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -310,4 +310,53 @@ ENUMS.Morse.N7="- - * * *"
|
||||
ENUMS.Morse.N8="- - - * *"
|
||||
ENUMS.Morse.N9="- - - - *"
|
||||
ENUMS.Morse.N0="- - - - -"
|
||||
ENUMS.Morse[" "]=" "
|
||||
ENUMS.Morse[" "]=" "
|
||||
|
||||
--- ISO (639-1) 2-letter Language Codes. See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
|
||||
--
|
||||
-- @type ENUMS.ISOLang
|
||||
ENUMS.ISOLang =
|
||||
{
|
||||
Arabic = 'AR',
|
||||
Chinese = 'ZH',
|
||||
English = 'EN',
|
||||
French = 'FR',
|
||||
German = 'DE',
|
||||
Russian = 'RU',
|
||||
Spanish = 'ES',
|
||||
Japanese = 'JA',
|
||||
Italian = 'IT',
|
||||
}
|
||||
|
||||
--- Phonetic Alphabet (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/NATO_phonetic_alphabet).
|
||||
--
|
||||
-- @type ENUMS.Phonetic
|
||||
ENUMS.Phonetic =
|
||||
{
|
||||
A = 'Alpha',
|
||||
B = 'Bravo',
|
||||
C = 'Charlie',
|
||||
D = 'Delta',
|
||||
E = 'Echo',
|
||||
F = 'Foxtrot',
|
||||
G = 'Golf',
|
||||
H = 'Hotel',
|
||||
I = 'India',
|
||||
J = 'Juliett',
|
||||
K = 'Kilo',
|
||||
L = 'Lima',
|
||||
M = 'Mike',
|
||||
N = 'November',
|
||||
O = 'Oscar',
|
||||
P = 'Papa',
|
||||
Q = 'Quebec',
|
||||
R = 'Romeo',
|
||||
S = 'Sierra',
|
||||
T = 'Tango',
|
||||
U = 'Uniform',
|
||||
V = 'Victor',
|
||||
W = 'Whiskey',
|
||||
X = 'Xray',
|
||||
Y = 'Yankee',
|
||||
Z = 'Zulu',
|
||||
}
|
||||
256
Moose Development/Moose/Utilities/STTS.lua
Normal file
256
Moose Development/Moose/Utilities/STTS.lua
Normal file
@ -0,0 +1,256 @@
|
||||
--- **Utilities** DCS Simple Text-To-Speech (STTS).
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @module Utils.STTS
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world)
|
||||
-- @type STTS
|
||||
-- @field #string DIRECTORY Path of the SRS directory.
|
||||
|
||||
--- Simple Text-To-Speech
|
||||
--
|
||||
-- Version 0.4 - Compatible with SRS version 1.9.6.0+
|
||||
--
|
||||
-- # DCS Modification Required
|
||||
--
|
||||
-- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitisation.
|
||||
-- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)"
|
||||
-- Do this without DCS running to allow mission scripts to use os functions.
|
||||
--
|
||||
-- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE*
|
||||
--
|
||||
-- # USAGE:
|
||||
--
|
||||
-- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialise it
|
||||
-- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission.
|
||||
-- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts.
|
||||
--
|
||||
-- Example calls:
|
||||
--
|
||||
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2)
|
||||
--
|
||||
-- Arguments in order are:
|
||||
--
|
||||
-- * Message to say, make sure not to use a newline (\n) !
|
||||
-- * Frequency in MHz
|
||||
-- * Modulation - AM/FM
|
||||
-- * Volume - 1.0 max, 0.5 half
|
||||
-- * Name of the transmitter - ATC, RockFM etc
|
||||
-- * Coalition - 0 spectator, 1 red 2 blue
|
||||
-- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed
|
||||
-- * OPTIONAL - Speed -10 to +10
|
||||
-- * OPTIONAL - Gender male, female or neuter
|
||||
-- * OPTIONAL - Culture - en-US, en-GB etc
|
||||
-- * OPTIONAL - Voice - a specfic voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line
|
||||
-- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly
|
||||
--
|
||||
--
|
||||
-- ## Example
|
||||
--
|
||||
-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only
|
||||
--
|
||||
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB")
|
||||
--
|
||||
-- ## Example
|
||||
--
|
||||
--This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT"
|
||||
--
|
||||
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB")
|
||||
--
|
||||
-- Arguments in order are:
|
||||
--
|
||||
-- * FULL path to the MP3 OR OGG to play
|
||||
-- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations
|
||||
-- * Modulation - AM/FM - to use multiple
|
||||
-- * Volume - 1.0 max, 0.5 half
|
||||
-- * Name of the transmitter - ATC, RockFM etc
|
||||
-- * Coalition - 0 spectator, 1 red 2 blue
|
||||
--
|
||||
-- ## Example
|
||||
--
|
||||
-- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only
|
||||
--
|
||||
-- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0)
|
||||
--
|
||||
-- @field #STTS
|
||||
STTS={
|
||||
ClassName="STTS",
|
||||
DIRECTORY="",
|
||||
SRS_PORT=5002,
|
||||
GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json",
|
||||
EXECUTABLE="DCS-SR-ExternalAudio.exe",
|
||||
}
|
||||
|
||||
--- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER
|
||||
STTS.DIRECTORY = "D:/DCS/_SRS"
|
||||
|
||||
--- LOCAL SRS PORT - DEFAULT IS 5002
|
||||
STTS.SRS_PORT = 5002
|
||||
|
||||
--- Google credentials file
|
||||
STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json"
|
||||
|
||||
--- DONT CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING
|
||||
STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe"
|
||||
|
||||
|
||||
--- Function for UUID.
|
||||
function STTS.uuid()
|
||||
local random = math.random
|
||||
local template ='yxxx-xxxxxxxxxxxx'
|
||||
return string.gsub(template, '[xy]', function (c)
|
||||
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
|
||||
return string.format('%x', v)
|
||||
end)
|
||||
end
|
||||
|
||||
--- Round a number.
|
||||
-- @param #number x Number.
|
||||
-- @param #number n Precision.
|
||||
function STTS.round(x, n)
|
||||
n = math.pow(10, n or 0)
|
||||
x = x * n
|
||||
if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end
|
||||
return x / n
|
||||
end
|
||||
|
||||
--- Function returns estimated speech time in seconds.
|
||||
-- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so
|
||||
--
|
||||
-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second
|
||||
--
|
||||
-- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function:
|
||||
--
|
||||
-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||
--
|
||||
function STTS.getSpeechTime(length,speed,isGoogle)
|
||||
|
||||
local maxRateRatio = 3
|
||||
|
||||
speed = speed or 1.0
|
||||
isGoogle = isGoogle or false
|
||||
|
||||
local speedFactor = 1.0
|
||||
if isGoogle then
|
||||
speedFactor = speed
|
||||
else
|
||||
if speed ~= 0 then
|
||||
speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1
|
||||
end
|
||||
if speed < 0 then
|
||||
speedFactor = 1/speedFactor
|
||||
end
|
||||
end
|
||||
|
||||
local wpm = math.ceil(100 * speedFactor)
|
||||
local cps = math.floor((wpm * 5)/60)
|
||||
|
||||
if type(length) == "string" then
|
||||
length = string.len(length)
|
||||
end
|
||||
|
||||
return math.ceil(length/cps)
|
||||
end
|
||||
|
||||
--- Text to speech function.
|
||||
function STTS.TextToSpeech(message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS)
|
||||
if os == nil or io == nil then
|
||||
env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ")
|
||||
return
|
||||
end
|
||||
|
||||
speed = speed or 1
|
||||
gender = gender or "female"
|
||||
culture = culture or ""
|
||||
voice = voice or ""
|
||||
coalition=coalition or "0"
|
||||
name=name or "ROBOT"
|
||||
volume=1
|
||||
speed=1
|
||||
|
||||
|
||||
message = message:gsub("\"","\\\"")
|
||||
|
||||
local cmd = string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name)
|
||||
|
||||
if voice ~= "" then
|
||||
cmd = cmd .. string.format(" -V \"%s\"",voice)
|
||||
else
|
||||
|
||||
if culture ~= "" then
|
||||
cmd = cmd .. string.format(" -l %s",culture)
|
||||
end
|
||||
|
||||
if gender ~= "" then
|
||||
cmd = cmd .. string.format(" -g %s",gender)
|
||||
end
|
||||
end
|
||||
|
||||
if googleTTS == true then
|
||||
cmd = cmd .. string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS)
|
||||
end
|
||||
|
||||
if speed ~= 1 then
|
||||
cmd = cmd .. string.format(" -s %s",speed)
|
||||
end
|
||||
|
||||
if volume ~= 1.0 then
|
||||
cmd = cmd .. string.format(" -v %s",volume)
|
||||
end
|
||||
|
||||
if point and type(point) == "table" and point.x then
|
||||
local lat, lon, alt = coord.LOtoLL(point)
|
||||
|
||||
lat = STTS.round(lat,4)
|
||||
lon = STTS.round(lon,4)
|
||||
alt = math.floor(alt)
|
||||
|
||||
cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt)
|
||||
end
|
||||
|
||||
cmd = cmd ..string.format(" -t \"%s\"",message)
|
||||
|
||||
if string.len(cmd) > 255 then
|
||||
local filename = os.getenv('TMP') .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat"
|
||||
local script = io.open(filename,"w+")
|
||||
script:write(cmd .. " && exit" )
|
||||
script:close()
|
||||
cmd = string.format("\"%s\"",filename)
|
||||
timer.scheduleFunction(os.remove, filename, timer.getTime() + 1)
|
||||
end
|
||||
|
||||
if string.len(cmd) > 255 then
|
||||
env.info("[DCS-STTS] - cmd string too long")
|
||||
env.info("[DCS-STTS] TextToSpeech Command :\n" .. cmd.."\n")
|
||||
end
|
||||
os.execute(cmd)
|
||||
|
||||
return STTS.getSpeechTime(message,speed,googleTTS)
|
||||
end
|
||||
|
||||
--- Play mp3 function.
|
||||
-- @param #string pathToMP3 Path to the sound file.
|
||||
-- @param #string freqs Frequencies, e.g. "305, 256".
|
||||
-- @param #string modulations Modulations, e.g. "AM, FM".
|
||||
-- @param #string volume Volume, e.g. "0.5".
|
||||
function STTS.PlayMP3(pathToMP3, freqs, modulations, volume, name, coalition, point)
|
||||
|
||||
local cmd = string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h",
|
||||
STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1")
|
||||
|
||||
if point and type(point) == "table" and point.x then
|
||||
local lat, lon, alt = coord.LOtoLL(point)
|
||||
|
||||
lat = STTS.round(lat,4)
|
||||
lon = STTS.round(lon,4)
|
||||
alt = math.floor(alt)
|
||||
|
||||
cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt)
|
||||
end
|
||||
|
||||
env.info("[DCS-STTS] MP3/OGG Command :\n" .. cmd.."\n")
|
||||
os.execute(cmd)
|
||||
|
||||
end
|
||||
612
Moose Development/Moose/Utilities/Templates.lua
Normal file
612
Moose Development/Moose/Utilities/Templates.lua
Normal file
@ -0,0 +1,612 @@
|
||||
--- **Utils** Templates
|
||||
--
|
||||
-- DCS unit templates
|
||||
--
|
||||
-- @module Utilities.Templates
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- TEMPLATE class.
|
||||
-- @type TEMPLATE
|
||||
-- @field #string ClassName Name of the class.
|
||||
|
||||
--- *Templates*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Get DCS templates from thin air.
|
||||
--
|
||||
-- # Ground Units
|
||||
--
|
||||
-- Ground units.
|
||||
--
|
||||
-- # Naval Units
|
||||
--
|
||||
-- Ships are not implemented yet.
|
||||
--
|
||||
-- # Aircraft
|
||||
--
|
||||
-- ## Airplanes
|
||||
--
|
||||
-- Airplanes are not implemented yet.
|
||||
--
|
||||
-- ## Helicopters
|
||||
--
|
||||
-- Helicopters are not implemented yet.
|
||||
--
|
||||
-- @field #TEMPLATE
|
||||
TEMPLATE = {
|
||||
ClassName = "TEMPLATE",
|
||||
Ground = {},
|
||||
Naval = {},
|
||||
Airplane = {},
|
||||
Helicopter = {},
|
||||
}
|
||||
|
||||
--- Ground unit type names.
|
||||
-- @type TEMPLATE.TypeGround
|
||||
-- @param #string InfantryAK
|
||||
TEMPLATE.TypeGround={
|
||||
InfantryAK="Infantry AK",
|
||||
ParatrooperAKS74="Paratrooper AKS-74",
|
||||
ParatrooperRPG16="Paratrooper RPG-16",
|
||||
SoldierWWIIUS="soldier_wwii_us",
|
||||
InfantryM248="Infantry M249",
|
||||
SoldierM4="Soldier M4",
|
||||
}
|
||||
|
||||
--- Naval unit type names.
|
||||
-- @type TEMPLATE.TypeNaval
|
||||
-- @param #string Ticonderoga
|
||||
TEMPLATE.TypeNaval={
|
||||
Ticonderoga="TICONDEROG",
|
||||
}
|
||||
|
||||
--- Rotary wing unit type names.
|
||||
-- @type TEMPLATE.TypeAirplane
|
||||
-- @param #string A10C
|
||||
TEMPLATE.TypeAirplane={
|
||||
A10C="A-10C",
|
||||
}
|
||||
|
||||
--- Rotary wing unit type names.
|
||||
-- @type TEMPLATE.TypeHelicopter
|
||||
-- @param #string AH1W
|
||||
TEMPLATE.TypeHelicopter={
|
||||
AH1W="AH-1W",
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Ground Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get template for ground units.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 50 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4
|
||||
GroupName=GroupName or "Ground-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=0, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 50
|
||||
|
||||
|
||||
-- Get generic template.
|
||||
local template=UTILS.DeepCopy(TEMPLATE.GenericGround)
|
||||
|
||||
-- Set group name.
|
||||
template.name=GroupName
|
||||
|
||||
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
|
||||
template.CountryID=CountryID
|
||||
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
|
||||
template.CategoryID=Unit.Category.GROUND_UNIT
|
||||
|
||||
-- Set first unit.
|
||||
template.units[1].type=TypeName
|
||||
template.units[1].name=GroupName.."-1"
|
||||
|
||||
if Vec3 then
|
||||
TEMPLATE.SetPositionFromVec3(template, Vec3)
|
||||
end
|
||||
|
||||
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Naval Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get template for ground units.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE.GetNaval(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga
|
||||
GroupName=GroupName or "Naval-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=0, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 500
|
||||
|
||||
|
||||
-- Get generic template.
|
||||
local template=UTILS.DeepCopy(TEMPLATE.GenericNaval)
|
||||
|
||||
-- Set group name.
|
||||
template.name=GroupName
|
||||
|
||||
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
|
||||
template.CountryID=CountryID
|
||||
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
|
||||
template.CategoryID=Unit.Category.SHIP
|
||||
|
||||
-- Set first unit.
|
||||
template.units[1].type=TypeName
|
||||
template.units[1].name=GroupName.."-1"
|
||||
|
||||
if Vec3 then
|
||||
TEMPLATE.SetPositionFromVec3(template, Vec3)
|
||||
end
|
||||
|
||||
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Aircraft Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get template for fixed wing units.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE.GetAirplane(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName or TEMPLATE.TypeAirplane.A10C
|
||||
GroupName=GroupName or "Airplane-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=1000, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 100
|
||||
|
||||
local template=TEMPLATE._GetAircraft(true, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
--- Get template for fixed wing units.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE.GetHelicopter(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W
|
||||
GroupName=GroupName or "Helicopter-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=500, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 100
|
||||
|
||||
-- Limit unis to 4.
|
||||
Nunits=math.min(Nunits, 4)
|
||||
|
||||
local template=TEMPLATE._GetAircraft(false, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
|
||||
--- Get template for aircraft units.
|
||||
-- @param #boolean Airplane If true, this is a fixed wing. Else, rotary wing.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE._GetAircraft(Airplane, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName
|
||||
GroupName=GroupName or "Aircraft-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=0, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 100
|
||||
|
||||
-- Get generic template.
|
||||
local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft)
|
||||
|
||||
-- Set group name.
|
||||
template.name=GroupName
|
||||
|
||||
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
|
||||
template.CountryID=CountryID
|
||||
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
|
||||
if Airplane then
|
||||
template.CategoryID=Unit.Category.AIRPLANE
|
||||
else
|
||||
template.CategoryID=Unit.Category.HELICOPTER
|
||||
end
|
||||
|
||||
-- Set first unit.
|
||||
template.units[1].type=TypeName
|
||||
template.units[1].name=GroupName.."-1"
|
||||
|
||||
-- Set position.
|
||||
if Vec3 then
|
||||
TEMPLATE.SetPositionFromVec3(template, Vec3)
|
||||
end
|
||||
|
||||
-- Set number of units.
|
||||
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set the position of the template.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group.
|
||||
function TEMPLATE.SetPositionFromVec2(Template, Vec2)
|
||||
|
||||
Template.x=Vec2.x
|
||||
Template.y=Vec2.y
|
||||
|
||||
for _,unit in pairs(Template.units) do
|
||||
unit.x=Vec2.x
|
||||
unit.y=Vec2.y
|
||||
end
|
||||
|
||||
Template.route.points[1].x=Vec2.x
|
||||
Template.route.points[1].y=Vec2.y
|
||||
Template.route.points[1].alt=0 --TODO: Use land height.
|
||||
|
||||
end
|
||||
|
||||
--- Set the position of the template.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param DCS#Vec3 Vec3 Position vector of the group.
|
||||
function TEMPLATE.SetPositionFromVec3(Template, Vec3)
|
||||
|
||||
local Vec2={x=Vec3.x, y=Vec3.z}
|
||||
|
||||
TEMPLATE.SetPositionFromVec2(Template, Vec2)
|
||||
|
||||
end
|
||||
|
||||
--- Set the position of the template.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param #number N Total number of units in the group.
|
||||
-- @param Core.Point#COORDINATE Coordinate Position of the first unit.
|
||||
-- @param #number Radius Radius in meters to randomly place the additional units.
|
||||
function TEMPLATE.SetUnits(Template, N, Coordinate, Radius)
|
||||
|
||||
local units=Template.units
|
||||
|
||||
local unit1=units[1]
|
||||
|
||||
local Vec3=Coordinate:GetVec3()
|
||||
|
||||
unit1.x=Vec3.x
|
||||
unit1.y=Vec3.z
|
||||
unit1.alt=Vec3.y
|
||||
|
||||
for i=2,N do
|
||||
units[i]=UTILS.DeepCopy(unit1)
|
||||
end
|
||||
|
||||
for i=1,N do
|
||||
local unit=units[i]
|
||||
unit.name=string.format("%s-%d", Template.name, i)
|
||||
if i>1 then
|
||||
local vec2=Coordinate:GetRandomCoordinateInRadius(Radius, 5):GetVec2()
|
||||
unit.x=vec2.x
|
||||
unit.y=vec2.y
|
||||
unit.alt=unit1.alt
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Set the position of the template.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param Wrapper.Airbase#AIRBASE AirBase The airbase where the aircraft are spawned.
|
||||
-- @param #table ParkingSpots List of parking spot IDs. Every unit needs one!
|
||||
-- @param #boolean EngineOn If true, aircraft are spawned hot.
|
||||
function TEMPLATE.SetAirbase(Template, AirBase, ParkingSpots, EngineOn)
|
||||
|
||||
-- Airbase ID.
|
||||
local AirbaseID=AirBase:GetID()
|
||||
|
||||
-- Spawn point.
|
||||
local point=Template.route.points[1]
|
||||
|
||||
-- Set ID.
|
||||
if AirBase:IsAirdrome() then
|
||||
point.airdromeId=AirbaseID
|
||||
else
|
||||
point.helipadId=AirbaseID
|
||||
point.linkUnit=AirbaseID
|
||||
end
|
||||
|
||||
if EngineOn then
|
||||
point.action=COORDINATE.WaypointAction.FromParkingAreaHot
|
||||
point.type=COORDINATE.WaypointType.TakeOffParkingHot
|
||||
else
|
||||
point.action=COORDINATE.WaypointAction.FromParkingArea
|
||||
point.type=COORDINATE.WaypointType.TakeOffParking
|
||||
end
|
||||
|
||||
for i,unit in ipairs(Template.units) do
|
||||
unit.parking_id=ParkingSpots[i]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Add a waypoint.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param #table Waypoint Waypoint table.
|
||||
function TEMPLATE.AddWaypoint(Template, Waypoint)
|
||||
|
||||
table.insert(Template.route.points, Waypoint)
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Generic Ground Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE.GenericGround=
|
||||
{
|
||||
["visible"] = false,
|
||||
["tasks"] = {}, -- end of ["tasks"]
|
||||
["uncontrollable"] = false,
|
||||
["task"] = "Ground Nothing",
|
||||
["route"] =
|
||||
{
|
||||
["spans"] = {}, -- end of ["spans"]
|
||||
["points"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["alt"] = 0,
|
||||
["type"] = "Turning Point",
|
||||
["ETA"] = 0,
|
||||
["alt_type"] = "BARO",
|
||||
["formation_template"] = "",
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["ETA_locked"] = true,
|
||||
["speed"] = 0,
|
||||
["action"] = "Off Road",
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] =
|
||||
{
|
||||
["tasks"] =
|
||||
{
|
||||
}, -- end of ["tasks"]
|
||||
}, -- end of ["params"]
|
||||
}, -- end of ["task"]
|
||||
["speed_locked"] = true,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["points"]
|
||||
}, -- end of ["route"]
|
||||
["groupId"] = nil,
|
||||
["hidden"] = false,
|
||||
["units"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["transportable"] =
|
||||
{
|
||||
["randomTransportable"] = false,
|
||||
}, -- end of ["transportable"]
|
||||
["skill"] = "Average",
|
||||
["type"] = "Infantry AK",
|
||||
["unitId"] = nil,
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["name"] = "Infantry AK-47 Rus",
|
||||
["heading"] = 0,
|
||||
["playerCanDrive"] = false,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["units"]
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["name"] = "Infantry AK-47 Rus",
|
||||
["start_time"] = 0,
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Generic Ship Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE.GenericNaval=
|
||||
{
|
||||
["visible"] = false,
|
||||
["tasks"] = {}, -- end of ["tasks"]
|
||||
["uncontrollable"] = false,
|
||||
["route"] =
|
||||
{
|
||||
["points"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["alt"] = 0,
|
||||
["type"] = "Turning Point",
|
||||
["ETA"] = 0,
|
||||
["alt_type"] = "BARO",
|
||||
["formation_template"] = "",
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["ETA_locked"] = true,
|
||||
["speed"] = 0,
|
||||
["action"] = "Turning Point",
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] =
|
||||
{
|
||||
["tasks"] =
|
||||
{
|
||||
}, -- end of ["tasks"]
|
||||
}, -- end of ["params"]
|
||||
}, -- end of ["task"]
|
||||
["speed_locked"] = true,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["points"]
|
||||
}, -- end of ["route"]
|
||||
["groupId"] = nil,
|
||||
["hidden"] = false,
|
||||
["units"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["transportable"] =
|
||||
{
|
||||
["randomTransportable"] = false,
|
||||
}, -- end of ["transportable"]
|
||||
["skill"] = "Average",
|
||||
["type"] = "TICONDEROG",
|
||||
["unitId"] = nil,
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["name"] = "Naval-1-1",
|
||||
["heading"] = 0,
|
||||
["modulation"] = 0,
|
||||
["frequency"] = 127500000,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["units"]
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["name"] = "Naval-1",
|
||||
["start_time"] = 0,
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Generic Aircraft Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE.GenericAircraft=
|
||||
{
|
||||
["groupId"] = nil,
|
||||
["name"] = "Rotary-1",
|
||||
["uncontrolled"] = false,
|
||||
["hidden"] = false,
|
||||
["task"] = "Nothing",
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["start_time"] = 0,
|
||||
["communication"] = true,
|
||||
["radioSet"] = false,
|
||||
["frequency"] = 127.5,
|
||||
["modulation"] = 0,
|
||||
["taskSelected"] = true,
|
||||
["tasks"] = {}, -- end of ["tasks"]
|
||||
["route"] =
|
||||
{
|
||||
["points"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["alt"] = 1000,
|
||||
["alt_type"] = "BARO",
|
||||
["action"] = "Turning Point",
|
||||
["type"] = "Turning Point",
|
||||
["airdromeId"] = nil,
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] =
|
||||
{
|
||||
["tasks"] = {}, -- end of ["tasks"]
|
||||
}, -- end of ["params"]
|
||||
}, -- end of ["task"]
|
||||
["ETA"] = 0,
|
||||
["ETA_locked"] = true,
|
||||
["speed"] = 100,
|
||||
["speed_locked"] = true,
|
||||
["formation_template"] = "",
|
||||
}, -- end of [1]
|
||||
}, -- end of ["points"]
|
||||
}, -- end of ["route"]
|
||||
["units"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["name"] = "Rotary-1-1",
|
||||
["unitId"] = nil,
|
||||
["type"] = "AH-1W",
|
||||
["onboard_num"] = "050",
|
||||
["livery_id"] = "USA X Black",
|
||||
["skill"] = "High",
|
||||
["ropeLength"] = 15,
|
||||
["speed"] = 0,
|
||||
["x"] = 0,
|
||||
["y"] = 0,
|
||||
["alt"] = 10,
|
||||
["alt_type"] = "BARO",
|
||||
["heading"] = 0,
|
||||
["psi"] = 0,
|
||||
["parking"] = nil,
|
||||
["parking_id"] = nil,
|
||||
["payload"] =
|
||||
{
|
||||
["pylons"] = {}, -- end of ["pylons"]
|
||||
["fuel"] = "1250.0",
|
||||
["flare"] = 30,
|
||||
["chaff"] = 30,
|
||||
["gun"] = 100,
|
||||
}, -- end of ["payload"]
|
||||
["callsign"] =
|
||||
{
|
||||
[1] = 2,
|
||||
[2] = 1,
|
||||
[3] = 1,
|
||||
["name"] = "Springfield11",
|
||||
}, -- end of ["callsign"]
|
||||
}, -- end of [1]
|
||||
}, -- end of ["units"]
|
||||
}
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,8 @@
|
||||
-- @field #table parking Parking spot data.
|
||||
-- @field #table parkingByID Parking spot data table with ID as key.
|
||||
-- @field #number activerwyno Active runway number (forced).
|
||||
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
|
||||
-- @field #table parkingBlacklist List of parking spot terminal IDs **not** considered for spawning.
|
||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||
|
||||
--- Wrapper class to handle the DCS Airbase objects:
|
||||
@ -72,7 +74,7 @@ AIRBASE = {
|
||||
|
||||
--- Enumeration to identify the airbases in the Caucasus region.
|
||||
--
|
||||
-- These are all airbases of Caucasus:
|
||||
-- Airbases of the Caucasus map:
|
||||
--
|
||||
-- * AIRBASE.Caucasus.Gelendzhik
|
||||
-- * AIRBASE.Caucasus.Krasnodar_Pashkovsky
|
||||
@ -121,7 +123,7 @@ AIRBASE.Caucasus = {
|
||||
["Beslan"] = "Beslan",
|
||||
}
|
||||
|
||||
--- These are all airbases of Nevada:
|
||||
--- Airbases of the Nevada map:
|
||||
--
|
||||
-- * AIRBASE.Nevada.Creech_AFB
|
||||
-- * AIRBASE.Nevada.Groom_Lake_AFB
|
||||
@ -140,6 +142,7 @@ AIRBASE.Caucasus = {
|
||||
-- * AIRBASE.Nevada.Pahute_Mesa_Airstrip
|
||||
-- * AIRBASE.Nevada.Tonopah_Airport
|
||||
-- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield
|
||||
--
|
||||
-- @field Nevada
|
||||
AIRBASE.Nevada = {
|
||||
["Creech_AFB"] = "Creech AFB",
|
||||
@ -161,7 +164,7 @@ AIRBASE.Nevada = {
|
||||
["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield",
|
||||
}
|
||||
|
||||
--- These are all airbases of Normandy:
|
||||
--- Airbases of the Normandy map:
|
||||
--
|
||||
-- * AIRBASE.Normandy.Saint_Pierre_du_Mont
|
||||
-- * AIRBASE.Normandy.Lignerolles
|
||||
@ -194,6 +197,7 @@ AIRBASE.Nevada = {
|
||||
-- * AIRBASE.Normandy.Funtington
|
||||
-- * AIRBASE.Normandy.Tangmere
|
||||
-- * AIRBASE.Normandy.Ford_AF
|
||||
--
|
||||
-- @field Normandy
|
||||
AIRBASE.Normandy = {
|
||||
["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont",
|
||||
@ -236,7 +240,7 @@ AIRBASE.Normandy = {
|
||||
["Conches"] = "Conches",
|
||||
}
|
||||
|
||||
--- These are all airbases of the Persion Gulf Map:
|
||||
--- Airbases of the Persion Gulf Map:
|
||||
--
|
||||
-- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport
|
||||
-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport
|
||||
@ -267,40 +271,41 @@ AIRBASE.Normandy = {
|
||||
-- * AIRBASE.PersianGulf.Sirri_Island
|
||||
-- * AIRBASE.PersianGulf.Tunb_Island_AFB
|
||||
-- * AIRBASE.PersianGulf.Tunb_Kochak
|
||||
--
|
||||
-- @field PersianGulf
|
||||
AIRBASE.PersianGulf = {
|
||||
["Abu_Dhabi_International_Airport"] = "Abu Dhabi International Airport",
|
||||
["Abu_Musa_Island_Airport"] = "Abu Musa Island Airport",
|
||||
["Al_Ain_International_Airport"] = "Al Ain International Airport",
|
||||
["Al_Bateen_Airport"] = "Al-Bateen Airport",
|
||||
["Al_Dhafra_AB"] = "Al Dhafra AB",
|
||||
["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl",
|
||||
["Abu_Musa_Island_Airport"] = "Abu Musa Island",
|
||||
["Al_Ain_International_Airport"] = "Al Ain Intl",
|
||||
["Al_Bateen_Airport"] = "Al-Bateen",
|
||||
["Al_Dhafra_AB"] = "Al Dhafra AFB",
|
||||
["Al_Maktoum_Intl"] = "Al Maktoum Intl",
|
||||
["Al_Minhad_AB"] = "Al Minhad AB",
|
||||
["Al_Minhad_AB"] = "Al Minhad AFB",
|
||||
["Bandar_Abbas_Intl"] = "Bandar Abbas Intl",
|
||||
["Bandar_Lengeh"] = "Bandar Lengeh",
|
||||
["Bandar_e_Jask_airfield"] = "Bandar-e-Jask airfield",
|
||||
["Bandar_e_Jask_airfield"] = "Bandar-e-Jask",
|
||||
["Dubai_Intl"] = "Dubai Intl",
|
||||
["Fujairah_Intl"] = "Fujairah Intl",
|
||||
["Havadarya"] = "Havadarya",
|
||||
["Jiroft_Airport"] = "Jiroft Airport",
|
||||
["Kerman_Airport"] = "Kerman Airport",
|
||||
["Jiroft_Airport"] = "Jiroft",
|
||||
["Kerman_Airport"] = "Kerman",
|
||||
["Khasab"] = "Khasab",
|
||||
["Kish_International_Airport"] = "Kish International Airport",
|
||||
["Lar_Airbase"] = "Lar Airbase",
|
||||
["Lavan_Island_Airport"] = "Lavan Island Airport",
|
||||
["Liwa_Airbase"] = "Liwa Airbase",
|
||||
["Kish_International_Airport"] = "Kish Intl",
|
||||
["Lar_Airbase"] = "Lar",
|
||||
["Lavan_Island_Airport"] = "Lavan Island",
|
||||
["Liwa_Airbase"] = "Liwa AFB",
|
||||
["Qeshm_Island"] = "Qeshm Island",
|
||||
["Ras_Al_Khaimah"] = "Ras Al Khaimah",
|
||||
["Sas_Al_Nakheel_Airport"] = "Sas Al Nakheel Airport",
|
||||
["Ras_Al_Khaimah"] = "Ras Al Khaimah Intl",
|
||||
["Sas_Al_Nakheel_Airport"] = "Sas Al Nakheel",
|
||||
["Sharjah_Intl"] = "Sharjah Intl",
|
||||
["Shiraz_International_Airport"] = "Shiraz International Airport",
|
||||
["Shiraz_International_Airport"] = "Shiraz Intl",
|
||||
["Sir_Abu_Nuayr"] = "Sir Abu Nuayr",
|
||||
["Sirri_Island"] = "Sirri Island",
|
||||
["Tunb_Island_AFB"] = "Tunb Island AFB",
|
||||
["Tunb_Kochak"] = "Tunb Kochak",
|
||||
}
|
||||
|
||||
--- These are all airbases of the The Channel Map:
|
||||
--- Airbases of The Channel Map:
|
||||
--
|
||||
-- * AIRBASE.TheChannel.Abbeville_Drucat
|
||||
-- * AIRBASE.TheChannel.Merville_Calonne
|
||||
@ -325,7 +330,7 @@ AIRBASE.TheChannel = {
|
||||
["High_Halden"] = "High Halden",
|
||||
}
|
||||
|
||||
--- Airbases of Syria
|
||||
--- Airbases of the Syria map:
|
||||
--
|
||||
-- * AIRBASE.Syria.Kuweires
|
||||
-- * AIRBASE.Syria.Marj_Ruhayyil
|
||||
@ -335,33 +340,53 @@ AIRBASE.TheChannel = {
|
||||
-- * AIRBASE.Syria.Incirlik
|
||||
-- * AIRBASE.Syria.Damascus
|
||||
-- * AIRBASE.Syria.Bassel_Al_Assad
|
||||
-- * AIRBASE.Syria.Rosh_Pina
|
||||
-- * AIRBASE.Syria.Aleppo
|
||||
-- * AIRBASE.Syria.Qabr_as_Sitt
|
||||
-- * AIRBASE.Syria.Al_Qusayr
|
||||
-- * AIRBASE.Syria.Wujah_Al_Hajar
|
||||
-- * AIRBASE.Syria.Al_Dumayr
|
||||
-- * AIRBASE.Syria.Gazipasa
|
||||
-- * AIRBASE.Syria.Ru_Convoy_4
|
||||
-- * AIRBASE.Syria.Hatay
|
||||
-- * AIRBASE.Syria.Nicosia
|
||||
-- * AIRBASE.Syria.Pinarbashi
|
||||
-- * AIRBASE.Syria.Paphos
|
||||
-- * AIRBASE.Syria.Kingsfield
|
||||
-- * AIRBASE.Syria.Thalah
|
||||
-- * AIRBASE.Syria.Haifa
|
||||
-- * AIRBASE.Syria.Khalkhalah
|
||||
-- * AIRBASE.Syria.Megiddo
|
||||
-- * AIRBASE.Syria.Lakatamia
|
||||
-- * AIRBASE.Syria.Rayak
|
||||
-- * AIRBASE.Syria.Larnaca
|
||||
-- * AIRBASE.Syria.Mezzeh
|
||||
-- * AIRBASE.Syria.King_Hussein_Air_College
|
||||
-- * AIRBASE.Syria.Jirah
|
||||
-- * AIRBASE.Syria.Gecitkale
|
||||
-- * AIRBASE.Syria.Akrotiri
|
||||
-- * AIRBASE.Syria.Naqoura
|
||||
-- * AIRBASE.Syria.Gaziantep
|
||||
-- * AIRBASE.Syria.CVN_71
|
||||
-- * AIRBASE.Syria.Sayqal
|
||||
-- * AIRBASE.Syria.Tiyas
|
||||
-- * AIRBASE.Syria.Shayrat
|
||||
-- * AIRBASE.Syria.Taftanaz
|
||||
-- * AIRBASE.Syria.H4
|
||||
-- * AIRBASE.Syria.King_Hussein_Air_College
|
||||
-- * AIRBASE.Syria.Rene_Mouawad
|
||||
-- * AIRBASE.Syria.Jirah
|
||||
-- * AIRBASE.Syria.Ramat_David
|
||||
-- * AIRBASE.Syria.Qabr_as_Sitt
|
||||
-- * AIRBASE.Syria.Minakh
|
||||
-- * AIRBASE.Syria.Adana_Sakirpasa
|
||||
-- * AIRBASE.Syria.Marj_as_Sultan_South
|
||||
-- * AIRBASE.Syria.Hama
|
||||
-- * AIRBASE.Syria.Al_Qusayr
|
||||
-- * AIRBASE.Syria.Palmyra
|
||||
-- * AIRBASE.Syria.Hama
|
||||
-- * AIRBASE.Syria.Ercan
|
||||
-- * AIRBASE.Syria.Marj_as_Sultan_South
|
||||
-- * AIRBASE.Syria.Tabqa
|
||||
-- * AIRBASE.Syria.Beirut_Rafic_Hariri
|
||||
-- * AIRBASE.Syria.An_Nasiriyah
|
||||
-- * AIRBASE.Syria.Abu_al_Duhur
|
||||
--
|
||||
-- @field Syria
|
||||
--@field Syria
|
||||
AIRBASE.Syria={
|
||||
["Kuweires"]="Kuweires",
|
||||
["Marj_Ruhayyil"]="Marj Ruhayyil",
|
||||
@ -371,27 +396,46 @@ AIRBASE.Syria={
|
||||
["Incirlik"]="Incirlik",
|
||||
["Damascus"]="Damascus",
|
||||
["Bassel_Al_Assad"]="Bassel Al-Assad",
|
||||
["Rosh_Pina"]="Rosh Pina",
|
||||
["Aleppo"]="Aleppo",
|
||||
["Qabr_as_Sitt"]="Qabr as Sitt",
|
||||
["Al_Qusayr"]="Al Qusayr",
|
||||
["Wujah_Al_Hajar"]="Wujah Al Hajar",
|
||||
["Al_Dumayr"]="Al-Dumayr",
|
||||
["Gazipasa"]="Gazipasa",
|
||||
["Ru_Convoy_4"]="Ru Convoy-4",
|
||||
["Hatay"]="Hatay",
|
||||
["Nicosia"]="Nicosia",
|
||||
["Pinarbashi"]="Pinarbashi",
|
||||
["Paphos"]="Paphos",
|
||||
["Kingsfield"]="Kingsfield",
|
||||
["Thalah"]="Tha'lah",
|
||||
["Haifa"]="Haifa",
|
||||
["Khalkhalah"]="Khalkhalah",
|
||||
["Megiddo"]="Megiddo",
|
||||
["Lakatamia"]="Lakatamia",
|
||||
["Rayak"]="Rayak",
|
||||
["Larnaca"]="Larnaca",
|
||||
["Mezzeh"]="Mezzeh",
|
||||
["King_Hussein_Air_College"]="King Hussein Air College",
|
||||
["Jirah"]="Jirah",
|
||||
["Gecitkale"]="Gecitkale",
|
||||
["Akrotiri"]="Akrotiri",
|
||||
["Naqoura"]="Naqoura",
|
||||
["Gaziantep"]="Gaziantep",
|
||||
["Sayqal"]="Sayqal",
|
||||
["Tiyas"]="Tiyas",
|
||||
["Shayrat"]="Shayrat",
|
||||
["Taftanaz"]="Taftanaz",
|
||||
["H4"]="H4",
|
||||
["King_Hussein_Air_College"]="King Hussein Air College",
|
||||
["Rene_Mouawad"]="Rene Mouawad",
|
||||
["Jirah"]="Jirah",
|
||||
["Ramat_David"]="Ramat David",
|
||||
["Qabr_as_Sitt"]="Qabr as Sitt",
|
||||
["Minakh"]="Minakh",
|
||||
["Adana_Sakirpasa"]="Adana Sakirpasa",
|
||||
["Marj_as_Sultan_South"]="Marj as Sultan South",
|
||||
["Hama"]="Hama",
|
||||
["Al_Qusayr"]="Al Qusayr",
|
||||
["Palmyra"]="Palmyra",
|
||||
["Hama"]="Hama",
|
||||
["Ercan"]="Ercan",
|
||||
["Marj_as_Sultan_South"]="Marj as Sultan South",
|
||||
["Tabqa"]="Tabqa",
|
||||
["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri",
|
||||
["An_Nasiriyah"]="An Nasiriyah",
|
||||
@ -399,6 +443,27 @@ AIRBASE.Syria={
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Airbases of the Mariana Islands map:
|
||||
--
|
||||
-- * AIRBASE.MarianaIslands.Rota_Intl
|
||||
-- * AIRBASE.MarianaIslands.Andersen_AFB
|
||||
-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl
|
||||
-- * AIRBASE.MarianaIslands.Saipan_Intl
|
||||
-- * AIRBASE.MarianaIslands.Tinian_Intl
|
||||
-- * AIRBASE.MarianaIslands.Olf_Orote
|
||||
--
|
||||
--@field MarianaIslands
|
||||
AIRBASE.MarianaIslands={
|
||||
["Rota_Intl"]="Rota Intl",
|
||||
["Andersen_AFB"]="Andersen AFB",
|
||||
["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl",
|
||||
["Saipan_Intl"]="Saipan Intl",
|
||||
["Tinian_Intl"]="Tinian Intl",
|
||||
["Olf_Orote"]="Olf Orote",
|
||||
}
|
||||
|
||||
|
||||
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
|
||||
-- @type AIRBASE.ParkingSpot
|
||||
-- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot.
|
||||
@ -471,6 +536,9 @@ function AIRBASE:Register(AirbaseName)
|
||||
|
||||
-- Get descriptors.
|
||||
self.descriptors=self:GetDesc()
|
||||
|
||||
-- Debug info.
|
||||
--self:I({airbase=AirbaseName, descriptors=self.descriptors})
|
||||
|
||||
-- Category.
|
||||
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
|
||||
@ -482,12 +550,21 @@ function AIRBASE:Register(AirbaseName)
|
||||
self.isHelipad=true
|
||||
elseif self.category==Airbase.Category.SHIP then
|
||||
self.isShip=true
|
||||
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
|
||||
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
|
||||
self.isHelipad=true
|
||||
self.isShip=false
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
_DATABASE:AddStatic(AirbaseName)
|
||||
end
|
||||
else
|
||||
self:E("ERROR: Unknown airbase category!")
|
||||
end
|
||||
|
||||
-- Init parking spots.
|
||||
self:_InitParkingSpots()
|
||||
|
||||
-- Get 2D position vector.
|
||||
local vec2=self:GetVec2()
|
||||
|
||||
-- Init coordinate.
|
||||
@ -597,7 +674,7 @@ end
|
||||
|
||||
--- Get all airbase names of the current map. This includes ships and FARPS.
|
||||
-- @param DCS#Coalition coalition (Optional) Return only airbases belonging to the specified coalition. By default, all airbases of the map are returned.
|
||||
-- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP
|
||||
-- @param #number category (Optional) Return only airbases of a certain category, e.g. `Airbase.Category.HELIPAD`.
|
||||
-- @return #table Table containing all airbase names of the current map.
|
||||
function AIRBASE.GetAllAirbaseNames(coalition, category)
|
||||
|
||||
@ -652,6 +729,54 @@ function AIRBASE:GetID(unique)
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Set parking spot whitelist. Only these spots will be considered for spawning.
|
||||
-- Black listed spots overrule white listed spots.
|
||||
-- **NOTE** that terminal IDs are not necessarily the same as those displayed in the mission editor!
|
||||
-- @param #AIRBASE self
|
||||
-- @param #table TerminalIdBlacklist Table of white listed terminal IDs.
|
||||
-- @return #AIRBASE self
|
||||
-- @usage AIRBASE:FindByName("Batumi"):SetParkingSpotWhitelist({2, 3, 4}) --Only allow terminal IDs 2, 3, 4
|
||||
function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist)
|
||||
|
||||
if TerminalIdWhitelist==nil then
|
||||
self.parkingWhitelist={}
|
||||
return self
|
||||
end
|
||||
|
||||
-- Ensure we got a table.
|
||||
if type(TerminalIdWhitelist)~="table" then
|
||||
TerminalIdWhitelist={TerminalIdWhitelist}
|
||||
end
|
||||
|
||||
self.parkingWhitelist=TerminalIdWhitelist
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set parking spot blacklist. These parking spots will *not* be used for spawning.
|
||||
-- Black listed spots overrule white listed spots.
|
||||
-- **NOTE** that terminal IDs are not necessarily the same as those displayed in the mission editor!
|
||||
-- @param #AIRBASE self
|
||||
-- @param #table TerminalIdBlacklist Table of black listed terminal IDs.
|
||||
-- @return #AIRBASE self
|
||||
-- @usage AIRBASE:FindByName("Batumi"):SetParkingSpotBlacklist({2, 3, 4}) --Forbit terminal IDs 2, 3, 4
|
||||
function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist)
|
||||
|
||||
if TerminalIdBlacklist==nil then
|
||||
self.parkingBlacklist={}
|
||||
return self
|
||||
end
|
||||
|
||||
-- Ensure we got a table.
|
||||
if type(TerminalIdBlacklist)~="table" then
|
||||
TerminalIdBlacklist={TerminalIdBlacklist}
|
||||
end
|
||||
|
||||
self.parkingBlacklist=TerminalIdBlacklist
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get category of airbase.
|
||||
-- @param #AIRBASE self
|
||||
@ -836,25 +961,27 @@ function AIRBASE:_InitParkingSpots()
|
||||
-- Put coordinates of parking spots into table.
|
||||
for _,spot in pairs(parkingdata) do
|
||||
|
||||
-- New parking spot.
|
||||
local park={} --#AIRBASE.ParkingSpot
|
||||
park.Vec3=spot.vTerminalPos
|
||||
park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos)
|
||||
park.DistToRwy=spot.fDistToRW
|
||||
park.Free=nil
|
||||
park.TerminalID=spot.Term_Index
|
||||
park.TerminalID0=spot.Term_Index_0
|
||||
park.TerminalType=spot.Term_Type
|
||||
park.TOAC=spot.TO_AC
|
||||
-- New parking spot.
|
||||
local park={} --#AIRBASE.ParkingSpot
|
||||
park.Vec3=spot.vTerminalPos
|
||||
park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos)
|
||||
park.DistToRwy=spot.fDistToRW
|
||||
park.Free=nil
|
||||
park.TerminalID=spot.Term_Index
|
||||
park.TerminalID0=spot.Term_Index_0
|
||||
park.TerminalType=spot.Term_Type
|
||||
park.TOAC=spot.TO_AC
|
||||
|
||||
for _,terminalType in pairs(AIRBASE.TerminalType) do
|
||||
if self._CheckTerminalType(terminalType, park.TerminalType) then
|
||||
self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
|
||||
end
|
||||
self.NparkingTotal=self.NparkingTotal+1
|
||||
|
||||
for _,terminalType in pairs(AIRBASE.TerminalType) do
|
||||
if self._CheckTerminalType(terminalType, park.TerminalType) then
|
||||
self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
|
||||
end
|
||||
end
|
||||
|
||||
self.parkingByID[park.TerminalID]=park
|
||||
table.insert(self.parking, park)
|
||||
self.parkingByID[park.TerminalID]=park
|
||||
table.insert(self.parking, park)
|
||||
end
|
||||
|
||||
return self
|
||||
@ -898,10 +1025,19 @@ function AIRBASE:GetParkingSpotsTable(termtype)
|
||||
|
||||
local spot=self:_GetParkingSpotByID(_spot.Term_Index)
|
||||
|
||||
spot.Free=_isfree(_spot) -- updated
|
||||
spot.TOAC=_spot.TO_AC -- updated
|
||||
if spot then
|
||||
|
||||
spot.Free=_isfree(_spot) -- updated
|
||||
spot.TOAC=_spot.TO_AC -- updated
|
||||
|
||||
table.insert(spots, spot)
|
||||
|
||||
else
|
||||
|
||||
self:E(string.format("ERROR: Parking spot %s is nil!", tostring(_spot.Term_Index)))
|
||||
|
||||
end
|
||||
|
||||
table.insert(spots, spot)
|
||||
end
|
||||
|
||||
end
|
||||
@ -1082,9 +1218,8 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
|
||||
self:T2({_termid=_termid})
|
||||
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then
|
||||
-- Check terminal type and black/white listed parking spots.
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingLists(_termid) then
|
||||
|
||||
-- Very safe uses the DCS getParking() info to check if a spot is free. Unfortunately, the function returns free=false until the aircraft has actually taken-off.
|
||||
if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then
|
||||
@ -1186,6 +1321,39 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
||||
return validspots
|
||||
end
|
||||
|
||||
--- Check black and white lists.
|
||||
-- @param #AIRBASE self
|
||||
-- @param #number TerminalID Terminal ID to check.
|
||||
-- @return #boolean `true` if this is a valid spot.
|
||||
function AIRBASE:_CheckParkingLists(TerminalID)
|
||||
|
||||
-- First check the black list. If we find a match, this spot is forbidden!
|
||||
if self.parkingBlacklist and #self.parkingBlacklist>0 then
|
||||
for _,terminalID in pairs(self.parkingBlacklist or {}) do
|
||||
if terminalID==TerminalID then
|
||||
-- This is a invalid spot.
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Check if a whitelist was defined.
|
||||
if self.parkingWhitelist and #self.parkingWhitelist>0 then
|
||||
for _,terminalID in pairs(self.parkingWhitelist or {}) do
|
||||
if terminalID==TerminalID then
|
||||
-- This is a valid spot.
|
||||
return true
|
||||
end
|
||||
end
|
||||
-- No match ==> invalid spot
|
||||
return false
|
||||
end
|
||||
|
||||
-- Neither black nor white lists were defined or spot is not in black list.
|
||||
return true
|
||||
end
|
||||
|
||||
--- Helper function to check for the correct terminal type including "artificial" ones.
|
||||
-- @param #number Term_Type Termial type from getParking routine.
|
||||
-- @param #AIRBASE.TerminalType termtype Terminal type from AIRBASE.TerminalType enumerator.
|
||||
@ -1279,7 +1447,8 @@ function AIRBASE:GetRunwayData(magvar, mark)
|
||||
name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or
|
||||
name==AIRBASE.PersianGulf.Dubai_Intl or
|
||||
name==AIRBASE.PersianGulf.Shiraz_International_Airport or
|
||||
name==AIRBASE.PersianGulf.Kish_International_Airport then
|
||||
name==AIRBASE.PersianGulf.Kish_International_Airport or
|
||||
name==AIRBASE.MarianaIslands.Andersen_AFB then
|
||||
|
||||
-- 1-->4, 2-->3, 3-->2, 4-->1
|
||||
exception=1
|
||||
|
||||
@ -3,8 +3,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -52,13 +51,6 @@
|
||||
--
|
||||
-- @field #CLIENT
|
||||
CLIENT = {
|
||||
ONBOARDSIDE = {
|
||||
NONE = 0,
|
||||
LEFT = 1,
|
||||
RIGHT = 2,
|
||||
BACK = 3,
|
||||
FRONT = 4
|
||||
},
|
||||
ClassName = "CLIENT",
|
||||
ClientName = nil,
|
||||
ClientAlive = false,
|
||||
@ -66,27 +58,20 @@ CLIENT = {
|
||||
ClientBriefingShown = false,
|
||||
_Menus = {},
|
||||
_Tasks = {},
|
||||
Messages = {
|
||||
}
|
||||
Messages = {},
|
||||
Players = {},
|
||||
}
|
||||
|
||||
|
||||
--- Finds a CLIENT from the _DATABASE using the relevant DCS Unit.
|
||||
-- @param #CLIENT self
|
||||
-- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor.
|
||||
-- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client.
|
||||
-- @return #CLIENT
|
||||
-- @usage
|
||||
-- -- Create new Clients.
|
||||
-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' )
|
||||
-- Mission:AddGoal( DeploySA6TroopsGoal )
|
||||
--
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() )
|
||||
function CLIENT:Find( DCSUnit, Error )
|
||||
-- @param DCS#Unit DCSUnit The DCS unit of the client.
|
||||
-- @param #boolean Error Throw an error message.
|
||||
-- @return #CLIENT The CLIENT found in the _DATABASE.
|
||||
function CLIENT:Find(DCSUnit, Error)
|
||||
|
||||
local ClientName = DCSUnit:getName()
|
||||
|
||||
local ClientFound = _DATABASE:FindClient( ClientName )
|
||||
|
||||
if ClientFound then
|
||||
@ -117,11 +102,15 @@ end
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() )
|
||||
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() )
|
||||
function CLIENT:FindByName( ClientName, ClientBriefing, Error )
|
||||
|
||||
-- Client
|
||||
local ClientFound = _DATABASE:FindClient( ClientName )
|
||||
|
||||
if ClientFound then
|
||||
ClientFound:F( { ClientName, ClientBriefing } )
|
||||
ClientFound:AddBriefing( ClientBriefing )
|
||||
|
||||
ClientFound:AddBriefing(ClientBriefing)
|
||||
|
||||
ClientFound.MessageSwitch = true
|
||||
|
||||
return ClientFound
|
||||
@ -132,51 +121,105 @@ function CLIENT:FindByName( ClientName, ClientBriefing, Error )
|
||||
end
|
||||
end
|
||||
|
||||
function CLIENT:Register( ClientName )
|
||||
--- Transport defines that the Client is a Transport. Transports show cargo.
|
||||
-- @param #CLIENT self
|
||||
-- @param #string ClientName Name of the client unit.
|
||||
-- @return #CLIENT self
|
||||
function CLIENT:Register(ClientName)
|
||||
|
||||
local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) -- #CLIENT
|
||||
|
||||
self:F( ClientName )
|
||||
self.ClientName = ClientName
|
||||
self.MessageSwitch = true
|
||||
self.ClientAlive2 = false
|
||||
-- Inherit unit.
|
||||
local self = BASE:Inherit( self, UNIT:Register(ClientName )) -- #CLIENT
|
||||
|
||||
--self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 )
|
||||
self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5, 0.5 )
|
||||
self.AliveCheckScheduler:NoTrace()
|
||||
-- Set client name.
|
||||
self.ClientName = ClientName
|
||||
|
||||
-- Message switch.
|
||||
self.MessageSwitch = true
|
||||
|
||||
-- Alive2.
|
||||
self.ClientAlive2 = false
|
||||
|
||||
self:F( self )
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Transport defines that the Client is a Transport. Transports show cargo.
|
||||
-- @param #CLIENT self
|
||||
-- @return #CLIENT
|
||||
-- @return #CLIENT self
|
||||
function CLIENT:Transport()
|
||||
self:F()
|
||||
|
||||
self.ClientTransport = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- AddBriefing adds a briefing to a CLIENT when a player joins a mission.
|
||||
--- Adds a briefing to a CLIENT when a player joins a mission.
|
||||
-- @param #CLIENT self
|
||||
-- @param #string ClientBriefing is the text defining the Mission briefing.
|
||||
-- @return #CLIENT self
|
||||
function CLIENT:AddBriefing( ClientBriefing )
|
||||
self:F( ClientBriefing )
|
||||
|
||||
self.ClientBriefing = ClientBriefing
|
||||
self.ClientBriefingShown = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add player name.
|
||||
-- @param #CLIENT self
|
||||
-- @param #string PlayerName Name of the player.
|
||||
-- @return #CLIENT self
|
||||
function CLIENT:AddPlayer(PlayerName)
|
||||
|
||||
table.insert(self.Players, PlayerName)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get player name(s).
|
||||
-- @param #CLIENT self
|
||||
-- @return #table List of player names or an empty table `{}`.
|
||||
function CLIENT:GetPlayers()
|
||||
return self.Players
|
||||
end
|
||||
|
||||
--- Get name of player.
|
||||
-- @param #CLIENT self
|
||||
-- @return #string Player name or `nil`.
|
||||
function CLIENT:GetPlayer()
|
||||
if #self.Players>0 then
|
||||
return self.Players[1]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Remove player.
|
||||
-- @param #CLIENT self
|
||||
-- @param #string PlayerName Name of the player.
|
||||
-- @return #CLIENT self
|
||||
function CLIENT:RemovePlayer(PlayerName)
|
||||
|
||||
for i,playername in pairs(self.Players) do
|
||||
if PlayerName==playername then
|
||||
table.remove(self.Players, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove all players.
|
||||
-- @param #CLIENT self
|
||||
-- @return #CLIENT self
|
||||
function CLIENT:RemovePlayers()
|
||||
self.Players={}
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Show the briefing of a CLIENT.
|
||||
-- @param #CLIENT self
|
||||
-- @return #CLIENT self
|
||||
function CLIENT:ShowBriefing()
|
||||
self:F( { self.ClientName, self.ClientBriefingShown } )
|
||||
|
||||
if not self.ClientBriefingShown then
|
||||
self.ClientBriefingShown = true
|
||||
@ -204,8 +247,6 @@ function CLIENT:ShowMissionBriefing( MissionBriefing )
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Resets a CLIENT.
|
||||
-- @param #CLIENT self
|
||||
-- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client.
|
||||
@ -241,6 +282,7 @@ end
|
||||
--- Checks for a client alive event and calls a function on a continuous basis.
|
||||
-- @param #CLIENT self
|
||||
-- @param #function CallBackFunction Create a function that will be called when a player joins the slot.
|
||||
-- @param ... (Optional) Arguments for callback function as comma separated list.
|
||||
-- @return #CLIENT
|
||||
function CLIENT:Alive( CallBackFunction, ... )
|
||||
self:F()
|
||||
@ -248,6 +290,9 @@ function CLIENT:Alive( CallBackFunction, ... )
|
||||
self.ClientCallBack = CallBackFunction
|
||||
self.ClientParameters = arg
|
||||
|
||||
self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. self.ClientName }, 0.1, 5, 0.5 )
|
||||
self.AliveCheckScheduler:NoTrace()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@ -255,19 +300,29 @@ end
|
||||
function CLIENT:_AliveCheckScheduler( SchedulerName )
|
||||
self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } )
|
||||
|
||||
if self:IsAlive() then
|
||||
if self:IsAlive() then
|
||||
|
||||
if self.ClientAlive2 == false then
|
||||
|
||||
-- Show briefing.
|
||||
self:ShowBriefing()
|
||||
|
||||
-- Callback function.
|
||||
if self.ClientCallBack then
|
||||
self:T("Calling Callback function")
|
||||
self.ClientCallBack( self, unpack( self.ClientParameters ) )
|
||||
end
|
||||
|
||||
-- Alive.
|
||||
self.ClientAlive2 = true
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if self.ClientAlive2 == true then
|
||||
self.ClientAlive2 = false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return true
|
||||
@ -291,6 +346,7 @@ function CLIENT:GetDCSGroup()
|
||||
local ClientUnit = Unit.getByName( self.ClientName )
|
||||
|
||||
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) }
|
||||
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
self:T3( { "CoalitionData:", CoalitionData } )
|
||||
for UnitId, UnitData in pairs( CoalitionData ) do
|
||||
@ -299,10 +355,14 @@ function CLIENT:GetDCSGroup()
|
||||
|
||||
--self:F(self.ClientName)
|
||||
if ClientUnit then
|
||||
|
||||
local ClientGroup = ClientUnit:getGroup()
|
||||
|
||||
if ClientGroup then
|
||||
self:T3( "ClientGroup = " .. self.ClientName )
|
||||
if ClientGroup:isExist() and UnitData:getGroup():isExist() then
|
||||
|
||||
if ClientGroup:isExist() and UnitData:getGroup():isExist() then
|
||||
|
||||
if ClientGroup:getID() == UnitData:getGroup():getID() then
|
||||
self:T3( "Normal logic" )
|
||||
self:T3( self.ClientName .. " : group found!" )
|
||||
@ -310,15 +370,22 @@ function CLIENT:GetDCSGroup()
|
||||
self.ClientGroupName = ClientGroup:getName()
|
||||
return ClientGroup
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Now we need to resolve the bugs in DCS 1.5 ...
|
||||
-- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil)
|
||||
self:T3( "Bug 1.5 logic" )
|
||||
|
||||
local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate
|
||||
|
||||
self.ClientGroupID = ClientGroupTemplate.groupId
|
||||
|
||||
self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName
|
||||
|
||||
self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" )
|
||||
return ClientGroup
|
||||
|
||||
end
|
||||
-- else
|
||||
-- error( "Client " .. self.ClientName .. " not found!" )
|
||||
@ -343,22 +410,22 @@ function CLIENT:GetDCSGroup()
|
||||
end
|
||||
end
|
||||
|
||||
-- Nothing could be found :(
|
||||
self.ClientGroupID = nil
|
||||
self.ClientGroupUnit = nil
|
||||
self.ClientGroupName = nil
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- TODO: Check DCS#Group.ID
|
||||
--- Get the group ID of the client.
|
||||
-- @param #CLIENT self
|
||||
-- @return DCS#Group.ID
|
||||
-- @return #number DCS#Group ID.
|
||||
function CLIENT:GetClientGroupID()
|
||||
|
||||
local ClientGroup = self:GetDCSGroup()
|
||||
-- This updates the ID.
|
||||
self:GetDCSGroup()
|
||||
|
||||
--self:F( self.ClientGroupID ) -- Determined in GetDCSGroup()
|
||||
return self.ClientGroupID
|
||||
end
|
||||
|
||||
@ -368,26 +435,28 @@ end
|
||||
-- @return #string
|
||||
function CLIENT:GetClientGroupName()
|
||||
|
||||
local ClientGroup = self:GetDCSGroup()
|
||||
|
||||
self:T( self.ClientGroupName ) -- Determined in GetDCSGroup()
|
||||
-- This updates the group name.
|
||||
self:GetDCSGroup()
|
||||
|
||||
return self.ClientGroupName
|
||||
end
|
||||
|
||||
--- Returns the UNIT of the CLIENT.
|
||||
-- @param #CLIENT self
|
||||
-- @return Wrapper.Unit#UNIT
|
||||
-- @return Wrapper.Unit#UNIT The client UNIT or `nil`.
|
||||
function CLIENT:GetClientGroupUnit()
|
||||
self:F2()
|
||||
|
||||
local ClientDCSUnit = Unit.getByName( self.ClientName )
|
||||
|
||||
self:T( self.ClientDCSUnit )
|
||||
|
||||
if ClientDCSUnit and ClientDCSUnit:isExist() then
|
||||
local ClientUnit = _DATABASE:FindUnit( self.ClientName )
|
||||
self:T2( ClientUnit )
|
||||
local ClientUnit=_DATABASE:FindUnit( self.ClientName )
|
||||
return ClientUnit
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the DCSUnit of the CLIENT.
|
||||
|
||||
@ -129,6 +129,7 @@
|
||||
-- * @{#CONTROLLABLE.Route}(): Make the Controllable to follow a given route.
|
||||
-- * @{#CONTROLLABLE.RouteGroundTo}(): Make the GROUND Controllable to drive towards a specific coordinate.
|
||||
-- * @{#CONTROLLABLE.RouteAirTo}(): Make the AIR Controllable to fly towards a specific coordinate.
|
||||
-- * @{#CONTROLLABLE.RelocateGroundRandomInRadius}(): Relocate the GROUND controllable to a random point in a given radius.
|
||||
--
|
||||
-- # 5) Option methods
|
||||
--
|
||||
@ -173,6 +174,9 @@
|
||||
-- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat}
|
||||
-- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat}
|
||||
--
|
||||
-- ## 5.5) Air-2-Air missile attack range:
|
||||
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets .
|
||||
--
|
||||
-- @field #CONTROLLABLE
|
||||
CONTROLLABLE = {
|
||||
ClassName = "CONTROLLABLE",
|
||||
@ -500,7 +504,7 @@ function CONTROLLABLE:TaskCombo( DCSTasks )
|
||||
tasks = DCSTasks
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return DCSTaskCombo
|
||||
end
|
||||
|
||||
@ -798,12 +802,12 @@ end
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:CommandSetFrequency(Frequency, Modulation, Delay)
|
||||
|
||||
local CommandSetFrequency = {
|
||||
id = 'SetFrequency',
|
||||
params = {
|
||||
frequency = Frequency*1000000,
|
||||
modulation = Modulation or radio.modulation.AM,
|
||||
}
|
||||
local CommandSetFrequency = {
|
||||
id = 'SetFrequency',
|
||||
params = {
|
||||
frequency = Frequency*1000000,
|
||||
modulation = Modulation or radio.modulation.AM,
|
||||
}
|
||||
}
|
||||
|
||||
if Delay and Delay>0 then
|
||||
@ -877,7 +881,7 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At
|
||||
groupId = AttackGroup:GetID(),
|
||||
weaponType = WeaponType or 1073741822,
|
||||
expend = WeaponExpend or "Auto",
|
||||
attackQtyLimit = AttackQty and true or false,
|
||||
attackQtyLimit = AttackQty and true or false,
|
||||
attackQty = AttackQty or 1,
|
||||
directionEnabled = Direction and true or false,
|
||||
direction = Direction and math.rad(Direction) or 0,
|
||||
@ -917,7 +921,7 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta
|
||||
weaponType = WeaponType or 1073741822,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return DCSTask
|
||||
end
|
||||
|
||||
@ -1081,7 +1085,7 @@ function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration,
|
||||
-- Distribution
|
||||
--local distribution={}
|
||||
--distribution[id]=gids
|
||||
|
||||
|
||||
local groupID=self and self:GetID()
|
||||
|
||||
local DCSTask = {
|
||||
@ -1310,7 +1314,7 @@ function CONTROLLABLE:TaskLandAtVec2(Vec2, Duration)
|
||||
duration = Duration,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
return DCSTask
|
||||
end
|
||||
|
||||
@ -1424,16 +1428,22 @@ end
|
||||
-- @param DCS#Distance Radius The radius of the zone to deploy the fire at.
|
||||
-- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted).
|
||||
-- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag
|
||||
-- @param #number Altitude (Optional) Altitude in meters.
|
||||
-- @param #number ASL Altitude is above mean sea level. Default is above ground level.
|
||||
-- @return DCS#Task The DCS task structure.
|
||||
function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType )
|
||||
function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Altitude, ASL )
|
||||
|
||||
local DCSTask = {
|
||||
id = 'FireAtPoint',
|
||||
params = {
|
||||
point = Vec2,
|
||||
x=Vec2.x,
|
||||
y=Vec2.y,
|
||||
zoneRadius = Radius,
|
||||
radius = Radius,
|
||||
expendQty = 100, -- dummy value
|
||||
expendQtyEnabled = false,
|
||||
alt_type = ASL and 0 or 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -1442,10 +1452,16 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType )
|
||||
DCSTask.params.expendQtyEnabled = true
|
||||
end
|
||||
|
||||
if Altitude then
|
||||
DCSTask.params.altitude=Altitude
|
||||
end
|
||||
|
||||
if WeaponType then
|
||||
DCSTask.params.weaponType=WeaponType
|
||||
end
|
||||
|
||||
--self:I(DCSTask)
|
||||
|
||||
return DCSTask
|
||||
end
|
||||
|
||||
@ -1463,6 +1479,7 @@ end
|
||||
--- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction.
|
||||
-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC.
|
||||
-- If the task is assigned to the controllable lead unit will be a FAC.
|
||||
-- It's important to note that depending on the type of unit that is being assigned the task (AIR or GROUND), you must choose the correct type of callsign enumerator. For airborne controllables use CALLSIGN.Aircraft and for ground based use CALLSIGN.JTAC enumerators.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param Wrapper.Group#GROUP AttackGroup Target GROUP object.
|
||||
-- @param #number WeaponType Bitmask of weapon types, which are allowed to use.
|
||||
@ -1470,7 +1487,7 @@ end
|
||||
-- @param #boolean Datalink (Optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default.
|
||||
-- @param #number Frequency Frequency in MHz used to communicate with the FAC. Default 133 MHz.
|
||||
-- @param #number Modulation Modulation of radio for communication. Default 0=AM.
|
||||
-- @param #number CallsignName Callsign enumerator name of the FAC.
|
||||
-- @param #number CallsignName Callsign enumerator name of the FAC. (CALLSIGN.Aircraft.{name} for airborne controllables, CALLSIGN.JTACS.{name} for ground units)
|
||||
-- @param #number CallsignNumber Callsign number, e.g. Axeman-**1**.
|
||||
-- @return DCS#Task The DCS task structure.
|
||||
function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignName, CallsignNumber )
|
||||
@ -1792,7 +1809,7 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... )
|
||||
|
||||
-- DCS task.
|
||||
local DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript )))
|
||||
|
||||
|
||||
return DCSTask
|
||||
end
|
||||
|
||||
@ -1815,7 +1832,7 @@ end
|
||||
|
||||
do -- Patrol methods
|
||||
|
||||
--- (GROUND) Patrol iteratively using the waypoints the for the (parent) group.
|
||||
--- (GROUND) Patrol iteratively using the waypoints of the (parent) group.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @return #CONTROLLABLE
|
||||
function CONTROLLABLE:PatrolRoute()
|
||||
@ -1834,8 +1851,27 @@ do -- Patrol methods
|
||||
|
||||
-- Calculate the new Route.
|
||||
local FromCoord = PatrolGroup:GetCoordinate()
|
||||
local From = FromCoord:WaypointGround( 120 )
|
||||
|
||||
|
||||
-- test for submarine
|
||||
local depth = 0
|
||||
local IsSub = false
|
||||
if PatrolGroup:IsShip() then
|
||||
local navalvec3 = FromCoord:GetVec3()
|
||||
if navalvec3.y < 0 then
|
||||
depth = navalvec3.y
|
||||
IsSub = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local Waypoint = Waypoints[1]
|
||||
local Speed = Waypoint.speed or (20 / 3.6)
|
||||
local From = FromCoord:WaypointGround( Speed )
|
||||
|
||||
if IsSub then
|
||||
From = FromCoord:WaypointNaval( Speed, Waypoint.alt )
|
||||
end
|
||||
|
||||
table.insert( Waypoints, 1, From )
|
||||
|
||||
local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" )
|
||||
@ -1875,7 +1911,16 @@ do -- Patrol methods
|
||||
if ToWaypoint then
|
||||
FromWaypoint = ToWaypoint
|
||||
end
|
||||
|
||||
-- test for submarine
|
||||
local depth = 0
|
||||
local IsSub = false
|
||||
if PatrolGroup:IsShip() then
|
||||
local navalvec3 = FromCoord:GetVec3()
|
||||
if navalvec3.y < 0 then
|
||||
depth = navalvec3.y
|
||||
IsSub = true
|
||||
end
|
||||
end
|
||||
-- Loop until a waypoint has been found that is not the same as the current waypoint.
|
||||
-- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly
|
||||
-- what it is supposed to do, which is making groups drive around.
|
||||
@ -1890,9 +1935,13 @@ do -- Patrol methods
|
||||
local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } )
|
||||
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
|
||||
local Route = {}
|
||||
Route[#Route+1] = FromCoord:WaypointGround( 0 )
|
||||
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
|
||||
|
||||
if IsSub then
|
||||
Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth )
|
||||
Route[#Route+1] = ToCoord:WaypointNaval( Speed, Waypoint.alt )
|
||||
else
|
||||
Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation )
|
||||
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
|
||||
end
|
||||
|
||||
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint )
|
||||
|
||||
@ -1933,9 +1982,20 @@ do -- Patrol methods
|
||||
self:F( { PatrolGroup = PatrolGroup:GetName() } )
|
||||
|
||||
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
|
||||
|
||||
|
||||
-- Calculate the new Route.
|
||||
local FromCoord = PatrolGroup:GetCoordinate()
|
||||
|
||||
-- test for submarine
|
||||
local depth = 0
|
||||
local IsSub = false
|
||||
if PatrolGroup:IsShip() then
|
||||
local navalvec3 = FromCoord:GetVec3()
|
||||
if navalvec3.y < 0 then
|
||||
depth = navalvec3.y
|
||||
IsSub = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Select a random Zone and get the Coordinate of the new Zone.
|
||||
local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE
|
||||
@ -1943,9 +2003,13 @@ do -- Patrol methods
|
||||
|
||||
-- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task
|
||||
local Route = {}
|
||||
Route[#Route+1] = FromCoord:WaypointGround( 20 )
|
||||
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
|
||||
|
||||
if IsSub then
|
||||
Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth )
|
||||
Route[#Route+1] = ToCoord:WaypointNaval( Speed, depth )
|
||||
else
|
||||
Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation )
|
||||
Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation )
|
||||
end
|
||||
|
||||
local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation, DelayMin, DelayMax )
|
||||
|
||||
@ -1969,7 +2033,7 @@ function CONTROLLABLE:TaskRoute( Points )
|
||||
id = 'Mission',
|
||||
params = {
|
||||
airborne = self:IsAir(),
|
||||
route = {points = Points},
|
||||
route = {points = Points},
|
||||
},
|
||||
}
|
||||
|
||||
@ -2891,13 +2955,13 @@ end
|
||||
--- Set option for Rules of Engagement (ROE).
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE self
|
||||
-- @param #number ROEvalue ROE value. See ENUMS.ROE.
|
||||
-- @return Wrapper.Controllable#CONTROLLABLE self
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionROE(ROEvalue)
|
||||
|
||||
local DCSControllable = self:GetDCSObject()
|
||||
|
||||
|
||||
if DCSControllable then
|
||||
|
||||
|
||||
local Controller = self:_GetController()
|
||||
|
||||
if self:IsAir() then
|
||||
@ -2933,8 +2997,8 @@ function CONTROLLABLE:OptionROEHoldFirePossible()
|
||||
end
|
||||
|
||||
--- Weapons Hold: AI will hold fire under all circumstances.
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE self
|
||||
-- @return Wrapper.Controllable#CONTROLLABLE self
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionROEHoldFire()
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
@ -3461,13 +3525,13 @@ end
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionProhibitAfterburner(Prohibit)
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
|
||||
if Prohibit==nil then
|
||||
Prohibit=true
|
||||
end
|
||||
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit)
|
||||
self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit)
|
||||
end
|
||||
|
||||
return self
|
||||
@ -3478,9 +3542,9 @@ end
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionECM_Never()
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.ECM_USING, 0)
|
||||
self:SetOption(AI.Option.Air.id.ECM_USING, 0)
|
||||
end
|
||||
|
||||
return self
|
||||
@ -3491,9 +3555,9 @@ end
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionECM_OnlyLockByRadar()
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.ECM_USING, 1)
|
||||
self:SetOption(AI.Option.Air.id.ECM_USING, 1)
|
||||
end
|
||||
|
||||
return self
|
||||
@ -3505,9 +3569,9 @@ end
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionECM_DetectedLockByRadar()
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.ECM_USING, 2)
|
||||
self:SetOption(AI.Option.Air.id.ECM_USING, 2)
|
||||
end
|
||||
|
||||
return self
|
||||
@ -3518,9 +3582,9 @@ end
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionECM_AlwaysOn()
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.ECM_USING, 3)
|
||||
self:SetOption(AI.Option.Air.id.ECM_USING, 3)
|
||||
end
|
||||
|
||||
return self
|
||||
@ -3532,7 +3596,7 @@ end
|
||||
-- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED!
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #table WayPoints If WayPoints is given, then use the route.
|
||||
-- @return #CONTROLLABLE
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:WayPointInitialize( WayPoints )
|
||||
self:F( { WayPoints } )
|
||||
|
||||
@ -3563,7 +3627,7 @@ end
|
||||
-- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1!
|
||||
-- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions.
|
||||
-- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters.
|
||||
-- @return #CONTROLLABLE
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
|
||||
self:F2( { WayPoint, WayPointIndex, WayPointFunction } )
|
||||
|
||||
@ -3579,7 +3643,7 @@ end
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number WayPoint The WayPoint from where to execute the mission.
|
||||
-- @param #number WaitTime The amount seconds to wait before initiating the mission.
|
||||
-- @return #CONTROLLABLE
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime )
|
||||
self:F( { WayPoint, WaitTime } )
|
||||
|
||||
@ -3659,3 +3723,124 @@ function CONTROLLABLE:OptionRestrictBurner(RestrictBurner)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Sets Controllable Option for A2A attack range for AIR FIGHTER units.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number range Defines the range
|
||||
-- @return #CONTROLLABLE self
|
||||
-- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack
|
||||
function CONTROLLABLE:OptionAAAttackRange(range)
|
||||
self:F2( { self.ControllableName } )
|
||||
-- defaults to 3
|
||||
local range = range or 3
|
||||
if range < 0 or range > 4 then
|
||||
range = 3
|
||||
end
|
||||
local DCSControllable = self:GetDCSObject()
|
||||
if DCSControllable then
|
||||
local Controller = self:_GetController()
|
||||
if Controller then
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Defines the range at which a GROUND unit/group is allowed to use its weapons automatically.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionEngageRange(EngageRange)
|
||||
self:F2( { self.ControllableName } )
|
||||
-- Set default if not specified.
|
||||
EngageRange=EngageRange or 100
|
||||
if EngageRange < 0 or EngageRange > 100 then
|
||||
EngageRange = 100
|
||||
end
|
||||
local DCSControllable = self:GetDCSObject()
|
||||
if DCSControllable then
|
||||
local Controller = self:_GetController()
|
||||
if Controller then
|
||||
if self:IsGround() then
|
||||
self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number speed Speed of the controllable, default 20
|
||||
-- @param #number radius Radius of the relocation zone, default 500
|
||||
-- @param #boolean onroad If true, route on road (less problems with AI way finding), default true
|
||||
-- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut)
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
local _coord = self:GetCoordinate()
|
||||
local _radius = radius or 500
|
||||
local _speed = speed or 20
|
||||
local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100)
|
||||
local _onroad = onroad or true
|
||||
local _grptsk = {}
|
||||
local _candoroad = false
|
||||
local _shortcut = shortcut or false
|
||||
|
||||
-- create a DCS Task an push it on the group
|
||||
-- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments)
|
||||
if onroad then
|
||||
_grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,"Off Road",_shortcut)
|
||||
self:Route(_grptsk,5)
|
||||
else
|
||||
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Defines how long a GROUND unit/group will move to avoid an ongoing attack.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:OptionDisperseOnAttack(Seconds)
|
||||
self:F2( { self.ControllableName } )
|
||||
-- Set default if not specified.
|
||||
local seconds = Seconds or 0
|
||||
local DCSControllable = self:GetDCSObject()
|
||||
if DCSControllable then
|
||||
local Controller = self:_GetController()
|
||||
if Controller then
|
||||
if self:IsGround() then
|
||||
self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns if the unit is a submarine.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return #boolean Submarines attributes result.
|
||||
function POSITIONABLE:IsSubmarine()
|
||||
self:F2()
|
||||
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
|
||||
if DCSUnit then
|
||||
local UnitDescriptor = DCSUnit:getDesc()
|
||||
if UnitDescriptor.attributes["Submarines"] == true then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
@ -310,8 +310,7 @@ end
|
||||
|
||||
--- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return DCS#Position The 3D position vectors of the POSITIONABLE.
|
||||
-- @return #nil The POSITIONABLE is not existing or alive.
|
||||
-- @return DCS#Position The 3D position vectors of the POSITIONABLE or #nil if the groups not existing or alive.
|
||||
function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3()
|
||||
self:F2( self.PositionableName )
|
||||
|
||||
@ -340,8 +339,7 @@ end
|
||||
--
|
||||
-- @param #GROUP self
|
||||
-- @return #boolean true if the group is alive and active.
|
||||
-- @return #boolean false if the group is alive but inactive.
|
||||
-- @return #nil if the group does not exist anymore.
|
||||
-- @return #boolean false if the group is alive but inactive or #nil if the group does not exist anymore.
|
||||
function GROUP:IsAlive()
|
||||
self:F2( self.GroupName )
|
||||
|
||||
@ -363,8 +361,7 @@ end
|
||||
|
||||
--- Returns if the group is activated.
|
||||
-- @param #GROUP self
|
||||
-- @return #boolean true if group is activated.
|
||||
-- @return #nil The group is not existing or alive.
|
||||
-- @return #boolean true if group is activated or #nil The group is not existing or alive.
|
||||
function GROUP:IsActive()
|
||||
self:F2( self.GroupName )
|
||||
|
||||
@ -412,7 +409,6 @@ function GROUP:Destroy( GenerateEvent, delay )
|
||||
self:F2( self.GroupName )
|
||||
|
||||
if delay and delay>0 then
|
||||
--SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay)
|
||||
self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent)
|
||||
else
|
||||
|
||||
@ -2169,40 +2165,6 @@ function GROUP:GetThreatLevel()
|
||||
return threatlevelMax
|
||||
end
|
||||
|
||||
--- Get the unit in the group with the highest threat level, which is still alive.
|
||||
-- @param #GROUP self
|
||||
-- @return Wrapper.Unit#UNIT The most dangerous unit in the group.
|
||||
-- @return #number Threat level of the unit.
|
||||
function GROUP:GetHighestThreat()
|
||||
|
||||
-- Get units of the group.
|
||||
local units=self:GetUnits()
|
||||
|
||||
if units then
|
||||
|
||||
local threat=nil ; local maxtl=0
|
||||
for _,_unit in pairs(units or {}) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
-- Threat level of group.
|
||||
local tl=unit:GetThreatLevel()
|
||||
|
||||
-- Check if greater the current threat.
|
||||
if tl>maxtl then
|
||||
maxtl=tl
|
||||
threat=unit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return threat, maxtl
|
||||
end
|
||||
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns true if the first unit of the GROUP is in the air.
|
||||
-- @param Wrapper.Group#GROUP self
|
||||
@ -2585,6 +2547,49 @@ do -- Players
|
||||
|
||||
end
|
||||
|
||||
--- GROUND - Switch on/off radar emissions for the group.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
|
||||
-- @return #GROUP self
|
||||
function GROUP:EnableEmission(switch)
|
||||
self:F2( self.GroupName )
|
||||
local switch = switch or false
|
||||
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
|
||||
if DCSUnit then
|
||||
|
||||
DCSUnit:enableEmission(switch)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch on/off invisible flag for the group.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
|
||||
-- @return #GROUP self
|
||||
function GROUP:SetCommandInvisible(switch)
|
||||
self:F2( self.GroupName )
|
||||
local switch = switch or false
|
||||
local SetInvisible = {id = 'SetInvisible', params = {value = true}}
|
||||
self:SetCommand(SetInvisible)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch on/off immortal flag for the group.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
|
||||
-- @return #GROUP self
|
||||
function GROUP:SetCommandImmortal(switch)
|
||||
self:F2( self.GroupName )
|
||||
local switch = switch or false
|
||||
local SetInvisible = {id = 'SetImmortal', params = {value = true}}
|
||||
self:SetCommand(SetInvisible)
|
||||
return self
|
||||
end
|
||||
|
||||
--do -- Smoke
|
||||
--
|
||||
----- Signal a flare at the position of the GROUP.
|
||||
@ -2675,4 +2680,4 @@ end
|
||||
--
|
||||
--
|
||||
--
|
||||
--end
|
||||
--end
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Wrapper.Marker
|
||||
-- @image Wrapper_Marker.png
|
||||
-- @image MOOSE_Core.JPG
|
||||
|
||||
|
||||
--- Marker class.
|
||||
@ -646,7 +646,7 @@ function MARKER:OnEventMarkRemoved(EventData)
|
||||
|
||||
local MarkID=EventData.MarkID
|
||||
|
||||
self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID)))
|
||||
self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s", tostring(MarkID)))
|
||||
|
||||
if MarkID==self.mid then
|
||||
|
||||
@ -673,9 +673,9 @@ function MARKER:OnEventMarkChange(EventData)
|
||||
|
||||
if MarkID==self.mid then
|
||||
|
||||
self:Changed(EventData)
|
||||
self.text=tostring(EventData.MarkText)
|
||||
|
||||
self:TextChanged(tostring(EventData.MarkText))
|
||||
self:Changed(EventData)
|
||||
|
||||
end
|
||||
|
||||
|
||||
@ -590,26 +590,29 @@ end
|
||||
|
||||
--- Returns the POSITIONABLE heading in degrees.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
-- @return #number The POSITIONABLE heading
|
||||
-- @return #nil The POSITIONABLE is not existing or alive.
|
||||
-- @return #number The POSITIONABLE heading in degrees or `nil` if not existing or alive.
|
||||
function POSITIONABLE:GetHeading()
|
||||
|
||||
local DCSPositionable = self:GetDCSObject()
|
||||
|
||||
if DCSPositionable then
|
||||
|
||||
local PositionablePosition = DCSPositionable:getPosition()
|
||||
|
||||
if PositionablePosition then
|
||||
local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x )
|
||||
|
||||
if PositionableHeading < 0 then
|
||||
PositionableHeading = PositionableHeading + 2 * math.pi
|
||||
end
|
||||
|
||||
PositionableHeading = PositionableHeading * 180 / math.pi
|
||||
self:T2( PositionableHeading )
|
||||
|
||||
return PositionableHeading
|
||||
end
|
||||
end
|
||||
|
||||
BASE:E( { "Cannot GetHeading", Positionable = self, Alive = self:IsAlive() } )
|
||||
self:E({"Cannot GetHeading", Positionable = self, Alive = self:IsAlive()})
|
||||
|
||||
return nil
|
||||
end
|
||||
@ -681,6 +684,27 @@ function POSITIONABLE:IsShip()
|
||||
end
|
||||
|
||||
|
||||
--- Returns if the unit is a submarine.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return #boolean Submarines attributes result.
|
||||
function POSITIONABLE:IsSubmarine()
|
||||
self:F2()
|
||||
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
|
||||
if DCSUnit then
|
||||
local UnitDescriptor = DCSUnit:getDesc()
|
||||
if UnitDescriptor.attributes["Submarines"] == true then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns true if the POSITIONABLE is in the air.
|
||||
-- Polymorphic, is overridden in GROUP and UNIT.
|
||||
-- @param Wrapper.Positionable#POSITIONABLE self
|
||||
@ -814,8 +838,7 @@ end
|
||||
-- @return #number The velocity in knots.
|
||||
function POSITIONABLE:GetVelocityKNOTS()
|
||||
self:F2( self.PositionableName )
|
||||
local velmps=self:GetVelocityMPS()
|
||||
return UTILS.MpsToKnots(velmps)
|
||||
return UTILS.MpsToKnots(self:GetVelocityMPS())
|
||||
end
|
||||
|
||||
--- Returns the Angle of Attack of a positionable.
|
||||
@ -1391,18 +1414,6 @@ do -- Cargo
|
||||
return ItemCount
|
||||
end
|
||||
|
||||
-- --- Get Cargo Bay Free Volume in m3.
|
||||
-- -- @param #POSITIONABLE self
|
||||
-- -- @return #number CargoBayFreeVolume
|
||||
-- function POSITIONABLE:GetCargoBayFreeVolume()
|
||||
-- local CargoVolume = 0
|
||||
-- for CargoName, Cargo in pairs( self.__.Cargo ) do
|
||||
-- CargoVolume = CargoVolume + Cargo:GetVolume()
|
||||
-- end
|
||||
-- return self.__.CargoBayVolumeLimit - CargoVolume
|
||||
-- end
|
||||
--
|
||||
|
||||
--- Get Cargo Bay Free Weight in kg.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @return #number CargoBayFreeWeight
|
||||
@ -1420,13 +1431,6 @@ do -- Cargo
|
||||
return self.__.CargoBayWeightLimit - CargoWeight
|
||||
end
|
||||
|
||||
-- --- Get Cargo Bay Volume Limit in m3.
|
||||
-- -- @param #POSITIONABLE self
|
||||
-- -- @param #number VolumeLimit
|
||||
-- function POSITIONABLE:SetCargoBayVolumeLimit( VolumeLimit )
|
||||
-- self.__.CargoBayVolumeLimit = VolumeLimit
|
||||
-- end
|
||||
|
||||
--- Set Cargo Bay Weight Limit in kg.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @param #number WeightLimit
|
||||
@ -1445,55 +1449,82 @@ do -- Cargo
|
||||
self:F({Desc=Desc})
|
||||
|
||||
local Weights = {
|
||||
["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry.,
|
||||
["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry.,
|
||||
["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry.
|
||||
["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry.
|
||||
}
|
||||
|
||||
self.__.CargoBayWeightLimit = Weights[Desc.typeName] or ( Desc.massMax - ( Desc.massEmpty + Desc.fuelMassMax ) )
|
||||
elseif self:IsShip() then
|
||||
local Desc = self:GetDesc()
|
||||
self:F({Desc=Desc})
|
||||
|
||||
local Weights = {
|
||||
["Type_071"] = 245000,
|
||||
["LHA_Tarawa"] = 500000,
|
||||
["Ropucha-class"] = 150000,
|
||||
["Dry-cargo ship-1"] = 70000,
|
||||
["Dry-cargo ship-2"] = 70000,
|
||||
["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia).
|
||||
["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops.
|
||||
["LST_Mk2"] = 2100000, -- Can carry 2100 tons according to wiki source!
|
||||
["speedboat"] = 500, -- 500 kg ~ 5 persons
|
||||
["Seawise_Giant"] =261000000, -- Gross tonnage is 261,000 tonns.
|
||||
}
|
||||
self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 )
|
||||
|
||||
else
|
||||
local Desc = self:GetDesc()
|
||||
|
||||
local Weights = {
|
||||
["M1126 Stryker ICV"] = 9,
|
||||
["M-113"] = 9,
|
||||
["AAV7"] = 25,
|
||||
["M2A1_halftrack"] = 9,
|
||||
["BMD-1"] = 9,
|
||||
["Bedford_MWD"] = 8, -- new by kappa
|
||||
["Blitz_36-6700A"] = 10, -- new by kappa
|
||||
["BMD-1"] = 9, -- IRL should be 4 passengers
|
||||
["BMP-1"] = 8,
|
||||
["BMP-2"] = 7,
|
||||
["BMP-3"] = 8,
|
||||
["BMP-3"] = 8, -- IRL should be 7+2 passengers
|
||||
["Boman"] = 25,
|
||||
["BTR-80"] = 9,
|
||||
["BTR_D"] = 12,
|
||||
["BTR-80"] = 9, -- IRL should be 7 passengers
|
||||
["BTR-82A"] = 9, -- new by kappa -- IRL should be 7 passengers
|
||||
["BTR_D"] = 12, -- IRL should be 10 passengers
|
||||
["Cobra"] = 8,
|
||||
["Land_Rover_101_FC"] = 11, -- new by kappa
|
||||
["Land_Rover_109_S3"] = 7, -- new by kappa
|
||||
["LAV-25"] = 6,
|
||||
["M-2 Bradley"] = 6,
|
||||
["M1043 HMMWV Armament"] = 4,
|
||||
["M1045 HMMWV TOW"] = 4,
|
||||
["M1126 Stryker ICV"] = 9,
|
||||
["M1134 Stryker ATGM"] = 9,
|
||||
["M2A1_halftrack"] = 9,
|
||||
["M-113"] = 9, -- IRL should be 11 passengers
|
||||
["Marder"] = 6,
|
||||
["MCV-80"] = 9,
|
||||
["MCV-80"] = 9, -- IRL should be 7 passengers
|
||||
["MLRS FDDM"] = 4,
|
||||
["MTLB"] = 25,
|
||||
["TPZ"] = 10,
|
||||
["Ural-4320 APA-5D"] = 10,
|
||||
["MTLB"] = 25, -- IRL should be 11 passengers
|
||||
["GAZ-66"] = 8,
|
||||
["GAZ-3307"] = 12,
|
||||
["GAZ-3308"] = 14,
|
||||
["Tigr_233036"] = 6,
|
||||
["Grad_FDDM"] = 6, -- new by kappa
|
||||
["KAMAZ Truck"] = 12,
|
||||
["KrAZ6322"] = 12,
|
||||
["M 818"] = 12,
|
||||
["Tigr_233036"] = 6,
|
||||
["TPZ"] = 10,
|
||||
["UAZ-469"] = 4, -- new by kappa
|
||||
["Ural-375"] = 12,
|
||||
["Ural-4320-31"] = 14,
|
||||
["Ural-4320 APA-5D"] = 10,
|
||||
["Ural-4320T"] = 14,
|
||||
["ZBD04A"] = 7, -- new by kappa
|
||||
}
|
||||
|
||||
local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95
|
||||
|
||||
self.__.CargoBayWeightLimit = CargoBayWeightLimit
|
||||
end
|
||||
end
|
||||
|
||||
self:F({CargoBayWeightLimit = self.__.CargoBayWeightLimit})
|
||||
end
|
||||
end --- Cargo
|
||||
|
||||
@ -57,3 +57,40 @@ end
|
||||
function SCENERY:GetThreatLevel()
|
||||
return 0, "Scenery"
|
||||
end
|
||||
|
||||
--- Find a SCENERY object by it's name/id.
|
||||
--@param #SCENERY self
|
||||
--@param #string name The name/id of the scenery object as taken from the ME. Ex. '595785449'
|
||||
--@return #SCENERY Scenery Object or nil if not found.
|
||||
function SCENERY:FindByName(name)
|
||||
local findAirbase = function ()
|
||||
local airbases = AIRBASE.GetAllAirbases()
|
||||
for index,airbase in pairs(airbases) do
|
||||
local surftype = airbase:GetCoordinate():GetSurfaceType()
|
||||
if surftype ~= land.SurfaceType.SHALLOW_WATER and surftype ~= land.SurfaceType.WATER then
|
||||
return airbase:GetCoordinate()
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local sceneryScan = function (scancoord)
|
||||
if scancoord ~= nil then
|
||||
local _,_,sceneryfound,_,_,scenerylist = scancoord:ScanObjects(200, false, false, true)
|
||||
if sceneryfound == true then
|
||||
scenerylist[1].id_ = name
|
||||
SCENERY.SceneryObject = SCENERY:Register(scenerylist[1].id_, scenerylist[1])
|
||||
return SCENERY.SceneryObject
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if SCENERY.SceneryObject then
|
||||
SCENERY.SceneryObject.SceneryObject.id_ = name
|
||||
SCENERY.SceneryObject.SceneryName = name
|
||||
return SCENERY:Register(SCENERY.SceneryObject.SceneryObject.id_, SCENERY.SceneryObject.SceneryObject)
|
||||
else
|
||||
return sceneryScan(findAirbase())
|
||||
end
|
||||
end
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -22,6 +22,8 @@
|
||||
|
||||
|
||||
--- @type UNIT
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string UnitName Name of the unit.
|
||||
-- @extends Wrapper.Controllable#CONTROLLABLE
|
||||
|
||||
--- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object.
|
||||
@ -61,7 +63,7 @@
|
||||
--
|
||||
-- The UNIT class provides methods to obtain the current point or position of the DCS Unit.
|
||||
-- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively.
|
||||
-- If you want to obtain the complete **3D position** including ori<EFBFBD>ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively.
|
||||
-- If you want to obtain the complete **3D position** including orientation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively.
|
||||
--
|
||||
-- ## Test if alive
|
||||
--
|
||||
@ -87,6 +89,7 @@
|
||||
-- @field #UNIT UNIT
|
||||
UNIT = {
|
||||
ClassName="UNIT",
|
||||
UnitName=nil,
|
||||
}
|
||||
|
||||
|
||||
@ -103,12 +106,18 @@ UNIT = {
|
||||
--- Create a new UNIT from DCSUnit.
|
||||
-- @param #UNIT self
|
||||
-- @param #string UnitName The name of the DCS unit.
|
||||
-- @return #UNIT
|
||||
-- @return #UNIT self
|
||||
function UNIT:Register( UnitName )
|
||||
|
||||
-- Inherit CONTROLLABLE.
|
||||
local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) )
|
||||
|
||||
-- Set unit name.
|
||||
self.UnitName = UnitName
|
||||
|
||||
-- Set event prio.
|
||||
self:SetEventPriority( 3 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@ -145,8 +154,8 @@ function UNIT:Name()
|
||||
return self.UnitName
|
||||
end
|
||||
|
||||
|
||||
--- @param #UNIT self
|
||||
--- Get the DCS unit object.
|
||||
-- @param #UNIT self
|
||||
-- @return DCS#Unit
|
||||
function UNIT:GetDCSObject()
|
||||
|
||||
@ -373,6 +382,32 @@ function UNIT:GetPlayerName()
|
||||
|
||||
end
|
||||
|
||||
--- Checks is the unit is a *Player* or *Client* slot.
|
||||
-- @param #UNIT self
|
||||
-- @return #boolean If true, unit is a player or client aircraft
|
||||
function UNIT:IsClient()
|
||||
|
||||
if _DATABASE.CLIENTS[self.UnitName] then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Get the CLIENT of the unit
|
||||
-- @param #UNIT self
|
||||
-- @return Wrapper.Client#CLIENT
|
||||
function UNIT:GetClient()
|
||||
|
||||
local client=_DATABASE.CLIENTS[self.UnitName]
|
||||
|
||||
if client then
|
||||
return client
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the unit's number in the group.
|
||||
-- The number is the same number the unit has in ME.
|
||||
-- It may not be changed during the mission.
|
||||
@ -726,6 +761,7 @@ function UNIT:GetFuel()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Returns a list of one @{Wrapper.Unit}.
|
||||
-- @param #UNIT self
|
||||
-- @return #list<Wrapper.Unit#UNIT> A list of one @{Wrapper.Unit}.
|
||||
@ -1139,8 +1175,9 @@ end
|
||||
|
||||
--- Returns true if the UNIT is in the air.
|
||||
-- @param #UNIT self
|
||||
-- @param #boolean NoHeloCheck If true, no additonal checks for helos are performed.
|
||||
-- @return #boolean Return true if in the air or #nil if the UNIT is not existing or alive.
|
||||
function UNIT:InAir()
|
||||
function UNIT:InAir(NoHeloCheck)
|
||||
self:F2( self.UnitName )
|
||||
|
||||
-- Get DCS unit object.
|
||||
@ -1150,14 +1187,14 @@ function UNIT:InAir()
|
||||
|
||||
-- Get DCS result of whether unit is in air or not.
|
||||
local UnitInAir = DCSUnit:inAir()
|
||||
|
||||
|
||||
-- Get unit category.
|
||||
local UnitCategory = DCSUnit:getDesc().category
|
||||
|
||||
-- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not.
|
||||
-- This is a workaround since DCS currently does not acknoledge that helos land on buildings.
|
||||
-- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier!
|
||||
if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER then
|
||||
if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER and (not NoHeloCheck) then
|
||||
local VelocityVec3 = DCSUnit:getVelocity()
|
||||
local Velocity = UTILS.VecNorm(VelocityVec3)
|
||||
local Coordinate = DCSUnit:getPoint()
|
||||
@ -1357,3 +1394,23 @@ function UNIT:GetTemplateFuel()
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- GROUND - Switch on/off radar emissions of a unit.
|
||||
-- @param #UNIT self
|
||||
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
|
||||
-- @return #UNIT self
|
||||
function UNIT:EnableEmission(switch)
|
||||
self:F2( self.UnitName )
|
||||
|
||||
local switch = switch or false
|
||||
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
|
||||
if DCSUnit then
|
||||
|
||||
DCSUnit:enableEmission(switch)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
Utilities/Enums.lua
|
||||
Utilities/Routines.lua
|
||||
Utilities/Utils.lua
|
||||
Utilities/Enums.lua
|
||||
Utilities/Profiler.lua
|
||||
Utilities/Templates.lua
|
||||
Utilities/STTS.lua
|
||||
|
||||
Core/Base.lua
|
||||
Core/Beacon.lua
|
||||
Core/UserFlag.lua
|
||||
Core/UserSound.lua
|
||||
Core/Report.lua
|
||||
Core/Scheduler.lua
|
||||
Core/ScheduleDispatcher.lua
|
||||
@ -13,13 +16,13 @@ Core/Event.lua
|
||||
Core/Settings.lua
|
||||
Core/Menu.lua
|
||||
Core/Zone.lua
|
||||
Core/Zone_Detection.lua
|
||||
Core/Database.lua
|
||||
Core/Set.lua
|
||||
Core/Point.lua
|
||||
Core/Velocity.lua
|
||||
Core/Message.lua
|
||||
Core/Fsm.lua
|
||||
Core/Radio.lua
|
||||
Core/Spawn.lua
|
||||
Core/SpawnStatic.lua
|
||||
Core/Timer.lua
|
||||
@ -52,6 +55,7 @@ Functional/Escort.lua
|
||||
Functional/MissileTrainer.lua
|
||||
Functional/ATC_Ground.lua
|
||||
Functional/Detection.lua
|
||||
Functional/DetectionZones.lua
|
||||
Functional/Designate.lua
|
||||
Functional/RAT.lua
|
||||
Functional/Range.lua
|
||||
@ -63,6 +67,8 @@ Functional/Suppression.lua
|
||||
Functional/PseudoATC.lua
|
||||
Functional/Warehouse.lua
|
||||
Functional/Fox.lua
|
||||
Functional/Mantis.lua
|
||||
Functional/Shorad.lua
|
||||
|
||||
Ops/Airboss.lua
|
||||
Ops/RecoveryTanker.lua
|
||||
@ -77,40 +83,53 @@ Ops/AirWing.lua
|
||||
Ops/Intelligence.lua
|
||||
Ops/WingCommander.lua
|
||||
Ops/ChiefOfStaff.lua
|
||||
Ops/CSAR.lua
|
||||
Ops/CTLD.lua
|
||||
|
||||
AI/AI_Balancer.lua
|
||||
AI/AI_Air.lua
|
||||
AI/AI_A2A.lua
|
||||
AI/AI_Air_Patrol.lua
|
||||
AI/AI_Air_Engage.lua
|
||||
AI/AI_A2A_Patrol.lua
|
||||
AI/AI_A2A_Cap.lua
|
||||
AI/AI_A2A_Gci.lua
|
||||
AI/AI_A2A_Dispatcher.lua
|
||||
AI/AI_A2G.lua
|
||||
AI/AI_A2G_Engage.lua
|
||||
AI/AI_A2G_BAI.lua
|
||||
AI/AI_A2G_CAS.lua
|
||||
AI/AI_A2G_SEAD.lua
|
||||
AI/AI_A2G_Patrol.lua
|
||||
AI/AI_A2G_Dispatcher.lua
|
||||
AI/AI_Patrol.lua
|
||||
AI/AI_Cap.lua
|
||||
AI/AI_Cas.lua
|
||||
AI/AI_Bai.lua
|
||||
AI/AI_CAP.lua
|
||||
AI/AI_CAS.lua
|
||||
AI/AI_BAI.lua
|
||||
AI/AI_Formation.lua
|
||||
AI/AI_Escort.lua
|
||||
AI/AI_Escort_Request.lua
|
||||
AI/AI_Escort_Dispatcher.lua
|
||||
AI/AI_Escort_Dispatcher_Request.lua
|
||||
AI/AI_Cargo.lua
|
||||
AI/AI_Cargo_APC.lua
|
||||
AI/AI_Cargo_Helicopter.lua
|
||||
AI/AI_Cargo_Airplane.lua
|
||||
AI/AI_Cargo_Ship.lua
|
||||
AI/AI_Cargo_Dispatcher.lua
|
||||
AI/AI_Cargo_Dispatcher_APC.lua
|
||||
AI/AI_Cargo_Dispatcher_Helicopter.lua
|
||||
AI/AI_Cargo_Dispatcher_Airplane.lua
|
||||
AI/AI_Cargo_Dispatcher_Ship.lua
|
||||
|
||||
Actions/Act_Assign.lua
|
||||
Actions/Act_Route.lua
|
||||
Actions/Act_Account.lua
|
||||
Actions/Act_Assist.lua
|
||||
|
||||
Sound/UserSound.lua
|
||||
Sound/SoundOutput.lua
|
||||
Sound/Radio.lua
|
||||
Sound/RadioQueue.lua
|
||||
Sound/RadioSpeech.lua
|
||||
Sound/SRS.lua
|
||||
|
||||
Tasking/CommandCenter.lua
|
||||
Tasking/Mission.lua
|
||||
Tasking/Task.lua
|
||||
@ -121,10 +140,11 @@ Tasking/Task_A2G_Dispatcher.lua
|
||||
Tasking/Task_A2G.lua
|
||||
Tasking/Task_A2A_Dispatcher.lua
|
||||
Tasking/Task_A2A.lua
|
||||
Tasking/Task_Cargo.lua
|
||||
Tasking/Task_CARGO.lua
|
||||
Tasking/Task_Cargo_Transport.lua
|
||||
Tasking/Task_Cargo_CSAR.lua
|
||||
Tasking/Task_Cargo_Dispatcher.lua
|
||||
Tasking/TaskZoneCapture.lua
|
||||
Tasking/Task_Capture_Zone.lua
|
||||
Tasking/Task_Capture_Dispatcher.lua
|
||||
|
||||
Globals.lua
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
[](https://ci.appveyor.com/project/Applevangelist/MOOSE)
|
||||
|
||||
# MOOSE framework
|
||||
|
||||
MOOSE is a **M**ission **O**bject **O**riented **S**cripting **E**nvironment, and is meant for mission designers in DCS World.
|
||||
@ -28,7 +30,7 @@ This repository contains the source lua code of the MOOSE framework.
|
||||
|
||||
### [MOOSE_INCLUDE](https://github.com/FlightControl-Master/MOOSE_INCLUDE) - For use and generated
|
||||
|
||||
This repository contains the Moose.lua file to be included within your missions.
|
||||
This repository contains the Moose.lua file to be included within your missions. Note that the Moose\_.lua is technically the same as Moose.lua, but without any commentary or unnecessary whitespace in it. You only need to load **one** of those at the beginning of your mission.
|
||||
|
||||
|
||||
### [MOOSE_DOCS](https://github.com/FlightControl-Master/MOOSE_DOCS) - Not for use
|
||||
@ -50,9 +52,7 @@ This repository contains all the demonstration missions in packed format (*.miz)
|
||||
|
||||
This repository contains all the demonstration missions in unpacked format. That means that there is no .miz file included, but all the .miz contents are unpacked.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [MOOSE Web Site](https://flightcontrol-master.github.io/MOOSE_DOCS/)
|
||||
|
||||
Documentation on the MOOSE class hierarchy, usage guides and background information can be found here for normal users, beta testers and contributors.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user