//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//

// cs_simple_hostage.cpp
// Simple CS1.6 level hostage
// Author: Michael S. Booth and Matt Boone, July 2004

#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_player.h"
#include "cs_gamerules.h"
#include "game.h"
#include "bot.h"
#include <KeyValues.h>
#include "obstacle_pushaway.h"
#include "props_shared.h"

//=============================================================================
// HPE_BEGIN
//=============================================================================
// [dwenger] Necessary for stats tracking
#include "cs_gamestats.h"

//[tj] Necessary for fast rescue achievement
#include "cs_achievement_constants.h"
//=============================================================================
// HPE_END
//=============================================================================

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define HOSTAGE_THINK_INTERVAL	0.1f

#define DrawLine( from, to, duration, red, green, blue )		NDebugOverlay::Line( from, to, red, green, blue, true, 0.1f )
#define HOSTAGE_PUSHAWAY_THINK_CONTEXT	"HostagePushawayThink"

#define HOSTAGE_BBOX_VEC_MIN	Vector( -13, -13, 0 )
#define HOSTAGE_BBOX_VEC_MAX	Vector( 13, 13, 72 )


ConVar mp_hostagepenalty( "mp_hostagepenalty", "13", FCVAR_NOTIFY, "Terrorist are kicked for killing too much hostages" );
ConVar hostage_debug( "hostage_debug", "0", FCVAR_CHEAT, "Show hostage AI debug information" );

extern ConVar sv_pushaway_force;
extern ConVar sv_pushaway_min_player_speed;
extern ConVar sv_pushaway_max_force;

// We need hostage-specific pushaway cvars because the hostage doesn't have the same friction etc as players
ConVar sv_pushaway_hostage_force( "sv_pushaway_hostage_force", "20000", FCVAR_REPLICATED | FCVAR_CHEAT, "How hard the hostage is pushed away from physics objects (falls off with inverse square of distance)." );
ConVar sv_pushaway_max_hostage_force( "sv_pushaway_max_hostage_force", "1000", FCVAR_REPLICATED | FCVAR_CHEAT, "Maximum of how hard the hostage is pushed away from physics objects." );

const int NumHostageModels = 4;
static const char *HostageModel[NumHostageModels] = 
{
	"models/Characters/Hostage_01.mdl",
	"models/Characters/Hostage_02.mdl",
	"models/Characters/hostage_03.mdl",
	"models/Characters/hostage_04.mdl",
};

Vector DropToGround( CBaseEntity *pMainEnt, const Vector &vPos, const Vector &vMins, const Vector &vMaxs );

//-----------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( hostage_entity, CHostage );


//-----------------------------------------------------------------------------------------------------
BEGIN_DATADESC( CHostage )

	DEFINE_INPUTFUNC( FIELD_VOID, "OnRescueZoneTouch", HostageRescueZoneTouch ),

	DEFINE_USEFUNC( HostageUse ), 
	DEFINE_THINKFUNC( HostageThink ),

END_DATADESC()


//-----------------------------------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( CHostage, DT_CHostage )
	SendPropBool( SENDINFO(m_isRescued) ),
	SendPropInt( SENDINFO(m_iHealth), 10 ),
	SendPropInt( SENDINFO(m_iMaxHealth), 10 ),
	SendPropInt( SENDINFO(m_lifeState), 3, SPROP_UNSIGNED ),

	SendPropEHandle( SENDINFO(m_leader) ),

END_SEND_TABLE()


//-----------------------------------------------------------------------------------------------------
CUtlVector< CHostage * > g_Hostages;
static CountdownTimer announceTimer;		// used to stop "hostage rescued" announcements from stepping on each other


//-----------------------------------------------------------------------------------------------------
CHostage::CHostage()
{
	g_Hostages.AddToTail( this );
	m_PlayerAnimState = CreateHostageAnimState( this, this, LEGANIM_8WAY, false );
	SetBloodColor( BLOOD_COLOR_RED );
}

//-----------------------------------------------------------------------------------------------------
CHostage::~CHostage()
{
	g_Hostages.FindAndRemove( this );
	m_PlayerAnimState->Release();
}

CWeaponCSBase* CHostage::CSAnim_GetActiveWeapon()
{
	return NULL;
}

bool CHostage::CSAnim_CanMove()
{
	return true;
}

