From 7a3ea2918dfbe73705a510f3fb1ae9047057b6ff Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 22 Jan 2026 12:34:41 +0100 Subject: [PATCH 1/4] #SEAD/#MANTIS - Adding functions for non-suppression of groups capable of HARM defense #CTLD/#CSAR - Added data for A/M-H6J --- Moose Development/Moose/Functional/Mantis.lua | 116 +++++++++++++++--- Moose Development/Moose/Functional/Sead.lua | 22 +++- Moose Development/Moose/Ops/CSAR.lua | 6 +- Moose Development/Moose/Ops/CTLD.lua | 6 +- 4 files changed, 126 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 10086cda1..3d526aea1 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: December 2025 +-- Last Update: January 2026 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -283,7 +283,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", - version = "0.9.42", + version = "0.9.43", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -337,6 +337,8 @@ MANTIS = { DetectAccoustic = false, DetectAccousticRadius = 2000, DetectAccousticCategories = {Unit.Category.HELICOPTER}, + ARMWeaponSeen = {}, + InboundARMs = {}, } --- Advanced state enumerator @@ -372,24 +374,25 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3 -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) -- @field #string Point Point defense capable +-- @field ARMCapacit ARMCapacity ie how many (H)ARMs the system can defend at the same time MANTIS.SamData = { ["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km - ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B + ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS", ARMCapacity=1 }, -- AIM 120B ["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" }, ["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" }, ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" }, ["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" }, ["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-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"}, + ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4", ARMCapacity=4}, ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, - ["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, + ["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland", ARMCapacity=1 }, ["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" }, ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["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="Point", Radar="Tor 9A331", Point="true" }, + ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true", ARMCapacity=2 }, ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" }, ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, @@ -398,13 +401,13 @@ MANTIS.SamData = { ["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, - ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" }, + ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=3 }, ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"}, - ["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M"}, - ["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M"}, - ["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E"}, + ["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4}, + ["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4}, + ["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E", ARMCapacity=4}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, @@ -412,7 +415,7 @@ MANTIS.SamData = { ["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" }, -- CH Added to DCS core 2.9.19.x ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" }, - ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, + ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=4 }, ["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, } @@ -430,7 +433,7 @@ MANTIS.SamDataHDS = { ["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" }, ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" }, ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD - ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD + ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln", ARMCapacity=3}, -- V55RUD ["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " }, ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"}, ["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, @@ -479,7 +482,7 @@ MANTIS.SamDataCH = { -- https://www.currenthill.com/ -- group name MUST contain CHM to ID launcher type correctly! ["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" }, - ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true" }, + ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true", ARMCapacity=3 }, ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, ["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" }, ["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, @@ -625,6 +628,8 @@ do self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP self.FilterZones = Zones + self.ARMWeaponSeen = {} + self.InboundARMs = {} self.SkateZones = nil self.SkateNumber = 3 @@ -1868,6 +1873,7 @@ do local HDSmod = false local SMAMod = false local CHMod = false + local ARMCapacity = 0 if string.find(grpname,"HDS",1,true) then HDSmod = true elseif string.find(grpname,"SMA",1,true) then @@ -1885,6 +1891,7 @@ do range = _entry.Range * 1000 * radiusscale -- max firing range height = _entry.Height * 1000 -- max firing height blind = _entry.Blindspot + ARMCapacity = _entry.ARMCapacity or 0 self:T("Matching Groupname = " .. grpname .. " Range= " .. range) found = true break @@ -1911,7 +1918,7 @@ do if found and string.find(grpname,"SHORAD",1,true) then type = MANTIS.SamType.POINT -- force short on match end - return range, height, type, blind + return range, height, type, blind, ARMCapacity end --- [Internal] Function to set the SAM start state @@ -1946,8 +1953,8 @@ do group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range local grpname = group:GetName() local grpcoord = group:GetCoordinate() - local grprange,grpheight,type,blind = self:_GetSAMRange(grpname) - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) + local grprange,grpheight,type,blind,ARMCapacity = self:_GetSAMRange(grpname) + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) --table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) @@ -2011,11 +2018,11 @@ do if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() - local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) + local grprange, grpheight,type,blind, ARMCapacity = self:_GetSAMRange(grpname) -- TODO the below might stop working at some point after some hours, needs testing --local radaralive = group:IsSAM() local radaralive = true - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG and radaralive then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) @@ -2098,6 +2105,77 @@ do ----------------------------------------------------------------------- -- MANTIS main functions ----------------------------------------------------------------------- + +--- [Internal] Check if a system can and should defend for HARMs itself +-- @param #MANTIS self +-- @param Wrapper.Group#GROUP targetGroup +-- @param #string targetName +-- @param Wrapper.Group#GROUPattackerGroup +-- @param #string weaponName +-- @param Wrapper.Weapon#WEAPON weaponWrapper +-- @param #number tti +-- @param #number delay +-- @return #boolean Outcome +function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, weaponName, weaponWrapper, tti, delay) + + -- Init per-SAM weapon tracking + self.ARMWeaponSeen = self.ARMWeaponSeen or {} + self.ARMWeaponSeen[targetName] = self.ARMWeaponSeen[targetName] or {} + + -- Extract weapon ID + local wid = nil + if weaponWrapper and weaponWrapper.GetDCSObject then + local wpn = weaponWrapper:GetDCSObject() + if wpn then + wid = wpn:getID() + end + end + + -- Ignore duplicate evaluations of the same missile + if wid and self.ARMWeaponSeen[targetName][wid] then + self:T(string.format( + "MANTIS: Duplicate ARM ignored for %s (weapon %d)", + targetName, wid + )) + return false + end + + -- Mark weapon as seen + if wid then + self.ARMWeaponSeen[targetName][wid] = true + end + + -- NOW increment ARM counter (once per weapon) + self.InboundARMs[targetName] = (self.InboundARMs[targetName] or 0) + 1 + + -- Lookup ARM capacity + local samdata + for _, sam in pairs(self.SAM_Table or {}) do + if sam[1] == targetName then + samdata = sam + break + end + end + + local armcap = samdata and samdata[7] + + if not armcap or armcap == 0 then + return true + end + + if targetGroup and targetGroup:IsAlive() then + local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition() + if AmmoM and AmmoM == 0 then + return true + end + end + + if self.InboundARMs[targetName] > armcap then + return true + end + + return false + end --- [Internal] Check detection function -- @param #MANTIS self @@ -2549,6 +2627,8 @@ do function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name) self:T({From, Event, To, Name}) self.SuppressedGroups[Name] = false + self.InboundARMs[Name] = 0 + self.ARMWeaponSeen[Name] = nil return self end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 34b5e7d13..2bc819169 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -19,7 +19,7 @@ -- -- ### Authors: **applevangelist**, **FlightControl** -- --- Last Update: Dec 2024 +-- Last Update: January 2026 -- -- === -- @@ -157,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) self:AddTransition("*", "ManageEvasion", "*") self:AddTransition("*", "CalculateHitZone", "*") - self:I("*** SEAD - Started Version 0.4.9") + self:I("*** SEAD - Started Version 0.4.10") return self end @@ -443,6 +443,24 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay local _targetgroupname = _targetgroup:GetName() if not self.SuppressedGroups[_targetgroupname] then + -- TODO: ask callback if suppression is allowed BEFORE scheduling timers + local allow = true + if self.UseCallBack and self.CallBack and self.CallBack.SeadAllowSuppression then + allow = self.CallBack:SeadAllowSuppression( + _targetgroup, + _targetgroupname, + SEADGroup, + SEADWeaponName, + Weapon, + _tti, + delay + ) + end + if not allow then + self:T(string.format("*** SEAD - %s | Suppression vetoed by callback", _targetgroupname)) + -- Important: do NOT schedule SuppressionStart/Stop and do NOT flip SuppressedGroups true. + return self + end self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime) timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index bd2d83bb5..b416051af 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Dec 2025 +-- Last Update Jan 2026 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -315,10 +315,12 @@ CSAR.AircraftType["MH-60R"] = 10 CSAR.AircraftType["OH-6A"] = 2 CSAR.AircraftType["OH58D"] = 2 CSAR.AircraftType["CH-47Fbl1"] = 31 +CSAR.AircraftType["AH-6J"] = 2 +CSAR.AircraftType["MH-6J"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.35" +CSAR.version="1.0.36" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f9b48a3db..a6e4c7179 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -25,7 +25,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Dec 2025 +-- Last Update Jan 2026 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -1496,6 +1496,8 @@ CTLD.UnitTypeCapabilities = { ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, + ["AH-6J"] = {type="AH-6J", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, + ["MH-6J"] = {type="MH-6J", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 10800}, @@ -1514,7 +1516,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.42" +CTLD.version="1.3.43" --- Instantiate a new CTLD. -- @param #CTLD self From 3ab3278db41dd4699341f1f640b6a75fbd4b3984 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 22 Jan 2026 17:18:59 +0100 Subject: [PATCH 2/4] xx --- Moose Development/Moose/Functional/Mantis.lua | 145 +++++++++++------- Moose Development/Moose/Functional/Sead.lua | 4 +- Moose Development/Moose/Wrapper/Weapon.lua | 2 +- 3 files changed, 94 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 3d526aea1..a737c8fd6 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -628,7 +628,7 @@ do self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP self.FilterZones = Zones - self.ARMWeaponSeen = {} + self.LastThreatEval = {} self.InboundARMs = {} self.SkateZones = nil @@ -2117,64 +2117,82 @@ do -- @param #number delay -- @return #boolean Outcome function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, weaponName, weaponWrapper, tti, delay) + self:T(self.lid.."SeadAllowSuppression") - -- Init per-SAM weapon tracking - self.ARMWeaponSeen = self.ARMWeaponSeen or {} - self.ARMWeaponSeen[targetName] = self.ARMWeaponSeen[targetName] or {} - - -- Extract weapon ID - local wid = nil - if weaponWrapper and weaponWrapper.GetDCSObject then - local wpn = weaponWrapper:GetDCSObject() - if wpn then - wid = wpn:getID() - end + --- Thanks to @Goon Jan 2026 + ---------------------------------------------------------------- + -- LOG INCOMING REQUEST + ---------------------------------------------------------------- + self:T(string.format("MANTIS:SeadAllowSuppression REQUEST | target=%s | weapon=%s | tti=%s | delay=%s",tostring(targetName), + tostring(weaponName),tostring(tti),tostring(delay))) + + ---------------------------------------------------------------- + -- LOOK UP ARM CAPACITY FOR THIS SAM + ---------------------------------------------------------------- + local armcap = nil + + for _, sam in pairs(self.SAM_Table or {}) do + if sam[1] == targetName then + armcap = sam[7] -- ARMCapacity + break end + end + + self:T(string.format("MANTIS:SeadAllowSuppression SAM DATA | target=%s | ARMCapacity=%s",tostring(targetName),armcap and tostring(armcap) or "nil")) + + ---------------------------------------------------------------- + -- TRACK SEAD THREATS (PER TARGET) + ---------------------------------------------------------------- + local THREAT_WINDOW = 0.1 -- seconds - -- Ignore duplicate evaluations of the same missile - if wid and self.ARMWeaponSeen[targetName][wid] then - self:T(string.format( - "MANTIS: Duplicate ARM ignored for %s (weapon %d)", - targetName, wid - )) - return false - end + self.LastThreatEval = self.LastThreatEval or {} + self.InboundARMs = self.InboundARMs or {} - -- Mark weapon as seen - if wid then - self.ARMWeaponSeen[targetName][wid] = true - end + local now = timer.getTime() + local last = self.LastThreatEval[targetName] or 0 - -- NOW increment ARM counter (once per weapon) + if (now - last) >= THREAT_WINDOW then self.InboundARMs[targetName] = (self.InboundARMs[targetName] or 0) + 1 + self.LastThreatEval[targetName] = now + self:T(string.format("MANTIS:SeadAllowSuppression NEW threat accepted | Δt=%.3f",now - last)) + else + self:T(string.format("MANTIS:SeadAllowSuppression duplicate evaluation ignored | Δt=%.3f",now - last)) + end + + local inbound = self.InboundARMs[targetName] or 0 + + self:T(string.format("MANTIS:SeadAllowSuppression THREAT COUNT | target=%s | inboundThreats=%d",tostring(targetName),inbound)) + + ---------------------------------------------------------------- + -- DECISION GATE + ---------------------------------------------------------------- - -- Lookup ARM capacity - local samdata - for _, sam in pairs(self.SAM_Table or {}) do - if sam[1] == targetName then - samdata = sam - break - end - end - - local armcap = samdata and samdata[7] - - if not armcap or armcap == 0 then + -- No missiles left over → legacy behavior + if targetGroup and targetGroup:IsAlive() then + local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition() + -- TODO Check C-RAM probably needs an exception as it is a gun, need to check its effectiveness + if AmmoM and AmmoM == 0 then + self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no MISSILES) | target=%s",tostring(targetName))) return true end - - if targetGroup and targetGroup:IsAlive() then - local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition() - if AmmoM and AmmoM == 0 then - return true - end - end + end - if self.InboundARMs[targetName] > armcap then - return true - end - - return false + -- No ARM capacity defined → legacy behavior + if (not armcap) or armcap == 0 then + self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no ARMCAP) | target=%s",tostring(targetName))) + return true + end + + -- Suppress only once enough threats accumulated + if inbound >= armcap then + self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (inbound %d >= cap %d) | target=%s",inbound,armcap,tostring(targetName))) + return true + end + + self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> DENIED (inbound %d < cap %d) | target=%s",inbound,armcap,tostring(targetName))) + + return false + end --- [Internal] Check detection function @@ -2225,6 +2243,7 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea switch = true end if self.SamStateTracker[name] ~= "RED" and switch then + self.SamStateTracker[name] = "RED" self:__RedState(1,samgroup) end -- DONE Restrict on Distance @@ -2257,8 +2276,8 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea samgroup:OptionAlarmStateGreen() end if self.SamStateTracker[name] ~= "GREEN" then - self:__GreenState(1,samgroup) self.SamStateTracker[name] = "GREEN" + self:__GreenState(1,samgroup) end if self.debug or self.verbose then local text = string.format("SAM %s in alarm state GREEN!", name) @@ -2268,12 +2287,13 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea end --end alive end --end check end --for loop + --[[ if self.debug or self.verbose or self.logsamstatus then for _,_status in pairs(self.SamStateTracker) do if _status == "GREEN" then instatusgreen=instatusgreen+1 elseif _status == "RED" then - instatusred=instatusred+1 + instatusred=instatusred+1 end end if self.Shorad then @@ -2282,6 +2302,8 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea end end end + self:T(self.lid..string.format("SAM State Count: GREEN %d | RED %d | SHORAD %d",instatusred, instatusgreen, activeshorads)) + --]] return instatusred, instatusgreen, activeshorads end @@ -2313,13 +2335,29 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange) local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange) + local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange) else local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) + local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) end local function GetReport() + + if self.debug or self.verbose or self.logsamstatus then + for _,_status in pairs(self.SamStateTracker) do + if _status == "GREEN" then + instatusgreen=instatusgreen+1 + elseif _status == "RED" then + instatusred=instatusred+1 + end + end + if self.Shorad then + for _,_name in pairs(self.Shorad.ActiveGroups or {}) do + activeshorads=activeshorads+1 + end + end + end + local statusreport = REPORT:New("\nMANTIS Status "..self.name) statusreport:Add("+-----------------------------+") statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) @@ -2628,7 +2666,6 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea self:T({From, Event, To, Name}) self.SuppressedGroups[Name] = false self.InboundARMs[Name] = 0 - self.ARMWeaponSeen[Name] = nil return self end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 2bc819169..88e94fa59 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -318,7 +318,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG elseif height <= 12500 then Ropt = Ropt * 0.98 end - + local WeaponWrapper = WEAPON:New(SEADWeapon) -- look at a couple of zones across the trajectory for n=1,3 do local dist = Ropt - ((n-1)*20000) @@ -342,7 +342,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG _targetgroupname = tgtgrp:GetName() -- group name _targetskill = tgtgrp:GetUnit(1):GetSkill() self:T("*** Found Target = ".. _targetgroupname) - self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20) + self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20, WeaponWrapper) end --end end diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua index 0ba988748..86b4cea03 100644 --- a/Moose Development/Moose/Wrapper/Weapon.lua +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -350,7 +350,7 @@ end -- myweapon:SetFuncImpact(OnImpact) -- -- -- Start tracking. --- myweapon:Track() +-- myweapon:StartTrack() -- function WEAPON:SetFuncImpact(FuncImpact, ...) self.impactFunc=FuncImpact From 49332fafec5852d5d5f74d007a1d93d70a30f5b0 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:45:23 +0100 Subject: [PATCH 3/4] Fixed bugs Fixed bugs for load troops. Added Mi-8 to the prevention method for ignoring what is inside when trying to build crates. Removed return self if the player is trying to build stuff that is outside the helicopter or C-130, while he still have stuff inside. Cargo will be built but it will ignore what is inside. Removed the menu "Others" if there is no sub category involved for that type. If user have crates subcat but not troops subcat, it won't show "others". --- Moose Development/Moose/Ops/CTLD.lua | 52 ++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index a6e4c7179..9b46c1b93 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2938,7 +2938,7 @@ function CTLD:_LoadTroopsQuantity(Group, Unit, Cargo, quantity) local prevSuppress = self.suppressmessages self.suppressmessages = true for i = 1, n do - timer.scheduleFunction(function() self:_LoadTroops(Group, Unit, Cargo, true) end, {}, timer.getTime() + 0.2 * i) + timer.scheduleFunction(function() self:_LoadTroops(Group, Unit, Cargo) end, {}, timer.getTime() + 0.2 * i) end timer.scheduleFunction(function() self.suppressmessages = prevSuppress @@ -3390,7 +3390,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 35 - local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true,true) + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true,true,true) -- to ignore what's inside if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) return false @@ -4399,6 +4399,8 @@ function CTLD:_UnloadTroops(Group, Unit) local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo -- looking for troops local cargotable = loadedcargo.Cargo + local deployedTroopsByName = {} + local deployedEngineersByName = {} for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum @@ -4455,12 +4457,22 @@ function CTLD:_UnloadTroops(Group, Unit) self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) - self:_SendMessage(string.format("Dropped Engineers %s into action!",name), 10, false, Group) + deployedEngineersByName[name] = (deployedEngineersByName[name] or 0) + 1 else - self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) + deployedTroopsByName[name] = (deployedTroopsByName[name] or 0) + 1 end end -- if type end end -- cargotable loop + local parts = {} + for nName,nCount in pairs(deployedTroopsByName) do + parts[#parts + 1] = tostring(nCount).."x Troops "..nName + end + for nName,nCount in pairs(deployedEngineersByName) do + parts[#parts + 1] = tostring(nCount).."x Engineers "..nName + end + if #parts > 0 then + self:_SendMessage("Dropped "..table.concat(parts, ", ").." into action!", 10, false, Group) + end else -- droppingatbase self:_SendMessage("Troops have returned to base!", 10, false, Group) self:__TroopsRTB(1, Group, Unit, zonename, zone) @@ -4652,9 +4664,8 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) local baseDist = self.CrateDistance or 35 local finddist=baseDist --if Engineering and self.EngineerSearch and self.EngineerSearch>baseDist then - if self.EngineerSearch and self.EngineerSearch>baseDist then -- this make also helicopter to be able to crates that are further away due to herc airdrop + if Engineering and self.EngineerSearch and self.EngineerSearch>baseDist then -- this make also helicopter to be able to crates that are further away due to herc airdrop finddist=self.EngineerSearch - finddist=self.EngineerSearch end local crates,number = self:_FindCratesNearby(Group,Unit,finddist,true,true,not Engineering) -- #table local buildables = {} @@ -4680,11 +4691,12 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) local isHercDrop=Crate:WasDropped(true) if not isHercDrop and distToUnit>baseDist then elseif self.UseC130LoadAndUnload and self:IsC130J(Unit) and distToUnit<15 then - self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) - return self + -- self:_SendMessage("Please unload crates from the C-130 before building!",10,false,Group) + -- return self elseif self.UseC130LoadAndUnload and self:IsHook(Unit) and distToUnit<5 then - self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) - return self + -- self:_SendMessage("Please unload crates from the CH-47 before building!",10,false,Group) + -- return self + elseif self.UseC130LoadAndUnload and (Unit:GetTypeName()=="Mi-8MTV2" or Unit:GetTypeName()=="Mi-8MT") and distToUnit<8 then else --local testmarker = ccoord:MarkToAll("Crate found",true,"Build Position") if not buildables[name] then @@ -4756,7 +4768,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) local base = Unit:GetCoordinate():Translate(20,hdg) if full == 1 then - local cratesNow, numberNow = self:_FindCratesNearby(Group,Unit, finddist,true,true) + local cratesNow, numberNow = self:_FindCratesNearby(Group,Unit, finddist,true,true, not Engineering) self:_CleanUpCrates(cratesNow,build,numberNow) self:_RefreshLoadCratesMenu(Group,Unit) if self.buildtime and self.buildtime > 0 then @@ -4773,7 +4785,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) else local start = -((full-1)*sep)/2 for n=1,full do - local cratesNow, numberNow = self:_FindCratesNearby(Group,Unit, finddist,true,true) + local cratesNow, numberNow = self:_FindCratesNearby(Group,Unit, finddist,true,true, not Engineering) self:_CleanUpCrates(cratesNow,build,numberNow) self:_RefreshLoadCratesMenu(Group,Unit) local off = start + (n-1)*sep @@ -5567,13 +5579,25 @@ function CTLD:_RefreshF10Menus() _group.CTLD_TroopMenus = {} if self.usesubcats then local subcatmenus = {} + local subcatcount = 0 + local onlycat = nil for catName, _ in pairs(self.subcatsTroop) do - subcatmenus[catName] = MENU_GROUP:New(_group, catName, troopsmenu) + subcatcount = subcatcount + 1 + onlycat = catName + end + local useTroopSubcats = subcatcount > 1 or (subcatcount == 1 and onlycat ~= "Other") + if useTroopSubcats then + for catName, _ in pairs(self.subcatsTroop) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, troopsmenu) + end end for _, cargoObj in pairs(self.Cargo_Troops) do if not cargoObj.DontShowInMenu then local menutext = cargoObj.Name - local parent = subcatmenus[cargoObj.Subcategory] or troopsmenu + local parent = troopsmenu + if useTroopSubcats and cargoObj.Subcategory and subcatmenus[cargoObj.Subcategory] then + parent = subcatmenus[cargoObj.Subcategory] + end local mSet = MENU_GROUP:New(_group, menutext, parent) _group.CTLD_TroopMenus[cargoObj.Name] = mSet self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj) From b980c5d93abb49c74d0e3a7013012c5be168b0d0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 23 Jan 2026 18:20:24 +0100 Subject: [PATCH 4/4] #EasyA2G/GCICAP - Added option to set Squadron Turnovertime --- Moose Development/Moose/Ops/EasyA2G.lua | 7 +++--- Moose Development/Moose/Ops/EasyGCICAP.lua | 27 +++++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index 9092e970d..1e97ea5c4 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -21,7 +21,7 @@ -- ------------------------------------------------------------------------- -- Date: Dec 2025 --- Last Update: Dec 2025 +-- Last Update: Jan 2026 ------------------------------------------------------------------------- -- -- === @@ -293,7 +293,7 @@ EASYA2G = { --- EASYA2G class version. -- @field #string version -EASYA2G.version="0.1.3" +EASYA2G.version="0.1.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -352,6 +352,7 @@ function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName) self.FuelCriticalThreshold = 10 self.showpatrolpointmarks = false self.EngageTargetTypes = {"Ground"} + self:SetDefaultTurnoverTime() -- Set some string id for output to DCS.log file. self.lid=string.format("EASYA2G %s | ", self.alias) @@ -546,7 +547,7 @@ function EASYA2G:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, S Squadron_One:AddMissionCapability({AUFTRAG.Type.CAS, AUFTRAG.Type.CASENHANCED, AUFTRAG.Type.BAI, AUFTRAG.Type.ALERT5, AUFTRAG.Type.BOMBING, AUFTRAG.Type.STRIKE}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 68804a66b..2c557773a 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -21,7 +21,7 @@ -- ------------------------------------------------------------------------- -- Date: September 2023 --- Last Update: Aug 2025 +-- Last Update: Jan 2026 ------------------------------------------------------------------------- -- --- **Ops** - Easy GCI & CAP Manager @@ -89,6 +89,8 @@ -- @field #number FuelCriticalThreshold -- @field #boolean showpatrolpointmarks -- @field #table EngageTargetTypes +-- @field #number maintenancetime +-- @field #number repairtime -- @extends Core.Fsm#FSM --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. @@ -284,7 +286,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.33" +EASYGCICAP.version="0.1.34" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- @@ -343,6 +345,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.FuelCriticalThreshold = 10 self.showpatrolpointmarks = false self.EngageTargetTypes = {"Air"} + self:SetDefaultTurnoverTime() -- Set some string id for output to DCS.log file. self.lid=string.format("EASYGCICAP %s | ", self.alias) @@ -608,6 +611,18 @@ function EASYGCICAP:SetDefaultMissionRange(Range) return self end +--- Set default turnover times for squadrons in minutes +-- @param #EASYGCICAP self +-- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 5 min. +-- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 10 min. +-- @return #EASYGCICAP self +function EASYGCICAP:SetDefaultTurnoverTime(MaintenanceTime,RepairTime) + self:T(self.lid.."SetDefaultTurnoverTime") + self.maintenancetime=MaintenanceTime or 5 + self.repairtime=RepairTime or 10 + return self +end + --- Set default number of airframes standing by for intercept tasks (visible on the airfield) -- @param #EASYGCICAP self -- @param #number Airframes defaults to 2 @@ -1217,7 +1232,7 @@ function EASYGCICAP:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) @@ -1248,7 +1263,7 @@ function EASYGCICAP:_AddReconSquadron(TemplateName, SquadName, AirbaseName, AirF Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) @@ -1282,7 +1297,7 @@ function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, Air Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) @@ -1319,7 +1334,7 @@ function EASYGCICAP:_AddAWACSSquadron(TemplateName, SquadName, AirbaseName, AirF Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)