|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "weapon_csbase.h"
#include "decals.h"
#include "cs_gamerules.h"
#include "weapon_c4.h"
#include "in_buttons.h"
#include "datacache/imdlcache.h"
#ifdef CLIENT_DLL
#include "c_cs_player.h"
#else
#include "cs_player.h"
#include "soundent.h"
#include "bot/cs_bot.h"
#include "KeyValues.h"
#include "triggers.h"
#include "cs_gamestats.h"
#endif
#include "cs_playeranimstate.h"
#include "basecombatweapon_shared.h"
#include "util_shared.h"
#include "takedamageinfo.h"
#include "effect_dispatch_data.h"
#include "engine/ivdebugoverlay.h"
#include "obstacle_pushaway.h"
#include "props_shared.h"
ConVar sv_showimpacts("sv_showimpacts", "0", FCVAR_REPLICATED, "Shows client (red) and server (blue) bullet impact point (1=both, 2=client-only, 3=server-only)" ); ConVar sv_showplayerhitboxes( "sv_showplayerhitboxes", "0", FCVAR_REPLICATED, "Show lag compensated hitboxes for the specified player index whenever a player fires." );
#define CS_MASK_SHOOT (MASK_SOLID|CONTENTS_DEBRIS)
void DispatchEffect( const char *pName, const CEffectData &data );
#ifdef _DEBUG
// This is some extra code to collect weapon accuracy stats:
struct bulletdata_s { float timedelta; // time delta since first shot of this round
float derivation; // derivation for first shoot view angle
int count; };
#define STATS_MAX_BULLETS 50
static bulletdata_s s_bullet_stats[STATS_MAX_BULLETS];
Vector s_firstImpact = Vector(0,0,0); float s_firstTime = 0; float s_LastTime = 0; int s_bulletCount = 0;
void ResetBulletStats() { s_firstTime = 0; s_LastTime = 0; s_bulletCount = 0; s_firstImpact = Vector(0,0,0); Q_memset( s_bullet_stats, 0, sizeof(s_bullet_stats) ); }
void PrintBulletStats() { for (int i=0; i<STATS_MAX_BULLETS; i++ ) { if (s_bullet_stats[i].count == 0) break;
Msg("%3i;%3i;%.4f;%.4f\n", i, s_bullet_stats[i].count, s_bullet_stats[i].timedelta, s_bullet_stats[i].derivation ); } }
void AddBulletStat( float time, float dist, Vector &impact ) { if ( time > s_LastTime + 2.0f ) { // time delta since last shoot is bigger than 2 seconds, start new row
s_LastTime = s_firstTime = time; s_bulletCount = 0; s_firstImpact = impact;
} else { s_LastTime = time; s_bulletCount++; }
if ( s_bulletCount >= STATS_MAX_BULLETS ) s_bulletCount = STATS_MAX_BULLETS -1;
if ( dist < 1 ) dist = 1;
int i = s_bulletCount;
float offset = VectorLength( s_firstImpact - impact );
float timedelta = time - s_firstTime; float derivation = offset / dist;
float weight = (float)s_bullet_stats[i].count/(float)(s_bullet_stats[i].count+1);
s_bullet_stats[i].timedelta *= weight; s_bullet_stats[i].timedelta += (1.0f-weight) * timedelta;
s_bullet_stats[i].derivation *= weight; s_bullet_stats[i].derivation += (1.0f-weight) * derivation;
s_bullet_stats[i].count++; }
CON_COMMAND( stats_bullets_reset, "Reset bullet stats") { ResetBulletStats(); }
CON_COMMAND( stats_bullets_print, "Print bullet stats") { PrintBulletStats(); }
#endif
float CCSPlayer::GetPlayerMaxSpeed() { if ( GetMoveType() == MOVETYPE_NONE ) { return CS_PLAYER_SPEED_STOPPED; }
if ( IsObserver() ) { // Player gets speed bonus in observer mode
return CS_PLAYER_SPEED_OBSERVER; }
bool bValidMoveState = ( State_Get() == STATE_ACTIVE || State_Get() == STATE_OBSERVER_MODE ); if ( !bValidMoveState || m_bIsDefusing || CSGameRules()->IsFreezePeriod() ) { // Player should not move during the freeze period
return CS_PLAYER_SPEED_STOPPED; }
float speed = BaseClass::GetPlayerMaxSpeed();
if ( IsVIP() == true ) // VIP is slow due to the armour he's wearing
{ speed = MIN(speed, CS_PLAYER_SPEED_VIP); } else {
CWeaponCSBase *pWeapon = dynamic_cast<CWeaponCSBase*>( GetActiveWeapon() );
if ( pWeapon ) { if ( HasShield() && IsShieldDrawn() ) { speed = MIN(speed, CS_PLAYER_SPEED_SHIELD); } else { speed = MIN(speed, pWeapon->GetMaxSpeed()); } } }
return speed; }
void CCSPlayer::GetBulletTypeParameters( int iBulletType, float &fPenetrationPower, float &flPenetrationDistance ) { //MIKETODO: make ammo types come from a script file.
if ( IsAmmoType( iBulletType, BULLET_PLAYER_50AE ) ) { fPenetrationPower = 30; flPenetrationDistance = 1000.0; } else if ( IsAmmoType( iBulletType, BULLET_PLAYER_762MM ) ) { fPenetrationPower = 39; flPenetrationDistance = 5000.0; } else if ( IsAmmoType( iBulletType, BULLET_PLAYER_556MM ) || IsAmmoType( iBulletType, BULLET_PLAYER_556MM_BOX ) ) { fPenetrationPower = 35; flPenetrationDistance = 4000.0; } else if ( IsAmmoType( iBulletType, BULLET_PLAYER_338MAG ) ) { fPenetrationPower = 45; flPenetrationDistance = 8000.0; } else if ( IsAmmoType( iBulletType, BULLET_PLAYER_9MM ) ) { fPenetrationPower = 21; flPenetrationDistance = 800.0; } else if ( IsAmmoType( iBulletType, BULLET_PLAYER_BUCKSHOT ) ) { fPenetrationPower = 0; flPenetrationDistance = 0.0; } else if ( IsAmmoType( iBulletType, BULLET_PLAYER_45ACP ) ) { fPenetrationPower = 15; flPenetrationDistance = 500.0; } else if ( IsAmmoType( iBulletType, BULLET_PLAYER_357SIG ) ) { fPenetrationPower = 25; flPenetrationDistance = 800.0; } else if ( IsAmmoType( iBulletType, BULLET_PLAYER_57MM ) ) { fPenetrationPower = 30; flPenetrationDistance = 2000.0; } else { // What kind of ammo is this?
Assert( false ); fPenetrationPower = 0; flPenetrationDistance = 0.0; } }
static void GetMaterialParameters( int iMaterial, float &flPenetrationModifier, float &flDamageModifier ) { switch ( iMaterial ) { case CHAR_TEX_METAL : flPenetrationModifier = 0.5; // If we hit metal, reduce the thickness of the brush we can't penetrate
flDamageModifier = 0.3; break; case CHAR_TEX_DIRT : flPenetrationModifier = 0.5; flDamageModifier = 0.3; break; case CHAR_TEX_CONCRETE : flPenetrationModifier = 0.4; flDamageModifier = 0.25; break; case CHAR_TEX_GRATE : flPenetrationModifier = 1.0; flDamageModifier = 0.99; break; case CHAR_TEX_VENT : flPenetrationModifier = 0.5; flDamageModifier = 0.45; break; case CHAR_TEX_TILE : flPenetrationModifier = 0.65; flDamageModifier = 0.3; break; case CHAR_TEX_COMPUTER : flPenetrationModifier = 0.4; flDamageModifier = 0.45; break; case CHAR_TEX_WOOD : flPenetrationModifier = 1.0; flDamageModifier = 0.6; break; default : flPenetrationModifier = 1.0; flDamageModifier = 0.5; break; }
Assert( flPenetrationModifier > 0 ); Assert( flDamageModifier < 1.0f ); // Less than 1.0f for avoiding infinite loops
}
static bool TraceToExit(Vector &start, Vector &dir, Vector &end, float flStepSize, float flMaxDistance ) { float flDistance = 0; Vector last = start;
while ( flDistance <= flMaxDistance ) { flDistance += flStepSize;
end = start + flDistance *dir;
if ( (UTIL_PointContents ( end ) & MASK_SOLID) == 0 ) { // found first free point
return true; } }
return false; }
inline void UTIL_TraceLineIgnoreTwoEntities( const Vector& vecAbsStart, const Vector& vecAbsEnd, unsigned int mask, const IHandleEntity *ignore, const IHandleEntity *ignore2, int collisionGroup, trace_t *ptr ) { Ray_t ray; ray.Init( vecAbsStart, vecAbsEnd ); CTraceFilterSkipTwoEntities traceFilter( ignore, ignore2, collisionGroup ); enginetrace->TraceRay( ray, mask, &traceFilter, ptr ); if( r_visualizetraces.GetBool() ) { DebugDrawLine( ptr->startpos, ptr->endpos, 255, 0, 0, true, -1.0f ); } }
void CCSPlayer::FireBullet( Vector vecSrc, // shooting postion
const QAngle &shootAngles, //shooting angle
float flDistance, // max distance
int iPenetration, // how many obstacles can be penetrated
int iBulletType, // ammo type
int iDamage, // base damage
float flRangeModifier, // damage range modifier
CBaseEntity *pevAttacker, // shooter
bool bDoEffects, float xSpread, float ySpread ) { float fCurrentDamage = iDamage; // damage of the bullet at it's current trajectory
float flCurrentDistance = 0.0; //distance that the bullet has traveled so far
Vector vecDirShooting, vecRight, vecUp; AngleVectors( shootAngles, &vecDirShooting, &vecRight, &vecUp );
// MIKETODO: put all the ammo parameters into a script file and allow for CS-specific params.
float flPenetrationPower = 0; // thickness of a wall that this bullet can penetrate
float flPenetrationDistance = 0; // distance at which the bullet is capable of penetrating a wall
float flDamageModifier = 0.5; // default modification of bullets power after they go through a wall.
float flPenetrationModifier = 1.f;
GetBulletTypeParameters( iBulletType, flPenetrationPower, flPenetrationDistance );
if ( !pevAttacker ) pevAttacker = this; // the default attacker is ourselves
// add the spray
Vector vecDir = vecDirShooting + xSpread * vecRight + ySpread * vecUp;
VectorNormalize( vecDir );
//Adrian: visualize server/client player positions
//This is used to show where the lag compesator thinks the player should be at.
#if 0
for ( int k = 1; k <= gpGlobals->maxClients; k++ ) { CBasePlayer *clientClass = (CBasePlayer *)CBaseEntity::Instance( k );
if ( clientClass == NULL ) continue;
if ( k == entindex() ) continue;
#ifdef CLIENT_DLL
debugoverlay->AddBoxOverlay( clientClass->GetAbsOrigin(), clientClass->WorldAlignMins(), clientClass->WorldAlignMaxs(), QAngle( 0, 0, 0), 255,0,0,127, 4 ); #else
NDebugOverlay::Box( clientClass->GetAbsOrigin(), clientClass->WorldAlignMins(), clientClass->WorldAlignMaxs(), 0,0,255,127, 4 ); #endif
}
#endif
//=============================================================================
// HPE_BEGIN:
//=============================================================================
#ifndef CLIENT_DLL
// [pfreese] Track number player entities killed with this bullet
int iPenetrationKills = 0;
// [menglish] Increment the shots fired for this player
CCS_GameStats.Event_ShotFired( this, GetActiveWeapon() ); #endif
//=============================================================================
// HPE_END
//=============================================================================
bool bFirstHit = true;
CBasePlayer *lastPlayerHit = NULL;
if( sv_showplayerhitboxes.GetInt() > 0 ) { CBasePlayer *lagPlayer = UTIL_PlayerByIndex( sv_showplayerhitboxes.GetInt() ); if( lagPlayer ) { #ifdef CLIENT_DLL
lagPlayer->DrawClientHitboxes(4, true); #else
lagPlayer->DrawServerHitboxes(4, true); #endif
} }
MDLCACHE_CRITICAL_SECTION(); while ( fCurrentDamage > 0 ) { Vector vecEnd = vecSrc + vecDir * flDistance;
trace_t tr; // main enter bullet trace
UTIL_TraceLineIgnoreTwoEntities( vecSrc, vecEnd, CS_MASK_SHOOT|CONTENTS_HITBOX, this, lastPlayerHit, COLLISION_GROUP_NONE, &tr ); { CTraceFilterSkipTwoEntities filter( this, lastPlayerHit, COLLISION_GROUP_NONE );
// Check for player hitboxes extending outside their collision bounds
const float rayExtension = 40.0f; UTIL_ClipTraceToPlayers( vecSrc, vecEnd + vecDir * rayExtension, CS_MASK_SHOOT|CONTENTS_HITBOX, &filter, &tr ); }
lastPlayerHit = ToBasePlayer(tr.m_pEnt);
if ( tr.fraction == 1.0f ) break; // we didn't hit anything, stop tracing shoot
#ifdef _DEBUG
if ( bFirstHit ) AddBulletStat( gpGlobals->realtime, VectorLength( vecSrc-tr.endpos), tr.endpos ); #endif
bFirstHit = false;
#ifndef CLIENT_DLL
//
// Propogate a bullet impact event
// @todo Add this for shotgun pellets (which dont go thru here)
//
IGameEvent * event = gameeventmanager->CreateEvent( "bullet_impact" ); if ( event ) { event->SetInt( "userid", GetUserID() ); event->SetFloat( "x", tr.endpos.x ); event->SetFloat( "y", tr.endpos.y ); event->SetFloat( "z", tr.endpos.z ); gameeventmanager->FireEvent( event ); } #endif
/************* MATERIAL DETECTION ***********/ surfacedata_t *pSurfaceData = physprops->GetSurfaceData( tr.surface.surfaceProps ); int iEnterMaterial = pSurfaceData->game.material;
GetMaterialParameters( iEnterMaterial, flPenetrationModifier, flDamageModifier );
bool hitGrate = tr.contents & CONTENTS_GRATE;
// since some railings in de_inferno are CONTENTS_GRATE but CHAR_TEX_CONCRETE, we'll trust the
// CONTENTS_GRATE and use a high damage modifier.
if ( hitGrate ) { // If we're a concrete grate (TOOLS/TOOLSINVISIBLE texture) allow more penetrating power.
flPenetrationModifier = 1.0f; flDamageModifier = 0.99f; }
#ifdef CLIENT_DLL
if ( sv_showimpacts.GetInt() == 1 || sv_showimpacts.GetInt() == 2 ) { // draw red client impact markers
debugoverlay->AddBoxOverlay( tr.endpos, Vector(-2,-2,-2), Vector(2,2,2), QAngle( 0, 0, 0), 255,0,0,127, 4 );
if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() ) { C_BasePlayer *player = ToBasePlayer( tr.m_pEnt ); player->DrawClientHitboxes( 4, true ); } } #else
if ( sv_showimpacts.GetInt() == 1 || sv_showimpacts.GetInt() == 3 ) { // draw blue server impact markers
NDebugOverlay::Box( tr.endpos, Vector(-2,-2,-2), Vector(2,2,2), 0,0,255,127, 4 );
if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() ) { CBasePlayer *player = ToBasePlayer( tr.m_pEnt ); player->DrawServerHitboxes( 4, true ); } } #endif
//calculate the damage based on the distance the bullet travelled.
flCurrentDistance += tr.fraction * flDistance; fCurrentDamage *= pow (flRangeModifier, (flCurrentDistance / 500));
// check if we reach penetration distance, no more penetrations after that
if (flCurrentDistance > flPenetrationDistance && iPenetration > 0) iPenetration = 0;
#ifndef CLIENT_DLL
// This just keeps track of sounds for AIs (it doesn't play anything).
CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, tr.endpos, 400, 0.2f, this ); #endif
int iDamageType = DMG_BULLET | DMG_NEVERGIB;
if( bDoEffects ) { // See if the bullet ended up underwater + started out of the water
if ( enginetrace->GetPointContents( tr.endpos ) & (CONTENTS_WATER|CONTENTS_SLIME) ) { trace_t waterTrace; UTIL_TraceLine( vecSrc, tr.endpos, (MASK_SHOT|CONTENTS_WATER|CONTENTS_SLIME), this, COLLISION_GROUP_NONE, &waterTrace );
if( waterTrace.allsolid != 1 ) { CEffectData data; data.m_vOrigin = waterTrace.endpos; data.m_vNormal = waterTrace.plane.normal; data.m_flScale = random->RandomFloat( 8, 12 );
if ( waterTrace.contents & CONTENTS_SLIME ) { data.m_fFlags |= FX_WATER_IN_SLIME; }
DispatchEffect( "gunshotsplash", data ); } } else { //Do Regular hit effects
// Don't decal nodraw surfaces
if ( !( tr.surface.flags & (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP) ) ) { CBaseEntity *pEntity = tr.m_pEnt; if ( !( !friendlyfire.GetBool() && pEntity && pEntity->GetTeamNumber() == GetTeamNumber() ) ) { UTIL_ImpactTrace( &tr, iDamageType ); } } } } // bDoEffects
// add damage to entity that we hit
#ifndef CLIENT_DLL
ClearMultiDamage();
//=============================================================================
// HPE_BEGIN:
// [pfreese] Check if enemy players were killed by this bullet, and if so,
// add them to the iPenetrationKills count
//=============================================================================
CBaseEntity *pEntity = tr.m_pEnt;
CTakeDamageInfo info( pevAttacker, pevAttacker, fCurrentDamage, iDamageType ); CalculateBulletDamageForce( &info, iBulletType, vecDir, tr.endpos ); pEntity->DispatchTraceAttack( info, vecDir, &tr );
bool bWasAlive = pEntity->IsAlive();
TraceAttackToTriggers( info, tr.startpos, tr.endpos, vecDir );
ApplyMultiDamage();
if (bWasAlive && !pEntity->IsAlive() && pEntity->IsPlayer() && pEntity->GetTeamNumber() != GetTeamNumber()) { ++iPenetrationKills; } //=============================================================================
// HPE_END
//=============================================================================
#endif
// check if bullet can penetrate another entity
if ( iPenetration == 0 && !hitGrate ) break; // no, stop
// If we hit a grate with iPenetration == 0, stop on the next thing we hit
if ( iPenetration < 0 ) break;
Vector penetrationEnd;
// try to penetrate object, maximum penetration is 128 inch
if ( !TraceToExit( tr.endpos, vecDir, penetrationEnd, 24, 128 ) ) break;
// find exact penetration exit
trace_t exitTr; UTIL_TraceLine( penetrationEnd, tr.endpos, CS_MASK_SHOOT|CONTENTS_HITBOX, NULL, &exitTr );
if( exitTr.m_pEnt != tr.m_pEnt && exitTr.m_pEnt != NULL ) { // something was blocking, trace again
UTIL_TraceLine( penetrationEnd, tr.endpos, CS_MASK_SHOOT|CONTENTS_HITBOX, exitTr.m_pEnt, COLLISION_GROUP_NONE, &exitTr ); }
// get material at exit point
pSurfaceData = physprops->GetSurfaceData( exitTr.surface.surfaceProps ); int iExitMaterial = pSurfaceData->game.material;
hitGrate = hitGrate && ( exitTr.contents & CONTENTS_GRATE );
// if enter & exit point is wood or metal we assume this is
// a hollow crate or barrel and give a penetration bonus
if ( iEnterMaterial == iExitMaterial ) { if( iExitMaterial == CHAR_TEX_WOOD || iExitMaterial == CHAR_TEX_METAL ) { flPenetrationModifier *= 2; } }
float flTraceDistance = VectorLength( exitTr.endpos - tr.endpos );
// check if bullet has enough power to penetrate this distance for this material
if ( flTraceDistance > ( flPenetrationPower * flPenetrationModifier ) ) break; // bullet hasn't enough power to penetrate this distance
// penetration was successful
// bullet did penetrate object, exit Decal
if ( bDoEffects ) { UTIL_ImpactTrace( &exitTr, iDamageType ); }
//setup new start end parameters for successive trace
flPenetrationPower -= flTraceDistance / flPenetrationModifier; flCurrentDistance += flTraceDistance;
// NDebugOverlay::Box( exitTr.endpos, Vector(-2,-2,-2), Vector(2,2,2), 0,255,0,127, 8 );
vecSrc = exitTr.endpos; flDistance = (flDistance - flCurrentDistance) * 0.5;
// reduce damage power each time we hit something other than a grate
fCurrentDamage *= flDamageModifier;
// reduce penetration counter
iPenetration--; }
#ifndef CLIENT_DLL
//=============================================================================
// HPE_BEGIN:
// [pfreese] If we killed at least two enemies with a single bullet, award the
// TWO_WITH_ONE_SHOT achievement
//=============================================================================
if (iPenetrationKills >= 2) { AwardAchievement(CSKillTwoWithOneShot); } //=============================================================================
// HPE_END
//=============================================================================
#endif
}
void CCSPlayer::UpdateStepSound( surfacedata_t *psurface, const Vector &vecOrigin, const Vector &vecVelocity ) { float speedSqr = vecVelocity.AsVector2D().LengthSqr();
// the fastest walk is 135 ( scout ), see CCSGameMovement::CheckParameters()
if ( speedSqr < 150.0 * 150.0 ) return; // player is not running, no footsteps
BaseClass::UpdateStepSound( psurface, vecOrigin, vecVelocity ); }
// GOOSEMAN : Kick the view..
void CCSPlayer::KickBack( float up_base, float lateral_base, float up_modifier, float lateral_modifier, float up_max, float lateral_max, int direction_change ) { float flKickUp; float flKickLateral;
if (m_iShotsFired == 1) // This is the first round fired
{ flKickUp = up_base; flKickLateral = lateral_base; } else { flKickUp = up_base + m_iShotsFired*up_modifier; flKickLateral = lateral_base + m_iShotsFired*lateral_modifier; }
QAngle angle = GetPunchAngle();
angle.x -= flKickUp; if ( angle.x < -1 * up_max ) angle.x = -1 * up_max;
if ( m_iDirection == 1 ) { angle.y += flKickLateral; if (angle.y > lateral_max) angle.y = lateral_max; } else { angle.y -= flKickLateral; if ( angle.y < -1 * lateral_max ) angle.y = -1 * lateral_max; }
if ( !SharedRandomInt( "KickBack", 0, direction_change ) ) m_iDirection = 1 - m_iDirection;
SetPunchAngle( angle ); }
bool CCSPlayer::CanMove() const { // When we're in intro camera mode, it's important to return false here
// so our physics object doesn't fall out of the world.
if ( GetMoveType() == MOVETYPE_NONE ) return false;
if ( IsObserver() ) return true; // observers can move all the time
bool bValidMoveState = (State_Get() == STATE_ACTIVE || State_Get() == STATE_OBSERVER_MODE);
if ( m_bIsDefusing || !bValidMoveState || CSGameRules()->IsFreezePeriod() ) { return false; } else { // Can't move while planting C4.
CC4 *pC4 = dynamic_cast< CC4* >( GetActiveWeapon() ); if ( pC4 && pC4->m_bStartedArming ) return false;
return true; } }
void CCSPlayer::OnJump( float fImpulse ) { CWeaponCSBase* pActiveWeapon = GetActiveCSWeapon(); if ( pActiveWeapon != NULL ) pActiveWeapon->OnJump(fImpulse); }
void CCSPlayer::OnLand( float fVelocity ) { CWeaponCSBase* pActiveWeapon = GetActiveCSWeapon(); if ( pActiveWeapon != NULL ) pActiveWeapon->OnLand(fVelocity); }
//-------------------------------------------------------------------------------------------------------------------------------
/**
* Track the last time we were on a ladder, along with the ladder's normal and where we * were grabbing it, so we don't reach behind us and grab it again as we are trying to * dismount. */ void CCSPlayer::SurpressLadderChecks( const Vector& pos, const Vector& normal ) { m_ladderSurpressionTimer.Start( 1.0f ); m_lastLadderPos = pos; m_lastLadderNormal = normal; }
//-------------------------------------------------------------------------------------------------------------------------------
/**
* Prevent us from re-grabbing the same ladder we were just on: * - if the timer is elapsed, let us grab again * - if the normal is different, let us grab * - if the 2D pos is very different, let us grab, since it's probably a different ladder */ bool CCSPlayer::CanGrabLadder( const Vector& pos, const Vector& normal ) { if ( m_ladderSurpressionTimer.GetRemainingTime() <= 0.0f ) { return true; }
const float MaxDist = 64.0f; if ( pos.AsVector2D().DistToSqr( m_lastLadderPos.AsVector2D() ) < MaxDist * MaxDist ) { return false; }
if ( normal != m_lastLadderNormal ) { return true; }
return false; }
void CCSPlayer::SetAnimation( PLAYER_ANIM playerAnim ) { // In CS, its CPlayerAnimState object manages ALL the animation state.
return; }
CWeaponCSBase* CCSPlayer::CSAnim_GetActiveWeapon() { return GetActiveCSWeapon(); }
bool CCSPlayer::CSAnim_CanMove() { return CanMove(); }
//--------------------------------------------------------------------------------------------------------------
#define MATERIAL_NAME_LENGTH 16
#ifdef GAME_DLL
class CFootstepControl : public CBaseTrigger { public: DECLARE_CLASS( CFootstepControl, CBaseTrigger ); DECLARE_DATADESC(); DECLARE_SERVERCLASS();
virtual int UpdateTransmitState( void ); virtual void Spawn( void );
CNetworkVar( string_t, m_source ); CNetworkVar( string_t, m_destination ); };
LINK_ENTITY_TO_CLASS( func_footstep_control, CFootstepControl );
BEGIN_DATADESC( CFootstepControl ) DEFINE_KEYFIELD( m_source, FIELD_STRING, "Source" ), DEFINE_KEYFIELD( m_destination, FIELD_STRING, "Destination" ), END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CFootstepControl, DT_FootstepControl ) SendPropStringT( SENDINFO(m_source) ), SendPropStringT( SENDINFO(m_destination) ), END_SEND_TABLE()
int CFootstepControl::UpdateTransmitState( void ) { return SetTransmitState( FL_EDICT_ALWAYS ); }
void CFootstepControl::Spawn( void ) { InitTrigger(); }
#else
//--------------------------------------------------------------------------------------------------------------
class C_FootstepControl : public C_BaseEntity { public: DECLARE_CLASS( C_FootstepControl, C_BaseEntity ); DECLARE_CLIENTCLASS();
C_FootstepControl( void ); ~C_FootstepControl();
char m_source[MATERIAL_NAME_LENGTH]; char m_destination[MATERIAL_NAME_LENGTH]; };
IMPLEMENT_CLIENTCLASS_DT(C_FootstepControl, DT_FootstepControl, CFootstepControl) RecvPropString( RECVINFO(m_source) ), RecvPropString( RECVINFO(m_destination) ), END_RECV_TABLE()
CUtlVector< C_FootstepControl * > s_footstepControllers;
C_FootstepControl::C_FootstepControl( void ) { s_footstepControllers.AddToTail( this ); }
C_FootstepControl::~C_FootstepControl() { s_footstepControllers.FindAndRemove( this ); }
surfacedata_t * CCSPlayer::GetFootstepSurface( const Vector &origin, const char *surfaceName ) { for ( int i=0; i<s_footstepControllers.Count(); ++i ) { C_FootstepControl *control = s_footstepControllers[i];
if ( FStrEq( control->m_source, surfaceName ) ) { if ( control->CollisionProp()->IsPointInBounds( origin ) ) { return physprops->GetSurfaceData( physprops->GetSurfaceIndex( control->m_destination ) ); } } }
return physprops->GetSurfaceData( physprops->GetSurfaceIndex( surfaceName ) ); }
#endif
|