//-----------------------------------------------------------------------------------------------------
void CHostage::Spawn( void )
{
	Precache();

	// round-robin through the hostage models
	static int index = 0;
	int whichModel = index % NumHostageModels;
	++index;

	SetModel( HostageModel[ whichModel ] );


	SetHullType( HULL_HUMAN );

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );
	SetCollisionGroup( COLLISION_GROUP_PLAYER );

	SetGravity( 1.0 );

	m_iHealth = 100;	
	m_iMaxHealth = m_iHealth;
	m_takedamage = DAMAGE_YES;

	InitBoneControllers( ); 

	// we must set this, because its zero by default thus putting their eyes in their feet
	SetViewOffset( Vector( 0, 0, 60 ) );


	// set up think callback
	SetNextThink( gpGlobals->curtime + HOSTAGE_THINK_INTERVAL );
	SetThink( &CHostage::HostageThink );

	SetContextThink( &CHostage::PushawayThink, gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, HOSTAGE_PUSHAWAY_THINK_CONTEXT );

	SetUse( &CHostage::HostageUse );

	m_leader = NULL;
	m_reuseTimer.Invalidate();
	m_hasBeenUsed = false;

	m_isRescued = false;

	m_vel = Vector( 0, 0, 0 );
	m_accel = Vector( 0, 0, 0 );

	m_path.Invalidate();
	m_repathTimer.Invalidate();

	m_pathFollower.Reset();
	m_pathFollower.SetPath( &m_path );
	m_pathFollower.SetImprov( this );

	m_lastKnownArea = NULL;

	// Need to make sure the hostages are on the ground when they spawn
	Vector GroundPos = DropToGround( this, GetAbsOrigin(), HOSTAGE_BBOX_VEC_MIN, HOSTAGE_BBOX_VEC_MAX );
	SetAbsOrigin( GroundPos );

	if (TheNavMesh)
	{
		Vector pos = GetAbsOrigin();
		m_lastKnownArea = TheNavMesh->GetNearestNavArea( pos );
	}

	m_isCrouching = false;
	m_isRunning = true;
	m_jumpTimer.Invalidate();
	m_inhibitObstacleAvoidanceTimer.Invalidate();

	m_isWaitingForLeader = false;
	
	m_isAdjusted = false;

	m_lastLeaderID = 0;

	announceTimer.Invalidate();
	m_disappearTime = 0.0f;
}

//-----------------------------------------------------------------------------------------------------
void CHostage::Precache()
{
	for ( int i=0; i<NumHostageModels; ++i )
	{
		PrecacheModel( HostageModel[i] );
	}

	PrecacheScriptSound( "Hostage.StartFollowCT" );
	PrecacheScriptSound( "Hostage.StopFollowCT" );
	PrecacheScriptSound( "Hostage.Pain" );

	BaseClass::Precache();
}

//-----------------------------------------------------------------------------------------------------
int CHostage::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	float actualDamage = info.GetDamage();

	// say something
	EmitSound( "Hostage.Pain" );

	CCSPlayer *player = ToCSPlayer( info.GetAttacker() );

	if (player)
	{
        //=============================================================================
        // HPE_BEGIN
        // [dwenger] Track which player injured the hostage
        //=============================================================================

        player->SetInjuredAHostage(true);
		CSGameRules()->HostageInjured();

        //=============================================================================
        // HPE_END
        //=============================================================================


		if ( !( player->m_iDisplayHistoryBits & DHF_HOSTAGE_INJURED ) )
		{
			player->HintMessage( "#Hint_careful_around_hostages", FALSE );
			player->m_iDisplayHistoryBits |= DHF_HOSTAGE_INJURED;
		}

		IGameEvent *event = gameeventmanager->CreateEvent( "hostage_hurt" );
		if ( event )
		{
			event->SetInt( "userid", player->GetUserID() );
			event->SetInt( "hostage", entindex() );
			event->SetInt( "priority", 5 );

			gameeventmanager->FireEvent( event );
		}

		player->AddAccount( -((int)actualDamage * 20)  );
	}

	return BaseClass::OnTakeDamage_Alive( info );
}

//-----------------------------------------------------------------------------------------------------
/**
 * Modify damage the hostage takes by hitgroup
 */
float CHostage::GetModifiedDamage( float flDamage, int nHitGroup )
{
	switch ( nHitGroup )
	{
	case HITGROUP_GENERIC:	flDamage *=	1.75;	break;
	case HITGROUP_HEAD:		flDamage *=	2.5;	break;
	case HITGROUP_CHEST:	flDamage *=	1.5;	break;
	case HITGROUP_STOMACH:	flDamage *=	1.75;	break;
	case HITGROUP_LEFTARM:	flDamage *=	0.75;	break;
	case HITGROUP_RIGHTARM:	flDamage *=	0.75;	break;
	case HITGROUP_LEFTLEG:	flDamage *=	0.6;	break;
	case HITGROUP_RIGHTLEG:	flDamage *=	0.6;	break;
	default:				flDamage *=	1.5;	break;
	} 

	return flDamage;
}

