/* ** 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: 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 #include "GameLogic/Module/StealthUpdate.h" #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 ); } } } } // ------------------------------------------------------------------------------------------------ void SpawnBehavior::orderSlavesDisabledUntil( DisabledType type, UnsignedInt frame ) { 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( CMD_FROM_AI ); } obj->setDisabledUntil( type, frame ); } } } // ------------------------------------------------------------------------------------------------ void SpawnBehavior::orderSlavesToClearDisabled( DisabledType type ) { for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it ) { Object *obj = TheGameLogic->findObjectByID( *it ); if( obj ) { obj->clearDisabled( type ); } } } // ------------------------------------------------------------------------------------------------ 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; } // ------------------------------------------------------------------------------------------------ void SpawnBehavior::giveSlavesStealthUpgrade( Bool grantStealth ) { for( objectIDListIterator it = m_spawnIDs.begin(); it != m_spawnIDs.end(); ++it ) { Object *obj = TheGameLogic->findObjectByID( *it ); if( obj ) { obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_CAN_STEALTH ), grantStealth ); } } } // ------------------------------------------------------------------------------------------------ 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::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( getObject()->getStatusBits().test( OBJECT_STATUS_RECONSTRUCTING ) && modData->m_isOneShotData ) { // 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( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); } //------------------------------------------------------------------------------------------------- Bool SpawnBehavior::areAllSlavesStealthed() const { Object *currentSpawn; for( std::list::const_iterator iter = m_spawnIDs.begin(); iter != m_spawnIDs.end(); iter++) { currentSpawn = TheGameLogic->findObjectByID( (*iter) ); if( currentSpawn ) { const StealthUpdate *stealthUpdate = currentSpawn->getStealth(); if( !stealthUpdate || !stealthUpdate->allowedToStealth( currentSpawn ) ) { return FALSE; } } } return TRUE; //0 or more spawns are ALL stealthed... I suppose if you have NO spawns, then they are considered stealthed ;) } //------------------------------------------------------------------------------------------------- void SpawnBehavior::revealSlaves() { Object *currentSpawn; for( objectIDListIterator iter = m_spawnIDs.begin(); iter != m_spawnIDs.end(); iter++) { currentSpawn = TheGameLogic->findObjectByID( (*iter) ); if( currentSpawn ) { StealthUpdate *stealthUpdate = currentSpawn->getStealth(); if( stealthUpdate ) { stealthUpdate->markAsDetected(); } } } } // ------------------------------------------------------------------------------------------------ /** 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