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.
331 lines
9.1 KiB
331 lines
9.1 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// ghost.cpp |
|
// A spooky halloween ghost bot |
|
// Michael Booth, October 2011 |
|
|
|
#include "cbase.h" |
|
|
|
#include "tf_player.h" |
|
#include "tf_gamerules.h" |
|
#include "tf_team.h" |
|
#include "tf_projectile_arrow.h" |
|
#include "tf_weapon_grenade_pipebomb.h" |
|
#include "nav_mesh/tf_nav_area.h" |
|
#include "ghost.h" |
|
|
|
#include "NextBot/Path/NextBotChasePath.h" |
|
#include "econ_wearable.h" |
|
#include "team_control_point_master.h" |
|
#include "particle_parse.h" |
|
#include "CRagdollMagnet.h" |
|
#include "NextBot/Behavior/BehaviorMoveTo.h" |
|
|
|
|
|
void CC_GhostSpawn( const CCommand& args ) |
|
{ |
|
MDLCACHE_CRITICAL_SECTION(); |
|
|
|
CBaseEntity *entity = CreateEntityByName( "ghost" ); |
|
if ( entity ) |
|
{ |
|
entity->Precache(); |
|
DispatchSpawn( entity ); |
|
|
|
// Now attempt to drop into the world |
|
CBasePlayer* pPlayer = UTIL_GetCommandClient(); |
|
trace_t tr; |
|
Vector forward; |
|
pPlayer->EyeVectors( &forward ); |
|
UTIL_TraceLine(pPlayer->EyePosition(), |
|
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID, |
|
pPlayer, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction != 1.0 ) |
|
{ |
|
// Raise the end position a little up off the floor, place the npc and drop him down |
|
tr.endpos.z += 12; |
|
entity->Teleport( &tr.endpos, NULL, NULL ); |
|
} |
|
} |
|
} |
|
static ConCommand ghost_spawn( "ghost_spawn", CC_GhostSpawn, "Spawns a Ghost where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT ); |
|
|
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
CGhost *SpawnGhost( const Vector &spot, const QAngle &angles, float lifetime ) |
|
{ |
|
CGhost *ghost = (CGhost *)CreateEntityByName( "ghost" ); |
|
if ( ghost ) |
|
{ |
|
DispatchSpawn( ghost ); |
|
|
|
ghost->SetAbsOrigin( spot ); |
|
ghost->SetLocalAngles( angles ); |
|
ghost->SetLifetime( lifetime ); |
|
|
|
return ghost; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
// The Ghost |
|
//----------------------------------------------------------------------------------------------------- |
|
LINK_ENTITY_TO_CLASS( ghost, CGhost ); |
|
|
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
CGhost::CGhost() |
|
{ |
|
ALLOCATE_INTENTION_INTERFACE( CGhost ); |
|
|
|
m_locomotor = new CGhostLocomotion( this ); |
|
|
|
m_eyeOffset = vec3_origin; |
|
m_lifetime = 10.0f; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
CGhost::~CGhost() |
|
{ |
|
DEALLOCATE_INTENTION_INTERFACE; |
|
|
|
if ( m_locomotor ) |
|
delete m_locomotor; |
|
} |
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
void CGhost::PrecacheGhost() |
|
{ |
|
PrecacheModel( "models/props_halloween/ghost_no_hat.mdl" ); |
|
PrecacheParticleSystem( "ghost_appearation" ); |
|
PrecacheScriptSound( "Halloween.GhostMoan" ); |
|
PrecacheScriptSound( "Halloween.GhostBoo" ); |
|
PrecacheScriptSound( "Halloween.Haunted" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
void CGhost::Precache() |
|
{ |
|
BaseClass::Precache(); |
|
|
|
// always allow late precaching, so we don't pay the cost of the |
|
// Halloween Ghost for the entire year |
|
|
|
bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed(); |
|
CBaseEntity::SetAllowPrecache( true ); |
|
|
|
PrecacheGhost(); |
|
|
|
CBaseEntity::SetAllowPrecache( bAllowPrecache ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
void CGhost::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
BaseClass::Spawn(); |
|
|
|
SetCollisionGroup( COLLISION_GROUP_NONE ); |
|
SetSolid( SOLID_NONE ); |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
SetModel( "models/props_halloween/ghost_no_hat.mdl" ); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
bool CGhost::ShouldCollide( int collisionGroup, int contentsMask ) const |
|
{ |
|
return false; |
|
//return BaseClass::ShouldCollide( collisionGroup, contentsMask ); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
//--------------------------------------------------------------------------------------------- |
|
class CGhostBehavior : public Action< CGhost > |
|
{ |
|
public: |
|
//--------------------------------------------------------------------------------------------- |
|
virtual ActionResult< CGhost > OnStart( CGhost *me, Action< CGhost > *priorAction ) |
|
{ |
|
m_lifeTimer.Start(); |
|
m_stuckAnchor = me->GetAbsOrigin(); |
|
m_stuckTimer.Start( 1.0f ); |
|
|
|
me->GetVectors( &m_forward, NULL, NULL ); |
|
|
|
DispatchParticleEffect( "ghost_appearation", me->WorldSpaceCenter(), me->GetAbsAngles() ); |
|
|
|
return Continue(); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
virtual ActionResult< CGhost > Update( CGhost *me, float interval ) |
|
{ |
|
if ( m_lifeTimer.IsGreaterThen( me->GetLifetime() ) || m_stuckTimer.IsElapsed() ) |
|
{ |
|
DispatchParticleEffect( "ghost_appearation", me->WorldSpaceCenter(), me->GetAbsAngles() ); |
|
|
|
me->EmitSound( "Halloween.Haunted" ); |
|
|
|
UTIL_Remove( me ); |
|
return Done(); |
|
} |
|
|
|
if ( m_moanTimer.IsElapsed() ) |
|
{ |
|
me->EmitSound( "Halloween.GhostMoan" ); |
|
m_moanTimer.Start( RandomFloat( 5.0f, 7.0f ) ); |
|
} |
|
|
|
DriftAroundAndAvoidObstacles( me ); |
|
ScareNearbyPlayers( me ); |
|
|
|
return Continue(); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
void DriftAroundAndAvoidObstacles( CGhost *me ) |
|
{ |
|
const float feelerRange = 150.0f; |
|
|
|
Vector left( -m_forward.y, m_forward.x, 0.0f ); |
|
Vector right( m_forward.y, -m_forward.x, 0.0f ); |
|
|
|
CTraceFilterNoNPCsOrPlayer traceFilter( me, COLLISION_GROUP_NONE ); |
|
trace_t resultLeft; |
|
UTIL_TraceLine( me->WorldSpaceCenter(), me->WorldSpaceCenter() + feelerRange * ( m_forward + left ), MASK_PLAYERSOLID, &traceFilter, &resultLeft ); |
|
//NDebugOverlay::Line( me->WorldSpaceCenter(), me->WorldSpaceCenter() + feelerRange * ( m_forward + left ), 0, 0, 255, true, interval ); |
|
|
|
trace_t resultRight; |
|
UTIL_TraceLine( me->WorldSpaceCenter(), me->WorldSpaceCenter() + feelerRange * ( m_forward + right ), MASK_PLAYERSOLID, &traceFilter, &resultRight ); |
|
//NDebugOverlay::Line( me->WorldSpaceCenter(), me->WorldSpaceCenter() + feelerRange * ( m_forward + right ), 255, 0, 0, true, interval ); |
|
|
|
const float turnRate = 0.2f; |
|
|
|
if ( resultLeft.DidHit() ) |
|
{ |
|
if ( resultRight.DidHit() ) |
|
{ |
|
// both sides hit |
|
if ( resultLeft.fraction < resultRight.fraction ) |
|
{ |
|
// left hit closer - turn right |
|
m_forward += turnRate * right; |
|
} |
|
else |
|
{ |
|
// right hit closer - turn left |
|
m_forward += turnRate * left; |
|
} |
|
} |
|
else |
|
{ |
|
// left hit - turn right |
|
m_forward += turnRate * right; |
|
} |
|
} |
|
else if ( resultRight.DidHit() ) |
|
{ |
|
// right hit - turn left |
|
m_forward += turnRate * left; |
|
} |
|
|
|
m_forward.NormalizeInPlace(); |
|
|
|
Vector goal = 100.0f * m_forward + me->GetAbsOrigin(); |
|
|
|
me->GetLocomotionInterface()->Approach( goal ); |
|
me->GetLocomotionInterface()->FaceTowards( goal ); |
|
me->GetLocomotionInterface()->Run(); |
|
|
|
if ( me->IsRangeGreaterThan( m_stuckAnchor, 50.0f ) ) |
|
{ |
|
m_stuckAnchor = me->GetAbsOrigin(); |
|
m_stuckTimer.Reset(); |
|
} |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
void ScareNearbyPlayers( CGhost *me ) |
|
{ |
|
if ( m_scareTimer.IsElapsed() ) |
|
{ |
|
m_scareTimer.Start( 1.0f ); |
|
|
|
CUtlVector< CTFPlayer * > playerVector; |
|
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS ); |
|
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS ); |
|
|
|
for( int i=0; i<playerVector.Count(); ++i ) |
|
{ |
|
CTFPlayer *victim = playerVector[i]; |
|
|
|
if ( victim && !victim->HasPurgatoryBuff() ) |
|
{ |
|
if ( me->IsRangeLessThan( victim, GHOST_SCARE_RADIUS ) ) |
|
{ |
|
if ( me->IsLineOfSightClear( victim ) ) |
|
{ |
|
// scare them! |
|
const float scareTime = 2.0f; |
|
const float speedReduction = 0.0f; |
|
|
|
// "stun by trigger" results in the Halloween "yikes" effects |
|
int stunFlags = TF_STUN_LOSER_STATE | TF_STUN_BY_TRIGGER; |
|
victim->m_Shared.StunPlayer( scareTime, speedReduction, stunFlags, NULL ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
virtual const char *GetName( void ) const { return "Behavior"; } // return name of this action |
|
|
|
private: |
|
IntervalTimer m_lifeTimer; |
|
CountdownTimer m_moanTimer; |
|
CountdownTimer m_scareTimer; |
|
Vector m_forward; |
|
Vector m_stuckAnchor; |
|
CountdownTimer m_stuckTimer; |
|
}; |
|
|
|
|
|
IMPLEMENT_INTENTION_INTERFACE( CGhost, CGhostBehavior ); |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
//--------------------------------------------------------------------------------------------- |
|
// Get maximum running speed |
|
float CGhostLocomotion::GetRunSpeed( void ) const |
|
{ |
|
return 90.0f; |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
// Return maximum acceleration of locomotor |
|
float CGhostLocomotion::GetMaxAcceleration( void ) const |
|
{ |
|
return 500.0f; |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
// Return maximum deceleration of locomotor |
|
float CGhostLocomotion::GetMaxDeceleration( void ) const |
|
{ |
|
return 500.0f; |
|
}
|
|
|