//-----------------------------------------------------------------------------------------------------
void CHostage::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
	CTakeDamageInfo scaledInfo = info;
	scaledInfo.SetDamage( GetModifiedDamage( info.GetDamage(), ptr->hitgroup ) );
	BaseClass::TraceAttack( scaledInfo, vecDir, ptr, pAccumulator );
}

//-----------------------------------------------------------------------------------------------------
/**
 * Check for hostage-killer abuse
 */
void CHostage::CheckForHostageAbuse( CCSPlayer *player )
{
	int hostageKillLimit = mp_hostagepenalty.GetInt();

	if (hostageKillLimit > 0)
	{
		player->m_iHostagesKilled++;

		if ( player->m_iHostagesKilled == hostageKillLimit - 1 )
		{
			player->HintMessage( "#Hint_removed_for_next_hostage_killed", TRUE );
		}
		else if ( player->m_iHostagesKilled >= hostageKillLimit )
		{
			Msg( "Kicking client \"%s\" for killing too many hostages\n", player->GetPlayerName() );
			engine->ServerCommand( UTIL_VarArgs( "kickid %d \"For killing too many hostages\"\n", player->GetUserID() ) );
		}
	}
}


//-----------------------------------------------------------------------------------------------------
/**
 * Hostage was killed
 */
void CHostage::Event_Killed( const CTakeDamageInfo &info )
{
	// tell the game logic that we've died
	CSGameRules()->CheckWinConditions();

	//=============================================================================
	// HPE_BEGIN:
	// [tj] Let the game know that a hostage has been killed
	//=============================================================================
	CSGameRules()->HostageKilled();
	//=============================================================================
	// HPE_END
	//=============================================================================

	CCSPlayer *attacker = ToCSPlayer( info.GetAttacker() );

	if (attacker)
	{
		if ( !( attacker->m_iDisplayHistoryBits & DHF_HOSTAGE_KILLED ) )
		{
			attacker->HintMessage( "#Hint_lost_money", FALSE );
			attacker->m_iDisplayHistoryBits |= DHF_HOSTAGE_KILLED;
		}

		// monetary penalty for killing the hostage
		attacker->AddAccount( -( 500 + ((int)info.GetDamage() * 20) ) );

		// check for hostage-killer abuse
		if (attacker->GetTeamNumber() == TEAM_TERRORIST)
		{
			CheckForHostageAbuse( attacker );
		}
	}

	m_lastLeaderID = 0;

	SetUse( NULL );	
	BaseClass::Event_Killed( info );

	IGameEvent *event = gameeventmanager->CreateEvent("hostage_killed");
	if ( event )
	{
		event->SetInt( "userid", (attacker)?attacker->GetUserID():0 );
		event->SetInt( "hostage", entindex() );
		event->SetInt( "priority", 6 );
		gameeventmanager->FireEvent( event );
	}
}


//-----------------------------------------------------------------------------------------------------
/**
 * Invoked when a Hostage touches a Rescue Zone
 */
