From 9a9c792b1c8ad94e1e866cad47add8c88302bb47 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/2] 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 | 33 ++++++++++++++++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index d783c359c4c..af92e368298 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 @@ -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 diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp index 55d59777358..038f77db30a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp @@ -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; @@ -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 // 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 80b8d0d64b57cf1cffcf4b3e91529f149d41406b 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/2] 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 038f77db30a..54660ec92a6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/NeutronMissileSlowDeathUpdate.cpp @@ -349,20 +349,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