mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-08-10 17:04:07 +00:00
Compare commits
201 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a406604bd | |||
| 55cee46a8d | |||
| 607c52c0b7 | |||
| 2694321256 | |||
| e8e790102a | |||
| 9c5561921b | |||
| 78fab9ab0c | |||
| 058c750bc6 | |||
| f29da39dff | |||
| 624a7c70c9 | |||
| 0447ee2d9e | |||
| 456fcd38d0 | |||
| a3cab7097a | |||
| 848e2f1294 | |||
| ef4dc48ea1 | |||
| 2138a33292 | |||
| a59343b987 | |||
| a4ca4bdc99 | |||
| a57b9a9081 | |||
| 249a6af456 | |||
| 18685d1a94 | |||
| 493b090534 | |||
| 389adab9b8 | |||
| 6360b8c58f | |||
| b0818977cf | |||
| 32deb160ef | |||
| 2ba5215036 | |||
| 73ea4c7b32 | |||
| 46c37ff06a | |||
| ff4708b624 | |||
| 82b2c84f13 | |||
| 48e8b1a9b3 | |||
| 1ad7c54ace | |||
| 9998c86c1f | |||
| 01a707ae0a | |||
| 570e8388fc | |||
| 9c5b5d4633 | |||
| 822bf13626 | |||
| c520de0087 | |||
| 0e9076efa3 | |||
| 74cd5e3387 | |||
| 65c92be09e | |||
| 8e776cb3ab | |||
| ab6cd2b751 | |||
| 18c3d990fc | |||
| 19d5cb8ecb | |||
| 2b56a78255 | |||
| 176d9df476 | |||
| c94275cb8b | |||
| 45dbce3677 | |||
| 18745158a3 | |||
| 98c6c88391 | |||
| 2e4fd72781 | |||
| 80ced88ef1 | |||
| 8af3f89c14 | |||
| fe3079caad | |||
| 61ac6b4131 | |||
| 36cb189512 | |||
| 15f9843878 | |||
| 67f847dd16 | |||
| 8b9143d3f1 | |||
| 0388d47f23 | |||
| de9b173d9b | |||
| 2cecc526fb | |||
| 968d178317 | |||
| 3c477b872a | |||
| 77e6088114 | |||
| edd6594953 | |||
| f8c05c99d0 | |||
| 50f6d98b49 | |||
| 147eeb05f6 | |||
| d8cb15a577 | |||
| 0daac876ea | |||
| 1832125022 | |||
| c311c40b72 | |||
| db516a2077 | |||
| ff8766669c | |||
| 06dc9a732e | |||
| 50c74d0852 | |||
| 1c97eb6f3c | |||
| 69449430d1 | |||
| 663cd34aa3 | |||
| cfed6f5153 | |||
| 2b22d5288c | |||
| a64424ecc8 | |||
| fd1b2ecb86 | |||
| 6cae3e62cf | |||
| 05ce7e4513 | |||
| 136bd19f19 | |||
| 8873504daf | |||
| a844a5d697 | |||
| a49f4eaa21 | |||
| e6e2651f8c | |||
| b93ba13644 | |||
| e4a51951b0 | |||
| ad56e39942 | |||
| ea09dc5a6e | |||
| 8ecfd913a3 | |||
| 53367c786e | |||
| db5797bb4e | |||
| 5e8fe97752 | |||
| 393fa0bfbb | |||
| 4f51884b9d | |||
| 4c5c320073 | |||
| 9098590568 | |||
| 555bb7e68b | |||
| c0a18957f0 | |||
| 2cf939560e | |||
| 9d3a7aae78 | |||
| f6ed592f92 | |||
| c98757d13c | |||
| 17378f509e | |||
| 7f18ea0e7a | |||
| 5172619cb1 | |||
| 3962529698 | |||
| 6481d5d41e | |||
| 6cc3d73c04 | |||
| e541e39403 | |||
| c7ea45e5fd | |||
| 20f28b3d2c | |||
| f3f63ab8aa | |||
| e91090cfff | |||
| 1a7fb3c13e | |||
| 59857ed79d | |||
| 4797665939 | |||
| b89749036d | |||
| c6268488de | |||
| de04369703 | |||
| 05b6f19a87 | |||
| 2753df8216 | |||
| 33d761503d | |||
| b52272e18e | |||
| 70fa1cf19f | |||
| 7ca7caea75 | |||
| 277c26821e | |||
| 22826b4cd1 | |||
| 8cc1c24b64 | |||
| 0db35a0e9f | |||
| b4707bb3eb | |||
| bbfc2e9ed7 | |||
| 2fb0ab1aed | |||
| 6fbf050b72 | |||
| 29210f670c | |||
| 18b45c9621 | |||
| 9356d67d74 | |||
| 5bb2b05f71 | |||
| 89308f7d06 | |||
| 353d6dfec0 | |||
| 3f5e322948 | |||
| 0e8732fd44 | |||
| 77a3c7369d | |||
| 8d8070bbd7 | |||
| 72df687ce7 | |||
| 246935622c | |||
| 078573629d | |||
| 973b8323c9 | |||
| 6d61c5ee94 | |||
| 3e8db6a1fa | |||
| 832d6b1c08 | |||
| e7936950f4 | |||
| 831e986ee8 | |||
| 8a44fae3d4 | |||
| 0c9390914a | |||
| 4fa525a4ae | |||
| 858b00336b | |||
| 82d78c98bb | |||
| 06c3f7998b | |||
| e03e87f501 | |||
| cf83abfe90 | |||
| 5a00f461e9 | |||
| bb43b08190 | |||
| 05f95796f6 | |||
| cfcd7d7588 | |||
| 4c3d44a63b | |||
| fcd75a34eb | |||
| 4827b73bb1 | |||
| 2d7e7d55a9 | |||
| 3129c8c8ea | |||
| 6e37300d9b | |||
| 6b747e924b | |||
| 85fef96d00 | |||
| e55bb21401 | |||
| 7a3fb95851 | |||
| d39074bf3e | |||
| 47d814e409 | |||
| 41b01a508d | |||
| 0410ae6877 | |||
| 1ae41319fa | |||
| 0265152c12 | |||
| a893d46cb9 | |||
| b68f271fb7 | |||
| 057e231a9d | |||
| b6fedbd97d | |||
| 7cd29501a9 | |||
| 59e4f48726 | |||
| 20fe2ee505 | |||
| ebb4623bb5 | |||
| d1f3e3f4bb | |||
| 45f578b5a3 | |||
| 2bce305451 | |||
| 2e66a854b1 |
@@ -0,0 +1,33 @@
|
||||
# See https://github.com/Koihik/LuaFormatter
|
||||
# Use '-- LuaFormatter off' and '-- LuaFormatter on' around code blocks to inhibit formatting
|
||||
|
||||
column_limit: 500
|
||||
indent_width: 2
|
||||
use_tab: false
|
||||
continuation_indent_width: 2
|
||||
keep_simple_control_block_one_line: false
|
||||
keep_simple_function_one_line: false
|
||||
align_args: true
|
||||
break_after_functioncall_lp: false
|
||||
break_before_functioncall_rp: false
|
||||
align_parameter: true
|
||||
chop_down_parameter: true
|
||||
break_after_functiondef_lp: false
|
||||
break_before_functiondef_rp: false
|
||||
align_table_field: true
|
||||
break_after_table_lb: true
|
||||
break_before_table_rb: true
|
||||
chop_down_table: true
|
||||
chop_down_kv_table: true
|
||||
column_table_limit: 500
|
||||
table_sep: ','
|
||||
extra_sep_at_table_end: true
|
||||
break_after_operator: true
|
||||
single_quote_to_double_quote: false
|
||||
double_quote_to_single_quote: false
|
||||
spaces_before_call: 1
|
||||
spaces_inside_functiondef_parens: true
|
||||
spaces_inside_functioncall_parens: true
|
||||
spaces_inside_table_braces: true
|
||||
spaces_around_equals_in_field: true
|
||||
line_breaks_after_function_body: 1
|
||||
File diff suppressed because it is too large
Load Diff
@@ -585,7 +585,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
|
||||
self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
|
||||
self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
|
||||
|
||||
self:ClearTargetDistance()
|
||||
--AIGroup:ClearTasks()
|
||||
|
||||
@@ -47,19 +47,21 @@ function AI_CARGO:New( Carrier, CargoSet )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
|
||||
self:AddTransition( "Unloaded", "Pickup", "*" )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
-- Board
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Reload", "*" )
|
||||
self:AddTransition( "*", "Board", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "*", "Load", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Board", "Boarding" )
|
||||
self:AddTransition( "Loaded", "Board", "Loaded" )
|
||||
self:AddTransition( "Boarding", "Loaded", "Boarding" )
|
||||
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
|
||||
-- Unload
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unboard", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
self:AddTransition( "Loaded", "Unload", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnBeforePickup
|
||||
@@ -393,7 +395,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() and From == "Boarding" then
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } )
|
||||
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
|
||||
self:__Board( -10, Cargo, CarrierUnit, PickupZone )
|
||||
@@ -509,7 +511,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() and From == "Unboarding" then
|
||||
if Carrier and Carrier:IsAlive() then
|
||||
if not Cargo:IsUnLoaded() then
|
||||
self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend )
|
||||
return
|
||||
@@ -580,4 +582,3 @@ function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -98,7 +98,8 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
|
||||
self:AddTransition( "*", "Guard", "Unloaded" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
self:AddTransition( "*", "Reload", "Boarding" )
|
||||
|
||||
self:AddTransition( "*", "Deployed", "*" )
|
||||
self:AddTransition( "*", "PickedUp", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
self:SetCombatRadius( CombatRadius )
|
||||
|
||||
@@ -64,21 +64,25 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
-- Boarding
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "Unloaded", "Pickup", "*" )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
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" , "*" )
|
||||
-- Unboarding
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
self:AddTransition( "*", "Destroyed", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
-- RTB
|
||||
self:AddTransition( "*", "Home" , "*" )
|
||||
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
@@ -207,6 +211,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
|
||||
self:SetCarrier( Helicopter )
|
||||
|
||||
self.landingspeed = 15 -- kph
|
||||
self.landingheight = 5.5 -- meter
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -255,6 +262,25 @@ function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #number speed Landing speed in kph(!), e.g. 15
|
||||
-- @param #number height Landing height in meters(!), e.g. 5.5
|
||||
-- @return #AI_CARGO_HELICOPTER self
|
||||
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
|
||||
--
|
||||
-- BASE:TraceOn()
|
||||
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
|
||||
--
|
||||
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
|
||||
-- Adjust if necessary.
|
||||
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
|
||||
local _speed = speed or 15
|
||||
local _height = height or 5.5
|
||||
self.landingheight = _height
|
||||
self.landingspeed = _speed
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
@@ -271,13 +297,13 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
-- 1 - When the helo lands normally on the ground.
|
||||
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
|
||||
-- For point 2, this is an issue, the infantry may not unload in this case!
|
||||
-- So we check if the helo is on the ground, and velocity< 5.
|
||||
-- So we check if the helo is on the ground, and velocity< 15.
|
||||
-- Only then the infantry can unload (and load too, for consistency)!
|
||||
|
||||
self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
|
||||
if self.RoutePickup == true then
|
||||
if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
--self:Load( Helicopter:GetPointVec2() )
|
||||
self:Load( self.PickupZone )
|
||||
self.RoutePickup = false
|
||||
@@ -285,7 +311,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
end
|
||||
|
||||
if self.RouteDeploy == true then
|
||||
if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
self:Unload( self.DeployZone )
|
||||
self.RouteDeploy = false
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-- * The construction and inheritance of MOOSE classes.
|
||||
-- * The class naming and numbering system.
|
||||
-- * The class hierarchy search system.
|
||||
-- * The tracing of information or objects during mission execution for debuggin purposes.
|
||||
-- * The tracing of information or objects during mission execution for debugging purposes.
|
||||
-- * The subscription to DCS events for event handling in MOOSE objects.
|
||||
-- * Object inspection.
|
||||
--
|
||||
@@ -49,7 +49,8 @@ local _ClassID = 0
|
||||
-- # 2. Trace information for debugging.
|
||||
--
|
||||
-- The BASE class contains trace methods to trace progress within a mission execution of a certain object.
|
||||
-- These trace methods are inherited by each MOOSE class interiting BASE, soeach object created from derived class from BASE can use the tracing methods to trace its execution.
|
||||
-- These trace methods are inherited by each MOOSE class inheriting BASE, thus all objects created from
|
||||
-- a class derived from BASE can use the tracing methods to trace its execution.
|
||||
--
|
||||
-- Any type of information can be passed to these tracing methods. See the following examples:
|
||||
--
|
||||
@@ -111,7 +112,6 @@ local _ClassID = 0
|
||||
--
|
||||
-- The method @{#BASE.IsTrace}() will validate if tracing is activated or not.
|
||||
--
|
||||
--
|
||||
-- # 3. DCS simulator Event Handling.
|
||||
--
|
||||
-- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator,
|
||||
@@ -157,8 +157,6 @@ local _ClassID = 0
|
||||
-- self:SmokeBlue()
|
||||
-- end
|
||||
--
|
||||
--
|
||||
--
|
||||
-- See the @{Event} module for more information about event handling.
|
||||
--
|
||||
-- # 4. Class identification methods.
|
||||
@@ -203,13 +201,12 @@ BASE = {
|
||||
Scheduler = nil,
|
||||
}
|
||||
|
||||
|
||||
--- @field #BASE.__
|
||||
BASE.__ = {}
|
||||
|
||||
--- @field #BASE._
|
||||
BASE._ = {
|
||||
Schedules = {} --- Contains the Schedulers Active
|
||||
Schedules = {}, --- Contains the Schedulers Active
|
||||
}
|
||||
|
||||
--- The Formation Class
|
||||
@@ -217,11 +214,9 @@ BASE._ = {
|
||||
-- @field Cone A cone formation.
|
||||
FORMATION = {
|
||||
Cone = "Cone",
|
||||
Vee = "Vee"
|
||||
Vee = "Vee",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- BASE constructor.
|
||||
--
|
||||
-- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE.
|
||||
@@ -236,16 +231,16 @@ FORMATION = {
|
||||
function BASE:New()
|
||||
local self = routines.utils.deepCopy( self ) -- Create a new self instance
|
||||
|
||||
_ClassID = _ClassID + 1
|
||||
self.ClassID = _ClassID
|
||||
_ClassID = _ClassID + 1
|
||||
self.ClassID = _ClassID
|
||||
|
||||
-- This is for "private" methods...
|
||||
-- When a __ is passed to a method as "self", the __index will search for the method on the public method list too!
|
||||
-- if rawget( self, "__" ) then
|
||||
--setmetatable( self, { __index = self.__ } )
|
||||
-- end
|
||||
-- This is for "private" methods...
|
||||
-- When a __ is passed to a method as "self", the __index will search for the method on the public method list too!
|
||||
-- if rawget( self, "__" ) then
|
||||
-- setmetatable( self, { __index = self.__ } )
|
||||
-- end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- This is the worker method to inherit from a parent class.
|
||||
@@ -256,26 +251,25 @@ end
|
||||
function BASE:Inherit( Child, Parent )
|
||||
|
||||
-- Create child.
|
||||
local Child = routines.utils.deepCopy( Child )
|
||||
local Child = routines.utils.deepCopy( Child )
|
||||
|
||||
if Child ~= nil then
|
||||
if Child ~= nil then
|
||||
|
||||
-- This is for "private" methods...
|
||||
-- When a __ is passed to a method as "self", the __index will search for the method on the public method list of the same object too!
|
||||
-- This is for "private" methods...
|
||||
-- When a __ is passed to a method as "self", the __index will search for the method on the public method list of the same object too!
|
||||
if rawget( Child, "__" ) then
|
||||
setmetatable( Child, { __index = Child.__ } )
|
||||
setmetatable( Child, { __index = Child.__ } )
|
||||
setmetatable( Child.__, { __index = Parent } )
|
||||
else
|
||||
setmetatable( Child, { __index = Parent } )
|
||||
end
|
||||
|
||||
--Child:_SetDestructor()
|
||||
end
|
||||
-- Child:_SetDestructor()
|
||||
end
|
||||
|
||||
return Child
|
||||
return Child
|
||||
end
|
||||
|
||||
|
||||
local function getParent( Child )
|
||||
local Parent = nil
|
||||
|
||||
@@ -291,7 +285,6 @@ local function getParent( Child )
|
||||
return Parent
|
||||
end
|
||||
|
||||
|
||||
--- This is the worker method to retrieve the Parent class.
|
||||
-- Note that the Parent class must be passed to call the parent class method.
|
||||
--
|
||||
@@ -304,19 +297,18 @@ end
|
||||
-- @return #BASE
|
||||
function BASE:GetParent( Child, FromClass )
|
||||
|
||||
|
||||
local Parent
|
||||
-- BASE class has no parent
|
||||
if Child.ClassName == 'BASE' then
|
||||
Parent = nil
|
||||
else
|
||||
|
||||
--self:E({FromClass = FromClass})
|
||||
--self:E({Child = Child.ClassName})
|
||||
-- self:E({FromClass = FromClass})
|
||||
-- self:E({Child = Child.ClassName})
|
||||
if FromClass then
|
||||
while( Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName ) do
|
||||
while (Child.ClassName ~= "BASE" and Child.ClassName ~= FromClass.ClassName) do
|
||||
Child = getParent( Child )
|
||||
--self:E({Child.ClassName})
|
||||
-- self:E({Child.ClassName})
|
||||
end
|
||||
end
|
||||
if Child.ClassName == 'BASE' then
|
||||
@@ -325,7 +317,7 @@ function BASE:GetParent( Child, FromClass )
|
||||
Parent = getParent( Child )
|
||||
end
|
||||
end
|
||||
--self:E({Parent.ClassName})
|
||||
-- self:E({Parent.ClassName})
|
||||
return Parent
|
||||
end
|
||||
|
||||
@@ -354,11 +346,11 @@ function BASE:IsInstanceOf( ClassName )
|
||||
-- Get the name of the Moose class as a string
|
||||
ClassName = ClassName.ClassName
|
||||
|
||||
-- className is neither a string nor a Moose class, throw an error
|
||||
-- className is neither a string nor a Moose class, throw an error
|
||||
else
|
||||
|
||||
-- I'm not sure if this should take advantage of MOOSE logging function, or throw an error for pcall
|
||||
local err_str = 'className parameter should be a string; parameter received: '..type( ClassName )
|
||||
local err_str = 'className parameter should be a string; parameter received: ' .. type( ClassName )
|
||||
self:E( err_str )
|
||||
-- error( err_str )
|
||||
return false
|
||||
@@ -372,7 +364,7 @@ function BASE:IsInstanceOf( ClassName )
|
||||
return true
|
||||
end
|
||||
|
||||
local Parent = getParent(self)
|
||||
local Parent = getParent( self )
|
||||
|
||||
while Parent do
|
||||
|
||||
@@ -419,7 +411,6 @@ do -- Event Handling
|
||||
return _EVENTDISPATCHER
|
||||
end
|
||||
|
||||
|
||||
--- Get the Class @{Event} processing Priority.
|
||||
-- The Event processing Priority is a number from 1 to 10,
|
||||
-- reflecting the order of the classes subscribed to the Event to be processed.
|
||||
@@ -474,35 +465,35 @@ do -- Event Handling
|
||||
|
||||
-- Event handling function prototypes
|
||||
|
||||
--- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or autocannon based weapon, those are handled by EVENT.ShootingStart.
|
||||
--- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or auto cannon based weapon, those are handled by EVENT.ShootingStart.
|
||||
-- @function [parent=#BASE] OnEventShot
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs whenever an object is hit by a weapon.
|
||||
-- initiator : The unit object the fired the weapon
|
||||
-- weapon: Weapon object that hit the target
|
||||
-- initiator : The unit object the fired the weapon.
|
||||
-- weapon: Weapon object that hit the target.
|
||||
-- target: The Object that was hit.
|
||||
-- @function [parent=#BASE] OnEventHit
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an aircraft takes off from an airbase, farp, or ship.
|
||||
-- initiator : The unit that tookoff
|
||||
-- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships
|
||||
-- initiator : The unit that took off.
|
||||
-- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships.
|
||||
-- @function [parent=#BASE] OnEventTakeoff
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an aircraft lands at an airbase, farp or ship
|
||||
-- initiator : The unit that has landed
|
||||
-- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships
|
||||
-- initiator : The unit that has landed.
|
||||
-- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships.
|
||||
-- @function [parent=#BASE] OnEventLand
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any aircraft crashes into the ground and is completely destroyed.
|
||||
-- initiator : The unit that has crashed
|
||||
-- initiator : The unit that has crashed.
|
||||
-- @function [parent=#BASE] OnEventCrash
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
@@ -538,18 +529,18 @@ do -- Event Handling
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a ground unit captures either an airbase or a farp.
|
||||
-- initiator : The unit that captured the base
|
||||
-- initiator : The unit that captured the base.
|
||||
-- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction.
|
||||
-- @function [parent=#BASE] OnEventBaseCaptured
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a mission starts
|
||||
--- Occurs when a mission starts.
|
||||
-- @function [parent=#BASE] OnEventMissionStart
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a mission ends
|
||||
--- Occurs when a mission ends.
|
||||
-- @function [parent=#BASE] OnEventMissionEnd
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
@@ -561,25 +552,25 @@ do -- Event Handling
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any object is spawned into the mission.
|
||||
-- initiator : The unit that was spawned
|
||||
-- initiator : The unit that was spawned.
|
||||
-- @function [parent=#BASE] OnEventBirth
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any system fails on a human controlled aircraft.
|
||||
-- initiator : The unit that had the failure
|
||||
-- initiator : The unit that had the failure.
|
||||
-- @function [parent=#BASE] OnEventHumanFailure
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any aircraft starts its engines.
|
||||
-- initiator : The unit that is starting its engines.
|
||||
-- initiator : The unit that is starting its engines..
|
||||
-- @function [parent=#BASE] OnEventEngineStartup
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any aircraft shuts down its engines.
|
||||
-- initiator : The unit that is stopping its engines.
|
||||
-- initiator : The unit that is stopping its engines..
|
||||
-- @function [parent=#BASE] OnEventEngineShutdown
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
@@ -596,7 +587,7 @@ do -- Event Handling
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns.
|
||||
--- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), auto cannons, and machine guns.
|
||||
-- initiator : The unit that is doing the shooting.
|
||||
-- target: The unit that is being targeted.
|
||||
-- @function [parent=#BASE] OnEventShootingStart
|
||||
@@ -627,7 +618,6 @@ do -- Event Handling
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
|
||||
--- Unknown precisely what creates this event, likely tied into newer damage model. Will update this page when new information become available.
|
||||
--
|
||||
-- * initiator: The unit that had the failure.
|
||||
@@ -644,7 +634,7 @@ do -- Event Handling
|
||||
|
||||
--- Occurs on the death of a unit. Contains more and different information. Similar to unit_lost it will occur for aircraft before the aircraft crash event occurs.
|
||||
--
|
||||
-- * initiator: The unit that killed the target
|
||||
-- * initiator: The unit that killed the target.
|
||||
-- * target: Target Object
|
||||
-- * weapon: Weapon Object
|
||||
--
|
||||
@@ -706,7 +696,6 @@ do -- Event Handling
|
||||
-- @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.
|
||||
@@ -716,7 +705,6 @@ do -- Event Handling
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Creation of a Birth Event.
|
||||
-- @param #BASE self
|
||||
-- @param DCS#Time EventTime The time stamp of the event.
|
||||
@@ -725,18 +713,18 @@ end
|
||||
-- @param place
|
||||
-- @param subplace
|
||||
function BASE:CreateEventBirth( EventTime, Initiator, IniUnitName, place, subplace )
|
||||
self:F( { EventTime, Initiator, IniUnitName, place, subplace } )
|
||||
self:F( { EventTime, Initiator, IniUnitName, place, subplace } )
|
||||
|
||||
local Event = {
|
||||
id = world.event.S_EVENT_BIRTH,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
IniUnitName = IniUnitName,
|
||||
place = place,
|
||||
subplace = subplace
|
||||
}
|
||||
local Event = {
|
||||
id = world.event.S_EVENT_BIRTH,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
IniUnitName = IniUnitName,
|
||||
place = place,
|
||||
subplace = subplace,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a Crash Event.
|
||||
@@ -744,15 +732,15 @@ end
|
||||
-- @param DCS#Time EventTime The time stamp of the event.
|
||||
-- @param DCS#Object Initiator The initiating object of the event.
|
||||
function BASE:CreateEventCrash( EventTime, Initiator )
|
||||
self:F( { EventTime, Initiator } )
|
||||
self:F( { EventTime, Initiator } )
|
||||
|
||||
local Event = {
|
||||
id = world.event.S_EVENT_CRASH,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
}
|
||||
local Event = {
|
||||
id = world.event.S_EVENT_CRASH,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a Dead Event.
|
||||
@@ -766,7 +754,7 @@ function BASE:CreateEventDead( EventTime, Initiator )
|
||||
id = world.event.S_EVENT_DEAD,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -782,7 +770,7 @@ function BASE:CreateEventRemoveUnit( EventTime, Initiator )
|
||||
id = EVENTS.RemoveUnit,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -798,56 +786,56 @@ function BASE:CreateEventTakeoff( EventTime, Initiator )
|
||||
id = world.event.S_EVENT_TAKEOFF,
|
||||
time = EventTime,
|
||||
initiator = 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 } )
|
||||
--- 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()
|
||||
}
|
||||
local Event = {
|
||||
id = EVENTS.PlayerEnterAircraft,
|
||||
time = timer.getTime(),
|
||||
initiator = PlayerUnit:GetDCSObject(),
|
||||
}
|
||||
|
||||
world.onEvent(Event)
|
||||
end
|
||||
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)
|
||||
function BASE:onEvent( event )
|
||||
|
||||
if self then
|
||||
if self then
|
||||
|
||||
for EventID, EventObject in pairs(self.Events) do
|
||||
if EventObject.EventEnabled then
|
||||
for EventID, EventObject in pairs( self.Events ) do
|
||||
if EventObject.EventEnabled then
|
||||
|
||||
if event.id == EventObject.Event then
|
||||
if event.id == EventObject.Event then
|
||||
|
||||
if self == EventObject.Self then
|
||||
if self == EventObject.Self then
|
||||
|
||||
if event.initiator and event.initiator:isExist() then
|
||||
event.IniUnitName = event.initiator:getName()
|
||||
end
|
||||
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
|
||||
if event.target and event.target:isExist() then
|
||||
event.TgtUnitName = event.target:getName()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do -- Scheduling
|
||||
@@ -865,23 +853,23 @@ do -- Scheduling
|
||||
local ObjectName = "-"
|
||||
ObjectName = self.ClassName .. self.ClassID
|
||||
|
||||
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
|
||||
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
|
||||
|
||||
if not self.Scheduler then
|
||||
self.Scheduler = SCHEDULER:New( self )
|
||||
end
|
||||
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
{ ... },
|
||||
Start,
|
||||
nil,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
self,
|
||||
SchedulerFunction,
|
||||
{ ... },
|
||||
Start,
|
||||
nil,
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
|
||||
self._.Schedules[#self._.Schedules+1] = ScheduleID
|
||||
self._.Schedules[#self._.Schedules + 1] = ScheduleID
|
||||
|
||||
return self._.Schedules[#self._.Schedules]
|
||||
end
|
||||
@@ -909,17 +897,17 @@ do -- Scheduling
|
||||
end
|
||||
|
||||
local ScheduleID = self.Scheduler:Schedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
{ ... },
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
4
|
||||
)
|
||||
self,
|
||||
SchedulerFunction,
|
||||
{ ... },
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
4
|
||||
)
|
||||
|
||||
self._.Schedules[#self._.Schedules+1] = ScheduleID
|
||||
self._.Schedules[#self._.Schedules + 1] = ScheduleID
|
||||
|
||||
return self._.Schedules[#self._.Schedules]
|
||||
end
|
||||
@@ -938,9 +926,8 @@ do -- Scheduling
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Set a state or property of the Object given a Key and a Value.
|
||||
-- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone.
|
||||
-- Note that if the Object is destroyed, set to nil, or garbage collected, then the Values and Keys will also be gone.
|
||||
-- @param #BASE self
|
||||
-- @param Object The object that will hold the Value set by the Key.
|
||||
-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type!
|
||||
@@ -956,9 +943,8 @@ function BASE:SetState( Object, Key, Value )
|
||||
return self.States[ClassNameAndID][Key]
|
||||
end
|
||||
|
||||
|
||||
--- Get a Value given a Key from the Object.
|
||||
-- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone.
|
||||
-- Note that if the Object is destroyed, set to nil, or garbage collected, then the Values and Keys will also be gone.
|
||||
-- @param #BASE self
|
||||
-- @param Object The object that holds the Value set by the Key.
|
||||
-- @param Key The key that is used to retrieve the value. Note that the key can be a #string, but it can also be any other type!
|
||||
@@ -1010,8 +996,6 @@ function BASE:TraceOff()
|
||||
self:TraceOnOff( false )
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Set trace on or off
|
||||
-- Note that when trace is off, no BASE.Debug statement is performed, increasing performance!
|
||||
-- When Moose is loaded statically, (as one file), tracing is switched off by default.
|
||||
@@ -1020,28 +1004,29 @@ end
|
||||
-- @param #BASE self
|
||||
-- @param #boolean TraceOnOff Switch the tracing on or off.
|
||||
-- @usage
|
||||
-- -- Switch the tracing On
|
||||
-- BASE:TraceOnOff( true )
|
||||
--
|
||||
-- -- Switch the tracing Off
|
||||
-- BASE:TraceOnOff( false )
|
||||
-- -- Switch the tracing On
|
||||
-- BASE:TraceOnOff( true )
|
||||
--
|
||||
-- -- Switch the tracing Off
|
||||
-- BASE:TraceOnOff( false )
|
||||
--
|
||||
function BASE:TraceOnOff( TraceOnOff )
|
||||
if TraceOnOff==false then
|
||||
if TraceOnOff == false then
|
||||
self:I( "Tracing in MOOSE is OFF" )
|
||||
_TraceOnOff = false
|
||||
else
|
||||
self:I( "Tracing in MOOSE is ON" )
|
||||
self:I( "Tracing in MOOSE is ON" )
|
||||
_TraceOnOff = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Enquires if tracing is on (for the class).
|
||||
-- @param #BASE self
|
||||
-- @return #boolean
|
||||
function BASE:IsTrace()
|
||||
|
||||
if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
|
||||
if BASE.Debug and (_TraceAll == true) or (_TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName]) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
@@ -1061,8 +1046,8 @@ end
|
||||
-- @param #boolean TraceAll true = trace all methods in MOOSE.
|
||||
function BASE:TraceAll( TraceAll )
|
||||
|
||||
if TraceAll==false then
|
||||
_TraceAll=false
|
||||
if TraceAll == false then
|
||||
_TraceAll = false
|
||||
else
|
||||
_TraceAll = true
|
||||
end
|
||||
@@ -1101,7 +1086,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
|
||||
|
||||
if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
|
||||
if BASE.Debug and (_TraceAll == true) or (_TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName]) then
|
||||
|
||||
local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo( 3, "l" )
|
||||
@@ -1120,7 +1105,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1140,7 +1125,6 @@ function BASE:F( Arguments )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Trace a function call level 2. Must be at the beginning of the function logic.
|
||||
-- @param #BASE self
|
||||
-- @param Arguments A #table or any field.
|
||||
@@ -1176,28 +1160,28 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
|
||||
|
||||
if BASE.Debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then
|
||||
if BASE.Debug and (_TraceAll == true) or (_TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName]) then
|
||||
|
||||
local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
local Function = "function"
|
||||
if DebugInfoCurrent.name then
|
||||
Function = DebugInfoCurrent.name
|
||||
end
|
||||
local Function = "function"
|
||||
if DebugInfoCurrent.name then
|
||||
Function = DebugInfoCurrent.name
|
||||
end
|
||||
|
||||
if _TraceAll == true or _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName].Method[Function] then
|
||||
local LineCurrent = 0
|
||||
if DebugInfoCurrent.currentline then
|
||||
LineCurrent = DebugInfoCurrent.currentline
|
||||
end
|
||||
local LineFrom = 0
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
local LineFrom = 0
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Trace a function logic level 1. Can be anywhere within the function logic.
|
||||
@@ -1215,7 +1199,6 @@ function BASE:T( Arguments )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Trace a function logic level 2. Can be anywhere within the function logic.
|
||||
-- @param #BASE self
|
||||
-- @param Arguments A #table or any field.
|
||||
@@ -1252,28 +1235,27 @@ end
|
||||
function BASE:E( Arguments )
|
||||
|
||||
if BASE.Debug then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
local Function = "function"
|
||||
if DebugInfoCurrent.name then
|
||||
Function = DebugInfoCurrent.name
|
||||
end
|
||||
local Function = "function"
|
||||
if DebugInfoCurrent.name then
|
||||
Function = DebugInfoCurrent.name
|
||||
end
|
||||
|
||||
local LineCurrent = DebugInfoCurrent.currentline
|
||||
local LineCurrent = DebugInfoCurrent.currentline
|
||||
local LineFrom = -1
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
else
|
||||
env.info( string.format( "%1s:%30s%05d(%s)" , "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Log an information which will be traced always. Can be anywhere within the function logic.
|
||||
-- @param #BASE self
|
||||
-- @param Arguments A #table or any field.
|
||||
@@ -1294,26 +1276,23 @@ function BASE:I( Arguments )
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
else
|
||||
env.info( string.format( "%1s:%30s%05d(%s)" , "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- old stuff
|
||||
|
||||
--function BASE:_Destructor()
|
||||
-- function BASE:_Destructor()
|
||||
-- --self:E("_Destructor")
|
||||
--
|
||||
-- --self:EventRemoveAll()
|
||||
--end
|
||||
|
||||
-- end
|
||||
|
||||
-- THIS IS WHY WE NEED LUA 5.2 ...
|
||||
--function BASE:_SetDestructor()
|
||||
-- function BASE:_SetDestructor()
|
||||
--
|
||||
-- -- TODO: Okay, this is really technical...
|
||||
-- -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak...
|
||||
@@ -1334,4 +1313,4 @@ end
|
||||
-- -- will be invoked and the destructor called
|
||||
-- rawset( self, '__proxy', proxy )
|
||||
--
|
||||
--end
|
||||
-- end
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
--- **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 Omnidirectional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glide slope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glide slope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number 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 scheduler.
|
||||
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 scheduler
|
||||
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 stopping it!" } )
|
||||
else
|
||||
self.Positionable:SetCommand( {
|
||||
id = 'DeactivateBeacon',
|
||||
params = {},
|
||||
} )
|
||||
end
|
||||
end
|
||||
|
||||
--- Activates a general purpose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **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 Frequency
|
||||
-- @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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
||||
-- 
|
||||
--
|
||||
-- Within a running mission, various DCS events occur. Units are dynamically created, crash, die, shoot stuff, get hit etc.
|
||||
-- This module provides a mechanism to dispatch those events occuring within your running mission, to the different objects orchestrating your mission.
|
||||
-- This module provides a mechanism to dispatch those events occurring within your running mission, to the different objects orchestrating your mission.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -141,7 +141,6 @@
|
||||
-- EventData.IniUnit:SmokeGreen()
|
||||
-- end
|
||||
--
|
||||
--
|
||||
-- Find below an overview which events populate which information categories:
|
||||
--
|
||||
-- 
|
||||
@@ -172,7 +171,6 @@
|
||||
-- @module Core.Event
|
||||
-- @image Core_Event.JPG
|
||||
|
||||
|
||||
--- @type EVENT
|
||||
-- @field #EVENT.Events Events
|
||||
-- @extends Core.Base#BASE
|
||||
@@ -194,7 +192,6 @@ world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1005
|
||||
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1006
|
||||
world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT = world.event.S_EVENT_MAX + 1007
|
||||
|
||||
|
||||
--- The different types of events supported by MOOSE.
|
||||
-- Use this structure to subscribe to events using the @{Core.Base#BASE.HandleEvent}() method.
|
||||
-- @type EVENTS
|
||||
@@ -227,20 +224,20 @@ EVENTS = {
|
||||
MarkChange = world.event.S_EVENT_MARK_CHANGE,
|
||||
MarkRemoved = world.event.S_EVENT_MARK_REMOVED,
|
||||
-- Moose Events
|
||||
NewCargo = world.event.S_EVENT_NEW_CARGO,
|
||||
DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
|
||||
NewZone = world.event.S_EVENT_NEW_ZONE,
|
||||
DeleteZone = world.event.S_EVENT_DELETE_ZONE,
|
||||
NewZoneGoal = world.event.S_EVENT_NEW_ZONE_GOAL,
|
||||
DeleteZoneGoal = world.event.S_EVENT_DELETE_ZONE_GOAL,
|
||||
RemoveUnit = world.event.S_EVENT_REMOVE_UNIT,
|
||||
NewCargo = world.event.S_EVENT_NEW_CARGO,
|
||||
DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
|
||||
NewZone = world.event.S_EVENT_NEW_ZONE,
|
||||
DeleteZone = world.event.S_EVENT_DELETE_ZONE,
|
||||
NewZoneGoal = world.event.S_EVENT_NEW_ZONE_GOAL,
|
||||
DeleteZoneGoal = world.event.S_EVENT_DELETE_ZONE_GOAL,
|
||||
RemoveUnit = world.event.S_EVENT_REMOVE_UNIT,
|
||||
PlayerEnterAircraft = world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT,
|
||||
-- Added with DCS 2.5.6
|
||||
DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier
|
||||
Kill = world.event.S_EVENT_KILL or -1,
|
||||
Score = world.event.S_EVENT_SCORE or -1,
|
||||
UnitLost = world.event.S_EVENT_UNIT_LOST or -1,
|
||||
LandingAfterEjection = world.event.S_EVENT_LANDING_AFTER_EJECTION or -1,
|
||||
DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier
|
||||
Kill = world.event.S_EVENT_KILL or -1,
|
||||
Score = world.event.S_EVENT_SCORE or -1,
|
||||
UnitLost = world.event.S_EVENT_UNIT_LOST or -1,
|
||||
LandingAfterEjection = world.event.S_EVENT_LANDING_AFTER_EJECTION or -1,
|
||||
-- Added with DCS 2.7.0
|
||||
ParatrooperLanding = world.event.S_EVENT_PARATROOPER_LENDING or -1,
|
||||
DiscardChairAfterEjection = world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or -1,
|
||||
@@ -304,8 +301,6 @@ EVENTS = {
|
||||
-- @field Core.ZONE#ZONE Zone The zone object.
|
||||
-- @field #string ZoneName The name of the zone.
|
||||
|
||||
|
||||
|
||||
local _EVENTMETA = {
|
||||
[world.event.S_EVENT_SHOT] = {
|
||||
Order = 1,
|
||||
@@ -562,7 +557,6 @@ local _EVENTMETA = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
--- The Events structure
|
||||
-- @type EVENT.Events
|
||||
-- @field #number IniUnit
|
||||
@@ -576,12 +570,11 @@ function EVENT:New()
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
-- Add world event handler.
|
||||
self.EventHandler = world.addEventHandler(self)
|
||||
self.EventHandler = world.addEventHandler( self )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Initializes the Events structure for the event.
|
||||
-- @param #EVENT self
|
||||
-- @param DCS#world.event EventID Event ID.
|
||||
@@ -595,7 +588,7 @@ function EVENT:Init( EventID, EventClass )
|
||||
self.Events[EventID] = {}
|
||||
end
|
||||
|
||||
-- Each event has a subtable of EventClasses, ordered by EventPriority.
|
||||
-- Each event has a sub-table of EventClasses, ordered by EventPriority.
|
||||
local EventPriority = EventClass:GetEventPriority()
|
||||
|
||||
if not self.Events[EventID][EventPriority] then
|
||||
@@ -603,7 +596,7 @@ function EVENT:Init( EventID, EventClass )
|
||||
end
|
||||
|
||||
if not self.Events[EventID][EventPriority][EventClass] then
|
||||
self.Events[EventID][EventPriority][EventClass] = {}
|
||||
self.Events[EventID][EventPriority][EventClass] = {}
|
||||
end
|
||||
|
||||
return self.Events[EventID][EventPriority][EventClass]
|
||||
@@ -614,7 +607,7 @@ end
|
||||
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
|
||||
-- @param DCS#world.event EventID Event ID.
|
||||
-- @return #EVENT self
|
||||
function EVENT:RemoveEvent( EventClass, EventID )
|
||||
function EVENT:RemoveEvent( EventClass, EventID )
|
||||
|
||||
-- Debug info.
|
||||
self:F2( { "Removing subscription for class: ", EventClass:GetClassNameAndID() } )
|
||||
@@ -638,7 +631,7 @@ end
|
||||
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
|
||||
-- @param DCS#world.event EventID Event ID.
|
||||
-- @return #EVENT.Events
|
||||
function EVENT:Reset( EventObject ) --R2.1
|
||||
function EVENT:Reset( EventObject ) -- R2.1
|
||||
|
||||
self:F( { "Resetting subscriptions for class: ", EventObject:GetClassNameAndID() } )
|
||||
|
||||
@@ -657,12 +650,11 @@ function EVENT:Reset( EventObject ) --R2.1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Clears all event subscriptions for a @{Core.Base#BASE} derived object.
|
||||
-- @param #EVENT self
|
||||
-- @param Core.Base#BASE EventClass The self class object for which the events are removed.
|
||||
-- @return #EVENT self
|
||||
function EVENT:RemoveAll(EventClass)
|
||||
function EVENT:RemoveAll( EventClass )
|
||||
|
||||
local EventClassName = EventClass:GetClassNameAndID()
|
||||
|
||||
@@ -676,8 +668,6 @@ function EVENT:RemoveAll(EventClass)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Create an OnDead event handler for a group
|
||||
-- @param #EVENT self
|
||||
-- @param #table EventTemplate
|
||||
@@ -709,7 +699,6 @@ function EVENT:OnEventGeneric( EventFunction, EventClass, EventID )
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set a new listener for an `S_EVENT_X` event for a UNIT.
|
||||
-- @param #EVENT self
|
||||
-- @param #string UnitName The name of the UNIT.
|
||||
@@ -797,7 +786,6 @@ do -- OnDead
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- OnLand
|
||||
|
||||
--- Create an OnLand event handler for a group
|
||||
@@ -864,7 +852,7 @@ do -- Event Creation
|
||||
id = EVENTS.NewCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -879,7 +867,7 @@ do -- Event Creation
|
||||
id = EVENTS.DeleteCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -894,7 +882,7 @@ do -- Event Creation
|
||||
id = EVENTS.NewZone,
|
||||
time = timer.getTime(),
|
||||
zone = Zone,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -909,7 +897,7 @@ do -- Event Creation
|
||||
id = EVENTS.DeleteZone,
|
||||
time = timer.getTime(),
|
||||
zone = Zone,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -924,12 +912,11 @@ do -- Event Creation
|
||||
id = EVENTS.NewZoneGoal,
|
||||
time = timer.getTime(),
|
||||
ZoneGoal = ZoneGoal,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
|
||||
--- Creation of a ZoneGoal Deletion Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created.
|
||||
@@ -940,12 +927,11 @@ do -- Event Creation
|
||||
id = EVENTS.DeleteZoneGoal,
|
||||
time = timer.getTime(),
|
||||
ZoneGoal = ZoneGoal,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
|
||||
--- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.Unit#UNIT PlayerUnit.
|
||||
@@ -955,8 +941,8 @@ do -- Event Creation
|
||||
local Event = {
|
||||
id = EVENTS.PlayerEnterUnit,
|
||||
time = timer.getTime(),
|
||||
initiator = PlayerUnit:GetDCSObject()
|
||||
}
|
||||
initiator = PlayerUnit:GetDCSObject(),
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -970,8 +956,8 @@ do -- Event Creation
|
||||
local Event = {
|
||||
id = EVENTS.PlayerEnterAircraft,
|
||||
time = timer.getTime(),
|
||||
initiator = PlayerUnit:GetDCSObject()
|
||||
}
|
||||
initiator = PlayerUnit:GetDCSObject(),
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -993,18 +979,13 @@ function EVENT:onEvent( Event )
|
||||
return errmsg
|
||||
end
|
||||
|
||||
|
||||
-- Get event meta data.
|
||||
local EventMeta = _EVENTMETA[Event.id]
|
||||
|
||||
-- Check if this is a known event?
|
||||
if EventMeta then
|
||||
|
||||
if self and
|
||||
self.Events and
|
||||
self.Events[Event.id] and
|
||||
self.MissionEnd == false and
|
||||
( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then
|
||||
if self and self.Events and self.Events[Event.id] and self.MissionEnd == false and (Event.initiator ~= nil or (Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit)) then
|
||||
|
||||
if Event.id and Event.id == EVENTS.MissionEnd then
|
||||
self.MissionEnd = true
|
||||
@@ -1028,9 +1009,9 @@ function EVENT:onEvent( Event )
|
||||
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
|
||||
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
|
||||
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
|
||||
--if Event.IniGroup then
|
||||
Event.IniGroupName = Event.IniDCSGroupName
|
||||
--end
|
||||
-- if Event.IniGroup then
|
||||
Event.IniGroupName = Event.IniDCSGroupName
|
||||
-- end
|
||||
end
|
||||
Event.IniPlayerName = Event.IniDCSUnit:getPlayerName()
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
@@ -1039,23 +1020,24 @@ function EVENT:onEvent( Event )
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.STATIC then
|
||||
if Event.id==31 then
|
||||
--env.info("FF event 31")
|
||||
-- Event.initiator is a Static object representing the pilot. But getName() error due to DCS bug.
|
||||
|
||||
if Event.id == 31 then
|
||||
|
||||
-- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug.
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
local ID=Event.initiator.id_
|
||||
Event.IniDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID))
|
||||
local ID = Event.initiator.id_
|
||||
Event.IniDCSUnitName = string.format( "Ejected Pilot ID %s", tostring( ID ) )
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniCoalition = 0
|
||||
Event.IniCategory = 0
|
||||
Event.IniCategory = 0
|
||||
Event.IniTypeName = "Ejected Pilot"
|
||||
elseif Event.id == 33 then -- ejection seat discarded
|
||||
elseif Event.id == 33 then -- ejection seat discarded
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
local ID=Event.initiator.id_
|
||||
Event.IniDCSUnitName = string.format("Ejection Seat ID %s", tostring(ID))
|
||||
local ID = Event.initiator.id_
|
||||
Event.IniDCSUnitName = string.format( "Ejection Seat ID %s", tostring( ID ) )
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniCoalition = 0
|
||||
Event.IniCategory = 0
|
||||
Event.IniCategory = 0
|
||||
Event.IniTypeName = "Ejection Seat"
|
||||
else
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
@@ -1091,7 +1073,7 @@ function EVENT:onEvent( Event )
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName)
|
||||
Event.IniUnit = AIRBASE:FindByName( Event.IniDCSUnitName )
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
@@ -1112,9 +1094,9 @@ function EVENT:onEvent( Event )
|
||||
if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then
|
||||
Event.TgtDCSGroupName = Event.TgtDCSGroup:getName()
|
||||
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
|
||||
--if Event.TgtGroup then
|
||||
Event.TgtGroupName = Event.TgtDCSGroupName
|
||||
--end
|
||||
-- if Event.TgtGroup then
|
||||
Event.TgtGroupName = Event.TgtDCSGroupName
|
||||
-- end
|
||||
end
|
||||
Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName()
|
||||
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
|
||||
@@ -1123,34 +1105,33 @@ function EVENT:onEvent( Event )
|
||||
end
|
||||
|
||||
if Event.TgtObjectCategory == Object.Category.STATIC then
|
||||
BASE:T({StaticTgtEvent = Event.id})
|
||||
-- get base data
|
||||
Event.TgtDCSUnit = Event.target
|
||||
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
Event.TgtDCSUnit = Event.target
|
||||
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
|
||||
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
else
|
||||
Event.TgtDCSUnitName = string.format( "No target object for Event ID %s", tostring( Event.id ) )
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = nil
|
||||
Event.TgtCoalition = 0
|
||||
Event.TgtCategory = 0
|
||||
if Event.id == 6 then
|
||||
Event.TgtTypeName = "Ejected Pilot"
|
||||
Event.TgtDCSUnitName = string.format( "Ejected Pilot ID %s", tostring( Event.IniDCSUnitName ) )
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
elseif Event.id == 33 then
|
||||
Event.TgtTypeName = "Ejection Seat"
|
||||
Event.TgtDCSUnitName = string.format( "Ejection Seat ID %s", tostring( Event.IniDCSUnitName ) )
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
|
||||
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
else
|
||||
Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id))
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = nil
|
||||
Event.TgtCoalition = 0
|
||||
Event.TgtCategory = 0
|
||||
if Event.id == 6 then
|
||||
Event.TgtTypeName = "Ejected Pilot"
|
||||
Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(Event.IniDCSUnitName))
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
elseif Event.id == 33 then
|
||||
Event.TgtTypeName = "Ejection Seat"
|
||||
Event.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName))
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
else
|
||||
Event.TgtTypeName = "Static"
|
||||
end
|
||||
end
|
||||
Event.TgtTypeName = "Static"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Event.TgtObjectCategory == Object.Category.SCENERY then
|
||||
@@ -1171,29 +1152,29 @@ function EVENT:onEvent( Event )
|
||||
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition()
|
||||
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category
|
||||
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()
|
||||
--Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
|
||||
-- Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
|
||||
end
|
||||
|
||||
-- Place should be given for takeoff and landing events as well as base captured. It should be a DCS airbase.
|
||||
if Event.place then
|
||||
if Event.id==EVENTS.LandingAfterEjection then
|
||||
if Event.id == EVENTS.LandingAfterEjection then
|
||||
-- Place is here the UNIT of which the pilot ejected.
|
||||
--local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
|
||||
-- local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
|
||||
-- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground.
|
||||
--Event.Place=UNIT:Find(Event.place)
|
||||
-- Event.Place=UNIT:Find(Event.place)
|
||||
else
|
||||
Event.Place=AIRBASE:Find(Event.place)
|
||||
Event.PlaceName=Event.Place:GetName()
|
||||
Event.Place = AIRBASE:Find( Event.place )
|
||||
Event.PlaceName = Event.Place:GetName()
|
||||
end
|
||||
end
|
||||
|
||||
-- Mark points.
|
||||
if Event.idx then
|
||||
Event.MarkID=Event.idx
|
||||
Event.MarkVec3=Event.pos
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
Event.MarkText=Event.text
|
||||
Event.MarkCoalition=Event.coalition
|
||||
Event.MarkID = Event.idx
|
||||
Event.MarkVec3 = Event.pos
|
||||
Event.MarkCoordinate = COORDINATE:NewFromVec3( Event.pos )
|
||||
Event.MarkText = Event.text
|
||||
Event.MarkCoalition = Event.coalition
|
||||
Event.MarkGroupID = Event.groupID
|
||||
end
|
||||
|
||||
@@ -1222,9 +1203,9 @@ function EVENT:onEvent( Event )
|
||||
-- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called.
|
||||
for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do
|
||||
|
||||
--if Event.IniObjectCategory ~= Object.Category.STATIC then
|
||||
-- if Event.IniObjectCategory ~= Object.Category.STATIC then
|
||||
-- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
|
||||
--end
|
||||
-- end
|
||||
|
||||
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
|
||||
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
|
||||
@@ -1241,8 +1222,8 @@ function EVENT:onEvent( Event )
|
||||
|
||||
local UnitName = EventClass:GetName()
|
||||
|
||||
if ( EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName ) or
|
||||
( EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName ) then
|
||||
if (EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName) or
|
||||
(EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName) then
|
||||
|
||||
-- First test if a EventFunction is Set, otherwise search for the default function
|
||||
if EventData.EventFunction then
|
||||
@@ -1251,15 +1232,14 @@ function EVENT:onEvent( Event )
|
||||
self:F( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventData.EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall( function()
|
||||
return EventData.EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
|
||||
else
|
||||
|
||||
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
|
||||
local EventFunction = EventClass[ EventMeta.Event ]
|
||||
local EventFunction = EventClass[EventMeta.Event]
|
||||
if EventFunction and type( EventFunction ) == "function" then
|
||||
|
||||
-- Now call the default event function.
|
||||
@@ -1267,10 +1247,9 @@ function EVENT:onEvent( Event )
|
||||
self:F( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall( function()
|
||||
return EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1294,8 +1273,8 @@ function EVENT:onEvent( Event )
|
||||
-- We can get the name of the EventClass, which is now always a GROUP object.
|
||||
local GroupName = EventClass:GetName()
|
||||
|
||||
if ( EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName ) or
|
||||
( EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName ) then
|
||||
if (EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName) or
|
||||
(EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName) then
|
||||
|
||||
-- First test if a EventFunction is Set, otherwise search for the default function
|
||||
if EventData.EventFunction then
|
||||
@@ -1304,15 +1283,14 @@ function EVENT:onEvent( Event )
|
||||
self:F( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall( function()
|
||||
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
|
||||
end, ErrorHandler )
|
||||
|
||||
else
|
||||
|
||||
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
|
||||
local EventFunction = EventClass[ EventMeta.Event ]
|
||||
local EventFunction = EventClass[EventMeta.Event]
|
||||
if EventFunction and type( EventFunction ) == "function" then
|
||||
|
||||
-- Now call the default event function.
|
||||
@@ -1320,16 +1298,15 @@ function EVENT:onEvent( Event )
|
||||
self:F( { "Calling " .. EventMeta.Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall( function()
|
||||
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
|
||||
end, ErrorHandler )
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
|
||||
--self:RemoveEvent( EventClass, Event.id )
|
||||
-- self:RemoveEvent( EventClass, Event.id )
|
||||
end
|
||||
else
|
||||
|
||||
@@ -1344,14 +1321,13 @@ function EVENT:onEvent( Event )
|
||||
if Event.IniObjectCategory ~= 3 then
|
||||
self:F2( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } )
|
||||
end
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventData.EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall( function()
|
||||
return EventData.EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
else
|
||||
|
||||
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
|
||||
local EventFunction = EventClass[ EventMeta.Event ]
|
||||
local EventFunction = EventClass[EventMeta.Event]
|
||||
if EventFunction and type( EventFunction ) == "function" then
|
||||
|
||||
-- Now call the default event function.
|
||||
@@ -1359,11 +1335,10 @@ function EVENT:onEvent( Event )
|
||||
self:F2( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
local Result, Value = EventFunction( EventClass, Event )
|
||||
return Result, Value
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall( function()
|
||||
local Result, Value = EventFunction( EventClass, Event )
|
||||
return Result, Value
|
||||
end, ErrorHandler )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1387,7 +1362,7 @@ function EVENT:onEvent( Event )
|
||||
self:T( { EventMeta.Text, Event } )
|
||||
end
|
||||
else
|
||||
self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?", tostring(Event.id)))
|
||||
self:E( string.format( "WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?", tostring( Event.id ) ) )
|
||||
end
|
||||
|
||||
Event = nil
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
--
|
||||
-- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections.
|
||||
--
|
||||
-- ##__Dislaimer:__
|
||||
-- ##__Disclaimer:__
|
||||
-- The FSM class development is based on a finite state machine implementation made by Conroy Kyle.
|
||||
-- The state machine can be found on [github](https://github.com/kyleconroy/lua-state-machine)
|
||||
-- I've reworked this development (taken the concept), and created a **hierarchical state machine** out of it, embedded within the DCS simulator.
|
||||
@@ -69,7 +69,6 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
@@ -89,7 +88,6 @@ do -- FSM
|
||||
-- @field #string current Current state name.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**.
|
||||
--
|
||||
-- A FSM can only be in one of a finite number of states.
|
||||
@@ -145,12 +143,12 @@ do -- FSM
|
||||
-- Most of the time, these Event Triggers are used within the Transition Handler methods, so that a workflow is created running through the state machine.
|
||||
--
|
||||
-- As explained above, a FSM supports **Linear State Transitions** and **Hierarchical State Transitions**, and both can be mixed to make a comprehensive FSM implementation.
|
||||
-- The below documentation has a seperate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**.
|
||||
-- The below documentation has a separate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**.
|
||||
--
|
||||
-- ## FSM Linear Transitions
|
||||
--
|
||||
-- Linear Transitions are Transition Rules allowing an FSM to transition from one or multiple possible **From** state(s) towards a **To** state upon a Triggered **Event**.
|
||||
-- The Lineair transition rule evaluation will always be done from the **current state** of the FSM.
|
||||
-- The Linear transition rule evaluation will always be done from the **current state** of the FSM.
|
||||
-- If no valid Transition Rule can be found in the FSM, the FSM will log an error and stop.
|
||||
--
|
||||
-- ### FSM Transition Rules
|
||||
@@ -185,7 +183,7 @@ do -- FSM
|
||||
-- * The From states can be **a table of strings**, indicating that the transition rule will be valid **if the current state** of the FSM will be **one of the given From states**.
|
||||
-- * The From state can be a **"*"**, indicating that **the transition rule will always be valid**, regardless of the current state of the FSM.
|
||||
--
|
||||
-- The below code snippet shows how the two last lines can be rewritten and consensed.
|
||||
-- The below code snippet shows how the two last lines can be rewritten and condensed.
|
||||
--
|
||||
-- FsmSwitch:AddTransition( { "On", "Middle" }, "SwitchOff", "Off" )
|
||||
--
|
||||
@@ -221,7 +219,7 @@ do -- FSM
|
||||
-- * The method **FSM:Event()** triggers an Event that will be processed **synchronously** or **immediately**.
|
||||
-- * The method **FSM:__Event( __seconds__ )** triggers an Event that will be processed **asynchronously** over time, waiting __x seconds__.
|
||||
--
|
||||
-- The destinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time.
|
||||
-- The distinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time.
|
||||
-- Processing will just continue. Synchronous Event Trigger methods are useful to change states of the FSM immediately, but may have a larger processing impact.
|
||||
--
|
||||
-- The following example provides a little demonstration on the difference between synchronous and asynchronous Event Triggering.
|
||||
@@ -345,7 +343,6 @@ do -- FSM
|
||||
-- ===
|
||||
--
|
||||
-- @field #FSM
|
||||
--
|
||||
FSM = {
|
||||
ClassName = "FSM",
|
||||
}
|
||||
@@ -379,7 +376,6 @@ do -- FSM
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Sets the start state of the FSM.
|
||||
-- @param #FSM self
|
||||
-- @param #string State A string defining the start state.
|
||||
@@ -388,7 +384,6 @@ do -- FSM
|
||||
self.current = State
|
||||
end
|
||||
|
||||
|
||||
--- Returns the start state of the FSM.
|
||||
-- @param #FSM self
|
||||
-- @return #string A string containing the start state.
|
||||
@@ -416,7 +411,6 @@ do -- FSM
|
||||
self:_eventmap( self.Events, Transition )
|
||||
end
|
||||
|
||||
|
||||
--- Returns a table of the transition rules defined within the FSM.
|
||||
-- @param #FSM self
|
||||
-- @return #table Transitions.
|
||||
@@ -450,7 +444,6 @@ do -- FSM
|
||||
return Process
|
||||
end
|
||||
|
||||
|
||||
--- Returns a table of the SubFSM rules defined within the FSM.
|
||||
-- @param #FSM self
|
||||
-- @return #table Sub processes.
|
||||
@@ -499,7 +492,6 @@ do -- FSM
|
||||
return self._EndStates or {}
|
||||
end
|
||||
|
||||
|
||||
--- Adds a score for the FSM to be achieved.
|
||||
-- @param #FSM self
|
||||
-- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process).
|
||||
@@ -540,7 +532,7 @@ do -- FSM
|
||||
|
||||
--- Returns a table with the scores defined.
|
||||
-- @param #FSM self
|
||||
-- @param #table Scores.
|
||||
-- @return #table Scores.
|
||||
function FSM:GetScores()
|
||||
return self._Scores or {}
|
||||
end
|
||||
@@ -563,27 +555,27 @@ do -- FSM
|
||||
|
||||
end
|
||||
|
||||
--- Event map.
|
||||
--- Event map.
|
||||
-- @param #FSM self
|
||||
-- @param #table Events Events.
|
||||
-- @param #table EventStructure Event structure.
|
||||
function FSM:_eventmap( Events, EventStructure )
|
||||
|
||||
local Event = EventStructure.Event
|
||||
local __Event = "__" .. EventStructure.Event
|
||||
local Event = EventStructure.Event
|
||||
local __Event = "__" .. EventStructure.Event
|
||||
|
||||
self[Event] = self[Event] or self:_create_transition(Event)
|
||||
self[__Event] = self[__Event] or self:_delayed_transition(Event)
|
||||
self[Event] = self[Event] or self:_create_transition( Event )
|
||||
self[__Event] = self[__Event] or self:_delayed_transition( Event )
|
||||
|
||||
-- Debug message.
|
||||
self:T2( "Added methods: " .. Event .. ", " .. __Event )
|
||||
-- Debug message.
|
||||
self:T2( "Added methods: " .. Event .. ", " .. __Event )
|
||||
|
||||
Events[Event] = self.Events[Event] or { map = {} }
|
||||
self:_add_to_map( Events[Event].map, EventStructure )
|
||||
Events[Event] = self.Events[Event] or { map = {} }
|
||||
self:_add_to_map( Events[Event].map, EventStructure )
|
||||
|
||||
end
|
||||
|
||||
--- Sub maps.
|
||||
--- Sub maps.
|
||||
-- @param #FSM self
|
||||
-- @param #table subs Subs.
|
||||
-- @param #table sub Sub.
|
||||
@@ -613,7 +605,7 @@ do -- FSM
|
||||
-- @param #string EventName Event name.
|
||||
-- @return Value.
|
||||
function FSM:_call_handler( step, trigger, params, EventName )
|
||||
--env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName))
|
||||
-- env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName))
|
||||
|
||||
local handler = step .. trigger
|
||||
|
||||
@@ -644,10 +636,12 @@ do -- FSM
|
||||
return errmsg
|
||||
end
|
||||
|
||||
--return self[handler](self, unpack( params ))
|
||||
-- return self[handler](self, unpack( params ))
|
||||
|
||||
-- Protected call.
|
||||
local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler )
|
||||
local Result, Value = xpcall( function()
|
||||
return self[handler]( self, unpack( params ) )
|
||||
end, ErrorHandler )
|
||||
return Value
|
||||
end
|
||||
|
||||
@@ -671,17 +665,16 @@ do -- FSM
|
||||
local From = self.current
|
||||
|
||||
-- Parameters.
|
||||
local Params = { From, EventName, To, ... }
|
||||
local Params = { From, EventName, To, ... }
|
||||
|
||||
|
||||
if self["onleave".. From] or
|
||||
self["OnLeave".. From] or
|
||||
self["onbefore".. EventName] or
|
||||
self["OnBefore".. EventName] or
|
||||
self["onafter".. EventName] or
|
||||
self["OnAfter".. EventName] or
|
||||
self["onenter".. To] or
|
||||
self["OnEnter".. To] then
|
||||
if self["onleave" .. From] or
|
||||
self["OnLeave" .. From] or
|
||||
self["onbefore" .. EventName] or
|
||||
self["OnBefore" .. EventName] or
|
||||
self["onafter" .. EventName] or
|
||||
self["OnAfter" .. EventName] or
|
||||
self["onenter" .. To] or
|
||||
self["OnEnter" .. To] then
|
||||
|
||||
if self:_call_handler( "onbefore", EventName, Params, EventName ) == false then
|
||||
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onbefore" .. EventName )
|
||||
@@ -716,11 +709,11 @@ do -- FSM
|
||||
end
|
||||
|
||||
if ClassName == "FSM_CONTROLLABLE" then
|
||||
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " )
|
||||
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " )
|
||||
end
|
||||
|
||||
if ClassName == "FSM_PROCESS" then
|
||||
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " )
|
||||
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -733,10 +726,10 @@ do -- FSM
|
||||
|
||||
for _, sub in pairs( subtable ) do
|
||||
|
||||
--if sub.nextevent then
|
||||
-- if sub.nextevent then
|
||||
-- self:F2( "nextevent = " .. sub.nextevent )
|
||||
-- self[sub.nextevent]( self )
|
||||
--end
|
||||
-- end
|
||||
|
||||
self:T( "*** FSM *** Sub *** " .. sub.StartEvent )
|
||||
|
||||
@@ -753,11 +746,11 @@ do -- FSM
|
||||
|
||||
self:T( "*** FSM *** End *** " .. Event )
|
||||
|
||||
self:_call_handler("onenter", To, Params, EventName )
|
||||
self:_call_handler("OnEnter", To, Params, EventName )
|
||||
self:_call_handler("onafter", EventName, Params, EventName )
|
||||
self:_call_handler("OnAfter", EventName, Params, EventName )
|
||||
self:_call_handler("onstate", "change", Params, EventName )
|
||||
self:_call_handler( "onenter", To, Params, EventName )
|
||||
self:_call_handler( "OnEnter", To, Params, EventName )
|
||||
self:_call_handler( "onafter", EventName, Params, EventName )
|
||||
self:_call_handler( "OnAfter", EventName, Params, EventName )
|
||||
self:_call_handler( "onstate", "change", Params, EventName )
|
||||
|
||||
fsmparent[Event]( fsmparent )
|
||||
|
||||
@@ -766,13 +759,13 @@ do -- FSM
|
||||
|
||||
if execute then
|
||||
|
||||
self:_call_handler("onafter", EventName, Params, EventName )
|
||||
self:_call_handler("OnAfter", EventName, Params, EventName )
|
||||
self:_call_handler( "onafter", EventName, Params, EventName )
|
||||
self:_call_handler( "OnAfter", EventName, Params, EventName )
|
||||
|
||||
self:_call_handler("onenter", To, Params, EventName )
|
||||
self:_call_handler("OnEnter", To, Params, EventName )
|
||||
self:_call_handler( "onenter", To, Params, EventName )
|
||||
self:_call_handler( "OnEnter", To, Params, EventName )
|
||||
|
||||
self:_call_handler("onstate", "change", Params, EventName )
|
||||
self:_call_handler( "onstate", "change", Params, EventName )
|
||||
|
||||
end
|
||||
else
|
||||
@@ -809,16 +802,16 @@ do -- FSM
|
||||
self._EventSchedules[EventName] = CallID
|
||||
|
||||
-- Debug output.
|
||||
self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
|
||||
self:T2( string.format( "NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring( CallID ) ) )
|
||||
else
|
||||
self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds))
|
||||
self:T2( string.format( "NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds ) )
|
||||
-- reschedule
|
||||
end
|
||||
else
|
||||
|
||||
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true )
|
||||
|
||||
self:T2(string.format("Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
|
||||
self:T2( string.format( "Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring( CallID ) ) )
|
||||
end
|
||||
else
|
||||
error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." )
|
||||
@@ -835,7 +828,9 @@ do -- FSM
|
||||
-- @param #string EventName Event name.
|
||||
-- @return #function Function.
|
||||
function FSM:_create_transition( EventName )
|
||||
return function( self, ... ) return self._handler( self, EventName , ... ) end
|
||||
return function( self, ... )
|
||||
return self._handler( self, EventName, ... )
|
||||
end
|
||||
end
|
||||
|
||||
--- Go sub.
|
||||
@@ -862,16 +857,16 @@ do -- FSM
|
||||
local FSMParent = self.fsmparent
|
||||
|
||||
if FSMParent and self.endstates[Current] then
|
||||
--self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } )
|
||||
-- self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } )
|
||||
FSMParent.current = Current
|
||||
local ParentFrom = FSMParent.current
|
||||
--self:T( { ParentFrom, self.ReturnEvents } )
|
||||
-- self:T( { ParentFrom, self.ReturnEvents } )
|
||||
local Event = self.ReturnEvents[Current]
|
||||
--self:T( { Event } )
|
||||
-- self:T( { Event } )
|
||||
if Event then
|
||||
return FSMParent, Event
|
||||
else
|
||||
--self:T( { "Could not find parent event name for state ", ParentFrom } )
|
||||
-- self:T( { "Could not find parent event name for state ", ParentFrom } )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -883,17 +878,17 @@ do -- FSM
|
||||
-- @param #table Map Map.
|
||||
-- @param #table Event Event table.
|
||||
function FSM:_add_to_map( Map, Event )
|
||||
self:F3( { Map, Event } )
|
||||
self:F3( { Map, Event } )
|
||||
|
||||
if type(Event.From) == 'string' then
|
||||
Map[Event.From] = Event.To
|
||||
if type( Event.From ) == 'string' then
|
||||
Map[Event.From] = Event.To
|
||||
else
|
||||
for _, From in ipairs(Event.From) do
|
||||
Map[From] = Event.To
|
||||
for _, From in ipairs( Event.From ) do
|
||||
Map[From] = Event.To
|
||||
end
|
||||
end
|
||||
|
||||
self:T3( { Map, Event } )
|
||||
self:T3( { Map, Event } )
|
||||
end
|
||||
|
||||
--- Get current state.
|
||||
@@ -922,7 +917,7 @@ do -- FSM
|
||||
-- @param #FSM self
|
||||
-- @param #string State State name.
|
||||
-- @param #boolean If true, FSM is in this state.
|
||||
function FSM:is(state)
|
||||
function FSM:is( state )
|
||||
return self.current == state
|
||||
end
|
||||
|
||||
@@ -931,11 +926,11 @@ do -- FSM
|
||||
-- @param #string e Event name.
|
||||
-- @return #boolean If true, FSM can do the event.
|
||||
-- @return #string To state.
|
||||
function FSM:can(e)
|
||||
function FSM:can( e )
|
||||
|
||||
local Event = self.Events[e]
|
||||
|
||||
--self:F3( { self.current, Event } )
|
||||
-- self:F3( { self.current, Event } )
|
||||
|
||||
local To = Event and Event.map[self.current] or Event.map['*']
|
||||
|
||||
@@ -946,8 +941,8 @@ do -- FSM
|
||||
-- @param #FSM self
|
||||
-- @param #string e Event name.
|
||||
-- @return #boolean If true, FSM cannot do the event.
|
||||
function FSM:cannot(e)
|
||||
return not self:can(e)
|
||||
function FSM:cannot( e )
|
||||
return not self:can( e )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1036,7 +1031,7 @@ do -- FSM_CONTROLLABLE
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To)
|
||||
function FSM_CONTROLLABLE:OnAfterStop( Controllable, From, Event, To )
|
||||
|
||||
-- Clear all pending schedules
|
||||
self.CallScheduler:Clear()
|
||||
@@ -1047,7 +1042,7 @@ do -- FSM_CONTROLLABLE
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE FSMControllable
|
||||
-- @return #FSM_CONTROLLABLE
|
||||
function FSM_CONTROLLABLE:SetControllable( FSMControllable )
|
||||
--self:F( FSMControllable:GetName() )
|
||||
-- self:F( FSMControllable:GetName() )
|
||||
self.Controllable = FSMControllable
|
||||
end
|
||||
|
||||
@@ -1075,9 +1070,11 @@ do -- FSM_CONTROLLABLE
|
||||
if self[handler] then
|
||||
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** TaskUnit: " .. self.Controllable:GetName() )
|
||||
self._EventSchedules[EventName] = nil
|
||||
local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler )
|
||||
local Result, Value = xpcall( function()
|
||||
return self[handler]( self, self.Controllable, unpack( params ) )
|
||||
end, ErrorHandler )
|
||||
return Value
|
||||
--return self[handler]( self, self.Controllable, unpack( params ) )
|
||||
-- return self[handler]( self, self.Controllable, unpack( params ) )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1089,16 +1086,13 @@ do -- FSM_PROCESS
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
--- FSM_PROCESS class models Finite State Machines for @{Task} actions, which control @{Client}s.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #FSM_PROCESS FSM_PROCESS
|
||||
--
|
||||
FSM_PROCESS = {
|
||||
ClassName = "FSM_PROCESS",
|
||||
}
|
||||
FSM_PROCESS = { ClassName = "FSM_PROCESS" }
|
||||
|
||||
--- Creates a new FSM_PROCESS object.
|
||||
-- @param #FSM_PROCESS self
|
||||
@@ -1107,7 +1101,7 @@ do -- FSM_PROCESS
|
||||
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
--self:F( Controllable )
|
||||
-- self:F( Controllable )
|
||||
|
||||
self:Assign( Controllable, Task )
|
||||
|
||||
@@ -1139,10 +1133,12 @@ do -- FSM_PROCESS
|
||||
self._EventSchedules[EventName] = nil
|
||||
local Result, Value
|
||||
if self.Controllable and self.Controllable:IsAlive() == true then
|
||||
Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler )
|
||||
Result, Value = xpcall( function()
|
||||
return self[handler]( self, self.Controllable, self.Task, unpack( params ) )
|
||||
end, ErrorHandler )
|
||||
end
|
||||
return Value
|
||||
--return self[handler]( self, self.Controllable, unpack( params ) )
|
||||
-- return self[handler]( self, self.Controllable, unpack( params ) )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1152,7 +1148,6 @@ do -- FSM_PROCESS
|
||||
function FSM_PROCESS:Copy( Controllable, Task )
|
||||
self:T( { self:GetClassNameAndID() } )
|
||||
|
||||
|
||||
local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
NewFsm:Assign( Controllable, Task )
|
||||
@@ -1170,7 +1165,7 @@ do -- FSM_PROCESS
|
||||
|
||||
-- Copy Processes
|
||||
for ProcessID, Process in pairs( self:GetProcesses() ) do
|
||||
--self:E( { Process:GetName() } )
|
||||
-- self:E( { Process:GetName() } )
|
||||
local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents )
|
||||
end
|
||||
|
||||
@@ -1244,7 +1239,7 @@ do -- FSM_PROCESS
|
||||
return self:GetTask():GetMission():GetCommandCenter()
|
||||
end
|
||||
|
||||
-- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP.
|
||||
-- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP.
|
||||
|
||||
--- Send a message of the @{Task} to the Group of the Unit.
|
||||
-- @param #FSM_PROCESS self
|
||||
@@ -1263,32 +1258,29 @@ do -- FSM_PROCESS
|
||||
CC:MessageToGroup( Message, TaskGroup )
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Assign the process to a @{Wrapper.Unit} and activate the process.
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Task.Tasking#TASK Task
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @return #FSM_PROCESS self
|
||||
function FSM_PROCESS:Assign( ProcessUnit, Task )
|
||||
--self:T( { Task:GetName(), ProcessUnit:GetName() } )
|
||||
-- self:T( { Task:GetName(), ProcessUnit:GetName() } )
|
||||
|
||||
self:SetControllable( ProcessUnit )
|
||||
self:SetTask( Task )
|
||||
|
||||
--self.ProcessGroup = ProcessUnit:GetGroup()
|
||||
-- self.ProcessGroup = ProcessUnit:GetGroup()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To )
|
||||
--
|
||||
-- if From( "Planned" ) then
|
||||
-- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
|
||||
-- self.Task:Assign()
|
||||
-- end
|
||||
-- end
|
||||
-- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To )
|
||||
--
|
||||
-- if From( "Planned" ) then
|
||||
-- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
|
||||
-- self.Task:Assign()
|
||||
-- end
|
||||
-- end
|
||||
|
||||
function FSM_PROCESS:onenterFailed( ProcessUnit, Task, From, Event, To )
|
||||
self:T( "*** FSM *** Failed *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
|
||||
@@ -1296,7 +1288,6 @@ do -- FSM_PROCESS
|
||||
self.Task:Fail()
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function for a FSM_PROCESS
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
|
||||
@@ -1309,10 +1300,10 @@ do -- FSM_PROCESS
|
||||
self:T( "*** FSM *** Change *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
|
||||
end
|
||||
|
||||
-- if self:IsTrace() then
|
||||
-- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll()
|
||||
-- self:F2( { Scores = self._Scores, To = To } )
|
||||
-- end
|
||||
-- if self:IsTrace() then
|
||||
-- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll()
|
||||
-- self:F2( { Scores = self._Scores, To = To } )
|
||||
-- end
|
||||
|
||||
-- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects...
|
||||
if self._Scores[To] then
|
||||
@@ -1374,8 +1365,10 @@ do -- FSM_TASK
|
||||
if self[handler] then
|
||||
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.TaskName )
|
||||
self._EventSchedules[EventName] = nil
|
||||
--return self[handler]( self, unpack( params ) )
|
||||
local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler )
|
||||
-- return self[handler]( self, unpack( params ) )
|
||||
local Result, Value = xpcall( function()
|
||||
return self[handler]( self, unpack( params ) )
|
||||
end, ErrorHandler )
|
||||
return Value
|
||||
end
|
||||
end
|
||||
@@ -1389,14 +1382,12 @@ do -- FSM_SET
|
||||
-- @field Core.Set#SET_BASE Set
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- FSM_SET class models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here
|
||||
-- for multiple objects or the position of the state machine in the process.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #FSM_SET FSM_SET
|
||||
--
|
||||
FSM_SET = {
|
||||
ClassName = "FSM_SET",
|
||||
}
|
||||
@@ -1434,8 +1425,8 @@ do -- FSM_SET
|
||||
return self.Controllable
|
||||
end
|
||||
|
||||
function FSM_SET:_call_handler( step, trigger, params, EventName )
|
||||
local handler = step .. trigger
|
||||
function FSM_SET:_call_handler( step, trigger, params, EventName )
|
||||
local handler = step .. trigger
|
||||
if self[handler] then
|
||||
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] )
|
||||
self._EventSchedules[EventName] = nil
|
||||
|
||||
@@ -22,13 +22,11 @@
|
||||
-- @module Core.Goal
|
||||
-- @image Core_Goal.JPG
|
||||
|
||||
|
||||
do -- Goal
|
||||
|
||||
--- @type GOAL
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
-- # 1. GOAL constructor
|
||||
@@ -105,9 +103,8 @@ do -- Goal
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
|
||||
self:SetStartState( "Pending" )
|
||||
self:AddTransition( "*", "Achieved", "Achieved" )
|
||||
self:AddTransition( "*", "Achieved", "Achieved" )
|
||||
|
||||
--- Achieved Handler OnBefore for GOAL
|
||||
-- @function [parent=#GOAL] OnBeforeAchieved
|
||||
@@ -138,25 +135,22 @@ do -- Goal
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add a new contribution by a player.
|
||||
-- @param #GOAL self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
function GOAL:AddPlayerContribution( PlayerName )
|
||||
self:F({PlayerName})
|
||||
self:F( { PlayerName } )
|
||||
self.Players[PlayerName] = self.Players[PlayerName] or 0
|
||||
self.Players[PlayerName] = self.Players[PlayerName] + 1
|
||||
self.TotalContributions = self.TotalContributions + 1
|
||||
end
|
||||
|
||||
|
||||
--- @param #GOAL self
|
||||
-- @param #number Player contribution.
|
||||
function GOAL:GetPlayerContribution( PlayerName )
|
||||
return self.Players[PlayerName] or 0
|
||||
end
|
||||
|
||||
|
||||
--- Get the players who contributed to achieve the goal.
|
||||
-- The result is a list of players, sorted by the name of the players.
|
||||
-- @param #GOAL self
|
||||
@@ -165,7 +159,6 @@ do -- Goal
|
||||
return self.Players or {}
|
||||
end
|
||||
|
||||
|
||||
--- Gets the total contributions that happened to achieve the goal.
|
||||
-- The result is a number.
|
||||
-- @param #GOAL self
|
||||
@@ -174,8 +167,6 @@ do -- Goal
|
||||
return self.TotalContributions or 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Validates if the goal is achieved.
|
||||
-- @param #GOAL self
|
||||
-- @return #boolean true if the goal is achieved.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-- * Only create or delete menus when required, and keep existing menus persistent.
|
||||
-- * Update menu structures.
|
||||
-- * Refresh menu structures intelligently, based on a time stamp of updates.
|
||||
-- - Delete obscolete menus.
|
||||
-- - Delete obsolete menus.
|
||||
-- - Create new one where required.
|
||||
-- - Don't touch the existing ones.
|
||||
-- * Provide a variable amount of parameters to menus.
|
||||
@@ -23,7 +23,7 @@
|
||||
-- * Provide a great tool to manage menus in your code.
|
||||
--
|
||||
-- DCS Menus can be managed using the MENU classes.
|
||||
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to
|
||||
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to
|
||||
-- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing
|
||||
-- menus is not a easy feat if you have complex menu hierarchies defined.
|
||||
-- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy.
|
||||
@@ -53,7 +53,6 @@
|
||||
-- @module Core.Menu
|
||||
-- @image Core_Menu.JPG
|
||||
|
||||
|
||||
MENU_INDEX = {}
|
||||
MENU_INDEX.MenuMission = {}
|
||||
MENU_INDEX.MenuMission.Menus = {}
|
||||
@@ -64,8 +63,6 @@ MENU_INDEX.Coalition[coalition.side.RED] = {}
|
||||
MENU_INDEX.Coalition[coalition.side.RED].Menus = {}
|
||||
MENU_INDEX.Group = {}
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
|
||||
local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or ""
|
||||
@@ -98,29 +95,25 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
|
||||
end
|
||||
|
||||
|
||||
function MENU_INDEX:PrepareMission()
|
||||
self.MenuMission.Menus = self.MenuMission.Menus or {}
|
||||
self.MenuMission.Menus = self.MenuMission.Menus or {}
|
||||
end
|
||||
|
||||
|
||||
function MENU_INDEX:PrepareCoalition( CoalitionSide )
|
||||
self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {}
|
||||
self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {}
|
||||
self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {}
|
||||
self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {}
|
||||
end
|
||||
|
||||
---
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
function MENU_INDEX:PrepareGroup( Group )
|
||||
if Group and Group:IsAlive() ~= nil then -- something was changed here!
|
||||
if Group and Group:IsAlive() ~= nil then -- something was changed here!
|
||||
local GroupName = Group:GetName()
|
||||
self.Group[GroupName] = self.Group[GroupName] or {}
|
||||
self.Group[GroupName].Menus = self.Group[GroupName].Menus or {}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
return self.MenuMission.Menus[Path]
|
||||
@@ -136,8 +129,6 @@ function MENU_INDEX:ClearMissionMenu( Path )
|
||||
self.MenuMission.Menus[Path] = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
return self.Coalition[Coalition].Menus[Path]
|
||||
@@ -153,8 +144,6 @@ function MENU_INDEX:ClearCoalitionMenu( Coalition, Path )
|
||||
self.Coalition[Coalition].Menus[Path] = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
if Group and Group:IsAlive() then
|
||||
local MenuGroupName = Group:GetName()
|
||||
@@ -166,7 +155,7 @@ end
|
||||
function MENU_INDEX:SetGroupMenu( Group, Path, Menu )
|
||||
|
||||
local MenuGroupName = Group:GetName()
|
||||
Group:F({MenuGroupName=MenuGroupName,Path=Path})
|
||||
Group:F( { MenuGroupName = MenuGroupName, Path = Path } )
|
||||
self.Group[MenuGroupName].Menus[Path] = Menu
|
||||
end
|
||||
|
||||
@@ -178,36 +167,29 @@ end
|
||||
|
||||
function MENU_INDEX:Refresh( Group )
|
||||
|
||||
for MenuID, Menu in pairs( self.MenuMission.Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
for MenuID, Menu in pairs( self.MenuMission.Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
local GroupName = Group:GetName()
|
||||
for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
local GroupName = Group:GetName()
|
||||
for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
do -- MENU_BASE
|
||||
|
||||
--- @type MENU_BASE
|
||||
-- @extends Base#BASE
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Defines the main MENU class where other MENU classes are derived from.
|
||||
-- This is an abstract class, so don't use it.
|
||||
@@ -216,10 +198,10 @@ do -- MENU_BASE
|
||||
ClassName = "MENU_BASE",
|
||||
MenuPath = nil,
|
||||
MenuText = "",
|
||||
MenuParentPath = nil
|
||||
MenuParentPath = nil,
|
||||
}
|
||||
|
||||
--- Consructor
|
||||
--- Constructor
|
||||
-- @param #MENU_BASE
|
||||
-- @return #MENU_BASE
|
||||
function MENU_BASE:New( MenuText, ParentMenu )
|
||||
@@ -229,13 +211,13 @@ do -- MENU_BASE
|
||||
MenuParentPath = ParentMenu.MenuPath
|
||||
end
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
self.MenuPath = nil
|
||||
self.MenuText = MenuText
|
||||
self.ParentMenu = ParentMenu
|
||||
self.MenuParentPath = MenuParentPath
|
||||
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
|
||||
self.MenuPath = nil
|
||||
self.MenuText = MenuText
|
||||
self.ParentMenu = ParentMenu
|
||||
self.MenuParentPath = MenuParentPath
|
||||
self.Path = (self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "") .. "@" .. self.MenuText
|
||||
self.Menus = {}
|
||||
self.MenuCount = 0
|
||||
self.MenuStamp = timer.getTime()
|
||||
@@ -246,7 +228,7 @@ do -- MENU_BASE
|
||||
self.ParentMenu.Menus[MenuText] = self
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
function MENU_BASE:SetParentMenu( MenuText, Menu )
|
||||
@@ -262,7 +244,7 @@ do -- MENU_BASE
|
||||
self.ParentMenu.Menus[MenuText] = nil
|
||||
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1
|
||||
if self.ParentMenu.MenuCount == 0 then
|
||||
--self.ParentMenu:Remove()
|
||||
-- self.ParentMenu:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -272,12 +254,11 @@ do -- MENU_BASE
|
||||
-- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}.
|
||||
-- @return #MENU_BASE
|
||||
function MENU_BASE:SetRemoveParent( RemoveParent )
|
||||
--self:F( { RemoveParent } )
|
||||
-- self:F( { RemoveParent } )
|
||||
self.MenuRemoveParent = RemoveParent
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Gets a @{Menu} from a parent @{Menu}
|
||||
-- @param #MENU_BASE self
|
||||
-- @param #string MenuText The text of the child menu.
|
||||
@@ -295,7 +276,6 @@ do -- MENU_BASE
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Gets a menu stamp for later prevention of menu removal.
|
||||
-- @param #MENU_BASE self
|
||||
-- @return MenuStamp
|
||||
@@ -303,7 +283,6 @@ do -- MENU_BASE
|
||||
return timer.getTime()
|
||||
end
|
||||
|
||||
|
||||
--- Sets a time stamp for later prevention of menu removal.
|
||||
-- @param #MENU_BASE self
|
||||
-- @param MenuStamp
|
||||
@@ -346,7 +325,7 @@ do -- MENU_COMMAND_BASE
|
||||
-- @return #MENU_COMMAND_BASE
|
||||
function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments )
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
|
||||
|
||||
-- When a menu function goes into error, DCS displays an obscure menu message.
|
||||
-- This error handler catches the menu error and displays the full call stack.
|
||||
@@ -367,7 +346,7 @@ do -- MENU_COMMAND_BASE
|
||||
local Status, Result = xpcall( MenuFunction, ErrorHandler )
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- This sets the new command function of a menu,
|
||||
@@ -394,7 +373,6 @@ do -- MENU_COMMAND_BASE
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- MENU_MISSION
|
||||
|
||||
--- @type MENU_MISSION
|
||||
@@ -406,13 +384,13 @@ do -- MENU_MISSION
|
||||
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}.
|
||||
-- @field #MENU_MISSION
|
||||
MENU_MISSION = {
|
||||
ClassName = "MENU_MISSION"
|
||||
ClassName = "MENU_MISSION",
|
||||
}
|
||||
|
||||
--- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file.
|
||||
-- @param #MENU_MISSION self
|
||||
-- @param #string MenuText The text for the menu.
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
|
||||
-- @return #MENU_MISSION
|
||||
function MENU_MISSION:New( MenuText, ParentMenu )
|
||||
|
||||
@@ -470,7 +448,7 @@ do -- MENU_MISSION
|
||||
if MissionMenu == self then
|
||||
self:RemoveSubMenus()
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then
|
||||
self:F( { Text = self.MenuText, Path = self.MenuPath } )
|
||||
if self.MenuPath ~= nil then
|
||||
missionCommands.removeItem( self.MenuPath )
|
||||
@@ -487,8 +465,6 @@ do -- MENU_MISSION
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
do -- MENU_MISSION_COMMAND
|
||||
@@ -503,7 +479,7 @@ do -- MENU_MISSION_COMMAND
|
||||
--
|
||||
-- @field #MENU_MISSION_COMMAND
|
||||
MENU_MISSION_COMMAND = {
|
||||
ClassName = "MENU_MISSION_COMMAND"
|
||||
ClassName = "MENU_MISSION_COMMAND",
|
||||
}
|
||||
|
||||
--- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters.
|
||||
@@ -556,7 +532,7 @@ do -- MENU_MISSION_COMMAND
|
||||
|
||||
if MissionMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then
|
||||
self:F( { Text = self.MenuText, Path = self.MenuPath } )
|
||||
if self.MenuPath ~= nil then
|
||||
missionCommands.removeItem( self.MenuPath )
|
||||
@@ -575,8 +551,6 @@ do -- MENU_MISSION_COMMAND
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- MENU_COALITION
|
||||
|
||||
--- @type MENU_COALITION
|
||||
@@ -627,14 +601,14 @@ do -- MENU_COALITION
|
||||
--
|
||||
-- @field #MENU_COALITION
|
||||
MENU_COALITION = {
|
||||
ClassName = "MENU_COALITION"
|
||||
ClassName = "MENU_COALITION",
|
||||
}
|
||||
|
||||
--- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition.
|
||||
-- @param #MENU_COALITION self
|
||||
-- @param DCS#coalition.side Coalition The coalition owning the menu.
|
||||
-- @param #string MenuText The text for the menu.
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
|
||||
-- @return #MENU_COALITION self
|
||||
function MENU_COALITION:New( Coalition, MenuText, ParentMenu )
|
||||
|
||||
@@ -693,7 +667,7 @@ do -- MENU_COALITION
|
||||
if CoalitionMenu == self then
|
||||
self:RemoveSubMenus()
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then
|
||||
self:F( { Coalition = self.Coalition, Text = self.MenuText, Path = self.MenuPath } )
|
||||
if self.MenuPath ~= nil then
|
||||
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
|
||||
@@ -712,8 +686,6 @@ do -- MENU_COALITION
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- MENU_COALITION_COMMAND
|
||||
|
||||
--- @type MENU_COALITION_COMMAND
|
||||
@@ -726,7 +698,7 @@ do -- MENU_COALITION_COMMAND
|
||||
--
|
||||
-- @field #MENU_COALITION_COMMAND
|
||||
MENU_COALITION_COMMAND = {
|
||||
ClassName = "MENU_COALITION_COMMAND"
|
||||
ClassName = "MENU_COALITION_COMMAND",
|
||||
}
|
||||
|
||||
--- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters.
|
||||
@@ -760,7 +732,6 @@ do -- MENU_COALITION_COMMAND
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a radio item for a coalition
|
||||
-- @param #MENU_COALITION_COMMAND self
|
||||
-- @return #MENU_COALITION_COMMAND
|
||||
@@ -784,7 +755,7 @@ do -- MENU_COALITION_COMMAND
|
||||
|
||||
if CoalitionMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then
|
||||
self:F( { Coalition = self.Coalition, Text = self.MenuText, Path = self.MenuPath } )
|
||||
if self.MenuPath ~= nil then
|
||||
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
|
||||
@@ -803,7 +774,6 @@ do -- MENU_COALITION_COMMAND
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- MENU_GROUP
|
||||
|
||||
do
|
||||
@@ -817,7 +787,6 @@ do
|
||||
--- @type MENU_GROUP
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
|
||||
--- Manages the main menus for @{Wrapper.Group}s.
|
||||
--
|
||||
-- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference.
|
||||
@@ -875,7 +844,7 @@ do
|
||||
--
|
||||
-- @field #MENU_GROUP
|
||||
MENU_GROUP = {
|
||||
ClassName = "MENU_GROUP"
|
||||
ClassName = "MENU_GROUP",
|
||||
}
|
||||
|
||||
--- MENU_GROUP constructor. Creates a new radio menu item for a group.
|
||||
@@ -938,7 +907,6 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP self
|
||||
-- @param MenuStamp
|
||||
@@ -953,7 +921,7 @@ do
|
||||
if GroupMenu == self then
|
||||
self:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then
|
||||
if self.MenuPath ~= nil then
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
@@ -971,7 +939,6 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @type MENU_GROUP_COMMAND
|
||||
-- @extends Core.Menu#MENU_COMMAND_BASE
|
||||
|
||||
@@ -981,7 +948,7 @@ do
|
||||
--
|
||||
-- @field #MENU_GROUP_COMMAND
|
||||
MENU_GROUP_COMMAND = {
|
||||
ClassName = "MENU_GROUP_COMMAND"
|
||||
ClassName = "MENU_GROUP_COMMAND",
|
||||
}
|
||||
|
||||
--- Creates a new radio command item for a group
|
||||
@@ -1043,9 +1010,9 @@ do
|
||||
|
||||
if GroupMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then
|
||||
if self.MenuPath ~= nil then
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
end
|
||||
MENU_INDEX:ClearGroupMenu( self.Group, Path )
|
||||
@@ -1069,7 +1036,6 @@ do
|
||||
--- @type MENU_GROUP_DELAYED
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
|
||||
--- The MENU_GROUP_DELAYED class manages the main menus for groups.
|
||||
-- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference.
|
||||
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}.
|
||||
@@ -1079,7 +1045,7 @@ do
|
||||
--
|
||||
-- @field #MENU_GROUP_DELAYED
|
||||
MENU_GROUP_DELAYED = {
|
||||
ClassName = "MENU_GROUP_DELAYED"
|
||||
ClassName = "MENU_GROUP_DELAYED",
|
||||
}
|
||||
|
||||
--- MENU_GROUP_DELAYED constructor. Creates a new radio menu item for a group.
|
||||
@@ -1116,7 +1082,6 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @return #MENU_GROUP_DELAYED
|
||||
@@ -1135,7 +1100,6 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @return #MENU_GROUP_DELAYED
|
||||
@@ -1167,7 +1131,6 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @param MenuStamp
|
||||
@@ -1182,7 +1145,7 @@ do
|
||||
if GroupMenu == self then
|
||||
self:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then
|
||||
if self.MenuPath ~= nil then
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
@@ -1200,7 +1163,6 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @type MENU_GROUP_COMMAND_DELAYED
|
||||
-- @extends Core.Menu#MENU_COMMAND_BASE
|
||||
|
||||
@@ -1211,7 +1173,7 @@ do
|
||||
--
|
||||
-- @field #MENU_GROUP_COMMAND_DELAYED
|
||||
MENU_GROUP_COMMAND_DELAYED = {
|
||||
ClassName = "MENU_GROUP_COMMAND_DELAYED"
|
||||
ClassName = "MENU_GROUP_COMMAND_DELAYED",
|
||||
}
|
||||
|
||||
--- Creates a new radio command item for a group
|
||||
@@ -1292,7 +1254,7 @@ do
|
||||
|
||||
if GroupMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then
|
||||
if self.MenuPath ~= nil then
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
|
||||
@@ -56,9 +56,9 @@
|
||||
--
|
||||
-- @field #MESSAGE
|
||||
MESSAGE = {
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
}
|
||||
|
||||
--- Message Types
|
||||
@@ -68,10 +68,9 @@ MESSAGE.Type = {
|
||||
Information = "Information",
|
||||
Briefing = "Briefing Report",
|
||||
Overview = "Overview Report",
|
||||
Detailed = "Detailed Report"
|
||||
Detailed = "Detailed Report",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
|
||||
-- @param self
|
||||
-- @param #string MessageText is the text of the Message.
|
||||
@@ -80,25 +79,26 @@ MESSAGE.Type = {
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Create a series of new Messages.
|
||||
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
|
||||
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
|
||||
--
|
||||
-- -- Create a series of new Messages.
|
||||
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
|
||||
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
|
||||
--
|
||||
function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
|
||||
self.MessageType = nil
|
||||
|
||||
-- When no MessageCategory is given, we don't show it as a title...
|
||||
if MessageCategory and MessageCategory ~= "" then
|
||||
if MessageCategory:sub(-1) ~= "\n" then
|
||||
if MessageCategory and MessageCategory ~= "" then
|
||||
if MessageCategory:sub( -1 ) ~= "\n" then
|
||||
self.MessageCategory = MessageCategory .. ": "
|
||||
else
|
||||
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
|
||||
@@ -107,23 +107,22 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen
|
||||
self.MessageCategory = ""
|
||||
end
|
||||
|
||||
self.ClearScreen=false
|
||||
if ClearScreen~=nil then
|
||||
self.ClearScreen=ClearScreen
|
||||
self.ClearScreen = false
|
||||
if ClearScreen ~= nil then
|
||||
self.ClearScreen = ClearScreen
|
||||
end
|
||||
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new MESSAGE object of a certain type.
|
||||
-- Note that these MESSAGE objects are not yet displayed on the display panel.
|
||||
-- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
|
||||
@@ -134,10 +133,12 @@ end
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information )
|
||||
-- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information )
|
||||
-- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update )
|
||||
-- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update )
|
||||
--
|
||||
function MESSAGE:NewType( MessageText, MessageType, ClearScreen )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
@@ -145,69 +146,67 @@ function MESSAGE:NewType( MessageText, MessageType, ClearScreen )
|
||||
|
||||
self.MessageType = MessageType
|
||||
|
||||
self.ClearScreen=false
|
||||
if ClearScreen~=nil then
|
||||
self.ClearScreen=ClearScreen
|
||||
self.ClearScreen = false
|
||||
if ClearScreen ~= nil then
|
||||
self.ClearScreen = ClearScreen
|
||||
end
|
||||
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
function MESSAGE:Clear()
|
||||
self:F()
|
||||
self.ClearScreen=true
|
||||
self.ClearScreen = true
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Client#CLIENT Client is the Group of the Client.
|
||||
-- @param Core.Settings#SETTINGS Settings Settings used to display the message.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send the 2 messages created with the @{New} method to the Client Group.
|
||||
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
|
||||
-- ClientGroup = Group.getByName( "ClientGroup" )
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
|
||||
-- MessageClient1:ToClient( ClientGroup )
|
||||
-- MessageClient2:ToClient( ClientGroup )
|
||||
-- -- Send the 2 messages created with the @{New} method to the Client Group.
|
||||
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
|
||||
-- ClientGroup = Group.getByName( "ClientGroup" )
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
|
||||
-- MessageClient1:ToClient( ClientGroup )
|
||||
-- MessageClient2:ToClient( ClientGroup )
|
||||
--
|
||||
function MESSAGE:ToClient( Client, Settings )
|
||||
self:F( Client )
|
||||
self:F( Client )
|
||||
|
||||
if Client and Client:GetClientGroupID() then
|
||||
if Client and Client:GetClientGroupID() then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
local Settings = Settings or (Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
local ClientGroupID = Client:GetClientGroupID()
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
end
|
||||
end
|
||||
local ClientGroupID = Client:GetClientGroupID()
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Group.
|
||||
@@ -220,14 +219,14 @@ function MESSAGE:ToGroup( Group, Settings )
|
||||
if Group then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -237,38 +236,42 @@ end
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send a message created with the @{New} method to the BLUE coalition.
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageBLUE:ToBlue()
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the BLUE coalition.
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageBLUE:ToBlue()
|
||||
--
|
||||
function MESSAGE:ToBlue()
|
||||
self:F()
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to the Red Coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToRed()
|
||||
function MESSAGE:ToRed( )
|
||||
self:F()
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToRed()
|
||||
--
|
||||
function MESSAGE:ToRed()
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition.
|
||||
@@ -277,15 +280,17 @@ end
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE Message object.
|
||||
-- @usage
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
--
|
||||
function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
self:F( CoalitionSide )
|
||||
self:F( CoalitionSide )
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
@@ -293,14 +298,14 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if CoalitionSide then
|
||||
if CoalitionSide then
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition if the given Condition is true.
|
||||
@@ -323,14 +328,16 @@ end
|
||||
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send a message created to all players.
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
|
||||
-- MessageAll:ToAll()
|
||||
function MESSAGE:ToAll(Settings)
|
||||
--
|
||||
-- -- Send a message created to all players.
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
|
||||
-- MessageAll:ToAll()
|
||||
--
|
||||
function MESSAGE:ToAll( Settings )
|
||||
self:F()
|
||||
|
||||
if self.MessageType then
|
||||
@@ -340,14 +347,13 @@ function MESSAGE:ToAll(Settings)
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Sends a MESSAGE to all players if the given Condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
@@ -357,5 +363,5 @@ function MESSAGE:ToAllIf( Condition )
|
||||
self:ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,832 +0,0 @@
|
||||
--- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, 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?
|
||||
--
|
||||
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
|
||||
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
|
||||
--
|
||||
-- How to supply DCS my own Sound Files?
|
||||
--
|
||||
-- * Your sound files need to be encoded in **.ogg** or .wav,
|
||||
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
|
||||
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
|
||||
-- * For simplicity sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
|
||||
--
|
||||
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
|
||||
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
|
||||
--
|
||||
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
|
||||
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
|
||||
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
|
||||
-- you won't hear/be able to use the TACAN beacon informations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Radio
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
|
||||
--- Models the radio capability.
|
||||
--
|
||||
-- ## RADIO usage
|
||||
--
|
||||
-- There are 3 steps to a successful radio transmission.
|
||||
--
|
||||
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
|
||||
-- * Then, you will **set the relevant parameters** to the transmission (see below),
|
||||
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
|
||||
--
|
||||
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
|
||||
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
|
||||
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
|
||||
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
|
||||
--
|
||||
-- Additional Methods to set relevant parameters if the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
|
||||
--
|
||||
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
|
||||
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
|
||||
--
|
||||
-- Additional Methods to set relevant parameters if the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
|
||||
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
|
||||
--
|
||||
-- What is this power thing?
|
||||
--
|
||||
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
|
||||
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
|
||||
-- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
|
||||
-- * This an automated DCS calculation you have no say on,
|
||||
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
|
||||
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
||||
--
|
||||
-- @type RADIO
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
|
||||
-- @field #string FileName Name of the sound file played.
|
||||
-- @field #number Frequency Frequency of the transmission in Hz.
|
||||
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM).
|
||||
-- @field #string Subtitle Subtitle of the transmission.
|
||||
-- @field #number SubtitleDuration Duration of the Subtitle in seconds.
|
||||
-- @field #number Power Power of the antenna is Watts.
|
||||
-- @field #boolean Loop Transmission is repeated (default true).
|
||||
-- @field #string alias Name of the radio transmitter.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIO = {
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
SubtitleDuration = 0,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias=nil,
|
||||
}
|
||||
|
||||
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
|
||||
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead.
|
||||
-- @param #RADIO self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #RADIO The RADIO object or #nil if Positionable is invalid.
|
||||
function RADIO:New(Positionable)
|
||||
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
|
||||
self:F(Positionable)
|
||||
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Set alias of the transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @param #string alias Name of the radio transmitter.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetAlias(alias)
|
||||
self.alias=tostring(alias)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get alias of the transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @return #string Name of the transmitter.
|
||||
function RADIO:GetAlias()
|
||||
return tostring(self.alias)
|
||||
end
|
||||
|
||||
--- Set the file name for the radio transmission.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFileName(FileName)
|
||||
self:F2(FileName)
|
||||
|
||||
if type(FileName) == "string" then
|
||||
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
|
||||
self.FileName = FileName
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the frequency for the radio transmission.
|
||||
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Frequency Frequency in MHz. Ranges allowed for radio transmissions in DCS : 30-87.995 / 108-173.995 / 225-399.975MHz.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFrequency(Frequency)
|
||||
self:F2(Frequency)
|
||||
|
||||
if type(Frequency) == "number" then
|
||||
|
||||
-- If frequency is in range
|
||||
if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
|
||||
|
||||
-- Convert frequency from MHz to Hz
|
||||
self.Frequency = Frequency * 1000000
|
||||
|
||||
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
local commandSetFrequency={
|
||||
id = "SetFrequency",
|
||||
params = {
|
||||
frequency = self.Frequency,
|
||||
modulation = self.Modulation,
|
||||
}
|
||||
}
|
||||
|
||||
self:T2(commandSetFrequency)
|
||||
self.Positionable:SetCommand(commandSetFrequency)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", Frequency})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set AM or FM modulation of the radio transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetModulation(Modulation)
|
||||
self:F2(Modulation)
|
||||
if type(Modulation) == "number" then
|
||||
if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self.Modulation = Modulation
|
||||
return self
|
||||
end
|
||||
end
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.", self.Modulation})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the power passed and sets RADIO.Power
|
||||
-- @param #RADIO self
|
||||
-- @param #number Power Power in W.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetPower(Power)
|
||||
self:F2(Power)
|
||||
|
||||
if type(Power) == "number" then
|
||||
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
else
|
||||
self:E({"Power is invalid. Power unchanged.", self.Power})
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set message looping on or off.
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean Loop If true, message is repeated indefinitely.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetLoop(Loop)
|
||||
self:F2(Loop)
|
||||
if type(Loop) == "boolean" then
|
||||
self.Loop = Loop
|
||||
return self
|
||||
end
|
||||
self:E({"Loop is invalid. Loop unchanged.", self.Loop})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the subtitle and the subtitleDuration passed and sets RADIO.subtitle and RADIO.subtitleDuration
|
||||
-- Both parameters are mandatory, since it wouldn't make much sense to change the Subtitle and not its duration
|
||||
-- @param #RADIO self
|
||||
-- @param #string Subtitle
|
||||
-- @param #number SubtitleDuration in s
|
||||
-- @return #RADIO self
|
||||
-- @usage
|
||||
-- -- create the broadcaster and attaches it a RADIO
|
||||
-- local MyUnit = UNIT:FindByName("MyUnit")
|
||||
-- local MyUnitRadio = MyUnit:GetRadio()
|
||||
--
|
||||
-- -- add a subtitle for the next transmission, which will be up for 10s
|
||||
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
|
||||
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
self:F2({Subtitle, SubtitleDuration})
|
||||
if type(Subtitle) == "string" then
|
||||
self.Subtitle = Subtitle
|
||||
else
|
||||
self.Subtitle = ""
|
||||
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
|
||||
end
|
||||
if type(SubtitleDuration) == "number" then
|
||||
self.SubtitleDuration = SubtitleDuration
|
||||
else
|
||||
self.SubtitleDuration = 0
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- Only the #RADIO and the Filename are mandatory
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of the sound file that will be transmitted.
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
|
||||
-- @param #number Power Power in W.
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
|
||||
self:F({FileName, Frequency, Modulation, Power})
|
||||
|
||||
self:SetFileName(FileName)
|
||||
if Frequency then self:SetFrequency(Frequency) end
|
||||
if Modulation then self:SetModulation(Modulation) end
|
||||
if Power then self:SetPower(Power) end
|
||||
if Loop then self:SetLoop(Loop) end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
|
||||
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
|
||||
-- Only the RADIO and the Filename are mandatory.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of sound file.
|
||||
-- @param #string Subtitle Subtitle to be displayed with sound file.
|
||||
-- @param #number SubtitleDuration Duration of subtitle display in seconds.
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #boolean Loop If true, loop message.
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
|
||||
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
|
||||
|
||||
-- Set file name.
|
||||
self:SetFileName(FileName)
|
||||
|
||||
-- Set modulation AM/FM.
|
||||
if Modulation then
|
||||
self:SetModulation(Modulation)
|
||||
end
|
||||
|
||||
-- Set frequency.
|
||||
if Frequency then
|
||||
self:SetFrequency(Frequency)
|
||||
end
|
||||
|
||||
-- Set subtitle.
|
||||
if Subtitle then
|
||||
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
|
||||
end
|
||||
|
||||
-- Set Looping.
|
||||
if Loop then
|
||||
self:SetLoop(Loop)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Broadcast the transmission.
|
||||
-- * The Radio has to be populated with the new transmission before broadcasting.
|
||||
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
|
||||
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
|
||||
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
|
||||
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
|
||||
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
|
||||
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
|
||||
-- @return #RADIO self
|
||||
function RADIO:Broadcast(viatrigger)
|
||||
self:F({viatrigger=viatrigger})
|
||||
|
||||
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
|
||||
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
|
||||
self:T("Broadcasting from a UNIT or a GROUP")
|
||||
|
||||
local commandTransmitMessage={
|
||||
id = "TransmitMessage",
|
||||
params = {
|
||||
file = self.FileName,
|
||||
duration = self.SubtitleDuration,
|
||||
subtitle = self.Subtitle,
|
||||
loop = self.Loop,
|
||||
}}
|
||||
|
||||
self:T3(commandTransmitMessage)
|
||||
self.Positionable:SetCommand(commandTransmitMessage)
|
||||
else
|
||||
-- If the POSITIONABLE is anything else, we revert to the general singleton function
|
||||
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
|
||||
self:T("Broadcasting from a POSITIONABLE")
|
||||
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Stops a transmission
|
||||
-- This function is especially usefull to stop the broadcast of looped transmissions
|
||||
-- @param #RADIO self
|
||||
-- @return #RADIO self
|
||||
function RADIO:StopBroadcast()
|
||||
self:F()
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
local commandStopTransmission={id="StopTransmission", params={}}
|
||||
|
||||
self.Positionable:SetCommand(commandStopTransmission)
|
||||
else
|
||||
-- Else, we use the appropriate singleton funciton
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
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
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
-- @module Core.Report
|
||||
-- @image Core_Report.JPG
|
||||
|
||||
|
||||
--- @type REPORT
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
@@ -45,28 +44,26 @@ end
|
||||
--- Has the REPORT Text?
|
||||
-- @param #REPORT self
|
||||
-- @return #boolean
|
||||
function REPORT:HasText() --R2.1
|
||||
function REPORT:HasText() -- R2.1
|
||||
|
||||
return #self.Report > 0
|
||||
end
|
||||
|
||||
|
||||
--- Set indent of a REPORT.
|
||||
-- @param #REPORT self
|
||||
-- @param #number Indent
|
||||
-- @return #REPORT
|
||||
function REPORT:SetIndent( Indent ) --R2.1
|
||||
function REPORT:SetIndent( Indent ) -- R2.1
|
||||
self.Indent = Indent
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add a new line to a REPORT.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Text
|
||||
-- @return #REPORT
|
||||
function REPORT:Add( Text )
|
||||
self.Report[#self.Report+1] = Text
|
||||
self.Report[#self.Report + 1] = Text
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -76,17 +73,17 @@ end
|
||||
-- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best.
|
||||
-- @return #REPORT
|
||||
function REPORT:AddIndent( Text, Separator )
|
||||
self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
|
||||
self.Report[#self.Report + 1] = ((Separator and Separator .. string.rep( " ", self.Indent - 1 )) or string.rep( " ", self.Indent )) .. Text:gsub( "\n", "\n" .. string.rep( " ", self.Indent ) )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
|
||||
--- Produces the text of the report, taking into account an optional delimiter, which is \n by default.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Delimiter (optional) A delimiter text.
|
||||
-- @return #string The report text.
|
||||
function REPORT:Text( Delimiter )
|
||||
Delimiter = Delimiter or "\n"
|
||||
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or ""
|
||||
local ReportText = (self.Title ~= "" and self.Title .. Delimiter or self.Title) .. table.concat( self.Report, Delimiter ) or ""
|
||||
return ReportText
|
||||
end
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory.
|
||||
-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection!
|
||||
--
|
||||
-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected.
|
||||
-- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected.
|
||||
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
|
||||
-- these will not be executed anymore when the SCHEDULER object has been destroyed.
|
||||
--
|
||||
@@ -38,7 +38,7 @@
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number CallID Call ID counter.
|
||||
-- @field #table PersistentSchedulers Persistant schedulers.
|
||||
-- @field #table PersistentSchedulers Persistent schedulers.
|
||||
-- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists.
|
||||
-- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ).
|
||||
-- @extends Core.Base#BASE
|
||||
@@ -46,11 +46,11 @@
|
||||
--- The SCHEDULEDISPATCHER structure
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
SCHEDULEDISPATCHER = {
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
}
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
@@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = {
|
||||
-- @field #function Function The schedule function to be called.
|
||||
-- @field #table Arguments Schedule function arguments.
|
||||
-- @field #number Start Start time in seconds.
|
||||
-- @field #number Repeat Repeat time intervall in seconds.
|
||||
-- @field #number Repeat Repeat time interval in seconds.
|
||||
-- @field #number Randomize Randomization factor [0,1].
|
||||
-- @field #number Stop Stop time in seconds.
|
||||
-- @field #number StartTime Time in seconds when the scheduler is created.
|
||||
@@ -77,7 +77,7 @@ end
|
||||
|
||||
--- Add a Schedule to the ScheduleDispatcher.
|
||||
-- The development of this method was really tidy.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil.
|
||||
-- Nothing of this code should be modified without testing it thoroughly.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
@@ -85,7 +85,7 @@ end
|
||||
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
|
||||
-- @param #number Start Start time in seconds.
|
||||
-- @param #number Repeat Repeat interval in seconds.
|
||||
-- @param #number Randomize Radomization factor [0,1].
|
||||
-- @param #number Randomize Randomization factor [0,1].
|
||||
-- @param #number Stop Stop time in seconds.
|
||||
-- @param #number TraceLevel Trace level [0,3].
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
@@ -97,9 +97,9 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
self.CallID = self.CallID + 1
|
||||
|
||||
-- Create ID.
|
||||
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
|
||||
local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or ""
|
||||
|
||||
self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID))
|
||||
self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) )
|
||||
|
||||
-- Initialize PersistentSchedulers
|
||||
self.PersistentSchedulers = self.PersistentSchedulers or {}
|
||||
@@ -110,7 +110,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
if Scheduler.MasterObject then
|
||||
self.ObjectSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } )
|
||||
else
|
||||
self.PersistentSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
|
||||
@@ -118,16 +118,15 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
|
||||
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
|
||||
self.Schedule[Scheduler][CallID] = {} --#SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
|
||||
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + (Start or 0)
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.1
|
||||
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
|
||||
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
|
||||
self.Schedule[Scheduler][CallID].Stop = Stop
|
||||
|
||||
|
||||
-- This section handles the tracing of the scheduled calls.
|
||||
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
|
||||
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
|
||||
@@ -149,7 +148,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
-- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result.
|
||||
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
|
||||
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
|
||||
-- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often.
|
||||
-- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often.
|
||||
|
||||
local Info = {}
|
||||
|
||||
@@ -181,24 +180,24 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
return errmsg
|
||||
end
|
||||
|
||||
-- Get object or persistant scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER
|
||||
-- Get object or persistent scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER
|
||||
if not Scheduler then
|
||||
Scheduler = self.PersistentSchedulers[CallID]
|
||||
end
|
||||
|
||||
--self:T3( { Scheduler = Scheduler } )
|
||||
-- self:T3( { Scheduler = Scheduler } )
|
||||
|
||||
if Scheduler then
|
||||
|
||||
local MasterObject = tostring(Scheduler.MasterObject)
|
||||
local MasterObject = tostring( Scheduler.MasterObject )
|
||||
|
||||
-- Schedule object.
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
--self:T3( { Schedule = Schedule } )
|
||||
-- self:T3( { Schedule = Schedule } )
|
||||
|
||||
local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object?
|
||||
local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object?
|
||||
local ShowTrace = Scheduler.ShowTrace
|
||||
|
||||
local ScheduleFunction = Schedule.Function
|
||||
@@ -209,11 +208,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
local Stop = Schedule.Stop or 0
|
||||
local ScheduleID = Schedule.ScheduleID
|
||||
|
||||
|
||||
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
|
||||
local Prefix = (Repeat == 0) and "--->" or "+++>"
|
||||
|
||||
local Status, Result
|
||||
--self:E( { SchedulerObject = SchedulerObject } )
|
||||
-- self:E( { SchedulerObject = SchedulerObject } )
|
||||
if SchedulerObject then
|
||||
local function Timer()
|
||||
if ShowTrace then
|
||||
@@ -236,14 +234,13 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
local StartTime = Schedule.StartTime
|
||||
|
||||
-- Debug info.
|
||||
self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
self:F3( { CallID = CallID, ScheduleID = ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
if Status and ((Result == nil) or (Result and Result ~= false)) then
|
||||
|
||||
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
|
||||
|
||||
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy
|
||||
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random( -(Randomize * Repeat / 2), (Randomize * Repeat / 2) ) + 0.0001 -- Accuracy
|
||||
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
return ScheduleTime -- returns the next time the function needs to be called.
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
@@ -287,21 +284,21 @@ function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only start when there is no ScheduleID defined!
|
||||
-- This prevents to "Start" the scheduler twice with the same CallID...
|
||||
if not Schedule.ScheduleID then
|
||||
|
||||
-- Current time in seconds.
|
||||
local Tnow=timer.getTime()
|
||||
local Tnow = timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
-- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction
|
||||
Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start)
|
||||
Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start )
|
||||
|
||||
self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
end
|
||||
|
||||
else
|
||||
@@ -323,20 +320,20 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
|
||||
if Schedule.ScheduleID then
|
||||
|
||||
self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
|
||||
-- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
|
||||
timer.removeFunction(Schedule.ScheduleID)
|
||||
timer.removeFunction( Schedule.ScheduleID )
|
||||
|
||||
Schedule.ScheduleID = nil
|
||||
|
||||
else
|
||||
self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID)))
|
||||
self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) )
|
||||
end
|
||||
|
||||
else
|
||||
@@ -359,7 +356,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
|
||||
end
|
||||
end
|
||||
|
||||
--- Shopw tracing info.
|
||||
--- Show tracing info.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
-- @field #boolean ShowTrace Trace info if true.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization.
|
||||
--
|
||||
-- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**.
|
||||
@@ -79,7 +78,7 @@
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence...
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
@@ -149,13 +148,13 @@
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
--
|
||||
-- Several parameters can be specified that influence the behaviour of a Schedule.
|
||||
-- Several parameters can be specified that influence the behavior of a Schedule.
|
||||
--
|
||||
-- ### A single schedule, immediately executed
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ...
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ...
|
||||
--
|
||||
-- ### A single schedule, planned over time
|
||||
--
|
||||
@@ -193,10 +192,10 @@
|
||||
--
|
||||
-- @field #SCHEDULER
|
||||
SCHEDULER = {
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
}
|
||||
|
||||
--- SCHEDULER constructor.
|
||||
@@ -235,7 +234,7 @@ end
|
||||
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
|
||||
-- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function.
|
||||
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stoppe.
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stopped.
|
||||
-- @param #number TraceLevel Trace level [0,3]. Default 3.
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
-- @return #table The ScheduleID of the planned schedule.
|
||||
@@ -248,25 +247,24 @@ function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
ObjectName = MasterObject.ClassName .. MasterObject.ClassID
|
||||
end
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
-- Set master object.
|
||||
self.MasterObject = MasterObject
|
||||
|
||||
-- Add schedule.
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules+1] = ScheduleID
|
||||
self.Schedules[#self.Schedules + 1] = ScheduleID
|
||||
|
||||
return ScheduleID
|
||||
end
|
||||
@@ -276,7 +274,7 @@ end
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Start( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:Start( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -285,7 +283,7 @@ end
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Stop( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:Stop( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -294,15 +292,15 @@ end
|
||||
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Remove( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID )
|
||||
end
|
||||
|
||||
--- Clears all pending schedules.
|
||||
-- @param #SCHEDULER self
|
||||
function SCHEDULER:Clear()
|
||||
self:F3( )
|
||||
self:T(string.format("Clearing scheduler"))
|
||||
self:F3()
|
||||
self:T( string.format( "Clearing scheduler" ) )
|
||||
_SCHEDULEDISPATCHER:Clear( self )
|
||||
end
|
||||
|
||||
|
||||
+1170
-508
File diff suppressed because it is too large
Load Diff
@@ -29,15 +29,14 @@
|
||||
-- @module Core.Settings
|
||||
-- @image Core_Settings.JPG
|
||||
|
||||
|
||||
--- @type SETTINGS
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
--- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
-- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
-- SETTINGS can work on 2 levels:
|
||||
--
|
||||
-- - **Default settings**: A running mission has **Default settings**.
|
||||
@@ -59,7 +58,7 @@
|
||||
--
|
||||
-- A menu is created automatically per Command Center that allows to modify the **Default** settings.
|
||||
-- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**!
|
||||
-- Note that the **Default settings** will only be used when a player has not choosen its own settings.
|
||||
-- Note that the **Default settings** will only be used when a player has not chosen its own settings.
|
||||
--
|
||||
-- ## 2.2) Player settings menu
|
||||
--
|
||||
@@ -69,7 +68,7 @@
|
||||
--
|
||||
-- ## 2.3) Show or Hide the Player Setting menus
|
||||
--
|
||||
-- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
|
||||
-- Of course, it may be required not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
|
||||
-- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus.
|
||||
-- Note that when this method is used, any player already in a slot will not have its menus visibility changed.
|
||||
-- The option will only have effect when a player enters a new slot or changes a slot.
|
||||
@@ -94,8 +93,8 @@
|
||||
--
|
||||
-- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)).
|
||||
-- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
|
||||
-- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
-- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
--
|
||||
-- ### 3.1.2) A2G coordinates setting **menu**
|
||||
--
|
||||
@@ -183,7 +182,7 @@
|
||||
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
|
||||
--
|
||||
-- Each Message Type has specific timings that will be applied when the message is displayed.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen.
|
||||
-- So the player can choose its own amount of seconds how long a message should be displayed of a certain type.
|
||||
-- Note that **Update** messages can be chosen not to be displayed at all!
|
||||
--
|
||||
@@ -196,7 +195,7 @@
|
||||
--
|
||||
-- ## 3.5) **Era** of the battle
|
||||
--
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare.
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare.
|
||||
-- Therefore, there are 4 era that are defined within the settings:
|
||||
--
|
||||
-- - **WWII** era: Use for warfare with equipment during the world war II time.
|
||||
@@ -213,8 +212,8 @@
|
||||
SETTINGS = {
|
||||
ClassName = "SETTINGS",
|
||||
ShowPlayerMenu = true,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
}
|
||||
|
||||
SETTINGS.__Enum = {}
|
||||
@@ -231,11 +230,11 @@ SETTINGS.__Enum.Era = {
|
||||
Modern = 4,
|
||||
}
|
||||
|
||||
|
||||
do -- SETTINGS
|
||||
|
||||
--- SETTINGS constructor.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string PlayerName (Optional) Set settings for this player.
|
||||
-- @return #SETTINGS
|
||||
function SETTINGS:Set( PlayerName )
|
||||
|
||||
@@ -267,14 +266,14 @@ do -- SETTINGS
|
||||
-- Short text are better suited for, e.g., VR.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default).
|
||||
function SETTINGS:SetMenutextShort(onoff)
|
||||
function SETTINGS:SetMenutextShort( onoff )
|
||||
_SETTINGS.MenuShort = onoff
|
||||
end
|
||||
|
||||
--- Set menu to be static.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default).
|
||||
function SETTINGS:SetMenuStatic(onoff)
|
||||
function SETTINGS:SetMenuStatic( onoff )
|
||||
_SETTINGS.MenuStatic = onoff
|
||||
end
|
||||
|
||||
@@ -288,7 +287,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if metric.
|
||||
function SETTINGS:IsMetric()
|
||||
return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric())
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS imperial.
|
||||
@@ -301,7 +300,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if imperial.
|
||||
function SETTINGS:IsImperial()
|
||||
return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsMetric())
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS LL accuracy.
|
||||
@@ -343,13 +342,12 @@ do -- SETTINGS
|
||||
self.MessageTypeTimings[MessageType] = MessageTime
|
||||
end
|
||||
|
||||
|
||||
--- Gets the SETTINGS Message Display Timing of a MessageType
|
||||
-- @param #SETTINGS self
|
||||
-- @param Core.Message#MESSAGE MessageType The type of the message.
|
||||
-- @return #number
|
||||
function SETTINGS:GetMessageTime( MessageType )
|
||||
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType )
|
||||
return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType )
|
||||
end
|
||||
|
||||
--- Sets A2G LL DMS
|
||||
@@ -370,14 +368,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2G_LL_DMS()
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() )
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2G_LL_DDM()
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() )
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
|
||||
end
|
||||
|
||||
--- Sets A2G MGRS
|
||||
@@ -391,7 +389,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2G_MGRS()
|
||||
return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() )
|
||||
return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
|
||||
end
|
||||
|
||||
--- Sets A2G BRA
|
||||
@@ -405,7 +403,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2G_BR()
|
||||
return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() )
|
||||
return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR())
|
||||
end
|
||||
|
||||
--- Sets A2A BRA
|
||||
@@ -419,7 +417,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2A_BRAA()
|
||||
return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() )
|
||||
return (self.A2ASystem and self.A2ASystem == "BRAA") or (not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
|
||||
end
|
||||
|
||||
--- Sets A2A BULLS
|
||||
@@ -433,7 +431,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BULLS
|
||||
function SETTINGS:IsA2A_BULLS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
|
||||
end
|
||||
|
||||
--- Sets A2A LL DMS
|
||||
@@ -454,14 +452,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2A_LL_DMS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2A_LL_DDM()
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() )
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
|
||||
end
|
||||
|
||||
--- Sets A2A MGRS
|
||||
@@ -475,7 +473,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2A_MGRS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
@@ -494,37 +492,37 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
-------
|
||||
|
||||
local text="A2G Coordinate System"
|
||||
local text = "A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G Coordinates"
|
||||
text = "A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
-- Set LL DMS
|
||||
if not self:IsA2G_LL_DMS() then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DMS"
|
||||
text = "LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DDM
|
||||
if not self:IsA2G_LL_DDM() then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DDM"
|
||||
text = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DMS accuracy.
|
||||
if self:IsA2G_LL_DDM() then
|
||||
local text1="LL DDM Accuracy 1"
|
||||
local text2="LL DDM Accuracy 2"
|
||||
local text3="LL DDM Accuracy 3"
|
||||
local text1 = "LL DDM Accuracy 1"
|
||||
local text2 = "LL DDM Accuracy 2"
|
||||
local text3 = "LL DDM Accuracy 3"
|
||||
if _SETTINGS.MenuShort then
|
||||
text1="LL DDM"
|
||||
text1 = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
|
||||
@@ -533,18 +531,18 @@ do -- SETTINGS
|
||||
|
||||
-- Set BR.
|
||||
if not self:IsA2G_BR() then
|
||||
local text="Bearing, Range (BR)"
|
||||
local text = "Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="BR"
|
||||
text = "BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set MGRS.
|
||||
if not self:IsA2G_MGRS() then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="MGRS"
|
||||
text = "MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -562,24 +560,24 @@ do -- SETTINGS
|
||||
-- A2A Coordinate System
|
||||
-------
|
||||
|
||||
local text="A2A Coordinate System"
|
||||
local text = "A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A Coordinates"
|
||||
text = "A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if not self:IsA2A_LL_DMS() then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DMS"
|
||||
text = "LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DDM"
|
||||
text = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -592,25 +590,25 @@ do -- SETTINGS
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() then
|
||||
local text="Bullseye (BULLS)"
|
||||
local text = "Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Bulls"
|
||||
text = "Bulls"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() then
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="BRAA"
|
||||
text = "BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="MGRS"
|
||||
text = "MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -623,31 +621,31 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text="Measures and Weights System"
|
||||
local text = "Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Unit System"
|
||||
text = "Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if self:IsMetric() then
|
||||
local text="Imperial (Miles,Feet)"
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Imperial"
|
||||
text = "Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if self:IsImperial() then
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Metric"
|
||||
text = "Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text="Messages and Reports"
|
||||
local text = "Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Messages & Reports"
|
||||
text = "Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
@@ -688,7 +686,6 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
|
||||
|
||||
|
||||
SettingsMenu:Remove( MenuTime )
|
||||
|
||||
return self
|
||||
@@ -732,11 +729,11 @@ do -- SETTINGS
|
||||
|
||||
self.PlayerMenu = PlayerMenu
|
||||
|
||||
self:I(string.format("Setting menu for player %s", tostring(PlayerName)))
|
||||
self:I( string.format( "Setting menu for player %s", tostring( PlayerName ) ) )
|
||||
|
||||
local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
|
||||
@@ -753,40 +750,40 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
------
|
||||
|
||||
local text="A2G Coordinate System"
|
||||
local text = "A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G Coordinates"
|
||||
text = "A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G LL DMS"
|
||||
text = "A2G LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G LL DDM"
|
||||
text = "A2G LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_BR() or _SETTINGS.MenuStatic then
|
||||
local text="Bearing, Range (BR)"
|
||||
local text = "Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G BR"
|
||||
text = "A2G BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G MGRS"
|
||||
text = "A2G MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -795,49 +792,48 @@ do -- SETTINGS
|
||||
-- A2A Coordinates Menu
|
||||
------
|
||||
|
||||
local text="A2A Coordinate System"
|
||||
local text = "A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A Coordinates"
|
||||
text = "A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
|
||||
if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A LL DMS"
|
||||
text = "A2A LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A LL DDM"
|
||||
text = "A2A LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then
|
||||
local text="Bullseye (BULLS)"
|
||||
local text = "Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A BULLS"
|
||||
text = "A2A BULLS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A BRAA"
|
||||
text = "A2A BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A MGRS"
|
||||
text = "A2A MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -846,24 +842,24 @@ do -- SETTINGS
|
||||
-- Unit system
|
||||
---
|
||||
|
||||
local text="Measures and Weights System"
|
||||
local text = "Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Unit System"
|
||||
text = "Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if self:IsMetric() or _SETTINGS.MenuStatic then
|
||||
local text="Imperial (Miles,Feet)"
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Imperial"
|
||||
text = "Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
|
||||
end
|
||||
|
||||
if self:IsImperial() or _SETTINGS.MenuStatic then
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Metric"
|
||||
text = "Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
|
||||
end
|
||||
@@ -872,9 +868,9 @@ do -- SETTINGS
|
||||
-- Messages and Reports
|
||||
---
|
||||
|
||||
local text="Messages and Reports"
|
||||
local text = "Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Messages & Reports"
|
||||
text = "Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
@@ -934,39 +930,38 @@ do -- SETTINGS
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
@@ -979,12 +974,12 @@ do -- SETTINGS
|
||||
do
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
|
||||
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
|
||||
BASE:E( { self, PlayerUnit:GetName(), A2GSystem } )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -992,9 +987,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1002,9 +997,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1012,9 +1007,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1022,9 +1017,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1054,7 +1049,6 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Cold war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1064,7 +1058,6 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Modern war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1074,7 +1067,4 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -138,24 +138,24 @@ SPAWNSTATIC = {
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
|
||||
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
|
||||
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
|
||||
|
||||
if TemplateStatic then
|
||||
self.SpawnTemplatePrefix = SpawnTemplateName
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
|
||||
self.CountryID = SpawnCountryID or CountryID
|
||||
self.CategoryID = CategoryID
|
||||
self.CoalitionID = CoalitionID
|
||||
self.SpawnIndex = 0
|
||||
else
|
||||
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
|
||||
end
|
||||
if TemplateStatic then
|
||||
self.SpawnTemplatePrefix = SpawnTemplateName
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
|
||||
self.CountryID = SpawnCountryID or CountryID
|
||||
self.CategoryID = CategoryID
|
||||
self.CoalitionID = CoalitionID
|
||||
self.SpawnIndex = 0
|
||||
else
|
||||
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
|
||||
end
|
||||
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates the main object to spawn a @{Static} given a template table.
|
||||
@@ -422,7 +422,11 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
end
|
||||
|
||||
if self.InitCargo~=nil then
|
||||
Template.isCargo=self.InitCargo
|
||||
Template.canCargo=self.InitCargo
|
||||
end
|
||||
|
||||
if self.InitCargoMass~=nil then
|
||||
Template.mass=self.InitCargoMass
|
||||
end
|
||||
|
||||
if self.InitLinkUnit then
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
-- * Get zone properties.
|
||||
-- * Get zone bounding box.
|
||||
-- * Set/get zone name.
|
||||
-- * Draw zones (circular and polygon) on the F10 map.
|
||||
--
|
||||
--
|
||||
-- There are essentially two core functions that zones accomodate:
|
||||
@@ -56,6 +57,8 @@
|
||||
--- @type ZONE_BASE
|
||||
-- @field #string ZoneName Name of the zone.
|
||||
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
|
||||
-- @field #number DrawID Unique ID of the drawn zone on the F10 map.
|
||||
-- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -104,7 +107,9 @@ ZONE_BASE = {
|
||||
ClassName = "ZONE_BASE",
|
||||
ZoneName = "",
|
||||
ZoneProbability = 1,
|
||||
}
|
||||
DrawID=nil,
|
||||
Color={}
|
||||
}
|
||||
|
||||
|
||||
--- The ZONE_BASE.BoundingSquare
|
||||
@@ -178,12 +183,12 @@ function ZONE_BASE:IsCoordinateInZone( Coordinate )
|
||||
return InZone
|
||||
end
|
||||
|
||||
--- Returns if a PointVec2 is within the zone.
|
||||
--- Returns if a PointVec2 is within the zone. (Name is misleading, actually takes a #COORDINATE)
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 to test.
|
||||
-- @param Core.Point#COORDINATE PointVec2 The coordinate to test.
|
||||
-- @return #boolean true if the PointVec2 is within the zone.
|
||||
function ZONE_BASE:IsPointVec2InZone( PointVec2 )
|
||||
local InZone = self:IsVec2InZone( PointVec2:GetVec2() )
|
||||
function ZONE_BASE:IsPointVec2InZone( Coordinate )
|
||||
local InZone = self:IsVec2InZone( Coordinate:GetVec2() )
|
||||
return InZone
|
||||
end
|
||||
|
||||
@@ -221,22 +226,6 @@ function ZONE_BASE:GetPointVec2()
|
||||
end
|
||||
|
||||
|
||||
--- Returns a @{Core.Point#COORDINATE} of the zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return Core.Point#COORDINATE The Coordinate of the zone.
|
||||
function ZONE_BASE:GetCoordinate()
|
||||
self:F2( self.ZoneName )
|
||||
|
||||
local Vec2 = self:GetVec2()
|
||||
|
||||
local Coordinate = COORDINATE:NewFromVec2( Vec2 )
|
||||
|
||||
self:T2( { Coordinate } )
|
||||
|
||||
return Coordinate
|
||||
end
|
||||
|
||||
|
||||
--- Returns the @{DCS#Vec3} of the zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
|
||||
@@ -276,15 +265,27 @@ end
|
||||
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
|
||||
-- @return Core.Point#COORDINATE The Coordinate of the zone.
|
||||
function ZONE_BASE:GetCoordinate( Height ) --R2.1
|
||||
self:F2( self.ZoneName )
|
||||
self:F2(self.ZoneName)
|
||||
|
||||
local Vec3 = self:GetVec3( Height )
|
||||
|
||||
local PointVec3 = COORDINATE:NewFromVec3( Vec3 )
|
||||
if self.Coordinate then
|
||||
|
||||
self:T2( { PointVec3 } )
|
||||
-- Update coordinates.
|
||||
self.Coordinate.x=Vec3.x
|
||||
self.Coordinate.y=Vec3.y
|
||||
self.Coordinate.z=Vec3.z
|
||||
|
||||
return PointVec3
|
||||
--env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName))
|
||||
else
|
||||
|
||||
-- Create a new coordinate object.
|
||||
self.Coordinate=COORDINATE:NewFromVec3(Vec3)
|
||||
|
||||
--env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName))
|
||||
end
|
||||
|
||||
return self.Coordinate
|
||||
end
|
||||
|
||||
|
||||
@@ -324,6 +325,76 @@ function ZONE_BASE:BoundZone()
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Set color of zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`.
|
||||
-- @param #number Alpha Transparacy between 0 and 1. Default 0.15.
|
||||
-- @return #ZONE_BASE self
|
||||
function ZONE_BASE:SetColor(RGBcolor, Alpha)
|
||||
|
||||
RGBcolor=RGBcolor or {1, 0, 0}
|
||||
Alpha=Alpha or 0.15
|
||||
|
||||
self.Color={}
|
||||
self.Color[1]=RGBcolor[1]
|
||||
self.Color[2]=RGBcolor[2]
|
||||
self.Color[3]=RGBcolor[3]
|
||||
self.Color[4]=Alpha
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get color table of the zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
|
||||
function ZONE_BASE:GetColor()
|
||||
return self.Color
|
||||
end
|
||||
|
||||
--- Get RGB color of zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code.
|
||||
function ZONE_BASE:GetColorRGB()
|
||||
local rgb={}
|
||||
rgb[1]=self.Color[1]
|
||||
rgb[2]=self.Color[2]
|
||||
rgb[3]=self.Color[3]
|
||||
return rgb
|
||||
end
|
||||
|
||||
--- Get transperency Alpha value of zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #number Alpha value.
|
||||
function ZONE_BASE:GetColorAlpha()
|
||||
local alpha=self.Color[4]
|
||||
return alpha
|
||||
end
|
||||
|
||||
--- Remove the drawing of the zone from the F10 map.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param #number Delay (Optional) Delay before the drawing is removed.
|
||||
-- @return #ZONE_BASE self
|
||||
function ZONE_BASE:UndrawZone(Delay)
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self)
|
||||
else
|
||||
if self.DrawID then
|
||||
UTILS.RemoveMark(self.DrawID)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get ID of the zone object drawn on the F10 map.
|
||||
-- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #number Unique ID of the
|
||||
function ZONE_BASE:GetDrawID()
|
||||
return self.DrawID
|
||||
end
|
||||
|
||||
|
||||
--- Smokes the zone boundaries in a color.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
|
||||
@@ -421,10 +492,14 @@ end
|
||||
-- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone.
|
||||
-- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight.
|
||||
--
|
||||
-- ## Draw zone
|
||||
--
|
||||
-- * @{#ZONE_RADIUS.DrawZone}(): Draws the zone on the F10 map.
|
||||
--
|
||||
-- @field #ZONE_RADIUS
|
||||
ZONE_RADIUS = {
|
||||
ClassName="ZONE_RADIUS",
|
||||
}
|
||||
ClassName="ZONE_RADIUS",
|
||||
}
|
||||
|
||||
--- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius.
|
||||
-- @param #ZONE_RADIUS self
|
||||
@@ -433,13 +508,52 @@ ZONE_RADIUS = {
|
||||
-- @param DCS#Distance Radius The radius of the zone.
|
||||
-- @return #ZONE_RADIUS self
|
||||
function ZONE_RADIUS:New( ZoneName, Vec2, Radius )
|
||||
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS
|
||||
self:F( { ZoneName, Vec2, Radius } )
|
||||
|
||||
self.Radius = Radius
|
||||
self.Vec2 = Vec2
|
||||
-- Inherit ZONE_BASE.
|
||||
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS
|
||||
self:F( { ZoneName, Vec2, Radius } )
|
||||
|
||||
return self
|
||||
self.Radius = Radius
|
||||
self.Vec2 = Vec2
|
||||
|
||||
--self.Coordinate=COORDINATE:NewFromVec2(Vec2)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Update zone from a 2D vector.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param DCS#Vec2 Vec2 The location of the zone.
|
||||
-- @param DCS#Distance Radius The radius of the zone.
|
||||
-- @return #ZONE_RADIUS self
|
||||
function ZONE_RADIUS:UpdateFromVec2(Vec2, Radius)
|
||||
|
||||
-- New center of the zone.
|
||||
self.Vec2=Vec2
|
||||
|
||||
if Radius then
|
||||
self.Radius=Radius
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Update zone from a 2D vector.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param DCS#Vec3 Vec3 The location of the zone.
|
||||
-- @param DCS#Distance Radius The radius of the zone.
|
||||
-- @return #ZONE_RADIUS self
|
||||
function ZONE_RADIUS:UpdateFromVec3(Vec3, Radius)
|
||||
|
||||
-- New center of the zone.
|
||||
self.Vec2.x=Vec3.x
|
||||
self.Vec2.y=Vec3.z
|
||||
|
||||
if Radius then
|
||||
self.Radius=Radius
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Mark the zone with markers on the F10 map.
|
||||
@@ -469,6 +583,32 @@ function ZONE_RADIUS:MarkZone(Points)
|
||||
|
||||
end
|
||||
|
||||
--- Draw the zone circle on the F10 map.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
|
||||
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red.
|
||||
-- @param #number Alpha Transparency [0,1]. Default 1.
|
||||
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
|
||||
-- @param #number FillAlpha Transparency [0,1]. Default 0.15.
|
||||
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
|
||||
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
|
||||
-- @return #ZONE_RADIUS self
|
||||
function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
|
||||
|
||||
local coordinate=self:GetCoordinate()
|
||||
|
||||
local Radius=self:GetRadius()
|
||||
|
||||
Color=Color or self:GetColorRGB()
|
||||
Alpha=Alpha or 1
|
||||
FillColor=FillColor or Color
|
||||
FillAlpha=FillAlpha or self:GetColorAlpha()
|
||||
|
||||
self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Bounds the zone with tires.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number Points (optional) The amount of points in the circle. Default 360.
|
||||
@@ -606,11 +746,11 @@ end
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @return DCS#Vec2 The location of the zone.
|
||||
function ZONE_RADIUS:GetVec2()
|
||||
self:F2( self.ZoneName )
|
||||
self:F2( self.ZoneName )
|
||||
|
||||
self:T2( { self.Vec2 } )
|
||||
self:T2( { self.Vec2 } )
|
||||
|
||||
return self.Vec2
|
||||
return self.Vec2
|
||||
end
|
||||
|
||||
--- Sets the @{DCS#Vec2} of the zone.
|
||||
@@ -781,6 +921,32 @@ function ZONE_RADIUS:GetScannedSetUnit()
|
||||
return SetUnit
|
||||
end
|
||||
|
||||
--- Get a set of scanned units.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @return Core.Set#SET_GROUP Set of groups.
|
||||
function ZONE_RADIUS:GetScannedSetGroup()
|
||||
|
||||
self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP
|
||||
|
||||
self.ScanSetGroup.Set={}
|
||||
|
||||
if self.ScanData then
|
||||
for ObjectID, UnitObject in pairs( self.ScanData.Units ) do
|
||||
local UnitObject = UnitObject -- DCS#Unit
|
||||
if UnitObject:isExist() then
|
||||
|
||||
local FoundUnit=UNIT:FindByName(UnitObject:getName())
|
||||
if FoundUnit then
|
||||
local group=FoundUnit:GetGroup()
|
||||
self.ScanSetGroup:AddGroup(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self.ScanSetGroup
|
||||
end
|
||||
|
||||
|
||||
--- Count the number of different coalitions inside the zone.
|
||||
-- @param #ZONE_RADIUS self
|
||||
@@ -999,20 +1165,20 @@ end
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
-- @return DCS#Vec2 The random location within the zone.
|
||||
function ZONE_RADIUS:GetRandomVec2( inner, outer )
|
||||
self:F( self.ZoneName, inner, outer )
|
||||
self:F( self.ZoneName, inner, outer )
|
||||
|
||||
local Point = {}
|
||||
local Vec2 = self:GetVec2()
|
||||
local _inner = inner or 0
|
||||
local _outer = outer or self:GetRadius()
|
||||
local Point = {}
|
||||
local Vec2 = self:GetVec2()
|
||||
local _inner = inner or 0
|
||||
local _outer = outer or self:GetRadius()
|
||||
|
||||
local angle = math.random() * math.pi * 2;
|
||||
Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer);
|
||||
Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer);
|
||||
local angle = math.random() * math.pi * 2;
|
||||
Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer);
|
||||
Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer);
|
||||
|
||||
self:T( { Point } )
|
||||
self:T( { Point } )
|
||||
|
||||
return Point
|
||||
return Point
|
||||
end
|
||||
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
|
||||
@@ -1116,22 +1282,37 @@ ZONE = {
|
||||
}
|
||||
|
||||
|
||||
--- Constructor of ZONE, taking the zone name.
|
||||
--- Constructor of ZONE taking the zone name.
|
||||
-- @param #ZONE self
|
||||
-- @param #string ZoneName The name of the zone as defined within the mission editor.
|
||||
-- @return #ZONE
|
||||
-- @return #ZONE self
|
||||
function ZONE:New( ZoneName )
|
||||
|
||||
-- First try to find the zone in the DB.
|
||||
local zone=_DATABASE:FindZone(ZoneName)
|
||||
|
||||
if zone then
|
||||
--env.info("FF found zone in DB")
|
||||
return zone
|
||||
end
|
||||
|
||||
-- Get zone from DCS trigger function.
|
||||
local Zone = trigger.misc.getZone( ZoneName )
|
||||
|
||||
-- Error!
|
||||
if not Zone then
|
||||
error( "Zone " .. ZoneName .. " does not exist." )
|
||||
return nil
|
||||
end
|
||||
|
||||
local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, { x = Zone.point.x, y = Zone.point.z }, Zone.radius ) )
|
||||
self:F( ZoneName )
|
||||
-- Create a new ZONE_RADIUS.
|
||||
local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius))
|
||||
self:F(ZoneName)
|
||||
|
||||
-- Color of zone.
|
||||
self.Color={1, 0, 0, 0.15}
|
||||
|
||||
-- DCS zone.
|
||||
self.Zone = Zone
|
||||
|
||||
return self
|
||||
@@ -1392,40 +1573,88 @@ end
|
||||
-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone.
|
||||
-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone.
|
||||
--
|
||||
-- ## Draw zone
|
||||
--
|
||||
-- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map.
|
||||
-- * @{#ZONE_POLYGON_BASE.Boundary}(): Draw a frontier on the F10 map with small filled circles.
|
||||
--
|
||||
--
|
||||
-- @field #ZONE_POLYGON_BASE
|
||||
ZONE_POLYGON_BASE = {
|
||||
ClassName="ZONE_POLYGON_BASE",
|
||||
}
|
||||
|
||||
--- A points array.
|
||||
--- A 2D points array.
|
||||
-- @type ZONE_POLYGON_BASE.ListVec2
|
||||
-- @list <DCS#Vec2>
|
||||
-- @list <DCS#Vec2> Table of 2D vectors.
|
||||
|
||||
--- A 3D points array.
|
||||
-- @type ZONE_POLYGON_BASE.ListVec3
|
||||
-- @list <DCS#Vec3> Table of 3D vectors.
|
||||
|
||||
--- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCS#Vec2}, forming a polygon.
|
||||
-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #string ZoneName Name of the zone.
|
||||
-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon..
|
||||
-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon.
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
function ZONE_POLYGON_BASE:New( ZoneName, PointsArray )
|
||||
|
||||
-- Inherit ZONE_BASE.
|
||||
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) )
|
||||
self:F( { ZoneName, PointsArray } )
|
||||
|
||||
local i = 0
|
||||
if PointsArray then
|
||||
|
||||
self._.Polygon = {}
|
||||
|
||||
for i = 1, #PointsArray do
|
||||
self._.Polygon[i] = {}
|
||||
self._.Polygon[i].x = PointsArray[i].x
|
||||
self._.Polygon[i].y = PointsArray[i].y
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Update polygon points with an array of @{DCS#Vec2}.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon.
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array)
|
||||
|
||||
self._.Polygon = {}
|
||||
|
||||
for i = 1, #PointsArray do
|
||||
for i=1,#Vec2Array do
|
||||
self._.Polygon[i] = {}
|
||||
self._.Polygon[i].x = PointsArray[i].x
|
||||
self._.Polygon[i].y = PointsArray[i].y
|
||||
self._.Polygon[i].x=Vec2Array[i].x
|
||||
self._.Polygon[i].y=Vec2Array[i].y
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Update polygon points with an array of @{DCS#Vec3}.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #ZONE_POLYGON_BASE.ListVec3 Vec2Array An array of @{DCS#Vec3}, forming a polygon.
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array)
|
||||
|
||||
self._.Polygon = {}
|
||||
|
||||
for i=1,#Vec3Array do
|
||||
self._.Polygon[i] = {}
|
||||
self._.Polygon[i].x=Vec3Array[i].x
|
||||
self._.Polygon[i].y=Vec3Array[i].z
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the center location of the polygon.
|
||||
-- @param #ZONE_GROUP self
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location.
|
||||
function ZONE_POLYGON_BASE:GetVec2()
|
||||
self:F( self.ZoneName )
|
||||
@@ -1435,6 +1664,78 @@ function ZONE_POLYGON_BASE:GetVec2()
|
||||
return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 }
|
||||
end
|
||||
|
||||
--- Get a vertex of the polygon.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #number Index Index of the vertex. Default 1.
|
||||
-- @return DCS#Vec2 Vertex of the polygon.
|
||||
function ZONE_POLYGON_BASE:GetVertexVec2(Index)
|
||||
return self._.Polygon[Index or 1]
|
||||
end
|
||||
|
||||
--- Get a vertex of the polygon.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #number Index Index of the vertex. Default 1.
|
||||
-- @return DCS#Vec3 Vertex of the polygon.
|
||||
function ZONE_POLYGON_BASE:GetVertexVec3(Index)
|
||||
local vec2=self:GetVertexVec2(Index)
|
||||
if vec2 then
|
||||
local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y}
|
||||
return vec3
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get a vertex of the polygon.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #number Index Index of the vertex. Default 1.
|
||||
-- @return Core.Point#COORDINATE Vertex of the polygon.
|
||||
function ZONE_POLYGON_BASE:GetVertexCoordinate(Index)
|
||||
local vec2=self:GetVertexVec2(Index)
|
||||
if vec2 then
|
||||
local coord=COORDINATE:NewFromVec2(vec2)
|
||||
return coord
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Get a list of verticies of the polygon.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return <DCS#Vec2> List of DCS#Vec2 verticies defining the edges of the polygon.
|
||||
function ZONE_POLYGON_BASE:GetVerticiesVec2()
|
||||
return self._.Polygon
|
||||
end
|
||||
|
||||
--- Get a list of verticies of the polygon.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return #table List of DCS#Vec3 verticies defining the edges of the polygon.
|
||||
function ZONE_POLYGON_BASE:GetVerticiesVec3()
|
||||
|
||||
local coords={}
|
||||
|
||||
for i,vec2 in ipairs(self._.Polygon) do
|
||||
local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y}
|
||||
table.insert(coords, vec3)
|
||||
end
|
||||
|
||||
return coords
|
||||
end
|
||||
|
||||
--- Get a list of verticies of the polygon.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return #table List of COORDINATES verticies defining the edges of the polygon.
|
||||
function ZONE_POLYGON_BASE:GetVerticiesCoordinates()
|
||||
|
||||
local coords={}
|
||||
|
||||
for i,vec2 in ipairs(self._.Polygon) do
|
||||
local coord=COORDINATE:NewFromVec2(vec2)
|
||||
table.insert(coords, coord)
|
||||
end
|
||||
|
||||
return coords
|
||||
end
|
||||
|
||||
--- Flush polygon coordinates as a table in DCS.log.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
@@ -1494,6 +1795,46 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound )
|
||||
end
|
||||
|
||||
|
||||
--- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported!
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
|
||||
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red.
|
||||
-- @param #number Alpha Transparency [0,1]. Default 1.
|
||||
-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value.
|
||||
-- @param #number FillAlpha Transparency [0,1]. Default 0.15.
|
||||
-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
|
||||
-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false.
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
|
||||
|
||||
local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1])
|
||||
|
||||
Color=Color or self:GetColorRGB()
|
||||
Alpha=Alpha or 1
|
||||
FillColor=FillColor or Color
|
||||
FillAlpha=FillAlpha or self:GetColorAlpha()
|
||||
|
||||
|
||||
if #self._.Polygon==4 then
|
||||
|
||||
local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2])
|
||||
local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3])
|
||||
local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4])
|
||||
|
||||
self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
|
||||
|
||||
else
|
||||
|
||||
local Coordinates=self:GetVerticiesCoordinates()
|
||||
table.remove(Coordinates, 1)
|
||||
|
||||
self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
|
||||
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Smokes the zone boundaries in a color.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
@@ -1685,6 +2026,45 @@ function ZONE_POLYGON_BASE:GetBoundingSquare()
|
||||
return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }
|
||||
end
|
||||
|
||||
--- Draw a frontier on the F10 map with small filled circles.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All.
|
||||
-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1, 0, 0} for red. Default {1, 1, 1}= White.
|
||||
-- @param #number Radius (Optional) Radius of the circles in meters. Default 1000.
|
||||
-- @param #number Alpha (Optional) Alpha transparency [0,1]. Default 1.
|
||||
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
|
||||
-- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed)
|
||||
Coalition = Coalition or -1
|
||||
Color = Color or {1, 1, 1}
|
||||
Radius = Radius or 1000
|
||||
Alpha = Alpha or 1
|
||||
Segments = Segments or 10
|
||||
Closed = Closed or false
|
||||
local i = 1
|
||||
local j = #self._.Polygon
|
||||
if (Closed) then
|
||||
Limit = #self._.Polygon + 1
|
||||
else
|
||||
Limit = #self._.Polygon
|
||||
end
|
||||
while i <= #self._.Polygon do
|
||||
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
if j ~= Limit then
|
||||
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
|
||||
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
|
||||
for Segment = 0, Segments do
|
||||
local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments )
|
||||
local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments )
|
||||
ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true)
|
||||
end
|
||||
end
|
||||
j = i
|
||||
i = i + 1
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- @type ZONE_POLYGON
|
||||
-- @extends #ZONE_POLYGON_BASE
|
||||
@@ -1711,7 +2091,7 @@ end
|
||||
-- This is especially handy if you want to quickly setup a SET_ZONE...
|
||||
-- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`,
|
||||
-- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection,
|
||||
-- without much scripting overhead!!!
|
||||
-- without much scripting overhead!
|
||||
--
|
||||
-- @field #ZONE_POLYGON
|
||||
ZONE_POLYGON = {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
-- @module Functional.FOX
|
||||
-- @image Functional_FOX.png
|
||||
|
||||
|
||||
--- FOX class.
|
||||
-- @type FOX
|
||||
-- @field #string ClassName Name of the class.
|
||||
@@ -48,7 +47,6 @@
|
||||
-- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec.
|
||||
-- @field #number dt01 Time step [sec] for missile position updates if distance to target > 1 km and < 5 km. Default 0.1 sec.
|
||||
-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
|
||||
-- @field #boolean
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Fox 3!
|
||||
@@ -1707,8 +1705,8 @@ end
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FOX self
|
||||
-- @param DCS#Weapon weapon The weapon.
|
||||
-- @return #number Notching heading right, i.e. missile heading +90�
|
||||
-- @return #number Notching heading left, i.e. missile heading -90�.
|
||||
-- @return #number Notching heading right, i.e. missile heading +90°.
|
||||
-- @return #number Notching heading left, i.e. missile heading -90°.
|
||||
function FOX:_GetNotchingHeadings(weapon)
|
||||
|
||||
if weapon then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range �
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range °
|
||||
-- * Provide alerts when a missile would have killed your aircraft.
|
||||
-- * Provide alerts when the missile self destructs.
|
||||
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
|
||||
|
||||
@@ -742,7 +742,7 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
|
||||
local T=position:GetTemperature()
|
||||
|
||||
-- Correct unit system.
|
||||
local _T=string.format('%d°F', UTILS.CelciusToFarenheit(T))
|
||||
local _T=string.format('%d°F', UTILS.CelsiusToFahrenheit(T))
|
||||
if settings:IsMetric() then
|
||||
_T=string.format('%d°C', T)
|
||||
end
|
||||
|
||||
@@ -5371,7 +5371,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take
|
||||
|
||||
if spawnonground then
|
||||
|
||||
-- Sh�ps and FARPS seem to have a build in queue.
|
||||
-- Sh°ps and FARPS seem to have a build in queue.
|
||||
if spawnonship or spawnonfarp or spawnonrunway or automatic then
|
||||
self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.", self.alias, departure:GetName()))
|
||||
|
||||
@@ -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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,8 @@
|
||||
--
|
||||
-- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible.
|
||||
-- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars.
|
||||
-- * SEAD calculates the time it takes for a HARM to reach the target - and will attempt to minimize the shut-down time.
|
||||
-- * Detection and evasion of shots has a random component based on the skill level of the SAM groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -17,19 +19,24 @@
|
||||
--
|
||||
-- ### Authors: **FlightControl**, **applevangelist**
|
||||
--
|
||||
-- Last Update: April 2021
|
||||
-- Last Update: Nov 2021
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.Sead
|
||||
-- @image SEAD.JPG
|
||||
|
||||
--- @type SEAD
|
||||
---
|
||||
-- @type SEAD
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
--
|
||||
-- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon.
|
||||
-- Once a HARM attack is detected, SEAD will shut down the radars of the attacked SAM site and take evasive action by moving the SAM
|
||||
-- vehicles around (*if* they are drivable, that is). There's a component of randomness in detection and evasion, which is based on the
|
||||
-- skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a
|
||||
-- period of time to stay defensive, before it takes evasive actions.
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
@@ -39,44 +46,30 @@
|
||||
--
|
||||
-- @field #SEAD
|
||||
SEAD = {
|
||||
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
|
||||
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
|
||||
Padding = 10,
|
||||
CallBack = nil,
|
||||
UseCallBack = false,
|
||||
}
|
||||
|
||||
-- TODO Complete list?
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SEAD.Harms = {
|
||||
--[[
|
||||
["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired
|
||||
["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired
|
||||
["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired
|
||||
["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired
|
||||
["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired
|
||||
["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired
|
||||
["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired
|
||||
["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired
|
||||
["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired
|
||||
["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired
|
||||
["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired
|
||||
["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired
|
||||
["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired
|
||||
["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired
|
||||
--]]
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARN"] = "ALARM",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
@@ -85,41 +78,68 @@ SEAD = {
|
||||
["Kh25"] = "Kh25",
|
||||
}
|
||||
|
||||
--- Missile enumerators - from DCS ME and Wikipedia
|
||||
-- @field HarmData
|
||||
SEAD.HarmData = {
|
||||
-- km and mach
|
||||
["AGM_88"] = { 150, 3},
|
||||
["AGM_45"] = { 12, 2},
|
||||
["AGM_122"] = { 16.5, 2.3},
|
||||
["AGM_84"] = { 280, 0.85},
|
||||
["ALARM"] = { 45, 2},
|
||||
["LD-10"] = { 60, 4},
|
||||
["X_58"] = { 70, 4},
|
||||
["X_28"] = { 80, 2.5},
|
||||
["X_25"] = { 25, 0.76},
|
||||
["X_31"] = {150, 3},
|
||||
["Kh25"] = {25, 0.8},
|
||||
}
|
||||
|
||||
--- 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 #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
|
||||
-- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken.
|
||||
-- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time
|
||||
-- @return #SEAD self
|
||||
-- @usage
|
||||
-- -- CCCP SEAD Defenses
|
||||
-- -- Defends the Russian SA installations from SEAD attacks.
|
||||
-- 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 )
|
||||
function SEAD:New( SEADGroupPrefixes, Padding )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( 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.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
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:I("*** SEAD - Started Version 0.2.7")
|
||||
return self
|
||||
local padding = Padding or 10
|
||||
if padding < 10 then padding = 10 end
|
||||
self.Padding = padding
|
||||
self.UseEmissionsOnOff = false
|
||||
|
||||
self.CallBack = nil
|
||||
self.UseCallBack = false
|
||||
|
||||
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
|
||||
|
||||
self:I("*** SEAD - Started Version 0.3.3")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Update the active SEAD Set
|
||||
--- Update the active SEAD Set (while running)
|
||||
-- @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 )
|
||||
self:T( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
@@ -134,10 +154,10 @@ 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
|
||||
-- @param #number range Set the engagement range in percent, e.g. 55 (default 75)
|
||||
-- @return #SEAD self
|
||||
function SEAD:SetEngagementRange(range)
|
||||
self:F( { range } )
|
||||
self:T( { range } )
|
||||
range = range or 75
|
||||
if range < 0 or range > 100 then
|
||||
range = 75
|
||||
@@ -147,129 +167,218 @@ function SEAD:SetEngagementRange(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: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
|
||||
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
self:T({ SEADWeapon })
|
||||
|
||||
--[[check for SEAD missiles
|
||||
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.LD-10" --LD-10 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_84E" --AGM84 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired
|
||||
--]]
|
||||
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
|
||||
--- Set the padding in seconds, which extends the radar off time calculated by SEAD
|
||||
-- @param #SEAD self
|
||||
-- @param #number Padding Extra number of seconds to add for the switch-on (default 10 seconds)
|
||||
-- @return #SEAD self
|
||||
function SEAD:SetPadding(Padding)
|
||||
self:T( { Padding } )
|
||||
local padding = Padding or 10
|
||||
if padding < 10 then padding = 10 end
|
||||
self.Padding = padding
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set SEAD to use emissions on/off in addition to alarm state.
|
||||
-- @param #SEAD self
|
||||
-- @param #boolean Switch True for on, false for off.
|
||||
-- @return #SEAD self
|
||||
function SEAD:SwitchEmissions(Switch)
|
||||
self:T({Switch})
|
||||
self.UseEmissionsOnOff = Switch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an object to call back when going evasive.
|
||||
-- @param #SEAD self
|
||||
-- @param #table Object The object to call. Needs to have object functions as follows:
|
||||
-- `:SeadSuppressionPlanned(Group, Name, SuppressionStartTime, SuppressionEndTime)`
|
||||
-- `:SeadSuppressionStart(Group, Name)`,
|
||||
-- `:SeadSuppressionEnd(Group, Name)`,
|
||||
-- @return #SEAD self
|
||||
function SEAD:AddCallBack(Object)
|
||||
self:T({Class=Object.ClassName})
|
||||
self.CallBack = Object
|
||||
self.UseCallBack = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Check if a known HARM was fired
|
||||
-- @param #SEAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
-- @return #string name Name of hit in table
|
||||
function SEAD:_CheckHarms(WeaponName)
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
local name = ""
|
||||
for _,_name in pairs (SEAD.Harms) do
|
||||
if string.find(WeaponName,_name,1) then
|
||||
hit = true
|
||||
name = _name
|
||||
break
|
||||
end
|
||||
end
|
||||
return hit, name
|
||||
end
|
||||
|
||||
--- (Internal) Return distance in meters between two coordinates or -1 on error.
|
||||
-- @param #SEAD self
|
||||
-- @param Core.Point#COORDINATE _point1 Coordinate one
|
||||
-- @param Core.Point#COORDINATE _point2 Coordinate two
|
||||
-- @return #number Distance in meters
|
||||
function SEAD:_GetDistance(_point1, _point2)
|
||||
self:T("_GetDistance")
|
||||
if _point1 and _point2 then
|
||||
local distance1 = _point1:Get2DDistance(_point2)
|
||||
local distance2 = _point1:DistanceFromPointVec2(_point2)
|
||||
--self:T({dist1=distance1, dist2=distance2})
|
||||
if distance1 and type(distance1) == "number" then
|
||||
return distance1
|
||||
elseif distance2 and type(distance2) == "number" then
|
||||
return distance2
|
||||
else
|
||||
self:E("*****Cannot calculate distance!")
|
||||
self:E({_point1,_point2})
|
||||
return -1
|
||||
end
|
||||
else
|
||||
self:E("******Cannot calculate distance!")
|
||||
self:E({_point1,_point2})
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) 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.
|
||||
-- @param #SEAD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #SEAD self
|
||||
function SEAD:HandleEventShot( EventData )
|
||||
self:T( { EventData.id } )
|
||||
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
|
||||
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
--self:T({ SEADWeapon })
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
self:T( '*** SEAD - Weapon Match' )
|
||||
local _targetskill = "Random"
|
||||
local _targetgroupname = "none"
|
||||
local _target = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
|
||||
local _targetgroup = nil -- Wrapper.Group#GROUP
|
||||
if _targetUnit and _targetUnit:IsAlive() then
|
||||
_targetgroup = _targetUnit:GetGroup()
|
||||
_targetgroupname = _targetgroup:GetName() -- group name
|
||||
local _targetUnitName = _targetUnit:GetName()
|
||||
_targetUnit:GetSkill()
|
||||
_targetskill = _targetUnit:GetSkill()
|
||||
end
|
||||
-- see if we are shot at
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
self:T( _targetgroupname, SEADGroupPrefix )
|
||||
if string.find( _targetgroupname, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( '*** SEAD - Group Match 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
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
self:T("*** SEAD - Evading")
|
||||
-- calculate distance of attacker
|
||||
local _targetpos = _targetgroup:GetCoordinate()
|
||||
local _distance = self:_GetDistance(SEADPlanePos, _targetpos)
|
||||
-- weapon speed
|
||||
local hit, data = self:_CheckHarms(SEADWeaponName)
|
||||
local wpnspeed = 666 -- ;)
|
||||
local reach = 10
|
||||
if hit then
|
||||
local wpndata = SEAD.HarmData[data]
|
||||
reach = wpndata[1] * 1,1
|
||||
local mach = wpndata[2]
|
||||
wpnspeed = math.floor(mach * 340.29)
|
||||
end
|
||||
-- time to impact
|
||||
local _tti = math.floor(_distance / wpnspeed) -- estimated impact time
|
||||
if _distance > 0 then
|
||||
_distance = math.floor(_distance / 1000) -- km
|
||||
else
|
||||
_distance = 0
|
||||
end
|
||||
|
||||
self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti ))
|
||||
|
||||
if reach >= _distance then
|
||||
self:T("*** SEAD - Shot in Reach")
|
||||
|
||||
local function SuppressionStart(args)
|
||||
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
|
||||
local grp = args[1] -- Wrapper.Group#GROUP
|
||||
local name = args[2] -- #string Group Name
|
||||
if self.UseEmissionsOnOff then
|
||||
grp:EnableEmission(false)
|
||||
end
|
||||
grp:OptionAlarmStateGreen() -- needed else we cannot move around
|
||||
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionStart(grp,name)
|
||||
end
|
||||
end
|
||||
|
||||
local function SuppressionStop(args)
|
||||
self:T(string.format("*** SEAD - %s Radar On",args[2]))
|
||||
local grp = args[1] -- Wrapper.Group#GROUP
|
||||
local name = args[2] -- #string Group Nam
|
||||
if self.UseEmissionsOnOff then
|
||||
grp:EnableEmission(true)
|
||||
end
|
||||
grp:OptionAlarmStateAuto()
|
||||
grp:OptionEngageRange(self.EngagementRange)
|
||||
self.SuppressedGroups[name] = false
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionEnd(grp,name)
|
||||
end
|
||||
end
|
||||
|
||||
-- randomize switch-on time
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
if delay > _tti then delay = delay / 2 end -- speed up
|
||||
if _tti > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar
|
||||
|
||||
local SuppressionStartTime = timer.getTime() + delay
|
||||
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
|
||||
|
||||
if not self.SuppressedGroups[_targetgroupname] then
|
||||
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
|
||||
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname},SuppressionStartTime)
|
||||
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
|
||||
self.SuppressedGroups[_targetgroupname] = true
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: May 2021
|
||||
-- Date: July 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -108,28 +108,12 @@ do
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SHORAD.Harms = {
|
||||
--[[
|
||||
["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired
|
||||
["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired
|
||||
["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired
|
||||
["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired
|
||||
["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired
|
||||
["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired
|
||||
["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired
|
||||
["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired
|
||||
["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired
|
||||
["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired
|
||||
["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired
|
||||
["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired
|
||||
["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired
|
||||
["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired
|
||||
--]]
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARN"] = "ALARM",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
@@ -157,7 +141,9 @@ do
|
||||
-- @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"
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition)
|
||||
-- @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})
|
||||
|
||||
@@ -171,22 +157,23 @@ do
|
||||
self.ActiveTimer = ActiveTimer or 600
|
||||
self.ActiveGroups = {}
|
||||
self.Groupset = GroupSet
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
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 = true -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.2.5")
|
||||
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})
|
||||
@@ -195,31 +182,48 @@ 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,10 do
|
||||
for i=1,100 do
|
||||
math.random()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean debug Switch debug on (true) or off (false)
|
||||
function SHORAD:SwitchDebug(debug)
|
||||
self:T( { debug } )
|
||||
local onoff = debug or false
|
||||
if debug then
|
||||
self.debug = true
|
||||
--tracing
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SHORAD")
|
||||
function SHORAD:SwitchDebug(onoff)
|
||||
self:T( { onoff } )
|
||||
if onoff then
|
||||
self:SwitchDebugOn()
|
||||
else
|
||||
self.debug = false
|
||||
BASE:TraceOff()
|
||||
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
|
||||
@@ -229,6 +233,7 @@ do
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendHarms = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for AGMs
|
||||
@@ -238,6 +243,7 @@ do
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendMavs = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set defense probability limits
|
||||
@@ -256,35 +262,42 @@ do
|
||||
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
|
||||
@@ -292,6 +305,7 @@ do
|
||||
-- @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
|
||||
@@ -307,6 +321,7 @@ do
|
||||
-- @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
|
||||
@@ -322,6 +337,7 @@ do
|
||||
-- @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
|
||||
@@ -344,6 +360,7 @@ do
|
||||
-- @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
|
||||
@@ -352,7 +369,7 @@ do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
returnname = true
|
||||
_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
@@ -363,6 +380,7 @@ do
|
||||
-- @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
|
||||
@@ -381,6 +399,7 @@ do
|
||||
-- @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
|
||||
@@ -405,6 +424,7 @@ do
|
||||
-- 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
|
||||
@@ -442,7 +462,7 @@ do
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
_group:SetAIOn()
|
||||
--_group:SetAIOn()
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
@@ -454,14 +474,15 @@ do
|
||||
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:OnEventShot( EventData )
|
||||
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
|
||||
|
||||
@@ -1601,7 +1601,7 @@ WAREHOUSE = {
|
||||
-- @field #number range Range of the unit in meters.
|
||||
-- @field #number speedmax Maximum speed in km/h the group can do.
|
||||
-- @field #number size Maximum size in length and with of the asset in meters.
|
||||
-- @field #number weight The weight of the whole asset group in kilo gramms.
|
||||
-- @field #number weight The weight of the whole asset group in kilograms.
|
||||
-- @field DCS#Object.Desc DCSdesc All DCS descriptors.
|
||||
-- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group.
|
||||
-- @field #table cargobay Array of cargo bays of all units in an asset group.
|
||||
|
||||
@@ -715,20 +715,21 @@ do -- ZONE_CAPTURE_COALITION
|
||||
|
||||
local UnitHit = EventData.TgtUnit
|
||||
|
||||
if UnitHit.ClassName ~= "SCENERY" then
|
||||
-- Check if unit is inside the capture zone and that it is of the defending coalition.
|
||||
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
|
||||
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
|
||||
|
||||
-- Update last hit time.
|
||||
self.HitTimeLast=timer.getTime()
|
||||
-- Update last hit time.
|
||||
self.HitTimeLast=timer.getTime()
|
||||
|
||||
-- Only trigger attacked event if not already in state "Attacked".
|
||||
if self:GetState()~="Attacked" then
|
||||
self:F2("Hit ==> Attack")
|
||||
self:Attack()
|
||||
end
|
||||
-- Only trigger attacked event if not already in state "Attacked".
|
||||
if self:GetState()~="Attacked" then
|
||||
self:F2("Hit ==> Attack")
|
||||
self:Attack()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -890,12 +891,14 @@ do -- ZONE_CAPTURE_COALITION
|
||||
end
|
||||
|
||||
-- Status text.
|
||||
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
|
||||
local NewState = self:GetState()
|
||||
if NewState~=State then
|
||||
text=text..string.format(" --> %s", NewState)
|
||||
if false then
|
||||
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
|
||||
local NewState = self:GetState()
|
||||
if NewState~=State then
|
||||
text=text..string.format(" --> %s", NewState)
|
||||
end
|
||||
self:I(text)
|
||||
end
|
||||
self:I(text)
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -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,9 +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()
|
||||
_DATABASE:_RegisterZones()
|
||||
|
||||
--- 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,9 +22,6 @@ __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' )
|
||||
@@ -74,6 +73,8 @@ __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
|
||||
@@ -112,6 +113,13 @@ __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' )
|
||||
|
||||
+1037
-831
File diff suppressed because it is too large
Load Diff
+6047
-6335
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
@@ -802,7 +802,7 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
|
||||
-- Debug.
|
||||
local text=string.format("Unit %s crashed or ejected.", unitname)
|
||||
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
|
||||
self:I(self.lid..text)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Get coordinate of unit.
|
||||
local coord=unit:GetCoordinate()
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
--- **Sound** - Radio transmissions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provide radio functionality to broadcast radio transmissions.
|
||||
--
|
||||
-- What are radio communications in DCS?
|
||||
--
|
||||
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
|
||||
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
|
||||
--
|
||||
-- How to supply DCS my own Sound Files?
|
||||
--
|
||||
-- * Your sound files need to be encoded in **.ogg** or .wav,
|
||||
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
|
||||
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
|
||||
-- * For simplicity sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
|
||||
--
|
||||
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
|
||||
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
|
||||
--
|
||||
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
|
||||
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
|
||||
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
|
||||
-- you won't hear/be able to use the TACAN beacon informations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Sound.Radio
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
|
||||
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
|
||||
--
|
||||
-- # RADIO usage
|
||||
--
|
||||
-- There are 3 steps to a successful radio transmission.
|
||||
--
|
||||
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
|
||||
-- * Then, you will **set the relevant parameters** to the transmission (see below),
|
||||
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
|
||||
--
|
||||
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
|
||||
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
|
||||
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
|
||||
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
|
||||
--
|
||||
-- Additional Methods to set relevant parameters if the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
|
||||
--
|
||||
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
|
||||
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
|
||||
--
|
||||
-- Additional Methods to set relevant parameters if the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
|
||||
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
|
||||
--
|
||||
-- What is this power thing?
|
||||
--
|
||||
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
|
||||
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
|
||||
-- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
|
||||
-- * This an automated DCS calculation you have no say on,
|
||||
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
|
||||
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
||||
--
|
||||
-- @type RADIO
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
|
||||
-- @field #string FileName Name of the sound file played.
|
||||
-- @field #number Frequency Frequency of the transmission in Hz.
|
||||
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM).
|
||||
-- @field #string Subtitle Subtitle of the transmission.
|
||||
-- @field #number SubtitleDuration Duration of the Subtitle in seconds.
|
||||
-- @field #number Power Power of the antenna is Watts.
|
||||
-- @field #boolean Loop Transmission is repeated (default true).
|
||||
-- @field #string alias Name of the radio transmitter.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIO = {
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
SubtitleDuration = 0,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias = nil,
|
||||
}
|
||||
|
||||
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
|
||||
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead.
|
||||
-- @param #RADIO self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #RADIO The RADIO object or #nil if Positionable is invalid.
|
||||
function RADIO:New(Positionable)
|
||||
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
|
||||
self:F(Positionable)
|
||||
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Set alias of the transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @param #string alias Name of the radio transmitter.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetAlias(alias)
|
||||
self.alias=tostring(alias)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get alias of the transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @return #string Name of the transmitter.
|
||||
function RADIO:GetAlias()
|
||||
return tostring(self.alias)
|
||||
end
|
||||
|
||||
--- Set the file name for the radio transmission.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFileName(FileName)
|
||||
self:F2(FileName)
|
||||
|
||||
if type(FileName) == "string" then
|
||||
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
|
||||
self.FileName = FileName
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the frequency for the radio transmission.
|
||||
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFrequency(Frequency)
|
||||
self:F2(Frequency)
|
||||
|
||||
if type(Frequency) == "number" then
|
||||
|
||||
-- If frequency is in range
|
||||
-- if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
|
||||
|
||||
-- Convert frequency from MHz to Hz
|
||||
self.Frequency = Frequency * 1000000
|
||||
|
||||
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
local commandSetFrequency={
|
||||
id = "SetFrequency",
|
||||
params = {
|
||||
frequency = self.Frequency,
|
||||
modulation = self.Modulation,
|
||||
}
|
||||
}
|
||||
|
||||
self:T2(commandSetFrequency)
|
||||
self.Positionable:SetCommand(commandSetFrequency)
|
||||
end
|
||||
|
||||
return self
|
||||
-- end
|
||||
end
|
||||
|
||||
self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set AM or FM modulation of the radio transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetModulation(Modulation)
|
||||
self:F2(Modulation)
|
||||
if type(Modulation) == "number" then
|
||||
if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self.Modulation = Modulation
|
||||
return self
|
||||
end
|
||||
end
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.", self.Modulation})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the power passed and sets RADIO.Power
|
||||
-- @param #RADIO self
|
||||
-- @param #number Power Power in W.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetPower(Power)
|
||||
self:F2(Power)
|
||||
|
||||
if type(Power) == "number" then
|
||||
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
else
|
||||
self:E({"Power is invalid. Power unchanged.", self.Power})
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set message looping on or off.
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean Loop If true, message is repeated indefinitely.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetLoop(Loop)
|
||||
self:F2(Loop)
|
||||
if type(Loop) == "boolean" then
|
||||
self.Loop = Loop
|
||||
return self
|
||||
end
|
||||
self:E({"Loop is invalid. Loop unchanged.", self.Loop})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the subtitle and the subtitleDuration passed and sets RADIO.subtitle and RADIO.subtitleDuration
|
||||
-- Both parameters are mandatory, since it wouldn't make much sense to change the Subtitle and not its duration
|
||||
-- @param #RADIO self
|
||||
-- @param #string Subtitle
|
||||
-- @param #number SubtitleDuration in s
|
||||
-- @return #RADIO self
|
||||
-- @usage
|
||||
-- -- create the broadcaster and attaches it a RADIO
|
||||
-- local MyUnit = UNIT:FindByName("MyUnit")
|
||||
-- local MyUnitRadio = MyUnit:GetRadio()
|
||||
--
|
||||
-- -- add a subtitle for the next transmission, which will be up for 10s
|
||||
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
|
||||
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
self:F2({Subtitle, SubtitleDuration})
|
||||
if type(Subtitle) == "string" then
|
||||
self.Subtitle = Subtitle
|
||||
else
|
||||
self.Subtitle = ""
|
||||
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
|
||||
end
|
||||
if type(SubtitleDuration) == "number" then
|
||||
self.SubtitleDuration = SubtitleDuration
|
||||
else
|
||||
self.SubtitleDuration = 0
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- Only the #RADIO and the Filename are mandatory
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of the sound file that will be transmitted.
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
|
||||
-- @param #number Power Power in W.
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
|
||||
self:F({FileName, Frequency, Modulation, Power})
|
||||
|
||||
self:SetFileName(FileName)
|
||||
if Frequency then self:SetFrequency(Frequency) end
|
||||
if Modulation then self:SetModulation(Modulation) end
|
||||
if Power then self:SetPower(Power) end
|
||||
if Loop then self:SetLoop(Loop) end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
|
||||
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
|
||||
-- Only the RADIO and the Filename are mandatory.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of sound file.
|
||||
-- @param #string Subtitle Subtitle to be displayed with sound file.
|
||||
-- @param #number SubtitleDuration Duration of subtitle display in seconds.
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #boolean Loop If true, loop message.
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
|
||||
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
|
||||
|
||||
-- Set file name.
|
||||
self:SetFileName(FileName)
|
||||
|
||||
-- Set modulation AM/FM.
|
||||
if Modulation then
|
||||
self:SetModulation(Modulation)
|
||||
end
|
||||
|
||||
-- Set frequency.
|
||||
if Frequency then
|
||||
self:SetFrequency(Frequency)
|
||||
end
|
||||
|
||||
-- Set subtitle.
|
||||
if Subtitle then
|
||||
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
|
||||
end
|
||||
|
||||
-- Set Looping.
|
||||
if Loop then
|
||||
self:SetLoop(Loop)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Broadcast the transmission.
|
||||
-- * The Radio has to be populated with the new transmission before broadcasting.
|
||||
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
|
||||
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
|
||||
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
|
||||
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
|
||||
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
|
||||
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
|
||||
-- @return #RADIO self
|
||||
function RADIO:Broadcast(viatrigger)
|
||||
self:F({viatrigger=viatrigger})
|
||||
|
||||
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
|
||||
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
|
||||
self:T("Broadcasting from a UNIT or a GROUP")
|
||||
|
||||
local commandTransmitMessage={
|
||||
id = "TransmitMessage",
|
||||
params = {
|
||||
file = self.FileName,
|
||||
duration = self.SubtitleDuration,
|
||||
subtitle = self.Subtitle,
|
||||
loop = self.Loop,
|
||||
}}
|
||||
|
||||
self:T3(commandTransmitMessage)
|
||||
self.Positionable:SetCommand(commandTransmitMessage)
|
||||
else
|
||||
-- If the POSITIONABLE is anything else, we revert to the general singleton function
|
||||
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
|
||||
self:T("Broadcasting from a POSITIONABLE")
|
||||
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Stops a transmission
|
||||
-- This function is especially usefull to stop the broadcast of looped transmissions
|
||||
-- @param #RADIO self
|
||||
-- @return #RADIO self
|
||||
function RADIO:StopBroadcast()
|
||||
self:F()
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
local commandStopTransmission={id="StopTransmission", params={}}
|
||||
|
||||
self.Positionable:SetCommand(commandStopTransmission)
|
||||
else
|
||||
-- Else, we use the appropriate singleton funciton
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
end
|
||||
return self
|
||||
end
|
||||
+74
-21
@@ -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
|
||||
@@ -281,18 +326,8 @@ end
|
||||
-- @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()
|
||||
+14
-15
@@ -11,7 +11,7 @@
|
||||
--
|
||||
-- ### Authors: FlightControl
|
||||
--
|
||||
-- @module Core.RadioSpeech
|
||||
-- @module Sound.RadioSpeech
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Makes the radio speak.
|
||||
@@ -162,33 +162,32 @@ RADIOSPEECH.Vocabulary.RU = {
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
|
||||
["степени"] = { "degrees", 0.5 },
|
||||
["километров"] = { "kilometers", 0.65 },
|
||||
["градусы"] = { "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 },
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
["возвращаясь на базу"] = { "returning_to_base", 1.40 },
|
||||
["возвращение на базу"] = { "returning_to_base", 1.40 },
|
||||
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
|
||||
["перехват самолетов"] = { "intercepting_bogeys", 1.22 },
|
||||
["перехват боги"] = { "intercepting_bogeys", 1.22 },
|
||||
["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
|
||||
["захватывающие самолеты"] = { "engaging_bogeys", 1.68 },
|
||||
["колеса вверх"] = { "wheels_up", 0.92 },
|
||||
["привлечение болотных птиц"] = { "engaging_bogeys", 1.68 },
|
||||
["колёса вверх..."] = { "wheels_up", 0.92 },
|
||||
["посадка на базу"] = { "landing at base", 1.04 },
|
||||
["патрулирующий"] = { "patrolling", 0.96 },
|
||||
["патрулирование"] = { "patrolling", 0.96 },
|
||||
|
||||
["за"] = { "for", 0.27 },
|
||||
["для"] = { "for", 0.27 },
|
||||
["и"] = { "and", 0.17 },
|
||||
["в"] = { "at", 0.19 },
|
||||
["dot"] = { "dot", 0.51 },
|
||||
["defender"] = { "defender", 0.45 },
|
||||
["на сайте"] = { "at", 0.19 },
|
||||
["точка"] = { "dot", 0.51 },
|
||||
["защитник"] = { "defender", 0.45 },
|
||||
}
|
||||
|
||||
--- Create a new RADIOSPEECH object for a given radio frequency/modulation.
|
||||
@@ -0,0 +1,701 @@
|
||||
--- **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 Google
|
||||
--
|
||||
-- Use Google's text-to-speech engine with the @{#MSRS.SetGoogle} function, e.g. ':SetGoogle()'.
|
||||
-- By enabling this it also allows you to utilize SSML in your text for added flexibilty.
|
||||
-- For more information on setting up a cloud account, visit: https://cloud.google.com/text-to-speech
|
||||
-- Google's supported SSML reference: https://cloud.google.com/text-to-speech/docs/ssml
|
||||
--
|
||||
-- ## 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.
|
||||
-- If enabling SetGoogle(), you can use voices provided by Google
|
||||
-- Google's supported voices: https://cloud.google.com/text-to-speech/docs/voices
|
||||
--
|
||||
-- ## 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(" --gender=%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(' --ssml -G "%s"', self.google)
|
||||
end
|
||||
|
||||
-- Debug output.
|
||||
self:T("MSRS command="..command)
|
||||
|
||||
return command
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -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
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -38,7 +38,7 @@ do -- TASK_A2A
|
||||
--
|
||||
-- @field #TASK_A2A
|
||||
TASK_A2A = {
|
||||
ClassName = "TASK_A2A",
|
||||
ClassName = "TASK_A2A"
|
||||
}
|
||||
|
||||
--- Instantiates a new TASK_A2A.
|
||||
@@ -60,29 +60,27 @@ do -- TASK_A2A
|
||||
|
||||
local Fsm = self:GetUnitProcess()
|
||||
|
||||
|
||||
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
|
||||
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
|
||||
Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" )
|
||||
|
||||
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
|
||||
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
|
||||
|
||||
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
|
||||
Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
|
||||
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
|
||||
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
|
||||
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
|
||||
Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" )
|
||||
|
||||
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
|
||||
-- Fsm:AddTransition( "Accounted", "Success", "Success" )
|
||||
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
|
||||
-- Fsm:AddTransition( "Accounted", "Success", "Success" )
|
||||
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
|
||||
Fsm:AddTransition( "Failed", "Fail", "Failed" )
|
||||
|
||||
|
||||
---- @param #FSM_PROCESS self
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param #TASK_CARGO Task
|
||||
function Fsm:OnLeaveAssigned( TaskUnit, Task )
|
||||
@@ -128,7 +126,7 @@ do -- TASK_A2A
|
||||
function Fsm:onafterEngage( TaskUnit, Task )
|
||||
self:F( { self } )
|
||||
self:__Account( 0.1 )
|
||||
self:__RouteToTarget(0.1 )
|
||||
self:__RouteToTarget( 0.1 )
|
||||
self:__RouteToTargets( -10 )
|
||||
end
|
||||
|
||||
@@ -177,8 +175,6 @@ do -- TASK_A2A
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
function TASK_A2A:GetPlannedMenuText()
|
||||
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
|
||||
@@ -188,7 +184,7 @@ do -- TASK_A2A
|
||||
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
|
||||
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
|
||||
function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
@@ -209,8 +205,6 @@ do -- TASK_A2A
|
||||
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -244,7 +238,6 @@ do -- TASK_A2A
|
||||
ActRouteTarget:SetCoordinate( TargetCoordinate )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map.
|
||||
@@ -256,7 +249,6 @@ do -- TASK_A2A
|
||||
return ActRouteTarget:GetCoordinate()
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -268,7 +260,6 @@ do -- TASK_A2A
|
||||
ActRouteTarget:SetZone( TargetZone, Altitude, Heading )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map.
|
||||
@@ -293,7 +284,7 @@ do -- TASK_A2A
|
||||
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats.
|
||||
-- @param #TASK_A2A self
|
||||
function TASK_A2A:ReportOrder( ReportGroup )
|
||||
self:UpdateTaskInfo( self.DetectedItem )
|
||||
self:UpdateTaskInfo( self.DetectedItem )
|
||||
|
||||
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
|
||||
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
|
||||
@@ -301,7 +292,6 @@ do -- TASK_A2A
|
||||
return Distance
|
||||
end
|
||||
|
||||
|
||||
--- This method checks every 10 seconds if the goal has been reached of the task.
|
||||
-- @param #TASK_A2A self
|
||||
function TASK_A2A:onafterGoal( TaskUnit, From, Event, To )
|
||||
@@ -314,7 +304,6 @@ do -- TASK_A2A
|
||||
self:__Goal( -10 )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
function TASK_A2A:UpdateTaskInfo( DetectedItem )
|
||||
|
||||
@@ -360,7 +349,7 @@ do -- TASK_A2A
|
||||
-- @param Wrapper.Group#GROUP TaskGroup The player group.
|
||||
function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
|
||||
|
||||
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
|
||||
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
|
||||
return math.random( 1, 9 )
|
||||
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
|
||||
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
|
||||
@@ -375,7 +364,6 @@ do -- TASK_A2A
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- TASK_A2A_INTERCEPT
|
||||
|
||||
--- The TASK_A2A_INTERCEPT class
|
||||
@@ -384,7 +372,7 @@ do -- TASK_A2A_INTERCEPT
|
||||
-- @extends Tasking.Task#TASK
|
||||
|
||||
--- Defines an intercept task for a human player to be executed.
|
||||
-- When enemy planes need to be intercepted by human players, use this task type to urgen the players to get out there!
|
||||
-- When enemy planes need to be intercepted by human players, use this task type to urge the players to get out there!
|
||||
--
|
||||
-- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks
|
||||
-- based on detected airborne enemy targets intruding friendly airspace.
|
||||
@@ -394,11 +382,9 @@ do -- TASK_A2A_INTERCEPT
|
||||
--
|
||||
-- @field #TASK_A2A_INTERCEPT
|
||||
TASK_A2A_INTERCEPT = {
|
||||
ClassName = "TASK_A2A_INTERCEPT",
|
||||
ClassName = "TASK_A2A_INTERCEPT"
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2A_INTERCEPT.
|
||||
-- @param #TASK_A2A_INTERCEPT self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -413,15 +399,12 @@ do -- TASK_A2A_INTERCEPT
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Intercept incoming intruders.\n"
|
||||
)
|
||||
self:SetBriefing( TaskBriefing or "Intercept incoming intruders.\n" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed .
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed.
|
||||
-- @param #TASK_A2A_INTERCEPT self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
-- @param #number Score The score in points to be granted when task process has been achieved.
|
||||
@@ -469,10 +452,8 @@ do -- TASK_A2A_INTERCEPT
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- TASK_A2A_SWEEP
|
||||
|
||||
--- The TASK_A2A_SWEEP class
|
||||
@@ -493,11 +474,9 @@ do -- TASK_A2A_SWEEP
|
||||
--
|
||||
-- @field #TASK_A2A_SWEEP
|
||||
TASK_A2A_SWEEP = {
|
||||
ClassName = "TASK_A2A_SWEEP",
|
||||
ClassName = "TASK_A2A_SWEEP"
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2A_SWEEP.
|
||||
-- @param #TASK_A2A_SWEEP self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -512,10 +491,7 @@ do -- TASK_A2A_SWEEP
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n"
|
||||
)
|
||||
self:SetBriefing( TaskBriefing or "Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" )
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -531,7 +507,7 @@ do -- TASK_A2A_SWEEP
|
||||
self:__Goal( -10 )
|
||||
end
|
||||
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed .
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed.
|
||||
-- @param #TASK_A2A_SWEEP self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
-- @param #number Score The score in points to be granted when task process has been achieved.
|
||||
@@ -581,7 +557,6 @@ do -- TASK_A2A_SWEEP
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- TASK_A2A_ENGAGE
|
||||
|
||||
--- The TASK_A2A_ENGAGE class
|
||||
@@ -600,11 +575,9 @@ do -- TASK_A2A_ENGAGE
|
||||
--
|
||||
-- @field #TASK_A2A_ENGAGE
|
||||
TASK_A2A_ENGAGE = {
|
||||
ClassName = "TASK_A2A_ENGAGE",
|
||||
ClassName = "TASK_A2A_ENGAGE"
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2A_ENGAGE.
|
||||
-- @param #TASK_A2A_ENGAGE self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -619,10 +592,7 @@ do -- TASK_A2A_ENGAGE
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n"
|
||||
)
|
||||
self:SetBriefing( TaskBriefing or "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -108,7 +108,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**.
|
||||
-- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set.
|
||||
-- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set.
|
||||
-- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is choosen, which is 6km.
|
||||
-- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is chosen, which is 6 km.
|
||||
-- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism.
|
||||
--
|
||||
-- ### 2. Define the detected **target grouping radius**:
|
||||
@@ -185,7 +185,6 @@ do -- TASK_A2A_DISPATCHER
|
||||
SweepZones = {},
|
||||
}
|
||||
|
||||
|
||||
--- TASK_A2A_DISPATCHER constructor.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
|
||||
@@ -208,7 +207,6 @@ do -- TASK_A2A_DISPATCHER
|
||||
|
||||
self:AddTransition( "Started", "Assign", "Started" )
|
||||
|
||||
|
||||
--- OnAfter Transition Handler for Event Assign.
|
||||
-- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
@@ -224,7 +222,6 @@ do -- TASK_A2A_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission.
|
||||
-- So, if there is a target area detected and reported,
|
||||
-- then any friendlies that are airborne near this target area,
|
||||
@@ -257,7 +254,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function TASK_A2A_DISPATCHER:SetSendMessages( onoff )
|
||||
self.FlashNewTask = onoff
|
||||
self.FlashNewTask = onoff
|
||||
end
|
||||
|
||||
--- Creates an INTERCEPT task when there are targets for it.
|
||||
@@ -286,7 +283,6 @@ do -- TASK_A2A_DISPATCHER
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Creates an SWEEP task when there are targets for it.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
|
||||
@@ -296,8 +292,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
self:F( { DetectedItem.ItemID } )
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
|
||||
local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove?
|
||||
|
||||
if DetectedItem.IsDetected == false then
|
||||
|
||||
@@ -312,7 +307,6 @@ do -- TASK_A2A_DISPATCHER
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Creates an ENGAGE task when there are human friendlies airborne near the targets.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
|
||||
@@ -322,11 +316,10 @@ do -- TASK_A2A_DISPATCHER
|
||||
self:F( { DetectedItem.ItemID } )
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove?
|
||||
|
||||
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
|
||||
|
||||
|
||||
-- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone.
|
||||
if PlayersCount > 0 and DetectedItem.IsDetected == true then
|
||||
|
||||
@@ -341,9 +334,6 @@ do -- TASK_A2A_DISPATCHER
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Evaluates the removal of the Task from the Mission.
|
||||
-- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned".
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
@@ -388,8 +378,8 @@ do -- TASK_A2A_DISPATCHER
|
||||
end
|
||||
|
||||
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
|
||||
--DetectedSet:Flush( self )
|
||||
--self:F( { DetectedSetCount = DetectedSet:Count() } )
|
||||
-- DetectedSet:Flush( self )
|
||||
-- self:F( { DetectedSetCount = DetectedSet:Count() } )
|
||||
if DetectedSet:Count() == 0 then
|
||||
Remove = true
|
||||
end
|
||||
@@ -423,7 +413,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel()
|
||||
FriendliesCount = FriendliesCount + 1
|
||||
local FriendlyType = FriendlyUnit:GetTypeName()
|
||||
FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1
|
||||
FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1
|
||||
if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then
|
||||
end
|
||||
end
|
||||
@@ -431,19 +421,18 @@ do -- TASK_A2A_DISPATCHER
|
||||
|
||||
end
|
||||
|
||||
--self:F( { FriendliesCount = FriendliesCount } )
|
||||
-- self:F( { FriendliesCount = FriendliesCount } )
|
||||
|
||||
local FriendlyTypesReport = REPORT:New()
|
||||
|
||||
if FriendliesCount > 0 then
|
||||
for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do
|
||||
FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) )
|
||||
FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) )
|
||||
end
|
||||
else
|
||||
FriendlyTypesReport:Add( "-" )
|
||||
end
|
||||
|
||||
|
||||
return FriendliesCount, FriendlyTypesReport
|
||||
end
|
||||
|
||||
@@ -464,7 +453,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do
|
||||
local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT
|
||||
local PlayerName = PlayerUnit:GetPlayerName()
|
||||
--self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
|
||||
-- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
|
||||
if PlayerUnit:IsAirPlane() and PlayerName ~= nil then
|
||||
local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel()
|
||||
PlayersCount = PlayersCount + 1
|
||||
@@ -481,13 +470,12 @@ do -- TASK_A2A_DISPATCHER
|
||||
|
||||
if PlayersCount > 0 then
|
||||
for PlayerName, PlayerType in pairs( PlayerTypes ) do
|
||||
PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) )
|
||||
PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) )
|
||||
end
|
||||
else
|
||||
PlayerTypesReport:Add( "-" )
|
||||
end
|
||||
|
||||
|
||||
return PlayersCount, PlayerTypesReport
|
||||
end
|
||||
|
||||
@@ -496,7 +484,6 @@ do -- TASK_A2A_DISPATCHER
|
||||
self.Tasks[TaskIndex] = nil
|
||||
end
|
||||
|
||||
|
||||
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
|
||||
@@ -536,8 +523,8 @@ do -- TASK_A2A_DISPATCHER
|
||||
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
|
||||
local DetectedCount = DetectedSet:Count()
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
--self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
|
||||
--DetectedSet:Flush( self )
|
||||
-- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
|
||||
-- DetectedSet:Flush( self )
|
||||
|
||||
local DetectedID = DetectedItem.ID
|
||||
local TaskIndex = DetectedItem.Index
|
||||
@@ -593,7 +580,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
|
||||
TaskReport:Add( Task:GetName() )
|
||||
else
|
||||
self:F("This should not happen")
|
||||
self:F( "This should not happen" )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -612,10 +599,10 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- TODO set menus using the HQ coordinator
|
||||
Mission:GetCommandCenter():SetMenu()
|
||||
|
||||
local TaskText = TaskReport:Text(", ")
|
||||
local TaskText = TaskReport:Text( ", " )
|
||||
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and (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
|
||||
|
||||
@@ -38,7 +38,7 @@ do -- TASK_A2G
|
||||
--
|
||||
-- @field #TASK_A2G
|
||||
TASK_A2G = {
|
||||
ClassName = "TASK_A2G",
|
||||
ClassName = "TASK_A2G"
|
||||
}
|
||||
|
||||
--- Instantiates a new TASK_A2G.
|
||||
@@ -61,27 +61,25 @@ do -- TASK_A2G
|
||||
local Fsm = self:GetUnitProcess()
|
||||
|
||||
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
|
||||
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
|
||||
Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" )
|
||||
|
||||
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
|
||||
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
|
||||
|
||||
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
|
||||
Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
|
||||
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
|
||||
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
|
||||
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
|
||||
Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" )
|
||||
|
||||
--Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
|
||||
--Fsm:AddTransition( "Accounted", "Success", "Success" )
|
||||
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
|
||||
-- Fsm:AddTransition( "Accounted", "Success", "Success" )
|
||||
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
|
||||
Fsm:AddTransition( "Failed", "Fail", "Failed" )
|
||||
|
||||
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -130,7 +128,7 @@ do -- TASK_A2G
|
||||
function Fsm:onafterEngage( TaskUnit, Task )
|
||||
self:F( { self } )
|
||||
self:__Account( 0.1 )
|
||||
self:__RouteToTarget(0.1 )
|
||||
self:__RouteToTarget( 0.1 )
|
||||
self:__RouteToTargets( -10 )
|
||||
end
|
||||
|
||||
@@ -179,8 +177,6 @@ do -- TASK_A2G
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
function TASK_A2G:GetPlannedMenuText()
|
||||
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
|
||||
@@ -190,7 +186,7 @@ do -- TASK_A2G
|
||||
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
|
||||
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
|
||||
function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
@@ -211,8 +207,6 @@ do -- TASK_A2G
|
||||
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -246,7 +240,6 @@ do -- TASK_A2G
|
||||
ActRouteTarget:SetCoordinate( TargetCoordinate )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map.
|
||||
@@ -258,7 +251,6 @@ do -- TASK_A2G
|
||||
return ActRouteTarget:GetCoordinate()
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -270,7 +262,6 @@ do -- TASK_A2G
|
||||
ActRouteTarget:SetZone( TargetZone )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map.
|
||||
@@ -295,7 +286,7 @@ do -- TASK_A2G
|
||||
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats.
|
||||
-- @param #TASK_A2G self
|
||||
function TASK_A2G:ReportOrder( ReportGroup )
|
||||
self:UpdateTaskInfo( self.DetectedItem )
|
||||
self:UpdateTaskInfo( self.DetectedItem )
|
||||
|
||||
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
|
||||
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
|
||||
@@ -303,7 +294,6 @@ do -- TASK_A2G
|
||||
return Distance
|
||||
end
|
||||
|
||||
|
||||
--- This method checks every 10 seconds if the goal has been reached of the task.
|
||||
-- @param #TASK_A2G self
|
||||
function TASK_A2G:onafterGoal( TaskUnit, From, Event, To )
|
||||
@@ -365,12 +355,12 @@ do -- TASK_A2G
|
||||
-- @param Wrapper.Group#GROUP TaskGroup The player group.
|
||||
function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
|
||||
|
||||
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
|
||||
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
|
||||
return math.random( 1, 9 )
|
||||
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
|
||||
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
|
||||
local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() )
|
||||
self:F({Distance=Distance})
|
||||
self:F( { Distance = Distance } )
|
||||
return math.floor( Distance )
|
||||
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then
|
||||
return 1
|
||||
@@ -381,7 +371,6 @@ do -- TASK_A2G
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- TASK_A2G_SEAD
|
||||
|
||||
--- The TASK_A2G_SEAD class
|
||||
@@ -397,7 +386,7 @@ do -- TASK_A2G_SEAD
|
||||
--
|
||||
-- @field #TASK_A2G_SEAD
|
||||
TASK_A2G_SEAD = {
|
||||
ClassName = "TASK_A2G_SEAD",
|
||||
ClassName = "TASK_A2G_SEAD"
|
||||
}
|
||||
|
||||
--- Instantiates a new TASK_A2G_SEAD.
|
||||
@@ -408,16 +397,13 @@ do -- TASK_A2G_SEAD
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param #string TaskBriefing The briefing of the task.
|
||||
-- @return #TASK_A2G_SEAD self
|
||||
function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)
|
||||
function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD
|
||||
self:F()
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Execute a Suppression of Enemy Air Defenses."
|
||||
)
|
||||
self:SetBriefing( TaskBriefing or "Execute a Suppression of Enemy Air Defenses." )
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -470,7 +456,6 @@ do -- TASK_A2G_SEAD
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
do -- TASK_A2G_BAI
|
||||
@@ -488,9 +473,7 @@ do -- TASK_A2G_BAI
|
||||
-- based on detected enemy ground targets.
|
||||
--
|
||||
-- @field #TASK_A2G_BAI
|
||||
TASK_A2G_BAI = {
|
||||
ClassName = "TASK_A2G_BAI",
|
||||
}
|
||||
TASK_A2G_BAI = { ClassName = "TASK_A2G_BAI" }
|
||||
|
||||
--- Instantiates a new TASK_A2G_BAI.
|
||||
-- @param #TASK_A2G_BAI self
|
||||
@@ -506,10 +489,7 @@ do -- TASK_A2G_BAI
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Execute a Battlefield Air Interdiction of a group of enemy targets."
|
||||
)
|
||||
self:SetBriefing( TaskBriefing or "Execute a Battlefield Air Interdiction of a group of enemy targets." )
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -564,9 +544,6 @@ do -- TASK_A2G_BAI
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
do -- TASK_A2G_CAS
|
||||
|
||||
--- The TASK_A2G_CAS class
|
||||
@@ -581,9 +558,7 @@ do -- TASK_A2G_CAS
|
||||
-- based on detected enemy ground targets.
|
||||
--
|
||||
-- @field #TASK_A2G_CAS
|
||||
TASK_A2G_CAS = {
|
||||
ClassName = "TASK_A2G_CAS",
|
||||
}
|
||||
TASK_A2G_CAS = { ClassName = "TASK_A2G_CAS" }
|
||||
|
||||
--- Instantiates a new TASK_A2G_CAS.
|
||||
-- @param #TASK_A2G_CAS self
|
||||
@@ -599,17 +574,11 @@ do -- TASK_A2G_CAS
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Execute a Close Air Support for a group of enemy targets. " ..
|
||||
"Beware of friendlies at the vicinity! "
|
||||
)
|
||||
|
||||
self:SetBriefing( TaskBriefing or ( "Execute a Close Air Support for a group of enemy targets. " .. "Beware of friendlies at the vicinity! " ) )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set a score when a target in scope of the A2G attack, has been destroyed .
|
||||
-- @param #TASK_A2G_CAS self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
@@ -658,5 +627,4 @@ do -- TASK_A2G_CAS
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
-- * Dynamically change the tasks as the tactical situation evolves during the mission.
|
||||
-- * Dynamically assign (CAS) Close Air Support tasks for human players.
|
||||
-- * Dynamically assign (BAI) Battlefield Air Interdiction tasks for human players.
|
||||
-- * Dynamically assign (SEAD) Supression of Enemy Air Defense tasks for human players to eliminate G2A missile threats.
|
||||
-- * Dynamically assign (SEAD) Suppression of Enemy Air Defense tasks for human players to eliminate G2A missile threats.
|
||||
-- * Define and use an EWR (Early Warning Radar) network.
|
||||
-- * Define different ranges to engage upon intruders.
|
||||
-- * Keep task achievements.
|
||||
-- * Score task achievements.--
|
||||
-- * Score task achievements.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
@@ -122,7 +123,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- F1. Command Center [Lima]
|
||||
-- F1. Mission "Overlord (High)"
|
||||
--
|
||||
-- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tacical mission Gamma.
|
||||
-- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tactical mission Gamma.
|
||||
-- Command Center [Lima] is controlling Missions "Overlord", which needs to be executed with High priority.
|
||||
--
|
||||
-- ## 1.1. Mission Menu (Under the Command Center Menu)
|
||||
@@ -196,7 +197,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
--
|
||||
-- The Mission Reports Menu is a sub menu, that provides options to retrieve further information on the current Mission:
|
||||
--
|
||||
-- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a %-tage of completion.
|
||||
-- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a % of completion.
|
||||
-- - **Report Players per Task**: Show which players are engaged on which Task within the Mission.
|
||||
--
|
||||
-- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this:
|
||||
@@ -264,7 +265,6 @@ do -- TASK_A2G_DISPATCHER
|
||||
--
|
||||
-- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.**
|
||||
--
|
||||
--
|
||||
-- ## 1.3. Join Planned Task Menu
|
||||
--
|
||||
-- The Join Planned Task Menu contains the different Planned A2G Tasks **in a structured Menu Hierarchy**.
|
||||
@@ -388,7 +388,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- - A @{Mission} object. Each task belongs to a Mission.
|
||||
-- - A @{Detection} object. There are several detection grouping methods to choose from.
|
||||
-- - A @{Task_A2G_Dispatcher} object. The master A2G task dispatcher.
|
||||
-- - A @{Set} of @{Wrapper.Group} objects that will detect the emeny, the RecceSet. This is attached to the @{Detection} object.
|
||||
-- - A @{Set} of @{Wrapper.Group} objects that will detect the enemy, the RecceSet. This is attached to the @{Detection} object.
|
||||
-- - A @{Set} ob @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Task_A2G_Dispatcher} object.
|
||||
--
|
||||
-- Below an example mission declaration that is defines a Task A2G Dispatcher object.
|
||||
@@ -425,7 +425,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
--
|
||||
-- -- Now we have everything to setup the main A2G TaskDispatcher.
|
||||
-- TaskDispatcher = TASK_A2G_DISPATCHER
|
||||
-- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will recieve the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher.
|
||||
-- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will receive the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher.
|
||||
--
|
||||
--
|
||||
--
|
||||
@@ -434,10 +434,9 @@ do -- TASK_A2G_DISPATCHER
|
||||
ClassName = "TASK_A2G_DISPATCHER",
|
||||
Mission = nil,
|
||||
Detection = nil,
|
||||
Tasks = {},
|
||||
Tasks = {}
|
||||
}
|
||||
|
||||
|
||||
--- TASK_A2G_DISPATCHER constructor.
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
|
||||
@@ -451,7 +450,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
|
||||
self.Detection = Detection
|
||||
self.Mission = Mission
|
||||
self.FlashNewTask = true --set to false to suppress flash messages
|
||||
self.FlashNewTask = true -- set to false to suppress flash messages
|
||||
|
||||
self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } )
|
||||
|
||||
@@ -472,11 +471,11 @@ do -- TASK_A2G_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
--- 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
|
||||
self.FlashNewTask = onoff
|
||||
end
|
||||
|
||||
--- Creates a SEAD task when there are targets for it.
|
||||
@@ -518,7 +517,6 @@ do -- TASK_A2G_DISPATCHER
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
|
||||
|
||||
-- Determine if the set has ground units.
|
||||
-- There should be ground unit friendlies nearby. Airborne units are valid friendlies types.
|
||||
-- And there shouldn't be any radar.
|
||||
@@ -550,7 +548,6 @@ do -- TASK_A2G_DISPATCHER
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
|
||||
|
||||
-- Determine if the set has ground units.
|
||||
-- There shouldn't be any ground unit friendlies nearby.
|
||||
-- And there shouldn't be any radar.
|
||||
@@ -571,7 +568,6 @@ do -- TASK_A2G_DISPATCHER
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex )
|
||||
self.Mission:RemoveTask( self.Tasks[TaskIndex] )
|
||||
self.Tasks[TaskIndex] = nil
|
||||
@@ -588,8 +584,8 @@ do -- TASK_A2G_DISPATCHER
|
||||
function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged )
|
||||
|
||||
if Task then
|
||||
if ( Task:IsStatePlanned() and DetectedItemChanged == true ) or Task:IsStateCancelled() then
|
||||
--self:F( "Removing Tasking: " .. Task:GetTaskName() )
|
||||
if (Task:IsStatePlanned() and DetectedItemChanged == true) or Task:IsStateCancelled() then
|
||||
-- self:F( "Removing Tasking: " .. Task:GetTaskName() )
|
||||
self:RemoveTask( TaskIndex )
|
||||
end
|
||||
end
|
||||
@@ -597,7 +593,6 @@ do -- TASK_A2G_DISPATCHER
|
||||
return Task
|
||||
end
|
||||
|
||||
|
||||
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
|
||||
@@ -638,8 +633,8 @@ do -- TASK_A2G_DISPATCHER
|
||||
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
|
||||
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
--self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
|
||||
--DetectedSet:Flush( self )
|
||||
-- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
|
||||
-- DetectedSet:Flush( self )
|
||||
|
||||
local DetectedItemID = DetectedItem.ID
|
||||
local TaskIndex = DetectedItem.Index
|
||||
@@ -660,7 +655,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
Task:SetTargetSetUnit( TargetSetUnit )
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
Task:UpdateTaskInfo( DetectedItem )
|
||||
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
|
||||
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
|
||||
else
|
||||
Task:Cancel()
|
||||
end
|
||||
@@ -694,8 +689,8 @@ 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 ~= "" and self.FlashNewTask then
|
||||
local TargetsText = TargetsReport:Text( ", " )
|
||||
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
|
||||
@@ -753,7 +748,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
|
||||
DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
|
||||
@@ -762,7 +757,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
|
||||
DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
|
||||
@@ -771,7 +766,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
|
||||
DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
end
|
||||
@@ -800,14 +795,12 @@ do -- TASK_A2G_DISPATCHER
|
||||
self:Aborted( Task )
|
||||
end
|
||||
|
||||
|
||||
TaskReport:Add( Task:GetName() )
|
||||
else
|
||||
self:F("This should not happen")
|
||||
self:F( "This should not happen" )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- OK, so the tasking has been done, now delete the changes reported for the area.
|
||||
Detection:AcceptChanges( DetectedItem )
|
||||
end
|
||||
@@ -815,9 +808,9 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- TODO set menus using the HQ coordinator
|
||||
Mission:GetCommandCenter():SetMenu()
|
||||
|
||||
local TaskText = TaskReport:Text(", ")
|
||||
local TaskText = TaskReport:Text( ", " )
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and 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
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
--
|
||||
-- DCS itself provides a lot of enumerators for various things. See [Enumerators](https://wiki.hoggitworld.com/view/Category:Enumerators) on Hoggit.
|
||||
--
|
||||
-- Other Moose classe also have enumerators. For example, the AIRBASE class has enumerators for airbase names.
|
||||
-- Other Moose classes also have enumerators. For example, the AIRBASE class has enumerators for airbase names.
|
||||
--
|
||||
-- @module ENUMS
|
||||
-- @image MOOSE.JPG
|
||||
@@ -30,16 +30,16 @@ ENUMS = {}
|
||||
-- @type ENUMS.ROE
|
||||
-- @field #number WeaponFree AI will engage any enemy group it detects. Target prioritization is based based on the threat of the target.
|
||||
-- @field #number OpenFireWeaponFree AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking.
|
||||
-- @field #number OpenFire AI will engage only targets specified in its taskings.
|
||||
-- @field #number OpenFire AI will engage only targets specified in its tasking.
|
||||
-- @field #number ReturnFire AI will only engage threats that shoot first.
|
||||
-- @field #number WeaponHold AI will hold fire under all circumstances.
|
||||
ENUMS.ROE = {
|
||||
WeaponFree=0,
|
||||
OpenFireWeaponFree=1,
|
||||
OpenFire=2,
|
||||
ReturnFire=3,
|
||||
WeaponHold=4,
|
||||
}
|
||||
WeaponFree = 0,
|
||||
OpenFireWeaponFree = 1,
|
||||
OpenFire = 2,
|
||||
ReturnFire = 3,
|
||||
WeaponHold = 4,
|
||||
}
|
||||
|
||||
--- Reaction On Threat.
|
||||
-- @type ENUMS.ROT
|
||||
@@ -49,11 +49,11 @@ ENUMS.ROE = {
|
||||
-- @field #number BypassAndEscape AI will attempt to avoid enemy threat zones all together. This includes attempting to fly above or around threats.
|
||||
-- @field #number AllowAbortMission If a threat is deemed severe enough the AI will abort its mission and return to base.
|
||||
ENUMS.ROT = {
|
||||
NoReaction=0,
|
||||
PassiveDefense=1,
|
||||
EvadeFire=2,
|
||||
BypassAndEscape=3,
|
||||
AllowAbortMission=4,
|
||||
NoReaction = 0,
|
||||
PassiveDefense = 1,
|
||||
EvadeFire = 2,
|
||||
BypassAndEscape = 3,
|
||||
AllowAbortMission = 4,
|
||||
}
|
||||
|
||||
--- Alarm state.
|
||||
@@ -62,12 +62,12 @@ ENUMS.ROT = {
|
||||
-- @field #number Green Group is not combat ready. Sensors are stowed if possible.
|
||||
-- @field #number Red Group is combat ready and actively searching for targets. Some groups like infantry will not move in this state.
|
||||
ENUMS.AlarmState = {
|
||||
Auto=0,
|
||||
Green=1,
|
||||
Red=2,
|
||||
Auto = 0,
|
||||
Green = 1,
|
||||
Red = 2,
|
||||
}
|
||||
|
||||
--- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerotor on hoggit wiki.
|
||||
--- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on Hoggit wiki.
|
||||
-- @type ENUMS.WeaponFlag
|
||||
ENUMS.WeaponFlag={
|
||||
-- Bombs
|
||||
@@ -111,7 +111,7 @@ ENUMS.WeaponFlag={
|
||||
--
|
||||
-- Bombs
|
||||
GuidedBomb = 14, -- (LGB + TvGB + SNSGB)
|
||||
AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb)
|
||||
AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispenser + CandleBomb + ParachuteBomb)
|
||||
AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb)
|
||||
--- Rockets
|
||||
AnyRocket = 30720, -- LightRocket + MarkerRocket + CandleRocket + HeavyRocket
|
||||
@@ -173,7 +173,7 @@ ENUMS.MissionTask={
|
||||
TRANSPORT="Transport",
|
||||
}
|
||||
|
||||
--- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki.
|
||||
--- Formations (new). See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on Hoggit wiki.
|
||||
-- @type ENUMS.Formation
|
||||
ENUMS.Formation={}
|
||||
ENUMS.Formation.FixedWing={}
|
||||
@@ -216,23 +216,23 @@ ENUMS.Formation.FixedWing.FighterVic.Close = 917505
|
||||
ENUMS.Formation.FixedWing.FighterVic.Open = 917506
|
||||
ENUMS.Formation.RotaryWing={}
|
||||
ENUMS.Formation.RotaryWing.Column={}
|
||||
ENUMS.Formation.RotaryWing.Column.D70=720896
|
||||
ENUMS.Formation.RotaryWing.Column.D70 = 720896
|
||||
ENUMS.Formation.RotaryWing.Wedge={}
|
||||
ENUMS.Formation.RotaryWing.Wedge.D70=8
|
||||
ENUMS.Formation.RotaryWing.Wedge.D70 = 8
|
||||
ENUMS.Formation.RotaryWing.FrontRight={}
|
||||
ENUMS.Formation.RotaryWing.FrontRight.D300=655361
|
||||
ENUMS.Formation.RotaryWing.FrontRight.D600=655362
|
||||
ENUMS.Formation.RotaryWing.FrontRight.D300 = 655361
|
||||
ENUMS.Formation.RotaryWing.FrontRight.D600 = 655362
|
||||
ENUMS.Formation.RotaryWing.FrontLeft={}
|
||||
ENUMS.Formation.RotaryWing.FrontLeft.D300=655617
|
||||
ENUMS.Formation.RotaryWing.FrontLeft.D600=655618
|
||||
ENUMS.Formation.RotaryWing.FrontLeft.D300 = 655617
|
||||
ENUMS.Formation.RotaryWing.FrontLeft.D600 = 655618
|
||||
ENUMS.Formation.RotaryWing.EchelonRight={}
|
||||
ENUMS.Formation.RotaryWing.EchelonRight.D70 =589825
|
||||
ENUMS.Formation.RotaryWing.EchelonRight.D300=589826
|
||||
ENUMS.Formation.RotaryWing.EchelonRight.D600=589827
|
||||
ENUMS.Formation.RotaryWing.EchelonRight.D70 = 589825
|
||||
ENUMS.Formation.RotaryWing.EchelonRight.D300 = 589826
|
||||
ENUMS.Formation.RotaryWing.EchelonRight.D600 = 589827
|
||||
ENUMS.Formation.RotaryWing.EchelonLeft={}
|
||||
ENUMS.Formation.RotaryWing.EchelonLeft.D70 =590081
|
||||
ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082
|
||||
ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083
|
||||
ENUMS.Formation.RotaryWing.EchelonLeft.D70 = 590081
|
||||
ENUMS.Formation.RotaryWing.EchelonLeft.D300 = 590082
|
||||
ENUMS.Formation.RotaryWing.EchelonLeft.D600 = 590083
|
||||
ENUMS.Formation.Vehicle={}
|
||||
ENUMS.Formation.Vehicle.Vee="Vee"
|
||||
ENUMS.Formation.Vehicle.EchelonRight="EchelonR"
|
||||
@@ -244,25 +244,25 @@ ENUMS.Formation.Vehicle.Cone="Cone"
|
||||
ENUMS.Formation.Vehicle.Diamond="Diamond"
|
||||
|
||||
--- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings.
|
||||
-- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki.
|
||||
-- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on Hoggit wiki.
|
||||
-- @type ENUMS.FormationOld
|
||||
ENUMS.FormationOld={}
|
||||
ENUMS.FormationOld.FixedWing={}
|
||||
ENUMS.FormationOld.FixedWing.LineAbreast=1
|
||||
ENUMS.FormationOld.FixedWing.Trail=2
|
||||
ENUMS.FormationOld.FixedWing.Wedge=3
|
||||
ENUMS.FormationOld.FixedWing.EchelonRight=4
|
||||
ENUMS.FormationOld.FixedWing.EchelonLeft=5
|
||||
ENUMS.FormationOld.FixedWing.FingerFour=6
|
||||
ENUMS.FormationOld.FixedWing.SpreadFour=7
|
||||
ENUMS.FormationOld.FixedWing.BomberElement=12
|
||||
ENUMS.FormationOld.FixedWing.BomberElementHeight=13
|
||||
ENUMS.FormationOld.FixedWing.FighterVic=14
|
||||
ENUMS.FormationOld.FixedWing.LineAbreast = 1
|
||||
ENUMS.FormationOld.FixedWing.Trail = 2
|
||||
ENUMS.FormationOld.FixedWing.Wedge = 3
|
||||
ENUMS.FormationOld.FixedWing.EchelonRight = 4
|
||||
ENUMS.FormationOld.FixedWing.EchelonLeft = 5
|
||||
ENUMS.FormationOld.FixedWing.FingerFour = 6
|
||||
ENUMS.FormationOld.FixedWing.SpreadFour = 7
|
||||
ENUMS.FormationOld.FixedWing.BomberElement = 12
|
||||
ENUMS.FormationOld.FixedWing.BomberElementHeight = 13
|
||||
ENUMS.FormationOld.FixedWing.FighterVic = 14
|
||||
ENUMS.FormationOld.RotaryWing={}
|
||||
ENUMS.FormationOld.RotaryWing.Wedge=8
|
||||
ENUMS.FormationOld.RotaryWing.Echelon=9
|
||||
ENUMS.FormationOld.RotaryWing.Front=10
|
||||
ENUMS.FormationOld.RotaryWing.Column=11
|
||||
ENUMS.FormationOld.RotaryWing.Wedge = 8
|
||||
ENUMS.FormationOld.RotaryWing.Echelon = 9
|
||||
ENUMS.FormationOld.RotaryWing.Front = 10
|
||||
ENUMS.FormationOld.RotaryWing.Column = 11
|
||||
|
||||
|
||||
--- Morse Code. See the [Wikipedia](https://en.wikipedia.org/wiki/Morse_code).
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
-- @module Utilities.PROFILER
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
--- PROFILER class.
|
||||
-- @type PROFILER
|
||||
-- @field #string ClassName Name of the class.
|
||||
@@ -25,7 +24,6 @@
|
||||
-- @field #number ThreshTtot Total time threshold. Only write output if total function CPU time is more than this value.
|
||||
-- @field #string fileNamePrefix Output file name prefix, e.g. "MooseProfiler".
|
||||
-- @field #string fileNameSuffix Output file name prefix, e.g. "txt"
|
||||
|
||||
--- *The emperor counsels simplicity. First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature? *
|
||||
--
|
||||
-- ===
|
||||
@@ -35,12 +33,11 @@
|
||||
-- # The PROFILER Concept
|
||||
--
|
||||
-- Profile your lua code. This tells you, which functions are called very often and which consume most real time.
|
||||
-- With this information you can optimize the perfomance of your code.
|
||||
-- With this information you can optimize the performance of your code.
|
||||
--
|
||||
-- # Prerequisites
|
||||
--
|
||||
-- The modules **os** and **lfs** need to be desanizied.
|
||||
--
|
||||
-- The modules **os** and **lfs** need to be de-sanitized.
|
||||
--
|
||||
-- # Start
|
||||
--
|
||||
@@ -117,61 +114,60 @@ PROFILER = {
|
||||
--- Start profiler.
|
||||
-- @param #number Delay Delay in seconds before profiler is stated. Default is immediately.
|
||||
-- @param #number Duration Duration in (game) seconds before the profiler is stopped. Default is when mission ends.
|
||||
function PROFILER.Start(Delay, Duration)
|
||||
function PROFILER.Start( Delay, Duration )
|
||||
|
||||
-- Check if os, io and lfs are available.
|
||||
local go=true
|
||||
local go = true
|
||||
if not os then
|
||||
env.error("ERROR: Profiler needs os to be desanitized!")
|
||||
go=false
|
||||
env.error( "ERROR: Profiler needs os to be de-sanitized!" )
|
||||
go = false
|
||||
end
|
||||
if not io then
|
||||
env.error("ERROR: Profiler needs io to be desanitized!")
|
||||
go=false
|
||||
env.error( "ERROR: Profiler needs io to be de-sanitized!" )
|
||||
go = false
|
||||
end
|
||||
if not lfs then
|
||||
env.error("ERROR: Profiler needs lfs to be desanitized!")
|
||||
go=false
|
||||
env.error( "ERROR: Profiler needs lfs to be de-sanitized!" )
|
||||
go = false
|
||||
end
|
||||
if not go then
|
||||
return
|
||||
end
|
||||
|
||||
if Delay and Delay>0 then
|
||||
BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration)
|
||||
if Delay and Delay > 0 then
|
||||
BASE:ScheduleOnce( Delay, PROFILER.Start, 0, Duration )
|
||||
else
|
||||
|
||||
-- Set start time.
|
||||
PROFILER.TstartGame=timer.getTime()
|
||||
PROFILER.TstartOS=os.clock()
|
||||
PROFILER.TstartGame = timer.getTime()
|
||||
PROFILER.TstartOS = os.clock()
|
||||
|
||||
-- Add event handler.
|
||||
world.addEventHandler(PROFILER.eventHandler)
|
||||
world.addEventHandler( PROFILER.eventHandler )
|
||||
|
||||
-- Info in log.
|
||||
env.info('############################ Profiler Started ############################')
|
||||
env.info( '############################ Profiler Started ############################' )
|
||||
if Duration then
|
||||
env.info(string.format("- Will be running for %d seconds", Duration))
|
||||
env.info( string.format( "- Will be running for %d seconds", Duration ) )
|
||||
else
|
||||
env.info(string.format("- Will be stopped when mission ends"))
|
||||
env.info( string.format( "- Will be stopped when mission ends" ) )
|
||||
end
|
||||
env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS))
|
||||
env.info(string.format("- Total function time threshold %.3f sec", PROFILER.ThreshTtot))
|
||||
env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename(PROFILER.fileNameSuffix)))
|
||||
env.info(string.format("- Output file \"%s\" in CSV format", PROFILER.getfilename("csv")))
|
||||
env.info('###############################################################################')
|
||||
|
||||
env.info( string.format( "- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS ) )
|
||||
env.info( string.format( "- Total function time threshold %.3f sec", PROFILER.ThreshTtot ) )
|
||||
env.info( string.format( "- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename( PROFILER.fileNameSuffix ) ) )
|
||||
env.info( string.format( "- Output file \"%s\" in CSV format", PROFILER.getfilename( "csv" ) ) )
|
||||
env.info( '###############################################################################' )
|
||||
|
||||
-- Message on screen
|
||||
local duration=Duration or 600
|
||||
trigger.action.outText("### Profiler running ###", duration)
|
||||
local duration = Duration or 600
|
||||
trigger.action.outText( "### Profiler running ###", duration )
|
||||
|
||||
-- Set hook.
|
||||
debug.sethook(PROFILER.hook, "cr")
|
||||
debug.sethook( PROFILER.hook, "cr" )
|
||||
|
||||
-- Auto stop profiler.
|
||||
if Duration then
|
||||
PROFILER.Stop(Duration)
|
||||
PROFILER.Stop( Duration )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -180,34 +176,33 @@ end
|
||||
|
||||
--- Stop profiler.
|
||||
-- @param #number Delay Delay before stop in seconds.
|
||||
function PROFILER.Stop(Delay)
|
||||
function PROFILER.Stop( Delay )
|
||||
|
||||
if Delay and Delay>0 then
|
||||
if Delay and Delay > 0 then
|
||||
|
||||
BASE:ScheduleOnce(Delay, PROFILER.Stop)
|
||||
BASE:ScheduleOnce( Delay, PROFILER.Stop )
|
||||
|
||||
else
|
||||
|
||||
-- Remove hook.
|
||||
debug.sethook()
|
||||
|
||||
|
||||
-- Run time game.
|
||||
local runTimeGame=timer.getTime()-PROFILER.TstartGame
|
||||
local runTimeGame = timer.getTime() - PROFILER.TstartGame
|
||||
|
||||
-- Run time real OS.
|
||||
local runTimeOS=os.clock()-PROFILER.TstartOS
|
||||
local runTimeOS = os.clock() - PROFILER.TstartOS
|
||||
|
||||
-- Show info.
|
||||
PROFILER.showInfo(runTimeGame, runTimeOS)
|
||||
PROFILER.showInfo( runTimeGame, runTimeOS )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Event handler.
|
||||
function PROFILER.eventHandler:onEvent(event)
|
||||
if event.id==world.event.S_EVENT_MISSION_END then
|
||||
function PROFILER.eventHandler:onEvent( event )
|
||||
if event.id == world.event.S_EVENT_MISSION_END then
|
||||
PROFILER.Stop()
|
||||
end
|
||||
end
|
||||
@@ -218,34 +213,34 @@ end
|
||||
|
||||
--- Debug hook.
|
||||
-- @param #table event Event.
|
||||
function PROFILER.hook(event)
|
||||
function PROFILER.hook( event )
|
||||
|
||||
local f=debug.getinfo(2, "f").func
|
||||
local f = debug.getinfo( 2, "f" ).func
|
||||
|
||||
if event=='call' then
|
||||
if event == 'call' then
|
||||
|
||||
if PROFILER.Counters[f]==nil then
|
||||
if PROFILER.Counters[f] == nil then
|
||||
|
||||
PROFILER.Counters[f]=1
|
||||
PROFILER.dInfo[f]=debug.getinfo(2,"Sn")
|
||||
PROFILER.Counters[f] = 1
|
||||
PROFILER.dInfo[f] = debug.getinfo( 2, "Sn" )
|
||||
|
||||
if PROFILER.fTimeTotal[f]==nil then
|
||||
PROFILER.fTimeTotal[f]=0
|
||||
if PROFILER.fTimeTotal[f] == nil then
|
||||
PROFILER.fTimeTotal[f] = 0
|
||||
end
|
||||
|
||||
else
|
||||
PROFILER.Counters[f]=PROFILER.Counters[f]+1
|
||||
PROFILER.Counters[f] = PROFILER.Counters[f] + 1
|
||||
end
|
||||
|
||||
if PROFILER.fTime[f]==nil then
|
||||
PROFILER.fTime[f]=os.clock()
|
||||
if PROFILER.fTime[f] == nil then
|
||||
PROFILER.fTime[f] = os.clock()
|
||||
end
|
||||
|
||||
elseif (event=='return') then
|
||||
elseif (event == 'return') then
|
||||
|
||||
if PROFILER.fTime[f]~=nil then
|
||||
PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f])
|
||||
PROFILER.fTime[f]=nil
|
||||
if PROFILER.fTime[f] ~= nil then
|
||||
PROFILER.fTimeTotal[f] = PROFILER.fTimeTotal[f] + (os.clock() - PROFILER.fTime[f])
|
||||
PROFILER.fTime[f] = nil
|
||||
end
|
||||
|
||||
end
|
||||
@@ -262,11 +257,11 @@ end
|
||||
-- @return #string Source file name.
|
||||
-- @return #string Line number.
|
||||
-- @return #number Function time in seconds.
|
||||
function PROFILER.getData(func)
|
||||
function PROFILER.getData( func )
|
||||
|
||||
local n=PROFILER.dInfo[func]
|
||||
local n = PROFILER.dInfo[func]
|
||||
|
||||
if n.what=="C" then
|
||||
if n.what == "C" then
|
||||
return n.name, "?", "?", PROFILER.fTimeTotal[func]
|
||||
end
|
||||
|
||||
@@ -276,31 +271,31 @@ end
|
||||
--- Write text to log file.
|
||||
-- @param #function f The file.
|
||||
-- @param #string txt The text.
|
||||
function PROFILER._flog(f, txt)
|
||||
f:write(txt.."\r\n")
|
||||
function PROFILER._flog( f, txt )
|
||||
f:write( txt .. "\r\n" )
|
||||
end
|
||||
|
||||
--- Show table.
|
||||
-- @param #table data Data table.
|
||||
-- @param #function f The file.
|
||||
-- @param #number runTimeGame Game run time in seconds.
|
||||
function PROFILER.showTable(data, f, runTimeGame)
|
||||
function PROFILER.showTable( data, f, runTimeGame )
|
||||
|
||||
-- Loop over data.
|
||||
for i=1, #data do
|
||||
local t=data[i] --#PROFILER.Data
|
||||
for i = 1, #data do
|
||||
local t = data[i] -- #PROFILER.Data
|
||||
|
||||
-- Calls per second.
|
||||
local cps=t.count/runTimeGame
|
||||
local cps = t.count / runTimeGame
|
||||
|
||||
local threshCPS=cps>=PROFILER.ThreshCPS
|
||||
local threshTot=t.tm>=PROFILER.ThreshTtot
|
||||
local threshCPS = cps >= PROFILER.ThreshCPS
|
||||
local threshTot = t.tm >= PROFILER.ThreshTtot
|
||||
|
||||
if threshCPS and threshTot then
|
||||
|
||||
-- Output
|
||||
local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line))
|
||||
PROFILER._flog(f, text)
|
||||
local text = string.format( "%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm / runTimeGame * 100, t.tm / t.count, tostring( t.src ), tostring( t.line ) )
|
||||
PROFILER._flog( f, text )
|
||||
|
||||
end
|
||||
end
|
||||
@@ -310,26 +305,26 @@ end
|
||||
--- Print csv file.
|
||||
-- @param #table data Data table.
|
||||
-- @param #number runTimeGame Game run time in seconds.
|
||||
function PROFILER.printCSV(data, runTimeGame)
|
||||
function PROFILER.printCSV( data, runTimeGame )
|
||||
|
||||
-- Output file.
|
||||
local file=PROFILER.getfilename("csv")
|
||||
local g=io.open(file, 'w')
|
||||
local file = PROFILER.getfilename( "csv" )
|
||||
local g = io.open( file, 'w' )
|
||||
|
||||
-- Header.
|
||||
local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number,"
|
||||
g:write(text.."\r\n")
|
||||
local text = "Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number,"
|
||||
g:write( text .. "\r\n" )
|
||||
|
||||
-- Loop over data.
|
||||
for i=1, #data do
|
||||
local t=data[i] --#PROFILER.Data
|
||||
for i = 1, #data do
|
||||
local t = data[i] -- #PROFILER.Data
|
||||
|
||||
-- Calls per second.
|
||||
local cps=t.count/runTimeGame
|
||||
local cps = t.count / runTimeGame
|
||||
|
||||
-- Output
|
||||
local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line))
|
||||
g:write(txt.."\r\n")
|
||||
local txt = string.format( "%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm / runTimeGame * 100, t.tm / t.count, tostring( t.src ), tostring( t.line ) )
|
||||
g:write( txt .. "\r\n" )
|
||||
|
||||
end
|
||||
|
||||
@@ -337,27 +332,26 @@ function PROFILER.printCSV(data, runTimeGame)
|
||||
g:close()
|
||||
end
|
||||
|
||||
|
||||
--- Write info to output file.
|
||||
-- @param #string ext Extension.
|
||||
-- @return #string File name.
|
||||
function PROFILER.getfilename(ext)
|
||||
function PROFILER.getfilename( ext )
|
||||
|
||||
local dir=lfs.writedir()..[[Logs\]]
|
||||
local dir = lfs.writedir() .. [[Logs\]]
|
||||
|
||||
ext=ext or PROFILER.fileNameSuffix
|
||||
ext = ext or PROFILER.fileNameSuffix
|
||||
|
||||
local file=dir..PROFILER.fileNamePrefix.."."..ext
|
||||
local file = dir .. PROFILER.fileNamePrefix .. "." .. ext
|
||||
|
||||
if not UTILS.FileExists(file) then
|
||||
if not UTILS.FileExists( file ) then
|
||||
return file
|
||||
end
|
||||
|
||||
for i=1,999 do
|
||||
for i = 1, 999 do
|
||||
|
||||
local file=string.format("%s%s-%03d.%s", dir,PROFILER.fileNamePrefix, i, ext)
|
||||
local file = string.format( "%s%s-%03d.%s", dir, PROFILER.fileNamePrefix, i, ext )
|
||||
|
||||
if not UTILS.FileExists(file) then
|
||||
if not UTILS.FileExists( file ) then
|
||||
return file
|
||||
end
|
||||
|
||||
@@ -368,81 +362,76 @@ end
|
||||
--- Write info to output file.
|
||||
-- @param #number runTimeGame Game time in seconds.
|
||||
-- @param #number runTimeOS OS time in seconds.
|
||||
function PROFILER.showInfo(runTimeGame, runTimeOS)
|
||||
function PROFILER.showInfo( runTimeGame, runTimeOS )
|
||||
|
||||
-- Output file.
|
||||
local file=PROFILER.getfilename(PROFILER.fileNameSuffix)
|
||||
local f=io.open(file, 'w')
|
||||
local file = PROFILER.getfilename( PROFILER.fileNameSuffix )
|
||||
local f = io.open( file, 'w' )
|
||||
|
||||
-- Gather data.
|
||||
local Ttot=0
|
||||
local Calls=0
|
||||
local Ttot = 0
|
||||
local Calls = 0
|
||||
|
||||
local t={}
|
||||
local t = {}
|
||||
|
||||
local tcopy=nil --#PROFILER.Data
|
||||
local tserialize=nil --#PROFILER.Data
|
||||
local tforgen=nil --#PROFILER.Data
|
||||
local tpairs=nil --#PROFILER.Data
|
||||
local tcopy = nil -- #PROFILER.Data
|
||||
local tserialize = nil -- #PROFILER.Data
|
||||
local tforgen = nil -- #PROFILER.Data
|
||||
local tpairs = nil -- #PROFILER.Data
|
||||
|
||||
for func, count in pairs( PROFILER.Counters ) do
|
||||
|
||||
for func, count in pairs(PROFILER.Counters) do
|
||||
local s, src, line, tm = PROFILER.getData( func )
|
||||
|
||||
local s,src,line,tm=PROFILER.getData(func)
|
||||
|
||||
if PROFILER.logUnknown==true then
|
||||
if s==nil then s="<Unknown>" end
|
||||
if PROFILER.logUnknown == true then
|
||||
if s == nil then
|
||||
s = "<Unknown>"
|
||||
end
|
||||
end
|
||||
|
||||
if s~=nil then
|
||||
if s ~= nil then
|
||||
|
||||
-- Profile data.
|
||||
local T=
|
||||
{ func=s,
|
||||
src=src,
|
||||
line=line,
|
||||
count=count,
|
||||
tm=tm,
|
||||
} --#PROFILER.Data
|
||||
local T = { func = s, src = src, line = line, count = count, tm = tm } -- #PROFILER.Data
|
||||
|
||||
-- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data.
|
||||
if s=="_copy" then
|
||||
if tcopy==nil then
|
||||
tcopy=T
|
||||
if s == "_copy" then
|
||||
if tcopy == nil then
|
||||
tcopy = T
|
||||
else
|
||||
tcopy.count=tcopy.count+T.count
|
||||
tcopy.tm=tcopy.tm+T.tm
|
||||
tcopy.count = tcopy.count + T.count
|
||||
tcopy.tm = tcopy.tm + T.tm
|
||||
end
|
||||
elseif s=="_Serialize" then
|
||||
if tserialize==nil then
|
||||
tserialize=T
|
||||
elseif s == "_Serialize" then
|
||||
if tserialize == nil then
|
||||
tserialize = T
|
||||
else
|
||||
tserialize.count=tserialize.count+T.count
|
||||
tserialize.tm=tserialize.tm+T.tm
|
||||
tserialize.count = tserialize.count + T.count
|
||||
tserialize.tm = tserialize.tm + T.tm
|
||||
end
|
||||
elseif s=="(for generator)" then
|
||||
if tforgen==nil then
|
||||
tforgen=T
|
||||
elseif s == "(for generator)" then
|
||||
if tforgen == nil then
|
||||
tforgen = T
|
||||
else
|
||||
tforgen.count=tforgen.count+T.count
|
||||
tforgen.tm=tforgen.tm+T.tm
|
||||
tforgen.count = tforgen.count + T.count
|
||||
tforgen.tm = tforgen.tm + T.tm
|
||||
end
|
||||
elseif s=="pairs" then
|
||||
if tpairs==nil then
|
||||
tpairs=T
|
||||
elseif s == "pairs" then
|
||||
if tpairs == nil then
|
||||
tpairs = T
|
||||
else
|
||||
tpairs.count=tpairs.count+T.count
|
||||
tpairs.tm=tpairs.tm+T.tm
|
||||
tpairs.count = tpairs.count + T.count
|
||||
tpairs.tm = tpairs.tm + T.tm
|
||||
end
|
||||
else
|
||||
table.insert(t, T)
|
||||
table.insert( t, T )
|
||||
end
|
||||
|
||||
-- Total function time.
|
||||
Ttot=Ttot+tm
|
||||
Ttot = Ttot + tm
|
||||
|
||||
-- Total number of calls.
|
||||
Calls=Calls+count
|
||||
Calls = Calls + count
|
||||
|
||||
end
|
||||
|
||||
@@ -450,90 +439,95 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
|
||||
|
||||
-- Add special cases.
|
||||
if tcopy then
|
||||
table.insert(t, tcopy)
|
||||
table.insert( t, tcopy )
|
||||
end
|
||||
if tserialize then
|
||||
table.insert(t, tserialize)
|
||||
table.insert( t, tserialize )
|
||||
end
|
||||
if tforgen then
|
||||
table.insert(t, tforgen)
|
||||
table.insert( t, tforgen )
|
||||
end
|
||||
if tpairs then
|
||||
table.insert(t, tpairs)
|
||||
table.insert( t, tpairs )
|
||||
end
|
||||
|
||||
env.info('############################ Profiler Stopped ############################')
|
||||
env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame))
|
||||
env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS))
|
||||
env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100))
|
||||
env.info(string.format("* Total functions : %d", #t))
|
||||
env.info(string.format("* Total func calls : %d", Calls))
|
||||
env.info(string.format("* Writing to file : \"%s\"", file))
|
||||
env.info(string.format("* Writing to file : \"%s\"", PROFILER.getfilename("csv")))
|
||||
env.info("##############################################################################")
|
||||
env.info( '############################ Profiler Stopped ############################' )
|
||||
env.info( string.format( "* Runtime Game : %s = %d sec", UTILS.SecondsToClock( runTimeGame, true ), runTimeGame ) )
|
||||
env.info( string.format( "* Runtime Real : %s = %d sec", UTILS.SecondsToClock( runTimeOS, true ), runTimeOS ) )
|
||||
env.info( string.format( "* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock( Ttot, true ), Ttot, Ttot / runTimeGame * 100 ) )
|
||||
env.info( string.format( "* Total functions : %d", #t ) )
|
||||
env.info( string.format( "* Total func calls : %d", Calls ) )
|
||||
env.info( string.format( "* Writing to file : \"%s\"", file ) )
|
||||
env.info( string.format( "* Writing to file : \"%s\"", PROFILER.getfilename( "csv" ) ) )
|
||||
env.info( "##############################################################################" )
|
||||
|
||||
-- Sort by total time.
|
||||
table.sort(t, function(a,b) return a.tm>b.tm end)
|
||||
table.sort( t, function( a, b )
|
||||
return a.tm > b.tm
|
||||
end )
|
||||
|
||||
-- Write data.
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,"-------------------------")
|
||||
PROFILER._flog(f,"---- Profiler Report ----")
|
||||
PROFILER._flog(f,"-------------------------")
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame))
|
||||
PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS))
|
||||
PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100))
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,string.format("* Total functions = %d", #t))
|
||||
PROFILER._flog(f,string.format("* Total func calls = %d", Calls))
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS))
|
||||
PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec", PROFILER.ThreshTtot))
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER.showTable(t, f, runTimeGame)
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, "-------------------------" )
|
||||
PROFILER._flog( f, "---- Profiler Report ----" )
|
||||
PROFILER._flog( f, "-------------------------" )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, string.format( "* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock( runTimeGame, true ), runTimeGame ) )
|
||||
PROFILER._flog( f, string.format( "* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock( runTimeOS, true ), runTimeOS ) )
|
||||
PROFILER._flog( f, string.format( "* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock( Ttot, true ), Ttot, Ttot / runTimeGame * 100 ) )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, string.format( "* Total functions = %d", #t ) )
|
||||
PROFILER._flog( f, string.format( "* Total func calls = %d", Calls ) )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, string.format( "* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS ) )
|
||||
PROFILER._flog( f, string.format( "* Total func time threshold = %.3f sec", PROFILER.ThreshTtot ) )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER.showTable( t, f, runTimeGame )
|
||||
|
||||
-- Sort by number of calls.
|
||||
table.sort(t, function(a,b) return a.tm/a.count>b.tm/b.count end)
|
||||
table.sort( t, function( a, b )
|
||||
return a.tm / a.count > b.tm / b.count
|
||||
end )
|
||||
|
||||
-- Detailed data.
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,"--------------------------------------")
|
||||
PROFILER._flog(f,"---- Data Sorted by Time per Call ----")
|
||||
PROFILER._flog(f,"--------------------------------------")
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER.showTable(t, f, runTimeGame)
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, "--------------------------------------" )
|
||||
PROFILER._flog( f, "---- Data Sorted by Time per Call ----" )
|
||||
PROFILER._flog( f, "--------------------------------------" )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER.showTable( t, f, runTimeGame )
|
||||
|
||||
-- Sort by number of calls.
|
||||
table.sort(t, function(a,b) return a.count>b.count end)
|
||||
table.sort( t, function( a, b )
|
||||
return a.count > b.count
|
||||
end )
|
||||
|
||||
-- Detailed data.
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,"------------------------------------")
|
||||
PROFILER._flog(f,"---- Data Sorted by Total Calls ----")
|
||||
PROFILER._flog(f,"------------------------------------")
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER.showTable(t, f, runTimeGame)
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, "------------------------------------" )
|
||||
PROFILER._flog( f, "---- Data Sorted by Total Calls ----" )
|
||||
PROFILER._flog( f, "------------------------------------" )
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER.showTable( t, f, runTimeGame )
|
||||
|
||||
-- Closing.
|
||||
PROFILER._flog(f,"")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog(f,"************************************************************************************************************************")
|
||||
PROFILER._flog( f, "" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
PROFILER._flog( f, "************************************************************************************************************************" )
|
||||
-- Close file.
|
||||
f:close()
|
||||
|
||||
-- Print csv file.
|
||||
PROFILER.printCSV(t, runTimeGame)
|
||||
PROFILER.printCSV( t, runTimeGame )
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 sanitization.
|
||||
-- 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 initialize 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 specific 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"
|
||||
|
||||
--- DON'T 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, average of 5 letters for english word so
|
||||
--
|
||||
-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second
|
||||
--
|
||||
-- So length 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
|
||||
@@ -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
@@ -17,6 +17,7 @@
|
||||
-- @field #table CategoryName Names of airbase categories.
|
||||
-- @field #string AirbaseName Name of the airbase.
|
||||
-- @field #number AirbaseID Airbase ID.
|
||||
-- @field Core.Zone#ZONE AirbaseZone Circular zone around the airbase with a radius of 2500 meters. For ships this is a ZONE_UNIT object.
|
||||
-- @field #number category Airbase category.
|
||||
-- @field #table descriptors DCS descriptors.
|
||||
-- @field #boolean isAirdrome Airbase is an airdrome.
|
||||
@@ -73,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
|
||||
@@ -122,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
|
||||
@@ -141,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",
|
||||
@@ -162,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
|
||||
@@ -195,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",
|
||||
@@ -237,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
|
||||
@@ -268,6 +271,7 @@ 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 Intl",
|
||||
@@ -301,7 +305,7 @@ AIRBASE.PersianGulf = {
|
||||
["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
|
||||
@@ -326,7 +330,7 @@ AIRBASE.TheChannel = {
|
||||
["High_Halden"] = "High Halden",
|
||||
}
|
||||
|
||||
--- Airbases of Syria
|
||||
--- Airbases of the Syria map:
|
||||
--
|
||||
-- * AIRBASE.Syria.Kuweires
|
||||
-- * AIRBASE.Syria.Marj_Ruhayyil
|
||||
@@ -336,41 +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
|
||||
-- * AIRBASE.Syria.H4
|
||||
-- * AIRBASE.Syria.Gaziantep
|
||||
-- * AIRBASE.Syria.Rosh_Pina
|
||||
-- * AIRBASE.Syria.Sayqal
|
||||
-- * AIRBASE.Syria.Shayrat
|
||||
-- * AIRBASE.Syria.Tiyas
|
||||
-- * AIRBASE.Syria.Tha_lah
|
||||
-- * AIRBASE.Syria.Naqoura
|
||||
--
|
||||
-- @field Syria
|
||||
--@field Syria
|
||||
AIRBASE.Syria={
|
||||
["Kuweires"]="Kuweires",
|
||||
["Marj_Ruhayyil"]="Marj Ruhayyil",
|
||||
@@ -380,39 +396,71 @@ 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",
|
||||
["Abu_al_Duhur"]="Abu al-Duhur",
|
||||
["H4"]="H4",
|
||||
["Gaziantep"]="Gaziantep",
|
||||
["Rosh_Pina"]="Rosh Pina",
|
||||
["Sayqal"]="Sayqal",
|
||||
["Shayrat"]="Shayrat",
|
||||
["Tiyas"]="Tiyas",
|
||||
["Tha_lah"]="Tha'lah",
|
||||
["Naqoura"]="Naqoura",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- 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",
|
||||
}
|
||||
|
||||
|
||||
@@ -499,6 +547,13 @@ 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
|
||||
@@ -511,8 +566,14 @@ function AIRBASE:Register(AirbaseName)
|
||||
self:GetCoordinate()
|
||||
|
||||
if vec2 then
|
||||
-- TODO: For ships we need a moving zone.
|
||||
self.AirbaseZone=ZONE_RADIUS:New( AirbaseName, vec2, 2500 )
|
||||
if self.isShip then
|
||||
local unit=UNIT:FindByName(AirbaseName)
|
||||
if unit then
|
||||
self.AirbaseZone=ZONE_UNIT:New(AirbaseName, unit, 2500)
|
||||
end
|
||||
else
|
||||
self.AirbaseZone=ZONE_RADIUS:New(AirbaseName, vec2, 2500)
|
||||
end
|
||||
else
|
||||
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
|
||||
end
|
||||
@@ -959,10 +1020,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
|
||||
@@ -1037,7 +1107,7 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
|
||||
|
||||
-- Get airbase name.
|
||||
local airbasename=self:GetName()
|
||||
self:E(string.format("Parking spots at %s for termial type %s:", airbasename, tostring(termtype)))
|
||||
self:E(string.format("Parking spots at %s for terminal type %s:", airbasename, tostring(termtype)))
|
||||
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
|
||||
@@ -1114,14 +1184,25 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
||||
parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype)
|
||||
|
||||
-- Get the aircraft size, i.e. it's longest side of x,z.
|
||||
local aircraft=group:GetUnit(1)
|
||||
local _aircraftsize, ax,ay,az=aircraft:GetObjectSize()
|
||||
local aircraft = nil -- fix local problem below
|
||||
local _aircraftsize, ax,ay,az
|
||||
if group and group.ClassName == "GROUP" then
|
||||
aircraft=group:GetUnit(1)
|
||||
_aircraftsize, ax,ay,az=aircraft:GetObjectSize()
|
||||
else
|
||||
-- SU27 dimensions
|
||||
_aircraftsize = 23
|
||||
ax = 23 -- length
|
||||
ay = 7 -- height
|
||||
az = 17 -- width
|
||||
end
|
||||
|
||||
|
||||
-- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size!
|
||||
local _nspots=nspots or group:GetSize()
|
||||
|
||||
-- Debug info.
|
||||
self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
|
||||
self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
|
||||
|
||||
-- Table of valid spots.
|
||||
local validspots={}
|
||||
@@ -1244,6 +1325,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
|
||||
|
||||
-- Retrun spots we found, even if there were not enough.
|
||||
return validspots
|
||||
|
||||
end
|
||||
|
||||
--- Check black and white lists.
|
||||
@@ -1372,7 +1454,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
|
||||
|
||||
@@ -175,10 +175,7 @@
|
||||
-- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat}
|
||||
--
|
||||
-- ## 5.5) Air-2-Air missile attack range:
|
||||
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets.
|
||||
--
|
||||
-- ## 5.6) GROUND units attack range:
|
||||
-- * @{#CONTROLLABLE.OptionEngageRange}(): Engage range limit in percent (a number between 0 and 100). Default 100. Defines the range at which a GROUND unit/group (e.g. a SAM site) is allowed to use its weapons automatically.
|
||||
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets .
|
||||
--
|
||||
-- @field #CONTROLLABLE
|
||||
CONTROLLABLE = {
|
||||
@@ -1482,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.
|
||||
@@ -1489,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 )
|
||||
@@ -1853,7 +1851,26 @@ 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 )
|
||||
|
||||
@@ -1894,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.
|
||||
@@ -1909,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( Speed, Formation )
|
||||
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 )
|
||||
|
||||
@@ -1956,15 +1986,30 @@ do -- Patrol methods
|
||||
-- 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
|
||||
local ToCoord = RandomZone:GetRandomCoordinate( 10 )
|
||||
|
||||
-- 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( Speed, Formation )
|
||||
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 )
|
||||
|
||||
@@ -3734,8 +3779,9 @@ end
|
||||
-- @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
|
||||
-- @param #string formation Formation string as in the mission editor, e.g. "Vee", "Diamond", "Line abreast", etc. Defaults to "Off Road"
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut)
|
||||
function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut, formation)
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
local _coord = self:GetCoordinate()
|
||||
@@ -3746,14 +3792,14 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc
|
||||
local _grptsk = {}
|
||||
local _candoroad = false
|
||||
local _shortcut = shortcut or false
|
||||
local _formation = formation or "Off Road"
|
||||
|
||||
-- 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)
|
||||
_grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,_formation,_shortcut)
|
||||
self:Route(_grptsk,5)
|
||||
else
|
||||
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road")
|
||||
self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,_formation)
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -3772,10 +3818,30 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds)
|
||||
local Controller = self:_GetController()
|
||||
if Controller then
|
||||
if self:IsGround() then
|
||||
self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds)
|
||||
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 )
|
||||
|
||||
@@ -339,9 +338,7 @@ end
|
||||
-- If the first @{Wrapper.Unit} of the group is inactive, it will return false.
|
||||
--
|
||||
-- @param #GROUP self
|
||||
-- @return #boolean true if the group is alive and active.
|
||||
-- @return #boolean false if the group is alive but inactive.
|
||||
-- @return #nil if the group does not exist anymore.
|
||||
-- @return #boolean `true` if the group is alive *and* active, `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 +360,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 +408,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
|
||||
|
||||
@@ -568,12 +563,12 @@ function GROUP:GetSpeedMax()
|
||||
|
||||
local Units=self:GetUnits()
|
||||
|
||||
local speedmax=nil
|
||||
local speedmax=0
|
||||
|
||||
for _,unit in pairs(Units) do
|
||||
local unit=unit --Wrapper.Unit#UNIT
|
||||
local speed=unit:GetSpeedMax()
|
||||
if speedmax==nil then
|
||||
if speedmax==0 then
|
||||
speedmax=speed
|
||||
elseif speed<speedmax then
|
||||
speedmax=speed
|
||||
@@ -1147,6 +1142,7 @@ function GROUP:GetAmmunition()
|
||||
local Nshells=0
|
||||
local Nrockets=0
|
||||
local Nmissiles=0
|
||||
local Nbombs=0
|
||||
|
||||
if DCSControllable then
|
||||
|
||||
@@ -1155,18 +1151,19 @@ function GROUP:GetAmmunition()
|
||||
local Unit = UnitData -- Wrapper.Unit#UNIT
|
||||
|
||||
-- Get ammo of the unit
|
||||
local ntot, nshells, nrockets, nmissiles = Unit:GetAmmunition()
|
||||
local ntot, nshells, nrockets, nbombs, nmissiles = Unit:GetAmmunition()
|
||||
|
||||
Ntot=Ntot+ntot
|
||||
Nshells=Nshells+nshells
|
||||
Nrockets=Nrockets+nrockets
|
||||
Nmissiles=Nmissiles+nmissiles
|
||||
Nbombs=Nbombs+nbombs
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return Ntot, Nshells, Nrockets, Nmissiles
|
||||
return Ntot, Nshells, Nrockets, Nbombs, Nmissiles
|
||||
end
|
||||
|
||||
|
||||
@@ -2324,41 +2321,44 @@ function GROUP:GetAttribute()
|
||||
local unarmedship=self:HasAttribute("Unarmed ships")
|
||||
|
||||
|
||||
-- Define attribute. Order is important.
|
||||
if transportplane then
|
||||
attribute=GROUP.Attribute.AIR_TRANSPORTPLANE
|
||||
elseif awacs then
|
||||
attribute=GROUP.Attribute.AIR_AWACS
|
||||
elseif fighter then
|
||||
-- Define attribute. Order of attack is important.
|
||||
if fighter then
|
||||
attribute=GROUP.Attribute.AIR_FIGHTER
|
||||
elseif bomber then
|
||||
attribute=GROUP.Attribute.AIR_BOMBER
|
||||
elseif awacs then
|
||||
attribute=GROUP.Attribute.AIR_AWACS
|
||||
elseif transportplane then
|
||||
attribute=GROUP.Attribute.AIR_TRANSPORTPLANE
|
||||
elseif tanker then
|
||||
attribute=GROUP.Attribute.AIR_TANKER
|
||||
elseif transporthelo then
|
||||
attribute=GROUP.Attribute.AIR_TRANSPORTHELO
|
||||
-- helos
|
||||
elseif attackhelicopter then
|
||||
attribute=GROUP.Attribute.AIR_ATTACKHELO
|
||||
elseif transporthelo then
|
||||
attribute=GROUP.Attribute.AIR_TRANSPORTHELO
|
||||
elseif uav then
|
||||
attribute=GROUP.Attribute.AIR_UAV
|
||||
elseif apc then
|
||||
attribute=GROUP.Attribute.GROUND_APC
|
||||
elseif infantry then
|
||||
attribute=GROUP.Attribute.GROUND_INFANTRY
|
||||
elseif artillery then
|
||||
attribute=GROUP.Attribute.GROUND_ARTILLERY
|
||||
elseif tank then
|
||||
attribute=GROUP.Attribute.GROUND_TANK
|
||||
elseif aaa then
|
||||
attribute=GROUP.Attribute.GROUND_AAA
|
||||
-- ground - order of attack
|
||||
elseif ewr then
|
||||
attribute=GROUP.Attribute.GROUND_EWR
|
||||
elseif sam then
|
||||
attribute=GROUP.Attribute.GROUND_SAM
|
||||
elseif aaa then
|
||||
attribute=GROUP.Attribute.GROUND_AAA
|
||||
elseif artillery then
|
||||
attribute=GROUP.Attribute.GROUND_ARTILLERY
|
||||
elseif tank then
|
||||
attribute=GROUP.Attribute.GROUND_TANK
|
||||
elseif apc then
|
||||
attribute=GROUP.Attribute.GROUND_APC
|
||||
elseif infantry then
|
||||
attribute=GROUP.Attribute.GROUND_INFANTRY
|
||||
elseif truck then
|
||||
attribute=GROUP.Attribute.GROUND_TRUCK
|
||||
elseif train then
|
||||
attribute=GROUP.Attribute.GROUND_TRAIN
|
||||
-- ships
|
||||
elseif aircraftcarrier then
|
||||
attribute=GROUP.Attribute.NAVAL_AIRCRAFTCARRIER
|
||||
elseif warship then
|
||||
@@ -2551,9 +2551,10 @@ do -- Players
|
||||
|
||||
end
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
--- GROUND - Switch on/off radar emissions for the group.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean switch
|
||||
-- @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
|
||||
@@ -2566,6 +2567,46 @@ function GROUP: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 )
|
||||
if switch==nil then
|
||||
switch=false
|
||||
end
|
||||
local SetInvisible = {id = 'SetInvisible', params = {value = switch}}
|
||||
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 )
|
||||
if switch==nil then
|
||||
switch=false
|
||||
end
|
||||
local SetImmortal = {id = 'SetImmortal', params = {value = switch}}
|
||||
self:SetCommand(SetImmortal)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get skill from Group. Effectively gets the skill from Unit 1 as the group holds no skill value.
|
||||
-- @param #GROUP self
|
||||
-- @return #string Skill String of skill name.
|
||||
function GROUP:GetSkill()
|
||||
self:F2( self.GroupName )
|
||||
local unit = self:GetUnit(1)
|
||||
local name = unit:GetName()
|
||||
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
|
||||
return skill
|
||||
end
|
||||
|
||||
--do -- Smoke
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,8 @@
|
||||
--
|
||||
-- @module Wrapper.Static
|
||||
-- @image Wrapper_Static.JPG
|
||||
|
||||
|
||||
--- @type STATIC
|
||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||
|
||||
--- Wrapper class to handle Static objects.
|
||||
--
|
||||
-- Note that Statics are almost the same as Units, but they don't have a controller.
|
||||
@@ -40,13 +37,10 @@
|
||||
--
|
||||
-- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name.
|
||||
--
|
||||
-- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil).
|
||||
-- IMPORTANT: ONE SHOULD NEVER SANITIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil).
|
||||
--
|
||||
-- @field #STATIC
|
||||
STATIC = {
|
||||
ClassName = "STATIC",
|
||||
}
|
||||
|
||||
STATIC = { ClassName = "STATIC" }
|
||||
|
||||
--- Register a static object.
|
||||
-- @param #STATIC self
|
||||
@@ -58,7 +52,6 @@ function STATIC:Register( StaticName )
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Finds a STATIC from the _DATABASE using a DCSStatic object.
|
||||
-- @param #STATIC self
|
||||
-- @param DCS#StaticObject DCSStatic An existing DCS Static object reference.
|
||||
@@ -85,7 +78,7 @@ function STATIC:FindByName( StaticName, RaiseError )
|
||||
self.StaticName = StaticName
|
||||
|
||||
if StaticFound then
|
||||
return StaticFound
|
||||
return StaticFound
|
||||
end
|
||||
|
||||
if RaiseError == nil or RaiseError == true then
|
||||
@@ -97,8 +90,9 @@ end
|
||||
|
||||
--- Destroys the STATIC.
|
||||
-- @param #STATIC self
|
||||
-- @param #boolean GenerateEvent (Optional) true if you want to generate a crash or dead event for the static.
|
||||
-- @param #boolean GenerateEvent (Optional) true to generate a crash or dead event, false to not generate any event. `nil` (default) creates a remove event.
|
||||
-- @return #nil The DCS StaticObject is not existing or alive.
|
||||
--
|
||||
-- @usage
|
||||
-- -- Air static example: destroy the static Helicopter and generate a S_EVENT_CRASH.
|
||||
-- Helicopter = STATIC:FindByName( "Helicopter" )
|
||||
@@ -117,7 +111,7 @@ end
|
||||
-- @usage
|
||||
-- -- Destroy without event generation example.
|
||||
-- Ship = STATIC:FindByName( "Boat" )
|
||||
-- Ship:Destroy( false ) -- Don't generate an event upon destruction.
|
||||
-- Ship:Destroy( false ) -- Don't generate any event upon destruction.
|
||||
--
|
||||
function STATIC:Destroy( GenerateEvent )
|
||||
self:F2( self.ObjectName )
|
||||
@@ -148,7 +142,6 @@ function STATIC:Destroy( GenerateEvent )
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Get DCS object of static of static.
|
||||
-- @param #STATIC self
|
||||
-- @return DCS static object
|
||||
@@ -180,7 +173,6 @@ function STATIC:GetUnits()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Get threat level of static.
|
||||
-- @param #STATIC self
|
||||
-- @return #number Threat level 1.
|
||||
@@ -194,15 +186,15 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static.
|
||||
-- @param #number Heading The heading of the static respawn in degrees. Default is 0 deg.
|
||||
-- @param #number Delay Delay in seconds before the static is spawned.
|
||||
function STATIC:SpawnAt(Coordinate, Heading, Delay)
|
||||
function STATIC:SpawnAt( Coordinate, Heading, Delay )
|
||||
|
||||
Heading=Heading or 0
|
||||
Heading = Heading or 0
|
||||
|
||||
if Delay and Delay>0 then
|
||||
SCHEDULER:New(nil, self.SpawnAt, {self, Coordinate, Heading}, Delay)
|
||||
if Delay and Delay > 0 then
|
||||
SCHEDULER:New( nil, self.SpawnAt, { self, Coordinate, Heading }, Delay )
|
||||
else
|
||||
|
||||
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName)
|
||||
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName )
|
||||
|
||||
SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName )
|
||||
|
||||
@@ -211,47 +203,43 @@ function STATIC:SpawnAt(Coordinate, Heading, Delay)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Respawn the @{Wrapper.Unit} at the same location with the same properties.
|
||||
-- This is useful to respawn a cargo after it has been destroyed.
|
||||
-- @param #STATIC self
|
||||
-- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently.
|
||||
-- @param #number Delay (Optional) Delay in seconds before static is respawned. Default now.
|
||||
function STATIC:ReSpawn(CountryID, Delay)
|
||||
function STATIC:ReSpawn( CountryID, Delay )
|
||||
|
||||
if Delay and Delay>0 then
|
||||
SCHEDULER:New(nil, self.ReSpawn, {self, CountryID}, Delay)
|
||||
if Delay and Delay > 0 then
|
||||
SCHEDULER:New( nil, self.ReSpawn, { self, CountryID }, Delay )
|
||||
else
|
||||
|
||||
CountryID=CountryID or self:GetCountry()
|
||||
CountryID = CountryID or self:GetCountry()
|
||||
|
||||
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID)
|
||||
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, CountryID )
|
||||
|
||||
SpawnStatic:Spawn(nil, self.StaticName)
|
||||
SpawnStatic:Spawn( nil, self.StaticName )
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Respawn the @{Wrapper.Unit} at a defined Coordinate with an optional heading.
|
||||
-- @param #STATIC self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static.
|
||||
-- @param #number Heading (Optional) The heading of the static respawn in degrees. Default the current heading.
|
||||
-- @param #number Delay (Optional) Delay in seconds before static is respawned. Default now.
|
||||
function STATIC:ReSpawnAt(Coordinate, Heading, Delay)
|
||||
-- @param #number Heading (Optional) The heading of the static respawn in degrees. Default is the current heading.
|
||||
-- @param #number Delay (Optional) Delay in seconds before static is respawned. Default is now.
|
||||
function STATIC:ReSpawnAt( Coordinate, Heading, Delay )
|
||||
|
||||
--Heading=Heading or 0
|
||||
-- Heading=Heading or 0
|
||||
|
||||
if Delay and Delay>0 then
|
||||
SCHEDULER:New(nil, self.ReSpawnAt, {self, Coordinate, Heading}, Delay)
|
||||
if Delay and Delay > 0 then
|
||||
SCHEDULER:New( nil, self.ReSpawnAt, { self, Coordinate, Heading }, Delay )
|
||||
else
|
||||
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, self:GetCountry() )
|
||||
|
||||
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, self:GetCountry())
|
||||
|
||||
SpawnStatic:SpawnFromCoordinate(Coordinate, Heading, self.StaticName)
|
||||
|
||||
SpawnStatic:SpawnFromCoordinate( Coordinate, Heading, self.StaticName )
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
@@ -168,9 +168,6 @@ function UNIT:GetDCSObject()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group.
|
||||
--
|
||||
-- This function will:
|
||||
@@ -263,8 +260,6 @@ function UNIT:ReSpawnAt( Coordinate, Heading )
|
||||
_DATABASE:Spawn( SpawnGroupTemplate )
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Returns if the unit is activated.
|
||||
-- @param #UNIT self
|
||||
-- @return #boolean `true` if Unit is activated. `nil` The DCS Unit is not existing or alive.
|
||||
@@ -301,8 +296,6 @@ function UNIT:IsAlive()
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Returns the Unit's callsign - the localized string.
|
||||
-- @param #UNIT self
|
||||
-- @return #string The Callsign of the Unit.
|
||||
@@ -442,7 +435,7 @@ function UNIT:GetSpeedMax()
|
||||
return SpeedMax*3.6
|
||||
end
|
||||
|
||||
return nil
|
||||
return 0
|
||||
end
|
||||
|
||||
--- Returns the unit's max range in meters derived from the DCS descriptors.
|
||||
@@ -580,6 +573,18 @@ function UNIT:GetAmmo()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Sets the Unit's Internal Cargo Mass, in kg
|
||||
-- @param #UNIT self
|
||||
-- @param #number mass to set cargo to
|
||||
-- @return #UNIT self
|
||||
function UNIT:SetUnitInternalCargo(mass)
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
if DCSUnit then
|
||||
trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has.
|
||||
-- @param #UNIT self
|
||||
-- @return #number Total amount of ammo the unit has left. This is the sum of shells, rockets, bombs and missiles.
|
||||
@@ -614,8 +619,8 @@ function UNIT:GetAmmunition()
|
||||
-- Type name of current weapon.
|
||||
local Tammo=ammotable[w]["desc"]["typeName"]
|
||||
|
||||
local _weaponString = UTILS.Split(Tammo,"%.")
|
||||
local _weaponName = _weaponString[#_weaponString]
|
||||
--local _weaponString = UTILS.Split(Tammo,"%.")
|
||||
--local _weaponName = _weaponString[#_weaponString]
|
||||
|
||||
-- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3
|
||||
local Category=ammotable[w].desc.category
|
||||
@@ -644,7 +649,8 @@ function UNIT:GetAmmunition()
|
||||
|
||||
elseif Category==Weapon.Category.MISSILE then
|
||||
|
||||
-- Add up all cruise missiles (category 5)
|
||||
|
||||
-- Add up all missiles (category 5)
|
||||
if MissileCategory==Weapon.MissileCategory.AAM then
|
||||
nmissiles=nmissiles+Nammo
|
||||
elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then
|
||||
@@ -653,6 +659,10 @@ function UNIT:GetAmmunition()
|
||||
nmissiles=nmissiles+Nammo
|
||||
elseif MissileCategory==Weapon.MissileCategory.OTHER then
|
||||
nmissiles=nmissiles+Nammo
|
||||
elseif MissileCategory==Weapon.MissileCategory.SAM then
|
||||
nmissiles=nmissiles+Nammo
|
||||
elseif MissileCategory==Weapon.MissileCategory.CRUISE then
|
||||
nmissiles=nmissiles+Nammo
|
||||
end
|
||||
|
||||
end
|
||||
@@ -761,40 +771,6 @@ function UNIT:GetFuel()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Sets the passed group or unit objects radar emitters on or off. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state.
|
||||
-- @param #UNIT self
|
||||
-- @param #boolean Switch If `true` or `nil`, emission is enabled. If `false`, emission is turned off.
|
||||
-- @return #UNIT self
|
||||
function UNIT:SetEmission(Switch)
|
||||
|
||||
if Switch==nil then
|
||||
Switch=true
|
||||
end
|
||||
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
|
||||
if DCSUnit then
|
||||
DCSUnit:enableEmission(Switch)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the passed group or unit objects radar emitters ON. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state.
|
||||
-- @param #UNIT self
|
||||
-- @return #UNIT self
|
||||
function UNIT:EnableEmission()
|
||||
self:SetEmission(true)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the passed group or unit objects radar emitters OFF. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state.
|
||||
-- @param #UNIT self
|
||||
-- @return #UNIT self
|
||||
function UNIT:DisableEmission()
|
||||
self:SetEmission(false)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns a list of one @{Wrapper.Unit}.
|
||||
-- @param #UNIT self
|
||||
@@ -978,7 +954,6 @@ end
|
||||
-- @return #string Some text.
|
||||
function UNIT:GetThreatLevel()
|
||||
|
||||
|
||||
local ThreatLevel = 0
|
||||
local ThreatText = ""
|
||||
|
||||
@@ -1004,7 +979,6 @@ function UNIT:GetThreatLevel()
|
||||
"LR SAMs"
|
||||
}
|
||||
|
||||
|
||||
if Attributes["LR SAM"] then ThreatLevel = 10
|
||||
elseif Attributes["MR SAM"] then ThreatLevel = 9
|
||||
elseif Attributes["SR SAM"] and
|
||||
@@ -1040,7 +1014,6 @@ function UNIT:GetThreatLevel()
|
||||
"Fighter"
|
||||
}
|
||||
|
||||
|
||||
if Attributes["Fighters"] then ThreatLevel = 10
|
||||
elseif Attributes["Multirole fighters"] then ThreatLevel = 9
|
||||
elseif Attributes["Battleplanes"] then ThreatLevel = 8
|
||||
@@ -1158,12 +1131,6 @@ function UNIT:OtherUnitInRadius( AwaitUnit, Radius )
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--- Returns if the unit is a friendly unit.
|
||||
-- @param #UNIT self
|
||||
-- @return #boolean IsFriendly evaluation result.
|
||||
@@ -1429,9 +1396,10 @@ function UNIT:GetTemplateFuel()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- GROUND - Switch on/off radar emissions.
|
||||
--- GROUND - Switch on/off radar emissions of a unit.
|
||||
-- @param #UNIT self
|
||||
-- @param #boolean switch
|
||||
-- @param #boolean switch If true, emission is enabled. If false, emission is disabled.
|
||||
-- @return #UNIT self
|
||||
function UNIT:EnableEmission(switch)
|
||||
self:F2( self.UnitName )
|
||||
|
||||
@@ -1445,4 +1413,15 @@ function UNIT:EnableEmission(switch)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get skill from Unit.
|
||||
-- @param #UNIT self
|
||||
-- @return #string Skill String of skill name.
|
||||
function UNIT:GetSkill()
|
||||
self:F2( self.UnitName )
|
||||
local name = self.UnitName
|
||||
local skill = _DATABASE.Templates.Units[name].Template.skill or "Random"
|
||||
return skill
|
||||
end
|
||||
|
||||
+12
-4
@@ -3,10 +3,12 @@ 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
|
||||
@@ -21,9 +23,6 @@ Core/Point.lua
|
||||
Core/Velocity.lua
|
||||
Core/Message.lua
|
||||
Core/Fsm.lua
|
||||
Core/Radio.lua
|
||||
Core/RadioQueue.lua
|
||||
Core/RadioSpeech.lua
|
||||
Core/Spawn.lua
|
||||
Core/SpawnStatic.lua
|
||||
Core/Timer.lua
|
||||
@@ -75,6 +74,8 @@ Ops/Airboss.lua
|
||||
Ops/RecoveryTanker.lua
|
||||
Ops/RescueHelo.lua
|
||||
Ops/ATIS.lua
|
||||
Ops/CTLD.lua
|
||||
Ops/CSAR.lua
|
||||
|
||||
AI/AI_Balancer.lua
|
||||
AI/AI_Air.lua
|
||||
@@ -113,6 +114,13 @@ 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
|
||||
|
||||
@@ -30,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
|
||||
@@ -74,6 +74,6 @@ MOOSE has a living (chat and video) community of users, beta testers and contrib
|
||||
|
||||
|
||||
Kind regards,
|
||||
FlightControl (FC)
|
||||
The Moose Team
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user