Skip to content

bugfix(neutronmissile): Fix and improve Nuke Missile damage for large objects inside the outer blast radius - #3161

Open
xezon wants to merge 1 commit into
TheSuperHackers:mainfrom
xezon:xezon/fix-nuke-missile-outer-radius
Open

bugfix(neutronmissile): Fix and improve Nuke Missile damage for large objects inside the outer blast radius#3161
xezon wants to merge 1 commit into
TheSuperHackers:mainfrom
xezon:xezon/fix-nuke-missile-outer-radius

Conversation

@xezon

@xezon xezon commented Aug 16, 2026

Copy link
Copy Markdown

Large structures are now properly damaged if they reach into the outer blast radius and more damage is applied by taking the closest edge into the damage calculation.

Original behavior 1

The China Nuke Missile applies no damage to the edges of buildings (and units). This is mostly noticeable with large structures, such as the Airfield. The blast radius needs to reach the center of every building (and unit).

Note that the Nuke Missile has an outer and inner damage radius. The outer radius applies less damage than the inner radius. The damage transition from inner to outer radius is linear.

Firing the Nuke Missile as follows applies NO damage to the structure:

sshot_20260808_113841_807

Fixed behavior 1

The China Nuke Missile now applies damage to the edges of a building like all other weapons do.

This is a BUFF, but a very small one, because the building will receive the SMALLEST damage of the outer blast radius.

This is how much damage is applied to the Airfield after fixing the bug:

sshot_20260808_114213_388

Rationale

The China Nuke blast radius is already not competitive compared to the SCUD Storm. The Nuke Missile needs buffing anyway and this bug fix is a good step into this direction.

Original behavior 2

Structures that are located between the inner and outer radii of the Nuke Missile do receive an interpolated damage between the max (inner) and min (outer) damages. This works well enough for small structures like a Gattling Cannon or Tunnel Network, but does not work well for large structures, such as the Airfield or SCUD Storm, because the damage sample point will be the center of the structure instead of a point closer to the actual damage radius of the Nuke Missile.

In practice this means that large structures at the egdes of the Nuke Missile will receive rather tiny damages, even if they reach reasonably far into the outer damage radius.

Nuke Blast Radius Damage
Inner (max) 60 3500
Outer (min) 210 300

To fix this, the damage sample point needs to be moved closer to the Nuke Missile origins.

Showcase

The Nuke Missile applies little damage to large structures at the outer edge

generalszh.2026-08-16.11-29-18-88.mp4

Improved behavior 2

The logic now samples the damage point from the center to half of the outer edge of the object closest to the Nuke Missile origin. This works well for small and large objects.

BUFF for China. Does NOT make the Nuke Missile any better against small buildings and units, nor against targets inside the inner damage radius. The Nuke Missile will still perform worse than the SCUD Storm.

nukedmg

Showcase

The Nuke Missile applies considerably more damage to large structures at the outer edge

generalszh.2026-08-16.11-13-29-30.mp4

The SCUD Storm still applies more damage to large structures at the outer edge, because it applies around 20% more damage overall.

generalszh.2026-08-16.11-14-02-24.mp4

TODO

  • Replicate to Generals
  • Wait for Committee Approval

… objects inside the outer blast radius

Large structures are now properly damaged if they reach into the outer blast radius and more damage is applied by taking the closest edge into the damage calculation
@xezon xezon added Buff Makes a thing more powerful Bug Something is not working right, typically is user facing China Affects China faction Design Is a matter of game design Major Severity: Minor < Major < Critical < Blocker NoRetail This fix or change is not applicable with Retail game compatibility labels Aug 16, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix Nuke Missile outer-radius targeting and improve large-object damage sampling

🐞 Bug fix ✨ Enhancement ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Fix outer-radius object search so large objects are hit when their edges enter blast range.
• Improve outer-radius damage sampling by biasing distance toward the closest object edge.
• Add build-time preserve flags to keep retail-compatible behavior when required.
Diagram

graph TD
  A["NeutronMissileSlowDeathBehavior::doBlast"] --> B{"Retail-compatible mode?"} -->|"Yes"| C["Range scan (center)"] --> D["Vector to center"] --> E["Damage falloff (inner→outer)"] --> F["Apply damage + topple"]
  A --> B -->|"No"| C2["Range scan (bounds)"] --> D2["Vector toward nearest edge"] --> E --> F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use true closest-point distance (footprint/geometry)
  • ➕ Most accurate for very large or non-circular buildings
  • ➕ Avoids bounding-sphere approximation artifacts
  • ➖ Requires more geometry/footprint queries and may be more expensive
  • ➖ Higher implementation risk across object types/LODs
