Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include "GameLogic/Module/DeliverPayloadAIUpdate.h"
#include "GameLogic/Module/HackInternetAIUpdate.h"
#include "GameLogic/Module/HordeUpdate.h"
#include "GameLogic/Module/BehaviorModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/PolygonTrigger.h"
Expand All @@ -73,6 +74,41 @@

#define SLEEPY_AI

// TheSuperHackers @bugfix arazmj 15/08/2026 Allow same-player tunnel and cave networks to exit through another endpoint.
static Bool isCaveContainer(const Object *obj)
{
if (!obj)
return FALSE;

for (BehaviorModule **module = obj->getBehaviorModules(); *module; ++module)
{
if ((*module)->getCaveInterface() != nullptr)
return TRUE;
}

return FALSE;
}

static Bool isSharedNetworkExitContainer(const Object *fromContainer, const Object *toContainer)
{
if (!fromContainer || !toContainer)
return FALSE;

const ContainModuleInterface *fromContain = fromContainer->getContain();
const ContainModuleInterface *toContain = toContainer->getContain();
if (!fromContain || !toContain)
return FALSE;

const Player *fromPlayer = fromContainer->getControllingPlayer();
const Player *toPlayer = toContainer->getControllingPlayer();
if (!fromPlayer || fromPlayer != toPlayer)
return FALSE;

if (fromContain->isTunnelContain() && toContain->isTunnelContain())
return TRUE;

return isCaveContainer(fromContainer) && isCaveContainer(toContainer);
}

//-------------------------------------------------------------------------------------------------
AIUpdateModuleData::AIUpdateModuleData()
Expand Down Expand Up @@ -3677,10 +3713,14 @@ void AIUpdateInterface::privateExit( Object *objectToExit, CommandSourceType cmd
// TheSuperHackers @bugfix Caball009 10/08/2026 Don't process invalid exit commands,
// because an object should not attempt to exit something it's not contained by.
#if !RETAIL_COMPATIBLE_CRC
// @todo Remove function parameter 'objectToExit' because it's become obsolete.

if (us->getContainedBy() != objectToExit)
return;
{
const Player *unitPlayer = us->getControllingPlayer();
const Player *exitPlayer = objectToExit ? objectToExit->getControllingPlayer() : nullptr;
if (!unitPlayer || unitPlayer != exitPlayer
|| !isSharedNetworkExitContainer(us->getContainedBy(), objectToExit))
return;
}
#endif
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include "GameLogic/Module/DeliverPayloadAIUpdate.h"
#include "GameLogic/Module/HackInternetAIUpdate.h"
#include "GameLogic/Module/HordeUpdate.h"
#include "GameLogic/Module/BehaviorModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/PolygonTrigger.h"
Expand All @@ -73,6 +74,41 @@

#define SLEEPY_AI

// TheSuperHackers @bugfix arazmj 15/08/2026 Allow same-player tunnel and cave networks to exit through another endpoint.
static Bool isCaveContainer(const Object *obj)
{
if (!obj)
return FALSE;

for (BehaviorModule **module = obj->getBehaviorModules(); *module; ++module)
{
if ((*module)->getCaveInterface() != nullptr)
return TRUE;
}

return FALSE;
}

static Bool isSharedNetworkExitContainer(const Object *fromContainer, const Object *toContainer)
{
if (!fromContainer || !toContainer)
return FALSE;

const ContainModuleInterface *fromContain = fromContainer->getContain();
const ContainModuleInterface *toContain = toContainer->getContain();
if (!fromContain || !toContain)
return FALSE;

const Player *fromPlayer = fromContainer->getControllingPlayer();
const Player *toPlayer = toContainer->getControllingPlayer();
if (!fromPlayer || fromPlayer != toPlayer)
return FALSE;

if (fromContain->isTunnelContain() && toContain->isTunnelContain())
return TRUE;

return isCaveContainer(fromContainer) && isCaveContainer(toContainer);
Comment on lines +107 to +110

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Cave index not checked 🐞 Bug ≡ Correctness

isSharedNetworkExitContainer allows exiting via any same-player cave endpoint solely based on the
presence of a CaveInterface, but caves can be partitioned into separate networks via
CaveIndex/TunnelTracker. This can permit exits between unrelated cave systems controlled by the same
player, bypassing intended cave-network isolation.
Agent Prompt
## Issue description
`isSharedNetworkExitContainer` currently treats *any* two same-player cave containers as being in the same shared network. Caves can be grouped into multiple independent networks using `CaveIndex` (each index maps to a different `TunnelTracker`). Without verifying that both endpoints are in the same tracker/network, a unit contained in cave-network A can be ordered to exit from cave-network B, which violates the cave system's intended separation.

## Issue Context
- Cave networks are keyed by `CaveIndex` and backed by `TunnelTracker` instances; separate indices represent separate networks.
- `CaveContain` stores/queries passengers through the `TunnelTracker` for its `m_caveIndex`.

## Fix Focus Areas
- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[77-111]
- Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp[77-111]

## Suggested fix
Update the cave-acceptance condition in `isSharedNetworkExitContainer` to also require that both containers are backed by the same shared contained-items list (i.e., the same tracker), for example:

- Fetch `fromContain->getContainedItemsList()` and `toContain->getContainedItemsList()`.
- Require both non-null and pointer-equal when allowing the cave path:
  - `return isCaveContainer(fromContainer) && isCaveContainer(toContainer) && fromList && fromList == toList;`

(Optionally, you can apply the same `getContainedItemsList()` pointer-equality check to tunnel endpoints as an extra safety net, while still keeping the existing `isTunnelContain()` checks.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}

//-------------------------------------------------------------------------------------------------
AIUpdateModuleData::AIUpdateModuleData()
Expand Down Expand Up @@ -3832,10 +3868,14 @@ void AIUpdateInterface::privateExit( Object *objectToExit, CommandSourceType cmd
// TheSuperHackers @bugfix Caball009 10/08/2026 Don't process invalid exit commands,
// because an object should not attempt to exit something it's not contained by.
#if !RETAIL_COMPATIBLE_CRC
// @todo Remove function parameter 'objectToExit' because it's become obsolete.

if (us->getContainedBy() != objectToExit)
return;
{
const Player *unitPlayer = us->getControllingPlayer();
const Player *exitPlayer = objectToExit ? objectToExit->getControllingPlayer() : nullptr;
if (!unitPlayer || unitPlayer != exitPlayer
|| !isSharedNetworkExitContainer(us->getContainedBy(), objectToExit))
return;
}
#endif
}

Expand Down Expand Up @@ -3871,10 +3911,14 @@ void AIUpdateInterface::privateExitInstantly( Object *objectToExit, CommandSourc
// TheSuperHackers @bugfix Caball009 10/08/2026 Don't process invalid exit commands,
// because an object should not attempt to exit something it's not contained by.
#if !RETAIL_COMPATIBLE_CRC
// @todo Remove function parameter 'objectToExit' because it's become obsolete.

if (us->getContainedBy() != objectToExit)
return;
{
const Player *unitPlayer = us->getControllingPlayer();
const Player *exitPlayer = objectToExit ? objectToExit->getControllingPlayer() : nullptr;
if (!unitPlayer || unitPlayer != exitPlayer
|| !isSharedNetworkExitContainer(us->getContainedBy(), objectToExit))
return;
}
#endif
}

Expand Down
Loading