From 3c863c792558e4a8e31d435a16c5cb80640bc145 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Wed, 20 May 2026 23:37:27 +0200 Subject: [PATCH 1/3] fix(particlesys): Decouple creation of particle systems from game logic --- .../Code/GameEngine/Include/Common/Geometry.h | 7 +- .../Source/Common/System/Geometry.cpp | 85 ++++++++++++++----- .../Behavior/GenerateMinefieldBehavior.cpp | 2 +- .../Object/Damage/TransitionDamageFX.cpp | 29 ++++++- .../GameLogic/Object/Update/EMPUpdate.cpp | 27 +++++- .../Object/Update/SpecialAbilityUpdate.cpp | 12 ++- .../GameLogic/ScriptEngine/ScriptActions.cpp | 4 +- 7 files changed, 132 insertions(+), 34 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h b/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h index 221ca370ce0..8a87989e786 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h @@ -171,9 +171,10 @@ 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 makeRandomOffsetOnPerimeter(Coord3D& pt) const; //Chooses a random point on the extent border. + void makeGameLogicRandomOffsetWithinFootprint(Coord3D& pt) const; + void makeGameLogicRandomOffsetOnPerimeter(Coord3D& pt) const; + void makeGameClientRandomOffsetWithinFootprint(Coord3D& pt) const; + void makeGameClientRandomOffsetOnPerimeter(Coord3D& pt) const; 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..6d3c554725b 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,8 +416,8 @@ 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; } @@ -416,9 +425,14 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const } //============================================================================= -void GeometryInfo::makeRandomOffsetOnPerimeter(Coord3D& pt) const +static void makeRandomOffsetOnPerimeter( + Real (*randomReal)(Real lo, Real hi), + GeometryType type, + Real majorRadius, + Real minorRadius, + Coord3D& pt) { - switch(m_type) + switch(type) { case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: @@ -434,27 +448,27 @@ void GeometryInfo::makeRandomOffsetOnPerimeter(Coord3D& pt) const case GEOMETRY_BOX: { - if( GameLogicRandomValueReal( 0.0f, 1.0f ) < 0.5f ) + if( randomReal( 0.0f, 1.0f ) < 0.5f ) { //Pick random point on x axis. - pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); + pt.x = randomReal(-majorRadius, majorRadius); //Min or max the y axis value - if( GameLogicRandomValueReal( 0.0f, 1.0f ) < 0.5f ) - pt.y = -m_minorRadius; + if( randomReal( 0.0f, 1.0f ) < 0.5f ) + pt.y = -minorRadius; else - pt.y = m_minorRadius; + pt.y = minorRadius; } else { //Pick random point on y axis. - pt.y = GameLogicRandomValueReal(-m_minorRadius, m_minorRadius); + pt.y = randomReal(-minorRadius, minorRadius); //Min or max the x axis value - if( GameLogicRandomValueReal( 0.0f, 1.0f ) < 0.5f ) - pt.x = -m_majorRadius; + if( randomReal( 0.0f, 1.0f ) < 0.5f ) + pt.x = -majorRadius; else - pt.x = m_majorRadius; + pt.x = majorRadius; } pt.z = 0.0f; break; @@ -462,6 +476,37 @@ void GeometryInfo::makeRandomOffsetOnPerimeter(Coord3D& pt) const }; } +//----------------------------------------------------------------------------- +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::makeGameLogicRandomOffsetOnPerimeter(Coord3D& pt) const +{ + makeRandomOffsetOnPerimeter(GameLogicGeometryRandomReal, m_type, m_majorRadius, m_minorRadius, pt); +} + +void GeometryInfo::makeGameClientRandomOffsetOnPerimeter(Coord3D& pt) const +{ + makeRandomOffsetOnPerimeter(GameClientGeometryRandomReal, m_type, m_majorRadius, m_minorRadius, pt); +} + //============================================================================= Real GeometryInfo::getFootprintArea() 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..e006c82857b 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 @@ -416,6 +416,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(pSystemT) + { + if( lastDamageInfo == nullptr || + getDamageTypeFlag( modData->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) ) + { + 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..10c603bcf77 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,31 @@ 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(tmp) + { + Real victimHeight = curVictim->getGeometryInfo().getMaxHeightAbovePosition(); + Real victimFootprintArea = curVictim->getGeometryInfo().getFootprintArea(); + Real victimVolume = victimFootprintArea * MIN(victimHeight, 10.0f); + UnsignedInt emitterCount = MAX(15, REAL_TO_INT_CEIL(data->m_sparksPerCubicFoot * victimVolume)); + for (UnsignedInt e = 0 ; e < emitterCount; ++e) + { + 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..072f92b2390 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp @@ -1404,13 +1404,23 @@ 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; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index 023533d1b4a..abbbd21cd96 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -3729,7 +3729,7 @@ void ScriptActions::doNamedSetBoobytrapped( const AsciiString& thingTemplateName { //The charge gets positioned randomly on the outside of the perimeter of the victim. Coord3D pos; - obj->getGeometryInfo().makeRandomOffsetOnPerimeter( pos ); + obj->getGeometryInfo().makeGameLogicRandomOffsetOnPerimeter( pos ); //Get the angle and transform matrix from the obj... then transform the calculated //position @@ -3768,7 +3768,7 @@ void ScriptActions::doTeamSetBoobytrapped( const AsciiString& thingTemplateName, { //The charge gets positioned randomly on the outside of the perimeter of the victim. Coord3D pos; - obj->getGeometryInfo().makeRandomOffsetOnPerimeter( pos ); + obj->getGeometryInfo().makeGameLogicRandomOffsetOnPerimeter( pos ); //Get the angle and transform matrix from the obj... then transform the calculated //position From 8e843317cb9bc024d07c04b53a08e60112b5c836 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Thu, 21 May 2026 09:37:04 +0200 Subject: [PATCH 2/3] Revert RandomOffsetOnPerimeter, fix logic error --- .../Code/GameEngine/Include/Common/Geometry.h | 3 +- .../Source/Common/System/Geometry.cpp | 78 ++++++++----------- .../Object/Update/SpecialAbilityUpdate.cpp | 19 ++--- .../GameLogic/ScriptEngine/ScriptActions.cpp | 4 +- 4 files changed, 44 insertions(+), 60 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h b/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h index 8a87989e786..a4de0f57611 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Geometry.h @@ -172,9 +172,8 @@ class GeometryInfo : public Snapshot void get2DBounds(const Coord3D& geomCenter, Real angle, Region2D& bounds ) const; void makeGameLogicRandomOffsetWithinFootprint(Coord3D& pt) const; - void makeGameLogicRandomOffsetOnPerimeter(Coord3D& pt) const; void makeGameClientRandomOffsetWithinFootprint(Coord3D& pt) const; - void makeGameClientRandomOffsetOnPerimeter(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 6d3c554725b..3fae44a5551 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp @@ -424,15 +424,30 @@ static void makeRandomOffsetWithinFootprint( }; } +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); +} + //============================================================================= -static void makeRandomOffsetOnPerimeter( - Real (*randomReal)(Real lo, Real hi), - GeometryType type, - Real majorRadius, - Real minorRadius, - Coord3D& pt) +void GeometryInfo::makeRandomOffsetOnPerimeter(Coord3D& pt) const { - switch(type) + switch(m_type) { case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: @@ -448,27 +463,27 @@ static void makeRandomOffsetOnPerimeter( case GEOMETRY_BOX: { - if( randomReal( 0.0f, 1.0f ) < 0.5f ) + if( GameLogicRandomValueReal( 0.0f, 1.0f ) < 0.5f ) { //Pick random point on x axis. - pt.x = randomReal(-majorRadius, majorRadius); + pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius); //Min or max the y axis value - if( randomReal( 0.0f, 1.0f ) < 0.5f ) - pt.y = -minorRadius; + if( GameLogicRandomValueReal( 0.0f, 1.0f ) < 0.5f ) + pt.y = -m_minorRadius; else - pt.y = minorRadius; + pt.y = m_minorRadius; } else { //Pick random point on y axis. - pt.y = randomReal(-minorRadius, minorRadius); + pt.y = GameLogicRandomValueReal(-m_minorRadius, m_minorRadius); //Min or max the x axis value - if( randomReal( 0.0f, 1.0f ) < 0.5f ) - pt.x = -majorRadius; + if( GameLogicRandomValueReal( 0.0f, 1.0f ) < 0.5f ) + pt.x = -m_majorRadius; else - pt.x = majorRadius; + pt.x = m_majorRadius; } pt.z = 0.0f; break; @@ -476,37 +491,6 @@ static void makeRandomOffsetOnPerimeter( }; } -//----------------------------------------------------------------------------- -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::makeGameLogicRandomOffsetOnPerimeter(Coord3D& pt) const -{ - makeRandomOffsetOnPerimeter(GameLogicGeometryRandomReal, m_type, m_majorRadius, m_minorRadius, pt); -} - -void GeometryInfo::makeGameClientRandomOffsetOnPerimeter(Coord3D& pt) const -{ - makeRandomOffsetOnPerimeter(GameClientGeometryRandomReal, m_type, m_majorRadius, m_minorRadius, pt); -} - //============================================================================= Real GeometryInfo::getFootprintArea() const { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp index 072f92b2390..b74f66672b5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp @@ -1409,19 +1409,20 @@ void SpecialAbilityUpdate::triggerAbilityEffect() 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 + // 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(tmp) + { + Coord3D offs = { 0,0,0 }; + target->getGeometryInfo().makeGameLogicRandomOffsetWithinFootprint(offs); } +#endif } break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp index abbbd21cd96..023533d1b4a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptActions.cpp @@ -3729,7 +3729,7 @@ void ScriptActions::doNamedSetBoobytrapped( const AsciiString& thingTemplateName { //The charge gets positioned randomly on the outside of the perimeter of the victim. Coord3D pos; - obj->getGeometryInfo().makeGameLogicRandomOffsetOnPerimeter( pos ); + obj->getGeometryInfo().makeRandomOffsetOnPerimeter( pos ); //Get the angle and transform matrix from the obj... then transform the calculated //position @@ -3768,7 +3768,7 @@ void ScriptActions::doTeamSetBoobytrapped( const AsciiString& thingTemplateName, { //The charge gets positioned randomly on the outside of the perimeter of the victim. Coord3D pos; - obj->getGeometryInfo().makeGameLogicRandomOffsetOnPerimeter( pos ); + obj->getGeometryInfo().makeRandomOffsetOnPerimeter( pos ); //Get the angle and transform matrix from the obj... then transform the calculated //position From 852d3ef3ad0a5492ced16d9cc20eddcb2ddafec0 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Fri, 22 May 2026 15:26:21 +0200 Subject: [PATCH 3/3] Simplify ahead of 2740 --- .../Object/Damage/TransitionDamageFX.cpp | 36 +++++++++---------- .../GameLogic/Object/Update/EMPUpdate.cpp | 30 +++++++--------- .../Object/Update/SpecialAbilityUpdate.cpp | 18 +++++----- 3 files changed, 37 insertions(+), 47 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp index e006c82857b..0df6f3072d9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp @@ -412,33 +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(pSystemT) - { - if( lastDamageInfo == nullptr || - getDamageTypeFlag( modData->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) ) - { - const FXLocInfo* locInfo = &modData->m_particleSystem[newState][i].locInfo; - if( locInfo->locType == FX_DAMAGE_LOC_TYPE_BONE && locInfo->randomBone && draw ) + // 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 Int MAX_BONES = 32; - Coord3D positions[ MAX_BONES ]; - Int boneCount = draw->getPristineBonePositions( locInfo->boneName.str(), 1, positions, nullptr, MAX_BONES ); - if( boneCount > 0 ) + const FXLocInfo* locInfo = &modData->m_particleSystem[newState][i].locInfo; + if( locInfo->locType == FX_DAMAGE_LOC_TYPE_BONE && locInfo->randomBone && draw ) { - static_cast(GameLogicRandomValue( 0, boneCount - 1 )); + 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 + } + } -#endif } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp index 10c603bcf77..20373ad4472 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp @@ -330,28 +330,22 @@ void EMPUpdate::doDisableAttack() sys->setSystemLifetime(MAX(0, data->m_disabledDuration - 30)); 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(tmp) - { - Real victimHeight = curVictim->getGeometryInfo().getMaxHeightAbovePosition(); - Real victimFootprintArea = curVictim->getGeometryInfo().getFootprintArea(); - Real victimVolume = victimFootprintArea * MIN(victimHeight, 10.0f); - UnsignedInt emitterCount = MAX(15, REAL_TO_INT_CEIL(data->m_sparksPerCubicFoot * victimVolume)); - for (UnsignedInt e = 0 ; e < emitterCount; ++e) - { - Coord3D offs = { 0,0,0 }; - curVictim->getGeometryInfo().makeGameLogicRandomOffsetWithinFootprint(offs); - static_cast(GameLogicRandomValue(0, 1)); - static_cast(GameLogicRandomValue(0, 1)); + // 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 } } -#endif + } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp index b74f66672b5..9f1979c768f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp @@ -1411,18 +1411,18 @@ void SpecialAbilityUpdate::triggerAbilityEffect() 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(tmp) - { - Coord3D offs = { 0,0,0 }; - target->getGeometryInfo().makeGameLogicRandomOffsetWithinFootprint(offs); - } + // 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; }