Skip to content

bugfix: Contact weapons are no longer blocked by obstacles - #3194

Open
Stubbjax wants to merge 1 commit into
TheSuperHackers:mainfrom
Stubbjax:fix-obstacles-blocking-contact-weapons
Open

bugfix: Contact weapons are no longer blocked by obstacles#3194
Stubbjax wants to merge 1 commit into
TheSuperHackers:mainfrom
Stubbjax:fix-obstacles-blocking-contact-weapons

Conversation

@Stubbjax

Copy link
Copy Markdown

This change fixes an issue where contact weapons fired on a location would be blocked by obstacles.

This was most notable when attempting to suicide any units that were intersecting obstacle geometry, where the respective unit(s) would get stuck due to the way in which the attack state machines would continuously bail out due to an obstacle being in the way, while being unable to find a new destination due to the weapon's attack range of 0 requiring no movement.

Before

A Suicide command will not detonate any units intersecting an obstacle, and they would often get stuck in a cyclical state

BEFORE.mp4

After

A Suicide command will detonate units regardless of any intersecting obstacles

AFTER.mp4

@Stubbjax Stubbjax self-assigned this Aug 23, 2026
@Stubbjax Stubbjax added Bug Something is not working right, typically is user facing Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour NoRetail This fix or change is not applicable with Retail game compatibility labels Aug 23, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix contact weapons being blocked by obstacle line-of-sight checks

🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Skip obstacle LOS blocking checks for contact weapons during ground attacks.
• Prevent contact-weapon attack-position logic from repathing when already in range.
• Apply identical fixes to both Generals and GeneralsMD engine variants.
Diagram

