From 745ccc615cd8fe114516d65c525021aa86af8b63 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Mon, 7 Sep 2026 18:52:17 +1000 Subject: [PATCH 1/3] bugfix: Detonated GLA Demo Battle Buses are no longer instantly deleted without firing their death weapons --- .../Module/BattleBusSlowDeathBehavior.h | 1 + .../Include/GameLogic/Module/SlowDeathBehavior.h | 2 ++ .../Source/GameLogic/Object/Body/UndeadBody.cpp | 16 ++++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattleBusSlowDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattleBusSlowDeathBehavior.h index 3993191f350..a7fa9b9444f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattleBusSlowDeathBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattleBusSlowDeathBehavior.h @@ -75,6 +75,7 @@ class BattleBusSlowDeathBehavior : public SlowDeathBehavior // slow death methods virtual void onDie( const DamageInfo *damageInfo ) override; virtual void beginSlowDeath( const DamageInfo *damageInfo ) override; + virtual Bool isRealDeath() const override { return m_isRealDeath; } virtual UpdateSleepTime update() override; protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SlowDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SlowDeathBehavior.h index 67cfccc5d31..2ca95eb6791 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SlowDeathBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SlowDeathBehavior.h @@ -117,6 +117,7 @@ class SlowDeathBehaviorInterface virtual void beginSlowDeath( const DamageInfo *damageInfo ) = 0; virtual Int getProbabilityModifier( const DamageInfo *damageInfo ) const = 0; virtual Bool isDieApplicable(const DamageInfo *damageInfo) const = 0; + virtual Bool isRealDeath() const = 0; }; //------------------------------------------------------------------------------------------------- @@ -152,6 +153,7 @@ class SlowDeathBehavior : public UpdateModule, virtual void beginSlowDeath( const DamageInfo *damageInfo ) override; virtual Int getProbabilityModifier( const DamageInfo *damageInfo ) const override; virtual Bool isDieApplicable(const DamageInfo *damageInfo) const override { return getSlowDeathBehaviorModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo); } + virtual Bool isRealDeath() const override { return true; } protected: diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp index a6101fae710..c1fcd6795b8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp @@ -96,6 +96,11 @@ void UndeadBody::attemptDamage( DamageInfo *damageInfo ) ActiveBody::attemptDamage(damageInfo); +#if !RETAIL_COMPATIBLE_CRC + if (getObject()->isEffectivelyDead()) + return; +#endif + // After we take it (which allows for damaging special effects), we will do our modifications to the body module if( shouldStartSecondLife ) startSecondLife(damageInfo); @@ -141,6 +146,17 @@ void UndeadBody::startSecondLife(DamageInfo *damageInfo) roll -= sdu->getProbabilityModifier( damageInfo ); if (roll <= 0) { +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Stubbjax 07/09/2026 Ensure the object's die modules trigger their + // onDie events if there is no SlowDeathBehavior to handle the second life logic. + if (sdu->isRealDeath()) + { + damageInfo->in.m_kill = true; + ActiveBody::attemptDamage(damageInfo); + break; + } +#endif + sdu->beginSlowDeath(damageInfo); return; } From 7e477beb5b72033eff50d9eeab2cdffceed587af Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Thu, 10 Sep 2026 00:45:17 +1000 Subject: [PATCH 2/3] refactor: Move second life logic to a more logical location --- .../Source/GameLogic/Object/Body/UndeadBody.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp index c1fcd6795b8..23fff571b82 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp @@ -110,16 +110,17 @@ void UndeadBody::attemptDamage( DamageInfo *damageInfo ) // ------------------------------------------------------------------------------------------------ void UndeadBody::startSecondLife(DamageInfo *damageInfo) { - const UndeadBodyModuleData *data = getUndeadBodyModuleData(); - +#if RETAIL_COMPATIBLE_CRC // Flag module as no longer intercepting damage m_isSecondLife = TRUE; // Modify ActiveBody's max health and initial health + const UndeadBodyModuleData* data = getUndeadBodyModuleData(); setMaxHealth(data->m_secondLifeMaxHealth, FULLY_HEAL); // Set Armor set flag to use second life armor setArmorSetFlag(ARMORSET_SECOND_LIFE); +#endif // Fire the Slow Death module. The fact that this is not the result of an onDie will cause the special behavior Int total = 0; @@ -155,6 +156,16 @@ void UndeadBody::startSecondLife(DamageInfo *damageInfo) ActiveBody::attemptDamage(damageInfo); break; } + + // Flag module as no longer intercepting damage + m_isSecondLife = TRUE; + + // Modify ActiveBody's max health and initial health + const UndeadBodyModuleData* data = getUndeadBodyModuleData(); + setMaxHealth(data->m_secondLifeMaxHealth, FULLY_HEAL); + + // Set Armor set flag to use second life armor + setArmorSetFlag(ARMORSET_SECOND_LIFE); #endif sdu->beginSlowDeath(damageInfo); @@ -162,7 +173,6 @@ void UndeadBody::startSecondLife(DamageInfo *damageInfo) } } } - } From b510d2321e9eda49e2c54cacbebad882345f3e5c Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:16:58 +0200 Subject: [PATCH 3/3] Improve battle bus second life impl (zero hour only) --- Core/GameEngine/Include/GameLogic/Damage.h | 2 + .../GameLogic/Module/SlowDeathBehavior.h | 5 +- .../Include/GameLogic/Module/UndeadBody.h | 3 +- .../Object/Behavior/SlowDeathBehavior.cpp | 45 ++++++-- .../GameLogic/Object/Body/UndeadBody.cpp | 104 +++++++++--------- 5 files changed, 91 insertions(+), 68 deletions(-) diff --git a/Core/GameEngine/Include/GameLogic/Damage.h b/Core/GameEngine/Include/GameLogic/Damage.h index 0334c32f27f..c7ea0b191a8 100644 --- a/Core/GameEngine/Include/GameLogic/Damage.h +++ b/Core/GameEngine/Include/GameLogic/Damage.h @@ -270,6 +270,7 @@ class DamageInfoInput : public Snapshot m_deathType = DEATH_NORMAL; m_amount = 0; m_kill = FALSE; + m_enterSecondLife = FALSE; m_shockWaveVector.zero(); m_shockWaveAmount = 0.0f; @@ -286,6 +287,7 @@ class DamageInfoInput : public Snapshot DeathType m_deathType; ///< if this kills us, death type to be used Real m_amount; ///< # value of how much damage to inflict Bool m_kill; ///< will always cause object to die regardless of damage. + Bool m_enterSecondLife; // These are used for damage causing shockwave, forcing units affected to be pushed around Coord3D m_shockWaveVector; ///< This represents the incoming damage vector diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SlowDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SlowDeathBehavior.h index 2ca95eb6791..cd5c39cbd0e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SlowDeathBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SlowDeathBehavior.h @@ -152,14 +152,17 @@ class SlowDeathBehavior : public UpdateModule, // SlowDeathBehaviorInterface virtual void beginSlowDeath( const DamageInfo *damageInfo ) override; virtual Int getProbabilityModifier( const DamageInfo *damageInfo ) const override; - virtual Bool isDieApplicable(const DamageInfo *damageInfo) const override { return getSlowDeathBehaviorModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo); } + virtual Bool isDieApplicable(const DamageInfo *damageInfo) const override; virtual Bool isRealDeath() const override { return true; } + static Int computeTotalSlowDeathProbability(const Object *obj, const DamageInfo *damageInfo); + protected: void doPhaseStuff(SlowDeathPhaseType sdphase); Bool isSlowDeathActivated() const { return (m_flags & (1<m_probabilityModifier + overkillModifier, 1 ); } +Bool SlowDeathBehavior::isDieApplicable(const DamageInfo *damageInfo) const +{ + if (!getSlowDeathBehaviorModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo)) + return false; + +#if !RETAIL_COMPATIBLE_CRC + if (damageInfo->in.m_enterSecondLife != canEnterSecondLife()) + return false; +#endif + + return true; +} + //------------------------------------------------------------------------------------------------- static void calcRandomForce(Real minMag, Real maxMag, Real minPitch, Real maxPitch, Coord3D& force) { @@ -490,23 +503,13 @@ void SlowDeathBehavior::onDie( const DamageInfo *damageInfo ) // deselect this unit for all players. TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE); - Int total = 0; - BehaviorModule** update = obj->getBehaviorModules(); - for (; *update; ++update) - { - SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface(); - if (sdu != nullptr && sdu->isDieApplicable(damageInfo)) - { - total += sdu->getProbabilityModifier( damageInfo ); - } - } + const Int total = computeTotalSlowDeathProbability(obj, damageInfo); DEBUG_ASSERTCRASH(total > 0, ("Hmm, this is wrong")); - // this returns a value from 1...total, inclusive Int roll = GameLogicRandomValue(1, total); - for (/* UpdateModuleInterface** */ update = obj->getBehaviorModules(); *update; ++update) + for (BehaviorModule** update = obj->getBehaviorModules(); *update; ++update) { SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface(); if (sdu != nullptr && sdu->isDieApplicable(damageInfo)) @@ -523,6 +526,24 @@ void SlowDeathBehavior::onDie( const DamageInfo *damageInfo ) DEBUG_CRASH(("We should never get here")); } + +// ------------------------------------------------------------------------------------------------ +Int SlowDeathBehavior::computeTotalSlowDeathProbability(const Object *obj, const DamageInfo *damageInfo) +{ + Int total = 0; + + for (BehaviorModule** update = obj->getBehaviorModules(); *update; ++update) + { + SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface(); + if (sdu != nullptr && sdu->isDieApplicable(damageInfo)) + { + total += sdu->getProbabilityModifier( damageInfo ); + } + } + + return total; +} + // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp index 23fff571b82..df143477c85 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/UndeadBody.cpp @@ -75,8 +75,7 @@ UndeadBody::~UndeadBody() // ------------------------------------------------------------------------------------------------ void UndeadBody::attemptDamage( DamageInfo *damageInfo ) { - // If we are on our first life, see if this damage will kill us. If it will, bind it to one hitpoint - // remaining, then go ahead and take it. + // If we are on our first life, see if this damage will kill us. Bool shouldStartSecondLife = FALSE; if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE @@ -90,56 +89,50 @@ void UndeadBody::attemptDamage( DamageInfo *damageInfo ) && IsHealthDamagingDamage(damageInfo->in.m_damageType) ) { - damageInfo->in.m_amount = min( damageInfo->in.m_amount, getHealth() - 1 ); shouldStartSecondLife = TRUE; } - ActiveBody::attemptDamage(damageInfo); - -#if !RETAIL_COMPATIBLE_CRC - if (getObject()->isEffectivelyDead()) - return; -#endif - // After we take it (which allows for damaging special effects), we will do our modifications to the body module if( shouldStartSecondLife ) - startSecondLife(damageInfo); + { + if( !startSecondLife(damageInfo) ) + { +#if !RETAIL_COMPATIBLE_CRC + damageInfo->in.m_kill = true; + damageInfo->in.m_enterSecondLife = false; + ActiveBody::attemptDamage(damageInfo); +#endif + } + } + else + { + ActiveBody::attemptDamage(damageInfo); + } } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void UndeadBody::startSecondLife(DamageInfo *damageInfo) +Bool UndeadBody::startSecondLife(DamageInfo *damageInfo) { #if RETAIL_COMPATIBLE_CRC - // Flag module as no longer intercepting damage - m_isSecondLife = TRUE; - - // Modify ActiveBody's max health and initial health - const UndeadBodyModuleData* data = getUndeadBodyModuleData(); - setMaxHealth(data->m_secondLifeMaxHealth, FULLY_HEAL); - - // Set Armor set flag to use second life armor - setArmorSetFlag(ARMORSET_SECOND_LIFE); + applySecondLife(damageInfo); #endif - // Fire the Slow Death module. The fact that this is not the result of an onDie will cause the special behavior - Int total = 0; - BehaviorModule** update = getObject()->getBehaviorModules(); - for( ; *update; ++update ) - { - SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface(); - if (sdu != nullptr && sdu->isDieApplicable(damageInfo) ) - { - total += sdu->getProbabilityModifier( damageInfo ); - } - } - DEBUG_ASSERTCRASH(total > 0, ("Hmm, this is wrong")); + damageInfo->in.m_enterSecondLife = TRUE; + const Int total = SlowDeathBehavior::computeTotalSlowDeathProbability(getObject(), damageInfo); + +#if !RETAIL_COMPATIBLE_CRC + if (total == 0) + return false; +#endif // this returns a value from 1...total, inclusive Int roll = GameLogicRandomValue(1, total); - for( update = getObject()->getBehaviorModules(); *update; ++update) + // Fire one of the Slow Death modules with Second Life at random. + // The fact that this is not the result of an onDie will cause the special behavior. + for (BehaviorModule** update = getObject()->getBehaviorModules(); *update; ++update) { SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface(); if (sdu != nullptr && sdu->isDieApplicable(damageInfo)) @@ -148,33 +141,36 @@ void UndeadBody::startSecondLife(DamageInfo *damageInfo) if (roll <= 0) { #if !RETAIL_COMPATIBLE_CRC - // TheSuperHackers @bugfix Stubbjax 07/09/2026 Ensure the object's die modules trigger their - // onDie events if there is no SlowDeathBehavior to handle the second life logic. - if (sdu->isRealDeath()) - { - damageInfo->in.m_kill = true; - ActiveBody::attemptDamage(damageInfo); - break; - } - - // Flag module as no longer intercepting damage - m_isSecondLife = TRUE; - - // Modify ActiveBody's max health and initial health - const UndeadBodyModuleData* data = getUndeadBodyModuleData(); - setMaxHealth(data->m_secondLifeMaxHealth, FULLY_HEAL); - - // Set Armor set flag to use second life armor - setArmorSetFlag(ARMORSET_SECOND_LIFE); + applySecondLife(damageInfo); #endif - sdu->beginSlowDeath(damageInfo); - return; + return true; } } } + + return false; } +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UndeadBody::applySecondLife(DamageInfo *damageInfo) +{ + // In second life, bind it to one hit point remaining, then go ahead and take it + damageInfo->in.m_amount = min(damageInfo->in.m_amount, getHealth() - 1); + + // Damage first to apply hit effects + ActiveBody::attemptDamage(damageInfo); + + // Flag module as no longer intercepting damage + m_isSecondLife = TRUE; + + // Modify ActiveBody's max health and initial health + setMaxHealth(getUndeadBodyModuleData()->m_secondLifeMaxHealth, FULLY_HEAL); + + // Set Armor set flag to use second life armor + setArmorSetFlag(ARMORSET_SECOND_LIFE); +} // ------------------------------------------------------------------------------------------------ /** CRC */