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..3b69eed6050 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -4829,9 +4829,28 @@ StateReturnType AIAttackAimAtTargetState::onEnter() //to move to the best fire point, check if it's in firing range, and if not //move it back so another unit with longer range can! Object *containedBy = source->getContainedBy(); + ContainModuleInterface *contain = containedBy ? containedBy->getContain() : nullptr; + +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC if( containedBy && weapon ) +#else + if( containedBy && weapon && contain && contain->isEnclosingContainerFor( source ) ) +#endif { - 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 + + // non enclosing garrison containers do not use firepoints. Lorenzen, 6/11/03 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..227bd3c49bf 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -4318,9 +4318,24 @@ 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. + // Sorry, stealthed and not allowed to idle fire when stealthed. + // Being in a firing container is an exception to this veto. return nullptr; } } 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}; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index 15276811ccd..6219c1663ae 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -252,6 +252,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/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index dbfddab2192..86f43fecc65 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -4953,8 +4953,25 @@ StateReturnType AIAttackAimAtTargetState::onEnter() Object *containedBy = source->getContainedBy(); ContainModuleInterface *contain = containedBy ? containedBy->getContain() : nullptr; +#if RTS_GENERALS && RETAIL_COMPATIBLE_CRC + if( containedBy && weapon ) +#else if( containedBy && weapon && contain && contain->isEnclosingContainerFor( source ) ) +#endif { +#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 + // non enclosing garrison containers do not use firepoints. Lorenzen, 6/11/03 if (victim) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index 195fcf552f9..07c89b71541 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -4559,13 +4559,33 @@ Object* AIUpdateInterface::getNextMoodTarget( Bool calledByAI, Bool calledDuring { if( !canAutoAcquireWhileStealthed() ) { - const Object *container = obj->getContainedBy(); - if( ! (container && container->getContain()->isPassengerAllowedToFire()) ) - { + //He's stealthed and idle, but if he's garrisoned, then that's a whole different matter.... + const Object *container = obj->getContainedBy(); + if( !container ) + { + //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() ) + { // Sorry, stealthed and not allowed to idle fire when stealthed. // Being in a firing container is an exception to this veto. - return nullptr; - } + return nullptr; + } } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 0d8d4e64692..86fa19d0241 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -55,8 +55,9 @@ #include "Common/ThingFactory.h" #include "Common/Team.h" #include "Common/ThingTemplate.h" -#include "GameClient/Water.h" -#include "GameClient/Snow.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,8 +74,10 @@ #include "GameClient/MapUtil.h" #include "GameClient/Mouse.h" #include "GameClient/ParticleSys.h" +#include "GameClient/Snow.h" #include "GameClient/TerrainVisual.h" #include "GameClient/View.h" +#include "GameClient/Water.h" #include "GameClient/CampaignManager.h" #include "GameClient/GameWindowTransitions.h" @@ -3154,11 +3157,39 @@ 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