void CHostage::HostageRescueZoneTouch( inputdata_t &inputdata )
{
	if (!m_isRescued)
	{
		m_isRescued = true;
		m_lastLeaderID = 0;

		SetSolid( SOLID_NONE );
		SetSolidFlags( 0 );

		// start fading out
		m_disappearTime = gpGlobals->curtime + 3.0f;

		SetUse( NULL );
		m_takedamage = DAMAGE_NO;

		// give rescuer a cash bonus
		CCSPlayer *player = GetLeader();
		if (player)
		{
			const int rescuerCashBonus = 1000;
			player->AddAccount( rescuerCashBonus );
		}
		
		Idle();

		// tell the bots someone has rescued a hostage
		IGameEvent *event = gameeventmanager->CreateEvent( "hostage_rescued" );
		if ( event )
		{
			event->SetInt( "userid", player ? player->GetUserID() : (-1) );
			event->SetInt( "hostage", entindex() );
			event->SetInt( "site", 0 ); // TODO add site index
			event->SetInt( "priority", 9 );
			gameeventmanager->FireEvent( event );
		}

		// update game rules
		CSGameRules()->m_iHostagesRescued++;

        //=============================================================================
        // HPE_BEGIN
        // [dwenger] Hostage rescue achievement processing
        //=============================================================================

        // Track last rescuer
        if ( CSGameRules()->m_pLastRescuer == NULL )
        {
            // No rescuer yet, so assign one & set rescuer count to 1
            CSGameRules()->m_pLastRescuer = player;
            CSGameRules()->m_iNumRescuers = 1;
        }
        else
        {
            if ( CSGameRules()->m_pLastRescuer != player )
            {
                // Rescuer changed
                CSGameRules()->m_pLastRescuer = player;
                CSGameRules()->m_iNumRescuers++;
            }
        }

		bool roundIsAlreadyOver = (CSGameRules()->m_iRoundWinStatus != WINNER_NONE);

        //=============================================================================
        // HPE_END
        //=============================================================================

		if (CSGameRules()->CheckWinConditions() == false)
		{
			// this hostage didn't win the round, so announce its rescue to everyone
			if (announceTimer.IsElapsed())
			{
				CSGameRules()->BroadcastSound( "Event.HostageRescued" );
			}

			// avoid having the announcer talk over himself
			announceTimer.Start( 2.0f );
		}
        //=============================================================================
        // HPE_BEGIN
        // [dwenger] Awarding of hostage rescue achievement
        //=============================================================================

        else
        {
            //Check hostage rescue achievements
            if ( CSGameRules()->m_iNumRescuers == 1 && !CSGameRules()->WasHostageKilled())
            {
				//check for unrescued hostages
				bool allHostagesRescued = true;				
				CHostage* hostage = NULL;
				int iNumHostages = g_Hostages.Count();				

				for ( int i = 0 ; i < iNumHostages; i++ )
				{
					hostage = g_Hostages[i];

					if ( hostage->m_iHealth > 0 && !hostage->IsRescued() )
					{
						allHostagesRescued = false;
						break;
					}
				}

				if (allHostagesRescued)
				{
					player->AwardAchievement(CSRescueAllHostagesInARound);

					//[tj] fast version                
					if (gpGlobals->curtime - CSGameRules()->GetRoundStartTime() < AchievementConsts::FastHostageRescue_Time)
					{
						if ( player )
						{
							player->AwardAchievement(CSFastHostageRescue);
						}
					}
				}
            }
			//=============================================================================
			// HPE_BEGIN:
			// [menglish] If this rescue ended the round give an mvp to the rescuer
			//=============================================================================

            if ( player && !roundIsAlreadyOver)
            {
			    player->IncrementNumMVPs( CSMVP_HOSTAGERESCUE );
            }

			//=============================================================================
			// HPE_END
			//=============================================================================
        }

        if ( player )
        {
            CCS_GameStats.Event_HostageRescued( player );
        }

        //=============================================================================
        // HPE_END
        //=============================================================================
	}
}


//-----------------------------------------------------------------------------------------------------
/**
 * In contact with "other"
 */
void CHostage::Touch( CBaseEntity *other )
{
	BaseClass::Touch( other );

	// allow players and other hostages to push me around
	if ( ( other->IsPlayer() && other->GetTeamNumber() == TEAM_CT ) || FClassnameIs( other, "hostage_entity" ) )
	{
		// only push in 2D
		Vector to = GetAbsOrigin() - other->GetAbsOrigin();
		to.z = 0.0f;
		to.NormalizeInPlace();
		
		const float pushForce = 500.0f;
		ApplyForce( pushForce * to );
	}
	else if ( m_inhibitDoorTimer.IsElapsed() &&
		( other->ClassMatches( "func_door*" ) || other->ClassMatches( "prop_door*" ) ) )
	{
		m_inhibitDoorTimer.Start( 3.0f );
		other->Use( this, this, USE_TOGGLE, 0.0f );
	}
}


//-----------------------------------------------------------------------------------------------------
/** 
 * Hostage is stuck - attempt to wiggle out
 */
void CHostage::Wiggle( void )
{
	if (m_wiggleTimer.IsElapsed())
	{
		m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
		m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) );
	}

	Vector dir, lat;
	AngleVectors( GetAbsAngles(), &dir, &lat, NULL );

	const float speed = 500.0f;

	switch( m_wiggleDirection )
	{
		case LEFT:
			ApplyForce( speed * lat );
			break;

		case RIGHT:
			ApplyForce( -speed * lat );
			break;

		case FORWARD:
			ApplyForce( speed * dir );
			break;

		case BACKWARD:
			ApplyForce( -speed * dir );
			break;
	}

	const float minStuckJumpTime = 0.25f;
	if (m_pathFollower.GetStuckDuration() > minStuckJumpTime)
	{
		Jump();
	}
}


//-----------------------------------------------------------------------------------------------------
/**
 * Do following behavior
 */
