aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/monstermaker.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/monstermaker.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/monstermaker.cpp')
-rw-r--r--mp/src/game/server/monstermaker.cpp1083
1 files changed, 1083 insertions, 0 deletions
diff --git a/mp/src/game/server/monstermaker.cpp b/mp/src/game/server/monstermaker.cpp
new file mode 100644
index 00000000..608f62f4
--- /dev/null
+++ b/mp/src/game/server/monstermaker.cpp
@@ -0,0 +1,1083 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: An entity that creates NPCs in the game. There are two types of NPC
+// makers -- one which creates NPCs using a template NPC, and one which
+// creates an NPC via a classname.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "datacache/imdlcache.h"
+#include "entityapi.h"
+#include "entityoutput.h"
+#include "ai_basenpc.h"
+#include "monstermaker.h"
+#include "TemplateEntities.h"
+#include "ndebugoverlay.h"
+#include "mapentities.h"
+#include "IEffects.h"
+#include "props.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static void DispatchActivate( CBaseEntity *pEntity )
+{
+ bool bAsyncAnims = mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, false );
+ pEntity->Activate();
+ mdlcache->SetAsyncLoad( MDLCACHE_ANIMBLOCK, bAsyncAnims );
+}
+
+ConVar ai_inhibit_spawners( "ai_inhibit_spawners", "0", FCVAR_CHEAT );
+
+
+LINK_ENTITY_TO_CLASS( info_npc_spawn_destination, CNPCSpawnDestination );
+
+BEGIN_DATADESC( CNPCSpawnDestination )
+ DEFINE_KEYFIELD( m_ReuseDelay, FIELD_FLOAT, "ReuseDelay" ),
+ DEFINE_KEYFIELD( m_RenameNPC,FIELD_STRING, "RenameNPC" ),
+ DEFINE_FIELD( m_TimeNextAvailable, FIELD_TIME ),
+
+ DEFINE_OUTPUT( m_OnSpawnNPC, "OnSpawnNPC" ),
+END_DATADESC()
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+CNPCSpawnDestination::CNPCSpawnDestination()
+{
+ // Available right away, the first time.
+ m_TimeNextAvailable = gpGlobals->curtime;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+bool CNPCSpawnDestination::IsAvailable()
+{
+ if( m_TimeNextAvailable > gpGlobals->curtime )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPCSpawnDestination::OnSpawnedNPC( CAI_BaseNPC *pNPC )
+{
+ // Rename the NPC
+ if( m_RenameNPC != NULL_STRING )
+ {
+ pNPC->SetName( m_RenameNPC );
+ }
+
+ m_OnSpawnNPC.FireOutput( pNPC, this );
+ m_TimeNextAvailable = gpGlobals->curtime + m_ReuseDelay;
+}
+
+//-------------------------------------
+BEGIN_DATADESC( CBaseNPCMaker )
+
+ DEFINE_KEYFIELD( m_nMaxNumNPCs, FIELD_INTEGER, "MaxNPCCount" ),
+ DEFINE_KEYFIELD( m_nMaxLiveChildren, FIELD_INTEGER, "MaxLiveChildren" ),
+ DEFINE_KEYFIELD( m_flSpawnFrequency, FIELD_FLOAT, "SpawnFrequency" ),
+ DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
+
+ DEFINE_FIELD( m_nLiveChildren, FIELD_INTEGER ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Spawn", InputSpawnNPC ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxChildren", InputSetMaxChildren ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "AddMaxChildren", InputAddMaxChildren ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxLiveChildren", InputSetMaxLiveChildren ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpawnFrequency", InputSetSpawnFrequency ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_OnAllSpawned, "OnAllSpawned" ),
+ DEFINE_OUTPUT( m_OnAllSpawnedDead, "OnAllSpawnedDead" ),
+ DEFINE_OUTPUT( m_OnAllLiveChildrenDead, "OnAllLiveChildrenDead" ),
+ DEFINE_OUTPUT( m_OnSpawnNPC, "OnSpawnNPC" ),
+
+ // Function Pointers
+ DEFINE_THINKFUNC( MakerThink ),
+
+ DEFINE_FIELD( m_hIgnoreEntity, FIELD_EHANDLE ),
+ DEFINE_KEYFIELD( m_iszIngoreEnt, FIELD_STRING, "IgnoreEntity" ),
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::Spawn( void )
+{
+ SetSolid( SOLID_NONE );
+ m_nLiveChildren = 0;
+ Precache();
+
+ // If I can make an infinite number of NPC, force them to fade
+ if ( m_spawnflags & SF_NPCMAKER_INF_CHILD )
+ {
+ m_spawnflags |= SF_NPCMAKER_FADE;
+ }
+
+ //Start on?
+ if ( m_bDisabled == false )
+ {
+ SetThink ( &CBaseNPCMaker::MakerThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+ else
+ {
+ //wait to be activated.
+ SetThink ( &CBaseNPCMaker::SUB_DoNothing );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// A not-very-robust check to see if a human hull could fit at this location.
+// used to validate spawn destinations.
+//-----------------------------------------------------------------------------
+bool CBaseNPCMaker::HumanHullFits( const Vector &vecLocation )
+{
+ trace_t tr;
+ UTIL_TraceHull( vecLocation,
+ vecLocation + Vector( 0, 0, 1 ),
+ NAI_Hull::Mins(HULL_HUMAN),
+ NAI_Hull::Maxs(HULL_HUMAN),
+ MASK_NPCSOLID,
+ m_hIgnoreEntity,
+ COLLISION_GROUP_NONE,
+ &tr );
+
+ if( tr.fraction == 1.0 )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether or not it is OK to make an NPC at this instant.
+//-----------------------------------------------------------------------------
+bool CBaseNPCMaker::CanMakeNPC( bool bIgnoreSolidEntities )
+{
+ if( ai_inhibit_spawners.GetBool() )
+ return false;
+
+ if ( m_nMaxLiveChildren > 0 && m_nLiveChildren >= m_nMaxLiveChildren )
+ {// not allowed to make a new one yet. Too many live ones out right now.
+ return false;
+ }
+
+ if ( m_iszIngoreEnt != NULL_STRING )
+ {
+ m_hIgnoreEntity = gEntList.FindEntityByName( NULL, m_iszIngoreEnt );
+ }
+
+ Vector mins = GetAbsOrigin() - Vector( 34, 34, 0 );
+ Vector maxs = GetAbsOrigin() + Vector( 34, 34, 0 );
+ maxs.z = GetAbsOrigin().z;
+
+ // If we care about not hitting solid entities, look for 'em
+ if ( !bIgnoreSolidEntities )
+ {
+ CBaseEntity *pList[128];
+
+ int count = UTIL_EntitiesInBox( pList, 128, mins, maxs, FL_CLIENT|FL_NPC );
+ if ( count )
+ {
+ //Iterate through the list and check the results
+ for ( int i = 0; i < count; i++ )
+ {
+ //Don't build on top of another entity
+ if ( pList[i] == NULL )
+ continue;
+
+ //If one of the entities is solid, then we may not be able to spawn now
+ if ( ( pList[i]->GetSolidFlags() & FSOLID_NOT_SOLID ) == false )
+ {
+ // Since the outer method doesn't work well around striders on account of their huge bounding box.
+ // Find the ground under me and see if a human hull would fit there.
+ trace_t tr;
+ UTIL_TraceHull( GetAbsOrigin() + Vector( 0, 0, 2 ),
+ GetAbsOrigin() - Vector( 0, 0, 8192 ),
+ NAI_Hull::Mins(HULL_HUMAN),
+ NAI_Hull::Maxs(HULL_HUMAN),
+ MASK_NPCSOLID,
+ m_hIgnoreEntity,
+ COLLISION_GROUP_NONE,
+ &tr );
+
+ if( !HumanHullFits( tr.endpos + Vector( 0, 0, 1 ) ) )
+ {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ // Do we need to check to see if the player's looking?
+ if ( HasSpawnFlags( SF_NPCMAKER_HIDEFROMPLAYER ) )
+ {
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
+ if ( pPlayer )
+ {
+ // Only spawn if the player's looking away from me
+ if( pPlayer->FInViewCone( GetAbsOrigin() ) && pPlayer->FVisible( GetAbsOrigin() ) )
+ {
+ if ( !(pPlayer->GetFlags() & FL_NOTARGET) )
+ return false;
+ DevMsg( 2, "Spawner %s spawning even though seen due to notarget\n", STRING( GetEntityName() ) );
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: If this had a finite number of children, return true if they've all
+// been created.
+//-----------------------------------------------------------------------------
+bool CBaseNPCMaker::IsDepleted()
+{
+ if ( (m_spawnflags & SF_NPCMAKER_INF_CHILD) || m_nMaxNumNPCs > 0 )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Toggle the spawner's state
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::Toggle( void )
+{
+ if ( m_bDisabled )
+ {
+ Enable();
+ }
+ else
+ {
+ Disable();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Start the spawner
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::Enable( void )
+{
+ // can't be enabled once depleted
+ if ( IsDepleted() )
+ return;
+
+ m_bDisabled = false;
+ SetThink ( &CBaseNPCMaker::MakerThink );
+ SetNextThink( gpGlobals->curtime );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop the spawner
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::Disable( void )
+{
+ m_bDisabled = true;
+ SetThink ( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler that spawns an NPC.
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::InputSpawnNPC( inputdata_t &inputdata )
+{
+ if( !IsDepleted() )
+ {
+ MakeNPC();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input hander that starts the spawner
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::InputEnable( inputdata_t &inputdata )
+{
+ Enable();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input hander that stops the spawner
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::InputDisable( inputdata_t &inputdata )
+{
+ Disable();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input hander that toggles the spawner
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::InputToggle( inputdata_t &inputdata )
+{
+ Toggle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::InputSetMaxChildren( inputdata_t &inputdata )
+{
+ m_nMaxNumNPCs = inputdata.value.Int();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::InputAddMaxChildren( inputdata_t &inputdata )
+{
+ m_nMaxNumNPCs += inputdata.value.Int();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::InputSetMaxLiveChildren( inputdata_t &inputdata )
+{
+ m_nMaxLiveChildren = inputdata.value.Int();
+}
+
+void CBaseNPCMaker::InputSetSpawnFrequency( inputdata_t &inputdata )
+{
+ m_flSpawnFrequency = inputdata.value.Float();
+}
+
+LINK_ENTITY_TO_CLASS( npc_maker, CNPCMaker );
+
+BEGIN_DATADESC( CNPCMaker )
+
+ DEFINE_KEYFIELD( m_iszNPCClassname, FIELD_STRING, "NPCType" ),
+ DEFINE_KEYFIELD( m_ChildTargetName, FIELD_STRING, "NPCTargetname" ),
+ DEFINE_KEYFIELD( m_SquadName, FIELD_STRING, "NPCSquadName" ),
+ DEFINE_KEYFIELD( m_spawnEquipment, FIELD_STRING, "additionalequipment" ),
+ DEFINE_KEYFIELD( m_strHintGroup, FIELD_STRING, "NPCHintGroup" ),
+ DEFINE_KEYFIELD( m_RelationshipString, FIELD_STRING, "Relationship" ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CNPCMaker::CNPCMaker( void )
+{
+ m_spawnEquipment = NULL_STRING;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache the target NPC
+//-----------------------------------------------------------------------------
+void CNPCMaker::Precache( void )
+{
+ BaseClass::Precache();
+
+ const char *pszNPCName = STRING( m_iszNPCClassname );
+ if ( !pszNPCName || !pszNPCName[0] )
+ {
+ Warning("npc_maker %s has no specified NPC-to-spawn classname.\n", STRING(GetEntityName()) );
+ }
+ else
+ {
+ UTIL_PrecacheOther( pszNPCName );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates the NPC.
+//-----------------------------------------------------------------------------
+void CNPCMaker::MakeNPC( void )
+{
+ if (!CanMakeNPC())
+ return;
+
+ CAI_BaseNPC *pent = (CAI_BaseNPC*)CreateEntityByName( STRING(m_iszNPCClassname) );
+
+ if ( !pent )
+ {
+ Warning("NULL Ent in NPCMaker!\n" );
+ return;
+ }
+
+ // ------------------------------------------------
+ // Intialize spawned NPC's relationships
+ // ------------------------------------------------
+ pent->SetRelationshipString( m_RelationshipString );
+
+ m_OnSpawnNPC.Set( pent, pent, this );
+
+ pent->SetAbsOrigin( GetAbsOrigin() );
+
+ // Strip pitch and roll from the spawner's angles. Pass only yaw to the spawned NPC.
+ QAngle angles = GetAbsAngles();
+ angles.x = 0.0;
+ angles.z = 0.0;
+ pent->SetAbsAngles( angles );
+
+ pent->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
+
+ if ( m_spawnflags & SF_NPCMAKER_FADE )
+ {
+ pent->AddSpawnFlags( SF_NPC_FADE_CORPSE );
+ }
+
+ pent->m_spawnEquipment = m_spawnEquipment;
+ pent->SetSquadName( m_SquadName );
+ pent->SetHintGroup( m_strHintGroup );
+
+ ChildPreSpawn( pent );
+
+ DispatchSpawn( pent );
+ pent->SetOwnerEntity( this );
+ DispatchActivate( pent );
+
+ if ( m_ChildTargetName != NULL_STRING )
+ {
+ // if I have a netname (overloaded), give the child NPC that name as a targetname
+ pent->SetName( m_ChildTargetName );
+ }
+
+ ChildPostSpawn( pent );
+
+ m_nLiveChildren++;// count this NPC
+
+ if (!(m_spawnflags & SF_NPCMAKER_INF_CHILD))
+ {
+ m_nMaxNumNPCs--;
+
+ if ( IsDepleted() )
+ {
+ m_OnAllSpawned.FireOutput( this, this );
+
+ // Disable this forever. Don't kill it because it still gets death notices
+ SetThink( NULL );
+ SetUse( NULL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pChild -
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::ChildPostSpawn( CAI_BaseNPC *pChild )
+{
+ // If I'm stuck inside any props, remove them
+ bool bFound = true;
+ while ( bFound )
+ {
+ trace_t tr;
+ UTIL_TraceHull( pChild->GetAbsOrigin(), pChild->GetAbsOrigin(), pChild->WorldAlignMins(), pChild->WorldAlignMaxs(), MASK_NPCSOLID, pChild, COLLISION_GROUP_NONE, &tr );
+ //NDebugOverlay::Box( pChild->GetAbsOrigin(), pChild->WorldAlignMins(), pChild->WorldAlignMaxs(), 0, 255, 0, 32, 5.0 );
+ if ( tr.fraction != 1.0 && tr.m_pEnt )
+ {
+ if ( FClassnameIs( tr.m_pEnt, "prop_physics" ) )
+ {
+ // Set to non-solid so this loop doesn't keep finding it
+ tr.m_pEnt->AddSolidFlags( FSOLID_NOT_SOLID );
+ UTIL_RemoveImmediate( tr.m_pEnt );
+ continue;
+ }
+ }
+
+ bFound = false;
+ }
+ if ( m_hIgnoreEntity != NULL )
+ {
+ pChild->SetOwnerEntity( m_hIgnoreEntity );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a new NPC every so often.
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::MakerThink ( void )
+{
+ SetNextThink( gpGlobals->curtime + m_flSpawnFrequency );
+
+ MakeNPC();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pVictim -
+//-----------------------------------------------------------------------------
+void CBaseNPCMaker::DeathNotice( CBaseEntity *pVictim )
+{
+ // ok, we've gotten the deathnotice from our child, now clear out its owner if we don't want it to fade.
+ m_nLiveChildren--;
+
+ // If we're here, we're getting erroneous death messages from children we haven't created
+ AssertMsg( m_nLiveChildren >= 0, "npc_maker receiving child death notice but thinks has no children\n" );
+
+ if ( m_nLiveChildren <= 0 )
+ {
+ m_OnAllLiveChildrenDead.FireOutput( this, this );
+
+ // See if we've exhausted our supply of NPCs
+ if ( ( (m_spawnflags & SF_NPCMAKER_INF_CHILD) == false ) && IsDepleted() )
+ {
+ // Signal that all our children have been spawned and are now dead
+ m_OnAllSpawnedDead.FireOutput( this, this );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates new NPCs from a template NPC. The template NPC must be marked
+// as a template (spawnflag) and does not spawn.
+//-----------------------------------------------------------------------------
+
+LINK_ENTITY_TO_CLASS( npc_template_maker, CTemplateNPCMaker );
+
+BEGIN_DATADESC( CTemplateNPCMaker )
+
+ DEFINE_KEYFIELD( m_iszTemplateName, FIELD_STRING, "TemplateName" ),
+ DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
+ DEFINE_FIELD( m_iszTemplateData, FIELD_STRING ),
+ DEFINE_KEYFIELD( m_iszDestinationGroup, FIELD_STRING, "DestinationGroup" ),
+ DEFINE_KEYFIELD( m_CriterionVisibility, FIELD_INTEGER, "CriterionVisibility" ),
+ DEFINE_KEYFIELD( m_CriterionDistance, FIELD_INTEGER, "CriterionDistance" ),
+ DEFINE_KEYFIELD( m_iMinSpawnDistance, FIELD_INTEGER, "MinSpawnDistance" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "SpawnNPCInRadius", InputSpawnInRadius ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SpawnNPCInLine", InputSpawnInLine ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SpawnMultiple", InputSpawnMultiple ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "ChangeDestinationGroup", InputChangeDestinationGroup ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMinimumSpawnDistance", InputSetMinimumSpawnDistance ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------
+// A hook that lets derived NPC makers do special stuff when precaching.
+//-----------------------------------------------------------------------------
+void CTemplateNPCMaker::PrecacheTemplateEntity( CBaseEntity *pEntity )
+{
+ pEntity->Precache();
+}
+
+
+void CTemplateNPCMaker::Precache()
+{
+ BaseClass::Precache();
+
+ if ( !m_iszTemplateData )
+ {
+ //
+ // This must be the first time we're activated, not a load from save game.
+ // Look up the template in the template database.
+ //
+ if (!m_iszTemplateName)
+ {
+ Warning( "npc_template_maker %s has no template NPC!\n", STRING(GetEntityName()) );
+ UTIL_Remove( this );
+ return;
+ }
+ else
+ {
+ m_iszTemplateData = Templates_FindByTargetName(STRING(m_iszTemplateName));
+ if ( m_iszTemplateData == NULL_STRING )
+ {
+ DevWarning( "npc_template_maker %s: template NPC %s not found!\n", STRING(GetEntityName()), STRING(m_iszTemplateName) );
+ UTIL_Remove( this );
+ return;
+ }
+ }
+ }
+
+ Assert( m_iszTemplateData != NULL_STRING );
+
+ // If the mapper marked this as "preload", then instance the entity preache stuff and delete the entity
+ //if ( !HasSpawnFlags(SF_NPCMAKER_NOPRELOADMODELS) )
+ if ( m_iszTemplateData != NULL_STRING )
+ {
+ CBaseEntity *pEntity = NULL;
+ MapEntity_ParseEntity( pEntity, STRING(m_iszTemplateData), NULL );
+ if ( pEntity != NULL )
+ {
+ PrecacheTemplateEntity( pEntity );
+ UTIL_RemoveImmediate( pEntity );
+ }
+ }
+}
+
+#define MAX_DESTINATION_ENTS 100
+CNPCSpawnDestination *CTemplateNPCMaker::FindSpawnDestination()
+{
+ CNPCSpawnDestination *pDestinations[ MAX_DESTINATION_ENTS ];
+ CBaseEntity *pEnt = NULL;
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+ int count = 0;
+
+ if( !pPlayer )
+ {
+ return NULL;
+ }
+
+ // Collect all the qualifiying destination ents
+ pEnt = gEntList.FindEntityByName( NULL, m_iszDestinationGroup );
+
+ if( !pEnt )
+ {
+ DevWarning("Template NPC Spawner (%s) doesn't have any spawn destinations!\n", GetDebugName() );
+ return NULL;
+ }
+
+ while( pEnt )
+ {
+ CNPCSpawnDestination *pDestination;
+
+ pDestination = dynamic_cast <CNPCSpawnDestination*>(pEnt);
+
+ if( pDestination && pDestination->IsAvailable() )
+ {
+ bool fValid = true;
+ Vector vecTest = pDestination->GetAbsOrigin();
+
+ if( m_CriterionVisibility != TS_YN_DONT_CARE )
+ {
+ // Right now View Cone check is omitted intentionally.
+ Vector vecTopOfHull = NAI_Hull::Maxs( HULL_HUMAN );
+ vecTopOfHull.x = 0;
+ vecTopOfHull.y = 0;
+ bool fVisible = (pPlayer->FVisible( vecTest ) || pPlayer->FVisible( vecTest + vecTopOfHull ) );
+
+ if( m_CriterionVisibility == TS_YN_YES )
+ {
+ if( !fVisible )
+ fValid = false;
+ }
+ else
+ {
+ if( fVisible )
+ {
+ if ( !(pPlayer->GetFlags() & FL_NOTARGET) )
+ fValid = false;
+ else
+ DevMsg( 2, "Spawner %s spawning even though seen due to notarget\n", STRING( GetEntityName() ) );
+ }
+ }
+ }
+
+ if( fValid )
+ {
+ pDestinations[ count ] = pDestination;
+ count++;
+ }
+ }
+
+ pEnt = gEntList.FindEntityByName( pEnt, m_iszDestinationGroup );
+ }
+
+ if( count < 1 )
+ return NULL;
+
+ // Now find the nearest/farthest based on distance criterion
+ if( m_CriterionDistance == TS_DIST_DONT_CARE )
+ {
+ // Pretty lame way to pick randomly. Try a few times to find a random
+ // location where a hull can fit. Don't try too many times due to performance
+ // concerns.
+ for( int i = 0 ; i < 5 ; i++ )
+ {
+ CNPCSpawnDestination *pRandomDest = pDestinations[ rand() % count ];
+
+ if( HumanHullFits( pRandomDest->GetAbsOrigin() ) )
+ {
+ return pRandomDest;
+ }
+ }
+
+ return NULL;
+ }
+ else
+ {
+ if( m_CriterionDistance == TS_DIST_NEAREST )
+ {
+ float flNearest = FLT_MAX;
+ CNPCSpawnDestination *pNearest = NULL;
+
+ for( int i = 0 ; i < count ; i++ )
+ {
+ Vector vecTest = pDestinations[ i ]->GetAbsOrigin();
+ float flDist = ( vecTest - pPlayer->GetAbsOrigin() ).Length();
+
+ if ( m_iMinSpawnDistance != 0 && m_iMinSpawnDistance > flDist )
+ continue;
+
+ if( flDist < flNearest && HumanHullFits( vecTest ) )
+ {
+ flNearest = flDist;
+ pNearest = pDestinations[ i ];
+ }
+ }
+
+ return pNearest;
+ }
+ else
+ {
+ float flFarthest = 0;
+ CNPCSpawnDestination *pFarthest = NULL;
+
+ for( int i = 0 ; i < count ; i++ )
+ {
+ Vector vecTest = pDestinations[ i ]->GetAbsOrigin();
+ float flDist = ( vecTest - pPlayer->GetAbsOrigin() ).Length();
+
+ if ( m_iMinSpawnDistance != 0 && m_iMinSpawnDistance > flDist )
+ continue;
+
+ if( flDist > flFarthest && HumanHullFits( vecTest ) )
+ {
+ flFarthest = flDist;
+ pFarthest = pDestinations[ i ];
+ }
+ }
+
+ return pFarthest;
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTemplateNPCMaker::MakeNPC( void )
+{
+ // If we should be using the radius spawn method instead, do so
+ if ( m_flRadius && HasSpawnFlags(SF_NPCMAKER_ALWAYSUSERADIUS) )
+ {
+ MakeNPCInRadius();
+ return;
+ }
+
+ if (!CanMakeNPC( ( m_iszDestinationGroup != NULL_STRING ) ))
+ return;
+
+ CNPCSpawnDestination *pDestination = NULL;
+ if ( m_iszDestinationGroup != NULL_STRING )
+ {
+ pDestination = FindSpawnDestination();
+ if ( !pDestination )
+ {
+ DevMsg( 2, "%s '%s' failed to find a valid spawnpoint in destination group: '%s'\n", GetClassname(), STRING(GetEntityName()), STRING(m_iszDestinationGroup) );
+ return;
+ }
+ }
+
+ CAI_BaseNPC *pent = NULL;
+ CBaseEntity *pEntity = NULL;
+ MapEntity_ParseEntity( pEntity, STRING(m_iszTemplateData), NULL );
+ if ( pEntity != NULL )
+ {
+ pent = (CAI_BaseNPC *)pEntity;
+ }
+
+ if ( !pent )
+ {
+ Warning("NULL Ent in NPCMaker!\n" );
+ return;
+ }
+
+ if ( pDestination )
+ {
+ pent->SetAbsOrigin( pDestination->GetAbsOrigin() );
+
+ // Strip pitch and roll from the spawner's angles. Pass only yaw to the spawned NPC.
+ QAngle angles = pDestination->GetAbsAngles();
+ angles.x = 0.0;
+ angles.z = 0.0;
+ pent->SetAbsAngles( angles );
+
+ pDestination->OnSpawnedNPC( pent );
+ }
+ else
+ {
+ pent->SetAbsOrigin( GetAbsOrigin() );
+
+ // Strip pitch and roll from the spawner's angles. Pass only yaw to the spawned NPC.
+ QAngle angles = GetAbsAngles();
+ angles.x = 0.0;
+ angles.z = 0.0;
+ pent->SetAbsAngles( angles );
+ }
+
+ m_OnSpawnNPC.Set( pEntity, pEntity, this );
+
+ if ( m_spawnflags & SF_NPCMAKER_FADE )
+ {
+ pent->AddSpawnFlags( SF_NPC_FADE_CORPSE );
+ }
+
+ pent->RemoveSpawnFlags( SF_NPC_TEMPLATE );
+
+ if ( ( m_spawnflags & SF_NPCMAKER_NO_DROP ) == false )
+ {
+ pent->RemoveSpawnFlags( SF_NPC_FALL_TO_GROUND ); // don't fall, slam
+ }
+
+ ChildPreSpawn( pent );
+
+ DispatchSpawn( pent );
+ pent->SetOwnerEntity( this );
+ DispatchActivate( pent );
+
+ ChildPostSpawn( pent );
+
+ m_nLiveChildren++;// count this NPC
+
+ if (!(m_spawnflags & SF_NPCMAKER_INF_CHILD))
+ {
+ m_nMaxNumNPCs--;
+
+ if ( IsDepleted() )
+ {
+ m_OnAllSpawned.FireOutput( this, this );
+
+ // Disable this forever. Don't kill it because it still gets death notices
+ SetThink( NULL );
+ SetUse( NULL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CTemplateNPCMaker::MakeNPCInLine( void )
+{
+ if (!CanMakeNPC(true))
+ return;
+
+ CAI_BaseNPC *pent = NULL;
+ CBaseEntity *pEntity = NULL;
+ MapEntity_ParseEntity( pEntity, STRING(m_iszTemplateData), NULL );
+ if ( pEntity != NULL )
+ {
+ pent = (CAI_BaseNPC *)pEntity;
+ }
+
+ if ( !pent )
+ {
+ Warning("NULL Ent in NPCMaker!\n" );
+ return;
+ }
+
+ m_OnSpawnNPC.Set( pEntity, pEntity, this );
+
+ PlaceNPCInLine( pent );
+
+ pent->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
+
+ pent->RemoveSpawnFlags( SF_NPC_TEMPLATE );
+ ChildPreSpawn( pent );
+
+ DispatchSpawn( pent );
+ pent->SetOwnerEntity( this );
+ DispatchActivate( pent );
+
+ ChildPostSpawn( pent );
+
+ m_nLiveChildren++;// count this NPC
+
+ if (!(m_spawnflags & SF_NPCMAKER_INF_CHILD))
+ {
+ m_nMaxNumNPCs--;
+
+ if ( IsDepleted() )
+ {
+ m_OnAllSpawned.FireOutput( this, this );
+
+ // Disable this forever. Don't kill it because it still gets death notices
+ SetThink( NULL );
+ SetUse( NULL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool CTemplateNPCMaker::PlaceNPCInLine( CAI_BaseNPC *pNPC )
+{
+ Vector vecPlace;
+ Vector vecLine;
+
+ GetVectors( &vecLine, NULL, NULL );
+
+ // invert this, line up NPC's BEHIND the maker.
+ vecLine *= -1;
+
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 8192 ), MASK_SHOT, pNPC, COLLISION_GROUP_NONE, &tr );
+ vecPlace = tr.endpos;
+ float flStepSize = pNPC->GetHullWidth();
+
+ // Try 10 times to place this npc.
+ for( int i = 0 ; i < 10 ; i++ )
+ {
+ UTIL_TraceHull( vecPlace,
+ vecPlace + Vector( 0, 0, 10 ),
+ pNPC->GetHullMins(),
+ pNPC->GetHullMaxs(),
+ MASK_SHOT,
+ pNPC,
+ COLLISION_GROUP_NONE,
+ &tr );
+
+ if( tr.fraction == 1.0 )
+ {
+ pNPC->SetAbsOrigin( tr.endpos );
+ return true;
+ }
+
+ vecPlace += vecLine * flStepSize;
+ }
+
+ DevMsg("**Failed to place NPC in line!\n");
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Place NPC somewhere on the perimeter of my radius.
+//-----------------------------------------------------------------------------
+void CTemplateNPCMaker::MakeNPCInRadius( void )
+{
+ if ( !CanMakeNPC(true))
+ return;
+
+ CAI_BaseNPC *pent = NULL;
+ CBaseEntity *pEntity = NULL;
+ MapEntity_ParseEntity( pEntity, STRING(m_iszTemplateData), NULL );
+ if ( pEntity != NULL )
+ {
+ pent = (CAI_BaseNPC *)pEntity;
+ }
+
+ if ( !pent )
+ {
+ Warning("NULL Ent in NPCMaker!\n" );
+ return;
+ }
+
+ if ( !PlaceNPCInRadius( pent ) )
+ {
+ // Failed to place the NPC. Abort
+ UTIL_RemoveImmediate( pent );
+ return;
+ }
+
+ m_OnSpawnNPC.Set( pEntity, pEntity, this );
+
+ pent->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
+
+ pent->RemoveSpawnFlags( SF_NPC_TEMPLATE );
+ ChildPreSpawn( pent );
+
+ DispatchSpawn( pent );
+
+ pent->SetOwnerEntity( this );
+ DispatchActivate( pent );
+
+ ChildPostSpawn( pent );
+
+ m_nLiveChildren++;// count this NPC
+
+ if (!(m_spawnflags & SF_NPCMAKER_INF_CHILD))
+ {
+ m_nMaxNumNPCs--;
+
+ if ( IsDepleted() )
+ {
+ m_OnAllSpawned.FireOutput( this, this );
+
+ // Disable this forever. Don't kill it because it still gets death notices
+ SetThink( NULL );
+ SetUse( NULL );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a place to spawn an npc within my radius.
+// Right now this function tries to place them on the perimeter of radius.
+// Output : false if we couldn't find a spot!
+//-----------------------------------------------------------------------------
+bool CTemplateNPCMaker::PlaceNPCInRadius( CAI_BaseNPC *pNPC )
+{
+ Vector vPos;
+
+ if ( CAI_BaseNPC::FindSpotForNPCInRadius( &vPos, GetAbsOrigin(), pNPC, m_flRadius ) )
+ {
+ pNPC->SetAbsOrigin( vPos );
+ return true;
+ }
+
+ DevMsg("**Failed to place NPC in radius!\n");
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CTemplateNPCMaker::MakeMultipleNPCS( int nNPCs )
+{
+ bool bInRadius = ( m_iszDestinationGroup == NULL_STRING && m_flRadius > 0.1 );
+ while ( nNPCs-- )
+ {
+ if ( !bInRadius )
+ {
+ MakeNPC();
+ }
+ else
+ {
+ MakeNPCInRadius();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CTemplateNPCMaker::InputSpawnMultiple( inputdata_t &inputdata )
+{
+ MakeMultipleNPCS( inputdata.value.Int() );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CTemplateNPCMaker::InputChangeDestinationGroup( inputdata_t &inputdata )
+{
+ m_iszDestinationGroup = inputdata.value.StringID();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CTemplateNPCMaker::InputSetMinimumSpawnDistance( inputdata_t &inputdata )
+{
+ m_iMinSpawnDistance = inputdata.value.Int();
+}