diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h b/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h index 221ca370ce0..a4de0f57611 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h @@ -171,8 +171,8 @@ class GeometryInfo : public Snapshot /// get the 2d bounding box void get2DBounds(const Coord3D& geomCenter, Real angle, Region2D& bounds ) const; - /// note that the pt is generated using game logic random, not game client random! - void makeRandomOffsetWithinFootprint(Coord3D& pt) const; + void makeGameLogicRandomOffsetWithinFootprint(Coord3D& pt) const; + void makeGameClientRandomOffsetWithinFootprint(Coord3D& pt) const; void makeRandomOffsetOnPerimeter(Coord3D& pt) const; //Chooses a random point on the extent border. void clipPointToFootprint(const Coord3D& geomCenter, Coord3D& ptToClip) const; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp index b69c01de560..3fae44a5551 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp @@ -376,9 +376,18 @@ Bool GeometryInfo::isPointInFootprint(const Coord3D& geomCenter, const Coord3D& } //============================================================================= -void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const +static void makeRandomOffsetWithinFootprint( + Real (*randomReal)(Real lo, Real hi), + GeometryType type, + Real majorRadius, + Real minorRadius, + Real boundingCircleRadius, + Coord3D& pt) { - switch(m_type) +#if 1 + (void)boundingCircleRadius; +#endif + switch(type) { case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: @@ -386,18 +395,18 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const #if 1 // this is a better technique than the more obvious radius-and-angle // one, below, because the latter tends to clump more towards the center. - Real maxDistSqr = sqr(m_majorRadius); + Real maxDistSqr = sqr(majorRadius); Real distSqr; do { - pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); - pt.y = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); + pt.x = randomReal(-majorRadius, majorRadius); + pt.y = randomReal(-majorRadius, majorRadius); pt.z = 0.0f; distSqr = sqr(pt.x) + sqr(pt.y); } while (distSqr > maxDistSqr); #else - Real radius = GameLogicRandomValueReal(0.0f, m_boundingCircleRadius); - Real angle = GameLogicRandomValueReal(-PI, PI); + Real radius = randomReal(0.0f, boundingCircleRadius); + Real angle = randomReal(-PI, PI); pt.x = radius * Cos(angle); pt.y = radius * Sin(angle); pt.z = 0.0f; @@ -407,14 +416,34 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const case GEOMETRY_BOX: { - pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); - pt.y = GameLogicRandomValueReal(-m_minorRadius, m_minorRadius); + pt.x = randomReal(-majorRadius, majorRadius); + pt.y = randomReal(-minorRadius, minorRadius); pt.z = 0.0f; break; } }; } +static Real GameLogicGeometryRandomReal(Real lo, Real hi) +{ + return GameLogicRandomValueReal(lo, hi); +} + +static Real GameClientGeometryRandomReal(Real lo, Real hi) +{ + return GameClientRandomValueReal(lo, hi); +} + +void GeometryInfo::makeGameLogicRandomOffsetWithinFootprint(Coord3D& pt) const +{ + makeRandomOffsetWithinFootprint(GameLogicGeometryRandomReal, m_type, m_majorRadius, m_minorRadius, m_boundingCircleRadius, pt); +} + +void GeometryInfo::makeGameClientRandomOffsetWithinFootprint(Coord3D& pt) const +{ + makeRandomOffsetWithinFootprint(GameClientGeometryRandomReal, m_type, m_majorRadius, m_minorRadius, m_boundingCircleRadius, pt); +} + //============================================================================= void GeometryInfo::makeRandomOffsetOnPerimeter(Coord3D& pt) const { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp index 03194dc1981..c091e8766d4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp @@ -350,7 +350,7 @@ void GenerateMinefieldBehavior::placeMinesInFootprint(const GeometryInfo& geom, Int maxRetry = 100; do { - geom.makeRandomOffsetWithinFootprint(pt); + geom.makeGameLogicRandomOffsetWithinFootprint(pt); pt.x += target->x; pt.y += target->y; pt.z += target->z; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp index 9e201c1508f..0df6f3072d9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp @@ -253,7 +253,7 @@ void TransitionDamageFX::onDelete() /** Given an FXLoc info struct, return the effect position that we are supposed to use. * The position is local to to the object */ //------------------------------------------------------------------------------------------------- -static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw ) +static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw, Bool useGameClientRandom = FALSE ) { DEBUG_ASSERTCRASH( locInfo, ("getLocalEffectPos: locInfo is null") ); @@ -290,7 +290,7 @@ static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw ) return locInfo->loc; // pick one of the bone positions - Int pick = GameLogicRandomValue( 0, boneCount - 1 ); + Int pick = useGameClientRandom ? GameClientRandomValue( 0, boneCount - 1 ) : GameLogicRandomValue( 0, boneCount - 1 ); return positions[ pick ]; } @@ -394,7 +394,7 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, { // get the what is the position we're going to played the effect at - pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw ); + pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw, TRUE ); // // set position on system given any bone position provided, the bone position is @@ -412,10 +412,29 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo, } +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix stephanmeesters 18/05/2026 Fix issue where the creation of a certain particle system + // would influence game logic CRC due to the incorrect usage of game logic RNG. This code block is required to + // forward the game logic RNG and keep things consistent. + if ( pSystem ) + { + const FXLocInfo* locInfo = &modData->m_particleSystem[newState][i].locInfo; + if( locInfo->locType == FX_DAMAGE_LOC_TYPE_BONE && locInfo->randomBone && draw ) + { + const Int MAX_BONES = 32; + Coord3D positions[ MAX_BONES ]; + Int boneCount = draw->getPristineBonePositions( locInfo->boneName.str(), 1, positions, nullptr, MAX_BONES ); + if( boneCount > 0 ) + { + static_cast(GameLogicRandomValue( 0, boneCount - 1 )); + } + } + } +#endif + } } - } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index aba51b4c2ee..20373ad4472 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -310,8 +310,8 @@ void EMPUpdate::doDisableAttack() if (sys) { Coord3D offs = {0,0,0}; - curVictim->getGeometryInfo().makeRandomOffsetWithinFootprint( offs ); - offs.z = GameLogicRandomValue(3, victimHeight); + curVictim->getGeometryInfo().makeGameClientRandomOffsetWithinFootprint( offs ); + offs.z = GameClientRandomValue(3, victimHeight); //This puts all the sparks within a quadrahemicycloid (rectangular dome) volume //The same shape as a four cornered camping dome tent, for those with less Greek @@ -328,12 +328,25 @@ void EMPUpdate::doDisableAttack() sys->attachToObject(curVictim); sys->setPosition( &offs ); sys->setSystemLifetime(MAX(0, data->m_disabledDuration - 30)); - sys->setInitialDelay(GameLogicRandomValue(1,100)); + sys->setInitialDelay(GameClientRandomValue(1,100)); } + +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix stephanmeesters 18/05/2026 Fix issue where the creation of a certain particle system + // would influence game logic CRC due to the incorrect usage of game logic RNG. This code block is required to + // forward the game logic RNG and keep things consistent. + if (sys) + { + Coord3D offs = { 0,0,0 }; + curVictim->getGeometryInfo().makeGameLogicRandomOffsetWithinFootprint(offs); + static_cast(GameLogicRandomValue(0, 1)); + static_cast(GameLogicRandomValue(0, 1)); + } +#endif } } - } + } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp index fbbc907f31b..9f1979c768f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp @@ -1404,13 +1404,24 @@ void SpecialAbilityUpdate::triggerAbilityEffect() if (sys) { Coord3D offs = {0,0,0}; - target->getGeometryInfo().makeRandomOffsetWithinFootprint( offs ); + target->getGeometryInfo().makeGameClientRandomOffsetWithinFootprint( offs ); sys->attachToObject(target); sys->setPosition( &offs ); sys->setSystemLifetime( data->m_effectDuration * durationInterleaveFactor ); //lifetime of the system, not the particles } + +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @fix stephanmeesters 18/05/2026 Fix issue where the creation of a certain particle system + // would influence game logic CRC due to the incorrect usage of game logic RNG. This code block is required to + // forward the game logic RNG and keep things consistent. + if (sys) + { + Coord3D offs = { 0,0,0 }; + target->getGeometryInfo().makeGameLogicRandomOffsetWithinFootprint(offs); + } +#endif } } break;