From 62d06421a48ae79090c60d7af4139cec3e1d811c Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:50:13 +0200 Subject: [PATCH] bugfix(contain): Avoid Generals crashes for units in tunnels after transfer to ally. --- .../GameEngine/Include/GameLogic/GameLogic.h | 4 ++ .../Source/GameLogic/AI/AIStates.cpp | 14 +++++++ .../GameLogic/Object/Update/AIUpdate.cpp | 14 +++++++ .../Source/GameLogic/System/GameLogic.cpp | 39 ++++++++++++++++++- 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index 9e2513986ce..e6ed8ae6d9b 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -249,6 +249,10 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool isQuitToDesktopRequested() const { return m_quitToDesktopAfterMatch; } +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC + void friend_destroyInTunnelContain(const Object* strandedObject); +#endif + protected: // snapshot methods diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index 2b77abfb794..4ce8ac5b2cf 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -4832,6 +4832,20 @@ StateReturnType AIAttackAimAtTargetState::onEnter() if( containedBy && weapon ) { ContainModuleInterface *contain = containedBy->getContain(); + +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Caball009 22/09/2026 Return early to prevent a crash. + // This is needed when a Tunnel Network with units is transferred to another player and then sold or destroyed. + // The units are not properly transferred and are left in an invalid state. + if (!contain) + { + // remove all such units from the tunnel tracker and destroy them + TheGameLogic->friend_destroyInTunnelContain(source); + + return STATE_FAILURE; + } +#endif + if (victim) { inFiringRange = contain->attemptBestFirePointPosition( source, weapon, victim ); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index 526516e21e7..e1752fa3db4 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -4318,6 +4318,20 @@ Object* AIUpdateInterface::getNextMoodTarget( Bool calledByAI, Bool calledDuring //Not contained return nullptr; } + +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Caball009 22/09/2026 Return early to prevent a crash. + // This is needed when a Tunnel Network with units is transferred to another player and then sold or destroyed. + // The units are not properly transferred and are left in an invalid state. + if (!container->getContain()) + { + // remove all such units from the tunnel tracker and destroy them + TheGameLogic->friend_destroyInTunnelContain(obj); + + return nullptr; + } +#endif + if( !container->getContain()->isPassengerAllowedToFire() ) { //Container doesn't allow for passenger to shoot. diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 09048b600d6..0659bac5ec9 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -54,7 +54,9 @@ #include "Common/ThingFactory.h" #include "Common/Team.h" #include "Common/ThingTemplate.h" -#include "GameClient/Water.h" +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC +#include "Common/TunnelTracker.h" +#endif #include "Common/WellKnownKeys.h" #include "Common/Xfer.h" #include "Common/XferCRC.h" @@ -73,6 +75,7 @@ #include "GameClient/View.h" #include "GameClient/CampaignManager.h" #include "GameClient/GameWindowTransitions.h" +#include "GameClient/Water.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" @@ -2829,6 +2832,40 @@ void GameLogic::friend_awakenUpdateModule(Object* obj, UpdateModulePtr u, Unsign } } +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC +// ------------------------------------------------------------------------------------------------ +// Find the tunnel tracker that contains the object and destroy all objects in the contain list +// ------------------------------------------------------------------------------------------------ +void GameLogic::friend_destroyInTunnelContain(const Object* strandedObject) +{ + for (Int i = 0; i < ThePlayerList->getPlayerCount(); ++i) + { + TunnelTracker* tracker = ThePlayerList->getNthPlayer(i)->getTunnelSystem(); + if (!tracker) + continue; + + const ContainedItemsList& list = *tracker->getContainedItemsList(); + + const ContainedItemsList::const_iterator it = std::find(list.begin(), list.end(), strandedObject); + if (it == list.end()) + continue; + + while (!list.empty()) + { + Object* obj = list.front(); + + tracker->removeFromContain(obj); + + obj->onRemovedFrom(nullptr); + + destroyObject(obj); + } + + break; + } +} +#endif + // ------------------------------------------------------------------------------------------------ #ifdef DO_UNIT_TIMINGS enum {TIME_FRAMES=100};