void CHostage::UpdateFollowing( float deltaT )
{
	if ( !IsFollowingSomeone() && m_lastLeaderID != 0 )
	{
		// emit hostage_stops_following event
		IGameEvent *event = gameeventmanager->CreateEvent( "hostage_stops_following" );
		if ( event )
		{
			event->SetInt( "userid", m_lastLeaderID );
			event->SetInt( "hostage", entindex() );
			event->SetInt( "priority", 6 );
			gameeventmanager->FireEvent( event );
		}

		m_lastLeaderID = 0;
	}

	// if we have a leader, follow him
	CCSPlayer *leader = GetLeader();
	if (leader)
	{
		// if leader is dead, stop following him
		if (!leader->IsAlive())
		{
			Idle();
			return;
		}

		// if leader has moved, repath
		if (m_path.IsValid())
		{
			Vector pathError = leader->GetAbsOrigin() - m_path.GetEndpoint();
			
			const float repathRange = 100.0f;
			if (pathError.IsLengthGreaterThan( repathRange ))
			{
				m_path.Invalidate();
			}
		}


		// build a path to our leader
		if (!m_path.IsValid() && m_repathTimer.IsElapsed())
		{
			const float repathInterval = 0.5f;
			m_repathTimer.Start( repathInterval );

			Vector from = GetAbsOrigin();
			Vector to = leader->GetAbsOrigin();
			HostagePathCost pathCost;

			m_path.Compute( from, to, pathCost );
			m_pathFollower.Reset();
		}


		// if our rescuer is too far away, give up
		const float giveUpRange = 2000.0f;
		const float maxPathLength = 4000.0f;
		Vector toLeader = leader->GetAbsOrigin() - GetAbsOrigin();
		if (toLeader.IsLengthGreaterThan( giveUpRange ) || (m_path.IsValid() && m_path.GetLength() > maxPathLength))
		{
			if ( hostage_debug.GetInt() < 2 )
			{
				Idle();
			}
			return;
		}

		
		// don't crowd the leader
		if (m_isWaitingForLeader)
		{
			// we are close to our leader and waiting for him to move
			const float waitRange = 150.0f;
			if (toLeader.IsLengthGreaterThan( waitRange ))
			{
				// leader has moved away - follow him
				m_isWaitingForLeader = false;
			}

			// face the leader
			//FaceTowards( leader->GetAbsOrigin(), deltaT );
		}
		else
		{
			// we are far from our leader, and need to check if we're close enough to wait
			const float nearRange = 125.0f;

			if (toLeader.IsLengthLessThan( nearRange ))
			{
				// we are close to the leader - wait for him to move
				m_isWaitingForLeader = true;
			}
		}

		if (!m_isWaitingForLeader)
		{
			// move along path towards the leader
			m_pathFollower.Update( deltaT, m_inhibitObstacleAvoidanceTimer.IsElapsed() );

			if (hostage_debug.GetBool())
			{
				m_pathFollower.Debug( true );
			}

			if (m_pathFollower.IsStuck())
			{
				Wiggle();
			}

			if (hostage_debug.GetBool())
			{
				m_path.Draw();
			}
		}
	}
}



//-----------------------------------------------------------------------------------------------------
void CHostage::AvoidPhysicsProps( void )
{
	if ( m_lifeState == LIFE_DEAD )
		return;

	CBaseEntity *props[512];
	int nEnts = GetPushawayEnts( this, props, ARRAYSIZE( props ), 0.0f, PARTITION_ENGINE_SOLID_EDICTS );

	for ( int i=0; i < nEnts; i++ )
	{
		// Don't respond to this entity on the client unless it has PHYSICS_MULTIPLAYER_FULL set.
		IMultiplayerPhysics *pInterface = dynamic_cast<IMultiplayerPhysics*>( props[i] );
		if ( pInterface && pInterface->GetMultiplayerPhysicsMode() != PHYSICS_MULTIPLAYER_SOLID )
			continue;

		const float minMass = 10.0f; // minimum mass that can push a player back
		const float maxMass = 30.0f; // cap at a decently large value
		float mass = maxMass;
		if ( pInterface )
		{
			mass = pInterface->GetMass();
		}
		mass = MIN( mass, maxMass );
		mass -= minMass;
		mass = MAX( mass, 0 );
		mass /= (maxMass-minMass); // bring into a 0..1 range

		// Push away from the collision point. The closer our center is to the collision point,
		// the harder we push away.
		Vector vPushAway = (WorldSpaceCenter() - props[i]->WorldSpaceCenter());
		float flDist = VectorNormalize( vPushAway );
		flDist = MAX( flDist, 1 );

		float flForce = sv_pushaway_hostage_force.GetFloat() / flDist * mass;
		flForce = MIN( flForce, sv_pushaway_max_hostage_force.GetFloat() );
		vPushAway *= flForce;

		ApplyForce( vPushAway );
	}

	//
	// Handle step and ledge "step-up" movement here, before m_accel is zero'd
	//
	if ( !m_accel.IsZero() )
	{
		trace_t trace;
		Vector start = GetAbsOrigin();
		Vector forward = m_accel;
		forward.NormalizeInPlace();
		UTIL_TraceEntity( this, start, start + forward, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER, &trace );
		if ( !trace.startsolid && trace.fraction < 1.0f && trace.plane.normal.z < 0.7f )
		{
			float groundFraction = trace.fraction;
			start.z += StepHeight;
			UTIL_TraceEntity( this, start, start + forward, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER, &trace );
			if ( !trace.startsolid && trace.fraction > groundFraction )
			{
				SetAbsOrigin( start );
			}
		}
	}
}


