From 1d09adb2b8244fa77e5bcca714c2fb43e5d46137 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:41:04 +0100 Subject: [PATCH 01/10] bugfix(particlesys): Delay creation of particle emitters until postProcess --- .../GameClient/Drawable/Draw/W3DTankDraw.cpp | 10 ++++-- .../Drawable/Draw/W3DTankTruckDraw.cpp | 10 ++++-- .../GameLogic/Module/AutoHealBehavior.h | 5 ++- .../GameLogic/Module/GrantStealthBehavior.h | 2 ++ .../Object/Behavior/AutoHealBehavior.cpp | 35 ++++++++++++------- .../Object/Behavior/GrantStealthBehavior.cpp | 35 +++++++++++-------- 6 files changed, 62 insertions(+), 35 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index 31b3d4c8c27..c6c3ed50532 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -34,6 +34,7 @@ #include "Common/Thing.h" #include "Common/ThingFactory.h" #include "Common/GameAudio.h" +#include "Common/GameState.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/Weapon.h" @@ -106,7 +107,12 @@ W3DTankDraw::W3DTankDraw( Thing *thing, const ModuleData* moduleData ) m_lastDirection.y=0.0f; m_lastDirection.z=0.0f; - createTreadEmitters(); + // TheSuperHackers @bugfix stephanmeesters 20/02/2026 + // If loading from savegame, delay non-saveable emitter creation until postProcess. + if (TheGameState == nullptr || TheGameState->isInLoadGame() == FALSE) + { + createTreadEmitters(); + } } //------------------------------------------------------------------------------------------------- @@ -435,8 +441,6 @@ void W3DTankDraw::loadPostProcess() // extend base class W3DModelDraw::loadPostProcess(); - // toss any existing tread emitters and re-create 'em (since this module expects 'em to always be around) - tossTreadEmitters(); createTreadEmitters(); } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index eecbbdc975b..30ea63bc6e9 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -32,6 +32,7 @@ #include "Common/Thing.h" #include "Common/ThingFactory.h" #include "Common/GameAudio.h" +#include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" @@ -119,7 +120,12 @@ m_prevRenderObj(nullptr) m_treadCount=0; - createTreadEmitters(); + // TheSuperHackers @bugfix stephanmeesters 20/02/2026 + // If loading from savegame, delay non-saveable emitter creation until postProcess. + if (TheGameState == nullptr || TheGameState->isInLoadGame() == FALSE) + { + createTreadEmitters(); + } } //------------------------------------------------------------------------------------------------- @@ -761,8 +767,6 @@ void W3DTankTruckDraw::loadPostProcess() // toss any existing wheel emitters (no need to re-create; we'll do that on demand) tossWheelEmitters(); - // toss any existing tread emitters and re-create 'em (since this module expects 'em to always be around) - tossTreadEmitters(); createTreadEmitters(); } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h index d9a2db32ee3..cdd0ab94b76 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h @@ -108,9 +108,7 @@ class AutoHealBehaviorModuleData : public UpdateModuleData //------------------------------------------------------------------------------------------------- class AutoHealBehavior : public UpdateModule, public UpgradeMux, - public DamageModuleInterface -{ - + public DamageModuleInterface { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( AutoHealBehavior, "AutoHealBehavior" ) MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( AutoHealBehavior, AutoHealBehaviorModuleData ) @@ -174,6 +172,7 @@ class AutoHealBehavior : public UpdateModule, private: void pulseHealObject( Object *obj ); + void createEmitters(); ParticleSystemID m_radiusParticleSystemID; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GrantStealthBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GrantStealthBehavior.h index e431caaedbe..19858cf8b77 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GrantStealthBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/GrantStealthBehavior.h @@ -101,6 +101,8 @@ class GrantStealthBehavior : public UpdateModule private: void grantStealthToObject( Object *obj ); + void createEmitters(); + ParticleSystemID m_radiusParticleSystemID; Real m_currentScanRadius; }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index b58e8b88592..f8360976554 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -34,6 +34,7 @@ #include "Common/ThingTemplate.h" #include "Common/INI.h" #include "Common/Player.h" +#include "Common/GameState.h" #include "Common/Xfer.h" #include "GameClient/ParticleSys.h" #include "GameClient/Anim2D.h" @@ -94,20 +95,12 @@ AutoHealBehavior::AutoHealBehavior( Thing *thing, const ModuleData* moduleData ) m_radiusParticleSystemID = INVALID_PARTICLE_SYSTEM_ID; m_soonestHealFrame = 0; m_stopped = false; - Object *obj = getObject(); + // TheSuperHackers @bugfix stephanmeesters 20/02/2026 + // If loading from savegame, delay non-saveable emitter creation until postProcess. + if (TheGameState == nullptr || TheGameState->isInLoadGame() == FALSE) { - if( d->m_radiusParticleSystemTmpl ) - { - ParticleSystem *particleSystem; - - particleSystem = TheParticleSystemManager->createParticleSystem( d->m_radiusParticleSystemTmpl ); - if( particleSystem ) - { - particleSystem->setPosition( obj->getPosition() ); - m_radiusParticleSystemID = particleSystem->getSystemID(); - } - } + createEmitters(); } if (d->m_initiallyActive) @@ -373,4 +366,22 @@ void AutoHealBehavior::loadPostProcess() // extend base class UpgradeMux::upgradeMuxLoadPostProcess(); + createEmitters(); + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void AutoHealBehavior::createEmitters( void ) +{ + const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); + if( d->m_radiusParticleSystemTmpl ) + { + ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); + if( particleSystem ) + { + particleSystem->setPosition( getObject()->getPosition() ); + m_radiusParticleSystemID = particleSystem->getSystemID(); + } + } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp index a638406ddff..16b764d32b3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp @@ -33,6 +33,7 @@ #include "Common/ThingTemplate.h" #include "Common/INI.h" #include "Common/Player.h" +#include "Common/GameState.h" #include "Common/Xfer.h" #include "GameClient/ParticleSys.h" #include "GameClient/Anim2D.h" @@ -96,21 +97,11 @@ GrantStealthBehavior::GrantStealthBehavior( Thing *thing, const ModuleData* modu m_currentScanRadius = d->m_startRadius; - - Object *obj = getObject(); - + // TheSuperHackers @bugfix stephanmeesters 20/02/2026 + // If loading from savegame, delay non-saveable emitter creation until postProcess. + if ( TheGameState == nullptr || TheGameState->isInLoadGame() == FALSE ) { - if( d->m_radiusParticleSystemTmpl ) - { - ParticleSystem *particleSystem; - - particleSystem = TheParticleSystemManager->createParticleSystem( d->m_radiusParticleSystemTmpl ); - if( particleSystem ) - { - particleSystem->setPosition( obj->getPosition() ); - m_radiusParticleSystemID = particleSystem->getSystemID(); - } - } + createEmitters(); } setWakeFrame( getObject(), UPDATE_SLEEP_NONE ); @@ -246,5 +237,21 @@ void GrantStealthBehavior::loadPostProcess() // extend base class UpdateModule::loadPostProcess(); + createEmitters(); +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void GrantStealthBehavior::createEmitters( void ) +{ + const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData(); + if( d->m_radiusParticleSystemTmpl ) + { + ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); + if( particleSystem ) + { + particleSystem->setPosition( getObject()->getPosition() ); + m_radiusParticleSystemID = particleSystem->getSystemID(); + } + } } From ce85d682c197ee9dac30166c6b275d139723b566 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:14:27 +0100 Subject: [PATCH 02/10] Apply suggestions from code review Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp | 2 +- .../Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index f8360976554..c85d7cd9a70 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -375,7 +375,7 @@ void AutoHealBehavior::loadPostProcess() void AutoHealBehavior::createEmitters( void ) { const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); - if( d->m_radiusParticleSystemTmpl ) + if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID && d->m_radiusParticleSystemTmpl ) { ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); if( particleSystem ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp index 16b764d32b3..6770fec51c5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp @@ -245,7 +245,7 @@ void GrantStealthBehavior::loadPostProcess() void GrantStealthBehavior::createEmitters( void ) { const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData(); - if( d->m_radiusParticleSystemTmpl ) + if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID && d->m_radiusParticleSystemTmpl ) { ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); if( particleSystem ) From d58f3a0655fca3979f75255fc841943af859a92c Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:23:19 +0100 Subject: [PATCH 03/10] Update GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index c85d7cd9a70..378d2655e4d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -374,7 +374,7 @@ void AutoHealBehavior::loadPostProcess() // ------------------------------------------------------------------------------------------------ void AutoHealBehavior::createEmitters( void ) { - const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); + const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID && d->m_radiusParticleSystemTmpl ) { ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); From 2fe0d63414d7d26cf20de639c9609530b0a1978f Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:29:11 +0100 Subject: [PATCH 04/10] Delay emitters created in constructor to update/render loop --- .../GameClient/Drawable/Draw/W3DTankDraw.cpp | 10 +++------- .../GameClient/Drawable/Draw/W3DTankTruckDraw.cpp | 10 +++------- .../GameLogic/Object/Behavior/AutoHealBehavior.cpp | 11 +++-------- .../Object/Behavior/GrantStealthBehavior.cpp | 13 ++++--------- 4 files changed, 13 insertions(+), 31 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index c6c3ed50532..956d56a0968 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -34,7 +34,6 @@ #include "Common/Thing.h" #include "Common/ThingFactory.h" #include "Common/GameAudio.h" -#include "Common/GameState.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/Weapon.h" @@ -107,12 +106,6 @@ W3DTankDraw::W3DTankDraw( Thing *thing, const ModuleData* moduleData ) m_lastDirection.y=0.0f; m_lastDirection.z=0.0f; - // TheSuperHackers @bugfix stephanmeesters 20/02/2026 - // If loading from savegame, delay non-saveable emitter creation until postProcess. - if (TheGameState == nullptr || TheGameState->isInLoadGame() == FALSE) - { - createTreadEmitters(); - } } //------------------------------------------------------------------------------------------------- @@ -316,6 +309,9 @@ void W3DTankDraw::doDrawModule(const Matrix3D* transformMtx) if (obj == nullptr) return; + // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until draw + createTreadEmitters(); + // get object physics state PhysicsBehavior *physics = obj->getPhysics(); if (physics == nullptr) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 30ea63bc6e9..280fc0f0f18 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -32,7 +32,6 @@ #include "Common/Thing.h" #include "Common/ThingFactory.h" #include "Common/GameAudio.h" -#include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" @@ -120,12 +119,6 @@ m_prevRenderObj(nullptr) m_treadCount=0; - // TheSuperHackers @bugfix stephanmeesters 20/02/2026 - // If loading from savegame, delay non-saveable emitter creation until postProcess. - if (TheGameState == nullptr || TheGameState->isInLoadGame() == FALSE) - { - createTreadEmitters(); - } } //------------------------------------------------------------------------------------------------- @@ -510,6 +503,9 @@ void W3DTankTruckDraw::doDrawModule(const Matrix3D* transformMtx) if (obj == nullptr) return; + // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until draw + createTreadEmitters(); + if (getRenderObject()==nullptr) return; if (getRenderObject() != m_prevRenderObj) { updateBones(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index 378d2655e4d..12e559b254a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -34,7 +34,6 @@ #include "Common/ThingTemplate.h" #include "Common/INI.h" #include "Common/Player.h" -#include "Common/GameState.h" #include "Common/Xfer.h" #include "GameClient/ParticleSys.h" #include "GameClient/Anim2D.h" @@ -96,13 +95,6 @@ AutoHealBehavior::AutoHealBehavior( Thing *thing, const ModuleData* moduleData ) m_soonestHealFrame = 0; m_stopped = false; - // TheSuperHackers @bugfix stephanmeesters 20/02/2026 - // If loading from savegame, delay non-saveable emitter creation until postProcess. - if (TheGameState == nullptr || TheGameState->isInLoadGame() == FALSE) - { - createEmitters(); - } - if (d->m_initiallyActive) { giveSelfUpgrade(); @@ -176,6 +168,9 @@ UpdateSleepTime AutoHealBehavior::update() if (m_stopped) return UPDATE_SLEEP_FOREVER; + // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until update + createEmitters(); + Object *obj = getObject(); const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp index 6770fec51c5..f24e4858372 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp @@ -33,7 +33,6 @@ #include "Common/ThingTemplate.h" #include "Common/INI.h" #include "Common/Player.h" -#include "Common/GameState.h" #include "Common/Xfer.h" #include "GameClient/ParticleSys.h" #include "GameClient/Anim2D.h" @@ -97,14 +96,7 @@ GrantStealthBehavior::GrantStealthBehavior( Thing *thing, const ModuleData* modu m_currentScanRadius = d->m_startRadius; - // TheSuperHackers @bugfix stephanmeesters 20/02/2026 - // If loading from savegame, delay non-saveable emitter creation until postProcess. - if ( TheGameState == nullptr || TheGameState->isInLoadGame() == FALSE ) - { - createEmitters(); - } - - setWakeFrame( getObject(), UPDATE_SLEEP_NONE ); + setWakeFrame( getObject(), UPDATE_SLEEP_NONE ); } //------------------------------------------------------------------------------------------------- @@ -130,6 +122,9 @@ UpdateSleepTime GrantStealthBehavior::update() if ( self->isEffectivelyDead()) return UPDATE_SLEEP_FOREVER; + // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until update + createEmitters(); + const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData(); // setup scan filters PartitionFilterRelationship relationship( self, PartitionFilterRelationship::ALLOW_ALLIES ); From 1f55acd4784ebd97ce8d6a7e911b89e105c2051d Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:16:27 +0100 Subject: [PATCH 05/10] refactor: Improve emitter functions perf and syntax --- .../GameClient/Drawable/Draw/W3DTankDraw.cpp | 40 ++++++++++-------- .../Drawable/Draw/W3DTankTruckDraw.cpp | 41 ++++++++++--------- .../Object/Behavior/AutoHealBehavior.cpp | 10 ++--- .../Object/Behavior/GrantStealthBehavior.cpp | 4 +- 4 files changed, 52 insertions(+), 43 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index 956d56a0968..826071d34b9 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -125,27 +125,33 @@ void W3DTankDraw::tossTreadEmitters() //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- -void W3DTankDraw::createTreadEmitters() +static ParticleSystemID createParticleSystem( const AsciiString &name, const Drawable *drawable ) { - const AsciiString *treadDebrisNames[2]; - static_assert(ARRAY_SIZE(treadDebrisNames) == ARRAY_SIZE(m_treadDebrisIDs), "Array size must match"); - treadDebrisNames[0] = &getW3DTankDrawModuleData()->m_treadDebrisNameLeft; - treadDebrisNames[1] = &getW3DTankDrawModuleData()->m_treadDebrisNameRight; + const ParticleSystemTemplate *sysTemplate = TheParticleSystemManager->findTemplate(name); + ParticleSystem *particleSys = TheParticleSystemManager->createParticleSystem( sysTemplate ); + if (!particleSys) + return INVALID_PARTICLE_SYSTEM_ID; + + particleSys->attachToDrawable(drawable); + // important: mark it as do-not-save, since we'll just re-create it when we reload. + particleSys->setSaveable(FALSE); + // they come into being stopped. + particleSys->stop(); + + return particleSys->getSystemID(); +} - for (size_t i = 0; i < ARRAY_SIZE(m_treadDebrisIDs); ++i) +void W3DTankDraw::createTreadEmitters() +{ + if (getW3DTankDrawModuleData()) { - if (m_treadDebrisIDs[i] == INVALID_PARTICLE_SYSTEM_ID) + if (m_treadDebrisIDs[0] == INVALID_PARTICLE_SYSTEM_ID) { - if (const ParticleSystemTemplate *sysTemplate = TheParticleSystemManager->findTemplate(*treadDebrisNames[i])) - { - ParticleSystem *particleSys = TheParticleSystemManager->createParticleSystem( sysTemplate ); - particleSys->attachToDrawable(getDrawable()); - // important: mark it as do-not-save, since we'll just re-create it when we reload. - particleSys->setSaveable(FALSE); - // they come into being stopped. - particleSys->stop(); - m_treadDebrisIDs[i] = particleSys->getSystemID(); - } + m_treadDebrisIDs[0] = createParticleSystem(getW3DTankDrawModuleData()->m_treadDebrisNameLeft, getDrawable()); + } + if (m_treadDebrisIDs[1] == INVALID_PARTICLE_SYSTEM_ID) + { + m_treadDebrisIDs[1] = createParticleSystem(getW3DTankDrawModuleData()->m_treadDebrisNameRight, getDrawable()); } } } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 280fc0f0f18..501bbf061e4 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -181,30 +181,33 @@ void W3DTankTruckDraw::setFullyObscuredByShroud(Bool fullyObscured) //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- +static ParticleSystemID createParticleSystem( const AsciiString &name, const Drawable *drawable ) +{ + const ParticleSystemTemplate *sysTemplate = TheParticleSystemManager->findTemplate(name); + ParticleSystem *particleSys = TheParticleSystemManager->createParticleSystem( sysTemplate ); + if (!particleSys) + return INVALID_PARTICLE_SYSTEM_ID; + + particleSys->attachToDrawable(drawable); + // important: mark it as do-not-save, since we'll just re-create it when we reload. + particleSys->setSaveable(FALSE); + // they come into being stopped. + particleSys->stop(); + + return particleSys->getSystemID(); +} + void W3DTankTruckDraw::createTreadEmitters() { if (getW3DTankTruckDrawModuleData()) { - const AsciiString *treadDebrisNames[2]; - static_assert(ARRAY_SIZE(treadDebrisNames) == ARRAY_SIZE(m_treadDebrisIDs), "Array size must match"); - treadDebrisNames[0] = &getW3DTankTruckDrawModuleData()->m_treadDebrisNameLeft; - treadDebrisNames[1] = &getW3DTankTruckDrawModuleData()->m_treadDebrisNameRight; - - for (size_t i = 0; i < ARRAY_SIZE(m_treadDebrisIDs); ++i) + if (m_treadDebrisIDs[0] == INVALID_PARTICLE_SYSTEM_ID) { - if (m_treadDebrisIDs[i] == INVALID_PARTICLE_SYSTEM_ID) - { - if (const ParticleSystemTemplate *sysTemplate = TheParticleSystemManager->findTemplate(*treadDebrisNames[i])) - { - ParticleSystem *particleSys = TheParticleSystemManager->createParticleSystem( sysTemplate ); - particleSys->attachToDrawable(getDrawable()); - // important: mark it as do-not-save, since we'll just re-create it when we reload. - particleSys->setSaveable(FALSE); - // they come into being stopped. - particleSys->stop(); - m_treadDebrisIDs[i] = particleSys->getSystemID(); - } - } + m_treadDebrisIDs[0] = createParticleSystem(getW3DTankTruckDrawModuleData()->m_treadDebrisNameLeft, getDrawable()); + } + if (m_treadDebrisIDs[1] == INVALID_PARTICLE_SYSTEM_ID) + { + m_treadDebrisIDs[1] = createParticleSystem(getW3DTankTruckDrawModuleData()->m_treadDebrisNameRight, getDrawable()); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index 12e559b254a..dd859243be9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -168,9 +168,6 @@ UpdateSleepTime AutoHealBehavior::update() if (m_stopped) return UPDATE_SLEEP_FOREVER; - // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until update - createEmitters(); - Object *obj = getObject(); const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); @@ -182,6 +179,9 @@ UpdateSleepTime AutoHealBehavior::update() return UPDATE_SLEEP_FOREVER; } + // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until update + createEmitters(); + //DEBUG_LOG(("doing auto heal %d",TheGameLogic->getFrame())); if( d->m_affectsWholePlayer ) @@ -369,9 +369,9 @@ void AutoHealBehavior::loadPostProcess() // ------------------------------------------------------------------------------------------------ void AutoHealBehavior::createEmitters( void ) { - const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); - if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID && d->m_radiusParticleSystemTmpl ) + if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID ) { + const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); if( particleSystem ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp index f24e4858372..ca245d2a564 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp @@ -239,9 +239,9 @@ void GrantStealthBehavior::loadPostProcess() // ------------------------------------------------------------------------------------------------ void GrantStealthBehavior::createEmitters( void ) { - const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData(); - if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID && d->m_radiusParticleSystemTmpl ) + if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID ) { + const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData(); ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); if( particleSystem ) { From 3605bfea3a0e221f7fc48a2ad9db9fd2ebc2849a Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:54:30 +0100 Subject: [PATCH 06/10] Remove void --- .../Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp | 2 +- .../Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index dd859243be9..6f193b5f148 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -367,7 +367,7 @@ void AutoHealBehavior::loadPostProcess() // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void AutoHealBehavior::createEmitters( void ) +void AutoHealBehavior::createEmitters() { if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp index ca245d2a564..3c733c1a6d9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp @@ -237,7 +237,7 @@ void GrantStealthBehavior::loadPostProcess() // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void GrantStealthBehavior::createEmitters( void ) +void GrantStealthBehavior::createEmitters() { if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID ) { From 751e97881efbb8dc8d40bc1d9fa3ba07486ddd29 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:16:32 +0100 Subject: [PATCH 07/10] Add assert in ParticleSystemManager --- Core/GameEngine/Source/GameClient/System/ParticleSys.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index c11bfefb2fa..5cbf3470c3c 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -3346,6 +3346,8 @@ void ParticleSystemManager::xfer( Xfer *xfer ) } else { + DEBUG_ASSERTCRASH(m_allParticleSystemList.size()==0, ("ParticleSystemManager: particle systems list must be empty at start of xfer-load.")); + const ParticleSystemTemplate *systemTemplate; // read each particle system From 9b28e282909794cd2e7d1a60fe9d274dba60390b Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:51:20 +0200 Subject: [PATCH 08/10] Process review comments Adjust assert syntax and message, add static_assert, formatting, remove createEmitters from postProcess --- .../Source/GameClient/System/ParticleSys.cpp | 2 +- .../GameClient/Drawable/Draw/W3DTankDraw.cpp | 10 +++++----- .../GameClient/Drawable/Draw/W3DTankTruckDraw.cpp | 11 ++++++----- .../Include/GameLogic/Module/AutoHealBehavior.h | 4 +++- .../GameLogic/Object/Behavior/AutoHealBehavior.cpp | 2 -- .../Object/Behavior/GrantStealthBehavior.cpp | 1 - 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index 5cbf3470c3c..17959d62c82 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -3346,7 +3346,7 @@ void ParticleSystemManager::xfer( Xfer *xfer ) } else { - DEBUG_ASSERTCRASH(m_allParticleSystemList.size()==0, ("ParticleSystemManager: particle systems list must be empty at start of xfer-load.")); + DEBUG_ASSERTCRASH(m_allParticleSystemList.empty(), ("ParticleSystemManager: particle systems list is expected empty at start of xfer-load.")); const ParticleSystemTemplate *systemTemplate; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index 826071d34b9..14c92d3644c 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -145,6 +145,8 @@ void W3DTankDraw::createTreadEmitters() { if (getW3DTankDrawModuleData()) { + static_assert(ARRAY_SIZE(m_treadDebrisIDs) == 2, "m_treadDebrisIDs array size is expected to be 2"); + if (m_treadDebrisIDs[0] == INVALID_PARTICLE_SYSTEM_ID) { m_treadDebrisIDs[0] = createParticleSystem(getW3DTankDrawModuleData()->m_treadDebrisNameLeft, getDrawable()); @@ -315,9 +317,6 @@ void W3DTankDraw::doDrawModule(const Matrix3D* transformMtx) if (obj == nullptr) return; - // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until draw - createTreadEmitters(); - // get object physics state PhysicsBehavior *physics = obj->getPhysics(); if (physics == nullptr) @@ -344,6 +343,9 @@ void W3DTankDraw::doDrawModule(const Matrix3D* transformMtx) if (velMult.z > 1.0f) velMult.z = 1.0f; + // TheSuperHackers @bugfix 14/03/2026 Delay emitter creation until draw + createTreadEmitters(); + for (size_t i = 0; i < ARRAY_SIZE(m_treadDebrisIDs); ++i) { if (ParticleSystem *particleSys = TheParticleSystemManager->findParticleSystem(m_treadDebrisIDs[i])) @@ -443,6 +445,4 @@ void W3DTankDraw::loadPostProcess() // extend base class W3DModelDraw::loadPostProcess(); - createTreadEmitters(); - } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 501bbf061e4..4ad1b3c7ec0 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -201,6 +201,8 @@ void W3DTankTruckDraw::createTreadEmitters() { if (getW3DTankTruckDrawModuleData()) { + static_assert(ARRAY_SIZE(m_treadDebrisIDs) == 2, "m_treadDebrisIDs array size is expected to be 2"); + if (m_treadDebrisIDs[0] == INVALID_PARTICLE_SYSTEM_ID) { m_treadDebrisIDs[0] = createParticleSystem(getW3DTankTruckDrawModuleData()->m_treadDebrisNameLeft, getDrawable()); @@ -506,14 +508,12 @@ void W3DTankTruckDraw::doDrawModule(const Matrix3D* transformMtx) if (obj == nullptr) return; - // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until draw - createTreadEmitters(); - if (getRenderObject()==nullptr) return; if (getRenderObject() != m_prevRenderObj) { updateBones(); updateTreadObjects(); } + // get object physics state PhysicsBehavior *physics = obj->getPhysics(); if (physics == nullptr) @@ -673,6 +673,9 @@ void W3DTankTruckDraw::doDrawModule(const Matrix3D* transformMtx) if (velMult.z > 1.0f) velMult.z = 1.0f; + // TheSuperHackers @bugfix 14/03/2026 Delay emitter creation until draw + createTreadEmitters(); + for (size_t i = 0; i < ARRAY_SIZE(m_treadDebrisIDs); ++i) { if (ParticleSystem *particleSys = TheParticleSystemManager->findParticleSystem(m_treadDebrisIDs[i])) @@ -766,6 +769,4 @@ void W3DTankTruckDraw::loadPostProcess() // toss any existing wheel emitters (no need to re-create; we'll do that on demand) tossWheelEmitters(); - createTreadEmitters(); - } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h index cdd0ab94b76..2cde681c77a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h @@ -108,7 +108,9 @@ class AutoHealBehaviorModuleData : public UpdateModuleData //------------------------------------------------------------------------------------------------- class AutoHealBehavior : public UpdateModule, public UpgradeMux, - public DamageModuleInterface { + public DamageModuleInterface +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( AutoHealBehavior, "AutoHealBehavior" ) MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( AutoHealBehavior, AutoHealBehaviorModuleData ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index 6f193b5f148..caccbfb7404 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -361,8 +361,6 @@ void AutoHealBehavior::loadPostProcess() // extend base class UpgradeMux::upgradeMuxLoadPostProcess(); - createEmitters(); - } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp index 3c733c1a6d9..570b687a7d4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp @@ -232,7 +232,6 @@ void GrantStealthBehavior::loadPostProcess() // extend base class UpdateModule::loadPostProcess(); - createEmitters(); } // ------------------------------------------------------------------------------------------------ From 145d77702493a6a8a9455f9dba097d1f2548d63d Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:29:39 +0200 Subject: [PATCH 09/10] Update @bugfix comments --- .../Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp | 3 ++- .../W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp | 3 ++- .../Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp | 3 ++- .../Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index 14c92d3644c..a00746a0fb9 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -343,7 +343,8 @@ void W3DTankDraw::doDrawModule(const Matrix3D* transformMtx) if (velMult.z > 1.0f) velMult.z = 1.0f; - // TheSuperHackers @bugfix 14/03/2026 Delay emitter creation until draw + // TheSuperHackers @bugfix stephanmeesters 18/04/2026 Delay emitter creation until draw, to ensure that the particle + // systems are not created before ParticleManager has xfer-loaded. createTreadEmitters(); for (size_t i = 0; i < ARRAY_SIZE(m_treadDebrisIDs); ++i) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 4ad1b3c7ec0..5638cd1b3fb 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -673,7 +673,8 @@ void W3DTankTruckDraw::doDrawModule(const Matrix3D* transformMtx) if (velMult.z > 1.0f) velMult.z = 1.0f; - // TheSuperHackers @bugfix 14/03/2026 Delay emitter creation until draw + // TheSuperHackers @bugfix stephanmeesters 18/04/2026 Delay emitter creation until draw, to ensure that the particle + // systems are not created before ParticleManager has xfer-loaded. createTreadEmitters(); for (size_t i = 0; i < ARRAY_SIZE(m_treadDebrisIDs); ++i) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index caccbfb7404..ce73fb30c47 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -179,7 +179,8 @@ UpdateSleepTime AutoHealBehavior::update() return UPDATE_SLEEP_FOREVER; } - // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until update + // TheSuperHackers @bugfix stephanmeesters 18/04/2026 Delay emitter creation until update, to ensure that the particle + // systems are not created before ParticleManager has xfer-loaded. createEmitters(); //DEBUG_LOG(("doing auto heal %d",TheGameLogic->getFrame())); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp index 570b687a7d4..cd917b4a293 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp @@ -122,7 +122,8 @@ UpdateSleepTime GrantStealthBehavior::update() if ( self->isEffectivelyDead()) return UPDATE_SLEEP_FOREVER; - // TheSuperHackers @bugfix stephanmeesters 14/03/2026 Delay emitter creation until update + // TheSuperHackers @bugfix stephanmeesters 18/04/2026 Delay emitter creation until update, to ensure that the particle + // systems are not created before ParticleManager has xfer-loaded. createEmitters(); const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData(); From f4a75b6f37a9a14227e001fde045889afe7a6a46 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:35:41 +0200 Subject: [PATCH 10/10] Replicate in Generals --- .../GameLogic/Module/AutoHealBehavior.h | 1 + .../Object/Behavior/AutoHealBehavior.cpp | 35 +++++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h b/Generals/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h index 64d77d3542f..7ea5a83ca89 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/AutoHealBehavior.h @@ -162,6 +162,7 @@ class AutoHealBehavior : public UpdateModule, private: void pulseHealObject( Object *obj ); + void createEmitters(); ParticleSystemID m_radiusParticleSystemID; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp index 4faf34c9604..2ba064bcf06 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -86,21 +86,6 @@ AutoHealBehavior::AutoHealBehavior( Thing *thing, const ModuleData* moduleData ) m_radiusParticleSystemID = INVALID_PARTICLE_SYSTEM_ID; m_soonestHealFrame = 0; m_stopped = false; - Object *obj = getObject(); - - { - if( d->m_radiusParticleSystemTmpl ) - { - ParticleSystem *particleSystem; - - particleSystem = TheParticleSystemManager->createParticleSystem( d->m_radiusParticleSystemTmpl ); - if( particleSystem ) - { - particleSystem->setPosition( obj->getPosition() ); - m_radiusParticleSystemID = particleSystem->getSystemID(); - } - } - } if (d->m_initiallyActive) { @@ -186,6 +171,10 @@ UpdateSleepTime AutoHealBehavior::update() return UPDATE_SLEEP_FOREVER; } + // TheSuperHackers @bugfix stephanmeesters 18/04/2026 Delay emitter creation until update, to ensure that the particle + // systems are not created before ParticleManager has xfer-loaded. + createEmitters(); + //DEBUG_LOG(("doing auto heal %d",TheGameLogic->getFrame())); if( d->m_affectsWholePlayer ) @@ -359,3 +348,19 @@ void AutoHealBehavior::loadPostProcess() UpgradeMux::upgradeMuxLoadPostProcess(); } + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void AutoHealBehavior::createEmitters() +{ + if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID ) + { + const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData(); + ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); + if( particleSystem ) + { + particleSystem->setPosition( getObject()->getPosition() ); + m_radiusParticleSystemID = particleSystem->getSystemID(); + } + } +}