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 diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index 31b3d4c8c27..826071d34b9 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -106,7 +106,6 @@ W3DTankDraw::W3DTankDraw( Thing *thing, const ModuleData* moduleData ) m_lastDirection.y=0.0f; m_lastDirection.z=0.0f; - createTreadEmitters(); } //------------------------------------------------------------------------------------------------- @@ -126,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()); } } } @@ -310,6 +315,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) @@ -435,8 +443,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..501bbf061e4 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -119,7 +119,6 @@ m_prevRenderObj(nullptr) m_treadCount=0; - createTreadEmitters(); } //------------------------------------------------------------------------------------------------- @@ -182,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()); } } } @@ -504,6 +506,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(); @@ -761,8 +766,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..6f193b5f148 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/AutoHealBehavior.cpp @@ -94,21 +94,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) { @@ -194,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 ) @@ -373,4 +361,22 @@ void AutoHealBehavior::loadPostProcess() // extend base class UpgradeMux::upgradeMuxLoadPostProcess(); + createEmitters(); + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +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(); + } + } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp index a638406ddff..3c733c1a6d9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp @@ -96,24 +96,7 @@ GrantStealthBehavior::GrantStealthBehavior( Thing *thing, const ModuleData* modu m_currentScanRadius = d->m_startRadius; - - 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(); - } - } - } - - setWakeFrame( getObject(), UPDATE_SLEEP_NONE ); + setWakeFrame( getObject(), UPDATE_SLEEP_NONE ); } //------------------------------------------------------------------------------------------------- @@ -139,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 ); @@ -246,5 +232,21 @@ void GrantStealthBehavior::loadPostProcess() // extend base class UpdateModule::loadPostProcess(); + createEmitters(); +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void GrantStealthBehavior::createEmitters() +{ + if( m_radiusParticleSystemID == INVALID_PARTICLE_SYSTEM_ID ) + { + const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData(); + ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem(d->m_radiusParticleSystemTmpl); + if( particleSystem ) + { + particleSystem->setPosition( getObject()->getPosition() ); + m_radiusParticleSystemID = particleSystem->getSystemID(); + } + } }