//-----------------------------------------------------------------------------------------------------
/**
 * Push physics objects away from the hostage
 */
void CHostage::PushawayThink( void )
{
	PerformObstaclePushaway( this );
	SetNextThink( gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, HOSTAGE_PUSHAWAY_THINK_CONTEXT );
}

//-----------------------------------------------------------------------------------------------------
/**
 * @TODO imitate player movement:
 * MoveHelperServer()->SetHost( this );
 * this->PlayerRunCommand( &cmd, MoveHelperServer() );
 */
void CHostage::PhysicsSimulate( void )
{
	BaseClass::PhysicsSimulate();

	SetAbsVelocity( m_vel );
}

//-----------------------------------------------------------------------------------------------------
/**
 * Update Hostage behaviors
 */
void CHostage::HostageThink( void )
{
	if (!m_isAdjusted)
	{
		m_isAdjusted = true;

		// HACK - figure out why the default bbox is 6 units too low
		SetCollisionBounds( HOSTAGE_BBOX_VEC_MIN, HOSTAGE_BBOX_VEC_MAX );
	}

	const float deltaT = HOSTAGE_THINK_INTERVAL;
	SetNextThink( gpGlobals->curtime + deltaT );

	// keep track of which Navigation Area we are in (or were in, if we're "off the mesh" right now)
	CNavArea *area = TheNavMesh->GetNavArea( GetAbsOrigin() );
	if (area != NULL && area != m_lastKnownArea)
	{
		// entered a new nav area
		m_lastKnownArea = area;
	}

	// do leader-following behavior, if necessary
	UpdateFollowing( deltaT );

	AvoidPhysicsProps();

	// update hostage velocity in the XY plane
	const float damping = 2.0f;
	m_vel += deltaT * (m_accel - damping * m_vel);

	// leave Z component untouched
	m_vel.z = GetAbsVelocity().z;

	if ( m_accel.IsZero() && m_vel.AsVector2D().IsZero( 1.0f ) )
	{
		m_vel.x = 0.0f;
		m_vel.y = 0.0f;
	}

	m_accel = Vector( 0, 0, 0 );

	// set animation to idle for now
	StudioFrameAdvance();

	int sequence = SelectWeightedSequence( ACT_IDLE );
	if (GetSequence() != sequence)
	{
		SetSequence( sequence );
	}

	m_PlayerAnimState->Update( GetAbsAngles()[YAW], GetAbsAngles()[PITCH] );


	if ( m_disappearTime && m_disappearTime < gpGlobals->curtime )
	{
		// finished fading - remove us completely
		AddEffects( EF_NODRAW );

		SetSolid( SOLID_NONE );
		SetSolidFlags( 0 );
		m_disappearTime = 0.0f;
	}
}

//-----------------------------------------------------------------------------------------------------
bool CHostage::IsFollowingSomeone( void )
{
	return (m_leader.m_Value != NULL);
}

//-----------------------------------------------------------------------------------------------------
bool CHostage::IsFollowing( const CBaseEntity *entity )
{
	return (m_leader.m_Value == entity);
}

//-----------------------------------------------------------------------------------------------------
bool CHostage::IsValid( void ) const
{
	return (m_iHealth > 0 && !IsRescued());
}

//-----------------------------------------------------------------------------------------------------
bool CHostage::IsRescuable( void ) const
{
	return (m_iHealth > 0 && !IsRescued());
}

//-----------------------------------------------------------------------------------------------------
bool CHostage::IsRescued( void ) const
{
	return m_isRescued;
}

//-----------------------------------------------------------------------------------------------------
bool CHostage::IsOnGround( void ) const
{
	return (GetFlags() & FL_ONGROUND);
}

//-----------------------------------------------------------------------------------------------------
/**
 * Return true if hostage can see position
 */
bool CHostage::IsVisible( const Vector &pos, bool testFOV ) const
{
	trace_t result;
	UTIL_TraceLine( EyePosition(), pos, CONTENTS_SOLID, this, COLLISION_GROUP_NONE, &result );
	return (result.fraction >= 1.0f);
}

