From 31c672c3be459170dbf4c2bbfab19e7f95ca99ca Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Mon, 7 Sep 2026 13:35:51 +0200 Subject: [PATCH 1/5] Rename m_isGroundAligned to m_particleAlignment --- Core/GameEngine/Include/GameClient/ParticleSys.h | 4 ++-- .../Source/GameClient/System/ParticleSys.cpp | 10 +++++----- .../Source/W3DDevice/GameClient/W3DParticleSys.cpp | 2 +- Core/Tools/ParticleEditor/ParticleEditorDialog.cpp | 4 ++-- .../Source/GameLogic/ScriptEngine/ScriptEngine.cpp | 2 +- .../Source/GameLogic/ScriptEngine/ScriptEngine.cpp | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index 4bf120e332d..f01831e501c 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -431,7 +431,7 @@ class ParticleSystemInfo : public Snapshot m_emissionVolume; ///< the dimensions of the emission volume Bool m_isEmissionVolumeHollow; ///< if true, only create particles at boundary of volume - Bool m_isGroundAligned; ///< if true, align with the ground. if false, then do the normal billboarding. + Bool m_particleAlignment; ///< if true, align with the ground. if false, then do the normal billboarding. Bool m_isEmitAboveGroundOnly; ///< if true, only emit particles when the system is above ground. Bool m_isParticleUpTowardsEmitter; ///< if true, align the up direction to be towards the emitter. @@ -614,7 +614,7 @@ class ParticleSystem : public MemoryPoolObject, Bool isUsingVolumeParticles() const { return m_particleType == VOLUME_PARTICLE; } UnsignedInt getVolumeParticleDepth() const { return m_volumeParticleDepth; } - Bool shouldBillboard() const { return !m_isGroundAligned; } + Bool shouldBillboard() const { return !m_particleAlignment; } ParticleShaderType getShaderType() const { return m_shaderType; } diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index 0d697e89fe7..368dea2ecdc 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -752,7 +752,7 @@ void Particle::loadPostProcess() ParticleSystemInfo::ParticleSystemInfo() { m_priority = PARTICLE_PRIORITY_LOWEST; - m_isGroundAligned = false; + m_particleAlignment = false; m_isEmitAboveGroundOnly = false; m_isParticleUpTowardsEmitter = false; @@ -1002,7 +1002,7 @@ void ParticleSystemInfo::xfer( Xfer *xfer ) xfer->xferBool( &m_isEmissionVolumeHollow ); // is ground aligned - xfer->xferBool( &m_isGroundAligned ); + xfer->xferBool( &m_particleAlignment ); // emit above ground only xfer->xferBool( &m_isEmitAboveGroundOnly ); @@ -1168,7 +1168,7 @@ ParticleSystem::ParticleSystem( const ParticleSystemTemplate *sysTemplate, m_emissionVolume = sysTemplate->m_emissionVolume; m_isEmissionVolumeHollow = sysTemplate->m_isEmissionVolumeHollow; - m_isGroundAligned = sysTemplate->m_isGroundAligned; + m_particleAlignment = sysTemplate->m_particleAlignment; m_isEmitAboveGroundOnly = sysTemplate->m_isEmitAboveGroundOnly; m_isParticleUpTowardsEmitter = sysTemplate->m_isParticleUpTowardsEmitter; @@ -1749,7 +1749,7 @@ Particle *ParticleSystem::createParticle( const ParticleInfo *info, TheGameLODManager->isParticleSkipped()) ) return nullptr; - if ( getParticleCount() > 0 && priority == AREA_EFFECT && m_isGroundAligned && TheParticleSystemManager->getFieldParticleCount() > (UnsignedInt)TheGlobalData->m_maxFieldParticleCount ) + if ( getParticleCount() > 0 && priority == AREA_EFFECT && m_particleAlignment && TheParticleSystemManager->getFieldParticleCount() > (UnsignedInt)TheGlobalData->m_maxFieldParticleCount ) return nullptr; // ALWAYS_RENDER particles are exempt from all count limits, and are always created, regardless of LOD issues. @@ -2756,7 +2756,7 @@ const FieldParse ParticleSystemTemplate::m_fieldParseTable[] = { "VolCylinderLength", INI::parseReal, nullptr, offsetof( ParticleSystemTemplate, m_emissionVolume.cylinder.length ) }, { "IsHollow", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isEmissionVolumeHollow ) }, - { "IsGroundAligned", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isGroundAligned ) }, + { "IsGroundAligned", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_particleAlignment ) }, { "IsEmitAboveGroundOnly", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isEmitAboveGroundOnly) }, { "IsParticleUpTowardsEmitter", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isParticleUpTowardsEmitter) }, diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index 9ceadaa2894..2a56fbae4de 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -247,7 +247,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) pos = p->getPosition(); psize = p->getSize(); - m_fieldParticleCount += ( sys->getPriority() == AREA_EFFECT && sys->m_isGroundAligned != FALSE ); + m_fieldParticleCount += ( sys->getPriority() == AREA_EFFECT && sys->m_particleAlignment != FALSE ); //@todo lorenzen sez: use pointer arithmetic for these arrays personalities[pointCount] = p->getPersonality(); diff --git a/Core/Tools/ParticleEditor/ParticleEditorDialog.cpp b/Core/Tools/ParticleEditor/ParticleEditorDialog.cpp index 256bf78938b..cff198540f0 100644 --- a/Core/Tools/ParticleEditor/ParticleEditorDialog.cpp +++ b/Core/Tools/ParticleEditor/ParticleEditorDialog.cpp @@ -1092,7 +1092,7 @@ void DebugWindowDialog::getSwitchFromSystem( IN SwitchType switchType, OUT Bool& { case ST_HOLLOW: switchVal = m_particleSystem->m_isEmissionVolumeHollow; break; case ST_ONESHOT: switchVal = m_particleSystem->m_isOneShot; break; - case ST_ALIGNXY: switchVal = m_particleSystem->m_isGroundAligned; break; + case ST_ALIGNXY: switchVal = m_particleSystem->m_particleAlignment; break; case ST_EMITABOVEGROUNDONLY: switchVal = m_particleSystem->m_isEmitAboveGroundOnly; break; case ST_PARTICLEUPTOWARDSEMITTER: switchVal = m_particleSystem->m_isParticleUpTowardsEmitter; break; }; @@ -1108,7 +1108,7 @@ void DebugWindowDialog::updateSwitchToSystem( IN SwitchType switchType, IN const { case ST_HOLLOW: m_particleSystem->m_isEmissionVolumeHollow = switchVal; break; case ST_ONESHOT: m_particleSystem->m_isOneShot = switchVal; break; - case ST_ALIGNXY: m_particleSystem->m_isGroundAligned = switchVal; break; + case ST_ALIGNXY: m_particleSystem->m_particleAlignment = switchVal; break; case ST_EMITABOVEGROUNDONLY: m_particleSystem->m_isEmitAboveGroundOnly = switchVal; break; case ST_PARTICLEUPTOWARDSEMITTER: m_particleSystem->m_isParticleUpTowardsEmitter = switchVal; break; }; diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index f1f23211fdc..5072d617d93 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -9375,7 +9375,7 @@ void _writeSingleParticleSystem( File *out, ParticleSystemTemplate *templ ) } thisEntry.append(SEP_HEAD).append(F_ISHOLLOW).append(EQ_WITH_SPACES).append((templ->m_isEmissionVolumeHollow ? STR_TRUE : STR_FALSE)).append(SEP_EOL); - thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append((templ->m_isGroundAligned ? STR_TRUE : STR_FALSE)).append(SEP_EOL); + thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append((templ->m_particleAlignment ? STR_TRUE : STR_FALSE)).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISEMITABOVEGROUNDONLY).append(EQ_WITH_SPACES).append((templ->m_isEmitAboveGroundOnly ? STR_TRUE : STR_FALSE)).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISPARTICLEUPTOWARDSEMITTER).append(EQ_WITH_SPACES).append((templ->m_isParticleUpTowardsEmitter ? STR_TRUE : STR_FALSE)).append(SEP_EOL); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 9ee0ee728f4..99c481152e9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -10078,7 +10078,7 @@ void _writeSingleParticleSystem( File *out, ParticleSystemTemplate *templ ) } thisEntry.append(SEP_HEAD).append(F_ISHOLLOW).append(EQ_WITH_SPACES).append((templ->m_isEmissionVolumeHollow ? STR_TRUE : STR_FALSE)).append(SEP_EOL); - thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append((templ->m_isGroundAligned ? STR_TRUE : STR_FALSE)).append(SEP_EOL); + thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append((templ->m_particleAlignment ? STR_TRUE : STR_FALSE)).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISEMITABOVEGROUNDONLY).append(EQ_WITH_SPACES).append((templ->m_isEmitAboveGroundOnly ? STR_TRUE : STR_FALSE)).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISPARTICLEUPTOWARDSEMITTER).append(EQ_WITH_SPACES).append((templ->m_isParticleUpTowardsEmitter ? STR_TRUE : STR_FALSE)).append(SEP_EOL); From e7df7a1c913a4477daa485036f1cdfc250feb668 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Mon, 7 Sep 2026 12:53:46 +0200 Subject: [PATCH 2/5] Change m_particleAlignment from boolean to enum --- .../Include/GameClient/ParticleSys.h | 16 ++++++++++-- .../Source/GameClient/System/ParticleSys.cpp | 26 +++++++++++++++---- .../W3DDevice/GameClient/W3DParticleSys.cpp | 2 +- .../ParticleEditor/ParticleEditorDialog.cpp | 4 +-- .../GameLogic/ScriptEngine/ScriptEngine.cpp | 2 +- .../GameLogic/ScriptEngine/ScriptEngine.cpp | 2 +- 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index f01831e501c..2f48430304e 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -431,7 +431,13 @@ class ParticleSystemInfo : public Snapshot m_emissionVolume; ///< the dimensions of the emission volume Bool m_isEmissionVolumeHollow; ///< if true, only create particles at boundary of volume - Bool m_particleAlignment; ///< if true, align with the ground. if false, then do the normal billboarding. + enum ParticleAlignmentType + { + PARTICLE_ALIGNMENT_BILLBOARD = 0, + PARTICLE_ALIGNMENT_XYPLANAR, + PARTICLE_ALIGNMENT_TYPE_COUNT + }; + ParticleAlignmentType m_particleAlignment; Bool m_isEmitAboveGroundOnly; ///< if true, only emit particles when the system is above ground. Bool m_isParticleUpTowardsEmitter; ///< if true, align the up direction to be towards the emitter. @@ -495,6 +501,12 @@ static const char *const ParticlePriorityNames[] = }; static_assert(ARRAY_SIZE(ParticlePriorityNames) == NUM_PARTICLE_PRIORITIES + 1, "Incorrect array size"); +static const char *const GroundAlignmentTypeNames[] = +{ + "No", "Yes", nullptr +}; +static_assert(ARRAY_SIZE(GroundAlignmentTypeNames) == ParticleSystemInfo::PARTICLE_ALIGNMENT_TYPE_COUNT + 1, "Incorrect array size"); + static const char *const WindMotionNames[] = { "NONE", "Unused", "PingPong", "Circular", nullptr @@ -614,7 +626,7 @@ class ParticleSystem : public MemoryPoolObject, Bool isUsingVolumeParticles() const { return m_particleType == VOLUME_PARTICLE; } UnsignedInt getVolumeParticleDepth() const { return m_volumeParticleDepth; } - Bool shouldBillboard() const { return !m_particleAlignment; } + Bool shouldBillboard() const { return m_particleAlignment == PARTICLE_ALIGNMENT_BILLBOARD; } ParticleShaderType getShaderType() const { return m_shaderType; } diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index 368dea2ecdc..da94faa0db6 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -752,7 +752,7 @@ void Particle::loadPostProcess() ParticleSystemInfo::ParticleSystemInfo() { m_priority = PARTICLE_PRIORITY_LOWEST; - m_particleAlignment = false; + m_particleAlignment = PARTICLE_ALIGNMENT_BILLBOARD; m_isEmitAboveGroundOnly = false; m_isParticleUpTowardsEmitter = false; @@ -815,7 +815,11 @@ void ParticleSystemInfo::xfer( Xfer *xfer ) Int i; // version +#if RETAIL_COMPATIBLE_XFER_SAVE XferVersion currentVersion = 1; +#else + XferVersion currentVersion = 2; +#endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -1001,8 +1005,20 @@ void ParticleSystemInfo::xfer( Xfer *xfer ) // is emission volume hollow xfer->xferBool( &m_isEmissionVolumeHollow ); - // is ground aligned - xfer->xferBool( &m_particleAlignment ); + // TheSuperHackers @refactor stephanmeesters 07/09/2026 + // Replace the original ground-alignment boolean with an enum to support additional particle alignments. + // Preserve save compatibility by mapping all non-billboard alignments to ground-aligned particles. + if (version <= 1) + { + Bool groundAligned = m_particleAlignment > PARTICLE_ALIGNMENT_BILLBOARD; + xfer->xferBool( &groundAligned ); + if (xfer->getXferMode() == XFER_LOAD) + m_particleAlignment = groundAligned ? PARTICLE_ALIGNMENT_XYPLANAR : PARTICLE_ALIGNMENT_BILLBOARD; + } + else + { + xfer->xferUser( &m_particleAlignment, sizeof( ParticleAlignmentType ) ); + } // emit above ground only xfer->xferBool( &m_isEmitAboveGroundOnly ); @@ -1749,7 +1765,7 @@ Particle *ParticleSystem::createParticle( const ParticleInfo *info, TheGameLODManager->isParticleSkipped()) ) return nullptr; - if ( getParticleCount() > 0 && priority == AREA_EFFECT && m_particleAlignment && TheParticleSystemManager->getFieldParticleCount() > (UnsignedInt)TheGlobalData->m_maxFieldParticleCount ) + if ( getParticleCount() > 0 && priority == AREA_EFFECT && !shouldBillboard() && TheParticleSystemManager->getFieldParticleCount() > (UnsignedInt)TheGlobalData->m_maxFieldParticleCount ) return nullptr; // ALWAYS_RENDER particles are exempt from all count limits, and are always created, regardless of LOD issues. @@ -2756,7 +2772,7 @@ const FieldParse ParticleSystemTemplate::m_fieldParseTable[] = { "VolCylinderLength", INI::parseReal, nullptr, offsetof( ParticleSystemTemplate, m_emissionVolume.cylinder.length ) }, { "IsHollow", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isEmissionVolumeHollow ) }, - { "IsGroundAligned", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_particleAlignment ) }, + { "IsGroundAligned", INI::parseIndexList, GroundAlignmentTypeNames, offsetof( ParticleSystemTemplate, m_particleAlignment ) }, { "IsEmitAboveGroundOnly", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isEmitAboveGroundOnly) }, { "IsParticleUpTowardsEmitter", INI::parseBool, nullptr, offsetof( ParticleSystemTemplate, m_isParticleUpTowardsEmitter) }, diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index 2a56fbae4de..c890104fe5a 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -247,7 +247,7 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) pos = p->getPosition(); psize = p->getSize(); - m_fieldParticleCount += ( sys->getPriority() == AREA_EFFECT && sys->m_particleAlignment != FALSE ); + m_fieldParticleCount += ( sys->getPriority() == AREA_EFFECT && !sys->shouldBillboard() ); //@todo lorenzen sez: use pointer arithmetic for these arrays personalities[pointCount] = p->getPersonality(); diff --git a/Core/Tools/ParticleEditor/ParticleEditorDialog.cpp b/Core/Tools/ParticleEditor/ParticleEditorDialog.cpp index cff198540f0..a2fe76fb132 100644 --- a/Core/Tools/ParticleEditor/ParticleEditorDialog.cpp +++ b/Core/Tools/ParticleEditor/ParticleEditorDialog.cpp @@ -1092,7 +1092,7 @@ void DebugWindowDialog::getSwitchFromSystem( IN SwitchType switchType, OUT Bool& { case ST_HOLLOW: switchVal = m_particleSystem->m_isEmissionVolumeHollow; break; case ST_ONESHOT: switchVal = m_particleSystem->m_isOneShot; break; - case ST_ALIGNXY: switchVal = m_particleSystem->m_particleAlignment; break; + case ST_ALIGNXY: switchVal = m_particleSystem->m_particleAlignment == ParticleSystemInfo::PARTICLE_ALIGNMENT_XYPLANAR; break; case ST_EMITABOVEGROUNDONLY: switchVal = m_particleSystem->m_isEmitAboveGroundOnly; break; case ST_PARTICLEUPTOWARDSEMITTER: switchVal = m_particleSystem->m_isParticleUpTowardsEmitter; break; }; @@ -1108,7 +1108,7 @@ void DebugWindowDialog::updateSwitchToSystem( IN SwitchType switchType, IN const { case ST_HOLLOW: m_particleSystem->m_isEmissionVolumeHollow = switchVal; break; case ST_ONESHOT: m_particleSystem->m_isOneShot = switchVal; break; - case ST_ALIGNXY: m_particleSystem->m_particleAlignment = switchVal; break; + case ST_ALIGNXY: m_particleSystem->m_particleAlignment = switchVal ? ParticleSystemInfo::PARTICLE_ALIGNMENT_XYPLANAR : ParticleSystemInfo::PARTICLE_ALIGNMENT_BILLBOARD; break; case ST_EMITABOVEGROUNDONLY: m_particleSystem->m_isEmitAboveGroundOnly = switchVal; break; case ST_PARTICLEUPTOWARDSEMITTER: m_particleSystem->m_isParticleUpTowardsEmitter = switchVal; break; }; diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 5072d617d93..1c87df51fe5 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -9375,7 +9375,7 @@ void _writeSingleParticleSystem( File *out, ParticleSystemTemplate *templ ) } thisEntry.append(SEP_HEAD).append(F_ISHOLLOW).append(EQ_WITH_SPACES).append((templ->m_isEmissionVolumeHollow ? STR_TRUE : STR_FALSE)).append(SEP_EOL); - thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append((templ->m_particleAlignment ? STR_TRUE : STR_FALSE)).append(SEP_EOL); + thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append(GroundAlignmentTypeNames[templ->m_particleAlignment]).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISEMITABOVEGROUNDONLY).append(EQ_WITH_SPACES).append((templ->m_isEmitAboveGroundOnly ? STR_TRUE : STR_FALSE)).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISPARTICLEUPTOWARDSEMITTER).append(EQ_WITH_SPACES).append((templ->m_isParticleUpTowardsEmitter ? STR_TRUE : STR_FALSE)).append(SEP_EOL); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 99c481152e9..be7b5e7144f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -10078,7 +10078,7 @@ void _writeSingleParticleSystem( File *out, ParticleSystemTemplate *templ ) } thisEntry.append(SEP_HEAD).append(F_ISHOLLOW).append(EQ_WITH_SPACES).append((templ->m_isEmissionVolumeHollow ? STR_TRUE : STR_FALSE)).append(SEP_EOL); - thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append((templ->m_particleAlignment ? STR_TRUE : STR_FALSE)).append(SEP_EOL); + thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append(GroundAlignmentTypeNames[templ->m_particleAlignment]).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISEMITABOVEGROUNDONLY).append(EQ_WITH_SPACES).append((templ->m_isEmitAboveGroundOnly ? STR_TRUE : STR_FALSE)).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISPARTICLEUPTOWARDSEMITTER).append(EQ_WITH_SPACES).append((templ->m_isParticleUpTowardsEmitter ? STR_TRUE : STR_FALSE)).append(SEP_EOL); From a83386bae795541626fdebb13cda185210113a61 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:40:34 +0200 Subject: [PATCH 3/5] Rename F_ISXYPLANAR to F_PARTICLEALIGNMENT --- .../GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp | 4 ++-- .../GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 1c87df51fe5..9e5a4cd606f 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -9073,7 +9073,7 @@ static const std::string F_VOLSPHERERAD = "VolSphereRadius"; static const std::string F_VOLCYLRAD = "VolCylinderRadius"; static const std::string F_VOLCYLLEN = "VolCylinderLength"; static const std::string F_ISHOLLOW = "IsHollow"; -static const std::string F_ISXYPLANAR = "IsGroundAligned"; +static const std::string F_PARTICLEALIGNMENT = "IsGroundAligned"; static const std::string F_ISEMITABOVEGROUNDONLY = "IsEmitAboveGroundOnly"; static const std::string F_ISPARTICLEUPTOWARDSEMITTER @@ -9375,7 +9375,7 @@ void _writeSingleParticleSystem( File *out, ParticleSystemTemplate *templ ) } thisEntry.append(SEP_HEAD).append(F_ISHOLLOW).append(EQ_WITH_SPACES).append((templ->m_isEmissionVolumeHollow ? STR_TRUE : STR_FALSE)).append(SEP_EOL); - thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append(GroundAlignmentTypeNames[templ->m_particleAlignment]).append(SEP_EOL); + thisEntry.append(SEP_HEAD).append(F_PARTICLEALIGNMENT).append(EQ_WITH_SPACES).append(GroundAlignmentTypeNames[templ->m_particleAlignment]).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISEMITABOVEGROUNDONLY).append(EQ_WITH_SPACES).append((templ->m_isEmitAboveGroundOnly ? STR_TRUE : STR_FALSE)).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISPARTICLEUPTOWARDSEMITTER).append(EQ_WITH_SPACES).append((templ->m_isParticleUpTowardsEmitter ? STR_TRUE : STR_FALSE)).append(SEP_EOL); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index be7b5e7144f..26368e6ec06 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -9776,7 +9776,7 @@ static const std::string F_VOLSPHERERAD = "VolSphereRadius"; static const std::string F_VOLCYLRAD = "VolCylinderRadius"; static const std::string F_VOLCYLLEN = "VolCylinderLength"; static const std::string F_ISHOLLOW = "IsHollow"; -static const std::string F_ISXYPLANAR = "IsGroundAligned"; +static const std::string F_PARTICLEALIGNMENT = "IsGroundAligned"; static const std::string F_ISEMITABOVEGROUNDONLY = "IsEmitAboveGroundOnly"; static const std::string F_ISPARTICLEUPTOWARDSEMITTER @@ -10078,7 +10078,7 @@ void _writeSingleParticleSystem( File *out, ParticleSystemTemplate *templ ) } thisEntry.append(SEP_HEAD).append(F_ISHOLLOW).append(EQ_WITH_SPACES).append((templ->m_isEmissionVolumeHollow ? STR_TRUE : STR_FALSE)).append(SEP_EOL); - thisEntry.append(SEP_HEAD).append(F_ISXYPLANAR).append(EQ_WITH_SPACES).append(GroundAlignmentTypeNames[templ->m_particleAlignment]).append(SEP_EOL); + thisEntry.append(SEP_HEAD).append(F_PARTICLEALIGNMENT).append(EQ_WITH_SPACES).append(GroundAlignmentTypeNames[templ->m_particleAlignment]).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISEMITABOVEGROUNDONLY).append(EQ_WITH_SPACES).append((templ->m_isEmitAboveGroundOnly ? STR_TRUE : STR_FALSE)).append(SEP_EOL); thisEntry.append(SEP_HEAD).append(F_ISPARTICLEUPTOWARDSEMITTER).append(EQ_WITH_SPACES).append((templ->m_isParticleUpTowardsEmitter ? STR_TRUE : STR_FALSE)).append(SEP_EOL); From 33479d46c7f1125066cc678acbc764c996871d44 Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:57:59 +0200 Subject: [PATCH 4/5] Update comments --- Core/GameEngine/Include/GameClient/ParticleSys.h | 2 +- Core/GameEngine/Source/GameClient/System/ParticleSys.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index 2f48430304e..727f3e6b898 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -437,7 +437,7 @@ class ParticleSystemInfo : public Snapshot PARTICLE_ALIGNMENT_XYPLANAR, PARTICLE_ALIGNMENT_TYPE_COUNT }; - ParticleAlignmentType m_particleAlignment; + ParticleAlignmentType m_particleAlignment; ///< align particles toward the camera or with the XY plane. Bool m_isEmitAboveGroundOnly; ///< if true, only emit particles when the system is above ground. Bool m_isParticleUpTowardsEmitter; ///< if true, align the up direction to be towards the emitter. diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index da94faa0db6..9fab714f490 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -808,7 +808,9 @@ void ParticleSystemInfo::crc( Xfer *xfer ) // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: - * 1: Initial version */ + * 1: Initial version + * 2: TheSuperHackers @refactor Serialize particle alignment as an enum instead of a boolean. + */ // ------------------------------------------------------------------------------------------------ void ParticleSystemInfo::xfer( Xfer *xfer ) { @@ -1005,11 +1007,10 @@ void ParticleSystemInfo::xfer( Xfer *xfer ) // is emission volume hollow xfer->xferBool( &m_isEmissionVolumeHollow ); - // TheSuperHackers @refactor stephanmeesters 07/09/2026 - // Replace the original ground-alignment boolean with an enum to support additional particle alignments. - // Preserve save compatibility by mapping all non-billboard alignments to ground-aligned particles. + // particle alignment if (version <= 1) { + // TheSuperHackers @info Preserve save compatibility by mapping all non-billboard alignments to ground-aligned particles. Bool groundAligned = m_particleAlignment > PARTICLE_ALIGNMENT_BILLBOARD; xfer->xferBool( &groundAligned ); if (xfer->getXferMode() == XFER_LOAD) From d0272329f3df174a0bfa87901bd85da3c38da96b Mon Sep 17 00:00:00 2001 From: stm <14291421+stephanmeesters@users.noreply.github.com> Date: Sat, 12 Sep 2026 12:10:30 +0200 Subject: [PATCH 5/5] Process review comments --- Core/GameEngine/Include/GameClient/ParticleSys.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index 727f3e6b898..d894d8b0ebb 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -431,7 +431,8 @@ class ParticleSystemInfo : public Snapshot m_emissionVolume; ///< the dimensions of the emission volume Bool m_isEmissionVolumeHollow; ///< if true, only create particles at boundary of volume - enum ParticleAlignmentType + + enum ParticleAlignmentType CPP_11(: Int) { PARTICLE_ALIGNMENT_BILLBOARD = 0, PARTICLE_ALIGNMENT_XYPLANAR,