From fbf3aa949122314da6aadea0a324b54028ecc47e Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 16 Aug 2026 17:14:53 +0200 Subject: [PATCH 1/5] bugfix(neutronmissile): Fix and improve Nuke Missile damage for large 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 --- Core/GameEngine/Include/Common/GameDefines.h | 14 ++++++-- .../Update/NeutronMissileSlowDeathUpdate.cpp | 32 ++++++++++++++++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index be20d29c292..959993f9e29 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -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 @@ -91,6 +88,17 @@ #define PRESERVE_RETAIL_PARTICLES (1) // Preserve original look of particles 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 diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp index 8a3e971fea4..339f2cf883f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp @@ -349,6 +349,7 @@ 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 defined(RTS_DEBUG) @@ -359,9 +360,17 @@ void NeutronMissileSlowDeathBehavior::doBlast( const BlastInfo *blastInfo ) } #endif +#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; @@ -374,10 +383,31 @@ void NeutronMissileSlowDeathBehavior::doBlast( const BlastInfo *blastInfo ) // get other position otherPos = other->getPosition(); +#if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE // 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); + + // 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; + forceVector.y = (forceVector.y + toCenterVector.y) * 0.5f; + forceVector.z = 0.0f; +#endif // try to topple other object other->topple( &forceVector, blastInfo->toppleSpeed, TOPPLE_OPTIONS_NO_BOUNCE | From ce9049c99bf748884c16c836cb1da45d15aeae06 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 17 Aug 2026 18:57:34 +0200 Subject: [PATCH 2/5] Readd Z to forceVector and rename variables --- .../Update/NeutronMissileSlowDeathUpdate.cpp | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp index 339f2cf883f..1f500c91796 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp @@ -393,20 +393,23 @@ void NeutronMissileSlowDeathBehavior::doBlast( const BlastInfo *blastInfo ) // 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; + Coord3D missileToObjectCenter; + missileToObjectCenter.x = otherPos->x - missilePos->x; + missileToObjectCenter.y = otherPos->y - missilePos->y; + missileToObjectCenter.z = otherPos->z - missilePos->z; - ThePartitionManager->getVectorTo(other, missilePos, FROM_BOUNDINGSPHERE_2D, forceVector); + Coord3D missileToObjectEdge; + ThePartitionManager->getVectorTo(other, missilePos, FROM_BOUNDINGSPHERE_2D, missileToObjectEdge); // 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; - forceVector.y = (forceVector.y + toCenterVector.y) * 0.5f; - forceVector.z = 0.0f; + missileToObjectEdge.x = -missileToObjectEdge.x; + missileToObjectEdge.y = -missileToObjectEdge.y; + missileToObjectEdge.z = -missileToObjectEdge.z; + + // take the average between the edge and center vectors + forceVector.x = (missileToObjectEdge.x + missileToObjectCenter.x) * 0.5f; + forceVector.y = (missileToObjectEdge.y + missileToObjectCenter.y) * 0.5f; + forceVector.z = (missileToObjectEdge.z + missileToObjectCenter.z) * 0.5f; #endif // try to topple other object From 6444eb6d242109bafa202e5f59ea86df00858fed Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:47:18 +0200 Subject: [PATCH 3/5] Use FROM_BOUNDINGSPHERE_3D --- .../GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp index 1f500c91796..0c46ea5186e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp @@ -365,7 +365,7 @@ void NeutronMissileSlowDeathBehavior::doBlast( const BlastInfo *blastInfo ) #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; + const DistanceCalculationType dc = FROM_BOUNDINGSPHERE_3D; #endif ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( missilePos, From 3eba883290e9896de1d706fe48f2a35b139a2c56 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:47:30 +0200 Subject: [PATCH 4/5] Simplify the math --- Core/Libraries/Include/Lib/BaseType.h | 54 +++++++++++++++++++ .../Update/NeutronMissileSlowDeathUpdate.cpp | 18 ++----- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/Core/Libraries/Include/Lib/BaseType.h b/Core/Libraries/Include/Lib/BaseType.h index 8b5e760aff4..2f5bc1e70f7 100644 --- a/Core/Libraries/Include/Lib/BaseType.h +++ b/Core/Libraries/Include/Lib/BaseType.h @@ -320,6 +320,19 @@ struct Coord2D sub(a); } + Coord2D operator+() const + { + return *this; + } + + Coord2D operator-() const + { + Coord2D c; + c.x = -x; + c.y = -y; + return c; + } + void set( const Coord2D &a ) { x = a.x; @@ -447,6 +460,19 @@ struct ICoord2D sub(a); } + ICoord2D operator+() const + { + return *this; + } + + ICoord2D operator-() const + { + ICoord2D c; + c.x = -x; + c.y = -y; + return c; + } + void set( const ICoord2D &a ) { x = a.x; @@ -577,6 +603,20 @@ struct Coord3D sub(a); } + Coord3D operator+() const + { + return *this; + } + + Coord3D operator-() const + { + Coord3D c; + c.x = -x; + c.y = -y; + c.z = -z; + return c; + } + void set( const Coord3D &a ) { x = a.x; @@ -670,6 +710,20 @@ struct ICoord3D sub(a); } + ICoord3D operator+() const + { + return *this; + } + + ICoord3D operator-() const + { + ICoord3D c; + c.x = -x; + c.y = -y; + c.z = -z; + return c; + } + void set( const ICoord3D &a ) { x = a.x; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp index 0c46ea5186e..4933566158d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp @@ -385,31 +385,23 @@ void NeutronMissileSlowDeathBehavior::doBlast( const BlastInfo *blastInfo ) #if RETAIL_COMPATIBLE_CRC || PRESERVE_RETAIL_NUKE_MISSILE_OUTER_RADIUS_DAMAGE // 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; + forceVector = *otherPos - *missilePos; #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. - Coord3D missileToObjectCenter; - missileToObjectCenter.x = otherPos->x - missilePos->x; - missileToObjectCenter.y = otherPos->y - missilePos->y; - missileToObjectCenter.z = otherPos->z - missilePos->z; + const Coord3D missileToObjectCenter = *otherPos - *missilePos; Coord3D missileToObjectEdge; ThePartitionManager->getVectorTo(other, missilePos, FROM_BOUNDINGSPHERE_2D, missileToObjectEdge); // flip direction - missileToObjectEdge.x = -missileToObjectEdge.x; - missileToObjectEdge.y = -missileToObjectEdge.y; - missileToObjectEdge.z = -missileToObjectEdge.z; + missileToObjectEdge = -missileToObjectEdge; // take the average between the edge and center vectors - forceVector.x = (missileToObjectEdge.x + missileToObjectCenter.x) * 0.5f; - forceVector.y = (missileToObjectEdge.y + missileToObjectCenter.y) * 0.5f; - forceVector.z = (missileToObjectEdge.z + missileToObjectCenter.z) * 0.5f; + forceVector = missileToObjectEdge + missileToObjectCenter; + forceVector.scale(0.5f); #endif // try to topple other object From 99cb13d0402549acc17355565b916b877934d8fa Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:29:19 +0200 Subject: [PATCH 5/5] Use more FROM_BOUNDINGSPHERE_3D --- .../GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp index 4933566158d..1d23b3cbd63 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp @@ -394,7 +394,7 @@ void NeutronMissileSlowDeathBehavior::doBlast( const BlastInfo *blastInfo ) const Coord3D missileToObjectCenter = *otherPos - *missilePos; Coord3D missileToObjectEdge; - ThePartitionManager->getVectorTo(other, missilePos, FROM_BOUNDINGSPHERE_2D, missileToObjectEdge); + ThePartitionManager->getVectorTo(other, missilePos, dc, missileToObjectEdge); // flip direction missileToObjectEdge = -missileToObjectEdge;