Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2021-07-23 18:49:35 +02:00
commit 4ffdf9e536
8 changed files with 1924 additions and 977 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
-----------------------------------------------------------------------
-----------------------------------------------------------------------

View File

@ -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.

View File

@ -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

View File

@ -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
----------------------------------------------------------------------------------------------

View File

@ -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