2. Unify search + sampling via a single PartitionManager API
  • ➕ Compute the closest-point vector once and reuse for search, topple, and falloff
  • ➕ Reduces duplication and chances of mismatched distance semantics
  • ➖ May require broader refactor of PartitionManager and callers
  • ➖ Larger surface area for regressions
3. Always enable improved sampling; keep only CRC-gated retail path
  • ➕ Simpler configuration surface (fewer preserve switches)
  • ➕ Ensures consistent gameplay behavior for non-retail builds
  • ➖ Reduces ability to independently ship search-fix vs damage-buff changes
  • ➖ May conflict with balance/committee approval workflow

Recommendation: Current approach is appropriate: it leverages existing distance calculation modes (FROM_BOUNDINGSPHERE_2D / getVectorTo) and cleanly gates behavior behind preserve/CRC macros for compatibility and balance control. If this becomes permanent gameplay behavior, consider collapsing the two preserve flags or moving to a single closest-point distance helper to avoid divergence between search and damage sampling.

Files changed (2) +43 / -4

Bug fix (1) +32 / -1
NeutronMissileSlowDeathUpdate.cppFix outer-radius scan and adjust damage sampling for large objects +32/-1

Fix outer-radius scan and adjust damage sampling for large objects

• Changes the outer-radius object iteration to optionally use bounding-sphere distance so objects intersecting the radius are included. Adds an optional edge-biased force/distance vector computation so damage falloff is sampled closer to the nearest object edge instead of always using the center.

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

Other (1) +11 / -3
GameDefines.hAdd preserve flags for Nuke Missile outer-radius behavior +11/-3

Add preserve flags for Nuke Missile outer-radius behavior

• Introduces PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_SEARCH and PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE macros to control whether retail-compatible center-based logic is retained. Also relocates the retail-compatibility note near these toggles for visibility.

Core/GameEngine/Include/Common/GameDefines.h

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

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Nuke falloff fix disabled 📎 Requirement gap ≡ Correctness
Description
The closest-edge distance logic for outer-radius nuke damage falloff is gated behind
RETAIL_COMPATIBLE_CRC / PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE, which default to
preserving retail center-based distance calculations. As a result, the PR’s improved “halfway to
edge” sampling/closest-edge behavior is not active in default configurations, violating the intended
behavior for large buildings.
Code

GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp[342]

+#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE
Evidence
The citations indicate that compliance requires nuke damage falloff to use closest-edge distance
rather than center distance, but the implementation in doBlast() is wrapped in a preprocessor
guard that selects the retail branch when RETAIL_COMPATIBLE_CRC or
PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE are true. Since RETAIL_COMPATIBLE_CRC is
defined as 1 and PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE is defined with a default
value of 1 (to preserve retail behavior), the #else path containing the new closest-edge /
“halfway to edge” damage sampling code is skipped/compiled out unless the macro is explicitly
overridden.

Nuke damage falloff for buildings should be based on closest edge, not building center
Core/GameEngine/Include/Common/GameDefines.h[87-100]
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp[319-366]
Core/GameEngine/Include/Common/GameDefines.h[87-93]
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp[342-366]

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 closest-edge / outer-radius nuke damage falloff behavior is effectively disabled by default because the improved damage sampling is placed under `#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE`, and `PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE` defaults to `1` (with `RETAIL_COMPATIBLE_CRC` also defined as `1`). This causes default builds to keep retail center-based distance calculations and prevents the PR’s intended closest-edge-based damage falloff for large buildings from taking effect.

## Issue Context
Compliance requires building nuke damage falloff to be based on closest edge (not center). If the intention is to ship the improved outer-radius sampling (not merely stage it behind a flag), the default configuration should allow the new path in non-retail builds (i.e., when `RETAIL_COMPATIBLE_CRC == 0`), or otherwise clearly document where/how the macro is expected to be overridden to enable the intended behavior.

## Fix Focus Areas
- Core/GameEngine/Include/Common/GameDefines.h[87-100]
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp[319-366]

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



Remediation recommended

2. Z dropped from distance 🐞 Bug ≡ Correctness
Description
When the non-retail outer-radius damage path is enabled, doBlast() forces forceVector.z = 0, so
damage falloff (dist = forceVector.length()) becomes purely 2D and differs from the retail path that
includes Z. This can over-apply damage/topple to airborne or differently-elevated objects compared
to the existing behavior.
Code

GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp[R363-366]

