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.
371 lines
10 KiB
371 lines
10 KiB
#include "cbase.h" |
|
#include "baseentity.h" |
|
#include "asw_base_spawner.h" |
|
#include "asw_marine.h" |
|
#include "asw_gamerules.h" |
|
#include "asw_marine_resource.h" |
|
#include "asw_game_resource.h" |
|
#include "entityapi.h" |
|
#include "entityoutput.h" |
|
#include "props.h" |
|
#include "asw_alien.h" |
|
#include "asw_director.h" |
|
#include "asw_fail_advice.h" |
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar asw_debug_spawners("asw_debug_spawners", "0", FCVAR_CHEAT, "Displays debug messages for the asw_spawners"); |
|
|
|
BEGIN_DATADESC( CASW_Base_Spawner ) |
|
DEFINE_KEYFIELD( m_bSpawnIfMarinesAreNear, FIELD_BOOLEAN, "SpawnIfMarinesAreNear" ), |
|
DEFINE_KEYFIELD( m_flNearDistance, FIELD_FLOAT, "NearDistance" ), |
|
DEFINE_KEYFIELD( m_AlienOrders, FIELD_INTEGER, "AlienOrders" ), |
|
DEFINE_KEYFIELD( m_AlienOrderTargetName, FIELD_STRING, "AlienOrderTargetName" ), |
|
DEFINE_KEYFIELD( m_AlienName, FIELD_STRING, "AlienNameTag" ), |
|
DEFINE_KEYFIELD( m_bStartBurrowed, FIELD_BOOLEAN, "StartBurrowed" ), |
|
DEFINE_KEYFIELD( m_UnburrowIdleActivity, FIELD_STRING, "UnburrowIdleActivity" ), |
|
DEFINE_KEYFIELD( m_UnburrowActivity, FIELD_STRING, "UnburrowActivity" ), |
|
DEFINE_KEYFIELD( m_bCheckSpawnIsClear, FIELD_BOOLEAN, "ClearCheck" ), |
|
DEFINE_KEYFIELD( m_bLongRangeNPC, FIELD_BOOLEAN, "LongRange" ), |
|
DEFINE_KEYFIELD( m_iMinSkillLevel, FIELD_INTEGER, "MinSkillLevel" ), |
|
DEFINE_KEYFIELD( m_iMaxSkillLevel, FIELD_INTEGER, "MaxSkillLevel" ), |
|
|
|
DEFINE_OUTPUT( m_OnSpawned, "OnSpawned" ), |
|
|
|
DEFINE_FIELD( m_iMoveAsideCount, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_hAlienOrderTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ToggleEnabled", InputToggleEnabled ), |
|
END_DATADESC() |
|
|
|
CASW_Base_Spawner::CASW_Base_Spawner() |
|
{ |
|
m_hAlienOrderTarget = NULL; |
|
m_flLastSpawnTime = 0.0f; |
|
m_bEnabled = true; |
|
} |
|
|
|
CASW_Base_Spawner::~CASW_Base_Spawner() |
|
{ |
|
|
|
} |
|
|
|
void CASW_Base_Spawner::Spawn() |
|
{ |
|
SetSolid( SOLID_NONE ); |
|
m_iMoveAsideCount = 0; |
|
Precache(); |
|
//SetModel( "models/editor/asw_spawner/asw_spawner.mdl" ); |
|
} |
|
|
|
void CASW_Base_Spawner::Precache() |
|
{ |
|
BaseClass::Precache(); |
|
|
|
PrecacheScriptSound( "Spawner.Horde" ); |
|
PrecacheScriptSound( "Spawner.AreaClear" ); |
|
//PrecacheModel( "models/editor/asw_spawner/asw_spawner.mdl" ); |
|
} |
|
|
|
bool CASW_Base_Spawner::CanSpawn( const Vector &vecHullMins, const Vector &vecHullMaxs ) |
|
{ |
|
if ( !m_bEnabled ) |
|
return false; |
|
|
|
// is a marine too near? |
|
if ( !m_bSpawnIfMarinesAreNear && m_flNearDistance > 0 ) |
|
{ |
|
CASW_Game_Resource* pGameResource = ASWGameResource(); |
|
float distance = 0.0f; |
|
for ( int i = 0; i < ASW_MAX_MARINE_RESOURCES; i++ ) |
|
{ |
|
CASW_Marine_Resource* pMR = pGameResource->GetMarineResource(i); |
|
if ( pMR && pMR->GetMarineEntity() && pMR->GetMarineEntity()->GetHealth() > 0 ) |
|
{ |
|
distance = pMR->GetMarineEntity()->GetAbsOrigin().DistTo( GetAbsOrigin() ); |
|
if ( distance < m_flNearDistance ) |
|
{ |
|
if ( asw_debug_spawners.GetBool() ) |
|
Msg("asw_spawner(%s): Alien can't spawn because a marine (%d) is %f away\n", GetEntityName(), i, distance); |
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
Vector mins = GetAbsOrigin() - Vector( 23, 23, 0 ); |
|
Vector maxs = GetAbsOrigin() + Vector( 23, 23, 0 ); |
|
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, 1 ), |
|
GetAbsOrigin() - Vector( 0, 0, 1 ), |
|
vecHullMins, |
|
vecHullMaxs, |
|
MASK_NPCSOLID, |
|
NULL, |
|
COLLISION_GROUP_NONE, |
|
&tr ); |
|
|
|
if (tr.fraction < 1.0f && tr.DidHitNonWorldEntity()) |
|
{ |
|
// some non-world entity is blocking the spawn point, so don't spawn |
|
if (tr.m_pEnt) |
|
{ |
|
if ( m_iMoveAsideCount < 6 ) // don't send 'move aside' commands more than 5 times in a row, else you'll stop blocked NPCs going to sleep. |
|
{ |
|
IASW_Spawnable_NPC *pSpawnable = dynamic_cast<IASW_Spawnable_NPC*>(tr.m_pEnt); |
|
if (pSpawnable) |
|
{ |
|
pSpawnable->MoveAside(); // try and make him move aside |
|
m_iMoveAsideCount++; |
|
} |
|
} |
|
if (asw_debug_spawners.GetBool()) |
|
Msg("asw_spawner(%s): Alien can't spawn because a non-world entity is blocking the spawn point: %s\n", GetEntityName(), tr.m_pEnt->GetClassname()); |
|
} |
|
else |
|
{ |
|
if (asw_debug_spawners.GetBool()) |
|
Msg("asw_spawner(%s): Alien can't spawn because a non-world entity is blocking the spawn point.\n", GetEntityName()); |
|
} |
|
|
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// is there something blocking the spawn point? |
|
if ( m_bCheckSpawnIsClear ) |
|
{ |
|
if ( asw_debug_spawners.GetBool() ) |
|
{ |
|
Msg("Checking spawn is clear...\n"); |
|
} |
|
|
|
trace_t tr; |
|
UTIL_TraceHull( GetAbsOrigin(), |
|
GetAbsOrigin() + Vector( 0, 0, 1 ), |
|
vecHullMins, |
|
vecHullMaxs, |
|
MASK_NPCSOLID, |
|
this, |
|
COLLISION_GROUP_NONE, |
|
&tr ); |
|
|
|
if( tr.fraction != 1.0 ) |
|
{ |
|
if ( asw_debug_spawners.GetBool() ) |
|
Msg("asw_spawner(%s): Alien can't spawn because he wouldn't fit in the spawn point.\n", GetEntityName()); |
|
// TODO: If we were trying to spawn an uber, change to spawning a regular instead |
|
return false; |
|
} |
|
} |
|
|
|
m_iMoveAsideCount = 0; |
|
return true; |
|
} |
|
|
|
void CASW_Base_Spawner::RemoveObstructingProps( CBaseEntity *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 ); |
|
if (asw_debug_spawners.GetBool()) |
|
{ |
|
NDebugOverlay::Box( pChild->GetAbsOrigin(), pChild->WorldAlignMins(), pChild->WorldAlignMaxs(), 0, 255, 0, 32, 5.0 ); |
|
} |
|
if ( tr.fraction != 1.0 && tr.m_pEnt ) |
|
{ |
|
if ( dynamic_cast<CBaseProp*>(tr.m_pEnt) ) |
|
{ |
|
// 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; |
|
} |
|
} |
|
|
|
CBaseEntity* CASW_Base_Spawner::GetOrderTarget() |
|
{ |
|
// find entity with name m_AlienOrderTargetName |
|
if (GetAlienOrderTarget() == NULL && |
|
(m_AlienOrders == AOT_MoveTo || m_AlienOrders == AOT_MoveToIgnoringMarines ) |
|
) |
|
{ |
|
m_hAlienOrderTarget = gEntList.FindEntityByName( NULL, m_AlienOrderTargetName, NULL ); |
|
|
|
if( !GetAlienOrderTarget() ) |
|
{ |
|
DevWarning("%s: asw_spawner can't find order object: %s\n", GetDebugName(), STRING(m_AlienOrderTargetName) ); |
|
return NULL; |
|
} |
|
} |
|
return GetAlienOrderTarget(); |
|
} |
|
|
|
IASW_Spawnable_NPC* CASW_Base_Spawner::SpawnAlien( const char *szAlienClassName, const Vector &vecHullMins, const Vector &vecHullMaxs ) |
|
{ |
|
if ( !IsValidOnThisSkillLevel() ) |
|
{ |
|
UTIL_Remove(this); // delete ourself if this spawner isn't valid on this difficulty level |
|
return NULL; |
|
} |
|
|
|
if ( !CanSpawn( vecHullMins, vecHullMaxs ) ) // this may turn off m_bCurrentlySpawningUber if there's no room |
|
return NULL; |
|
|
|
CBaseEntity *pEntity = CreateEntityByName( szAlienClassName ); |
|
if ( !pEntity ) |
|
{ |
|
Msg( "Failed to spawn %s\n", szAlienClassName ); |
|
return NULL; |
|
} |
|
|
|
CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); |
|
if ( pNPC ) |
|
{ |
|
pNPC->AddSpawnFlags( SF_NPC_FALL_TO_GROUND ); |
|
} |
|
|
|
// check if he can see far |
|
if ( m_bLongRangeNPC ) |
|
pEntity->AddSpawnFlags( SF_NPC_LONG_RANGE ); |
|
|
|
if ( HasSpawnFlags( ASW_SF_NEVER_SLEEP ) ) |
|
pEntity->AddSpawnFlags( SF_NPC_ALWAYSTHINK ); |
|
|
|
// 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; |
|
pEntity->SetAbsOrigin( GetAbsOrigin() ); |
|
pEntity->SetAbsAngles( angles ); |
|
|
|
IASW_Spawnable_NPC* pSpawnable = dynamic_cast<IASW_Spawnable_NPC*>(pEntity); |
|
Assert( pSpawnable ); |
|
if ( !pSpawnable ) |
|
{ |
|
Warning( "NULL Spawnable Ent in asw_spawner! AlienClass = %s\n", szAlienClassName ); |
|
UTIL_Remove( pEntity ); |
|
return NULL; |
|
} |
|
m_flLastSpawnTime = gpGlobals->curtime; |
|
if ( m_bStartBurrowed ) |
|
{ |
|
pSpawnable->StartBurrowed(); |
|
} |
|
|
|
if ( m_bStartBurrowed ) |
|
{ |
|
pSpawnable->SetUnburrowIdleActivity( m_UnburrowIdleActivity ); |
|
pSpawnable->SetUnburrowActivity( m_UnburrowActivity ); |
|
} |
|
|
|
DispatchSpawn( pEntity ); |
|
|
|
pEntity->SetOwnerEntity( this ); |
|
pEntity->Activate(); |
|
|
|
if ( m_AlienName != NULL_STRING ) |
|
{ |
|
pEntity->SetName( m_AlienName ); |
|
} |
|
|
|
pSpawnable->SetSpawner( this ); |
|
|
|
RemoveObstructingProps( pEntity ); |
|
|
|
// give our aliens the orders |
|
pSpawnable->SetAlienOrders( m_AlienOrders, vec3_origin, GetOrderTarget() ); |
|
|
|
m_OnSpawned.FireOutput(pEntity, this); |
|
|
|
return pSpawnable; |
|
} |
|
|
|
CBaseEntity* CASW_Base_Spawner::GetAlienOrderTarget() |
|
{ |
|
return m_hAlienOrderTarget.Get(); |
|
} |
|
|
|
bool CASW_Base_Spawner::IsValidOnThisSkillLevel() |
|
{ |
|
// treat difficulty 5 and 4 as the same |
|
int nSkillLevel = ASWGameRules()->GetSkillLevel(); |
|
nSkillLevel = clamp<int>( nSkillLevel, 1, 4 ); |
|
if (m_iMinSkillLevel > 0 && nSkillLevel < m_iMinSkillLevel) |
|
return false; |
|
if (m_iMaxSkillLevel > 0 && m_iMaxSkillLevel < 10 |
|
&& nSkillLevel > m_iMaxSkillLevel) |
|
return false; |
|
return true; |
|
} |
|
|
|
bool CASW_Base_Spawner::HasRecentlySpawned( float flRecent ) |
|
{ |
|
return m_flLastSpawnTime > 0.0f && ( ( gpGlobals->curtime - m_flLastSpawnTime ) < flRecent ); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Inputs |
|
//------------------------------------------------------------------------------ |
|
void CASW_Base_Spawner::InputToggleEnabled( inputdata_t &inputdata ) |
|
{ |
|
if ( !m_bEnabled ) |
|
{ |
|
InputEnable( inputdata ); |
|
} |
|
else |
|
{ |
|
InputDisable( inputdata ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CASW_Base_Spawner::InputEnable( inputdata_t &inputdata ) |
|
{ |
|
if ( !m_bEnabled ) |
|
{ |
|
m_bEnabled = true; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CASW_Base_Spawner::InputDisable( inputdata_t &inputdata ) |
|
{ |
|
if ( m_bEnabled ) |
|
{ |
|
m_bEnabled = false; |
|
} |
|
} |