You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1083 lines
29 KiB
1083 lines
29 KiB
//========= 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(); |
|
}
|
|
|