From 831ea01a6f50aec00b8b15e034ae8eb169f9e8fe Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Fri, 15 May 2026 11:53:59 +0200 Subject: [PATCH 1/4] fix(particlesys): Simplify ParticleSystemManagerDummy setup --- .../Include/GameClient/ParticleSys.h | 45 +++++++++++++++++-- .../Source/Common/ReplaySimulation.cpp | 2 - .../Drawable/Update/BeaconClientUpdate.cpp | 6 +-- Core/GameEngine/Source/GameClient/FXList.cpp | 2 +- .../Include/GameClient/GameClient.h | 2 - .../Source/GameClient/GameClient.cpp | 9 ---- 6 files changed, 45 insertions(+), 21 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index 5054cd2b8f8..e9b391c3baa 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -753,6 +753,8 @@ class ParticleSystemManager : public SubsystemInterface, virtual void reset() override; ///< reset the manager and all particle systems virtual void update() override; ///< update all particle systems + virtual Bool isDummy() const { return false; } + virtual Int getOnScreenParticleCount() = 0; ///< returns the number of particles on screen virtual void setOnScreenParticleCount(int count); @@ -761,8 +763,7 @@ class ParticleSystemManager : public SubsystemInterface, ParticleSystemTemplate *newTemplate( const AsciiString &name ); /// given a template, instantiate a particle system - ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate, - Bool createSlaves = TRUE ); + virtual ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE ); /** given a template, instantiate a particle system. if attachTo is not null, attach the particle system to the given object. @@ -835,15 +836,53 @@ class ParticleSystemManager : public SubsystemInterface, ParticleSystemIDMap m_systemMap; ///< a hash map of all particle systems }; + // TheSuperHackers @feature bobtista 31/01/2026 -// ParticleSystemManager that does nothing. Used for Headless Mode. +// ParticleSystemManager that does nothing. Cannot create particle systems and templates. Used for Headless Mode. class ParticleSystemManagerDummy : public ParticleSystemManager { +#if RETAIL_COMPATIBLE_CRC + struct StaticParticleSystemTemplate : public ParticleSystemTemplate + { + StaticParticleSystemTemplate() + : ParticleSystemTemplate("dummy") {} + }; + struct StaticParticleSystem : public ParticleSystem + { + StaticParticleSystem(const StaticParticleSystemTemplate *sysTemplate) + : ParticleSystem(sysTemplate, ParticleSystemID(0), TRUE) {} + }; +#endif + public: +#if RETAIL_COMPATIBLE_CRC + // Must not overload init to keep loading the particle system templates, + // which are unfortunately needed to preserve the correct logic crc. +#else + virtual void init() override {} + virtual void reset() override {} +#endif + virtual void update() override {} + + virtual Bool isDummy() const override { return true; } + virtual Int getOnScreenParticleCount() override { return 0; } virtual void doParticles(RenderInfoClass &rinfo) override {} virtual void queueParticleRender() override {} + virtual ParticleSystem *createParticleSystem(const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE) override + { +#if RETAIL_COMPATIBLE_CRC + if (sysTemplate == nullptr) + return nullptr; + static StaticParticleSystemTemplate dummyTemplate; + static StaticParticleSystem dummySystem(&dummyTemplate); + return &dummySystem; +#else + return nullptr; +#endif + } + protected: virtual void crc( Xfer *xfer ) override {} virtual void xfer( Xfer *xfer ) override {} diff --git a/Core/GameEngine/Source/Common/ReplaySimulation.cpp b/Core/GameEngine/Source/Common/ReplaySimulation.cpp index 7d18b5cb58f..e6871769a94 100644 --- a/Core/GameEngine/Source/Common/ReplaySimulation.cpp +++ b/Core/GameEngine/Source/Common/ReplaySimulation.cpp @@ -86,8 +86,6 @@ int ReplaySimulation::simulateReplaysInThisProcess(const std::vectorgetPlaybackFrameCount() / LOGICFRAMES_PER_SECOND; while (TheRecorder->isPlaybackInProgress()) { - TheGameClient->updateHeadless(); - const int progressFrameInterval = 10*60*LOGICFRAMES_PER_SECOND; if (TheGameLogic->getFrame() != 0 && TheGameLogic->getFrame() % progressFrameInterval == 0) { diff --git a/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp b/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp index 63071f8340c..77a594ab1df 100644 --- a/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp +++ b/Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp @@ -94,9 +94,7 @@ static ParticleSystem* createParticleSystem( Drawable *draw ) AsciiString templateName; templateName.format("BeaconSmoke%6.6X", (0xffffff & obj->getIndicatorColor())); const ParticleSystemTemplate *particleTemplate = TheParticleSystemManager->findTemplate( templateName ); - - DEBUG_ASSERTCRASH(particleTemplate, ("Could not find particle system %s", templateName.str())); - + DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || particleTemplate, ("Could not find particle system %s", templateName.str())); if (particleTemplate) { system = TheParticleSystemManager->createParticleSystem( particleTemplate ); @@ -107,7 +105,7 @@ static ParticleSystem* createParticleSystem( Drawable *draw ) {// THis this will whip up a new particle system to match the house color provided templateName.format("BeaconSmokeFFFFFF"); const ParticleSystemTemplate *failsafeTemplate = TheParticleSystemManager->findTemplate( templateName ); - DEBUG_ASSERTCRASH(failsafeTemplate, ("Doh, this is bad \n I Could not even find the white particle system to make a failsafe system out of.")); + DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || failsafeTemplate, ("Doh, this is bad \n I Could not even find the white particle system to make a failsafe system out of.")); system = TheParticleSystemManager->createParticleSystem( failsafeTemplate ); if (system) { diff --git a/Core/GameEngine/Source/GameClient/FXList.cpp b/Core/GameEngine/Source/GameClient/FXList.cpp index 50ab4135ae0..a9f630638fd 100644 --- a/Core/GameEngine/Source/GameClient/FXList.cpp +++ b/Core/GameEngine/Source/GameClient/FXList.cpp @@ -600,7 +600,7 @@ class ParticleSystemFXNugget : public FXNugget } const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate(m_name); - DEBUG_ASSERTCRASH(tmp, ("ParticleSystem %s not found",m_name.str())); + DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || tmp, ("ParticleSystem %s not found",m_name.str())); if (tmp) { for (Int i = 0; i < m_count; i++ ) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h index ea8e3a59c5d..3650be5ee24 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h @@ -98,8 +98,6 @@ class GameClient : public SubsystemInterface, void step(); ///< Do one fixed time step - void updateHeadless(); - void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index cb91389555b..d1c956e533a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -787,15 +787,6 @@ void GameClient::step() TheDisplay->step(); } -void GameClient::updateHeadless() -{ - // TheSuperHackers @info helmutbuhler 03/05/2025 bobtista 02/02/2026 - // Update particles to prevent accumulation in headless mode. Particles are generated - // during GameLogic and only cleaned up during rendering. update() lets particles finish - // their lifecycle naturally instead of abruptly removing them with reset(). - TheParticleSystemManager->update(); -} - Bool GameClient::isMovieAbortRequested() { if (TheGameEngine) From 90f01ef76eb7e40600bf392a70a6d34a79596d1f Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Fri, 22 May 2026 21:22:59 +0200 Subject: [PATCH 2/4] Pass false --- Core/GameEngine/Include/GameClient/ParticleSys.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index e9b391c3baa..b8e9ba698ed 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -850,7 +850,7 @@ class ParticleSystemManagerDummy : public ParticleSystemManager struct StaticParticleSystem : public ParticleSystem { StaticParticleSystem(const StaticParticleSystemTemplate *sysTemplate) - : ParticleSystem(sysTemplate, ParticleSystemID(0), TRUE) {} + : ParticleSystem(sysTemplate, ParticleSystemID(0), false) {} }; #endif From e82a03b1129d637c425d76173d140fa35c2a9a86 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 25 May 2026 13:01:55 +0200 Subject: [PATCH 3/4] Remove now unnecessary overload of createParticleSystem --- .../Include/GameClient/ParticleSys.h | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index b8e9ba698ed..e65d8773675 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -763,7 +763,7 @@ class ParticleSystemManager : public SubsystemInterface, ParticleSystemTemplate *newTemplate( const AsciiString &name ); /// given a template, instantiate a particle system - virtual ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE ); + ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE ); /** given a template, instantiate a particle system. if attachTo is not null, attach the particle system to the given object. @@ -838,22 +838,10 @@ class ParticleSystemManager : public SubsystemInterface, // TheSuperHackers @feature bobtista 31/01/2026 -// ParticleSystemManager that does nothing. Cannot create particle systems and templates. Used for Headless Mode. +// ParticleSystemManager that does nothing. Used for Headless Mode. +// Generally does not load particle system templates. Certainly does not create particle systems. class ParticleSystemManagerDummy : public ParticleSystemManager { -#if RETAIL_COMPATIBLE_CRC - struct StaticParticleSystemTemplate : public ParticleSystemTemplate - { - StaticParticleSystemTemplate() - : ParticleSystemTemplate("dummy") {} - }; - struct StaticParticleSystem : public ParticleSystem - { - StaticParticleSystem(const StaticParticleSystemTemplate *sysTemplate) - : ParticleSystem(sysTemplate, ParticleSystemID(0), false) {} - }; -#endif - public: #if RETAIL_COMPATIBLE_CRC // Must not overload init to keep loading the particle system templates, @@ -870,19 +858,6 @@ class ParticleSystemManagerDummy : public ParticleSystemManager virtual void doParticles(RenderInfoClass &rinfo) override {} virtual void queueParticleRender() override {} - virtual ParticleSystem *createParticleSystem(const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE) override - { -#if RETAIL_COMPATIBLE_CRC - if (sysTemplate == nullptr) - return nullptr; - static StaticParticleSystemTemplate dummyTemplate; - static StaticParticleSystem dummySystem(&dummyTemplate); - return &dummySystem; -#else - return nullptr; -#endif - } - protected: virtual void crc( Xfer *xfer ) override {} virtual void xfer( Xfer *xfer ) override {} From 301075d3e2cd1355001931d762c1006276482e32 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Mon, 25 May 2026 13:10:26 +0200 Subject: [PATCH 4/4] Replicate in Generals --- Generals/Code/GameEngine/Include/GameClient/GameClient.h | 2 -- .../Code/GameEngine/Source/GameClient/GameClient.cpp | 9 --------- 2 files changed, 11 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameClient/GameClient.h b/Generals/Code/GameEngine/Include/GameClient/GameClient.h index 7d42104edb4..7183371d887 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameClient.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameClient.h @@ -94,8 +94,6 @@ class GameClient : public SubsystemInterface, void step(); ///< Do one fixed time step - void updateHeadless(); - void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index b9678a641ef..e2d293df015 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -750,15 +750,6 @@ void GameClient::step() TheDisplay->step(); } -void GameClient::updateHeadless() -{ - // TheSuperHackers @info helmutbuhler 03/05/2025 bobtista 02/02/2026 - // Update particles to prevent accumulation in headless mode. Particles are generated - // during GameLogic and only cleaned up during rendering. update() lets particles finish - // their lifecycle naturally instead of abruptly removing them with reset(). - TheParticleSystemManager->update(); -} - Bool GameClient::isMovieAbortRequested() { if (TheGameEngine)