graph TD
  A["AI Attack State"] --> B["Range Check"] --> C{ "Contact weapon?" }
  C -->|"No"| D["Obstacle LOS check"] --> E["Approach / Reposition"]
  C -->|"Yes"| F["Skip LOS block"] --> G["Allow attack"]
  H["Attack Position (AIUpdate)"] --> I{ "In range?" } --> J["Path available?"] --> K["Find alternate position"]
  I -->|"Yes"| L["Do not repath"]
  subgraph Legend
    direction LR
    _proc["Process"] ~~~ _decision{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Centralize obstacle-blocking policy in pathfinder/weapon logic
  • ➕ Single source of truth for when obstacle LOS should matter
  • ➕ Reduces duplicated conditional logic across AI states and update code
  • ➖ Higher risk change (pathfinder behavior affects many weapon types)
  • ➖ Requires broader regression testing across multiple attack modes
2. Introduce an explicit weapon flag (e.g., ignoresObstacleLOSForAttack)
  • ➕ More expressive than inferring behavior solely from isContactWeapon()
  • ➕ Easier to extend for future special-case weapons
  • ➖ Requires data/schema plumbing and audits of existing weapons
  • ➖ More upfront refactor than a targeted bug fix

Recommendation: The PR’s targeted approach (skip obstacle LOS checks for contact weapons and avoid repathing when already in range) is appropriate for a contained behavioral fix with low blast radius. Consider a follow-up refactor to centralize the policy (or add an explicit weapon capability flag) if additional weapon classes need similar exceptions, but keeping this PR minimal is the right call.

Files changed (4) +24 / -0

Bug fix (4) +24 / -0
AIStates.cppBypass obstacle view-blocking for contact weapons in attack states +8/-0

Bypass obstacle view-blocking for contact weapons in attack states

• Updates attack-range/approach state logic to only perform obstacle view-blocking checks when the weapon is not a contact weapon (in non-RETAIL_COMPATIBLE_CRC builds). This prevents contact-weapon attacks (e.g., suicide) from being rejected due to intersecting obstacle geometry.

Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp

AIUpdate.cppAvoid repathing contact-weapon attacks when already within range +4/-0

Avoid repathing contact-weapon attacks when already within range

• Refines the contact-weapon path-availability guard in privateAttackPosition so it only searches for an alternate reachable position when the attacker is not already within attack range (in non-RETAIL_COMPATIBLE_CRC builds). This addresses zero-range contact weapons getting stuck in repeated reposition attempts near obstacles.

Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp

AIStates.cppMirror contact-weapon obstacle-blocking fix in GeneralsMD AI states +8/-0

Mirror contact-weapon obstacle-blocking fix in GeneralsMD AI states

• Applies the same conditional obstacle view-blocking bypass for contact weapons to the GeneralsMD AI state implementations. Keeps behavior consistent across both engine variants.

GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp

AIUpdate.cppMirror contact-weapon in-range gating for repathing in GeneralsMD +4/-0

Mirror contact-weapon in-range gating for repathing in GeneralsMD

• Matches the Generals AIUpdate change by adding an in-range condition before using path-availability to force alternate positioning for contact weapons. Prevents stuck attack loops for contact weapons targeting positions intersecting obstacles.

GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Fix compiled out by default 🐞 Bug ≡ Correctness
Description
All behavior changes are under #else branches that only compile when RETAIL_COMPATIBLE_CRC is
disabled, but RETAIL_COMPATIBLE_CRC defaults to 1 in GameDefines.h. If your default/release
builds do not override this macro, the reported obstacle-blocking bug will remain unfixed in shipped
builds.
Code

Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[R1217-1221]

+#if RETAIL_COMPATIBLE_CRC
		if (onGround)
+#else
+		if (onGround && !weapon->isContactWeapon())
+#endif
Evidence
The PR’s new logic is only in #else blocks, while RETAIL_COMPATIBLE_CRC is defined to 1 by
default, so the new code paths won’t be compiled unless the build overrides the macro.

Core/GameEngine/Include/Common/GameDefines.h[23-99]
Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[1216-1224]
Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[2658-2668]
Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[3398-3406]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The PR’s fix is guarded by `#if RETAIL_COMPATIBLE_CRC` / `#else`, but `RETAIL_COMPATIBLE_CRC` defaults to `1`, so the new behavior won’t apply unless the build explicitly overrides the macro.

### Issue Context
`RETAIL_COMPATIBLE_CRC` is defined to preserve retail lockstep compatibility; if you *intend* this fix to ship in retail-compatible builds, it needs a different approach (or an explicit decision to break compatibility). If you *don’t* intend it to ship in retail-compatible builds, the PR should make that explicit (and ideally provide a feature flag specific to this behavior rather than tying it to CRC compatibility).

### Fix Focus Areas
- Core/GameEngine/Include/Common/GameDefines.h[23-99]
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[1216-1224]
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[2658-2668]
- Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[3398-3406]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Contact attacks ignore obstacle LOS 🐞 Bug ≡ Correctness
Description
For attack-position commands, contact weapons now skip isAttackViewBlockedByObstacle, so the
approach/aim states can succeed and fire based only on distance even when an obstacle blocks the
target point. This can enable detonating a contact weapon at an obstacle-blocked position (e.g.,
through a thin wall/fence) from an adjacent reachable cell, which is broader than “unit intersecting
obstacle geometry.”
Code

Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[R2661-2665]

+#if RETAIL_COMPATIBLE_CRC
			if ( ai->isDoingGroundMovement() )
+#else
+			if ( ai->isDoingGroundMovement() && !weapon->isContactWeapon() )
+#endif
Evidence
The PR disables obstacle LOS checks for contact weapons in the position-range predicate and in the
approach state. Since contact weapons immediately succeed in the aim state when inFiringRange is
true, this allows firing solely based on distance without obstacle validation.

Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[285-289]
Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[1216-1232]
Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[2658-2673]
Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[4871-4878]
Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[535-547]
Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[2056-2070]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The PR skips obstacle visibility checks for contact weapons when attacking a *position*, which can allow contact detonation at obstacle-blocked points as long as the attacker is within (small) range.

### Issue Context
- `WeaponTemplate::isContactWeapon()` is range-based (< pathfind cell size), not “must be physically colliding right now”.
- `Weapon::isWithinAttackRange(source, pos)` is purely distance/min-range based and does not account for obstacles.
- The aim state explicitly treats contact weapons as “don’t aim, just go boom”, so once the state machine believes it is in range, it will fire.

### Fix Focus Areas
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[1216-1232]
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[2658-2673]
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[4811-4879]
- Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[535-547]
- Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[2056-2070]

### Suggested direction
Keep the LOS/obstacle check for contact weapons except for the specific “already overlapping the target point” case you’re trying to fix (e.g., only bypass when the attacker is essentially at the goal position within a very small epsilon, not merely within attack range).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Unpathable contact target preserved 🐞 Bug ≡ Correctness
Description
privateAttackPosition now skips the “find a nearby pathable spot” adjustment for contact weapons
if isWithinAttackRange() is true, even when isPathAvailable() is false. Combined with the LOS
bypass, this lets a unit execute a contact attack on an explicitly unpathable goal point (e.g.,
inside blocking geometry) as long as it can get within range outside the obstacle.
Code

Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[R3402-3406]

+#if RETAIL_COMPATIBLE_CRC
	if (weapon && weapon->isContactWeapon() && !isPathAvailable(&localPos))
+#else
+	if (weapon && weapon->isContactWeapon() && !weapon->isWithinAttackRange(getObject(), &localPos) && !isPathAvailable(&localPos))
+#endif
Evidence
The PR changes the pathability fallback to depend on !weapon->isWithinAttackRange(...), but
isWithinAttackRange is computed solely from distance/min-range and does not imply the goal point
is reachable. This directly weakens the existing safeguard intended to avoid targeting unreachable
points for contact weapons.

Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[3398-3415]
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[3553-3561]
Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[2056-2070]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new condition in `privateAttackPosition()` bypasses the existing safety behavior for contact weapons (“must be able to path to the target pos”) whenever the attacker is merely within attack range, even if the goal point is unpathable.

### Issue Context
`Weapon::isWithinAttackRange(source, pos)` is distance-only, while the comment/behavior here is about reachability/pathing. For contact weapons, “within range” can still be on the other side of a blocking obstacle.

### Fix Focus Areas
- Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[3398-3415]
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[3553-3561]
- Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[2056-2070]

### Suggested direction
Only skip the `isPathAvailable()`/fallback relocation when the unit is effectively already at the target point (very small positional epsilon), rather than when it is merely within attack range.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn these tips off under Display preferences

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp
Comment on lines +2661 to +2665
#if RETAIL_COMPATIBLE_CRC
if ( ai->isDoingGroundMovement() )
#else
if ( ai->isDoingGroundMovement() && !weapon->isContactWeapon() )
#endif

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Contact attacks ignore obstacle los 🐞 Bug ≡ Correctness

For attack-position commands, contact weapons now skip isAttackViewBlockedByObstacle, so the
approach/aim states can succeed and fire based only on distance even when an obstacle blocks the
target point. This can enable detonating a contact weapon at an obstacle-blocked position (e.g.,
through a thin wall/fence) from an adjacent reachable cell, which is broader than “unit intersecting
obstacle geometry.”
Agent Prompt
### Issue description
The PR skips obstacle visibility checks for contact weapons when attacking a *position*, which can allow contact detonation at obstacle-blocked points as long as the attacker is within (small) range.

### Issue Context
- `WeaponTemplate::isContactWeapon()` is range-based (< pathfind cell size), not “must be physically colliding right now”.
- `Weapon::isWithinAttackRange(source, pos)` is purely distance/min-range based and does not account for obstacles.
- The aim state explicitly treats contact weapons as “don’t aim, just go boom”, so once the state machine believes it is in range, it will fire.

### Fix Focus Areas
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[1216-1232]
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[2658-2673]
- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp[4811-4879]
- Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[535-547]
- Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[2056-2070]

### Suggested direction
Keep the LOS/obstacle check for contact weapons except for the specific “already overlapping the target point” case you’re trying to fix (e.g., only bypass when the attacker is essentially at the goal position within a very small epsilon, not merely within attack range).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +3402 to +3406
#if RETAIL_COMPATIBLE_CRC
if (weapon && weapon->isContactWeapon() && !isPathAvailable(&localPos))
#else
if (weapon && weapon->isContactWeapon() && !weapon->isWithinAttackRange(getObject(), &localPos) && !isPathAvailable(&localPos))
#endif

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Unpathable contact target preserved 🐞 Bug ≡ Correctness

privateAttackPosition now skips the “find a nearby pathable spot” adjustment for contact weapons
if isWithinAttackRange() is true, even when isPathAvailable() is false. Combined with the LOS
bypass, this lets a unit execute a contact attack on an explicitly unpathable goal point (e.g.,
inside blocking geometry) as long as it can get within range outside the obstacle.
Agent Prompt
### Issue description
The new condition in `privateAttackPosition()` bypasses the existing safety behavior for contact weapons (“must be able to path to the target pos”) whenever the attacker is merely within attack range, even if the goal point is unpathable.

### Issue Context
`Weapon::isWithinAttackRange(source, pos)` is distance-only, while the comment/behavior here is about reachability/pathing. For contact weapons, “within range” can still be on the other side of a blocking obstacle.

### Fix Focus Areas
- Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[3398-3415]
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[3553-3561]
- Generals/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp[2056-2070]

### Suggested direction
Only skip the `isPathAvailable()`/fallback relocation when the unit is effectively already at the target point (very small positional epsilon), rather than when it is merely within attack range.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@DrGoldFish1

Copy link
Copy Markdown

Do we know exactly what units this effects? Or is this only for the suicide ability?

@Stubbjax

Copy link
Copy Markdown
Author

Do we know exactly what units this effects? Or is this only for the suicide ability?

Also this issue with Terrorists:

TERROR_DANCE.mp4

@DrGoldFish1

Copy link
Copy Markdown

Okey this is actually great seeing this fixed. If you need testing for it feel free to share it in the testing discord. And I will see what can be done. Good work

@Skyaero42

Copy link
Copy Markdown

Code looks good to me.

I know it basically fixes a bug, but should this go passed the game committee? Non-bugged is a lot more powerful. I don't know if the 'bug' is considered to be a feature nowadays.

if ( ai->isDoingGroundMovement() && !weapon->isContactWeapon() )
#endif
{
viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(source, *source->getPosition(), nullptr, m_goalPosition);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 8 calls to isAttackViewBlockedByObstacle in this code base but only 2 callsites are tackled in this change. Is this sufficient?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something is not working right, typically is user facing Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker NoRetail This fix or change is not applicable with Retail game compatibility ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants