diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index f5093697f..c7cba4443 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -4,8 +4,8 @@ -- -- **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 +-- Automatic mode (default since 0.8) can set-up your SAM site network automatically for you +-- Leverage evasiveness from SEAD, leverage attack range setting -- -- === -- @@ -65,14 +65,51 @@ -- -- #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. +-- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy. +-- * **Automatic mode** (default since 0.8) can set-up your SAM site network automatically for you +-- * **Classic mode** behaves like before +-- * Leverage evasiveness from SEAD, leverage attack range setting -- --- 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. +-- # 0. Base considerations and naming conventions +-- +-- **Before** you start to set up your SAM sites in the mission editor, please think of naming conventions. This is especially critical to make +-- eveything work as intended, also if you have both a blue and a red installation! +-- +-- You need three **non-overlapping** "name spaces" for everything to work properly: +-- +-- * SAM sites, e.g. each **group name** begins with "Red SAM" +-- * EWR network and AWACS, e.g. each **group name** begins with "Red EWR" and *not* e.g. "Red SAM EWR" (overlap with "Red SAM"), "Red EWR Awacs" will be found by "Red EWR" +-- * SHORAD, e.g. each **group name** begins with "Red SHORAD" and *not" e.g. just "SHORAD" because you might also have "Blue SHORAD" +-- +-- It's important to get this right because of the nature of the filter-system in @{Core.Set#SET_GROUP}. Filters are "greedy", that is they +-- will match *any* string that contains the search string - hence we need to avoid that SAMs, EWR and SHORAD step on each other\'s toes. +-- +-- Second, for auto-mode to work, the SAMs need the **SAM Type Name** in their group name, as MANTIS will determine their capabilities from this. +-- This is case-sensitive, so "sa-11" is not equal to "SA-11" is not equal to "Sa-11"! +-- +-- Known SAM types at the time of writing are: +-- +-- * Avenger +-- * Chaparrel +-- * Hawk +-- * Linebacker +-- * NASAMS +-- * Patriot +-- * Rapier +-- * Roland +-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19 +-- +-- Folowing the example started above, an SA-6 site group name should start with "Red SAM SA-6" then, or a blue Patriot installation with e.g. "Blue SAM Patriot" +-- +-- # 0.1 Set-up in the mission editor +-- +-- Set up your SAM sites in the mission editor. Name the groups as stated above. +-- Set up your EWR system in the mission editor. Name the groups as stated above. Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. +-- Search Radars usually have "SR" or "STR" in their names. +-- Set up your SHORAD systems. They need to be close to the SAM sites to be effective. Use **one** group per SAM location. SA-15 TOR systems offer a good missile defense. +-- -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. --- +-- -- # 1. Basic tactical considerations when setting up your SAM sites -- -- ## 1.1 Radar systems and AWACS @@ -104,30 +141,47 @@ -- myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false) -- myredmantis:Start() -- --- [optional] Use +-- Use -- --- * MANTIS:SetEWRGrouping(radius) --- * MANTIS:SetEWRRange(radius) --- * MANTIS:SetSAMRadius(radius) --- * MANTIS:SetDetectInterval(interval) --- * MANTIS:SetAutoRelocate(hq, ewr) +-- * MANTIS:SetEWRGrouping(radius) [classic mode] +-- * MANTIS:SetSAMRadius(radius) [classic mode] +-- * MANTIS:SetDetectInterval(interval) [classic & auto modes] +-- * MANTIS:SetAutoRelocate(hq, ewr) [classic & auto modes] -- -- before starting #MANTIS to fine-tune your setup. -- --- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: +-- If you want to use a separate AWACS unit to support your EWR system, use e.g. the following setup: -- -- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") -- mybluemantis:Start() --- --- # 3. Default settings +-- +-- # 2.1 Auto mode features +-- +-- You can now add Accept- and Reject-Zones to your setup, e.g. to consider borders or de-militarized zones: +-- +-- -- paramters are tables of @{Core.Zone#ZONE} objects! +-- mybluemantis:AddZones(AcceptZones,RejectZones) +-- +-- Change the number of long-, mid- and short-range systems going live on a detected target: +-- +-- -- parameters are numbers. Defaults are 1,2,2,6 respectively +-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic) +-- +-- Advanced feature +-- +-- -- switch off auto mode **before** you start MANTIS +-- mybluemantis.automode = false +-- -- scale of the firing range, i.e. don't fire at the fringes of max range, default 90% +-- mybluemantis.radiusscale = 0.9 +-- +-- # 3. Default settings [both modes unless stated otherwise] -- -- By default, the following settings are active: -- -- * SAM_Templates_Prefix = "Red SAM" - SAM site group names in the mission editor begin with "Red SAM" -- * EWR_Templates_Prefix = "Red EWR" - EWR group names in the mission editor begin with "Red EWR" - can also be combined with an AWACS unit --- * checkradius = 25000 (meters) - SAMs will engage enemy flights, if they are within a 25km around each SAM site - `MANTIS:SetSAMRadius(radius)` +-- * [classic mode] checkradius = 25000 (meters) - SAMs will engage enemy flights, if they are within a 25km around each SAM site - `MANTIS:SetSAMRadius(radius)` -- * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)` --- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` -- * detectinterval = 30 (seconds) - MANTIS will decide every 30 seconds which SAM to activate - `MANTIS:SetDetectInterval(interval)` -- * engagerange = 85 (percent) - SAMs will only fire if flights are inside of a 85% radius of their max firerange - `MANTIS:SetSAMRange(range)` -- * dynamic = false - Group filtering is set to once, i.e. newly added groups will not be part of the setup by default - `MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic)` @@ -190,9 +244,12 @@ MANTIS = { EWR_Templates_Prefix = "", EWR_Group = nil, Adv_EWR_Group = nil, - HQ_Template_CC = "", - HQ_CC = nil, + HQ_Template_CC = "", + HQ_CC = nil, SAM_Table = {}, + SAM_Table_Long = {}, + SAM_Table_Medium = {}, + SAM_Table_Short = {}, lid = "", Detection = nil, AWACS_Detection = nil, @@ -246,27 +303,28 @@ MANTIS.SamType = { -- @field #number Blindspot no-firing range (green circle) -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) +-- @field #string Radar Radar typename on unit level MANTIS.SamData = { - ["Hawk"] = { Range=44, Blindspot=0, Height=9, Type="Medium" }, -- measures in km - ["NASAMS"] = { Range=14, Blindspot=0, Height=3, Type="Short" }, - ["Patriot"] = { Range=99, Blindspot=0, Height=9, Type="Long" }, - ["Rapier"] = { Range=6, Blindspot=0, Height=3, Type="Short" }, - ["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long" }, - ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium" }, - ["SA-3"] = { Range=18, Blindspot=0, Height=18, Type="Short" }, - ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium" }, - ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium" }, - ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" }, - ["Roland"] = { Range=8, Blindspot=0, Height=3, Type="Short" }, - ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short" }, - ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short" }, - ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short" }, - ["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short" }, - ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Short" }, - ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Short" }, - ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short" }, - ["Chaparrel"] = { Range=8, Blindspot=0, Height=3, Type="Short" }, - ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Short" }, + ["Hawk"] = { Range=44, Blindspot=0, Height=9, Type="Medium", Radar="Hawk" }, -- measures in km + ["NASAMS"] = { Range=14, Blindspot=0, Height=3, Type="Short", Radar="NSAMS" }, + ["Patriot"] = { Range=99, Blindspot=0, Height=9, Type="Long", Radar="Patriot" }, + ["Rapier"] = { Range=6, Blindspot=0, Height=3, Type="Short", Radar="rapier" }, + ["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" }, + ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" }, + ["SA-3"] = { Range=18, Blindspot=0, Height=18, Type="Short", Radar="s-125" }, + ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="SNR_75V" }, + ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, + ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS"}, + ["Roland"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Roland" }, + ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, + ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, + ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" }, + ["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" }, + ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Short", Radar="Tor 9A331" }, + ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, + ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" }, + ["Chaparrel"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, + ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Linebacker" }, } ----------------------------------------------------------------------- @@ -293,9 +351,6 @@ do -- -- [optional] Use -- - -- * MANTIS:SetEWRGrouping(radius) - -- * MANTIS:SetEWRRange(radius) - -- * MANTIS:SetSAMRadius(radius) -- * MANTIS:SetDetectInterval(interval) -- * MANTIS:SetAutoRelocate(hq, ewr) -- @@ -313,20 +368,23 @@ do -- DONE: Set SAMs to auto if EWR dies -- DONE: Refresh SAM table in dynamic mode -- DONE: Treat Awacs separately, since they might be >80km off site - -- TODO: Allow tables of prefixes for the setup - -- TODO: Auto-Mode with range setups for various known SAM types. + -- DONE: Allow tables of prefixes for the setup + -- DONE: Auto-Mode with range setups for various known SAM types. self.SAM_Templates_Prefix = samprefix or "Red SAM" self.EWR_Templates_Prefix = ewrprefix or "Red EWR" self.HQ_Template_CC = hq or nil self.Coalition = coaltion or "red" self.SAM_Table = {} + self.SAM_Table_Long = {} + self.SAM_Table_Medium = {} + self.SAM_Table_Short = {} self.dynamic = dynamic or false self.checkradius = 25000 self.grouping = 5000 self.acceptrange = 80000 self.detectinterval = 30 - self.engagerange = 75 + self.engagerange = 85 self.autorelocate = false self.autorelocateunits = { HQ = false, EWR = false} self.advanced = false @@ -347,13 +405,21 @@ do self.DLink = false self.Padding = Padding or 10 self.SuppressedGroups = {} - - if EmOnOff then - if EmOnOff == false then - self.UseEmOnOff = false - else - self.UseEmOnOff = true - end + -- 0.8 additions + self.automode = true + self.radiusscale = 0.9 + self.SAMCheckRanges = {} + self.usezones = false + self.AcceptZones = {} + self.RejectZones = {} + self.maxlongrange = 1 + self.maxmidrange = 2 + self.maxshortrange = 2 + self.maxclassic = 6 + + self.UseEmOnOff = true + if EmOnOff == false then + self.UseEmOnOff = false end if type(awacs) == "string" then @@ -376,7 +442,7 @@ do BASE:TraceLevel(1) end - local ewr_templates = {} + self.ewr_templates = {} if type(samprefix) ~= "table" then self.SAM_Templates_Prefix = {samprefix} end @@ -386,23 +452,29 @@ do end for _,_group in pairs (self.SAM_Templates_Prefix) do - table.insert(ewr_templates,_group) + table.insert(self.ewr_templates,_group) end for _,_group in pairs (self.EWR_Templates_Prefix) do - table.insert(ewr_templates,_group) + table.insert(self.ewr_templates,_group) end + if self.advAwacs then + table.insert(self.ewr_templates,awacs) + end + + self:T({self.ewr_templates}) + 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(ewr_templates):FilterCoalitions(self.Coalition):FilterStart() + self.EWR_Group = SET_GROUP:New():FilterPrefixes(self.ewr_templates):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(ewr_templates):FilterCoalitions(self.Coalition):FilterOnce() + self.EWR_Group = SET_GROUP:New():FilterPrefixes(self.ewr_templates):FilterCoalitions(self.Coalition):FilterOnce() end -- set up CC @@ -572,20 +644,35 @@ do self.grouping = radius return self end - - --- Function to set the detection radius of the EWR in meters + + --- Function to set accept and reject zones. + -- @param #MANTIS self + -- @param #table AcceptZones Table of @{Core.Zone#ZONE} objects + -- @param #table RejectZones Table of @{Core.Zone#ZONE} objects + -- @return #MANTIS self + function MANTIS:AddZones(AcceptZones,RejectZones) + self:T(self.lid .. "AddZones") + self.AcceptZones = AcceptZones or {} + self.RejectZones = RejectZones or {} + if #AcceptZones > 0 or #RejectZones > 0 then + self.usezones = true + end + return self + end + + --- Function to set the detection radius of the EWR in meters. (Deprecated, SAM range is used) -- @param #MANTIS self -- @param #number radius Radius of the EWR detection zone function MANTIS:SetEWRRange(radius) self:T(self.lid .. "SetEWRRange") - local radius = radius or 80000 - self.acceptrange = radius + --local radius = radius or 80000 + -- self.acceptrange = radius return self end - --- Function to set switch-on/off zone for the SAM sites in meters + --- Function to set switch-on/off zone for the SAM sites in meters. Overwritten per SAM in automode. -- @param #MANTIS self - -- @param #number radius Radius of the firing zone + -- @param #number radius Radius of the firing zone in classic mode function MANTIS:SetSAMRadius(radius) self:T(self.lid .. "SetSAMRadius") local radius = radius or 25000 @@ -593,18 +680,34 @@ do return self end - --- Function to set SAM firing engage range, 0-100 percent, e.g. 75 + --- Function to set SAM firing engage range, 0-100 percent, e.g. 85 -- @param #MANTIS self -- @param #number range Percent of the max fire range function MANTIS:SetSAMRange(range) self:T(self.lid .. "SetSAMRange") - local range = range or 75 + local range = range or 85 if range < 0 or range > 100 then - range = 75 + range = 85 end self.engagerange = range return self end + + --- Function to set number of SAMs going active on a valid, detected thread + -- @param #MANTIS self + -- @param #number Short Number of short-range systems activated, defaults to 1. + -- @param #number Mid Number of mid-range systems activated, defaults to 2. + -- @param #number Long Number of long-range systems activated, defaults to 2. + -- @param #number Classic (non-automode) Number of overall systems activated, defaults to 6. + -- @return #MANTIS self + function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic) + self:T(self.lid .. "SetMaxActiveSAMs") + self.maxclassic = Classic or 6 + self.maxlongrange = Long or 1 + self.maxmidrange = Mid or 2 + self.maxshortrange = Short or 2 + return self + end --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night -- @param #MANTIS self @@ -737,7 +840,7 @@ do return self end - --- Set using an #INTEL_DLINK object instead of #DETECTION + --- Set using your own #INTEL_DLINK object instead of #DETECTION -- @param #MANTIS self -- @param Ops.Intelligence#INTEL_DLINK DLink The data link object to be used. function MANTIS:SetUsingDLink(DLink) @@ -858,6 +961,7 @@ do --- [Internal] Function to execute the relocation -- @param #MANTIS self + -- @return #MANTIS self function MANTIS:_RelocateGroups() self:T(self.lid .. "RelocateGroups") local text = self.lid.." Relocating Groups" @@ -892,38 +996,113 @@ do end return self end - + + --- [Internal] Function to check accept and reject zones + -- @param #MANTIS self + -- @param Core.Point#COORDINATE coord The coordinate to check + -- @return #boolean outcome + function MANTIS:_CheckCoordinateInZones(coord) + -- DEBUG + self:T(self.lid.."_CheckCoordinateInZones") + local inzone = false + -- acceptzones + if #self.AcceptZones > 0 then + for _,_zone in pairs(self.AcceptZones) do + local zone = _zone -- Core.Zone#ZONE + if zone:IsCoordinateInZone(coord) then + inzone = true + self:T(self.lid.."Target coord in Accept Zone!") + break + end + end + end + -- rejectzones + if #self.RejectZones > 0 and inzone then -- maybe in accept zone, but check the overlaps + for _,_zone in pairs(self.RejectZones) do + local zone = _zone -- Core.Zone#ZONE + if zone:IsCoordinateInZone(coord) then + inzone = false + self:T(self.lid.."Target coord in Reject Zone!") + break + end + end + end + return inzone + end + + --- [Internal] Function to prefilter height based + -- @param #MANTIS self + -- @param #number height + -- @return #table set + function MANTIS:_PreFilterHeight(height) + self:T(self.lid.."_PreFilterHeight") + local set = {} + local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK + local detectedgroups = dlink:GetContactTable() + for _,_contact in pairs(detectedgroups) do + local contact = _contact -- Ops.Intelligence#INTEL.Contact + local grp = contact.group -- Wrapper.Group#GROUP + if grp:IsAlive() then + if grp:GetHeight(true) < height then + local coord = grp:GetCoordinate() + table.insert(set,coord) + end + end + end + return set + end + --- [Internal] 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 Core.Point#COORDINATE samcoordinate Coordinate object. + -- @param #number radius Radius to check. + -- @param #number height Height to check. + -- @param #boolean dlink Data from DLINK. -- @return #boolean True if in any zone, else false -- @return #number Distance Target distance in meters or zero when no object is in zone - function MANTIS:CheckObjectInZone(dectset, samcoordinate) - self:T(self.lid.."CheckObjectInZone") + function MANTIS:_CheckObjectInZone(dectset, samcoordinate, radius, height, dlink) + self:T(self.lid.."_CheckObjectInZone") -- check if non of the coordinate is in the given defense zone - local radius = self.checkradius + local rad = radius or self.checkradius local set = dectset + if dlink then + -- DEBUG + set = self:_PreFilterHeight(height) + end for _,_coord in pairs (set) do local coord = _coord -- get current coord to check -- output for cross-check local targetdistance = samcoordinate:DistanceFromPointVec2(coord) - if self.verbose or self.debug then + if not targetdistance then + targetdistance = samcoordinate:Get2DDistance(coord) + end + -- check accept/reject zones + local zonecheck = true + if self.usezones then + -- DONE + zonecheck = self:_CheckCoordinateInZones(coord) + end + if self.verbose and self.debug then local dectstring = coord:ToStringLLDMS() local samstring = samcoordinate:ToStringLLDMS() - local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) + local inrange = "false" + if targetdistance <= rad then + inrange = "true" + end + local text = string.format("Checking SAM at %s | Targetdist %d | Rad %d | Inrange %s", samstring, targetdistance, rad, inrange) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) - self:I(self.lid..text) + self:T(self.lid..text) end -- end output to cross-check - if targetdistance <= radius then + if targetdistance <= rad and zonecheck then return true, targetdistance end end return false, 0 end - --- [Internal] Function to start the detection via EWR groups + --- [Internal] Function to start the detection via EWR groups - if INTEL isn\'t available -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartDetection() @@ -932,29 +1111,52 @@ do -- 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 + --local acceptrange = self.acceptrange or 80000 + local interval = self.detectinterval or 20 + local 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:SetAcceptRange(acceptrange) -- deprecated - in range of SAMs is used anyway MANTISdetection:SetRefreshTimeInterval(interval) - MANTISdetection:Start() + MANTISdetection:__Start(2) - 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 - - --- [Internal] Function to start the detection via AWACS if defined as separate + + --- [Internal] Function to start the detection with INTEL via EWR groups + -- @param #MANTIS self + -- @return Ops.Intel#INTEL_DLINK The running detection set + function MANTIS:StartIntelDetection() + self:T(self.lid.."Starting Intel Detection") + -- DEBUG + -- start detection + local groupset = self.EWR_Group + local samset = self.SAM_Group + + self.intelset = {} + + local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne") + --IntelOne:SetClusterAnalysis(true,true) + --IntelOne:SetClusterRadius(5000) + IntelOne:Start() + + local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo") + --IntelTwo:SetClusterAnalysis(true,true) + --IntelTwo:SetClusterRadius(5000) + IntelTwo:Start() + + local IntelDlink = INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,300) + IntelDlink:__Start(1) + + self:SetUsingDLink(IntelDlink) + + table.insert(self.intelset, IntelOne) + table.insert(self.intelset, IntelTwo) + + return IntelDlink + end + + --- [Internal] Function to start the detection via AWACS if defined as separate (classic) -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartAwacsDetection() @@ -974,34 +1176,102 @@ do MANTISAwacs:SetRefreshTimeInterval(interval) MANTISAwacs:Start() - function MANTISAwacs: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 = "Awacs Detection at "..Coordinate:ToStringLLDMS() - local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - end - end return MANTISAwacs end - + + --- [Internal] Function to get SAM firing data from units types. + -- @param #MANTIS self + -- @param #string grpname Name of the group + -- @return #number range Max firing range + -- @return #number height Max firing height + -- @return #string type Long, medium or short range + function MANTIS:_GetSAMDataFromUnits(grpname) + self:T(self.lid.."_GetSAMRange") + local found = false + local range = self.checkradius + local height = 3000 + local type = MANTIS.SamType.MEDIUM + local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP + local units = group:GetUnits() + for _,_unit in pairs(units) do + local unit = _unit -- Wrapper.Unit#UNIT + local type = string.lower(unit:GetTypeName()) + --self:I(string.format("Matching typename: %s",type)) + for idx,entry in pairs(self.SamData) do + local _entry = entry -- #MANTIS.SamData + local _radar = string.lower(_entry.Radar) + --self:I(string.format("Typing typename: %s",_radar)) + if string.find(type,_radar,1,true) then + range = _entry.Range * 1000 * self.radiusscale -- max firing range + height = _entry.Height * 1000 -- max firing height + type = _entry.Type + --self:I(string.format("Match: %s - %s",_radar,type)) + found = true + break + end + end + if found then break end + end + if not found then + self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) + end + return range, height, type + end + + --- [Internal] Function to get SAM firing data + -- @param #MANTIS self + -- @param #string grpname Name of the group + -- @return #number range Max firing range + -- @return #number height Max firing height + -- @return #string type Long, medium or short range + function MANTIS:_GetSAMRange(grpname) + self:T(self.lid.."_GetSAMRange") + local range = self.checkradius + local height = 3000 + local type = MANTIS.SamType.MEDIUM + local found = false + if self.automode then + for idx,entry in pairs(self.SamData) do + --self:I("ID = " .. idx) + if string.find(grpname,idx,1,true) then + local _entry = entry -- #MANTIS.SamData + range = _entry.Range * 1000 * self.radiusscale -- max firing range + height = _entry.Height * 1000 -- max firing height + type = _entry.Type + --self:I("Groupname = " .. grpname .. " Range= " .. range) + found = true + break + end + end + end + -- secondary filter if not found + if not found and self.automode then + range, height, type = self:_GetSAMDataFromUnits(grpname) + end + return range, height, type + end + --- [Internal] Function to set the SAM start state -- @param #MANTIS self -- @return #MANTIS self function MANTIS:SetSAMStartState() -- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 + -- DONE: Auto mode self:T(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 SAM_Tbl_lg = {} -- table of long range SAM defense zones + local SAM_Tbl_md = {} -- table of mid range SAM defense zones + local SAM_Tbl_sh = {} -- table of short range 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 + if _group:IsGround() and _group:IsAlive() then local group = _group -- Wrapper.Group#GROUP - -- TODO: add emissions on/off + -- DONE: add emissions on/off if self.UseEmOnOff then group:OptionAlarmStateRed() group:EnableEmission(false) @@ -1009,16 +1279,29 @@ do else group:OptionAlarmStateGreen() -- AI off end - group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --default engagement will be 75% of firing range - if group:IsGround() and group:IsAlive() then - local grpname = group:GetName() - local grpcoord = group:GetCoordinate() - table.insert( SAM_Tbl, {grpname, grpcoord}) - table.insert( SEAD_Grps, grpname ) - self.SamStateTracker[grpname] = "GREEN" + group:OptionEngageRange(engagerange) --default engagement will be 85% of firing range + local grpname = group:GetName() + local grpcoord = group:GetCoordinate() + local grprange,grpheight,type = self:_GetSAMRange(grpname) + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight}) + table.insert( SEAD_Grps, grpname ) + if type == MANTIS.SamType.LONG then + table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight}) + --self:T("SAM "..grpname.." is type LONG") + elseif type == MANTIS.SamType.MEDIUM then + table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight}) + --self:T("SAM "..grpname.." is type MEDIUM") + elseif type == MANTIS.SamType.SHORT then + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight}) + --self:T("SAM "..grpname.." is type SHORT") + end + self.SamStateTracker[grpname] = "GREEN" end end self.SAM_Table = SAM_Tbl + self.SAM_Table_Long = SAM_Tbl_lg + self.SAM_Table_Medium = SAM_Tbl_md + self.SAM_Table_Short = SAM_Tbl_sh -- make SAMs evasive local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD mysead:SetEngagementRange(engagerange) @@ -1040,20 +1323,37 @@ do local SAM_SET = self.SAM_Group local SAM_Grps = SAM_SET.Set --table of objects local SAM_Tbl = {} -- table of SAM defense zones + local SAM_Tbl_lg = {} -- table of long range SAM defense zones + local SAM_Tbl_md = {} -- table of mid range SAM defense zones + local SAM_Tbl_sh = {} -- table of short range SAM defense zon 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 - local group = _group - group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range + local group = _group -- Wrapper.Group#GROUP + group:OptionEngageRange(engagerange) --engagement will be 85% of firing range if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() - table.insert( SAM_Tbl, {grpname, grpcoord}) -- make the table lighter, as I don't really use the zone here + local grprange, grpheight,type = self:_GetSAMRange(grpname) + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) + if type == MANTIS.SamType.LONG then + table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight}) + --self:I({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.MEDIUM then + table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight}) + --self:I({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.SHORT then + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight}) + -- self:I({grpname,grprange, grpheight}) + end end end self.SAM_Table = SAM_Tbl + self.SAM_Table_Long = SAM_Tbl_lg + self.SAM_Table_Medium = SAM_Tbl_md + self.SAM_Table_Short = SAM_Tbl_sh -- make SAMs evasive if self.mysead ~= nil then local mysead = self.mysead @@ -1090,40 +1390,40 @@ do ----------------------------------------------------------------------- -- MANTIS main functions ----------------------------------------------------------------------- - + --- [Internal] Check detection function -- @param #MANTIS self - -- @param Functional.Detection#DETECTION_AREAS detection Detection object + -- @param #table samset Table of SAM data + -- @param #table detset Table of COORDINATES + -- @param #boolean dlink Using DLINK + -- @param #limit limit of SAM sites to go active on a contact -- @return #MANTIS self - function MANTIS:_Check(detection) - self:T(self.lid .. "Check") - --get detected set - local detset = detection:GetDetectedItemCoordinates() - self:T("Check:", {detset}) - -- randomly update SAM Table - local rand = math.random(1,100) - if rand > 65 then -- 1/3 of cases - self:_RefreshSAMTable() - end - -- 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 + function MANTIS:_CheckLoop(samset,detset,dlink,limit) + self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates") + local switchedon = 0 for _,_data in pairs (samset) do local samcoordinate = _data[2] local name = _data[1] + local radius = _data[3] + local height = _data[4] local samgroup = GROUP:FindByName(name) - local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) + local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) local suppressed = self.SuppressedGroups[name] or false - if IsInZone then --check any target in zone and not curr managed by SEAD + if IsInZone and not suppressed then --check any target in zone and not currently managed by SEAD if samgroup:IsAlive() then -- switch on SAM - if self.UseEmOnOff and not suppressed then + local switch = false + if self.UseEmOnOff and switchedon < limit then -- DONE: add emissions on/off - --samgroup:SetAIOn() samgroup:EnableEmission(true) - elseif not self.UseEmOnOff and not suppressed then + switchedon = switchedon + 1 + switch = true + elseif (not self.UseEmOnOff) and switchedon < limit then samgroup:OptionAlarmStateRed() + switchedon = switchedon + 1 + switch = true end - if self.SamStateTracker[name] ~= "RED" and not suppressed then + if self.SamStateTracker[name] ~= "RED" and switch then self:__RedState(1,samgroup) self.SamStateTracker[name] = "RED" end @@ -1137,25 +1437,25 @@ do self:__ShoradActivated(1,name, radius, ontime) end -- debug output - if self.debug or self.verbose and not suppressed then + if (self.debug or self.verbose) and switch then local text = string.format("SAM %s switched to alarm state RED!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end end end --end alive else - if samgroup:IsAlive() then + if samgroup:IsAlive() and not suppressed then -- switch off SAM - if self.UseEmOnOff and not suppressed then + if self.UseEmOnOff then samgroup:EnableEmission(false) - elseif not self.UseEmOnOff and not suppressed then + else samgroup:OptionAlarmStateGreen() end - if self.SamStateTracker[name] ~= "GREEN" and not suppressed then + if self.SamStateTracker[name] ~= "GREEN" then self:__GreenState(1,samgroup) self.SamStateTracker[name] = "GREEN" end - if self.debug or self.verbose and not suppressed then + if self.debug or self.verbose then local text = string.format("SAM %s switched to alarm state GREEN!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end @@ -1165,6 +1465,36 @@ do end --for for loop return self end + + --- [Internal] Check detection function + -- @param #MANTIS self + -- @param Functional.Detection#DETECTION_AREAS detection Detection object + -- @param #boolean dlink + -- @return #MANTIS self + function MANTIS:_Check(detection,dlink) + self:T(self.lid .. "Check") + --get detected set + local detset = detection:GetDetectedItemCoordinates() + --self:T("Check:", {detset}) + -- randomly update SAM Table + local rand = math.random(1,100) + if rand > 65 then -- 1/3 of cases + self:_RefreshSAMTable() + end + -- switch SAMs on/off if (n)one of the detected groups is inside their reach + if self.automode then + local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height + self:_CheckLoop(samset,detset,dlink,self.maxlongrange) + local samset = self.SAM_Table_Medium -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height + self:_CheckLoop(samset,detset,dlink,self.maxmidrange) + local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height + self:_CheckLoop(samset,detset,dlink,self.maxshortrange) + else + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height + self:_CheckLoop(samset,detset,dlink,self.maxclassic) + end + return self + end --- [Internal] Relocation relay function -- @param #MANTIS self @@ -1194,11 +1524,12 @@ do local samgroup = GROUP:FindByName(name) if samgroup:IsAlive() then if self.UseEmOnOff then - -- TODO: add emissions on/off + -- DONE: add emissions on/off --samgroup:SetAIOn() samgroup:EnableEmission(true) + else + samgroup:OptionAlarmStateRed() end - samgroup:OptionAlarmStateRed() end -- end alive end -- end for loop elseif newstate <= 1 then @@ -1234,12 +1565,16 @@ do self:T({From, Event, To}) self:T(self.lid.."Starting MANTIS") self:SetSAMStartState() - if not self.DLink then + if not INTEL then self.Detection = self:StartDetection() + else + self.Detection = self:StartIntelDetection() end - if self.advAwacs then + --[[ + if self.advAwacs and not self.automode then self.AWACS_Detection = self:StartAwacsDetection() end + --]] self:__Status(-math.random(1,10)) return self end @@ -1254,14 +1589,15 @@ do self:T({From, Event, To}) -- check detection if not self.state2flag then - self:_Check(self.Detection) + self:_Check(self.Detection,self.DLink) end - -- check Awacs + --[[ check Awacs if self.advAwacs and not self.state2flag then - self:_Check(self.AWACS_Detection) + self:_Check(self.AWACS_Detection,false) end - + --]] + -- relocate HQ and EWR if self.autorelocate then local relointerval = self.relointerval @@ -1301,7 +1637,7 @@ do function MANTIS:onafterStatus(From,Event,To) self:T({From, Event, To}) -- Display some states - if self.debug then + if self.debug and self.verbose then self:I(self.lid .. "Status Report") for _name,_state in pairs(self.SamStateTracker) do self:I(string.format("Site %s\tStatus %s",_name,_state)) @@ -1342,7 +1678,7 @@ do -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed -- @return #MANTIS self function MANTIS:onafterGreenState(From, Event, To, Group) - self:T({From, Event, To, Group}) + self:T({From, Event, To, Group:GetName()}) return self end @@ -1354,7 +1690,7 @@ do -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed -- @return #MANTIS self function MANTIS:onafterRedState(From, Event, To, Group) - self:T({From, Event, To, Group}) + self:T({From, Event, To, Group:GetName()}) return self end @@ -1395,6 +1731,13 @@ do function MANTIS:onafterSeadSuppressionStart(From, Event, To, Group, Name) self:T({From, Event, To, Name}) self.SuppressedGroups[Name] = true + if self.ShoradLink then + local Shorad = self.Shorad + local radius = self.checkradius + local ontime = self.ShoradTime + Shorad:WakeUpShorad(Name, radius, ontime) + self:__ShoradActivated(1,Name, radius, ontime) + end return self end