+			forceVector.x = (forceVector.x + toCenterVector.x) * 0.5f;
+			forceVector.y = (forceVector.y + toCenterVector.y) * 0.5f;
+			forceVector.z = 0.0f;
+#endif
Evidence
The new branch explicitly zeros Z, and the same forceVector is later used to compute dist for
damage falloff via forceVector.length(), so dropping Z changes the computed distance. The
PartitionManager 2D boundary distance calculation also hard-sets Z to 0, confirming the new path is
2D-only unless Z is restored explicitly.

GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp[342-390]
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp[806-856]

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

### Issue description
In `NeutronMissileSlowDeathBehavior::doBlast()`, the new outer-radius damage sampling branch computes an adjusted XY vector but then sets `forceVector.z = 0.0f`. A few lines later, damage falloff uses `dist = forceVector.length()`, so this change makes damage distance purely 2D in that branch, unlike the retail-compatible branch which includes Z.

### Issue Context
- This impacts both the `topple()` vector and the damage falloff distance.
- The new branch is intended to adjust *horizontal* sampling toward the closest edge for large objects, but it shouldn’t implicitly change vertical distance behavior.

### Fix Focus Areas
- Ensure the adjusted vector retains a meaningful Z component (e.g., set `forceVector.z = otherPos->z - missilePos->z` after averaging XY), or split vectors: use a 2D vector for topple if desired and a 3D vector for damage falloff.

- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp[342-390]

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


Grey Divider

Context

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +363 to +366
forceVector.x = (forceVector.x + toCenterVector.x) * 0.5f;
forceVector.y = (forceVector.y + toCenterVector.y) * 0.5f;
forceVector.z = 0.0f;
#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. Z dropped from distance 🐞 Bug ≡ Correctness

When the non-retail outer-radius damage path is enabled, doBlast() forces forceVector.z = 0, so
damage falloff (dist = forceVector.length()) becomes purely 2D and differs from the retail path that
includes Z. This can over-apply damage/topple to airborne or differently-elevated objects compared
to the existing behavior.
Agent Prompt
### Issue description
In `NeutronMissileSlowDeathBehavior::doBlast()`, the new outer-radius damage sampling branch computes an adjusted XY vector but then sets `forceVector.z = 0.0f`. A few lines later, damage falloff uses `dist = forceVector.length()`, so this change makes damage distance purely 2D in that branch, unlike the retail-compatible branch which includes Z.

### Issue Context
- This impacts both the `topple()` vector and the damage falloff distance.
- The new branch is intended to adjust *horizontal* sampling toward the closest edge for large objects, but it shouldn’t implicitly change vertical distance behavior.

### Fix Focus Areas
- Ensure the adjusted vector retains a meaningful Z component (e.g., set `forceVector.z = otherPos->z - missilePos->z` after averaging XY), or split vectors: use a 2D vector for topple if desired and a 3D vector for damage falloff.

- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp[342-390]

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

// take average between min and max force
forceVector.x = (forceVector.x + toCenterVector.x) * 0.5f;
forceVector.y = (forceVector.y + toCenterVector.y) * 0.5f;
forceVector.z = 0.0f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

forceVector.z = 0 has effect on helicopters and planes that are flying. Previously the blast was modeled as a (half) sphere, while now it is modeled as a cylinder.

Helicopters and planes that are within the outer radius at ground level now always receive damage, while previously, they could have been undamaged due to still being outside the sphere.

If this change is intentional, it may need documentation.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The nuke missile explodes 15 units or so above the ground. I dediced to model the damage as 2D, because everything else is 2D here, and if not treated as 2D, then the damage radii are not perfectly accurate on the ground.

I have not considered aircraft. I will think about whether it needs returning to sphere.

toCenterVector.x = otherPos->x - missilePos->x;
toCenterVector.y = otherPos->y - missilePos->y;

ThePartitionManager->getVectorTo(other, missilePos, FROM_BOUNDINGSPHERE_2D, forceVector);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would prefer a fromBoundVector rather than reusing the forceVector variable.

forceVector.y = -forceVector.y;

// take average between min and max force
forceVector.x = (forceVector.x + toCenterVector.x) * 0.5f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

With the proper comment (and renaming to fromBoundVector, the flipping and average can be consolidated into one statement.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I would like to keep the flip separate, because it is only necessary because ThePartitionManager->getVectorTo gives us the direction in reverse of what we need.

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

Labels

Buff Makes a thing more powerful Bug Something is not working right, typically is user facing China Affects China faction Design Is a matter of game design Major Severity: Minor < Major < Critical < Blocker NoRetail This fix or change is not applicable with Retail game compatibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The centre of the building decide the damage done by a nuke instead of the closest edge

2 participants