Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

1345 lines
36 KiB

//========= 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 )
SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
// cs_playeranimstate and clientside animation takes care of these on the client
SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
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 );
UseClientSideAnimation();
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;
}