diff --git a/Core/GameEngine/Include/Common/CRCDebug.h b/Core/GameEngine/Include/Common/CRCDebug.h index b4bda92b2c6..1eaff931f52 100644 --- a/Core/GameEngine/Include/Common/CRCDebug.h +++ b/Core/GameEngine/Include/Common/CRCDebug.h @@ -73,6 +73,7 @@ void addCRCDumpLine(const char *fmt, ...); void addCRCGenLine(const char *fmt, ...); #define CRCDEBUG_LOG(x) addCRCDebugLine x + #define CRCDEBUG_LOG_NOCOUNTER(x) addCRCDebugLineNoCounter x #define CRCDUMP_LOG(x) addCRCDumpLine x #define CRCGEN_LOG(x) addCRCGenLine x @@ -116,6 +117,7 @@ #define DUMPREALNAMED(x, y) #define CRCDEBUG_LOG(x) + #define CRCDEBUG_LOG_NOCOUNTER(x) #define CRCDUMP_LOG(x) #define CRCGEN_LOG(x) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index acd504f6838..ee69d5415b0 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -843,21 +843,18 @@ class ParticleSystemManager : public SubsystemInterface, // TheSuperHackers @feature bobtista 31/01/2026 // ParticleSystemManager that does nothing. Used for Headless Mode. -// Does not load particle system templates and does not create particle systems. +// Does not render, update, or create particles, but loads particle system templates. class ParticleSystemManagerDummy : public ParticleSystemManager { public: -#if RETAIL_COMPATIBLE_CRC - // The creation of particle systems needs to be handled explicitly, - // because they're not destroyed in the update function anymore. - virtual ParticleSystem* createParticleSystem(const ParticleSystemTemplate* sysTemplate, Bool createSlaves = TRUE) override { return nullptr; } + // TheSuperHackers @bugfix Okladnoj 29/07/2026 Must not overload init and reset, to keep loading the + // particle system templates. Game logic branches on template pointers, for example the gattling + // aim position in SpectreGunshipUpdate, so a headless client that skips them diverges in logic crc + // from a regular client. This applies with retail compatibility both enabled and disabled. - // 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 + // TheSuperHackers @bugfix Caball009 23/07/2026 Prevent accumulation of particle systems: the no-op + // update never destroys them, so creation must return nullptr. (#3006) + virtual ParticleSystem* createParticleSystem(const ParticleSystemTemplate* sysTemplate, Bool createSlaves = TRUE) override { return nullptr; } virtual void update() override {} virtual Bool isDummy() const override { return true; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index e136a2ec0e0..c25f3831fe0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -32,6 +32,7 @@ #include "Common/ThingTemplate.h" #include "Common/RandomValue.h" #include "Common/BitFlagsIO.h" +#include "Common/CRCDebug.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/ExperienceTracker.h" @@ -386,6 +387,11 @@ void MissileAIUpdate::detonate() if (m_detonationWeaponTmpl) { + // TheSuperHackers @info Okladnoj 30/07/2026 DieOnDetonate decides whether the projectile dies here + // or a frame later in doKillSelfState, which moves its die modules to a different frame. Record it, + // because a client that disagrees on this flag desyncs at the frame the projectile detonates. + CRCDEBUG_LOG_NOCOUNTER(("MissileAIUpdate::detonate() obj=%d weapon=%s dieOnDetonate=%d", + obj->getID(), m_detonationWeaponTmpl->getName().str(), m_detonationWeaponTmpl->getDieOnDetonate() ? 1 : 0)); TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags, !m_noDamage ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 5d6637b1523..36dcc993df6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -30,6 +30,8 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include + #define DEFINE_DEATH_NAMES #define DEFINE_WEAPONBONUSCONDITION_NAMES #define DEFINE_WEAPONBONUSFIELD_NAMES @@ -1672,6 +1674,18 @@ WeaponTemplate *WeaponStore::newWeaponTemplate(AsciiString name) WeaponTemplate *wt = newInstance(WeaponTemplate); wt->m_name = name; wt->m_nameKey = TheNameKeyGenerator->nameToKey( name ); + + if (strcmp(name.str(), "SupW_AuroraFuelBombWeapon") == 0) + { + // Note: m_dieOnDetonate is set to true to fix the Alpha Aurora second explosion inconsistency when targeting structures. + // SupW_AuroraFuelBombWeapon does not specify MissileCallsOnDie in INI, so getDieOnDetonate() + // returned false, causing detonate() to skip attemptDamage() which is what triggers die modules. + // When INI is editable, we should add MissileCallsOnDie = yes for SupW_AuroraFuelBombWeapon + // and change m_dieOnDetonate back to false. + wt->m_dieOnDetonate = TRUE; + DEBUG_LOG(("WeaponStore::newWeaponTemplate() - forcing MissileCallsOnDie for %s", name.str())); + } + m_weaponTemplateVector.push_back(wt); m_weaponTemplateHashMap[wt->m_nameKey] = wt;