css_enhanced_waf/game/shared/cstrike/cs_player_shared.cpp
Kamay Xutax 9d17d2252c Improved lag compensation for animations
Now we can debug properly lag compensation for animations and make it
more perfect without using the CUserCmd struct
For now it's used to sync better with client but in theory this can be
removed soon.
There's a lot of work to do in anim layers too.
2024-07-10 16:14:53 +02:00

952 lines
25 KiB
C++

//========= 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." );
ConVar weapon_accuracy_nospread( "weapon_accuracy_nospread", "0", FCVAR_REPLICATED );
#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
if ( weapon_accuracy_nospread.GetBool() )
{
xSpread = 0.0f;
ySpread = 0.0f;
}
// 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(60, true);
DevMsg("Client: %s => %f %f %f => %f (%f)\n", lagPlayer->GetPlayerName(), lagPlayer->GetAbsOrigin().x, lagPlayer->GetAbsOrigin().y, lagPlayer->GetAbsOrigin().z,
lagPlayer->m_flInterpolatedSimulationTime, fmod(lagPlayer->m_flInterpolatedSimulationTime, TICK_INTERVAL) / TICK_INTERVAL);
#else
lagPlayer->DrawServerHitboxes(60, 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( 60, false );
}
}
#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