Applevangelist c01b94518f
Update to Mantis 0.2.5
Changelog
-- changed HQ to be a GROUP object instead of creating a CommandCenter. Allows user to use their own CC.
-- added functionality to autorelocate HQ and EWR group(s) at random intervals between 30 and 60 mins
-- added verbosity option for logging
-- tidied up documentation
2020-12-17 11:16:30 +01:00

475 lines
18 KiB
Lua

--- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses
--
-- ===
--
-- **MANTIS** - Moose derived Modular, Automatic and Network capable Targeting and Interception System
-- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy
-- Leverage evasiveness from SEAD
-- Leverage attack range setup added by DCS in 11/20
--
-- ===
--
-- ## Missions:
--
-- ### [MANTIS - Modular, Automatic and Network capable Targeting and Interception System](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MTS%20-%20Mantis/MTS-010%20-%20Basic%20Mantis%20Demo)
--
-- ===
--
-- ### Author : **applevangelist **
--
-- @module Functional.Mantis
-- @image Functional.Mantis.jpg
-- Date: Dec 2020
-------------------------------------------------------------------------
--- **MANTIS** class, extends @{Core.Base#BASE}
-- @type MANTIS
-- @field #string Classname
-- @field #string name Name of this Mantis
-- @field #string SAM_Templates_Prefix Prefix to build the #GROUP_SET for SAM sites
-- @field @{Core.Set#GROUP_SET} SAM_Group The SAM #GROUP_SET
-- @field #string EWR_Templates_Prefix Prefix to build the #GROUP_SET for EWR group
-- @field @{Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET
-- @field #string SEAD_Template_CC The ME name of the HQ object
-- @field @{Wrapper.Group#GROUP} SEAD_CC The #GROUP object of the HQ
-- @field #table SAM_Table Table of SAM sites
-- @field #string lid Prefix for logging
-- @field @{Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object
-- @field #boolean debug Switch on extra messages
-- @field #boolean verbose Switch on extra logging
-- @field #number checkradius Radius of the SAM sites
-- @field #number grouping Radius to group detected objects
-- @field #number acceptrange Radius of the EWR detection
-- @field #number detectinterval Interval in seconds for the target detection
-- @field #number engagerange Firing engage range of the SAMs, see [https://wiki.hoggitworld.com/view/DCS_option_engagementRange]
-- @field #boolean autorelocate Relocate HQ and EWR groups in random intervals. Note: You need to select units for this which are *actually mobile*
-- @extends @{Core.Base#BASE}
--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat
--
-- Simple Class for a more intelligent Air Defense System
--
-- #MANTIS
-- Moose derived Modular, Automatic and Network capable Targeting and Interception System.
-- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy.
-- Leverage evasiveness from @{Functional.Sead#SEAD}.
-- Leverage attack range setup added by DCS in 11/20.
--
-- Set up your SAM sites in the mission editor. Name the groups with common prefix like "Red SAM".
-- Set up your EWR system in the mission editor. Name the groups with common prefix like "Red EWR". Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc.
-- [optional] Set up your HQ. Can be any group, e.g. a command vehicle.
--
-- Start up your MANTIS
--
-- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)`
--
-- [optional] Use
-- * `MANTIS:SetEWRGrouping(radius)`
-- * `MANTIS:SetEWRRange(radius)`
-- * `MANTIS:SetSAMRadius(radius)`
-- * `MANTIS:SetDetectInterval(interval)`
-- * `MANTIS:SetAutoRelocate(hq, ewr)`
-- to fine-tune your setup.
--
-- `myredmantis:Start()`
--
--
-- @field #MANTIS
MANTIS = {
ClassName = "MANTIS",
name = "mymantis",
SAM_Templates_Prefix = "",
SAM_Group = nil,
EWR_Templates_Prefix = "",
EWR_Group = nil,
SEAD_Template_CC = "",
SEAD_CC = nil,
SAM_Table = {},
lid = "",
Detection = nil,
debug = false,
checkradius = 25000,
grouping = 5000,
acceptrange = 80000,
detectinterval = 30,
engagerange = 75,
autorelocate = false,
verbose = false
}
-----------------------------------------------------------------------
-- MANTIS System
-----------------------------------------------------------------------
do
--- Function instantiate new class
--@param #MANTIS self
--@param #string name Name of this MANTIS for reporting
--@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..."
--@param #string ewrprefix Prefixes for the EWR and AWACS groups from the ME, e.g. all groups starting with "Red EWR..."
--@param #string hq Group name of your HQ (optional)
--@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral"
--@param #boolean dynamic Use constant (true) filtering or just filter once (false, default)
--@return #MANTIS self
function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic)
-- TODO: Create some user functions for these
-- TODO: Make HQ useful
-- TODO: Set SAMs to auto if EWR dies
self.name = name or "mymantis"
self.SAM_Templates_Prefix = samprefix or "Red SAM"
self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
self.SEAD_Template_CC = hq or nil
self.Coalition = coaltion or "red"
self.SAM_Table = {}
self.dynamic = dynamic or false
self.checkradius = 25000
self.grouping = 5000
self.acceptrange = 80000
self.detectinterval = 30
self.engagerange = 75
self.autorelocate = false
self.autorelocateunits = { HQ = false, EWR = false}
self.verbose = false
-- @field #string version
self.version="0.2.5"
env.info(string.format("***** Starting MANTIS Version %s *****", self.version))
-- Set the string id for output to DCS.log file.
self.lid=string.format("MANTIS %s | ", self.name)
-- Debug trace.
if self.debug then
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
--BASE:TraceClass("SEAD")
BASE:TraceLevel(1)
end
if self.dynamic then
-- Set SAM SET_GROUP
self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart()
-- Set EWR SET_GROUP
self.EWR_Group = SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterStart()
else
-- Set SAM SET_GROUP
self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
-- Set EWR SET_GROUP
self.EWR_Group = SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterOnce()
end
-- set up CC
if self.SEAD_Template_CC then
self.SEAD_CC = GROUP:FindByName(self.SEAD_Template_CC)
--self.SEAD_CC = COMMANDCENTER:New(GROUP:FindByName(self.SEAD_Template_CC),self.SEAD_Template_CC)
end
-- Inherit everything from BASE class.
local self = BASE:Inherit(self, BASE:New()) -- #MANTIS
return self
end
-----------------------------------------------------------------------
-- MANTIS helper functions
-----------------------------------------------------------------------
--- [internal] Function to get the self.SAM_Table
-- @param #MANTIS self
-- @return #table table
function MANTIS:_GetSAMTable()
return self.SAM_Table
end
--- [internal] Function to set the self.SAM_Table
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:_SetSAMTable(table)
self.SAM_Table = table
return self
end
--- Function to set the grouping radius of the detection in meters
-- @param #MANTIS self
-- @param #number radius Radius upon which detected objects will be grouped
function MANTIS:SetEWRGrouping(radius)
local radius = radius or 5000
self.grouping = radius
end
--- Function to set the detection radius of the EWR in meters
-- @param #MANTIS self
-- @param #number radius Radius of the EWR detection zone
function MANTIS:SetEWRRange(radius)
local radius = radius or 80000
self.acceptrange = radius
end
--- Function to set switch-on/off zone for the SAM sites in meters
-- @param #MANTIS self
-- @param #number radius Radius of the firing zone
function MANTIS:SetSAMRadius(radius)
local radius = radius or 25000
self.checkradius = radius
end
--- Function to set SAM firing engage range, 0-100 percent, e.g. 75
-- @param #MANTIS self
-- @param #number range Percent of the max fire range
function MANTIS:SetSAMRadius(range)
local range = range or 75
if range < 0 or range > 100 then
range = 75
end
self.engagerange = range
end
--- Function to set switch-on/off the debug state
-- @param #MANTIS self
-- @param #boolean onoff Set true to switch on
function MANTIS:Debug(onoff)
local onoff = onoff or false
self.debug = onoff
end
--- Function to get the HQ object for further use
-- @param #MANTIS self
-- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist
function MANTIS:GetCommandCenter()
if self.SEAD_CC then
return self.SEAD_CC
else
return nil
end
end
--- Function to set the detection interval
-- @param #MANTIS self
-- @param #number interval The interval in seconds
function MANTIS:SetDetectInterval(interval)
local interval = interval or 30
self.detectinterval = interval
end
--- Function to set autorelocation for HQ and EWR objects. Note: Units must be actually mobile in DCS!
-- @param #MANTIS self
-- @param #boolean hq If true, will relocate HQ object
-- @param #boolean ewr If true, will relocate EWR objects
function MANTIS:SetAutoRelocate(hq, ewr)
self:F({hq, ewr})
local hqrel = hq or false
local ewrel = ewr or false
if hqrel or ewrel then
self.autorelocate = true
self.autorelocateunits = { HQ = hqrel, EWR = ewrel }
self:T({self.autorelocate, self.autorelocateunits})
end
end
--- [Internal] Function to execute the relocation
-- @param #MANTIS self
function MANTIS:_RelocateGroups()
self:T(self.lid.." Relocating Groups")
local text = self.lid.." Relocating Groups"
local m= MESSAGE:New(text,15,"MANTIS",true):ToAllIf(self.debug)
if self.verbose then env.info(text) end
if self.autorelocate then
-- relocate HQ
if self.autorelocateunits.HQ and self.SEAD_CC then --only relocate if HQ exists
local _hqgrp = self.SEAD_CC
self:T(self.lid.." Relocating HQ")
local text = self.lid.." Relocating HQ"
local m= MESSAGE:New(text,15,"MANTIS"):ToAll()
_hqgrp:RelocateGroundRandomInRadius(20,500,true,true)
end
--relocate EWR
-- TODO: maybe dependent on AlarmState? Observed: SA11 SR only relocates if no objects in reach
if self.autorelocateunits.EWR then
-- get EWR Group
local EWR_GRP = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce()
local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP
for _,_grp in pairs (EWR_Grps) do
if _grp:IsGround() then
self:T(self.lid.." Relocating EWR ".._grp:GetName())
local text = self.lid.." Relocating EWR ".._grp:GetName()
local m= MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(text) end
_grp:RelocateGroundRandomInRadius(20,500,true,true)
end
end
end
end
end
--- Function to check if any object is in the given SAM zone
-- @param #MANTIS self
-- @param #table dectset Table of coordinates of detected items
-- @param samcoordinate Core.Point#COORDINATE Coordinate object.
-- @return #boolean True if in any zone, else false
function MANTIS:CheckObjectInZone(dectset, samcoordinate)
self:F(self.lid.."CheckObjectInZone Called")
-- check if non of the coordinate is in the given defense zone
local radius = self.checkradius
local set = dectset
for _,_coord in pairs (set) do
local coord = _coord -- get current coord to check
-- output for cross-check
local dectstring = coord:ToStringLLDMS()
local samstring = samcoordinate:ToStringLLDMS()
local targetdistance = samcoordinate:DistanceFromPointVec2(coord)
local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring)
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
if self.verbose then env.info(self.lid..text) end
-- end output to cross-check
if targetdistance <= radius then
return true
end
end
return false
end
--- Function to start the detection via EWR groups
-- @param #MANTIS self
-- @return Functional.Detection #DETECTION_AREAS The running detection set
function MANTIS:StartDetection()
self:F(self.lid.."Starting Detection")
-- start detection
local groupset = self.EWR_Group
local grouping = self.grouping or 5000
local acceptrange = self.acceptrange or 80000
local interval = self.detectinterval or 60
--@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object
_MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones
_MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER })
_MANTISdetection:SetAcceptRange(acceptrange)
_MANTISdetection:SetRefreshTimeInterval(interval)
_MANTISdetection:Start()
function _MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem)
--BASE:I( { From, Event, To, DetectedItem })
local debug = false
if DetectedItem.IsDetected and debug then
local Coordinate = DetectedItem.Coordinate -- Core.Point#COORDINATE
local text = "MANTIS: Detection at "..Coordinate:ToStringLLDMS()
local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
end
end
return _MANTISdetection
end
--- Function to set the SAM start state
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:SetSAMStartState()
self:F(self.lid.."Setting SAM Start States")
-- get SAM Group
local SAM_SET = self.SAM_Group
local SAM_Grps = SAM_SET.Set --table of objects
local SAM_Tbl = {} -- table of SAM defense zones
local SEAD_Grps = {} -- table of SAM names to make evasive
local engagerange = self.engagerange -- firing range in % of max
--cycle through groups and set alarm state etc
for _i,_group in pairs (SAM_Grps) do
--for i=1,#SAM_Grps do
local group = _group
group:OptionAlarmStateGreen()
--group:OptionROEHoldFire()
--group:SetAIOn()
group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range
if group:IsGround() then
local grpname = group:GetName()
local grpcoord = group:GetCoordinate()
local grpzone = ZONE_UNIT:New(grpname,group:GetUnit(1),5000) -- defense zone around each SAM site 5000 meters
table.insert( SAM_Tbl, {grpname, grpcoord, grpzone})
table.insert( SEAD_Grps, grpname )
end
end
self.SAM_Table = SAM_Tbl
-- make SAMs evasive
SEAD:New( SEAD_Grps )
return self
end
-----------------------------------------------------------------------
-- MANTIS main functions
-----------------------------------------------------------------------
--- Function to set the SAM start state
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:Start()
self:F(self.lid.."Starting MANTIS")
self:SetSAMStartState()
self.Detection = self:StartDetection()
local function check(detection)
--get detected set
local detset = detection:GetDetectedItemCoordinates()
self:F("Check:", {detset})
-- switch SAMs on/off if (n)one of the detected groups is inside their reach
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates and i.3=zones of SAM sites
for _,_data in pairs (samset) do
local samcoordinate = _data[2]
local name = _data[1]
local samgroup = GROUP:FindByName(name)
if self:CheckObjectInZone(detset, samcoordinate) then --check any target in zone
if samgroup:IsAlive() then
-- switch off SAM
samgroup:OptionAlarmStateRed()
--samgroup:OptionROEWeaponFree()
--samgroup:SetAIOn()
local text = string.format("SAM %s switched to alarm state RED!", name)
local m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(self.lid..text) end
end --end alive
else
if samgroup:IsAlive() then
-- switch off SAM
samgroup:OptionAlarmStateGreen()
--samgroup:OptionROEWeaponFree()
--samgroup:SetAIOn()
local text = string.format("SAM %s switched to alarm state GREEN!", name)
local m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug)
if self.verbose then env.info(self.lid..text) end
end --end alive
end --end check
end --for for loop
end --end function
-- relocation relay function
local function relocate()
self:_RelocateGroups()
end
-- timer to run the system
local interval = self.detectinterval
self.MantisTimer = TIMER:New(check,self.Detection)
self.MantisTimer:Start(5,interval,nil)
-- relocate HQ and EWR
local relointerval = math.random(1800,3600) -- random between 30 and 60 mins
self.MantisReloTimer = TIMER:New(relocate)
self.MantisReloTimer:Start(relointerval,relointerval,nil)
return self
end
--- Function to stop MANTIS
-- @param #MANTIS self
-- @return #MANTIS self
function MANTIS:Stop()
if self.MantisTimer then
self.MantisTimer:Stop()
end
if self.MantisReloTimer then
self.MantisReloTimer:Stop()
end
return self
end
end
-----------------------------------------------------------------------
-- MANTIS end
-----------------------------------------------------------------------