From 274e3fd594f3a5c9e836fccee6b2e9a84506c5f2 Mon Sep 17 00:00:00 2001 From: Atrik Date: Mon, 18 May 2026 14:54:26 +0200 Subject: [PATCH] Filter out hidden targets in CanAttack Units should not be able to attack entities with "hidden" visibility. Visible and fogged (mirage/retainInFog) targets remain attackable. --- .../data/mods/public/simulation/components/Attack.js | 9 +++++++++ .../data/mods/public/simulation/components/UnitAI.js | 2 ++ .../public/simulation/components/tests/test_Attack.js | 3 ++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/binaries/data/mods/public/simulation/components/Attack.js b/binaries/data/mods/public/simulation/components/Attack.js index 9ea860167e..508475d4a5 100644 --- a/binaries/data/mods/public/simulation/components/Attack.js +++ b/binaries/data/mods/public/simulation/components/Attack.js @@ -271,6 +271,15 @@ Attack.prototype.CanAttack = function(target, wantedTypes) if (!cmpTargetPlayer || !cmpEntityPlayer) return false; + // Must be visible or miraged / with retainInFog flag, not completely hidden + const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (cmpRangeManager) + { + const visibility = cmpRangeManager.GetLosVisibility(target, cmpEntityPlayer.GetPlayerID()); + if (visibility == "hidden") + return false; + } + const types = this.GetAttackTypes(wantedTypes); const entityOwner = cmpEntityPlayer.GetPlayerID(); const targetOwner = cmpTargetPlayer.GetPlayerID(); diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 33467c0281..2b8587e082 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -6399,6 +6399,8 @@ UnitAI.prototype.GetQueryRange = function(iid) } else if (this.GetStance().respondChase) // Chase stances: use full vision range (unit will chase anything it sees) + // Range being sometimes parabolic, it could extend beyond vision range, + // which can cause vision range to limit the effective attack range. ret.max = visionRange; else if (this.GetStance().respondHoldGround) { diff --git a/binaries/data/mods/public/simulation/components/tests/test_Attack.js b/binaries/data/mods/public/simulation/components/tests/test_Attack.js index 7adfcc334f..c90fd46111 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_Attack.js +++ b/binaries/data/mods/public/simulation/components/tests/test_Attack.js @@ -60,7 +60,8 @@ function attackComponentTest(defenderClass, isEnemy, test_function) AddMock(SYSTEM_ENTITY, IID_RangeManager, { "GetEffectiveParabolicRange": () => 25, - "GetMaxParabolicHeightDiff": () => 15 + "GetMaxParabolicHeightDiff": () => 15, + "GetLosVisibility": (target, owner) => "visible" }); const attacker = entityID;