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..54660ec92a6 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,34 @@ 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. + Coord3D missileToObjectCenter; + missileToObjectCenter.x = otherPos->x - missilePos->x; + missileToObjectCenter.y = otherPos->y - missilePos->y; + missileToObjectCenter.z = otherPos->z - missilePos->z; + + Coord3D missileToObjectEdge; + ThePartitionManager->getVectorTo(other, missilePos, FROM_BOUNDINGSPHERE_2D, missileToObjectEdge); + + // flip direction + 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 other->topple( &forceVector, blastInfo->toppleSpeed, TOPPLE_OPTIONS_NO_BOUNCE |