Skip to content
Open
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 @@ -94,7 +94,9 @@ class TunnelContain : public OpenContain, public CreateModuleInterface
virtual void onContaining( Object *obj ) override; ///< object now contains 'obj'
virtual void onRemoving( Object *obj ) override; ///< object no longer contains 'obj'
virtual void onSelling() override;///< Container is being sold. Tunnel responds by kicking people out if this is the last tunnel.
#if !(RTS_GENERALS && RETAIL_COMPATIBLE_CRC)
virtual void onCapture( Player *oldOwner, Player *newOwner ) override; // Need to change who we are registered with.
#endif

virtual void orderAllPassengersToExit( CommandSourceType commandSource ) override; ///< All of the smarts of exiting are in the passenger's AIExit. removeAllFrommContain is a last ditch system call, this is the game Evacuate

Expand Down
26 changes: 13 additions & 13 deletions Generals/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,18 +231,14 @@ void TunnelTracker::onTunnelCreated( const Object *newTunnel )
// ------------------------------------------------------------------------
void TunnelTracker::onTunnelDestroyed( const Object *deadTunnel )
{
{
std::list<ObjectID>::iterator it = std::find(m_tunnelIDs.begin(), m_tunnelIDs.end(), deadTunnel->getID());
if (it == m_tunnelIDs.end())
{
DEBUG_CRASH(("TunnelTracker::onTunnelDestroyed - Attempting to remove object '%s' that has never been tracked as a tunnel", deadTunnel->getName().str()));
return;
}
DEBUG_ASSERTCRASH(static_cast<Int>(m_tunnelCount) > 0 && m_tunnelCount == m_tunnelIDs.size(),
("TunnelTracker::onTunnelDestroyed - Tunnel count is unexpected"));
DEBUG_ASSERTCRASH(std::find(m_tunnelIDs.begin(), m_tunnelIDs.end(), deadTunnel->getID()) != m_tunnelIDs.end(),
("TunnelTracker::onTunnelDestroyed - Attempting to remove object '%s' that has never been tracked as a tunnel", deadTunnel->getName().str()));

m_tunnelCount--;
m_tunnelIDs.erase(it);
m_needsFullHealTimeUpdate = true;
}
m_tunnelCount--;
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
Comment thread
greptile-apps[bot] marked this conversation as resolved.
m_tunnelIDs.remove( deadTunnel->getID() );
m_needsFullHealTimeUpdate = true;

if( m_tunnelCount == 0 )
{
Expand All @@ -253,15 +249,19 @@ void TunnelTracker::onTunnelDestroyed( const Object *deadTunnel )
}
else
{
Object *validTunnel = TheGameLogic->findObjectByID( m_tunnelIDs.front() );
// TheSuperHackers @bugfix Caball009 01/09/2026 Check if container is empty before accessing it.
// The tunnel count may diverge from the container size, because of bugs that cannot be fixed with
// retail compatibility enabled. Expected retail behavior: empty container access results in a nullptr.
Object *tunnel = m_tunnelIDs.empty() ? nullptr : TheGameLogic->findObjectByID( m_tunnelIDs.front() );
Comment thread
Caball009 marked this conversation as resolved.

// Otherwise, make sure nobody inside remembers the dead tunnel as the one they entered
// (scripts need to use so there must be something valid here)
for(ContainedItemsList::iterator it = m_containList.begin(); it != m_containList.end(); )
{
Object* obj = *it;
++it;
if( obj->getContainedBy() == deadTunnel )
obj->onContainedBy( validTunnel );
obj->onContainedBy( tunnel );
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ void TunnelContain::onBuildComplete()

// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
#if !(RTS_GENERALS && RETAIL_COMPATIBLE_CRC)
void TunnelContain::onCapture( Player *oldOwner, Player *newOwner )
{
if( m_isCurrentlyRegistered )
Expand All @@ -421,6 +422,7 @@ void TunnelContain::onCapture( Player *oldOwner, Player *newOwner )
// extend base class
OpenContain::onCapture( oldOwner, newOwner );
}
#endif

//-------------------------------------------------------------------------------------------------
void TunnelContain::orderAllPassengersToExit( CommandSourceType commandSource )
Expand Down
6 changes: 6 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/TunnelTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ class TunnelTracker : public MemoryPoolObject,
void healObjects(); ///< heal all objects within the tunnel
#endif

#if RETAIL_COMPATIBLE_CRC
UnsignedInt friend_getTunnelCount() const {return m_tunnelCount;}///< TunnelContains are allowed to ask if they are the last one ahead of deletion time
#else
UnsignedInt friend_getTunnelCount() const { return m_tunnelIDs.size(); }///< TunnelContains are allowed to ask if they are the last one ahead of deletion time
#endif

const std::list< ObjectID > *getContainerList() const {return &m_tunnelIDs;}

Expand All @@ -87,7 +91,9 @@ class TunnelTracker : public MemoryPoolObject,
std::list< ObjectID > m_xferContainList;///< for loading of m_containList during post processing
Int m_containListSize; ///< size of the contain list
UnsignedInt m_heroUnitsContained; ///< cached hero count
#if RETAIL_COMPATIBLE_CRC
UnsignedInt m_tunnelCount; ///< How many tunnels have registered so we know when we should kill our contain list
#endif
UnsignedInt m_framesForFullHeal; ///< How many frames it takes to fully heal a unit
Bool m_needsFullHealTimeUpdate; ///< Set to true when needing to recalc full heal time to batch the operation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ class TunnelContain : public OpenContain, public CreateModuleInterface
virtual void onContaining( Object *obj, Bool wasSelected ) override; ///< object now contains 'obj'
virtual void onRemoving( Object *obj ) override; ///< object no longer contains 'obj'
virtual void onSelling() override;///< Container is being sold. Tunnel responds by kicking people out if this is the last tunnel.
#if !(RTS_GENERALS && RETAIL_COMPATIBLE_CRC)
virtual void onCapture( Player *oldOwner, Player *newOwner ) override; // Need to change who we are registered with.
#endif

virtual void orderAllPassengersToExit( CommandSourceType commandSource, Bool instantly ) override; ///< All of the smarts of exiting are in the passenger's AIExit. removeAllFrommContain is a last ditch system call, this is the game Evacuate
virtual void orderAllPassengersToIdle( CommandSourceType commandSource ) override; ///< Just like it sounds
Expand Down
95 changes: 85 additions & 10 deletions GeneralsMD/Code/GameEngine/Source/Common/RTS/TunnelTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
// ------------------------------------------------------------------------
TunnelTracker::TunnelTracker()
{
#if RETAIL_COMPATIBLE_CRC
m_tunnelCount = 0;
#endif
m_containListSize = 0;
m_heroUnitsContained = 0;
m_curNemesisID = INVALID_ID;
Expand Down Expand Up @@ -224,28 +226,73 @@ Bool TunnelTracker::isInContainer( Object *obj )
// ------------------------------------------------------------------------
void TunnelTracker::onTunnelCreated( const Object *newTunnel )
{
DEBUG_ASSERTCRASH(std::find(m_tunnelIDs.begin(), m_tunnelIDs.end(), newTunnel->getID()) == m_tunnelIDs.end(),
("TunnelTracker::onTunnelCreated - New tunnel was already added; this shouldn't happen"));

#if RETAIL_COMPATIBLE_CRC
m_tunnelCount++;
#endif
m_tunnelIDs.push_back( newTunnel->getID() );
m_needsFullHealTimeUpdate = true;
}

// ------------------------------------------------------------------------
void TunnelTracker::onTunnelDestroyed( const Object *deadTunnel )
{
#if RETAIL_COMPATIBLE_CRC

DEBUG_ASSERTCRASH(static_cast<Int>(m_tunnelCount) > 0 && m_tunnelCount == m_tunnelIDs.size(),
Comment thread
xezon marked this conversation as resolved.
("TunnelTracker::onTunnelDestroyed - Tunnel count is unexpected"));

const size_t oldSize = m_tunnelIDs.size();
Comment thread
Caball009 marked this conversation as resolved.
m_tunnelIDs.remove(deadTunnel->getID());

(void)oldSize;
DEBUG_ASSERTCRASH(oldSize != m_tunnelIDs.size(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In reality this assert is hit right and we cannot fix it? Maybe it should be a debug log instead then?

Asserts are meant to be used to validate conditions and need to be fixed if violated.

("TunnelTracker::onTunnelDestroyed - Attempting to remove object '%s' that has never been tracked as a tunnel", deadTunnel->getName().str()));

m_tunnelCount--;
m_needsFullHealTimeUpdate = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

m_needsFullHealTimeUpdate is unused with RETAIL_COMPATIBLE_CRC so it can be omitted here.


if( m_tunnelCount == 0 )
{
std::list<ObjectID>::iterator it = std::find(m_tunnelIDs.begin(), m_tunnelIDs.end(), deadTunnel->getID());
if (it == m_tunnelIDs.end())
// Kill everyone in our contain list. Cave in!
iterateContained( destroyObject, nullptr, FALSE );
m_containList.clear();
m_containListSize = 0;
}
else
{
// TheSuperHackers @bugfix Caball009 01/09/2026 Check if container is empty before accessing it.
// The tunnel count may diverge from the container size, because of bugs that cannot be fixed with
// retail compatibility enabled. Expected retail behavior: empty container access results in a nullptr.
Object *tunnel = m_tunnelIDs.empty() ? nullptr : TheGameLogic->findObjectByID( m_tunnelIDs.front() );

// Otherwise, make sure nobody inside remembers the dead tunnel as the one they entered
// (scripts need to use so there must be something valid here)
for(ContainedItemsList::iterator it = m_containList.begin(); it != m_containList.end(); )
{
DEBUG_CRASH(("TunnelTracker::onTunnelDestroyed - Attempting to remove object '%s' that has never been tracked as a tunnel", deadTunnel->getName().str()));
return;
Object* obj = *it;
++it;
if( obj->getContainedBy() == deadTunnel )
obj->onContainedBy( tunnel );
}
}

m_tunnelCount--;
#else

const std::list<ObjectID>::iterator it = std::find(m_tunnelIDs.begin(), m_tunnelIDs.end(), deadTunnel->getID());
if (it != m_tunnelIDs.end())
{
m_tunnelIDs.erase(it);
m_needsFullHealTimeUpdate = true;
}
else
{
DEBUG_CRASH(("TunnelTracker::onTunnelDestroyed - Attempting to remove object '%s' that has never been tracked as a tunnel",
deadTunnel->getName().str()));
}

if( m_tunnelCount == 0 )
if( m_tunnelIDs.empty() )
{
// Kill everyone in our contain list. Cave in!
iterateContained( destroyObject, nullptr, FALSE );
Expand All @@ -254,7 +301,10 @@ void TunnelTracker::onTunnelDestroyed( const Object *deadTunnel )
}
else
{
m_needsFullHealTimeUpdate = true;

Object *validTunnel = TheGameLogic->findObjectByID( m_tunnelIDs.front() );

// Otherwise, make sure nobody inside remembers the dead tunnel as the one they entered
// (scripts need to use so there must be something valid here)
for(ContainedItemsList::iterator it = m_containList.begin(); it != m_containList.end(); )
Expand All @@ -265,6 +315,8 @@ void TunnelTracker::onTunnelDestroyed( const Object *deadTunnel )
obj->onContainedBy( validTunnel );
}
}

#endif // RETAIL_COMPATIBLE_CRC
}

// ------------------------------------------------------------------------
Expand Down Expand Up @@ -379,13 +431,19 @@ void TunnelTracker::crc( Xfer *xfer )
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
* 1: Initial version
* 2: TheSuperHackers @tweak Removed m_tunnelCount (should always be equal to m_tunnelIDs.size())
*/
// ------------------------------------------------------------------------------------------------
void TunnelTracker::xfer( Xfer *xfer )
{

// version
#if RETAIL_COMPATIBLE_XFER_SAVE
XferVersion currentVersion = 1;
#else
XferVersion currentVersion = 2;
#endif
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );

Expand Down Expand Up @@ -424,8 +482,25 @@ void TunnelTracker::xfer( Xfer *xfer )
m_needsFullHealTimeUpdate = true;
}

// tunnel count
xfer->xferUnsignedInt( &m_tunnelCount );
#if RETAIL_COMPATIBLE_CRC
if (version <= 1)
{
xfer->xferUnsignedInt(&m_tunnelCount);
}
else
{
if (xfer->getXferMode() == XFER_LOAD)
{
m_tunnelCount = m_tunnelIDs.size();
}
}
#else
if (version <= 1)
{
UnsignedInt tunnelCount = m_tunnelIDs.size();
xfer->xferUnsignedInt(&tunnelCount);
}
#endif

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ void TunnelContain::onBuildComplete()

// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
#if !(RTS_GENERALS && RETAIL_COMPATIBLE_CRC)
void TunnelContain::onCapture( Player *oldOwner, Player *newOwner )
{
if( m_isCurrentlyRegistered )
Expand All @@ -523,6 +524,7 @@ void TunnelContain::onCapture( Player *oldOwner, Player *newOwner )
// extend base class
OpenContain::onCapture( oldOwner, newOwner );
}
#endif

//-------------------------------------------------------------------------------------------------
void TunnelContain::orderAllPassengersToExit( CommandSourceType commandSource, Bool instantly )
Expand Down
Loading