//========= 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; iHasPurgatoryBuff() ) { 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; }