text: //========= 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;
  }
  