From 8160aad1bfc3e38cc9489c37db112fff7d98b5dd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 14 Dec 2025 14:34:54 +0100 Subject: [PATCH 1/5] #INTELLIGENCE - We can optionally detect units accousticly --- Moose Development/Moose/Ops/Intelligence.lua | 69 +++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index f582f0675..73e89134a 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -39,6 +39,9 @@ -- @field #number prediction Seconds default to be used with CalcClusterFuturePosition. -- @field #boolean detectStatics If `true`, detect STATIC objects. Default `false`. -- @field #number statusupdate Time interval in seconds after which the status is refreshed. Default 60 sec. Should be negative. +-- @field #boolean DetectAccoustic If true, also detect by sound (ie proximity). +-- @field #number DetectAccousticRadius Radius dfor accoustic detection, defaults to 2000 meters. +-- @field #table DetectAccousticUnitTypes Types of units we can detect accousticly. Defaults to {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER} -- @extends Core.Fsm#FSM --- Top Secret! @@ -102,6 +105,9 @@ INTEL = { clusterarrows = false, prediction = 300, detectStatics = false, + DetectAccoustic = false, + DetectAccousticRadius = 1000, + DetectAccousticUnitTypes = {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER}, } --- Detected item info. @@ -160,7 +166,7 @@ INTEL.Ctype={ --- INTEL class version. -- @field #string version -INTEL.version="0.3.6" +INTEL.version="0.3.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -398,6 +404,26 @@ function INTEL:SetAcceptZones(AcceptZoneSet) return self end +--- Set to accept accoustic detection. +-- @param #INTEL self +-- @param #number Radius Radius in which we can "hear" units. Defaults to 1000 meters. +-- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER}` +-- @return #INTEL self +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) + self.DetectAccoustic = true + self.DetectAccousticRadius = Radius or 1000 + self.DetectAccousticUnitTypes = UnitCategories or {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER} + return self +end + +--- Switch off accoustic detection. +-- @param #INTEL self +-- @return #INTEL self +function INTEL:SetAccousticDetectionOff() + self.DetectAccoustic = false + return self +end + --- Add an accept zone. Only contacts detected in this zone are considered. -- @param #INTEL self -- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. @@ -831,6 +857,18 @@ function INTEL:UpdateIntel() self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK) end + + if self.DetectAccoustic then + local recce = group:GetFirstUnitAlive() + local detectionzone = group:GetProperty("INTEL_DETECT_ACCZONE") + if not detectionzone then + detectionzone = ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) + group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) + end + if recce then + self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) + end + end end end @@ -1106,7 +1144,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti return self end ---- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}. +--- (Internal) Return the detected target groups of the controllable as a table. -- The optional parameters specify the detection methods that can be applied. -- If no detection method is given, the detection will use all the available methods by default. -- @param #INTEL self @@ -1202,6 +1240,33 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua end end +--- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}. +-- @param #INTEL self +-- @param Wrapper.Unit#UNIT Recce The unit detecting. +-- @param #table DetectedUnits Table of detected units to be filled. +-- @param #table RecceDetecting Table of recce per unit to be filled. +-- @param Core.Zone#ZONE_GROUP detectionzone The zone where to look. +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) + local othercoalition = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE + self:T("Other coalition = "..othercoalition) + if detectionzone then + -- Get detected units + local reccename = Recce:GetName() + detectionzone:Scan({Object.Category.UNIT},self.DetectAccousticUnitTypes) + local unitset = detectionzone:GetScannedSetUnit(othercoalition) -- Core.Set#SET_UNIT + self:T("Accoustic detection found #Units "..unitset:CountAlive()) + for _,_unit in pairs(unitset.Set or {}) do + if _unit and _unit:IsAlive() then + local name = _unit:GetName() or "none" + DetectedUnits[name]=_unit + RecceDetecting[name]=reccename + self:T("Unit name = "..name) + end + end + unitset = nil + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 093526f4ffed53c944a43be7fde725da25d6cf40 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 14 Dec 2025 14:35:50 +0100 Subject: [PATCH 2/5] #ZONE - Added optional coalition filter on GetScannedUnitSet() and GetScannedGroupSet() --- Moose Development/Moose/Core/Zone.lua | 30 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 2c8459c9d..e9b618cf2 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1158,7 +1158,7 @@ end -- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) -- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) - + self.ScanData = {} self.ScanData.Coalitions = {} self.ScanData.Scenery = {} @@ -1168,7 +1168,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local ZoneCoord = self:GetCoordinate():SetAlt() local ZoneRadius = self:GetRadius() - --self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) + --self:I({x = ZoneCoord.x, y=ZoneCoord.y, z=ZoneCoord.z, ZoneRadius = ZoneRadius}) local SphereSearch = { id = world.VolumeType.SPHERE, @@ -1180,6 +1180,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local function EvaluateZone( ZoneObject ) --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 + if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then -- Get object category. @@ -1265,8 +1266,9 @@ end --- Get a set of scanned units. -- @param #ZONE_RADIUS self +-- @param #number Coalition (optional) Filter for this coalition only. -- @return Core.Set#SET_UNIT Set of units and statics inside the zone. -function ZONE_RADIUS:GetScannedSetUnit() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) local SetUnit = SET_UNIT:New() @@ -1275,7 +1277,12 @@ function ZONE_RADIUS:GetScannedSetUnit() local UnitObject = UnitObject -- DCS#Unit if UnitObject:isExist() then local FoundUnit = UNIT:FindByName( UnitObject:getName() ) - if FoundUnit then + local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil + local includeoncoalition = true + if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end + if Coalition == nil then includeoncoalition = true end + --self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition))) + if FoundUnit and includeoncoalition then SetUnit:AddUnit( FoundUnit ) else local FoundStatic = STATIC:FindByName( UnitObject:getName(), false ) @@ -1292,11 +1299,13 @@ end --- Get a set of scanned groups. -- @param #ZONE_RADIUS self +-- @param #number Coalition (optional) Filter for this coalition only. -- @return Core.Set#SET_GROUP Set of groups. -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP - + + self.ScanSetGroup:Clear(false) self.ScanSetGroup.Set={} if self.ScanData then @@ -1305,7 +1314,12 @@ function ZONE_RADIUS:GetScannedSetGroup() if UnitObject:isExist() then local FoundUnit=UNIT:FindByName(UnitObject:getName()) - if FoundUnit then + local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil + local includeoncoalition = true + if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end + if Coalition == nil then includeoncoalition = true end + --self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition))) + if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end @@ -3380,7 +3394,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName]) - --self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) + --self:I( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end From 0e960b77b96b6635368b6de33fb5f279c760785e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 14 Dec 2025 14:37:00 +0100 Subject: [PATCH 3/5] #MANTIS - Mantis can use acoustic detection, e.g. for Helos sneaking in on our position: `my_mantis:SetAccousticDetectionOn(2000,{Unit.Category.HELICOPTER})` --- Moose Development/Moose/Functional/Mantis.lua | 195 ++++++++++++++++-- 1 file changed, 183 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 2e1ff3b18..efa4d72aa 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: August 2025 +-- Last Update: December 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -61,10 +61,13 @@ -- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range. -- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. -- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. --- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White +-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White. -- @field #number checkcounter Counter for SAM Table refreshes. -- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay. --- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true +-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true. +-- @field #boolean DetectAccoustic Set if we can also detect units accousticly. +-- @field #number DetectAccousticRadius We can hear in this range. +-- @field #table DetectAccousticCategories We can hear these categories. -- @extends Core.Base#BASE @@ -280,7 +283,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", - version = "0.9.34", + version = "0.9.41", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -331,6 +334,9 @@ MANTIS = { checkcounter = 1, DLinkCacheTime = 120, logsamstatus = false, + DetectAccoustic = false, + DetectAccousticRadius = 2000, + DetectAccousticCategories = {Unit.Category.HELICOPTER}, } --- Advanced state enumerator @@ -561,6 +567,7 @@ do -- DONE: Treat Awacs separately, since they might be >80km off site -- DONE: Allow tables of prefixes for the setup -- DONE: Auto-Mode with range setups for various known SAM types. + -- DONE: Added reaction on HIT and UNIT LOST events. self.name = name or "mymantis" self.SAM_Templates_Prefix = samprefix or "Red SAM" @@ -716,6 +723,8 @@ do self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group. self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group. self:AddTransition("*", "SeadSuppressionPlanned", "*") -- SEAD has planned a suppression. + self:AddTransition("*", "SAMUnitHit", "*") -- A SAM unit was hit + self:AddTransition("*", "SAMUnitLost", "*") -- A SAM Unit was lost self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. ------------------------ @@ -826,6 +835,24 @@ do -- @param Wrapper.Group#GROUP Group The suppressed GROUP object -- @param #string Name Name of the suppressed group + --- On After "SAMUnitHit" event. A SAM Unit was hit. + -- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP of the hit UNIT object + -- @param #string Name Name of the suppressed group + + --- On After "SAMUnitLoast" event. A SAM Unit was lost. + -- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP of the lost UNIT object + -- @param #string Name Name of the suppressed group + return self end @@ -833,6 +860,145 @@ do -- MANTIS helper functions ----------------------------------------------------------------------- + --- Set to accept accoustic detection. Set this *before* MANTIS starts! + -- @param #MANTIS self + -- @param #number Radius Radius in which we can "hear" units. Defaults to 2000 meters. + -- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.HELICOPTER}` + -- @return #MANTIS self + function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) + self.DetectAccoustic = true + self.DetectAccousticRadius = Radius or 2000 + self.DetectAccousticCategories = UnitCategories or {Unit.Category.HELICOPTER} + return self + end + + --- Switch off accoustic detection. + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:SetAccousticDetectionOff() + self.DetectAccoustic = false + return self + end + + --- [Internal] Function to manage hits on SAM units + -- @param #MANTIS self + -- @param Core.Event#EVENTDATA EventData The EVENT data + -- @return #MANTIS self + function MANTIS:_EventHandler(EventData) + self:T(self.lid .. "_EventHandler") + + local function IsOneOfOurs(name) + for _,_name in pairs(self.ewr_templates) do + if string.find(name,_name,1,true) then + return true + end + end + return false + end + + local function SwitchSAMOn(Name,Group) + local suppressed = self.SuppressedGroups[Name] or false + if not suppressed and self.SamStateTracker[Name] == "GREEN" then + self.SamStateTracker[Name] = "RED" + if self.UseEmOnOff then + -- DONE: add emissions on/off + Group:EnableEmission(true) + elseif (not self.UseEmOnOff) then + Group:OptionAlarmStateRed() + end + self:__RedState(1,Group) + if self.SmokeDecoy == true then --shortsam == true and + self:T("Smoking") + local units = Group:GetUnits() or {} + local smoke = self.SmokeDecoyColor or SMOKECOLOR.White + for _,unit in pairs(units) do + if unit and unit:IsAlive() then + unit:GetCoordinate():Smoke(smoke) + end + end + end + end + end + + local coordinate -- Core.Point#COORDINATE + local Name -- #string + local Group -- Wrapper.Group#GROUP + local lasthit = 0 + local firsthit = false + local alerton = false + + -- Check if we can get a location + + local data = EventData -- Core.Event#EVENTDATA + if data.id == EVENTS.Hit then + -- Unit hit, one of ours? + if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName) then + self:T("Unit hit in group: "..data.TgtGroupName) + if data.TgtGroup then + lasthit = data.TgtGroup:GetProperty("MANTIS_LASTHIT") + firsthit = (lasthit==nil) and true or false + if firsthit == true then alerton = true end + if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end + coordinate = data.TgtGroup:GetCoordinate() + Name = data.TgtGroupName + Group = data.TgtGroup + if alerton == true then + self:__SAMUnitHit(1,Group,Name) + SwitchSAMOn(Name,Group) + end + if coordinate and self.debug then + local text = coordinate:ToStringMGRS() + self:I("Location: "..text) + end + end + end + end + + if data.id == EVENTS.UnitLost then + if data.IniGroupName and IsOneOfOurs(data.IniGroupName) then + self:T("Unit lost in group: "..data.IniGroupName) + if data.IniGroup then + lasthit = data.IniGroup:GetProperty("MANTIS_LASTHIT") + firsthit = (lasthit==nil) and true or false + if firsthit == true then alerton = true end + if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end + coordinate = data.IniGroup:GetCoordinate() + Name = data.IniGroupName + Group = data.IniGroup + alerton = true + SwitchSAMOn(Name,Group) + self:__SAMUnitLost(1,Group,Name) + if coordinate and self.debug then + local text = coordinate:ToStringMGRS() + self:I("Location: "..text) + end + end + end + end + + if firsthit == true or alerton == true then + Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) + end + + + if coordinate ~= nil and Name ~= nil and Group ~=nil and alerton == true then + if self.ShoradLink then + self:T("Shorad activated for: "..Name) + local Shorad = self.Shorad -- Functional.Shorad#SHORAD + local radius = self.checkradius + local ontime = self.ShoradTime + Shorad:WakeUpShorad(Name, radius, ontime, nil, true) + self:__ShoradActivated(1,Name, radius, ontime) + end + if self.autorelocate and Group then + Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) + end + end + + return self + end + + --- [Internal] Function to get the self.SAM_Table -- @param #MANTIS self -- @return #table table @@ -1445,13 +1611,15 @@ do self.intelset = {} local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne") - --IntelOne:SetClusterAnalysis(true,true) - --IntelOne:SetClusterRadius(5000) + IntelOne.DetectAccoustic = self.DetectAccoustic + IntelOne.DetectAccousticRadius = self.DetectAccousticRadius or 2000 + IntelOne.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} IntelOne:Start() local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo") - --IntelTwo:SetClusterAnalysis(true,true) - --IntelTwo:SetClusterRadius(5000) + IntelTwo.DetectAccoustic = self.DetectAccoustic + IntelTwo.DetectAccousticRadius = self.DetectAccousticRadius or 2000 + IntelTwo.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} IntelTwo:Start() local CacheTime = self.DLinkCacheTime or 120 @@ -1807,7 +1975,7 @@ do local radius = _data[3] local height = _data[4] local blind = _data[5] * 1.25 + 1 - local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false + local shortsam = (_data[6] ~= MANTIS.SamType.LONG) and true or false if not shortsam then shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false end @@ -1834,10 +2002,9 @@ do end if self.SamStateTracker[name] ~= "RED" and switch then self:__RedState(1,samgroup) - self.SamStateTracker[name] = "RED" end - -- TODO doesn't work - if shortsam == true and self.SmokeDecoy == true then + -- TODO Check doesn't work + if shortsam == true and self.SmokeDecoy == true then self:T("Smoking") local units = samgroup:GetUnits() or {} local smoke = self.SmokeDecoyColor or SMOKECOLOR.White @@ -2045,6 +2212,10 @@ do if self.shootandscoot and self.SkateZones and self.Shorad then self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation) end + + self:HandleEvent(EVENTS.Hit,self._EventHandler) + self:HandleEvent(EVENTS.UnitLost,self._EventHandler) + self:__Status(-math.random(1,10)) return self end From b6a90d46339161e0b6a6e56bf08bfc5ad6eb828b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 14 Dec 2025 16:05:18 +0100 Subject: [PATCH 4/5] #GROUP Added `GROUP:SetFormation(Formation)` --- Moose Development/Moose/Wrapper/Group.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index d4c93960f..e63c84fed 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2944,6 +2944,16 @@ function GROUP:EnableEmission(switch) return self end + +--- Set a formation for this group. +-- @param #GROUP self +-- @param #number Formation See. ENUMS.Formation or [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) for options. +-- @return #GROUP self +function GROUP:SetFormation(Formation) + self:SetOption(AI.Option.Air.id.FORMATION,Formation) + return self +end + --- Switch on/off invisible flag for the group. -- @param #GROUP self -- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled. From 34f3e24d3a48a28ef57665aa708fa4633955ef36 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 15 Dec 2025 09:40:42 +0100 Subject: [PATCH 5/5] #MANTIS - Fixing over-smoke and ensure helos are not attacked by long-range systems --- Moose Development/Moose/Functional/Mantis.lua | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index efa4d72aa..3d5dde62b 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -907,15 +907,8 @@ do Group:OptionAlarmStateRed() end self:__RedState(1,Group) - if self.SmokeDecoy == true then --shortsam == true and - self:T("Smoking") - local units = Group:GetUnits() or {} - local smoke = self.SmokeDecoyColor or SMOKECOLOR.White - for _,unit in pairs(units) do - if unit and unit:IsAlive() then - unit:GetCoordinate():Smoke(smoke) - end - end + if self.SmokeDecoy == true then + self:_SmokeUnits(Group) end end end @@ -1491,11 +1484,12 @@ do return inzone end - --- [Internal] Function to prefilter height based + --- [Internal] Function to prefilter height based and check for Helo activity. -- @param #MANTIS self -- @param #number height + -- @param Core.Point#COORDINATE SamCoordinate -- @return #table set - function MANTIS:_PreFilterHeight(height) + function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set = {} local dlink = self.Detection -- Ops.Intel#INTEL_DLINK @@ -1504,8 +1498,14 @@ do local contact = _contact -- Ops.Intel#INTEL.Contact local grp = contact.group -- Wrapper.Group#GROUP if grp:IsAlive() then - if grp:GetHeight(true) < height then - local coord = grp:GetCoordinate() + local coord = grp:GetCoordinate() + local dist = 0 + local include = true + if coord and SamCoordinate and grp:IsHelicopter() then + dist = coord:Get2DDistance(SamCoordinate) or 0 + if dist > self.ShoradActDistance then include = false end -- we do not want long range shooting at helos + end + if grp:GetHeight(true) < height and include == true then table.insert(set,coord) end end @@ -1529,7 +1529,7 @@ do local set = dectset if dlink then -- DEBUG - set = self:_PreFilterHeight(height) + set = self:_PreFilterHeight(height,samcoordinate) end --self.friendlyset -- Core.Set#SET_GROUP if self.checkforfriendlies == true and self.friendlyset == nil then @@ -1948,6 +1948,27 @@ do self.ShoradLink = false return self end + + --- [Internal] Function to smoke a group in decoy. + -- @param #MANTIS self + -- @param Wrapper.Group#GROUP Group + -- @return #MANTIS self + function MANTIS:_SmokeUnits(Group) + self:T("Smoking") + local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME") or 0 + local TNow = timer.getTime() + if TNow - LastSmoketime > 290 then -- Smoking lasts 5 minutes + Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) + local units = Group:GetUnits() or {} + local smoke = self.SmokeDecoyColor or SMOKECOLOR.White + for _,unit in pairs(units) do + if unit and unit:IsAlive() then + unit:GetCoordinate():Smoke(smoke) + end + end + end + return self + end ----------------------------------------------------------------------- -- MANTIS main functions @@ -2003,16 +2024,10 @@ do if self.SamStateTracker[name] ~= "RED" and switch then self:__RedState(1,samgroup) end - -- TODO Check doesn't work - if shortsam == true and self.SmokeDecoy == true then + -- DONE Restrict on Distance + if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*2 then self:T("Smoking") - local units = samgroup:GetUnits() or {} - local smoke = self.SmokeDecoyColor or SMOKECOLOR.White - for _,unit in pairs(units) do - if unit and unit:IsAlive() then - unit:GetCoordinate():Smoke(smoke) - end - end + self:_SmokeUnits(samgroup) end -- link in to SHORAD if available -- DONE: Test integration fully