/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: TunnelTracker.cpp /////////////////////////////////////////////////////////// // The part of a Player's brain that holds the communal Passenger list of all tunnels. // Author: Graham Smallwood, March, 2002 #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/KindOf.h" #include "Common/TunnelTracker.h" #include "Common/Xfer.h" #include "GameClient/ControlBar.h" #include "GameClient/Drawable.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Module/TunnelContain.h" // ------------------------------------------------------------------------ TunnelTracker::TunnelTracker() { m_tunnelCount = 0; m_containListSize = 0; m_curNemesisID = INVALID_ID; m_nemesisTimestamp = 0; } // ------------------------------------------------------------------------ TunnelTracker::~TunnelTracker() { m_tunnelIDs.clear(); } // ------------------------------------------------------------------------ void TunnelTracker::iterateContained( ContainIterateFunc func, void *userData, Bool reverse ) { if (reverse) { // note that this has to be smart enough to handle items in the list being deleted // via the callback function. for(ContainedItemsList::reverse_iterator it = m_containList.rbegin(); it != m_containList.rend(); ) { // save the obj... Object* obj = *it; // incr the iterator BEFORE calling the func (if the func removes the obj, // the iterator becomes invalid) ++it; // call it (*func)( obj, userData ); } } else { // note that this has to be smart enough to handle items in the list being deleted // via the callback function. for(ContainedItemsList::iterator it = m_containList.begin(); it != m_containList.end(); ) { // save the obj... Object* obj = *it; // incr the iterator BEFORE calling the func (if the func removes the obj, // the iterator becomes invalid) ++it; // call it (*func)( obj, userData ); } } } // ------------------------------------------------------------------------ Int TunnelTracker::getContainMax() const { return TheGlobalData->m_maxTunnelCapacity; } // ------------------------------------------------------------------------ void TunnelTracker::updateNemesis(const Object *target) { if (getCurNemesis()==NULL) { if (target) { if (target->isKindOf(KINDOF_VEHICLE) || target->isKindOf(KINDOF_STRUCTURE) || target->isKindOf(KINDOF_INFANTRY) || target->isKindOf(KINDOF_AIRCRAFT)) { m_curNemesisID = target->getID(); m_nemesisTimestamp = TheGameLogic->getFrame(); } } } else if (getCurNemesis()==target) { m_nemesisTimestamp = TheGameLogic->getFrame(); } } // ------------------------------------------------------------------------ Object *TunnelTracker::getCurNemesis(void) { if (m_curNemesisID == INVALID_ID) { return NULL; } if (m_nemesisTimestamp + 4*LOGICFRAMES_PER_SECOND < TheGameLogic->getFrame()) { m_curNemesisID = INVALID_ID; return NULL; } Object *target = TheGameLogic->findObjectByID(m_curNemesisID); if (target) { //If the enemy unit is stealthed and not detected, then we can't attack it! if( target->testStatus( OBJECT_STATUS_STEALTHED ) && !target->testStatus( OBJECT_STATUS_DETECTED ) && !target->testStatus( OBJECT_STATUS_DISGUISED ) ) { target = NULL; } } if (target && target->isEffectivelyDead()) { target = NULL; } if (target == NULL) { m_curNemesisID = INVALID_ID; } return target; } // ------------------------------------------------------------------------ Bool TunnelTracker::isValidContainerFor(const Object* obj, Bool checkCapacity) const { //October 11, 2002 -- Kris : Dustin wants ALL units to be able to use tunnels! // srj sez: um, except aircraft. if (obj && !obj->isKindOf(KINDOF_AIRCRAFT)) { if (checkCapacity) { Int containMax = getContainMax(); Int containCount = getContainCount(); return ( containCount < containMax ); } else { return true; } } return false; } // ------------------------------------------------------------------------ void TunnelTracker::addToContainList( Object *obj ) { m_containList.push_back(obj); ++m_containListSize; } // ------------------------------------------------------------------------ void TunnelTracker::removeFromContain( Object *obj, Bool exposeStealthUnits ) { ContainedItemsList::iterator it = std::find(m_containList.begin(), m_containList.end(), obj); if (it != m_containList.end()) { // note that this invalidates the iterator! m_containList.erase(it); --m_containListSize; } } // ------------------------------------------------------------------------ Bool TunnelTracker::isInContainer( Object *obj ) { return (std::find(m_containList.begin(), m_containList.end(), obj) != m_containList.end()) ; } // ------------------------------------------------------------------------ void TunnelTracker::onTunnelCreated( const Object *newTunnel ) { m_tunnelCount++; m_tunnelIDs.push_back( newTunnel->getID() ); } // ------------------------------------------------------------------------ void TunnelTracker::onTunnelDestroyed( const Object *deadTunnel ) { m_tunnelCount--; m_tunnelIDs.remove( deadTunnel->getID() ); if( m_tunnelCount == 0 ) { // Kill everyone in our contain list. Cave in! iterateContained( destroyObject, NULL, FALSE ); m_containList.clear(); m_containListSize = 0; } else { 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(); ) { Object* obj = *it; ++it; if( obj->getContainedBy() == deadTunnel ) obj->onContainedBy( validTunnel ); } } } // ------------------------------------------------------------------------ void TunnelTracker::destroyObject( Object *obj, void * ) { // Now that tunnels consider ContainedBy to be "the tunnel you entered", I need to say goodbye // llike other contain types so they don't look us up on their deletion and crash obj->onRemovedFrom( obj->getContainedBy() ); TheGameLogic->destroyObject( obj ); } // ------------------------------------------------------------------------ // heal all the objects within the tunnel system using the iterateContained function void TunnelTracker::healObjects(Real frames) { iterateContained(healObject, &frames, FALSE); } // ------------------------------------------------------------------------ // heal one object within the tunnel network system void TunnelTracker::healObject( Object *obj, void *frames) { //get the number of frames to heal Real *framesForFullHeal = (Real*)frames; // setup the healing damageInfo structure with all but the amount DamageInfo healInfo; healInfo.in.m_damageType = DAMAGE_HEALING; healInfo.in.m_deathType = DEATH_NONE; //healInfo.in.m_sourceID = getObject()->getID(); // get body module of the thing to heal BodyModuleInterface *body = obj->getBodyModule(); // if we've been in here long enough ... set our health to max if( TheGameLogic->getFrame() - obj->getContainedByFrame() >= *framesForFullHeal ) { // set the amount to max just to be sure we're at the top healInfo.in.m_amount = body->getMaxHealth(); // set max health body->attemptHealing( &healInfo ); } // end if else { // // given the *whole* time it would take to heal this object, lets pretend that the // object is at zero health ... and give it a sliver of health as if it were at 0 health // and would be fully healed at 'framesForFullHeal' // healInfo.in.m_amount = body->getMaxHealth() / *framesForFullHeal; // do the healing body->attemptHealing( &healInfo ); } // end else } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void TunnelTracker::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void TunnelTracker::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // tunnel object id list xfer->xferSTLObjectIDList( &m_tunnelIDs ); // contain list count xfer->xferInt( &m_containListSize ); // contain list data ObjectID objectID; if( xfer->getXferMode() == XFER_SAVE ) { ContainedItemsList::const_iterator it; for( it = m_containList.begin(); it != m_containList.end(); ++it ) { objectID = (*it)->getID(); xfer->xferObjectID( &objectID ); } // end for, it } // end if, save else { for( UnsignedShort i = 0; i < m_containListSize; ++i ) { xfer->xferObjectID( &objectID ); m_xferContainList.push_back( objectID ); } // end for, i } // end else, load // tunnel count xfer->xferUnsignedInt( &m_tunnelCount ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void TunnelTracker::loadPostProcess( void ) { // sanity, the contain list should be empty until we post process the id list if( m_containList.size() != 0 ) { DEBUG_CRASH(( "TunnelTracker::loadPostProcess - m_containList should be empty but is not\n" )); throw SC_INVALID_DATA; } // end if // translate each object ids on the xferContainList into real object pointers in the contain list Object *obj; std::list< ObjectID >::const_iterator it; for( it = m_xferContainList.begin(); it != m_xferContainList.end(); ++it ) { obj = TheGameLogic->findObjectByID( *it ); if( obj == NULL ) { DEBUG_CRASH(( "TunnelTracker::loadPostProcess - Unable to find object ID '%d'\n", *it )); throw SC_INVALID_DATA; } // end if // push on the back of the contain list m_containList.push_back( obj ); // Crap. This is in OpenContain as a fix, but not here. { // remove object from its group (if any) obj->leaveGroup(); // remove rider from partition manager ThePartitionManager->unRegisterObject( obj ); // hide the drawable associated with rider if( obj->getDrawable() ) obj->getDrawable()->setDrawableHidden( true ); // remove object from pathfind map if( TheAI ) TheAI->pathfinder()->removeObjectFromPathfindMap( obj ); } } // end for, it // we're done with the xfer contain list now m_xferContainList.clear(); } // end loadPostProcess