Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions Core/GameEngine/Include/Common/GameDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@

#include "WWLib/WWDefines.h"

// Note: Retail compatibility must not be broken before this project officially does.
// Use RETAIL_COMPATIBLE_CRC and RETAIL_COMPATIBLE_XFER_SAVE to guard breaking changes.

#ifndef PRESERVE_BUILDING_RESUMPTION_DELAY
#define PRESERVE_BUILDING_RESUMPTION_DELAY (0) // The fix for this unfavorable behavior was approved by the Game Design Committee.
#endif
Expand Down Expand Up @@ -87,6 +84,17 @@
#define PRESERVE_RETAIL_SCRIPTED_CAMERA (1) // Retain scripted camera behavior present in retail Generals 1.08 and Zero Hour 1.04
#endif

#ifndef PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_SEARCH
#define PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_SEARCH (0) // The fix for this unfavorable behavior was approved by the Game Design Committee.
#endif

#ifndef PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE
#define PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE (1)
#endif

// Note: Retail compatibility must not be broken before this project officially does.
// Use RETAIL_COMPATIBLE_CRC and RETAIL_COMPATIBLE_XFER_SAVE to guard breaking changes.

#ifndef RETAIL_COMPATIBLE_CRC
#define RETAIL_COMPATIBLE_CRC (1) // Game is expected to be CRC compatible with retail Generals 1.08, Zero Hour 1.04
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,21 @@ void NeutronMissileSlowDeathBehavior::doBlast( const BlastInfo *blastInfo )
damageInfo.in.m_amount = blastInfo->minDamage;

// scan objects around us and do damage to objects we have "passed over" and are behind us
// TheSuperHackers @todo Optimize this function. Iterates through the blasts even if they apply zero damage.
if( blastInfo->outerRadius )
{

#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_SEARCH
const DistanceCalculationType dc = FROM_CENTER_2D;
#else
// TheSuperHackers @bugfix xezon 16/08/2026 From FROM_CENTER_2D,
// because objects that reach into the outer radius should also receive damage.
const DistanceCalculationType dc = FROM_BOUNDINGSPHERE_2D;
#endif

ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( missilePos,
blastInfo->outerRadius,
FROM_CENTER_2D,
dc,
nullptr );
MemoryPoolObjectHolder hold( iter );
Object *other;
Expand All @@ -329,10 +339,31 @@ void NeutronMissileSlowDeathBehavior::doBlast( const BlastInfo *blastInfo )
// get other position
otherPos = other->getPosition();

#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE
Comment thread
Skyaero42 marked this conversation as resolved.
// compute vector from the missile to other object
forceVector.x = otherPos->x - missilePos->x;
forceVector.y = otherPos->y - missilePos->y;
forceVector.z = otherPos->z - missilePos->z;
#else
// compute vector from the missile to other object
// TheSuperHackers @tweak xezon 16/08/2026 No longer calculate the force vector to the center of the object,
// but to half way between the closest edge and the center of the object. This way a more appropriate
// damage value is sampled for a large structure inside the damage fall off range.
Coord2D toCenterVector;
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.


// flip direction
forceVector.x = -forceVector.x;
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.

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.

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 have no problem with a cylinder. Probably easier for players as well. But it is a balance change.

#endif
Comment on lines +363 to +366

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


// try to topple other object
other->topple( &forceVector, blastInfo->toppleSpeed, TOPPLE_OPTIONS_NO_BOUNCE |
Expand Down
Loading