/*
** 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 .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (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::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