//-----------------------------------------------------------------------------------------------------
/**
 * Give bonus to CT's for talking to a hostage
 */
void CHostage::GiveCTUseBonus( CCSPlayer *rescuer )
{
	// money to team
	const int teamBonus = 100;
	CSGameRules()->m_iAccountCT += teamBonus;

	// money to rescuer
	const int rescuerBonus = 150;
	rescuer->AddAccount( rescuerBonus );
}

//-----------------------------------------------------------------------------------------------------
/**
 * Stand idle
 */
void CHostage::Idle( void )
{
	m_leader = NULL;
}

//-----------------------------------------------------------------------------------------------------
/**
 * Begin following "leader"
 */
void CHostage::Follow( CCSPlayer *leader )
{
    //=============================================================================
    // HPE_BEGIN
    // [dwenger] Set variable to track whether player is currently rescuing hostages
    //=============================================================================

    if ( leader )
    {
        leader->IncrementNumFollowers();
        leader->SetIsRescuing(true);
    }

    //=============================================================================
    // HPE_END
    //=============================================================================

	m_leader = leader;
	m_isWaitingForLeader = false;
	m_lastLeaderID = (leader) ? leader->GetUserID() : 0;
}


//-----------------------------------------------------------------------------------------------------
/**
 * Return our leader, or NULL
 */
CCSPlayer *CHostage::GetLeader( void ) const
{
	return ToCSPlayer( m_leader.m_Value );
}


//-----------------------------------------------------------------------------------------------------
/**
 * Invoked when a Hostage is "used" by a player
 */
void CHostage::HostageUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	Vector to = pActivator->GetAbsOrigin() - GetAbsOrigin();

	// limit use range
	float useRange = 1000.0f;
	if (to.IsLengthGreaterThan( useRange ))
	{
		return;
	}

	// TODO: check line of sight to hostage


	CCSPlayer *user = ToCSPlayer( pActivator );
	if (user == NULL)
	{
		return;
	}

	// only members of the CT team can use hostages (no T's or spectators)
	if (!hostage_debug.GetBool() && user->GetTeamNumber() != TEAM_CT)
	{
		if ( user->GetTeamNumber() == TEAM_TERRORIST )
		{
			if ( !(user->m_iDisplayHistoryBits & DHF_HOSTAGE_CTMOVE) )
			{
				user->m_iDisplayHistoryBits |= DHF_HOSTAGE_CTMOVE;
				user->HintMessage( "#Only_CT_Can_Move_Hostages", false, true );
			}
		}

		return;
	}

	CCSPlayer *leader = GetLeader();
	if( leader && !leader->IsAlive() )
	{
		Idle();
		leader = NULL;
	}

	// throttle how often leader can change
	if (!m_reuseTimer.IsElapsed())
	{
		return;
	}

	// give money bonus to first CT touching this hostage
	if (!m_hasBeenUsed)
	{
		m_hasBeenUsed = true;

		GiveCTUseBonus( user );

		CSGameRules()->HostageTouched();
	}

	// if we are already following the player who used us, stop following
	if (IsFollowing( user ))
	{
		Idle();

		// say something
		EmitSound( "Hostage.StopFollowCT" );
	}
	else
	{
		// if we're already following a CT, ignore new uses
		if (IsFollowingSomeone())
		{
			return;
		}

		// start following
		Follow( user );

		// say something
		EmitSound( "Hostage.StartFollowCT" );

		// emit hostage_follows event
		IGameEvent *event = gameeventmanager->CreateEvent( "hostage_follows" );
		if ( event )
		{
			event->SetInt( "userid", user->GetUserID() );
			event->SetInt( "hostage", entindex() );
			event->SetInt( "priority", 6 );
			gameeventmanager->FireEvent( event );
		}

		if ( !(user->m_iDisplayHistoryBits & DHF_HOSTAGE_USED) )
		{
			user->m_iDisplayHistoryBits |= DHF_HOSTAGE_USED;
			user->HintMessage( "#Hint_lead_hostage_to_rescue_point", false );
		}
	}

	m_reuseTimer.Start( 1.0f );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Rotate body to face towards "target"
 */
void CHostage::FaceTowards( const Vector &target, float deltaT )
{
	Vector to = target - GetFeet();
	to.z = 0.0f;

	QAngle desiredAngles;
	VectorAngles( to, desiredAngles );

	QAngle angles = GetAbsAngles();

	const float turnSpeed = 250.0f;	
	angles.y = ApproachAngle( desiredAngles.y, angles.y, turnSpeed * deltaT );

	SetAbsAngles( angles );
}

