quake4-sdk/source/game/spawner.cpp

601 lines
15 KiB
C++

/*
================
Spawner.cpp
================
*/
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "Game_local.h"
#include "spawner.h"
#include "vehicle/Vehicle.h"
#include "ai/AI.h"
#include "ai/AI_Manager.h"
#include "ai/AI_Util.h"
const idEventDef EV_Spawner_RemoveNullActiveEntities( "removeNullActiveEntities" );
const idEventDef EV_Spawner_NumActiveEntities( "numActiveEntities", "", 'd' );
const idEventDef EV_Spawner_GetActiveEntity( "getActiveEntity", "d", 'e' );
CLASS_DECLARATION( idEntity, rvSpawner )
EVENT( EV_Activate, rvSpawner::Event_Activate )
EVENT( EV_Spawner_RemoveNullActiveEntities, rvSpawner::Event_RemoveNullActiveEntities )
EVENT( EV_Spawner_NumActiveEntities, rvSpawner::Event_NumActiveEntities )
EVENT( EV_Spawner_GetActiveEntity, rvSpawner::Event_GetActiveEntity )
END_CLASS
/*
==============
rvSpawner::Spawn
==============
*/
void rvSpawner::Spawn( void ){
GetPhysics()->SetContents( 0 );
// TEMP: read max_team_test until we can get it out of all the current maps
if ( !spawnArgs.GetInt ( "max_active", "4", maxActive ) ) {
if ( spawnArgs.GetInt ( "max_team_test", "4", maxActive ) ) {
gameLocal.Warning ( "spawner '%s' using outdated 'max_team_test', please change to 'max_active'", GetName() );
}
}
maxToSpawn = spawnArgs.GetInt( "count", "-1" );
skipVisible = spawnArgs.GetBool ( "skipvisible", "1" );
spawnWaves = spawnArgs.GetInt( "waves", "1" );
spawnDelay = SEC2MS( spawnArgs.GetFloat( "delay", "2" ) );
numSpawned = 0;
nextSpawnTime = 0;
// Spawn waves has to be less than max active
if ( spawnWaves > maxActive ) {
spawnWaves = maxActive;
}
FindSpawnTypes ( );
}
/*
==============
rvSpawner::Save
==============
*/
void rvSpawner::Save ( idSaveGame *savefile ) const{
savefile->WriteInt( numSpawned );
savefile->WriteInt( maxToSpawn );
savefile->WriteFloat( nextSpawnTime );
savefile->WriteInt( maxActive );
int i;
savefile->WriteInt( currentActive.Num() );
for( i = 0; i < currentActive.Num(); i++ ) {
currentActive[i].Save ( savefile );
}
savefile->WriteInt( spawnWaves );
savefile->WriteInt( spawnDelay );
savefile->WriteBool( skipVisible );
savefile->WriteInt( spawnPoints.Num() );
for ( i = 0; i < spawnPoints.Num(); i++ ) {
spawnPoints[ i ].Save ( savefile );
}
savefile->WriteInt ( callbacks.Num() );
for ( i = 0; i < callbacks.Num(); i ++ ) {
callbacks[i].ent.Save ( savefile );
savefile->WriteString ( callbacks[i].event );
}
}
/*
==============
rvSpawner::Restore
==============
*/
void rvSpawner::Restore ( idRestoreGame *savefile ){
int num;
int i;
savefile->ReadInt( numSpawned );
savefile->ReadInt( maxToSpawn );
savefile->ReadFloat( nextSpawnTime );
savefile->ReadInt( maxActive );
savefile->ReadInt( num );
currentActive.Clear ( );
currentActive.SetNum( num );
for( i = 0; i < num; i++ ) {
currentActive[i].Restore ( savefile );
}
savefile->ReadInt( spawnWaves );
savefile->ReadInt( spawnDelay );
savefile->ReadBool( skipVisible );
savefile->ReadInt( num );
spawnPoints.SetNum( num);
for( i = 0; i < num; i ++ ) {
spawnPoints[i].Restore ( savefile );
}
savefile->ReadInt ( num );
callbacks.SetNum ( num );
for ( i = 0; i < num; i ++ ) {
callbacks[i].ent.Restore ( savefile );
savefile->ReadString ( callbacks[i].event );
}
FindSpawnTypes ();
}
/*
==============
rvSpawner::FindSpawnTypes
Generate the list of classnames to spawn from the spawnArgs. Anything matching the
prefix "def_spawn" will be included in the list.
==============
*/
void rvSpawner::FindSpawnTypes ( void ){
const idKeyValue *kv;
for ( kv = spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = spawnArgs.MatchPrefix( "def_spawn", kv ) ) {
spawnTypes.Append ( kv->GetValue ( ) );
// precache decls
declManager->FindType( DECL_AF, kv->GetValue(), true, false );
}
}
/*
==============
rvSpawner::FindTargets
==============
*/
void rvSpawner::FindTargets ( void ) {
int i;
idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) );
trace_t tr;
idEntity::FindTargets ( );
// Copy the relevant targets to the spawn point list (right now only target_null entities)
for ( i = targets.Num() - 1; i >= 0; i -- ) {
idEntity* ent;
ent = targets[i];
if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) {
continue;
}
idEntityPtr<idEntity> &entityPtr = spawnPoints.Alloc();
entityPtr = ent;
if( !spawnArgs.GetBool("ignoreSpawnPointValidation") ) {
gameLocal.TraceBounds( this, tr, ent->GetPhysics()->GetOrigin(), ent->GetPhysics()->GetOrigin(), bounds, MASK_MONSTERSOLID, NULL );
if ( gameLocal.entities[tr.c.entityNum] && !gameLocal.entities[tr.c.entityNum]->IsType ( idActor::GetClassType ( ) ) ) {
//drop a console warning here
gameLocal.Warning ( "Spawner '%s' can't spawn at point '%s', the monster won't fit.", GetName(), ent->GetName() );
}
}
}
}
/*
==============
rvSpawner::ValidateSpawnPoint
==============
*/
bool rvSpawner::ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ){
trace_t tr;
if( spawnArgs.GetBool("ignoreSpawnPointValidation") ) {
return true;
}
gameLocal.TraceBounds( this, tr, origin, origin, bounds, MASK_MONSTERSOLID, NULL );
return tr.fraction >= 1.0f;
}
/*
==============
rvSpawner::AddSpawnPoint
==============
*/
void rvSpawner::AddSpawnPoint ( idEntity* point ) {
idEntityPtr<idEntity> &entityPtr = spawnPoints.Alloc();
entityPtr = point;
// If there were no spawnPoints then start with the delay
if ( spawnPoints.Num () == 1 ) {
nextSpawnTime = gameLocal.time + spawnDelay;
}
}
/*
==============
rvSpawner::RemoveSpawnPoint
==============
*/
void rvSpawner::RemoveSpawnPoint ( idEntity* point ) {
int i;
for ( i = spawnPoints.Num()-1; i >= 0; i -- ) {
if ( spawnPoints[i] == point ) {
spawnPoints.RemoveIndex ( i );
break;
}
}
}
/*
==============
rvSpawner::GetSpawnPoint
==============
*/
void rvSpawner::AddCallback ( idEntity* owner, const idEventDef* ev ) {
spawnerCallback_t& callback = callbacks.Alloc ( );
callback.event = ev->GetName ( );
callback.ent = owner;
}
/*
==============
rvSpawner::GetSpawnPoint
==============
*/
idEntity *rvSpawner::GetSpawnPoint ( void ) {
idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) );
idList< idEntityPtr<idEntity> > spawns;
int spawnIndex;
idEntity* spawnEnt;
// Run through all spawnPoints and choose a random one. Each time a spawn point is excluded
// it will be removed from the list until there are no more items in the list.
for ( spawns = spawnPoints ; spawns.Num(); spawns.RemoveIndex ( spawnIndex ) ) {
spawnIndex = gameLocal.random.RandomInt ( spawns.Num() );
spawnEnt = spawns[spawnIndex];
if ( !spawnEnt || !spawnEnt->GetPhysics() ) {
continue;
}
// Check to see if something is in the way at this spawn point
if ( !ValidateSpawnPoint ( spawnEnt->GetPhysics()->GetOrigin(), bounds ) ) {
continue;
}
// Skip the spawn point because its currently visible?
if ( skipVisible && gameLocal.GetLocalPlayer()->CanSee ( spawnEnt, true ) ) {
continue;
}
// Found one!
return spawnEnt;
}
return NULL;
}
/*
==============
rvSpawner::GetSpawnType
==============
*/
const char* rvSpawner::GetSpawnType ( idEntity* spawnPoint ) {
const idKeyValue* kv;
if ( spawnPoint ) {
// If the spawn point has any "def_spawn" keys then they override the normal spawn keys
kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", NULL );
if ( kv ) {
const char* types [ MAX_SPAWN_TYPES ];
int typeCount;
for ( typeCount = 0;
typeCount < MAX_SPAWN_TYPES && kv;
kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", kv ) ) {
types [ typeCount++ ] = kv->GetValue ( ).c_str();
}
return types[ gameLocal.random.RandomInt( typeCount ) ];
}
}
// No spawn types?
if ( !spawnTypes.Num ( ) ) {
return "";
}
// Return from the spawners list of types
return spawnTypes[ gameLocal.random.RandomInt( spawnTypes.Num() ) ];
}
/*
==============
rvSpawner::CopyPrefixedSpawnArgs
==============
*/
void rvSpawner::CopyPrefixedSpawnArgs( idEntity *src, const char *prefix, idDict &args ){
const idKeyValue *kv = src->spawnArgs.MatchPrefix( prefix, NULL );
while( kv ) {
args.Set( kv->GetKey().c_str() + idStr::Length( prefix ), kv->GetValue() );
kv = src->spawnArgs.MatchPrefix( prefix, kv );
}
}
/*
==============
rvSpawner::SpawnEnt
==============
*/
bool rvSpawner::SpawnEnt( void ){
idDict args;
idEntity* spawnPoint;
idEntity* spawnedEnt;
const char* temp;
// Find a spawn point to spawn the entity
spawnPoint = GetSpawnPoint ( );
if( !spawnPoint ){
return false;
}
// No valid spawn types for this point
temp = GetSpawnType ( spawnPoint );
if ( !temp || !*temp ) {
gameLocal.Warning ( "Spawner '%s' could not find any valid spawn types for spawn point '%s'", GetName(), spawnPoint->GetName() );
return false;
}
// Build the spawn parameters for the entity about to be spawned
args.Set ( "origin", spawnPoint->GetPhysics()->GetOrigin().ToString() );
args.SetFloat ( "angle", spawnPoint->GetPhysics()->GetAxis().ToAngles()[YAW] );
args.Set ( "classname", temp );
args.SetBool ( "forceEnemy", spawnArgs.GetBool ( "auto_target", "1" ) );
args.SetBool ( "faceEnemy", spawnArgs.GetBool ( "faceEnemy", "0" ) );
// Copy all keywords prefixed with "spawn_" to the entity being spawned.
CopyPrefixedSpawnArgs( this, "spawn_", args );
if( spawnPoint != this ) {
CopyPrefixedSpawnArgs( spawnPoint, "spawn_", args );
}
// Spawn the entity
if ( !gameLocal.SpawnEntityDef ( args, &spawnedEnt ) ) {
return false;
}
// Activate the spawned entity
spawnedEnt->ProcessEvent( &EV_Activate, this );
// Play a spawning effect if it has one - do we possibly want some script hooks in here?
gameLocal.PlayEffect ( spawnArgs, "fx_spawning", spawnPoint->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3() );
// script function for spawning guys
if( spawnArgs.GetString( "call", "", &temp ) && *temp ) {
gameLocal.CallFrameCommand ( this, temp );
}
// script function for the guy being spawned
if ( spawnArgs.GetString( "call_spawned", "", &temp ) && *temp ) {
gameLocal.CallFrameCommand ( spawnedEnt, temp );
}
// Call all of our callbacks
int c;
for ( c = callbacks.Num() - 1; c >= 0; c-- ) {
if ( callbacks[c].ent ) {
callbacks[c].ent->ProcessEvent ( idEventDef::FindEvent ( callbacks[c].event ), this, spawnedEnt );
}
}
// Activate the spawn point entity when an enemy is spawned there and all of its targets
if( spawnPoint != this ){
spawnPoint->ProcessEvent( &EV_Activate, spawnPoint );
spawnPoint->ActivateTargets( spawnedEnt );
// One time use on this target?
if ( spawnPoint->spawnArgs.GetBool ( "remove" ) ) {
spawnPoint->PostEventMS ( &EV_Remove, 0 );
}
}
// Increment the total number spawned
numSpawned++;
return true;
}
/*
==============
rvSpawner::Think
==============
*/
void rvSpawner::Think( void ){
if ( thinkFlags & TH_THINK ) {
if( ActiveListChanged() ) {// If an entity has been removed and we have not been informed via Detach
nextSpawnTime = gameLocal.GetTime() + spawnDelay;
}
CheckSpawn ( );
}
}
/*
==============
rvSpawner::CheckSpawn
==============
*/
void rvSpawner::CheckSpawn ( void ) {
int count;
// Any spawn points?
if ( !spawnPoints.Num ( ) ) {
return;
}
// Is it time to spawn yet?
if ( nextSpawnTime == 0 || gameLocal.time < nextSpawnTime ) {
return;
}
// Any left to spawn?
if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ){
return;
}
// Spawn in waves?
for ( count = 0; count < spawnWaves; count ++ ) {
// Too many active?
if( currentActive.Num() >= maxActive ) {
return;
}
// Spawn a new entity
SpawnEnt ( );
// Are we at the limit now?
if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ) {
CallScriptEvents( "script_used_up", this );
PostEventMS ( &EV_Remove, 0 );
break;
}
}
// Dont spawn again until after the delay
nextSpawnTime = gameLocal.time + spawnDelay;
}
/*
==============
rvSpawner::CallScriptEvents
==============
*/
void rvSpawner::CallScriptEvents( const char* prefixKey, idEntity* parm ) {
if( !prefixKey || !prefixKey[0] ) {
return;
}
rvScriptFuncUtility func;
for( const idKeyValue* kv = spawnArgs.MatchPrefix(prefixKey); kv; kv = spawnArgs.MatchPrefix(prefixKey, kv) ) {
if( !kv->GetValue().Length() ) {
continue;
}
if( func.Init(kv->GetValue()) <= SFU_ERROR ) {
continue;
}
func.InsertEntity( parm, 0 );
func.CallFunc( &spawnArgs );
}
}
/*
==============
rvSpawner::ActiveListChanged
==============
*/
bool rvSpawner::ActiveListChanged() {
int previousCount = currentActive.Num();
currentActive.RemoveNull();
return previousCount > currentActive.Num();
}
/*
==============
rvSpawner::Attach
Attach the given AI to the spawner. This will increase the active count of the spawner and
set the spawner pointer in the ai.
==============
*/
void rvSpawner::Attach ( idEntity* ent ) {
currentActive.AddUnique( ent );
}
/*
==============
rvSpawner::Detach
Detaches the given AI from the spawner which will free up an active spot for spawning.
Attach the given AI to the spawner. This will increase the active count of the spawner and
set the spawner pointer in the ai.
==============
*/
void rvSpawner::Detach ( idEntity* ent ){
currentActive.Remove( ent );
nextSpawnTime = gameLocal.GetTime() + spawnDelay;
}
/*
==============
rvSpawner::Event_Activate
==============
*/
void rvSpawner::Event_Activate ( idEntity *activator ) {
// "trigger_only" spawners will attempt to spawn when triggered
if ( spawnArgs.GetBool ( "trigger_only" ) ) {
// Update next spawn time to follo CheckSpawn into thinking its time to spawn again
nextSpawnTime = gameLocal.time;
CheckSpawn ( );
return;
}
// If nextSpawnTime is zero then the spawner is currently deactivated
if ( nextSpawnTime == 0 ) {
// Start thinking
BecomeActive( TH_THINK );
// Allow immediate spawn
nextSpawnTime = gameLocal.time;
// Spawn any ai targets and add them to the current count
ActivateTargets ( this );
} else {
nextSpawnTime = 0;
BecomeInactive( TH_THINK );
// Remove the spawner if need be
if ( spawnArgs.GetBool ( "remove", "1" ) ) {
PostEventMS ( &EV_Remove, 0 );
}
}
}
/*
==============
rvSpawner::Event_RemoveNullActiveEntities
==============
*/
void rvSpawner::Event_RemoveNullActiveEntities( void ) {
for( int ix = currentActive.Num() - 1; ix >= 0; --ix ) {
if( !currentActive[ix].IsValid() ) {
currentActive.RemoveIndex( ix );
}
}
}
/*
==============
rvSpawner::Event_NumActiveEntities
==============
*/
void rvSpawner::Event_NumActiveEntities( void ) {
idThread::ReturnInt( currentActive.Num() );
}
/*
==============
rvSpawner::Event_GetActiveEntity
==============
*/
void rvSpawner::Event_GetActiveEntity( int index ) {
idThread::ReturnEntity( (index < 0 || index >= currentActive.Num()) ? NULL : currentActive[index] );
}