From 2bb9b2c54962ab27db02dd94b495faf73885a607 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Tue, 31 Mar 2026 11:28:07 +1100 Subject: [PATCH 1/3] fix: Ensure container exists when checking if a specific rider is free to exit --- .../Source/GameLogic/Object/Contain/TransportContain.cpp | 2 +- .../Source/GameLogic/Object/Contain/TransportContain.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 7c533936c5b..38634494fc2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -460,7 +460,7 @@ Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject) // 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)) + if (specificObject->getContainedBy() && 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 fe364eee869..3e84a6eb135 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -571,7 +571,7 @@ Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject) // 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)) + if (specificObject->getContainedBy() && specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) return FALSE; #endif From 206aaec8460f2917628abf6caa5e8f8b6edd0c57 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Sun, 3 May 2026 21:42:06 +1000 Subject: [PATCH 2/3] bugfix: Abort AIExitState if the containing object dies --- Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp | 5 +++++ .../Source/GameLogic/Object/Contain/TransportContain.cpp | 2 +- GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp | 5 +++++ .../Source/GameLogic/Object/Contain/TransportContain.cpp | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 3ac19981a52..805fde24418 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -6272,7 +6272,12 @@ StateReturnType AIExitState::update() //GS. The goal of unified ExitInterfaces dies a horrible death. I can't ask Object for the exit, // as removeFromContain is only in the Contain type. I'm splitting the names in shame. ExitInterface* goalExitInterface = goal->getContain() ? goal->getContain()->getContainExitInterface() : nullptr; +#if RETAIL_COMPATIBLE_CRC if( goalExitInterface == nullptr ) +#else + // TheSuperHackers @bugfix Stubbjax 03/05/2026 Stop trying to exit if the container is dead, as we are already ejected. + if (goalExitInterface == nullptr || goal->isEffectivelyDead()) +#endif return STATE_FAILURE; if( goalExitInterface->isExitBusy() ) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 38634494fc2..7c533936c5b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -460,7 +460,7 @@ Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject) // 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() && specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) + if (specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) return FALSE; #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index ed72618ea52..8917d155ba7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -6484,7 +6484,12 @@ StateReturnType AIExitState::update() //GS. The goal of unified ExitInterfaces dies a horrible death. I can't ask Object for the exit, // as removeFromContain is only in the Contain type. I'm splitting the names in shame. ExitInterface* goalExitInterface = goal->getContain() ? goal->getContain()->getContainExitInterface() : nullptr; +#if RETAIL_COMPATIBLE_CRC if( goalExitInterface == nullptr ) +#else + // TheSuperHackers @bugfix Stubbjax 03/05/2026 Stop trying to exit if the container is dead, as we are already ejected. + if (goalExitInterface == nullptr || goal->isEffectivelyDead()) +#endif return STATE_FAILURE; if( goalExitInterface->isExitBusy() ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 3e84a6eb135..fe364eee869 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -571,7 +571,7 @@ Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject) // 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() && specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) + if (specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) return FALSE; #endif From 90b207deee4dddbbfa70313bddc34399edb629ba Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Tue, 5 May 2026 12:43:50 +1000 Subject: [PATCH 3/3] refactor: Convert fix into a standalone block --- .../Code/GameEngine/Source/GameLogic/AI/AIStates.cpp | 9 +++++---- .../Code/GameEngine/Source/GameLogic/AI/AIStates.cpp | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 805fde24418..12fcf564aec 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -6272,13 +6272,14 @@ StateReturnType AIExitState::update() //GS. The goal of unified ExitInterfaces dies a horrible death. I can't ask Object for the exit, // as removeFromContain is only in the Contain type. I'm splitting the names in shame. ExitInterface* goalExitInterface = goal->getContain() ? goal->getContain()->getContainExitInterface() : nullptr; -#if RETAIL_COMPATIBLE_CRC if( goalExitInterface == nullptr ) -#else + return STATE_FAILURE; + +#if !RETAIL_COMPATIBLE_CRC // TheSuperHackers @bugfix Stubbjax 03/05/2026 Stop trying to exit if the container is dead, as we are already ejected. - if (goalExitInterface == nullptr || goal->isEffectivelyDead()) -#endif + if (goal->isEffectivelyDead()) return STATE_FAILURE; +#endif if( goalExitInterface->isExitBusy() ) return STATE_CONTINUE;// Just wait a sec. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 8917d155ba7..33b4bcf7171 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -6484,13 +6484,14 @@ StateReturnType AIExitState::update() //GS. The goal of unified ExitInterfaces dies a horrible death. I can't ask Object for the exit, // as removeFromContain is only in the Contain type. I'm splitting the names in shame. ExitInterface* goalExitInterface = goal->getContain() ? goal->getContain()->getContainExitInterface() : nullptr; -#if RETAIL_COMPATIBLE_CRC if( goalExitInterface == nullptr ) -#else + return STATE_FAILURE; + +#if !RETAIL_COMPATIBLE_CRC // TheSuperHackers @bugfix Stubbjax 03/05/2026 Stop trying to exit if the container is dead, as we are already ejected. - if (goalExitInterface == nullptr || goal->isEffectivelyDead()) -#endif + if (goal->isEffectivelyDead()) return STATE_FAILURE; +#endif if( goalExitInterface->isExitBusy() ) return STATE_CONTINUE;// Just wait a sec.