From 891735b424bb843af5017e2f0db39661c02ae16b Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Tue, 3 Feb 2026 00:13:13 +1100 Subject: [PATCH 1/7] bugfix: Occupants killed by their containers now kill their occupants --- .../Source/GameLogic/Object/Contain/OpenContain.cpp | 13 +++++++++++-- .../Source/GameLogic/Object/Contain/OpenContain.cpp | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 65e0565cb3a..a97b5f7941a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -1294,6 +1294,7 @@ void OpenContain::orderAllPassengersToExit( CommandSourceType commandSource ) void OpenContain::processDamageToContained() { const OpenContainModuleData* data = getOpenContainModuleData(); + const bool killContained = data->m_damagePercentageToUnits == 1.0f; #if RETAIL_COMPATIBLE_CRC @@ -1317,7 +1318,7 @@ void OpenContain::processDamageToContained() damageInfo.in.m_amount = damage; object->attemptDamage( &damageInfo ); - if( !object->isEffectivelyDead() && data->m_damagePercentageToUnits == 1.0f ) + if( !object->isEffectivelyDead() && killContained ) object->kill(); // in case we are carrying flame proof troops we have been asked to kill // TheSuperHackers @info Calls to Object::attemptDamage and Object::kill will not remove @@ -1364,6 +1365,14 @@ void OpenContain::processDamageToContained() DEBUG_ASSERTCRASH( object, ("Contain list must not contain null element") ); + // TheSuperHackers @bugfix Stubbjax 02/02/2026 If the parent container kills its occupants + // on death, then those occupants also kill their occupants, and so on. + if (killContained) + { + if (object->getContain()) + object->getContain()->processDamageToContained(percentDamage); + } + // Calculate the damage to be inflicted on each unit. Real damage = object->getBodyModule()->getMaxHealth() * data->m_damagePercentageToUnits; @@ -1374,7 +1383,7 @@ void OpenContain::processDamageToContained() damageInfo.in.m_amount = damage; object->attemptDamage( &damageInfo ); - if( !object->isEffectivelyDead() && data->m_damagePercentageToUnits == 1.0f ) + if( !object->isEffectivelyDead() && killContained ) object->kill(); // in case we are carrying flame proof troops we have been asked to kill if ( object->isEffectivelyDead() ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index d2a741e441c..d53e58d904c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -1462,6 +1462,7 @@ void OpenContain::orderAllPassengersToHackInternet( CommandSourceType commandSou void OpenContain::processDamageToContained(Real percentDamage) { const OpenContainModuleData *data = getOpenContainModuleData(); + const bool killContained = percentDamage == 1.0f; #if RETAIL_COMPATIBLE_CRC @@ -1485,7 +1486,7 @@ void OpenContain::processDamageToContained(Real percentDamage) damageInfo.in.m_amount = damage; object->attemptDamage( &damageInfo ); - if( !object->isEffectivelyDead() && percentDamage == 1.0f ) + if( !object->isEffectivelyDead() && killContained ) object->kill(); // in case we are carrying flame proof troops we have been asked to kill // TheSuperHackers @info Calls to Object::attemptDamage and Object::kill will not remove @@ -1532,6 +1533,14 @@ void OpenContain::processDamageToContained(Real percentDamage) DEBUG_ASSERTCRASH( object, ("Contain list must not contain null element") ); + // TheSuperHackers @bugfix Stubbjax 02/02/2026 If the parent container kills its occupants + // on death, then those occupants also kill their occupants, and so on. + if (killContained) + { + if (object->getContain()) + object->getContain()->processDamageToContained(percentDamage); + } + // Calculate the damage to be inflicted on each unit. Real damage = object->getBodyModule()->getMaxHealth() * percentDamage; @@ -1542,7 +1551,7 @@ void OpenContain::processDamageToContained(Real percentDamage) damageInfo.in.m_amount = damage; object->attemptDamage( &damageInfo ); - if( !object->isEffectivelyDead() && percentDamage == 1.0f ) + if( !object->isEffectivelyDead() && killContained ) object->kill(); // in case we are carrying flame proof troops we have been asked to kill if ( object->isEffectivelyDead() ) From d1943d726c0ec1bcf39008f6868de37a5ec44413 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Tue, 3 Feb 2026 01:26:54 +1100 Subject: [PATCH 2/7] unify: Align Generals implementation with Zero Hour --- .../Include/GameLogic/Module/ContainModule.h | 2 +- .../GameEngine/Include/GameLogic/Module/OpenContain.h | 2 +- .../Source/GameLogic/Object/Contain/OpenContain.cpp | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h index 0fd739a4822..6ebd21ba40a 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/ContainModule.h @@ -164,7 +164,7 @@ class ContainModuleInterface // Player Occupancy. virtual PlayerMaskType getPlayerWhoEntered() const = 0; - virtual void processDamageToContained() = 0; ///< Do our % damage to units now. + virtual void processDamageToContained(Real percentDamage) = 0; ///< Do our % damage to units now. virtual void enableLoadSounds( Bool enable ) = 0; diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h b/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h index c3dc8a535ba..bb696ba16e9 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/OpenContain.h @@ -204,7 +204,7 @@ class OpenContain : public UpdateModule, // returns true iff there are objects currently waiting to enter. virtual Bool hasObjectsWantingToEnterOrExit() const override; - virtual void processDamageToContained() override; ///< Do our % damage to units now. + virtual void processDamageToContained(Real percentDamage) override; ///< Do our % damage to units now. virtual void enableLoadSounds( Bool enable ) override { m_loadSoundsEnabled = enable; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index a97b5f7941a..09a9af61767 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -764,7 +764,7 @@ void OpenContain::onDie( const DamageInfo * damageInfo ) if( getOpenContainModuleData()->m_damagePercentageToUnits > 0 ) { //Cycle through the units and apply damage to them! - processDamageToContained(); + processDamageToContained(getOpenContainModuleData()->m_damagePercentageToUnits); } killRidersWhoAreNotFreeToExit(); @@ -1291,10 +1291,9 @@ void OpenContain::orderAllPassengersToExit( CommandSourceType commandSource ) } //------------------------------------------------------------------------------------------------- -void OpenContain::processDamageToContained() +void OpenContain::processDamageToContained(Real percentDamage) { - const OpenContainModuleData* data = getOpenContainModuleData(); - const bool killContained = data->m_damagePercentageToUnits == 1.0f; + const bool killContained = percentDamage == 1.0f; #if RETAIL_COMPATIBLE_CRC @@ -1309,7 +1308,7 @@ void OpenContain::processDamageToContained() Object *object = *it++; //Calculate the damage to be inflicted on each unit. - Real damage = object->getBodyModule()->getMaxHealth() * data->m_damagePercentageToUnits; + Real damage = object->getBodyModule()->getMaxHealth() * percentDamage; DamageInfo damageInfo; damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE; @@ -1374,7 +1373,7 @@ void OpenContain::processDamageToContained() } // Calculate the damage to be inflicted on each unit. - Real damage = object->getBodyModule()->getMaxHealth() * data->m_damagePercentageToUnits; + Real damage = object->getBodyModule()->getMaxHealth() * percentDamage; DamageInfo damageInfo; damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE; From 9eaf868d216f1e337f2a1c972ae39f13664fddf7 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Mon, 2 Mar 2026 03:05:07 +1100 Subject: [PATCH 3/7] tweak: Adjust solution implementation --- .../Source/GameLogic/Object/Contain/OpenContain.cpp | 8 -------- .../GameLogic/Object/Contain/TransportContain.cpp | 11 +++++++++++ .../Source/GameLogic/Object/Contain/OpenContain.cpp | 8 -------- .../GameLogic/Object/Contain/TransportContain.cpp | 11 +++++++++++ 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 09a9af61767..eeba053d995 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -1364,14 +1364,6 @@ void OpenContain::processDamageToContained(Real percentDamage) DEBUG_ASSERTCRASH( object, ("Contain list must not contain null element") ); - // TheSuperHackers @bugfix Stubbjax 02/02/2026 If the parent container kills its occupants - // on death, then those occupants also kill their occupants, and so on. - if (killContained) - { - if (object->getContain()) - object->getContain()->processDamageToContained(percentDamage); - } - // Calculate the damage to be inflicted on each unit. Real damage = object->getBodyModule()->getMaxHealth() * percentDamage; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 65333ea9918..46671e64675 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -434,7 +434,11 @@ void TransportContain::killRidersWhoAreNotFreeToExit() if (d->m_destroyRidersWhoAreNotFreeToExit) TheGameLogic->destroyObject(obj); else +#if RETAIL_COMPATIBLE_CRC obj->kill(); +#else + obj->kill(DAMAGE_UNRESISTABLE, d->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL); +#endif } } } @@ -456,6 +460,13 @@ Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject) if (ai && ai->getAiFreeToExit(specificObject) != FREE_TO_EXIT) return FALSE; +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Stubbjax 02/03/2026 If our parent container is held, then we + // are not free to exit. + if (specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) + return FALSE; +#endif + // I can always kick people out if I am in the air, I know what I'm doing if (me->isUsingAirborneLocomotor()) return TRUE; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index d53e58d904c..88d535313cc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -1533,14 +1533,6 @@ void OpenContain::processDamageToContained(Real percentDamage) DEBUG_ASSERTCRASH( object, ("Contain list must not contain null element") ); - // TheSuperHackers @bugfix Stubbjax 02/02/2026 If the parent container kills its occupants - // on death, then those occupants also kill their occupants, and so on. - if (killContained) - { - if (object->getContain()) - object->getContain()->processDamageToContained(percentDamage); - } - // Calculate the damage to be inflicted on each unit. Real damage = object->getBodyModule()->getMaxHealth() * percentDamage; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 32de3e281df..1fd2893afbb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -540,7 +540,11 @@ void TransportContain::killRidersWhoAreNotFreeToExit() if (d->m_destroyRidersWhoAreNotFreeToExit) TheGameLogic->destroyObject(obj); else +#if RETAIL_COMPATIBLE_CRC obj->kill(); +#else + obj->kill(DAMAGE_UNRESISTABLE, d->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL); +#endif } } } @@ -562,6 +566,13 @@ Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject) if (ai && ai->getAiFreeToExit(specificObject) != FREE_TO_EXIT) return FALSE; +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Stubbjax 02/03/2026 If our parent container is held, then we + // are not free to exit. + if (specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) + return FALSE; +#endif + // I can always kick people out if I am in the air, I know what I'm doing if (me->isUsingAirborneLocomotor()) return TRUE; From 1f155e57238a12b0758517daf7266a72176f9368 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Mon, 2 Mar 2026 15:25:31 +1100 Subject: [PATCH 4/7] perf: Optimise order of operations --- .../Source/GameLogic/Object/Contain/OpenContain.cpp | 6 ++++++ .../Source/GameLogic/Object/Contain/OpenContain.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index eeba053d995..0c1df3eaf31 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -760,6 +760,10 @@ void OpenContain::onDie( const DamageInfo * damageInfo ) if (!getOpenContainModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo)) return; +#if !RETAIL_COMPATIBLE_CRC + killRidersWhoAreNotFreeToExit(); +#endif + //Check to see if we are going to inflict damage on contained units. if( getOpenContainModuleData()->m_damagePercentageToUnits > 0 ) { @@ -767,7 +771,9 @@ void OpenContain::onDie( const DamageInfo * damageInfo ) processDamageToContained(getOpenContainModuleData()->m_damagePercentageToUnits); } +#if RETAIL_COMPATIBLE_CRC killRidersWhoAreNotFreeToExit(); +#endif // Leaving this commented out to show it can't work. We are about to die, so they will have zero // chance to hit an exitState::Update. At least we would clean them up in onDelete. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 88d535313cc..f436ed26245 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -880,6 +880,10 @@ void OpenContain::onDie( const DamageInfo * damageInfo ) if (!getOpenContainModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo)) return; +#if !RETAIL_COMPATIBLE_CRC + killRidersWhoAreNotFreeToExit(); +#endif + //Check to see if we are going to inflict damage on contained units. if( getDamagePercentageToUnits() > 0 ) { @@ -887,7 +891,9 @@ void OpenContain::onDie( const DamageInfo * damageInfo ) processDamageToContained(getDamagePercentageToUnits()); } +#if RETAIL_COMPATIBLE_CRC killRidersWhoAreNotFreeToExit(); +#endif // Leaving this commented out to show it can't work. We are about to die, so they will have zero // chance to hit an exitState::Update. At least we would clean them up in onDelete. From d0581dac5ab86f81404afc8328e7eceea4abbfa9 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Mon, 2 Mar 2026 15:41:41 +1100 Subject: [PATCH 5/7] docs: Add comments --- .../Source/GameLogic/Object/Contain/TransportContain.cpp | 1 + .../Source/GameLogic/Object/Contain/TransportContain.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 46671e64675..0e32db9cc02 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -437,6 +437,7 @@ void TransportContain::killRidersWhoAreNotFreeToExit() #if RETAIL_COMPATIBLE_CRC obj->kill(); #else + // TheSuperHackers @info Burned death prevents infantry corpses dropping out of the container. obj->kill(DAMAGE_UNRESISTABLE, d->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL); #endif } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 1fd2893afbb..893fdd2b7e9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -543,6 +543,7 @@ void TransportContain::killRidersWhoAreNotFreeToExit() #if RETAIL_COMPATIBLE_CRC obj->kill(); #else + // TheSuperHackers @info Burned death prevents infantry corpses dropping out of the container. obj->kill(DAMAGE_UNRESISTABLE, d->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL); #endif } From ecf8ac159d041f2afbdc75d07a09ec8e8b03b593 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Tue, 3 Mar 2026 02:45:53 +1100 Subject: [PATCH 6/7] fix: Object::kill does not take arguments in Generals --- .../Source/GameLogic/Object/Contain/TransportContain.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 0e32db9cc02..58f6ab23229 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -434,12 +434,7 @@ void TransportContain::killRidersWhoAreNotFreeToExit() if (d->m_destroyRidersWhoAreNotFreeToExit) TheGameLogic->destroyObject(obj); else -#if RETAIL_COMPATIBLE_CRC obj->kill(); -#else - // TheSuperHackers @info Burned death prevents infantry corpses dropping out of the container. - obj->kill(DAMAGE_UNRESISTABLE, d->m_isBurnedDeathToUnits ? DEATH_BURNED : DEATH_NORMAL); -#endif } } } From e0692683ccdde1e6548574bc73332de00deab265 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Wed, 18 Mar 2026 19:16:44 +1100 Subject: [PATCH 7/7] tweak: Add assertions --- .../Source/GameLogic/Object/Contain/TransportContain.cpp | 1 + .../Source/GameLogic/Object/Contain/TransportContain.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 58f6ab23229..7c533936c5b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -459,6 +459,7 @@ Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject) #if !RETAIL_COMPATIBLE_CRC // TheSuperHackers @bugfix Stubbjax 02/03/2026 If our parent container is held, then we // are not free to exit. + DEBUG_ASSERTCRASH(specificObject->getContainedBy(), ("rider must be contained")); if (specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) return FALSE; #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 893fdd2b7e9..fe364eee869 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -570,6 +570,7 @@ Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject) #if !RETAIL_COMPATIBLE_CRC // TheSuperHackers @bugfix Stubbjax 02/03/2026 If our parent container is held, then we // are not free to exit. + DEBUG_ASSERTCRASH(specificObject->getContainedBy(), ("rider must be contained")); if (specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) return FALSE; #endif