| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078 |
- /*
- ** Command & Conquer Generals(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 <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // FILE: SpawnBehavior.cpp ////////////////////////////////////////////////////////////////////////
- // Author: Graham Smallwood, January 2002
- // Desc: Update will create and monitor a group of spawned units and replace as needed
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "Common/GameState.h"
- #include "Common/ThingFactory.h"
- #include "Common/ThingTemplate.h"
- #include "Common/Player.h"
- #include "Common/Xfer.h"
- #include "GameLogic/GameLogic.h"
- #include "GameLogic/Object.h"
- #include "GameLogic/PartitionManager.h"
- #include "GameLogic/Module/AIUpdate.h"
- #include "GameLogic/Module/SpawnBehavior.h"
- #include "GameLogic/Module/BodyModule.h"
- #include "GameClient/Drawable.h" //selection logic
- #include "GameClient/InGameUI.h" // selection logic
- #include "GameLogic/ExperienceTracker.h" //veterancy logic
- #define NONE_SPAWNED_YET (0xffffffff)
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- #define SPAWN_DELAY_MIN_FRAMES (16) // about as rapidly as you'd expect people to successively exit through the same door
- //-------------------------------------------------------------------------------------------------
- SpawnBehavior::SpawnBehavior( Thing *thing, const ModuleData* moduleData )
- : UpdateModule( thing, moduleData )
- {
- const SpawnBehaviorModuleData* md = getSpawnBehaviorModuleData();
- // GEE, THIS IS NEW...
- // NOW, WE CAN HAVE A LIST OF TEMPLATE NAMES
- m_templateNameIterator = md->m_spawnTemplateNameData.begin();
- m_spawnTemplate = TheThingFactory->findTemplate( *m_templateNameIterator );
- //each time m_spawn template is used, it will increment m_templateNameIterator,
- //thus scanning through the ordered list of template names
- //looping back to the beginning
- m_framesToWait = 0;
- //Added By Sadullah Nader
- //Initialization(s) inserted
- m_firstBatchCount = 0;
- //
- if( md->m_isOneShotData )
- m_oneShotCountdown = md->m_spawnNumberData;
- else
- m_oneShotCountdown = -1;
- m_active = TRUE;
- m_replacementTimes.clear();
- // The initializing of the initial bursters is handled in the first update @todo invent an object::postConstructionProcess() some day
- m_initialBurstCountdown = md->m_initialBurst;
- m_initialBurstTimesInited = FALSE;
-
- m_aggregateHealth = md->m_aggregateHealth;
- m_spawnCount = NONE_SPAWNED_YET;
- m_active = TRUE;
- m_selfTaskingSpawnCount = 0;
- }
- //-------------------------------------------------------------------------------------------------
- SpawnBehavior::~SpawnBehavior( void )
- {
- m_replacementTimes.clear();
- }
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::onDelete()
- {
- const SpawnBehaviorModuleData *modData = getSpawnBehaviorModuleData();
- // destroy anything that we have spawned that is not already dead
- if( modData->m_spawnedRequireSpawner )
- {
- Object *obj;
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); /*empty*/ )
- {
- // get object
- obj = TheGameLogic->findObjectByID( *it );
- // increment iterator incase the id list is alterd because what we're about to do
- ++it;
- //
- // destroy this if it's alive, in the usual case this object "dies" and is
- // not "destroyed". on *death* we *KILL* our spawned things, but this is here
- // and will *DESTROY* our spawned things if we ourselves are destroyed and they
- // are still alive (such a case would be calling destroy object on us directly)
- //
- if( obj && obj->isEffectivelyDead() == FALSE )
- TheGameLogic->destroyObject( obj );
- } // end for, it
- } // end if
-
- }
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::onDie( const DamageInfo *damageInfo )
- {
- const SpawnBehaviorModuleData *modData = getSpawnBehaviorModuleData();
- ///@todo isDieApplicable should be called outside of the onDie call
- if( modData->m_dieMuxData.isDieApplicable( getObject(), damageInfo ) == FALSE )
- return;
- for( objectIDListIterator iter = m_spawnIDs.begin();
- iter != m_spawnIDs.end();
- iter++
- )
- {
- Object *currentSpawn = TheGameLogic->findObjectByID( (*iter) );
- if( currentSpawn )
- {
- // Go through all my spawns and see if they have a SlavedUpdate I can tell I was killed to
- for (BehaviorModule** update = currentSpawn->getBehaviorModules(); *update; ++update)
- {
- SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface();
- if (sdu != NULL)
- {
- sdu->onSlaverDie( damageInfo );
- break;
- }
- }
- // our spawner has died, we must invalidate the ID now in the spawned object
- currentSpawn->setProducer( NULL );
- }
- }
- // kill anything that we have spawned if our module data directs us to do so
- if( modData->m_spawnedRequireSpawner )
- {
- Object *obj;
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); /*emtpy*/ )
- {
- // get object
- obj = TheGameLogic->findObjectByID( *it );
- // increment iterator incase the id list is alterd because what we're about to do
- ++it;
- // kill it if not already dead
- if( obj && obj->isEffectivelyDead() == FALSE )
- obj->kill();
- } // end for, it
- } // end if
- }
- //-------------------------------------------------------------------------------------------------
- UpdateSleepTime SpawnBehavior::update( void )
- {
- /// @todo srj use SLEEPY_UPDATE here
- //EVERY FRAME
- if ( m_aggregateHealth )
- {
- computeAggregateStates();
- }
- const SpawnBehaviorModuleData* md = getSpawnBehaviorModuleData();
- if ( ! m_initialBurstTimesInited )
- {
- m_initialBurstTimesInited = TRUE;
- Bool runtimeProduced = getObject()->getProducerID()!=INVALID_ID; //this was produced by a production module, rather than a script or worldbuilder
- Int now = TheGameLogic->getFrame();
- Int burstInitCount = m_initialBurstCountdown;
- for( int listIndex = 0; listIndex < md->m_spawnNumberData; listIndex++ )
- {
- if ( md->m_initialBurst > 0 )
- {
- UnsignedInt birthFrame = now;
- if ( runtimeProduced && burstInitCount > 0)
- {
- --burstInitCount;
- birthFrame += (listIndex*SPAWN_DELAY_MIN_FRAMES);
- }
- m_replacementTimes.push_back( runtimeProduced );// Set all spawns to be created in rapid succession
- }
- else
- m_replacementTimes.push_back( listIndex );// Set all spawns to be created as soon as possible
- }
- }
- //SPARSELY SOLVED
- if (--m_framesToWait > 0)
- {
- return UPDATE_SLEEP_NONE;
- }
- m_framesToWait = SPAWN_UPDATE_RATE;
- //Go through the list and make a spawn for each number that is less than now's frame
- if( shouldTryToSpawn() )
- {
- // Due to STL's list vomiting when you erase in a for loop,
- // Start at the beginning, and if you erase one, use the return
- // value to manually step to the next. Otherwise, step by yourself.
- // begin == end means empty list, so no special check is needed.
- intListIterator iterator = m_replacementTimes.begin();
- while( iterator != m_replacementTimes.end() )
- {
- Int replacementTime = *iterator;
- UnsignedInt currentTime = TheGameLogic->getFrame();
- if( currentTime > replacementTime )
- {
- //If you create one, you pop the number off the list
- if( createSpawn() )
- iterator = m_replacementTimes.erase( iterator );
- else
- iterator++;
- }
- else
- iterator++;
- }
- if( md->m_isOneShotData && (m_oneShotCountdown <= 0) )
- stopSpawning(); //I only trigger one batch. ie on Creation.
- }
- return UPDATE_SLEEP_NONE;
- }
- // ------------------------------------------------------------------------------------------------
- Bool SpawnBehavior::maySpawnSelfTaskAI( Real maxSelfTaskersRatio )
- {
- if ( m_spawnCount == 0)
- return FALSE;
- if ( maxSelfTaskersRatio == 0)
- return FALSE;
-
- //if my last attack command was from player or script, I need to forbid my spawn from disobeying that command
- //otherwise (since my attack state was autoacquired my ny own ai), let them deviate by the ratio specified.
- Object* obj = getObject();
- if ( ! obj )
- return FALSE;
- AIUpdateInterface *ai = obj->getAI();
- if ( ! ai )
- return FALSE;
-
- CommandSourceType lastAttackCommandSource = ai->getLastCommandSource();
-
- if ( lastAttackCommandSource != CMD_FROM_AI )
- return FALSE;
-
- Real curSelfTaskersRatio = (Real)m_selfTaskingSpawnCount / (Real)m_spawnCount;
- return ( curSelfTaskersRatio < maxSelfTaskersRatio );
- }
- // ------------------------------------------------------------------------------------------------
- Object* SpawnBehavior::getClosestSlave( const Coord3D *pos )
- {
- Object *closest = NULL;
- Real closestDistance;
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( obj )
- {
- Real distance = ThePartitionManager->getDistanceSquared( obj, pos, FROM_CENTER_2D );
-
- if( !closest || closestDistance > distance )
- {
- closest = obj;
- closestDistance = distance;
- }
- }
- }
- return closest; //Could be null!
- }
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::orderSlavesToAttackTarget( Object *target, Int maxShotsToFire, CommandSourceType cmdSource )
- {
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( obj )
- {
- AIUpdateInterface *ai = obj->getAI();
- if( ai )
- {
- ai->aiForceAttackObject( target, maxShotsToFire, cmdSource );
- }
- }
- }
- }
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::orderSlavesToAttackPosition( const Coord3D *pos, Int maxShotsToFire, CommandSourceType cmdSource )
- {
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( obj )
- {
- AIUpdateInterface *ai = obj->getAI();
- if( ai )
- {
- ai->aiAttackPosition( pos, maxShotsToFire, cmdSource );
- }
- }
- }
- }
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::orderSlavesToGoIdle( CommandSourceType cmdSource )
- {
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( obj )
- {
- AIUpdateInterface *ai = obj->getAI();
- if( ai )
- {
- ai->aiIdle( cmdSource );
- }
- }
- }
- }
- // ------------------------------------------------------------------------------------------------
- CanAttackResult SpawnBehavior::getCanAnySlavesAttackSpecificTarget( AbleToAttackType attackType, const Object *target, CommandSourceType cmdSource )
- {
- Bool invalidShot = FALSE;
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( obj )
- {
- CanAttackResult result = obj->getAbleToAttackSpecificObject( attackType, target, cmdSource );
- switch( result )
- {
- case ATTACKRESULT_POSSIBLE:
- case ATTACKRESULT_POSSIBLE_AFTER_MOVING:
- return result;
- case ATTACKRESULT_NOT_POSSIBLE:
- break;
-
- case ATTACKRESULT_INVALID_SHOT:
- invalidShot = TRUE;
- break;
- default:
- DEBUG_CRASH( ("SpawnBehavior::getCanAnySlavesAttackSpecificTarget encountered unhandled CanAttackResult of %d. Treating as not possible...", result) );
- break;
- }
- }
- }
- //Prioritize the reasonings!
- if( invalidShot )
- {
- return ATTACKRESULT_INVALID_SHOT;
- }
- return ATTACKRESULT_NOT_POSSIBLE;
- }
- // ------------------------------------------------------------------------------------------------
- CanAttackResult SpawnBehavior::getCanAnySlavesUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType cmdSource )
- {
- Bool invalidShot = FALSE;
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( obj )
- {
- CanAttackResult result = obj->getAbleToUseWeaponAgainstTarget( attackType, victim, pos, cmdSource );
- switch( result )
- {
- case ATTACKRESULT_POSSIBLE:
- case ATTACKRESULT_POSSIBLE_AFTER_MOVING:
- return result;
- case ATTACKRESULT_NOT_POSSIBLE:
- break;
-
- case ATTACKRESULT_INVALID_SHOT:
- invalidShot = TRUE;
- break;
- default:
- DEBUG_CRASH( ("SpawnBehavior::getCanAnySlavesUseWeaponAgainstTarget encountered unhandled CanAttackResult of %d. Treating as not possible...", result) );
- break;
- }
- }
- }
- //Prioritize the reasonings!
- if( invalidShot )
- {
- return ATTACKRESULT_INVALID_SHOT;
- }
- return ATTACKRESULT_NOT_POSSIBLE;
- }
- // ------------------------------------------------------------------------------------------------
- Bool SpawnBehavior::canAnySlavesAttack()
- {
- for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it )
- {
- Object *obj = TheGameLogic->findObjectByID( *it );
- if( obj )
- {
- if( obj->isAbleToAttack() )
- {
- return true;
- }
- }
- }
- return false;
- }
- // ------------------------------------------------------------------------------------------------
- // ------------------------------------------------------------------------------------------------
- class OrphanData
- {
- public:
- OrphanData( void );
- const ThingTemplate *m_matchTemplate;
- Object *m_source;
- Object *m_closest;
- Real m_closestDistSq;
- };
- #define BIG_DISTANCE 99999999.9f
- // ------------------------------------------------------------------------------------------------
- // ------------------------------------------------------------------------------------------------
- OrphanData::OrphanData( void )
- {
- m_matchTemplate = NULL;
- m_source = NULL;
- m_closest = NULL;
- m_closestDistSq = BIG_DISTANCE;
- }
- // ------------------------------------------------------------------------------------------------
- static void findClosestOrphan( Object *obj, void *userData )
- {
- OrphanData *orphanData = (OrphanData *)userData;
- // if template doesn't match do nothing
- if( obj->getTemplate()->isEquivalentTo( orphanData->m_matchTemplate ) == FALSE )
- return;
- // this object must be orphaned
- if( obj->getProducerID() != INVALID_ID )
- return;
- // is this the closest one so far
- Real distSq = ThePartitionManager->getDistanceSquared( orphanData->m_source, obj, FROM_CENTER_2D );
- if( distSq < orphanData->m_closestDistSq )
- {
-
- orphanData->m_closest = obj;
- orphanData->m_closestDistSq = distSq;
- } // end if
-
- } // findClosestOrphan
- // ------------------------------------------------------------------------------------------------
- Object *SpawnBehavior::reclaimOrphanSpawn( void )
- {
- Player *player = getObject()->getControllingPlayer();
- const SpawnBehaviorModuleData *md = getSpawnBehaviorModuleData();
- //
- // iterate all the objects my controlling player has and look for any orphaned things
- // that we would normally spawn, if found we'll just make it our own
- //
- // EVEN MORE NEW AND DIFFERENT
- // This block scans the list for matchTemplates
- //
- OrphanData orphanData;
- AsciiString prevName = "";
- for (std::vector<AsciiString>::const_iterator tempName = md->m_spawnTemplateNameData.begin();
- tempName != md->m_spawnTemplateNameData.end();
- ++tempName)
- {
- if (prevName.compare(*tempName)) // the list may have redundancy, this will skip some of it
- continue;
- orphanData.m_matchTemplate = TheThingFactory->findTemplate( *tempName );;
- orphanData.m_source = getObject();
- orphanData.m_closest = NULL;
- orphanData.m_closestDistSq = BIG_DISTANCE;
- player->iterateObjects( findClosestOrphan, &orphanData );
- prevName = *tempName;
- }
- return orphanData.m_closest;
- }
- //-------------------------------------------------------------------------------------------------
- Bool SpawnBehavior::createSpawn()
- {
- Object *parent = getObject();
- const SpawnBehaviorModuleData *md = getSpawnBehaviorModuleData();
- ExitInterface* exitInterface = parent->getObjectExitInterface();
- if( exitInterface == NULL )
- {
- DEBUG_ASSERTCRASH( exitInterface != NULL, ("Something cannot have SpawnBehavior without an exit interface") );
- return FALSE;
- }
-
- ExitDoorType exitDoor = exitInterface->reserveDoorForExit(NULL, NULL);
- if (exitDoor == DOOR_NONE_AVAILABLE)
- return FALSE;
- Object *newSpawn = NULL;
-
- // try to reclaim orphaned objects if possible
- Bool reclaimedOrphan = FALSE;
- if( md->m_canReclaimOrphans && md->m_isOneShotData == FALSE )
- {
- newSpawn = reclaimOrphanSpawn();
- if( newSpawn )
- reclaimedOrphan = TRUE;
- }
- // This assures that an orphan has not just been reclaimed,
- if( newSpawn == NULL ) // and that we really want a new spawn, here.
- {
- m_spawnTemplate = TheThingFactory->findTemplate( *m_templateNameIterator );
- //newSpawn = TheThingFactory->newObject( m_spawnTemplate, parent->getControllingPlayer()->getDefaultTeam() );
- newSpawn = TheThingFactory->newObject( m_spawnTemplate, parent->getTeam() ); // just a little worried about this...
- // Count this unit towards our score.
- newSpawn->getControllingPlayer()->onUnitCreated(parent, newSpawn);
- // Gee, this is new...
- // Now, we can have a list of template names.
- // Each time m_spawn template is used, it will increment m_templateNameIterator,
- // thus scanning through the ordered list of template names, and
- // looping back to the beginning
- ++m_templateNameIterator;
- if ( m_templateNameIterator == md->m_spawnTemplateNameData.end())
- {
- m_templateNameIterator = md->m_spawnTemplateNameData.begin();
- }
- }
- newSpawn->setProducer(parent);
- // If they have a SlavedUpdate, then I have to tell them who their daddy is from now on.
- for (BehaviorModule** update = newSpawn->getBehaviorModules(); *update; ++update)
- {
- SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface();
- if (sdu != NULL)
- {
- sdu->onEnslave( parent );
- break;
- }
- }
- m_spawnIDs.push_back( newSpawn->getID() );
- if( reclaimedOrphan == FALSE )
- {
- if ( md->m_exitByBudding )
- {
- Bool barracksExitSuccess = FALSE;
- if ( m_initialBurstCountdown > 0 )
- {
- Object *barracks = TheGameLogic->findObjectByID( parent->getProducerID() );
- if ( barracks && barracks->isKindOf( KINDOF_STRUCTURE ) )
- {
- ExitInterface* barracksExitInterface = barracks->getObjectExitInterface();
- if ( barracksExitInterface )
- {
- ExitDoorType barracksDoor = barracksExitInterface->reserveDoorForExit(NULL, NULL);
- barracksExitInterface->exitObjectViaDoor( newSpawn, barracksDoor );
- newSpawn->setProducer(parent);//let parents producer exit him, but he thinks it was me
- --m_initialBurstCountdown;
- barracksExitSuccess = TRUE;
- }
-
- }
- }
-
- if ( ! barracksExitSuccess )
- {
- // find the closest spawn to the nexus...
- //there is probably a more elegant way to choose the budHost, but oh well
- Object *budHost = NULL;
- Object *curSpawn = NULL;
- Real tapeMeasure = 99999;
- Real closest = 999999.9f; // 1000 * 1000
- objectIDListIterator iter;
- for( iter = m_spawnIDs.begin(); iter != m_spawnIDs.end(); iter++)
- {
- curSpawn = TheGameLogic->findObjectByID( *iter );
- if ( curSpawn )
- {
- if (curSpawn == newSpawn )
- continue;
- tapeMeasure = ThePartitionManager->getDistanceSquared( curSpawn, parent, FROM_CENTER_2D );
- if ( tapeMeasure < closest )
- {
- closest = tapeMeasure;
- budHost = curSpawn;
- }
- }
- }
- exitInterface->exitObjectByBudding( newSpawn, budHost );// also handles the NULL pointer okay
- }
- }
- else
- {
- exitInterface->exitObjectViaDoor( newSpawn, exitDoor );
- }
- }
- else
- exitInterface->unreserveDoorForExit( exitDoor );
- if( md->m_isOneShotData )
- m_oneShotCountdown--;
- if ( m_spawnCount == NONE_SPAWNED_YET )
- {
- m_spawnCount = 1;
- }
- else
- {
- ++ m_spawnCount;
- }
- return TRUE;
- }
- //-------------------------------------------------------------------------------------------------
- void SpawnBehavior::onSpawnDeath( ObjectID deadSpawn, DamageInfo *damageInfo )
- {
- objectIDListIterator it = std::find(m_spawnIDs.begin(), m_spawnIDs.end(), deadSpawn);
-
- // If the iterator is at the end, we didn't find deadSpawn, so bail out.
- // Otherwise, bad crash stuff will happen.
- if (it == m_spawnIDs.end())
- return;
- //When one dies, you push (now + delay) as the time a new one should be made
- const SpawnBehaviorModuleData* md = getSpawnBehaviorModuleData();
-
- Int replacementTime = md->m_spawnReplaceDelayData + TheGameLogic->getFrame();
- m_replacementTimes.push_back( replacementTime );
- m_spawnIDs.erase( it );
- --m_spawnCount;
- if ( (m_spawnCount == 0) && m_aggregateHealth) // I'm dead without my spawn
- {
- Object *killer = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID);
- if (killer != NULL) {
- killer->scoreTheKill(getObject());
- }
- TheGameLogic->destroyObject(getObject());
- //getObject()->kill();
- return;
- }
- }
- //-------------------------------------------------------------------------------------------------
- void SpawnBehavior::stopSpawning()
- {
- m_active = FALSE;
- }
- //-------------------------------------------------------------------------------------------------
- void SpawnBehavior::startSpawning()
- {
- m_active = TRUE;
- }
- //-------------------------------------------------------------------------------------------------
- /** When I become damaged */
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::onDamage( DamageInfo *info )
- {
- for( objectIDListIterator iter = m_spawnIDs.begin();
- iter != m_spawnIDs.end();
- iter++
- )
- {
- Object *currentSpawn = TheGameLogic->findObjectByID( (*iter) );
- if( currentSpawn )
- {
- // Go through all my spawns and see if they have a SlavedUpdate I can tell I was hurt to
- for (BehaviorModule** update = currentSpawn->getBehaviorModules(); *update; ++update)
- {
- SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface();
- if (sdu != NULL)
- {
- sdu->onSlaverDamage( info );
- break;
- }
- }
- }
- }
- }
- // ------------------------------------------------------------------------------------------------
- // ------------------------------------------------------------------------------------------------
- Bool SpawnBehavior::shouldTryToSpawn()
- {
- const SpawnBehaviorModuleData *modData = getSpawnBehaviorModuleData();
- // Not if we are turned off
- if( !m_active )
- return FALSE;
- if( BitTest( getObject()-> getStatusBits(), OBJECT_STATUS_RECONSTRUCTING ) &&
- modData->m_isOneShotData == TRUE )
- {
- // If we are a Hole rebuild, not only should we not, but we should never ask again.
- stopSpawning();
- return FALSE;
- }
- // Not if we are under construction or being sold
- if( getObject()->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) || getObject()->testStatus(OBJECT_STATUS_SOLD) )
- return FALSE;
- // Not if we are civilian controlled
- if( getObject()->isNeutralControlled() )
- return FALSE;
- return TRUE;
- }
- //********************************************************************
- //* This function allows various object states to be either inherited
- //* from the spawner to the spawn, or vice versa
- //* Selection is distributed across all spawn and spawner at once
- //* Health for the spawner is calc'd by aggregating the sum of all
- //* spawn health, and dividing by optimal health of full population
- //* (that is, the max spawn, SpawnBehaviorModuleData::m_spawnNumberData)
- //* at full health.
- //* Veterancy is sucked out of any unit that has any and put into the
- //* Spawner, scaled by 1/SpawnBehaviorModuleData::m_spawnNumberData;
- //* The HealthBoxPosition (maybe to include moodicon, vet icon) is calc'd
- //* as an average position of all the spawn.
- //********************************************************************
- void SpawnBehavior::computeAggregateStates(void)
- {
- if ( ! m_aggregateHealth ) // sanity
- return;
- Object* obj = getObject();
- const SpawnBehaviorModuleData* md = getSpawnBehaviorModuleData();
- Int spawnCount = 0;
- Int spawnCountMax = md->m_spawnNumberData;
- Coord3D avgSpawnPos;
- avgSpawnPos.set(0,0,0);
- Real acrHealth = 0.0f;
- Real avgHealthMax = 0.0f;
- ExperienceTracker *expTracker = obj->getExperienceTracker();
- VeterancyLevel vetLevel = expTracker->getVeterancyLevel();
- Bool SomebodyIsSelected = FALSE;
- Bool SomebodyIsNotSelected = FALSE;
- Drawable *spawnDraw = NULL;
- Object *currentSpawn = NULL;
- WeaponBonusConditionFlags spawnWeaponBonus;
- m_selfTaskingSpawnCount = 0;
- for( objectIDListIterator iter = m_spawnIDs.begin(); iter != m_spawnIDs.end(); iter++)
- {
- currentSpawn = TheGameLogic->findObjectByID( (*iter) );
- if( currentSpawn )
- {
- //m_selfTaskingSpawnCount += ( currentSpawn->isSelf);
- for (BehaviorModule** update = currentSpawn->getBehaviorModules(); *update; ++update)
- {
- SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface();
- if (sdu != NULL)
- {
- m_selfTaskingSpawnCount += ( sdu->isSelfTasking());;
- break;
- }
- }
- // VETERANCY LEVEL *************************************
- VeterancyLevel spawnVetLevel = currentSpawn->getVeterancyLevel();
- if ( spawnVetLevel > vetLevel) // whoever has more, set the other guy
- {
- expTracker->setVeterancyLevel(spawnVetLevel);
- }
- else if ( spawnVetLevel < vetLevel) // whoever has more, set the other guy
- {
- currentSpawn->getExperienceTracker()->setVeterancyLevel(vetLevel);
- }
- spawnWeaponBonus = currentSpawn->getWeaponBonusCondition();
- avgSpawnPos.add(currentSpawn->getPosition());
- BodyModuleInterface *body = currentSpawn->getBodyModule();
- acrHealth += body->getHealth();
- avgHealthMax += body->getMaxHealth();
- spawnDraw = currentSpawn->getDrawable();
- if ( spawnDraw->isSelected() )
- {
- SomebodyIsSelected = TRUE;
- }
- else
- {
- SomebodyIsNotSelected = TRUE;
- }
- spawnCount++;
- }
- } // next iter
-
- // SELECTION STATE *****************************************
- // THIS LOGIC IS SIMPLE
- // IF ANY ONE OF MY SPAWN ARE SELECTED RIGHT NOW,
- // I JUST SET THEM ALL SELECTED
- // THIS WAY WE ARE ALWAYS ONE BIG HAPPY MOB
- // BUT I ONLY PROCEED IF THERE IS ONE AMONG ALL OF US WHO IS NOT SELECTED
- if ( SomebodyIsSelected && ( ! obj->getDrawable()->isSelected() || SomebodyIsNotSelected ) )
- {
- GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP );
- teamMsg->appendBooleanArgument( FALSE );// not creating new team so pass false
- if ( SomebodyIsNotSelected ) // lets select everybody
- {
- for( objectIDListIterator iter = m_spawnIDs.begin(); iter != m_spawnIDs.end(); iter++)
- {
- currentSpawn = TheGameLogic->findObjectByID( (*iter) );
- if( currentSpawn )
- {
- spawnDraw = currentSpawn->getDrawable();
- if ( ! spawnDraw->isSelected())
- {
- TheInGameUI->selectDrawable( spawnDraw );
- TheInGameUI->setDisplayedMaxWarning( FALSE );
- teamMsg->appendBooleanArgument( FALSE );// not creating new team so pass false
- teamMsg->appendObjectIDArgument( currentSpawn->getID() );
- }
- }
- } // next iter
- }
- // if somebody is selected then I sure need to be!
- if ( ! obj->getDrawable()->isSelected())
- {
- TheInGameUI->selectDrawable( obj->getDrawable() );
- TheInGameUI->setDisplayedMaxWarning( FALSE );
- teamMsg->appendBooleanArgument( FALSE );// not creating new team so pass false
- teamMsg->appendObjectIDArgument( obj->getID() );
- }
- }
- // HEALTH BOX POSITION *****************************
- // pick a centered, average spot to draw the health box
- avgSpawnPos.scale(1.0f / spawnCount);
- avgSpawnPos.sub(obj->getPosition());
- obj->setHealthBoxOffset(avgSpawnPos);
- // HEALTH STATE *************************************
- // make my health an aggregate of all my spawns' healths
- if ( spawnCount )
- {
- avgHealthMax /= spawnCount;
- Real perfectTotalHealth = avgHealthMax * spawnCountMax;
- Real actualHealth = acrHealth / perfectTotalHealth;
- obj->getBodyModule()->setInitialHealth(100.0f * actualHealth);
- }
- else
- {
- obj->getBodyModule()->setInitialHealth(0);// I been sick <
- }
- // HOUSEKEEPING *************************************
- //make sure no enemies are shooting at the nexus, since it doesn't 'exist'
- obj->setStatus(OBJECT_STATUS_MASKED);
- }
- // ------------------------------------------------------------------------------------------------
- /** CRC */
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::crc( Xfer *xfer )
- {
- // extend base class
- BehaviorModule::crc( xfer );
- } // end crc
- // ------------------------------------------------------------------------------------------------
- /** Xfer method
- * Version Info:
- * 1: Initial version
- * 2: Added m_initialBurstTimesInited to the save. jba.
- */
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::xfer( Xfer *xfer )
- {
- AsciiString name;
- // version
- XferVersion currentVersion = 2;
- XferVersion version = currentVersion;
- xfer->xferVersion( &version, currentVersion );
- // extend base class
- BehaviorModule::xfer( xfer );
-
- if (version >= 2) {
- xfer->xferBool(&m_initialBurstTimesInited);
- }
- // spawn template
- name = m_spawnTemplate ? m_spawnTemplate->getName() : AsciiString::TheEmptyString;
- xfer->xferAsciiString( &name );
- if( xfer->getXferMode() == XFER_LOAD )
- {
- m_spawnTemplate = NULL;
- if( name.isEmpty() == FALSE )
- {
-
- m_spawnTemplate = TheThingFactory->findTemplate( name );
- if( m_spawnTemplate == NULL )
- {
- DEBUG_CRASH(( "SpawnBehavior::xfer - Unable to find template '%s'\n", name.str() ));
- throw SC_INVALID_DATA;
- } // end if
- } // end if
- } // end if
- // one shot countdown
- xfer->xferInt( &m_oneShotCountdown );
- // frames to wait
- xfer->xferInt( &m_framesToWait );
- // first batch count
- xfer->xferInt( &m_firstBatchCount );
- // replacement times
- if( xfer->getXferMode() == XFER_LOAD )
- m_replacementTimes.clear();
- xfer->xferSTLIntList( &m_replacementTimes );
- // spawn ids
- xfer->xferSTLObjectIDList( &m_spawnIDs );
- // active
- xfer->xferBool( &m_active );
- // aggregate health
- xfer->xferBool( &m_aggregateHealth );
- // spawn count
- xfer->xferInt( &m_spawnCount );
- // self tasking spawn count
- xfer->xferUnsignedInt( &m_selfTaskingSpawnCount );
- } // end xfer
- // ------------------------------------------------------------------------------------------------
- /** Load post process */
- // ------------------------------------------------------------------------------------------------
- void SpawnBehavior::loadPostProcess( void )
- {
- // extend base class
- BehaviorModule::loadPostProcess();
- } // end loadPostProcess
|