Skip to content

fix(particlesys): Decouple creation of particle systems from game logic#2739

Closed
stephanmeesters wants to merge 3 commits into
TheSuperHackers:mainfrom
stephanmeesters:fix/decouple-particle-creation-gamelogic
Closed

fix(particlesys): Decouple creation of particle systems from game logic#2739
stephanmeesters wants to merge 3 commits into
TheSuperHackers:mainfrom
stephanmeesters:fix/decouple-particle-creation-gamelogic

Conversation

@stephanmeesters
Copy link
Copy Markdown

@stephanmeesters stephanmeesters commented May 20, 2026

Based on branch by Caball.

Todo

  • Replicate in Generals
  • Test on a large number of replays


// 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 );
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to make this more elegant, by accepting generally structs from FXDamageFXListInfo, FXDamageOCLInfo or FXDamageParticleSystemInfo and deciding based on that whether to use game logic or client logic RNG, but we can't touch the structs really without causing mismatching.

for (UnsignedInt e = 0 ; e < emitterCount; ++e)
{
Coord3D offs = { 0,0,0 };
curVictim->getGeometryInfo().makeGameLogicRandomOffsetWithinFootprint(offs);
Copy link
Copy Markdown
Author

@stephanmeesters stephanmeesters May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best to keep these as-is as not much was gained by unrolling

if(sys)
{
Coord3D offs = { 0,0,0 };
target->getGeometryInfo().makeGameLogicRandomOffsetWithinFootprint(offs);
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 20, 2026

Greptile Summary

This PR decouples particle system creation from game logic by splitting makeRandomOffsetWithinFootprint into separate makeGameLogicRandomOffsetWithinFootprint and makeGameClientRandomOffsetWithinFootprint variants, and switches the visual particle system placement calls in EMPUpdate, TransitionDamageFX, and SpecialAbilityUpdate to use game-client RNG instead of game-logic RNG.

  • Geometry refactor: The old member function is replaced by a private static helper parameterised with a function-pointer, and two public thin-wrapper methods are exposed — one per RNG domain.
  • Game logic RNG forwarding: Under #if RETAIL_COMPATIBLE_CRC, each affected call site replays the original game-logic RNG advancement (same geometry, same call count) so that CRC-based replay compatibility is preserved even though the functional path now uses client RNG.
  • getLocalEffectPos extension: An optional useGameClientRandom parameter (default FALSE) is added to avoid touching the OCL code path, keeping bone-pick behaviour identical for object-creation effects while switching only the particle-system path.

Confidence Score: 5/5

Safe to merge for GeneralsMD; Generals counterpart is tracked as a separate follow-up TODO in the PR.

The refactoring is mechanical and well-scoped: geometry offset calls in particle-system paths are switched to client RNG, and every RETAIL_COMPATIBLE_CRC block gates its game-logic RNG forwarding on the same null check as the original code, preserving RNG advancement count exactly. The one intentional functional gap — Generals/ not yet updated — is explicitly called out in the PR description.

No files require special attention. The Generals/ directory equivalents are intentionally deferred per the PR TODO list.

Important Files Changed

Filename Overview
GeneralsMD/Code/GameEngine/Include/Common/Geometry.h Old makeRandomOffsetWithinFootprint declaration removed and replaced with two clearly-named public variants; already uses #pragma once.
GeneralsMD/Code/GameEngine/Source/Common/System/Geometry.cpp Static helper refactored to accept a function-pointer for RNG, with two thin forwarding wrappers; logic identical to original for both geometry types.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp Trivial rename from makeRandomOffsetWithinFootprint to makeGameLogicRandomOffsetWithinFootprint; semantics unchanged.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Damage/TransitionDamageFX.cpp getLocalEffectPos gains useGameClientRandom param; particle-system path now uses client RNG; RETAIL block correctly gates game-logic RNG forwarding on if(pSystem) matching the original guard.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/EMPUpdate.cpp All three game-logic RNG calls (offset, z-height, initial delay) correctly moved to client RNG; RETAIL block inside if(sys) advances logic RNG the same number of times as the original.
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpecialAbilityUpdate.cpp Single offset call correctly switched to client RNG; RETAIL block inside if(sys) mirrors original logic-RNG advancement.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Particle System Creation Site] --> B{pSystemT / tmp exists?}
    B -- No --> Z[Skip — no RNG advanced]
    B -- Yes --> C[createParticleSystem]
    C --> D{sys != null?}
    D -- No --> Z2[Skip — no RNG advanced]
    D -- Yes --> E[makeGameClientRandomOffsetWithinFootprint\nGameClientRandomValue calls\nVisual positioning only]
    E --> F{RETAIL_COMPATIBLE_CRC?}
    F -- No --> G[Done]
    F -- Yes --> H[makeGameLogicRandomOffsetWithinFootprint\nGameLogicRandomValue calls\nDiscard results — advance RNG state only]
    H --> G

    subgraph GenerateMinefieldBehavior
        M[makeGameLogicRandomOffsetWithinFootprint\nGame logic RNG — affects simulation]
    end
Loading

Reviews (5): Last reviewed commit: "Simplify ahead of 2740" | Re-trigger Greptile

@Caball009
Copy link
Copy Markdown

This should be tested against a large number of replays.

@stephanmeesters stephanmeesters force-pushed the fix/decouple-particle-creation-gamelogic branch from bd19747 to 968de38 Compare May 21, 2026 07:41
@stephanmeesters stephanmeesters force-pushed the fix/decouple-particle-creation-gamelogic branch from 968de38 to 8e84331 Compare May 21, 2026 07:50
@xezon xezon added Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Fix Is fixing something, but is not user facing labels May 21, 2026
@xezon
Copy link
Copy Markdown

xezon commented May 22, 2026

While this change is technically ok, the implementation details are a bit complicated. I created an alternative fix with #2742 to address it.

@stephanmeesters
Copy link
Copy Markdown
Author

While this change is technically ok, the implementation details are a bit complicated. I created an alternative fix with #2742 to address it.

Very nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Fix Is fixing something, but is not user facing Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Particle systems cause game logic / CRC changes

3 participants