//-----------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------

const Vector &CHostage::GetCentroid( void ) const
{
	static Vector centroid;
	
	centroid = GetFeet();
	centroid.z += HalfHumanHeight;

	return centroid;
}

//-----------------------------------------------------------------------------------------------------
/**
 * Return position of "feet" - point below centroid of improv at feet level
 */
const Vector &CHostage::GetFeet( void ) const
{
	static Vector feet;
	
	feet = GetAbsOrigin();

	return feet;
}

//-----------------------------------------------------------------------------------------------------
const Vector &CHostage::GetEyes( void ) const
{
	static Vector eyes;
	
	eyes = EyePosition();

	return eyes;
}

//-----------------------------------------------------------------------------------------------------
/**
 * Return direction of movement
 */
float CHostage::GetMoveAngle( void ) const
{
	return GetAbsAngles().y;
}

//-----------------------------------------------------------------------------------------------------
CNavArea *CHostage::GetLastKnownArea( void ) const
{
	return m_lastKnownArea;
}

//-----------------------------------------------------------------------------------------------------
/**
 * Find "simple" ground height, treating current nav area as part of the floo
 */
bool CHostage::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
{
	if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
	{
		// our current nav area also serves as a ground polygon
		if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
		{
			*height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
		}

		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------------------------------
void CHostage::Crouch( void )
{
	m_isCrouching = true;
}

//-----------------------------------------------------------------------------------------------------
/**
 * un-crouch
 */
void CHostage::StandUp( void )
{
	m_isCrouching = false;
}

//-----------------------------------------------------------------------------------------------------
bool CHostage::IsCrouching( void ) const
{
	return m_isCrouching;
}

//-----------------------------------------------------------------------------------------------------
/**
 * Initiate a jump
 */
void CHostage::Jump( void )
{
	if (m_jumpTimer.IsElapsed() && IsOnGround())
	{
		const float minJumpInterval = 0.5f;
		m_jumpTimer.Start( minJumpInterval );

		Vector vel = GetAbsVelocity();
		vel.z += 200.0f;
		SetAbsVelocity( vel );
	}
}


//-----------------------------------------------------------------------------------------------------
bool CHostage::IsJumping( void ) const
{
	return !m_jumpTimer.IsElapsed();
}

//-----------------------------------------------------------------------------------------------------
/**
 * Set movement speed to running
 */
void CHostage::Run( void )
{
	m_isRunning = true;
}


//-----------------------------------------------------------------------------------------------------
/**
 * Set movement speed to walking
 */
void CHostage::Walk( void )
{
	m_isRunning = false;
}


//-----------------------------------------------------------------------------------------------------
bool CHostage::IsRunning( void ) const
{
	return m_isRunning;
}

//-----------------------------------------------------------------------------------------------------
/**
 * Invoked when a ladder is encountered while following a path
 */
void CHostage::StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos )
{
}

//-----------------------------------------------------------------------------------------------------
/**
 * Traverse given ladder
 */
bool CHostage::TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT )
{
	return false;
}

//-----------------------------------------------------------------------------------------------------
bool CHostage::IsUsingLadder( void ) const
{
	return false;
}

//-----------------------------------------------------------------------------------------------------
/**
 * Move Hostage directly toward "pathGoal", causing Hostage to track the current path.
 */
void CHostage::TrackPath( const Vector &pathGoal, float deltaT )
{
	// face in the direction of our motion
	FaceTowards( GetAbsOrigin() + 10.0f * m_vel, deltaT );

	if (GetFlags() & FL_ONGROUND)
	{
		// on the ground - move towards pathGoal
		Vector to = pathGoal - GetFeet();
		to.z = 0.0f;
		to.NormalizeInPlace();

		const float speed = 1000.0f;
		ApplyForce( speed * to );
	}
	else
	{
		// in the air - continue forward motion
		Vector to;
		QAngle angles = GetAbsAngles();
		AngleVectors( angles, &to );

		const float airSpeed = 350.0f;
		ApplyForce( airSpeed * to );
	}
}

//-----------------------------------------------------------------------------------------------------
/**
 * Invoked when an improv reaches its MoveTo goal
 */
void CHostage::OnMoveToSuccess( const Vector &goal )
{
}

//-----------------------------------------------------------------------------------------------------
/**
 * Invoked when an improv fails to reach a MoveTo goal
 */
void CHostage::OnMoveToFailure( const Vector &goal, MoveToFailureType reason )
{
}


unsigned int CHostage::PhysicsSolidMaskForEntity() const
{
	return MASK_PLAYERSOLID;
}

