mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Merge branch 'develop' into FF/Ops
This commit is contained in:
commit
4ffdf9e536
File diff suppressed because it is too large
Load Diff
@ -1,26 +1,26 @@
|
||||
--- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * 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.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Authors: **FlightControl**, **applevangelist**
|
||||
--
|
||||
-- Last Update: April 2021
|
||||
--
|
||||
--
|
||||
-- Last Update: July 2021
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Functional.Sead
|
||||
-- @image SEAD.JPG
|
||||
|
||||
@ -28,49 +28,32 @@
|
||||
-- @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.
|
||||
--
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
--
|
||||
-- Use the @{#SEAD.New}() constructor to create a new SEAD object.
|
||||
--
|
||||
--
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
--
|
||||
--
|
||||
-- @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
|
||||
}
|
||||
|
||||
-- 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",
|
||||
@ -84,7 +67,7 @@ SEAD = {
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
}
|
||||
|
||||
|
||||
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
|
||||
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
|
||||
-- Chances are big that the missile will miss.
|
||||
@ -97,20 +80,20 @@ SEAD = {
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
function SEAD:New( SEADGroupPrefixes )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
self:I("*** SEAD - Started Version 0.2.7")
|
||||
return self
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
|
||||
self:I("*** SEAD - Started Version 0.2.8")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Update the active SEAD Set
|
||||
@ -120,7 +103,7 @@ end
|
||||
function SEAD:UpdateSet( SEADGroupPrefixes )
|
||||
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
@ -164,112 +147,83 @@ end
|
||||
-- @see SEAD
|
||||
-- @param #SEAD
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SEAD:OnEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
function SEAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
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
|
||||
--]]
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
self:T({ SEADWeapon })
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
local _targetskill = "Random"
|
||||
local _targetMimgroupName = "none"
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
local _targetMim = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object
|
||||
if _targetUnit and _targetUnit:IsAlive() then
|
||||
local _targetMimgroup = _targetUnit:GetGroup()
|
||||
local _targetMimgroupName = _targetMimgroup:GetName() -- group name
|
||||
--local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill
|
||||
self:T( self.SEADGroupPrefixes )
|
||||
self:T( _targetMimgroupName )
|
||||
end
|
||||
-- see if we are shot at
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( '*** SEAD - Group Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
local function SuppressionEnd(id) --switch group back on
|
||||
local range = self.EngagementRange -- Feature Request #1355
|
||||
self:T(string.format("*** SEAD - Engagement Range is %d", range))
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
||||
--id.groupName:enableEmission(true)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355
|
||||
self.SuppressedGroups[id.groupName] = nil --delete group id from table when done
|
||||
end
|
||||
-- randomize switch-on time
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
local SuppressionEndTime = timer.getTime() + delay
|
||||
--create entry
|
||||
if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet
|
||||
self.SuppressedGroups[id.groupName] = {
|
||||
SuppressionEndTime = delay
|
||||
}
|
||||
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
--_targetMimgroup:enableEmission(false)
|
||||
timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
--- **Functional** -- Short Range Air Defense System
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- **SHORAD** - Short Range Air Defense System
|
||||
-- Controls a network of short range air/missile defense groups.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author : **applevangelist**
|
||||
--
|
||||
--
|
||||
-- ### Author : **applevangelist **
|
||||
--
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: May 2021
|
||||
-- Date: July 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@ -26,7 +26,7 @@
|
||||
-- @field #string ClassName
|
||||
-- @field #string name Name of this Shorad
|
||||
-- @field #boolean debug Set the debug state
|
||||
-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP}
|
||||
-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP}
|
||||
-- @field #number Radius Shorad defense radius in meters
|
||||
-- @field Core.Set#SET_GROUP Groupset The set of Shorad groups
|
||||
-- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend
|
||||
@ -41,10 +41,10 @@
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
|
||||
--
|
||||
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
|
||||
--
|
||||
-- Simple Class for a more intelligent Short Range Air Defense System
|
||||
--
|
||||
--
|
||||
-- #SHORAD
|
||||
-- Moose derived missile intercepting short range defense system.
|
||||
-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy.
|
||||
@ -52,26 +52,26 @@
|
||||
--
|
||||
-- ## Usage
|
||||
--
|
||||
-- Set up a #SET_GROUP for the SAM sites to be protected:
|
||||
--
|
||||
-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()`
|
||||
--
|
||||
-- Set up a #SET_GROUP for the SAM sites to be protected:
|
||||
--
|
||||
-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()`
|
||||
--
|
||||
-- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%.
|
||||
-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire.
|
||||
--
|
||||
-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire.
|
||||
--
|
||||
-- ### Start a new SHORAD system, parameters are:
|
||||
--
|
||||
-- * Name: Name of this SHORAD.
|
||||
-- * ShoradPrefix: Filter for the Shorad #SET_GROUP.
|
||||
-- * Samset: The #SET_GROUP of SAM sites to defend.
|
||||
-- * Radius: Defense radius in meters.
|
||||
-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call.
|
||||
-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".*
|
||||
--
|
||||
-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")`
|
||||
--
|
||||
-- * Name: Name of this SHORAD.
|
||||
-- * ShoradPrefix: Filter for the Shorad #SET_GROUP.
|
||||
-- * Samset: The #SET_GROUP of SAM sites to defend.
|
||||
-- * Radius: Defense radius in meters.
|
||||
-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call.
|
||||
-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".*
|
||||
--
|
||||
-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")`
|
||||
--
|
||||
-- ## Customize options
|
||||
--
|
||||
-- ## Customize options
|
||||
--
|
||||
-- * SHORAD:SwitchDebug(debug)
|
||||
-- * SHORAD:SwitchHARMDefense(onoff)
|
||||
-- * SHORAD:SwitchAGMDefense(onoff)
|
||||
@ -94,9 +94,9 @@ SHORAD = {
|
||||
lid = "",
|
||||
DefendHarms = true,
|
||||
DefendMavs = true,
|
||||
DefenseLowProb = 75,
|
||||
DefenseLowProb = 70,
|
||||
DefenseHighProb = 90,
|
||||
UseEmOnOff = false,
|
||||
UseEmOnOff = false,
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
@ -108,22 +108,6 @@ 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",
|
||||
@ -137,7 +121,7 @@ do
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
}
|
||||
|
||||
|
||||
--- TODO complete list?
|
||||
-- @field Mavs
|
||||
SHORAD.Mavs = {
|
||||
@ -148,7 +132,7 @@ do
|
||||
["Kh31"] = "Kh31",
|
||||
["Kh66"] = "Kh66",
|
||||
}
|
||||
|
||||
|
||||
--- Instantiates a new SHORAD object
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Name Name of this SHORAD
|
||||
@ -157,10 +141,12 @@ 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})
|
||||
|
||||
|
||||
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
|
||||
|
||||
self.name = Name or "MyShorad"
|
||||
@ -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,33 +182,50 @@ 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
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
@ -229,8 +233,9 @@ do
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendHarms = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Switch defense for AGMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
@ -238,8 +243,9 @@ do
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendMavs = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set defense probability limits
|
||||
-- @param #SHORAD self
|
||||
-- @param #number low Minimum detection limit, integer 1-100
|
||||
@ -256,42 +262,50 @@ 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
|
||||
-- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active
|
||||
function SHORAD:SetDefenseRadius(meters)
|
||||
self:T(self.lid .. " SetDefenseRadius")
|
||||
local radius = meters or 20000
|
||||
if radius < 0 then
|
||||
radius = 20000
|
||||
end
|
||||
self.Radius = radius
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set using Emission on/off instead of changing alarm state
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean switch Decide if we are changing alarm state or AI state
|
||||
function SHORAD:SetUsingEmOnOff(switch)
|
||||
self:T(self.lid .. " SetUsingEmOnOff")
|
||||
self.UseEmOnOff = switch or false
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Check if a HARM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckHarms(WeaponName)
|
||||
self:T(self.lid .. " _CheckHarms")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
@ -301,12 +315,13 @@ do
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
|
||||
--- Check if an AGM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckMavs(WeaponName)
|
||||
self:T(self.lid .. " _CheckMavs")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
@ -316,15 +331,16 @@ do
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
|
||||
--- Check the coalition of the attacker
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Coalition name
|
||||
-- @return #boolean Returns false for a match
|
||||
function SHORAD:_CheckCoalition(Coalition)
|
||||
self:T(self.lid .. " _CheckCoalition")
|
||||
local owncoalition = self.Coalition
|
||||
local othercoalition = ""
|
||||
if Coalition == 0 then
|
||||
if Coalition == 0 then
|
||||
othercoalition = "neutral"
|
||||
elseif Coalition == 1 then
|
||||
othercoalition = "red"
|
||||
@ -338,12 +354,13 @@ do
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Check if the missile is aimed at a SHORAD
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtShorad(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtShorad")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Groupset
|
||||
local shoradset = shorad:GetAliveSet() --#table
|
||||
@ -352,17 +369,18 @@ 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
|
||||
return returnname
|
||||
end
|
||||
|
||||
|
||||
--- Check if the missile is aimed at a SAM site
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtSams(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtSams")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Samset
|
||||
--local shoradset = shorad:GetAliveSet() --#table
|
||||
@ -376,11 +394,12 @@ do
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
|
||||
--- Calculate if the missile shot is detected
|
||||
-- @param #SHORAD self
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
function SHORAD:_ShotIsDetected()
|
||||
self:T(self.lid .. " _ShotIsDetected")
|
||||
local IsDetected = false
|
||||
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
|
||||
local ActualDetection = math.random(1,100) -- value for this shot
|
||||
@ -389,15 +408,15 @@ do
|
||||
end
|
||||
return IsDetected
|
||||
end
|
||||
|
||||
|
||||
--- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroup Name of the target group used to build the #ZONE
|
||||
-- @param #number Radius Radius of the #ZONE
|
||||
-- @param #number ActiveTimer Number of seconds to stay active
|
||||
-- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC
|
||||
-- @usage Use this function to integrate with other systems, example
|
||||
--
|
||||
-- @usage Use this function to integrate with other systems, example
|
||||
--
|
||||
-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
|
||||
-- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")
|
||||
-- myshorad:SwitchDebug(true)
|
||||
@ -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
|
||||
@ -473,7 +494,7 @@ do
|
||||
local IsDetected = self:_ShotIsDetected()
|
||||
-- convert to text
|
||||
local DetectedText = "false"
|
||||
if IsDetected then
|
||||
if IsDetected then
|
||||
DetectedText = "true"
|
||||
end
|
||||
local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText)
|
||||
@ -490,7 +511,7 @@ do
|
||||
targetunit = UNIT:Find(targetdata)
|
||||
elseif targetcat == Object.Category.STATIC then -- STATIC
|
||||
targetunit = STATIC:Find(targetdata)
|
||||
end
|
||||
end
|
||||
--local targetunitname = Unit.getName(targetdata) -- Unit name
|
||||
if targetunit and targetunit:IsAlive() then
|
||||
local targetunitname = targetunit:GetName()
|
||||
@ -507,7 +528,7 @@ do
|
||||
local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname))
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
-- check if we or a SAM site are the target
|
||||
-- check if we or a SAM site are the target
|
||||
--local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP
|
||||
local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean
|
||||
local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean
|
||||
@ -516,12 +537,12 @@ do
|
||||
self:T({shotatsams=shotatsams,shotatus=shotatus})
|
||||
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--
|
||||
end
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD end
|
||||
-----------------------------------------------------------------------
|
||||
-----------------------------------------------------------------------
|
||||
@ -11639,7 +11639,7 @@ end
|
||||
|
||||
--- Get wind speed on carrier deck parallel and perpendicular to runway.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number alt Altitude in meters. Default 50 m.
|
||||
-- @param #number alt Altitude in meters. Default 15 m. (change made from 50m from Discord discussion from Sickdog)
|
||||
-- @return #number Wind component parallel to runway im m/s.
|
||||
-- @return #number Wind component perpendicular to runway in m/s.
|
||||
-- @return #number Total wind strength in m/s.
|
||||
@ -11662,7 +11662,7 @@ function AIRBOSS:GetWindOnDeck(alt)
|
||||
zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle)
|
||||
|
||||
-- Wind (from) vector
|
||||
local vw=cv:GetWindWithTurbulenceVec3(alt or 50)
|
||||
local vw=cv:GetWindWithTurbulenceVec3(alt or 15)
|
||||
|
||||
-- Total wind velocity vector.
|
||||
-- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total.
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
--- **Ops** -- Combat Search and Rescue.
|
||||
--
|
||||
-- ===
|
||||
@ -212,8 +213,11 @@ CSAR = {
|
||||
-- @field #string player Player name if applicable.
|
||||
-- @field Wrapper.Group#GROUP group Spawned group object.
|
||||
-- @field #number timestamp Timestamp for approach process
|
||||
|
||||
-- @field #boolean alive Group is alive or dead/rescued
|
||||
--
|
||||
--- Updated and sorted list of known NDB beacons (in kHz!) from the available maps.
|
||||
|
||||
--[[ Moved to Utils
|
||||
-- @field #CSAR.SkipFrequencies
|
||||
CSAR.SkipFrequencies = {
|
||||
214,274,291.5,295,297.5,
|
||||
@ -229,7 +233,8 @@ CSAR.SkipFrequencies = {
|
||||
905,907,920,935,942,950,995,
|
||||
1000,1025,1030,1050,1065,1116,1175,1182,1210
|
||||
}
|
||||
|
||||
--]]
|
||||
|
||||
--- All slot / Limit settings
|
||||
-- @type CSAR.AircraftType
|
||||
-- @field #string typename Unit type name.
|
||||
@ -239,13 +244,14 @@ CSAR.AircraftType["SA342Minigun"] = 2
|
||||
CSAR.AircraftType["SA342L"] = 4
|
||||
CSAR.AircraftType["SA342M"] = 4
|
||||
CSAR.AircraftType["UH-1H"] = 8
|
||||
CSAR.AircraftType["Mi-8MTV2"] = 12
|
||||
CSAR.AircraftType["Mi-8MTV2"] = 12
|
||||
CSAR.AircraftType["Mi-8MT"] = 12
|
||||
CSAR.AircraftType["Mi-24P"] = 8
|
||||
CSAR.AircraftType["Mi-24V"] = 8
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="0.1.8r1"
|
||||
CSAR.version="0.1.8r3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@ -317,6 +323,7 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
self:AddTransition("*", "Boarded", "*") -- Pilot boarded.
|
||||
self:AddTransition("*", "Returning", "*") -- CSAR able to return to base.
|
||||
self:AddTransition("*", "Rescued", "*") -- Pilot at MASH.
|
||||
self:AddTransition("*", "KIA", "*") -- Pilot killed in action.
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
-- tables, mainly for tracking actions
|
||||
@ -361,7 +368,7 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
self.mashprefix = {"MASH"} -- prefixes used to find MASHes
|
||||
self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also?
|
||||
self.autosmoke = false -- automatically smoke location when heli is near
|
||||
self.autosmokedistance = 1000 -- distance for autosmoke
|
||||
self.autosmokedistance = 2000 -- distance for autosmoke
|
||||
-- added 0.1.4
|
||||
self.limitmaxdownedpilots = true
|
||||
self.maxdownedpilots = 25
|
||||
@ -458,6 +465,14 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
-- @param #string HeliName Name of the helicopter group.
|
||||
-- @param #number PilotsSaved Number of the saved pilots on board when landing.
|
||||
|
||||
--- On After "KIA" event. Pilot is dead.
|
||||
-- @function [parent=#CSAR] OnAfterKIA
|
||||
-- @param #CSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Pilotname Name of the pilot KIA.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@ -491,6 +506,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
|
||||
DownedPilot.typename = Typename or ""
|
||||
DownedPilot.group = Group
|
||||
DownedPilot.timestamp = 0
|
||||
DownedPilot.alive = true
|
||||
|
||||
-- Add Pilot
|
||||
local PilotTable = self.downedPilots
|
||||
@ -609,7 +625,8 @@ end
|
||||
-- @param #number _freq Frequency
|
||||
-- @param #boolean noMessage
|
||||
-- @param #string _description Description
|
||||
function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description )
|
||||
-- @param #boolean forcedesc Use the description only for the pilot track entry
|
||||
function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc )
|
||||
self:T(self.lid .. " _AddCsar")
|
||||
self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description})
|
||||
|
||||
@ -622,11 +639,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
|
||||
|
||||
local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq)
|
||||
|
||||
local _typeName = _typeName or "PoW"
|
||||
local _typeName = _typeName or "Pilot"
|
||||
|
||||
if not noMessage then
|
||||
self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10)
|
||||
--local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition)
|
||||
end
|
||||
|
||||
if _freq then
|
||||
@ -635,15 +651,14 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
|
||||
|
||||
self:_AddSpecialOptions(_spawnedGroup)
|
||||
|
||||
local _text = " "
|
||||
if _playerName ~= nil then
|
||||
_text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName
|
||||
elseif _typeName ~= nil then
|
||||
_text = "AI Pilot of " .. _unitName .. " - " .. _typeName
|
||||
else
|
||||
_text = _description
|
||||
end
|
||||
|
||||
local _text = _description
|
||||
if not forcedesc then
|
||||
if _playerName ~= nil then
|
||||
_text = "Pilot " .. _playerName
|
||||
elseif _unitName ~= nil then
|
||||
_text = "AI Pilot of " .. _unitName
|
||||
end
|
||||
end
|
||||
self:T({_spawnedGroup, _alias})
|
||||
|
||||
local _GroupName = _spawnedGroup:GetName() or _alias
|
||||
@ -662,7 +677,10 @@ end
|
||||
-- @param #string _description (optional) Description.
|
||||
-- @param #boolean _randomPoint (optional) Random yes or no.
|
||||
-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR.
|
||||
function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage)
|
||||
-- @param #string unitname (optional) Name of the lost unit.
|
||||
-- @param #string typename (optional) Type of plane.
|
||||
-- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names.
|
||||
function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc)
|
||||
self:T(self.lid .. " _SpawnCsarAtZone")
|
||||
local freq = self:_GenerateADFFrequency()
|
||||
local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position
|
||||
@ -671,7 +689,9 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _
|
||||
return
|
||||
end
|
||||
|
||||
local _description = _description or "Unknown"
|
||||
local _description = _description or "PoW"
|
||||
local unitname = unitname or "Old Rusty"
|
||||
local typename = typename or "Phantom II"
|
||||
|
||||
local pos = {}
|
||||
if _randomPoint then
|
||||
@ -690,7 +710,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _
|
||||
_country = country.id.UN_PEACEKEEPERS
|
||||
end
|
||||
|
||||
self:_AddCsar(_coalition, _country, pos, "PoW", _description, nil, freq, _nomessage, _description)
|
||||
self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc)
|
||||
|
||||
return self
|
||||
end
|
||||
@ -702,12 +722,15 @@ end
|
||||
-- @param #string Description (optional) Description.
|
||||
-- @param #boolean RandomPoint (optional) Random yes or no.
|
||||
-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR.
|
||||
-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so:
|
||||
-- @param #string Unitname (optional) Name of the lost unit.
|
||||
-- @param #string Typename (optional) Type of plane.
|
||||
-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names.
|
||||
-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so:
|
||||
--
|
||||
-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition
|
||||
-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true )
|
||||
function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage)
|
||||
self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage)
|
||||
-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" )
|
||||
function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc)
|
||||
self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc)
|
||||
return self
|
||||
end
|
||||
|
||||
@ -865,7 +888,11 @@ function CSAR:_EventHandler(EventData)
|
||||
end
|
||||
|
||||
if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then
|
||||
self:_RescuePilots(_unit)
|
||||
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then
|
||||
self:_DisplayMessageToSAR(_unit, "Open the door to let me out!", self.messageTime, true)
|
||||
else
|
||||
self:_RescuePilots(_unit)
|
||||
end
|
||||
else
|
||||
self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition()))
|
||||
end
|
||||
@ -916,7 +943,7 @@ function CSAR:_CheckNameInDownedPilots(name)
|
||||
local found = false
|
||||
local table = nil
|
||||
for _,_pilot in pairs(PilotTable) do
|
||||
if _pilot.name == name then
|
||||
if _pilot.name == name and _pilot.alive == true then
|
||||
found = true
|
||||
table = _pilot
|
||||
break
|
||||
@ -933,25 +960,10 @@ end
|
||||
function CSAR:_RemoveNameFromDownedPilots(name,force)
|
||||
local PilotTable = self.downedPilots --#CSAR.DownedPilot
|
||||
local found = false
|
||||
for _,_pilot in pairs(PilotTable) do
|
||||
for _index,_pilot in pairs(PilotTable) do
|
||||
if _pilot.name == name then
|
||||
local group = _pilot.group -- Wrapper.Group#GROUP
|
||||
if group then
|
||||
if (not group:IsAlive()) or ( force == true) then -- don\'t delete groups which still exist
|
||||
found = true
|
||||
_pilot.desc = nil
|
||||
_pilot.frequency = nil
|
||||
_pilot.index = nil
|
||||
_pilot.name = nil
|
||||
_pilot.originalUnit = nil
|
||||
_pilot.player = nil
|
||||
_pilot.side = nil
|
||||
_pilot.typename = nil
|
||||
_pilot.group = nil
|
||||
_pilot.timestamp = nil
|
||||
end
|
||||
self.downedPilots[_index].alive = false
|
||||
end
|
||||
end
|
||||
end
|
||||
return found
|
||||
end
|
||||
@ -974,7 +986,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname)
|
||||
end
|
||||
|
||||
local _woundedGroup = _downedpilot.group
|
||||
if _woundedGroup ~= nil then
|
||||
if _woundedGroup ~= nil and _woundedGroup:IsAlive() then
|
||||
local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT
|
||||
|
||||
local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking
|
||||
@ -986,11 +998,15 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname)
|
||||
self:T("...helinunit nil!")
|
||||
return
|
||||
end
|
||||
|
||||
--if self:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then
|
||||
|
||||
local _heliCoord = _heliUnit:GetCoordinate()
|
||||
local _leaderCoord = _woundedGroup:GetCoordinate()
|
||||
local _distance = self:_GetDistance(_heliCoord,_leaderCoord)
|
||||
-- autosmoke
|
||||
if (self.autosmoke == true) and (_distance < self.autosmokedistance) and (_distance ~= -1) then
|
||||
self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup)
|
||||
end
|
||||
|
||||
if _distance < self.approachdist_near and _distance > 0 then
|
||||
if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then
|
||||
-- we\'re close, reschedule
|
||||
@ -998,14 +1014,35 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname)
|
||||
self:__Approach(-5,heliname,woundedgroupname)
|
||||
end
|
||||
elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then
|
||||
self.heliVisibleMessage[_lookupKeyHeli] = nil
|
||||
-- message once
|
||||
if self.heliVisibleMessage[_lookupKeyHeli] == nil then
|
||||
local _pilotName = _downedpilot.desc
|
||||
if self.autosmoke == true then
|
||||
local dist = self.autosmokedistance / 1000
|
||||
local disttext = string.format("%.0fkm",dist)
|
||||
if _SETTINGS:IsImperial() then
|
||||
local dist = UTILS.MetersToNM(self.autosmokedistance)
|
||||
disttext = string.format("%.0fnm",dist)
|
||||
end
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true)
|
||||
else
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true)
|
||||
end
|
||||
--mark as shown for THIS heli and THIS group
|
||||
self.heliVisibleMessage[_lookupKeyHeli] = true
|
||||
end
|
||||
self.heliCloseMessage[_lookupKeyHeli] = nil
|
||||
self.landedStatus[_lookupKeyHeli] = nil
|
||||
--reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away
|
||||
_downedpilot.timestamp = timer.getAbsTime()
|
||||
self:__Approach(-10,heliname,woundedgroupname)
|
||||
end
|
||||
else
|
||||
self:T("...Downed Pilot KIA?!")
|
||||
self:_RemoveNameFromDownedPilots(_downedpilot.name)
|
||||
if not _downedpilot.alive then
|
||||
--self:__KIA(1,_downedpilot.name)
|
||||
self:_RemoveNameFromDownedPilots(_downedpilot.name, true)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
@ -1106,27 +1143,27 @@ function CSAR:_IsLoadingDoorOpen( unit_name )
|
||||
local type_name = unit:getTypeName()
|
||||
|
||||
if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then
|
||||
self:I(unit_name .. " Cargo doors are open or cargo door not present")
|
||||
self:T(unit_name .. " Cargo doors are open or cargo door not present")
|
||||
ret_val = true
|
||||
end
|
||||
|
||||
if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then
|
||||
self:I(unit_name .. " a side door is open")
|
||||
self:T(unit_name .. " a side door is open")
|
||||
ret_val = true
|
||||
end
|
||||
|
||||
if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then
|
||||
self:I(unit_name .. " a side door is open ")
|
||||
self:T(unit_name .. " a side door is open ")
|
||||
ret_val = true
|
||||
end
|
||||
|
||||
if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then
|
||||
self:I(unit_name .. " front door(s) are open")
|
||||
self:T(unit_name .. " front door(s) are open")
|
||||
ret_val = true
|
||||
end
|
||||
|
||||
if ret_val == false then
|
||||
self:I(unit_name .. " all doors are closed")
|
||||
self:T(unit_name .. " all doors are closed")
|
||||
end
|
||||
return ret_val
|
||||
|
||||
@ -1155,27 +1192,13 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
|
||||
local _reset = true
|
||||
|
||||
if (self.autosmoke == true) and (_distance < self.autosmokedistance) then
|
||||
self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader)
|
||||
end
|
||||
|
||||
if self.heliVisibleMessage[_lookupKeyHeli] == nil then
|
||||
if self.autosmoke == true then
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime,true,true)
|
||||
else
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime,true,true)
|
||||
end
|
||||
--mark as shown for THIS heli and THIS group
|
||||
self.heliVisibleMessage[_lookupKeyHeli] = true
|
||||
end
|
||||
|
||||
if (_distance < 500) then
|
||||
|
||||
if self.heliCloseMessage[_lookupKeyHeli] == nil then
|
||||
if self.autosmoke == true then
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,false,true)
|
||||
else
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true)
|
||||
end
|
||||
--mark as shown for THIS heli and THIS group
|
||||
self.heliCloseMessage[_lookupKeyHeli] = true
|
||||
@ -1192,14 +1215,14 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 )
|
||||
_time = self.landedStatus[_lookupKeyHeli]
|
||||
self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate())
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false)
|
||||
else
|
||||
_time = self.landedStatus[_lookupKeyHeli] - 10
|
||||
self.landedStatus[_lookupKeyHeli] = _time
|
||||
end
|
||||
if _time <= 0 or _distance < self.loadDistance then
|
||||
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, bugger!", self.messageTime, true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true)
|
||||
return true
|
||||
else
|
||||
self.landedStatus[_lookupKeyHeli] = nil
|
||||
@ -1211,7 +1234,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
else
|
||||
if (_distance < self.loadDistance) then
|
||||
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, honk!", self.messageTime, true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true)
|
||||
return true
|
||||
else
|
||||
self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
|
||||
@ -1252,7 +1275,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true)
|
||||
else
|
||||
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, noob!", self.messageTime, true)
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true)
|
||||
return true
|
||||
else
|
||||
self.hoverStatus[_lookupKeyHeli] = nil
|
||||
@ -1281,36 +1304,6 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Check if group not KIA.
|
||||
-- @param #CSAR self
|
||||
-- @param Wrapper.Group#GROUP _woundedGroup
|
||||
-- @param #string _woundedGroupName
|
||||
-- @param Wrapper.Unit#UNIT _heliUnit
|
||||
-- @param #string _heliName
|
||||
-- @return #boolean Outcome
|
||||
function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName)
|
||||
self:T(self.lid .. " _CheckGroupNotKIA")
|
||||
-- check if unit has died or been picked up
|
||||
local inTransit = false
|
||||
if _woundedGroup and _heliUnit then
|
||||
for _currentHeli, _groups in pairs(self.inTransitGroups) do
|
||||
if _groups[_woundedGroupName] then
|
||||
inTransit = true
|
||||
self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, self.messageTime)
|
||||
break
|
||||
end -- end name check
|
||||
end -- end loop
|
||||
if not inTransit then
|
||||
-- KIA
|
||||
self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, self.messageTime)
|
||||
end
|
||||
--stops the message being displayed again
|
||||
self:_RemoveNameFromDownedPilots(_woundedGroupName)
|
||||
end
|
||||
--continue
|
||||
return inTransit
|
||||
end
|
||||
|
||||
--- (Internal) Monitor in-flight returning groups.
|
||||
-- @param #CSAR self
|
||||
-- @param #string heliname Heli name
|
||||
@ -1339,8 +1332,12 @@ function CSAR:_ScheduledSARFlight(heliname,groupname)
|
||||
end
|
||||
|
||||
if _dist < 200 and _heliUnit:InAir() == false then
|
||||
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(heliname) then
|
||||
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true)
|
||||
else
|
||||
self:_RescuePilots(_heliUnit)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--queue up
|
||||
@ -1428,12 +1425,8 @@ function CSAR:_GetPositionOfWounded(_woundedGroup)
|
||||
_coordinatesText = _coordinate:ToStringLLDMS()
|
||||
elseif self.coordtype == 2 then -- MGRS
|
||||
_coordinatesText = _coordinate:ToStringMGRS()
|
||||
elseif self.coordtype == 3 then -- Bullseye Imperial
|
||||
local Settings = _SETTINGS:SetImperial()
|
||||
_coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings)
|
||||
else -- Bullseye Metric --(medevac.coordtype == 4)
|
||||
local Settings = _SETTINGS:SetMetric()
|
||||
_coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings)
|
||||
else -- Bullseye Metric --(medevac.coordtype == 4 or 3)
|
||||
_coordinatesText = _coordinate:ToStringBULLS(self.coalition)
|
||||
end
|
||||
end
|
||||
return _coordinatesText
|
||||
@ -1461,18 +1454,17 @@ function CSAR:_DisplayActiveSAR(_unitName)
|
||||
self:T({Table=_value})
|
||||
--local _woundedGroup = GROUP:FindByName(_groupName)
|
||||
local _woundedGroup = _value.group
|
||||
if _woundedGroup then
|
||||
if _woundedGroup and _value.alive then
|
||||
local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup)
|
||||
local _helicoord = _heli:GetCoordinate()
|
||||
local _woundcoord = _woundedGroup:GetCoordinate()
|
||||
local _distance = self:_GetDistance(_helicoord, _woundcoord)
|
||||
self:T({_distance = _distance})
|
||||
-- change distance to miles if self.coordtype < 4
|
||||
local distancetext = ""
|
||||
if self.coordtype < 4 then
|
||||
distancetext = string.format("%.3fnm",UTILS.MetersToNM(_distance))
|
||||
if _SETTINGS:IsImperial() then
|
||||
distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance))
|
||||
else
|
||||
distancetext = string.format("%.3fkm", _distance/1000.0)
|
||||
distancetext = string.format("%.1fkm", _distance/1000.0)
|
||||
end
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) })
|
||||
end
|
||||
@ -1537,15 +1529,16 @@ function CSAR:_SignalFlare(_unitName)
|
||||
end
|
||||
|
||||
local _closest = self:_GetClosestDownedPilot(_heli)
|
||||
|
||||
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then
|
||||
local smokedist = 8000
|
||||
if self.approachdist_far > smokedist then smokedist = self.approachdist_far end
|
||||
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then
|
||||
|
||||
local _clockDir = self:_GetClockDirection(_heli, _closest.pilot)
|
||||
local _distance = 0
|
||||
if self.coordtype < 4 then
|
||||
_distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance))
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
|
||||
else
|
||||
_distance = string.format("%.3fkm",_closest.distance)
|
||||
_distance = string.format("%.1fkm",_closest.distance)
|
||||
end
|
||||
local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance)
|
||||
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true)
|
||||
@ -1553,11 +1546,13 @@ function CSAR:_SignalFlare(_unitName)
|
||||
local _coord = _closest.pilot:GetCoordinate()
|
||||
_coord:FlareRed(_clockDir)
|
||||
else
|
||||
local disttext = "4.3nm"
|
||||
if self.coordtype == 4 then
|
||||
disttext = "8km"
|
||||
end
|
||||
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime)
|
||||
local _distance = smokedist
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist))
|
||||
else
|
||||
_distance = string.format("%.1fkm",smokedist/1000)
|
||||
end
|
||||
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime)
|
||||
end
|
||||
return self
|
||||
end
|
||||
@ -1589,14 +1584,16 @@ function CSAR:_Reqsmoke( _unitName )
|
||||
if _heli == nil then
|
||||
return
|
||||
end
|
||||
local smokedist = 8000
|
||||
if smokedist < self.approachdist_far then smokedist = self.approachdist_far end
|
||||
local _closest = self:_GetClosestDownedPilot(_heli)
|
||||
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then
|
||||
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then
|
||||
local _clockDir = self:_GetClockDirection(_heli, _closest.pilot)
|
||||
local _distance = 0
|
||||
if self.coordtype < 4 then
|
||||
_distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance))
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
|
||||
else
|
||||
_distance = string.format("%.3fkm",_closest.distance)
|
||||
_distance = string.format("%.1fkm",_closest.distance)
|
||||
end
|
||||
local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance)
|
||||
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true)
|
||||
@ -1604,11 +1601,13 @@ function CSAR:_Reqsmoke( _unitName )
|
||||
local color = self.smokecolor
|
||||
_coord:Smoke(color)
|
||||
else
|
||||
local disttext = "4.3nm"
|
||||
if self.coordtype == 4 then
|
||||
disttext = "8km"
|
||||
end
|
||||
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime)
|
||||
local _distance = 0
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist))
|
||||
else
|
||||
_distance = string.format("%.1fkm",smokedist/1000)
|
||||
end
|
||||
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime)
|
||||
end
|
||||
return self
|
||||
end
|
||||
@ -1753,70 +1752,10 @@ end
|
||||
-- @param #CSAR self
|
||||
function CSAR:_GenerateVHFrequencies()
|
||||
self:T(self.lid .. " _GenerateVHFrequencies")
|
||||
local _skipFrequencies = self.SkipFrequencies
|
||||
--local _skipFrequencies = self.SkipFrequencies
|
||||
|
||||
local FreeVHFFrequencies = {}
|
||||
local UsedVHFFrequencies = {}
|
||||
|
||||
-- first range
|
||||
local _start = 200000
|
||||
while _start < 400000 do
|
||||
|
||||
-- skip existing NDB frequencies
|
||||
local _found = false
|
||||
for _, value in pairs(_skipFrequencies) do
|
||||
if value * 1000 == _start then
|
||||
_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if _found == false then
|
||||
table.insert(FreeVHFFrequencies, _start)
|
||||
end
|
||||
|
||||
_start = _start + 10000
|
||||
end
|
||||
|
||||
-- second range
|
||||
_start = 400000
|
||||
while _start < 850000 do
|
||||
|
||||
-- skip existing NDB frequencies
|
||||
local _found = false
|
||||
for _, value in pairs(_skipFrequencies) do
|
||||
if value * 1000 == _start then
|
||||
_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if _found == false then
|
||||
table.insert(FreeVHFFrequencies, _start)
|
||||
end
|
||||
|
||||
_start = _start + 10000
|
||||
end
|
||||
|
||||
-- third range
|
||||
_start = 850000
|
||||
while _start <= 999000 do -- updated for Gazelle
|
||||
|
||||
-- skip existing NDB frequencies
|
||||
local _found = false
|
||||
for _, value in pairs(_skipFrequencies) do
|
||||
if value * 1000 == _start then
|
||||
_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if _found == false then
|
||||
table.insert(FreeVHFFrequencies, _start)
|
||||
end
|
||||
|
||||
_start = _start + 50000
|
||||
end
|
||||
FreeVHFFrequencies = UTILS.GenerateVHFrequencies()
|
||||
self.FreeVHFFrequencies = FreeVHFFrequencies
|
||||
return self
|
||||
end
|
||||
@ -1853,7 +1792,8 @@ function CSAR:_GetClockDirection(_heli, _group)
|
||||
if _heading then
|
||||
local Aspect = Angle - _heading
|
||||
if Aspect == 0 then Aspect = 360 end
|
||||
clock = math.floor(Aspect / 30)
|
||||
--clock = math.floor(Aspect / 30)
|
||||
clock = math.abs(UTILS.Round((Aspect / 30),0))
|
||||
if clock == 0 then clock = 12 end
|
||||
end
|
||||
return clock
|
||||
@ -1913,8 +1853,8 @@ function CSAR:_CountActiveDownedPilots()
|
||||
self:T(self.lid .. " _CountActiveDownedPilots")
|
||||
local PilotsInFieldN = 0
|
||||
for _, _unitName in pairs(self.downedPilots) do
|
||||
self:T({_unitName})
|
||||
if _unitName.name ~= nil then
|
||||
self:T({_unitName.desc})
|
||||
if _unitName.alive == true then
|
||||
PilotsInFieldN = PilotsInFieldN + 1
|
||||
end
|
||||
end
|
||||
@ -1965,6 +1905,26 @@ function CSAR:onafterStart(From, Event, To)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function called before Status() event.
|
||||
-- @param #CSAR self
|
||||
function CSAR:_CheckDownedPilotTable()
|
||||
local pilots = self.downedPilots
|
||||
for _,_entry in pairs (pilots) do
|
||||
self:T("Checking for " .. _entry.name)
|
||||
self:T({entry=_entry})
|
||||
local group = _entry.group
|
||||
if not group:IsAlive() then
|
||||
self:T("Group is dead")
|
||||
if _entry.alive == true then
|
||||
self:T("Switching .alive to false")
|
||||
self:__KIA(1,_entry.desc)
|
||||
self:_RemoveNameFromDownedPilots(_entry.name,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function called before Status() event.
|
||||
-- @param #CSAR self.
|
||||
-- @param #string From From state.
|
||||
@ -1975,15 +1935,18 @@ function CSAR:onbeforeStatus(From, Event, To)
|
||||
-- housekeeping
|
||||
self:_AddMedevacMenuItem()
|
||||
self:_RefreshRadioBeacons()
|
||||
self:_CheckDownedPilotTable()
|
||||
for _,_sar in pairs (self.csarUnits) do
|
||||
local PilotTable = self.downedPilots
|
||||
for _,_entry in pairs (PilotTable) do
|
||||
local entry = _entry -- #CSAR.DownedPilot
|
||||
local name = entry.name
|
||||
local timestamp = entry.timestamp or 0
|
||||
local now = timer.getAbsTime()
|
||||
if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10.
|
||||
self:_CheckWoundedGroupStatus(_sar,name)
|
||||
if _entry.alive then
|
||||
local entry = _entry -- #CSAR.DownedPilot
|
||||
local name = entry.name
|
||||
local timestamp = entry.timestamp or 0
|
||||
local now = timer.getAbsTime()
|
||||
if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10.
|
||||
self:_CheckWoundedGroupStatus(_sar,name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -56,30 +56,30 @@
|
||||
--
|
||||
-- ## set up a detection SET_GROUP
|
||||
--
|
||||
-- `Red_DetectionSetGroup = SET_GROUP:New()`
|
||||
-- `Red_DetectionSetGroup:FilterPrefixes( { "Red EWR" } )`
|
||||
-- `Red_DetectionSetGroup:FilterOnce()`
|
||||
-- `Red_DetectionSetGroup = SET_GROUP:New()`
|
||||
-- `Red_DetectionSetGroup:FilterPrefixes( { "Red EWR" } )`
|
||||
-- `Red_DetectionSetGroup:FilterOnce()`
|
||||
--
|
||||
-- ## New Intel type detection for the red side, logname "KGB"
|
||||
--
|
||||
-- `RedIntel = INTEL:New(Red_DetectionSetGroup,"red","KGB")`
|
||||
-- `RedIntel:SetClusterAnalysis(true,true)`
|
||||
-- `RedIntel:SetVerbosity(2)`
|
||||
-- `RedIntel:Start()`
|
||||
-- `RedIntel = INTEL:New(Red_DetectionSetGroup,"red","KGB")`
|
||||
-- `RedIntel:SetClusterAnalysis(true,true)`
|
||||
-- `RedIntel:SetVerbosity(2)`
|
||||
-- `RedIntel:__Start(2)`
|
||||
--
|
||||
-- ## Hook into new contacts found
|
||||
--
|
||||
-- `function RedIntel:OnAfterNewContact(From, Event, To, Contact)`
|
||||
-- `local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown")`
|
||||
-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()`
|
||||
-- `end`
|
||||
-- `function RedIntel:OnAfterNewContact(From, Event, To, Contact)`
|
||||
-- `local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown")`
|
||||
-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()`
|
||||
-- `end`
|
||||
--
|
||||
-- ## And/or new clusters found
|
||||
--
|
||||
-- `function RedIntel:OnAfterNewCluster(From, Event, To, Contact, Cluster)`
|
||||
-- `local text = string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)`
|
||||
-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()`
|
||||
-- `end`
|
||||
-- `function RedIntel:OnAfterNewCluster(From, Event, To, Contact, Cluster)`
|
||||
-- `local text = string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)`
|
||||
-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()`
|
||||
-- `end`
|
||||
--
|
||||
--
|
||||
-- @field #INTEL
|
||||
@ -116,7 +116,7 @@ INTEL = {
|
||||
-- @field #number speed Last known speed in m/s.
|
||||
-- @field #boolean isship
|
||||
-- @field #boolean ishelo
|
||||
-- @field #boolean isgrund
|
||||
-- @field #boolean isground
|
||||
-- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this contact
|
||||
-- @field #string recce The name of the recce unit that detected this contact
|
||||
|
||||
@ -135,13 +135,13 @@ INTEL = {
|
||||
|
||||
--- INTEL class version.
|
||||
-- @field #string version
|
||||
INTEL.version="0.2.2"
|
||||
INTEL.version="0.2.6"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Filter detection methods.
|
||||
-- DONE: Filter detection methods.
|
||||
-- TODO: process detected set asynchroniously for better performance.
|
||||
-- DONE: Accept zones.
|
||||
-- DONE: Reject zones.
|
||||
@ -203,7 +203,14 @@ function INTEL:New(DetectionSet, Coalition, Alias)
|
||||
self.alias="CIA"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.DetectVisual = true
|
||||
self.DetectOptical = true
|
||||
self.DetectRadar = true
|
||||
self.DetectIRST = true
|
||||
self.DetectRWR = true
|
||||
self.DetectDLINK = true
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("INTEL %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
||||
@ -222,7 +229,8 @@ function INTEL:New(DetectionSet, Coalition, Alias)
|
||||
self:AddTransition("*", "LostContact", "*") -- Contact could not be detected any more.
|
||||
|
||||
self:AddTransition("*", "NewCluster", "*") -- New cluster has been detected.
|
||||
self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more.
|
||||
self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more.
|
||||
self:AddTransition("*", "Stop", "Stopped")
|
||||
|
||||
-- Defaults
|
||||
self:SetForgetTime()
|
||||
@ -475,6 +483,47 @@ function INTEL:SetClusterRadius(radius)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set detection types for this #INTEL - all default to true.
|
||||
-- @param #INTEL self
|
||||
-- @param #boolean DetectVisual Visual detection
|
||||
-- @param #boolean DetectOptical Optical detection
|
||||
-- @param #boolean DetectRadar Radar detection
|
||||
-- @param #boolean DetectIRST IRST detection
|
||||
-- @param #boolean DetectRWR RWR detection
|
||||
-- @param #boolean DetectDLINK Data link detection
|
||||
-- @return self
|
||||
function INTEL:SetDetectionTypes(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK)
|
||||
self.DetectVisual = DetectVisual and true
|
||||
self.DetectOptical = DetectOptical and true
|
||||
self.DetectRadar = DetectRadar and true
|
||||
self.DetectIRST = DetectIRST and true
|
||||
self.DetectRWR = DetectRWR and true
|
||||
self.DetectDLINK = DetectDLINK and true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get table of #INTEL.Contact objects
|
||||
-- @param #INTEL self
|
||||
-- @return #table Contacts or nil if not running
|
||||
function INTEL:GetContactTable()
|
||||
if self:Is("Running") then
|
||||
return self.Contacts
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get table of #INTEL.Cluster objects
|
||||
-- @param #INTEL self
|
||||
-- @return #table Clusters or nil if not running
|
||||
function INTEL:GetClusterTable()
|
||||
if self:Is("Running") and self.clusteranalysis then
|
||||
return self.Clusters
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -530,7 +579,7 @@ function INTEL:onafterStatus(From, Event, To)
|
||||
text=text..string.format("\n- %s (%s): %s, units=%d, T=%d sec", contact.categoryname, contact.attribute, contact.groupname, contact.group:CountAliveUnits(), dT)
|
||||
if contact.mission then
|
||||
local mission=contact.mission --Ops.Auftrag#AUFTRAG
|
||||
text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown")
|
||||
text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unknown")
|
||||
end
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
@ -558,14 +607,13 @@ function INTEL:UpdateIntel()
|
||||
local recce=_recce --Wrapper.Unit#UNIT
|
||||
|
||||
-- Get detected units.
|
||||
self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting)
|
||||
self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Filter detection methods?
|
||||
local remove={}
|
||||
for unitname,_unit in pairs(DetectedUnits) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
@ -700,7 +748,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, RecceDetecting)
|
||||
item.velocity=group:GetVelocityVec3()
|
||||
item.speed=group:GetVelocityMPS()
|
||||
item.recce=RecceDetecting[groupname]
|
||||
self:T(string.format("%s group detect by %s/%s", groupname, RecceDetecting[groupname] or "unknonw", item.recce or "unknown"))
|
||||
self:T(string.format("%s group detect by %s/%s", groupname, RecceDetecting[groupname] or "unknown", item.recce or "unknown"))
|
||||
-- Add contact to table.
|
||||
self:AddContact(item)
|
||||
|
||||
@ -728,7 +776,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, RecceDetecting)
|
||||
|
||||
end
|
||||
|
||||
--- Return the detected target groups of the controllable as a @{SET_GROUP}.
|
||||
--- (Internal) Return the detected target groups of the controllable as a @{SET_GROUP}.
|
||||
-- The optional parametes specify the detection methods that can be applied.
|
||||
-- If no detection method is given, the detection will use all the available methods by default.
|
||||
-- @param #INTEL self
|
||||
@ -815,7 +863,7 @@ function INTEL:onafterLostCluster(From, Event, To, Cluster, Mission)
|
||||
local text = self.lid..string.format("LOST cluster %d", Cluster.index)
|
||||
if Mission then
|
||||
local mission=Mission --Ops.Auftrag#AUFTRAG
|
||||
text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown")
|
||||
text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unknown")
|
||||
end
|
||||
self:T(text)
|
||||
end
|
||||
@ -1391,3 +1439,306 @@ end
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
-- Start INTEL_DLINK
|
||||
----------------------------------------------------------------------------------------------
|
||||
|
||||
--- **Ops_DLink** - Support for Office of Military Intelligence.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Overcome limitations of (non-available) datalinks between ground radars
|
||||
-- * Detect and track contacts consistently across INTEL instances
|
||||
-- * Use FSM events to link functionality into your scripts
|
||||
-- * Easy setup
|
||||
--
|
||||
--- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
|
||||
--- INTEL_DLINK class.
|
||||
-- @type INTEL_DLINK
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #number verbose Make the logging verbose.
|
||||
-- @field #string alias Alias name for logging.
|
||||
-- @field #number cachetime Number of seconds to keep an object.
|
||||
-- @field #number interval Number of seconds between collection runs.
|
||||
-- @field #table contacts Table of Ops.Intelligence#INTEL.Contact contacts.
|
||||
-- @field #table clusters Table of Ops.Intelligence#INTEL.Cluster clusters.
|
||||
-- @field #table contactcoords Table of contacts' Core.Point#COORDINATE objects.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- INTEL_DLINK data aggregator
|
||||
-- @field #INTEL_DLINK
|
||||
INTEL_DLINK = {
|
||||
ClassName = "INTEL_DLINK",
|
||||
verbose = 0,
|
||||
lid = nil,
|
||||
alias = nil,
|
||||
cachetime = 300,
|
||||
interval = 20,
|
||||
contacts = {},
|
||||
clusters = {},
|
||||
contactcoords = {},
|
||||
}
|
||||
|
||||
--- Version string
|
||||
-- @field #string version
|
||||
INTEL_DLINK.version = "0.0.1"
|
||||
|
||||
--- Function to instantiate a new object
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #table Intels Table of Ops.Intelligence#INTEL objects.
|
||||
-- @param #string Alias (optional) Name of this instance. Default "SPECTRE"
|
||||
-- @param #number Interval (optional) When to query #INTEL objects for detected items (default 20 seconds).
|
||||
-- @param #number Cachetime (optional) How long to cache detected items (default 300 seconds).
|
||||
-- @usage Use #INTEL_DLINK if you want to merge data from a number of #INTEL objects into one. This might be useful to simulate a
|
||||
-- Data Link, e.g. for Russian-tech based EWR, realising a Star Topology @{https://en.wikipedia.org/wiki/Network_topology#Star}
|
||||
-- in a basic setup. It will collect the contacts and clusters from the #INTEL objects.
|
||||
-- Contact duplicates are removed. Clusters might contain duplicates (Might fix that later, WIP).
|
||||
--
|
||||
-- Basic setup:
|
||||
-- local datalink = INTEL_DLINK:New({myintel1,myintel2}), "FSB", 20, 300)
|
||||
-- datalink:__Start(2)
|
||||
--
|
||||
-- Add an Intel while running:
|
||||
-- datalink:AddIntel(myintel3)
|
||||
--
|
||||
-- Gather the data:
|
||||
-- datalink:GetContactTable() -- #table of #INTEL.Contact contacts.
|
||||
-- datalink:GetClusterTable() -- #table of #INTEL.Cluster clusters.
|
||||
-- datalink:GetDetectedItemCoordinates() -- #table of contact coordinates, to be compatible with @{Functional.Detection#DETECTION}.
|
||||
--
|
||||
-- Gather data with the event function:
|
||||
-- function datalink:OnAfterCollected(From, Event, To, Contacts, Clusters)
|
||||
-- ... <your code here> ...
|
||||
-- end
|
||||
--
|
||||
function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime)
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #INTEL
|
||||
|
||||
self.intels = Intels or {}
|
||||
self.contacts = {}
|
||||
self.clusters = {}
|
||||
self.contactcoords = {}
|
||||
|
||||
-- Set alias.
|
||||
if Alias then
|
||||
self.alias=tostring(Alias)
|
||||
else
|
||||
self.alias="SPECTRE"
|
||||
end
|
||||
|
||||
-- Cache time
|
||||
self.cachetime = Cachetime or 300
|
||||
|
||||
-- Interval
|
||||
self.interval = Interval or 20
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("INTEL_DLINK %s | ", self.alias)
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
|
||||
self:AddTransition("*", "Collect", "*") -- Collect data.
|
||||
self:AddTransition("*", "Collected", "*") -- Collection of data done.
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
-- Pseudo Functions
|
||||
----------------------------------------------------------------------------------------------
|
||||
--- Triggers the FSM event "Start". Starts the INTEL_DLINK.
|
||||
-- @function [parent=#INTEL_DLINK] Start
|
||||
-- @param #INTEL_DLINK self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the INTEL_DLINK.
|
||||
-- @function [parent=#INTEL_DLINK] __Start
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the INTEL_DLINK.
|
||||
-- @param #INTEL_DLINK self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the INTEL_DLINK.
|
||||
-- @function [parent=#INTEL_DLINK] __Stop
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Collect". Used internally to collect all data.
|
||||
-- @function [parent=#INTEL_DLINK] Collect
|
||||
-- @param #INTEL_DLINK self
|
||||
|
||||
--- Triggers the FSM event "Collect" after a delay.
|
||||
-- @function [parent=#INTEL_DLINK] __Status
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- On After "Collected" event. Data tables have been refreshed.
|
||||
-- @function [parent=#INTEL_DLINK] OnAfterCollected
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #table Contacts Table of #INTEL.Contact Contacts.
|
||||
-- @param #table Clusters Table of #INTEL.Cluster Clusters.
|
||||
|
||||
return self
|
||||
end
|
||||
----------------------------------------------------------------------------------------------
|
||||
-- Helper & User Functions
|
||||
----------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function to add an #INTEL object to the aggregator
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param Ops.Intelligence#INTEL Intel the #INTEL object to add
|
||||
-- @return #INTEL_DLINK self
|
||||
function INTEL_DLINK:AddIntel(Intel)
|
||||
self:T(self.lid .. "AddIntel")
|
||||
if Intel then
|
||||
table.insert(self.intels,Intel)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
----------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function to start the work.
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event triggering this call
|
||||
-- @param #string To The To state
|
||||
-- @return #INTEL_DLINK self
|
||||
function INTEL_DLINK:onafterStart(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
local text = string.format("Version %s started.", self.version)
|
||||
self:I(self.lid .. text)
|
||||
self:__Collect(-math.random(1,10))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to collect data from the various #INTEL
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event triggering this call
|
||||
-- @param #string To The To state
|
||||
-- @return #INTEL_DLINK self
|
||||
function INTEL_DLINK:onbeforeCollect(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
-- run through our #INTEL objects and gather the contacts tables
|
||||
self:T("Contacts Data Gathering")
|
||||
local newcontacts = {}
|
||||
local intels = self.intels -- #table
|
||||
for _,_intel in pairs (intels) do
|
||||
_intel = _intel -- #INTEL
|
||||
if _intel:Is("Running") then
|
||||
local ctable = _intel:GetContactTable() or {} -- #INTEL.Contact
|
||||
for _,_contact in pairs (ctable) do
|
||||
local _ID = string.format("%s-%d",_contact.groupname, _contact.Tdetected)
|
||||
self:T(string.format("Adding %s",_ID))
|
||||
newcontacts[_ID] = _contact
|
||||
end
|
||||
end
|
||||
end
|
||||
-- clean up for stale contacts and dupes
|
||||
self:T("Cleanup")
|
||||
local contacttable = {}
|
||||
local coordtable = {}
|
||||
local TNow = timer.getAbsTime()
|
||||
local Tcache = self.cachetime
|
||||
for _ind, _contact in pairs(newcontacts) do -- #string, #INTEL.Contact
|
||||
if TNow - _contact.Tdetected < Tcache then
|
||||
if (not contacttable[_contact.groupname]) or (contacttable[_contact.groupname] and contacttable[_contact.groupname].Tdetected < _contact.Tdetected) then
|
||||
self:T(string.format("Adding %s",_contact.groupname))
|
||||
contacttable[_contact.groupname] = _contact
|
||||
table.insert(coordtable,_contact.position)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- run through our #INTEL objects and gather the clusters tables
|
||||
self:T("Clusters Data Gathering")
|
||||
local newclusters = {}
|
||||
local intels = self.intels -- #table
|
||||
for _,_intel in pairs (intels) do
|
||||
_intel = _intel -- #INTEL
|
||||
if _intel:Is("Running") then
|
||||
local ctable = _intel:GetClusterTable() or {} -- #INTEL.Cluster
|
||||
for _,_cluster in pairs (ctable) do
|
||||
local _ID = string.format("%s-%d", _intel.alias, _cluster.index)
|
||||
self:T(string.format("Adding %s",_ID))
|
||||
table.insert(newclusters,_cluster)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- update self tables
|
||||
self.contacts = contacttable
|
||||
self.contactcoords = coordtable
|
||||
self.clusters = newclusters
|
||||
self:__Collected(1, contacttable, newclusters) -- make table available via FSM Event
|
||||
-- schedule next round
|
||||
local interv = self.interval * -1
|
||||
self:__Collect(interv)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function called after collection is done
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event triggering this call
|
||||
-- @param #string To The To state
|
||||
-- @param #table Contacts The table of collected #INTEL.Contact contacts
|
||||
-- @param #table Clusters The table of collected #INTEL.Cluster clusters
|
||||
-- @return #INTEL_DLINK self
|
||||
function INTEL_DLINK:onbeforeCollected(From, Event, To, Contacts, Clusters)
|
||||
self:T({From, Event, To})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to stop
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event triggering this call
|
||||
-- @param #string To The To state
|
||||
-- @return #INTEL_DLINK self
|
||||
function INTEL_DLINK:onafterStop(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
local text = string.format("Version %s stopped.", self.version)
|
||||
self:I(self.lid .. text)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to query the detected contacts
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @return #table Table of #INTEL.Contact contacts
|
||||
function INTEL_DLINK:GetContactTable()
|
||||
self:T(self.lid .. "GetContactTable")
|
||||
return self.contacts
|
||||
end
|
||||
|
||||
--- Function to query the detected clusters
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @return #table Table of #INTEL.Cluster clusters
|
||||
function INTEL_DLINK:GetClusterTable()
|
||||
self:T(self.lid .. "GetClusterTable")
|
||||
return self.clusters
|
||||
end
|
||||
|
||||
--- Function to query the detected contact coordinates
|
||||
-- @param #INTEL_DLINK self
|
||||
-- @return #table Table of the contacts' Core.Point#COORDINATE objects.
|
||||
function INTEL_DLINK:GetDetectedItemCoordinates()
|
||||
self:T(self.lid .. "GetDetectedItemCoordinates")
|
||||
return self.contactcoords
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
-- End INTEL_DLINK
|
||||
----------------------------------------------------------------------------------------------
|
||||
|
||||
@ -1668,4 +1668,149 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
||||
end -- nil
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Function to generate valid FM frequencies in mHz for radio beacons (FM).
|
||||
-- @return #table Table of frequencies.
|
||||
function UTILS.GenerateFMFrequencies()
|
||||
local FreeFMFrequencies = {}
|
||||
for _first = 3, 7 do
|
||||
for _second = 0, 5 do
|
||||
for _third = 0, 9 do
|
||||
local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit
|
||||
table.insert(FreeFMFrequencies, _frequency)
|
||||
end
|
||||
end
|
||||
end
|
||||
return FreeFMFrequencies
|
||||
end
|
||||
|
||||
--- Function to generate valid VHF frequencies in kHz for radio beacons (FM).
|
||||
-- @return #table VHFrequencies
|
||||
function UTILS.GenerateVHFrequencies()
|
||||
|
||||
-- known and sorted map-wise NDBs in kHz
|
||||
local _skipFrequencies = {
|
||||
214,274,291.5,295,297.5,
|
||||
300.5,304,307,309.5,311,312,312.5,316,
|
||||
320,324,328,329,330,332,336,337,
|
||||
342,343,348,351,352,353,358,
|
||||
363,365,368,372.5,374,
|
||||
380,381,384,385,389,395,396,
|
||||
414,420,430,432,435,440,450,455,462,470,485,
|
||||
507,515,520,525,528,540,550,560,570,577,580,
|
||||
602,625,641,662,670,680,682,690,
|
||||
705,720,722,730,735,740,745,750,770,795,
|
||||
822,830,862,866,
|
||||
905,907,920,935,942,950,995,
|
||||
1000,1025,1030,1050,1065,1116,1175,1182,1210
|
||||
}
|
||||
|
||||
local FreeVHFFrequencies = {}
|
||||
|
||||
-- first range
|
||||
local _start = 200000
|
||||
while _start < 400000 do
|
||||
|
||||
-- skip existing NDB frequencies#
|
||||
local _found = false
|
||||
for _, value in pairs(_skipFrequencies) do
|
||||
if value * 1000 == _start then
|
||||
_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if _found == false then
|
||||
table.insert(FreeVHFFrequencies, _start)
|
||||
end
|
||||
_start = _start + 10000
|
||||
end
|
||||
|
||||
-- second range
|
||||
_start = 400000
|
||||
while _start < 850000 do
|
||||
-- skip existing NDB frequencies
|
||||
local _found = false
|
||||
for _, value in pairs(_skipFrequencies) do
|
||||
if value * 1000 == _start then
|
||||
_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if _found == false then
|
||||
table.insert(FreeVHFFrequencies, _start)
|
||||
end
|
||||
_start = _start + 10000
|
||||
end
|
||||
|
||||
-- third range
|
||||
_start = 850000
|
||||
while _start <= 999000 do -- adjusted for Gazelle
|
||||
-- skip existing NDB frequencies
|
||||
local _found = false
|
||||
for _, value in pairs(_skipFrequencies) do
|
||||
if value * 1000 == _start then
|
||||
_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if _found == false then
|
||||
table.insert(FreeVHFFrequencies, _start)
|
||||
end
|
||||
_start = _start + 50000
|
||||
end
|
||||
|
||||
return FreeVHFFrequencies
|
||||
end
|
||||
|
||||
--- Function to generate valid UHF Frequencies in mHz (AM).
|
||||
-- @return #table UHF Frequencies
|
||||
function UTILS.GenerateUHFrequencies()
|
||||
|
||||
local FreeUHFFrequencies = {}
|
||||
local _start = 220000000
|
||||
|
||||
while _start < 399000000 do
|
||||
table.insert(FreeUHFFrequencies, _start)
|
||||
_start = _start + 500000
|
||||
end
|
||||
|
||||
return FreeUHFFrequencies
|
||||
end
|
||||
|
||||
--- Function to generate valid laser codes for JTAC.
|
||||
-- @return #table Laser Codes.
|
||||
function UTILS.GenerateLaserCodes()
|
||||
local jtacGeneratedLaserCodes = {}
|
||||
|
||||
-- helper function
|
||||
local function ContainsDigit(_number, _numberToFind)
|
||||
local _thisNumber = _number
|
||||
local _thisDigit = 0
|
||||
while _thisNumber ~= 0 do
|
||||
_thisDigit = _thisNumber % 10
|
||||
_thisNumber = math.floor(_thisNumber / 10)
|
||||
if _thisDigit == _numberToFind then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- generate list of laser codes
|
||||
local _code = 1111
|
||||
local _count = 1
|
||||
while _code < 1777 and _count < 30 do
|
||||
while true do
|
||||
_code = _code + 1
|
||||
if not self:_ContainsDigit(_code, 8)
|
||||
and not ContainsDigit(_code, 9)
|
||||
and not ContainsDigit(_code, 0) then
|
||||
table.insert(jtacGeneratedLaserCodes, _code)
|
||||
break
|
||||
end
|
||||
end
|
||||
_count = _count + 1
|
||||
end
|
||||
return jtacGeneratedLaserCodes
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user