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.
2553 lines
68 KiB
2553 lines
68 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "c_cs_player.h" |
|
#include "c_user_message_register.h" |
|
#include "view.h" |
|
#include "iclientvehicle.h" |
|
#include "ivieweffects.h" |
|
#include "input.h" |
|
#include "IEffects.h" |
|
#include "fx.h" |
|
#include "c_basetempentity.h" |
|
#include "hud_macros.h" //HOOK_COMMAND |
|
#include "engine/ivdebugoverlay.h" |
|
#include "smoke_fog_overlay.h" |
|
#include "bone_setup.h" |
|
#include "in_buttons.h" |
|
#include "r_efx.h" |
|
#include "dlight.h" |
|
#include "shake.h" |
|
#include "cl_animevent.h" |
|
#include "c_physicsprop.h" |
|
#include "props_shared.h" |
|
#include "obstacle_pushaway.h" |
|
#include "death_pose.h" |
|
|
|
#include "effect_dispatch_data.h" //for water ripple / splash effect |
|
#include "c_te_effect_dispatch.h" //ditto |
|
#include "c_te_legacytempents.h" |
|
#include "cs_gamerules.h" |
|
#include "fx_cs_blood.h" |
|
#include "c_cs_playerresource.h" |
|
#include "c_team.h" |
|
|
|
#include "history_resource.h" |
|
#include "ragdoll_shared.h" |
|
#include "collisionutils.h" |
|
|
|
// NVNT - haptics system for spectating |
|
#include "haptics/haptic_utils.h" |
|
|
|
#include "steam/steam_api.h" |
|
|
|
#include "cs_blackmarket.h" // for vest/helmet prices |
|
|
|
#if defined( CCSPlayer ) |
|
#undef CCSPlayer |
|
#endif |
|
|
|
#include "materialsystem/imesh.h" //for materials->FindMaterial |
|
#include "iviewrender.h" //for view-> |
|
|
|
#include "iviewrender_beams.h" // flashlight beam |
|
|
|
//============================================================================= |
|
// HPE_BEGIN: |
|
// [menglish] Adding and externing variables needed for the freezecam |
|
//============================================================================= |
|
|
|
static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET); |
|
static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET); |
|
|
|
extern ConVar spec_freeze_time; |
|
extern ConVar spec_freeze_traveltime; |
|
extern ConVar spec_freeze_distance_min; |
|
extern ConVar spec_freeze_distance_max; |
|
|
|
//============================================================================= |
|
// HPE_END |
|
//============================================================================= |
|
|
|
ConVar cl_left_hand_ik( "cl_left_hand_ik", "0", 0, "Attach player's left hand to rifle with IK." ); |
|
|
|
ConVar cl_ragdoll_physics_enable( "cl_ragdoll_physics_enable", "1", 0, "Enable/disable ragdoll physics." ); |
|
|
|
ConVar cl_minmodels( "cl_minmodels", "0", 0, "Uses one player model for each team." ); |
|
ConVar cl_min_ct( "cl_min_ct", "1", 0, "Controls which CT model is used when cl_minmodels is set.", true, 1, true, 4 ); |
|
ConVar cl_min_t( "cl_min_t", "1", 0, "Controls which Terrorist model is used when cl_minmodels is set.", true, 1, true, 4 ); |
|
const float CycleLatchTolerance = 0.15; // amount we can diverge from the server's cycle before we're corrected |
|
|
|
extern ConVar mp_playerid_delay; |
|
extern ConVar mp_playerid_hold; |
|
extern ConVar sv_allowminmodels; |
|
|
|
class CAddonInfo |
|
{ |
|
public: |
|
const char *m_pAttachmentName; |
|
const char *m_pWeaponClassName; // The addon uses the w_ model from this weapon. |
|
const char *m_pModelName; //If this is present, will use this model instead of looking up the weapon |
|
const char *m_pHolsterName; |
|
}; |
|
|
|
|
|
|
|
// These must follow the ADDON_ ordering. |
|
CAddonInfo g_AddonInfo[] = |
|
{ |
|
{ "grenade0", "weapon_flashbang", 0, 0 }, |
|
{ "grenade1", "weapon_flashbang", 0, 0 }, |
|
{ "grenade2", "weapon_hegrenade", 0, 0 }, |
|
{ "grenade3", "weapon_smokegrenade", 0, 0 }, |
|
{ "c4", "weapon_c4", 0, 0 }, |
|
{ "defusekit", 0, "models/weapons/w_defuser.mdl", 0 }, |
|
{ "primary", 0, 0, 0 }, // Primary addon model is looked up based on m_iPrimaryAddon |
|
{ "pistol", 0, 0, 0 }, // Pistol addon model is looked up based on m_iSecondaryAddon |
|
{ "eholster", 0, "models/weapons/w_eq_eholster_elite.mdl", "models/weapons/w_eq_eholster.mdl" }, |
|
}; |
|
|
|
// -------------------------------------------------------------------------------- // |
|
// Player animation event. Sent to the client when a player fires, jumps, reloads, etc.. |
|
// -------------------------------------------------------------------------------- // |
|
|
|
class C_TEPlayerAnimEvent : public C_BaseTempEntity |
|
{ |
|
public: |
|
DECLARE_CLASS( C_TEPlayerAnimEvent, C_BaseTempEntity ); |
|
DECLARE_CLIENTCLASS(); |
|
|
|
virtual void PostDataUpdate( DataUpdateType_t updateType ) |
|
{ |
|
// Create the effect. |
|
C_CSPlayer *pPlayer = dynamic_cast< C_CSPlayer* >( m_hPlayer.Get() ); |
|
if ( pPlayer && !pPlayer->IsDormant() ) |
|
{ |
|
pPlayer->DoAnimationEvent( (PlayerAnimEvent_t)m_iEvent.Get(), m_nData ); |
|
} |
|
} |
|
|
|
public: |
|
CNetworkHandle( CBasePlayer, m_hPlayer ); |
|
CNetworkVar( int, m_iEvent ); |
|
CNetworkVar( int, m_nData ); |
|
}; |
|
|
|
IMPLEMENT_CLIENTCLASS_EVENT( C_TEPlayerAnimEvent, DT_TEPlayerAnimEvent, CTEPlayerAnimEvent ); |
|
|
|
BEGIN_RECV_TABLE_NOBASE( C_TEPlayerAnimEvent, DT_TEPlayerAnimEvent ) |
|
RecvPropEHandle( RECVINFO( m_hPlayer ) ), |
|
RecvPropInt( RECVINFO( m_iEvent ) ), |
|
RecvPropInt( RECVINFO( m_nData ) ) |
|
END_RECV_TABLE() |
|
|
|
BEGIN_PREDICTION_DATA( C_CSPlayer ) |
|
#ifdef CS_SHIELD_ENABLED |
|
DEFINE_PRED_FIELD( m_bShieldDrawn, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), |
|
#endif |
|
DEFINE_PRED_FIELD_TOL( m_flStamina, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, 0.1f ), |
|
DEFINE_PRED_FIELD( m_flCycle, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ), |
|
DEFINE_PRED_FIELD( m_iShotsFired, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), |
|
DEFINE_PRED_FIELD( m_iDirection, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), |
|
DEFINE_PRED_FIELD( m_bResumeZoom, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), |
|
DEFINE_PRED_FIELD( m_iLastZoom, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), |
|
|
|
END_PREDICTION_DATA() |
|
|
|
vgui::IImage* GetDefaultAvatarImage( C_BasePlayer *pPlayer ) |
|
{ |
|
vgui::IImage* result = NULL; |
|
|
|
switch ( pPlayer ? pPlayer->GetTeamNumber() : TEAM_MAXCOUNT ) |
|
{ |
|
case TEAM_TERRORIST: |
|
result = vgui::scheme()->GetImage( CSTRIKE_DEFAULT_T_AVATAR, true ); |
|
break; |
|
|
|
case TEAM_CT: |
|
result = vgui::scheme()->GetImage( CSTRIKE_DEFAULT_CT_AVATAR, true ); |
|
break; |
|
|
|
default: |
|
result = vgui::scheme()->GetImage( CSTRIKE_DEFAULT_AVATAR, true ); |
|
break; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
// ----------------------------------------------------------------------------- // |
|
// Client ragdoll entity. |
|
// ----------------------------------------------------------------------------- // |
|
|
|
float g_flDieTranslucentTime = 0.6; |
|
|
|
class C_CSRagdoll : public C_BaseAnimatingOverlay |
|
{ |
|
public: |
|
DECLARE_CLASS( C_CSRagdoll, C_BaseAnimatingOverlay ); |
|
DECLARE_CLIENTCLASS(); |
|
|
|
C_CSRagdoll(); |
|
~C_CSRagdoll(); |
|
|
|
virtual void OnDataChanged( DataUpdateType_t type ); |
|
|
|
int GetPlayerEntIndex() const; |
|
IRagdoll* GetIRagdoll() const; |
|
bool GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ) OVERRIDE; |
|
|
|
void ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ); |
|
|
|
virtual void ComputeFxBlend(); |
|
virtual bool IsTransparent(); |
|
bool IsInitialized() { return m_bInitialized; } |
|
// fading ragdolls don't cast shadows |
|
virtual ShadowType_t ShadowCastType() |
|
{ |
|
if ( m_flRagdollSinkStart == -1 ) |
|
return BaseClass::ShadowCastType(); |
|
return SHADOWS_NONE; |
|
} |
|
|
|
virtual void ValidateModelIndex( void ); |
|
|
|
private: |
|
|
|
C_CSRagdoll( const C_CSRagdoll & ) {} |
|
|
|
void Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity ); |
|
|
|
void CreateLowViolenceRagdoll( void ); |
|
void CreateCSRagdoll( void ); |
|
|
|
private: |
|
|
|
EHANDLE m_hPlayer; |
|
CNetworkVector( m_vecRagdollVelocity ); |
|
CNetworkVector( m_vecRagdollOrigin ); |
|
CNetworkVar(int, m_iDeathPose ); |
|
CNetworkVar(int, m_iDeathFrame ); |
|
float m_flRagdollSinkStart; |
|
bool m_bInitialized; |
|
bool m_bCreatedWhilePlaybackSkipping; |
|
}; |
|
|
|
|
|
IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_CSRagdoll, DT_CSRagdoll, CCSRagdoll ) |
|
RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), |
|
RecvPropVector( RECVINFO(m_vecRagdollOrigin) ), |
|
RecvPropEHandle( RECVINFO( m_hPlayer ) ), |
|
RecvPropInt( RECVINFO( m_nModelIndex ) ), |
|
RecvPropInt( RECVINFO(m_nForceBone) ), |
|
RecvPropVector( RECVINFO(m_vecForce) ), |
|
RecvPropVector( RECVINFO( m_vecRagdollVelocity ) ), |
|
RecvPropInt( RECVINFO(m_iDeathPose) ), |
|
RecvPropInt( RECVINFO(m_iDeathFrame) ), |
|
RecvPropInt(RECVINFO(m_iTeamNum)), |
|
RecvPropInt( RECVINFO(m_bClientSideAnimation)), |
|
END_RECV_TABLE() |
|
|
|
|
|
C_CSRagdoll::C_CSRagdoll() |
|
{ |
|
m_flRagdollSinkStart = -1; |
|
m_bInitialized = false; |
|
m_bCreatedWhilePlaybackSkipping = engine->IsSkippingPlayback(); |
|
} |
|
|
|
C_CSRagdoll::~C_CSRagdoll() |
|
{ |
|
PhysCleanupFrictionSounds( this ); |
|
} |
|
|
|
bool C_CSRagdoll::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ) |
|
{ |
|
// otherwise use the death pose to set up the ragdoll |
|
ForceSetupBonesAtTime( pDeltaBones0, gpGlobals->curtime - boneDt ); |
|
GetRagdollCurSequenceWithDeathPose( this, pDeltaBones1, gpGlobals->curtime, m_iDeathPose, m_iDeathFrame ); |
|
return SetupBones( pCurrentBones, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); |
|
} |
|
|
|
void C_CSRagdoll::Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity ) |
|
{ |
|
if ( !pSourceEntity ) |
|
return; |
|
|
|
VarMapping_t *pSrc = pSourceEntity->GetVarMapping(); |
|
VarMapping_t *pDest = GetVarMapping(); |
|
|
|
// Find all the VarMapEntry_t's that represent the same variable. |
|
for ( int i = 0; i < pDest->m_Entries.Count(); i++ ) |
|
{ |
|
VarMapEntry_t *pDestEntry = &pDest->m_Entries[i]; |
|
for ( int j=0; j < pSrc->m_Entries.Count(); j++ ) |
|
{ |
|
VarMapEntry_t *pSrcEntry = &pSrc->m_Entries[j]; |
|
if ( !Q_strcmp( pSrcEntry->watcher->GetDebugName(), |
|
pDestEntry->watcher->GetDebugName() ) ) |
|
{ |
|
pDestEntry->watcher->Copy( pSrcEntry->watcher ); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void C_CSRagdoll::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ) |
|
{ |
|
IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); |
|
|
|
if( !pPhysicsObject ) |
|
return; |
|
|
|
Vector dir = pTrace->endpos - pTrace->startpos; |
|
|
|
if ( iDamageType == DMG_BLAST ) |
|
{ |
|
dir *= 4000; // adjust impact strenght |
|
|
|
// apply force at object mass center |
|
pPhysicsObject->ApplyForceCenter( dir ); |
|
} |
|
else |
|
{ |
|
Vector hitpos; |
|
|
|
VectorMA( pTrace->startpos, pTrace->fraction, dir, hitpos ); |
|
VectorNormalize( dir ); |
|
|
|
dir *= 4000; // adjust impact strenght |
|
|
|
// apply force where we hit it |
|
pPhysicsObject->ApplyForceOffset( dir, hitpos ); |
|
|
|
// Blood spray! |
|
FX_CS_BloodSpray( hitpos, dir, 10 ); |
|
} |
|
|
|
m_pRagdoll->ResetRagdollSleepAfterTime(); |
|
} |
|
|
|
|
|
void C_CSRagdoll::ValidateModelIndex( void ) |
|
{ |
|
if ( sv_allowminmodels.GetBool() && cl_minmodels.GetBool() ) |
|
{ |
|
if ( GetTeamNumber() == TEAM_CT ) |
|
{ |
|
int index = cl_min_ct.GetInt() - 1; |
|
if ( index >= 0 && index < CTPlayerModels.Count() ) |
|
{ |
|
m_nModelIndex = modelinfo->GetModelIndex(CTPlayerModels[index]); |
|
} |
|
} |
|
else if ( GetTeamNumber() == TEAM_TERRORIST ) |
|
{ |
|
int index = cl_min_t.GetInt() - 1; |
|
if ( index >= 0 && index < TerroristPlayerModels.Count() ) |
|
{ |
|
m_nModelIndex = modelinfo->GetModelIndex(TerroristPlayerModels[index]); |
|
} |
|
} |
|
} |
|
|
|
BaseClass::ValidateModelIndex(); |
|
} |
|
|
|
|
|
void C_CSRagdoll::CreateLowViolenceRagdoll( void ) |
|
{ |
|
// Just play a death animation. |
|
// Find a death anim to play. |
|
int iMinDeathAnim = 9999, iMaxDeathAnim = -9999; |
|
for ( int iAnim=1; iAnim < 100; iAnim++ ) |
|
{ |
|
char str[512]; |
|
Q_snprintf( str, sizeof( str ), "death%d", iAnim ); |
|
if ( LookupSequence( str ) == -1 ) |
|
break; |
|
|
|
iMinDeathAnim = MIN( iMinDeathAnim, iAnim ); |
|
iMaxDeathAnim = MAX( iMaxDeathAnim, iAnim ); |
|
} |
|
|
|
if ( iMinDeathAnim == 9999 ) |
|
{ |
|
CreateCSRagdoll(); |
|
return; |
|
} |
|
|
|
SetNetworkOrigin( m_vecRagdollOrigin ); |
|
SetAbsOrigin( m_vecRagdollOrigin ); |
|
SetAbsVelocity( m_vecRagdollVelocity ); |
|
|
|
C_CSPlayer *pPlayer = dynamic_cast< C_CSPlayer* >( m_hPlayer.Get() ); |
|
if ( pPlayer ) |
|
{ |
|
if ( !pPlayer->IsDormant() ) |
|
{ |
|
// move my current model instance to the ragdoll's so decals are preserved. |
|
pPlayer->SnatchModelInstance( this ); |
|
} |
|
|
|
SetAbsAngles( pPlayer->GetRenderAngles() ); |
|
SetNetworkAngles( pPlayer->GetRenderAngles() ); |
|
} |
|
|
|
int iDeathAnim = RandomInt( iMinDeathAnim, iMaxDeathAnim ); |
|
char str[512]; |
|
Q_snprintf( str, sizeof( str ), "death%d", iDeathAnim ); |
|
SetSequence( LookupSequence( str ) ); |
|
ForceClientSideAnimationOn(); |
|
|
|
Interp_Reset( GetVarMapping() ); |
|
} |
|
|
|
|
|
void C_CSRagdoll::CreateCSRagdoll() |
|
{ |
|
// First, initialize all our data. If we have the player's entity on our client, |
|
// then we can make ourselves start out exactly where the player is. |
|
C_CSPlayer *pPlayer = dynamic_cast< C_CSPlayer* >( m_hPlayer.Get() ); |
|
|
|
// mark this to prevent model changes from overwriting the death sequence with the server sequence |
|
SetReceivedSequence(); |
|
|
|
if ( pPlayer && !pPlayer->IsDormant() ) |
|
{ |
|
// move my current model instance to the ragdoll's so decals are preserved. |
|
pPlayer->SnatchModelInstance( this ); |
|
|
|
VarMapping_t *varMap = GetVarMapping(); |
|
|
|
// Copy all the interpolated vars from the player entity. |
|
// The entity uses the interpolated history to get bone velocity. |
|
bool bRemotePlayer = (pPlayer != C_BasePlayer::GetLocalPlayer()); |
|
if ( bRemotePlayer ) |
|
{ |
|
Interp_Copy( pPlayer ); |
|
|
|
SetAbsAngles( pPlayer->GetRenderAngles() ); |
|
GetRotationInterpolator().Reset(); |
|
|
|
m_flAnimTime = pPlayer->m_flAnimTime; |
|
SetSequence( pPlayer->GetSequence() ); |
|
m_flPlaybackRate = pPlayer->GetPlaybackRate(); |
|
} |
|
else |
|
{ |
|
// This is the local player, so set them in a default |
|
// pose and slam their velocity, angles and origin |
|
SetAbsOrigin( m_vecRagdollOrigin ); |
|
|
|
SetAbsAngles( pPlayer->GetRenderAngles() ); |
|
|
|
SetAbsVelocity( m_vecRagdollVelocity ); |
|
|
|
int iSeq = LookupSequence( "walk_lower" ); |
|
if ( iSeq == -1 ) |
|
{ |
|
Assert( false ); // missing walk_lower? |
|
iSeq = 0; |
|
} |
|
|
|
SetSequence( iSeq ); // walk_lower, basic pose |
|
SetCycle( 0.0 ); |
|
|
|
// go ahead and set these on the player in case the code below decides to set up bones using |
|
// that entity instead of this one. The local player may not have valid animation |
|
pPlayer->SetSequence( iSeq ); // walk_lower, basic pose |
|
pPlayer->SetCycle( 0.0 ); |
|
|
|
Interp_Reset( varMap ); |
|
} |
|
} |
|
else |
|
{ |
|
// overwrite network origin so later interpolation will |
|
// use this position |
|
SetNetworkOrigin( m_vecRagdollOrigin ); |
|
|
|
SetAbsOrigin( m_vecRagdollOrigin ); |
|
SetAbsVelocity( m_vecRagdollVelocity ); |
|
|
|
Interp_Reset( GetVarMapping() ); |
|
} |
|
|
|
// Turn it into a ragdoll. |
|
if ( cl_ragdoll_physics_enable.GetInt() ) |
|
{ |
|
// Make us a ragdoll.. |
|
m_nRenderFX = kRenderFxRagdoll; |
|
|
|
matrix3x4_t boneDelta0[MAXSTUDIOBONES]; |
|
matrix3x4_t boneDelta1[MAXSTUDIOBONES]; |
|
matrix3x4_t currentBones[MAXSTUDIOBONES]; |
|
const float boneDt = 0.05f; |
|
|
|
//============================================================================= |
|
// [pfreese], [tj] |
|
// There are visual problems with the attempted blending of the |
|
// death pose animations in C_CSRagdoll::GetRagdollInitBoneArrays. The version |
|
// in C_BasePlayer::GetRagdollInitBoneArrays doesn't attempt to blend death |
|
// poses, so if the player is relevant, use that one regardless of whether the |
|
// player is the local one or not. |
|
//============================================================================= |
|
if ( pPlayer && !pPlayer->IsDormant() ) |
|
{ |
|
pPlayer->GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt ); |
|
} |
|
else |
|
{ |
|
GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt ); |
|
} |
|
|
|
InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); |
|
m_flRagdollSinkStart = -1; |
|
} |
|
else |
|
{ |
|
m_flRagdollSinkStart = gpGlobals->curtime; |
|
DestroyShadow(); |
|
ClientLeafSystem()->SetRenderGroup( GetRenderHandle(), RENDER_GROUP_TRANSLUCENT_ENTITY ); |
|
} |
|
m_bInitialized = true; |
|
} |
|
|
|
|
|
void C_CSRagdoll::ComputeFxBlend( void ) |
|
{ |
|
if ( m_flRagdollSinkStart == -1 ) |
|
{ |
|
BaseClass::ComputeFxBlend(); |
|
} |
|
else |
|
{ |
|
float elapsed = gpGlobals->curtime - m_flRagdollSinkStart; |
|
float flVal = RemapVal( elapsed, 0, g_flDieTranslucentTime, 255, 0 ); |
|
flVal = clamp( flVal, 0, 255 ); |
|
m_nRenderFXBlend = (int)flVal; |
|
|
|
#ifdef _DEBUG |
|
m_nFXComputeFrame = gpGlobals->framecount; |
|
#endif |
|
} |
|
} |
|
|
|
|
|
bool C_CSRagdoll::IsTransparent( void ) |
|
{ |
|
if ( m_flRagdollSinkStart == -1 ) |
|
{ |
|
return BaseClass::IsTransparent(); |
|
} |
|
else |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
|
|
void C_CSRagdoll::OnDataChanged( DataUpdateType_t type ) |
|
{ |
|
BaseClass::OnDataChanged( type ); |
|
|
|
if ( type == DATA_UPDATE_CREATED ) |
|
{ |
|
// Prevent replays from creating ragdolls on the first frame of playback after skipping through playback. |
|
// If a player died (leaving a ragdoll) previous to the first frame of replay playback, |
|
// their ragdoll wasn't yet initialized because OnDataChanged events are queued but not processed |
|
// until the first render. |
|
if ( engine->IsPlayingDemo() && m_bCreatedWhilePlaybackSkipping ) |
|
{ |
|
Release(); |
|
return; |
|
} |
|
|
|
if ( g_RagdollLVManager.IsLowViolence() ) |
|
{ |
|
CreateLowViolenceRagdoll(); |
|
} |
|
else |
|
{ |
|
CreateCSRagdoll(); |
|
} |
|
} |
|
else |
|
{ |
|
if ( !cl_ragdoll_physics_enable.GetInt() ) |
|
{ |
|
// Don't let it set us back to a ragdoll with data from the server. |
|
m_nRenderFX = kRenderFxNone; |
|
} |
|
} |
|
} |
|
|
|
IRagdoll* C_CSRagdoll::GetIRagdoll() const |
|
{ |
|
return m_pRagdoll; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when the player toggles nightvision |
|
// Input : *pData - the int value of the nightvision state |
|
// *pStruct - the player |
|
// *pOut - |
|
//----------------------------------------------------------------------------- |
|
void RecvProxy_NightVision( const CRecvProxyData *pData, void *pStruct, void *pOut ) |
|
{ |
|
C_CSPlayer *pPlayerData = (C_CSPlayer *) pStruct; |
|
|
|
bool bNightVisionOn = ( pData->m_Value.m_Int > 0 ); |
|
|
|
if ( pPlayerData->m_bNightVisionOn != bNightVisionOn ) |
|
{ |
|
if ( bNightVisionOn ) |
|
pPlayerData->m_flNightVisionAlpha = 1; |
|
} |
|
|
|
pPlayerData->m_bNightVisionOn = bNightVisionOn; |
|
} |
|
|
|
void RecvProxy_FlashTime( const CRecvProxyData *pData, void *pStruct, void *pOut ) |
|
{ |
|
C_CSPlayer *pPlayerData = (C_CSPlayer *) pStruct; |
|
|
|
if( pPlayerData != C_BasePlayer::GetLocalPlayer() ) |
|
return; |
|
|
|
if ( (pPlayerData->m_flFlashDuration != pData->m_Value.m_Float) && pData->m_Value.m_Float > 0 ) |
|
{ |
|
pPlayerData->m_flFlashAlpha = 1; |
|
} |
|
|
|
pPlayerData->m_flFlashDuration = pData->m_Value.m_Float; |
|
pPlayerData->m_flFlashBangTime = gpGlobals->curtime + pPlayerData->m_flFlashDuration; |
|
} |
|
|
|
void RecvProxy_HasDefuser( const CRecvProxyData *pData, void *pStruct, void *pOut ) |
|
{ |
|
C_CSPlayer *pPlayerData = (C_CSPlayer *)pStruct; |
|
|
|
if (pPlayerData == NULL) |
|
{ |
|
return; |
|
} |
|
|
|
bool drawIcon = false; |
|
|
|
if (pData->m_Value.m_Int == 0) |
|
{ |
|
pPlayerData->RemoveDefuser(); |
|
} |
|
else |
|
{ |
|
if (pPlayerData->HasDefuser() == false) |
|
{ |
|
drawIcon = true; |
|
} |
|
pPlayerData->GiveDefuser(); |
|
} |
|
|
|
if (pPlayerData->IsLocalPlayer() && drawIcon) |
|
{ |
|
// add to pickup history |
|
CHudHistoryResource *pHudHR = GET_HUDELEMENT( CHudHistoryResource ); |
|
|
|
if ( pHudHR ) |
|
{ |
|
pHudHR->AddToHistory(HISTSLOT_ITEM, "defuser_pickup"); |
|
} |
|
} |
|
} |
|
|
|
void C_CSPlayer::RecvProxy_CycleLatch( const CRecvProxyData *pData, void *pStruct, void *pOut ) |
|
{ |
|
// This receive proxy looks to see if the server's value is close enough to what we think it should |
|
// be. We've been running the same code; this is an error correction for changes we didn't simulate |
|
// while they were out of PVS. |
|
C_CSPlayer *pPlayer = (C_CSPlayer *)pStruct; |
|
if( pPlayer->IsLocalPlayer() ) |
|
return; // Don't need to fixup ourselves. |
|
|
|
float incomingCycle = (float)(pData->m_Value.m_Int) / 16; // Came in as 4 bit fixed point |
|
float currentCycle = pPlayer->GetCycle(); |
|
bool closeEnough = fabs(currentCycle - incomingCycle) < CycleLatchTolerance; |
|
if( fabs(currentCycle - incomingCycle) > (1 - CycleLatchTolerance) ) |
|
{ |
|
closeEnough = true;// Handle wrapping around 1->0 |
|
} |
|
|
|
if( !closeEnough ) |
|
{ |
|
// Server disagrees too greatly. Correct our value. |
|
if ( pPlayer && pPlayer->GetTeam() ) |
|
{ |
|
DevMsg( 2, "%s %s(%d): Cycle latch wants to correct %.2f in to %.2f.\n", |
|
pPlayer->GetTeam()->Get_Name(), pPlayer->GetPlayerName(), pPlayer->entindex(), currentCycle, incomingCycle ); |
|
} |
|
pPlayer->SetServerIntendedCycle( incomingCycle ); |
|
} |
|
} |
|
|
|
void __MsgFunc_ReloadEffect( bf_read &msg ) |
|
{ |
|
int iPlayer = msg.ReadShort(); |
|
C_CSPlayer *pPlayer = dynamic_cast< C_CSPlayer* >( C_BaseEntity::Instance( iPlayer ) ); |
|
if ( pPlayer ) |
|
pPlayer->PlayReloadEffect(); |
|
|
|
} |
|
USER_MESSAGE_REGISTER( ReloadEffect ); |
|
|
|
BEGIN_RECV_TABLE_NOBASE( C_CSPlayer, DT_CSLocalPlayerExclusive ) |
|
RecvPropFloat( RECVINFO(m_flStamina) ), |
|
RecvPropInt( RECVINFO( m_iDirection ) ), |
|
RecvPropInt( RECVINFO( m_iShotsFired ) ), |
|
RecvPropFloat( RECVINFO( m_flVelocityModifier ) ), |
|
|
|
RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), |
|
|
|
//============================================================================= |
|
// HPE_BEGIN: |
|
// [tj]Set up the receive table for per-client domination data |
|
//============================================================================= |
|
|
|
RecvPropArray3( RECVINFO_ARRAY( m_bPlayerDominated ), RecvPropBool( RECVINFO( m_bPlayerDominated[0] ) ) ), |
|
RecvPropArray3( RECVINFO_ARRAY( m_bPlayerDominatingMe ), RecvPropBool( RECVINFO( m_bPlayerDominatingMe[0] ) ) ) |
|
|
|
//============================================================================= |
|
// HPE_END |
|
//============================================================================= |
|
|
|
END_RECV_TABLE() |
|
|
|
|
|
BEGIN_RECV_TABLE_NOBASE( C_CSPlayer, DT_CSNonLocalPlayerExclusive ) |
|
RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ), |
|
END_RECV_TABLE() |
|
|
|
|
|
IMPLEMENT_CLIENTCLASS_DT( C_CSPlayer, DT_CSPlayer, CCSPlayer ) |
|
RecvPropDataTable( "cslocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_CSLocalPlayerExclusive) ), |
|
RecvPropDataTable( "csnonlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_CSNonLocalPlayerExclusive) ), |
|
RecvPropInt( RECVINFO( m_iAddonBits ) ), |
|
RecvPropInt( RECVINFO( m_iPrimaryAddon ) ), |
|
RecvPropInt( RECVINFO( m_iSecondaryAddon ) ), |
|
RecvPropInt( RECVINFO( m_iThrowGrenadeCounter ) ), |
|
RecvPropInt( RECVINFO( m_iPlayerState ) ), |
|
RecvPropInt( RECVINFO( m_iAccount ) ), |
|
RecvPropInt( RECVINFO( m_bInBombZone ) ), |
|
RecvPropInt( RECVINFO( m_bInBuyZone ) ), |
|
RecvPropInt( RECVINFO( m_iClass ) ), |
|
RecvPropInt( RECVINFO( m_ArmorValue ) ), |
|
RecvPropFloat( RECVINFO( m_angEyeAngles[0] ) ), |
|
RecvPropFloat( RECVINFO( m_angEyeAngles[1] ) ), |
|
RecvPropFloat( RECVINFO( m_flStamina ) ), |
|
RecvPropInt( RECVINFO( m_bHasDefuser ), 0, RecvProxy_HasDefuser ), |
|
RecvPropInt( RECVINFO( m_bNightVisionOn), 0, RecvProxy_NightVision ), |
|
RecvPropBool( RECVINFO( m_bHasNightVision ) ), |
|
|
|
|
|
//============================================================================= |
|
// HPE_BEGIN: |
|
// [dwenger] Added for fun-fact support |
|
//============================================================================= |
|
|
|
//RecvPropBool( RECVINFO( m_bPickedUpDefuser ) ), |
|
//RecvPropBool( RECVINFO( m_bDefusedWithPickedUpKit ) ), |
|
|
|
//============================================================================= |
|
// HPE_END |
|
//============================================================================= |
|
|
|
RecvPropBool( RECVINFO( m_bInHostageRescueZone ) ), |
|
RecvPropInt( RECVINFO( m_ArmorValue ) ), |
|
RecvPropBool( RECVINFO( m_bIsDefusing ) ), |
|
RecvPropBool( RECVINFO( m_bResumeZoom ) ), |
|
RecvPropInt( RECVINFO( m_iLastZoom ) ), |
|
|
|
#ifdef CS_SHIELD_ENABLED |
|
RecvPropBool( RECVINFO( m_bHasShield ) ), |
|
RecvPropBool( RECVINFO( m_bShieldDrawn ) ), |
|
#endif |
|
RecvPropInt( RECVINFO( m_bHasHelmet ) ), |
|
RecvPropVector( RECVINFO( m_vecRagdollVelocity ) ), |
|
RecvPropFloat( RECVINFO( m_flFlashDuration ), 0, RecvProxy_FlashTime ), |
|
RecvPropFloat( RECVINFO( m_flFlashMaxAlpha)), |
|
RecvPropInt( RECVINFO( m_iProgressBarDuration ) ), |
|
RecvPropFloat( RECVINFO( m_flProgressBarStartTime ) ), |
|
RecvPropEHandle( RECVINFO( m_hRagdoll ) ), |
|
RecvPropInt( RECVINFO( m_cycleLatch ), 0, &C_CSPlayer::RecvProxy_CycleLatch ), |
|
|
|
END_RECV_TABLE() |
|
|
|
|
|
|
|
C_CSPlayer::C_CSPlayer() : |
|
m_iv_angEyeAngles( "C_CSPlayer::m_iv_angEyeAngles" ) |
|
{ |
|
m_PlayerAnimState = CreatePlayerAnimState( this, this, LEGANIM_9WAY, true ); |
|
|
|
m_angEyeAngles.Init(); |
|
|
|
AddVar( &m_angEyeAngles, &m_iv_angEyeAngles, LATCH_SIMULATION_VAR ); |
|
|
|
m_iLastAddonBits = m_iAddonBits = 0; |
|
m_iLastPrimaryAddon = m_iLastSecondaryAddon = WEAPON_NONE; |
|
m_iProgressBarDuration = 0; |
|
m_flProgressBarStartTime = 0.0f; |
|
m_ArmorValue = 0; |
|
m_bHasHelmet = false; |
|
m_iIDEntIndex = 0; |
|
m_delayTargetIDTimer.Reset(); |
|
m_iOldIDEntIndex = 0; |
|
m_holdTargetIDTimer.Reset(); |
|
m_iDirection = 0; |
|
|
|
m_Activity = ACT_IDLE; |
|
|
|
m_pFlashlightBeam = NULL; |
|
m_fNextThinkPushAway = 0.0f; |
|
|
|
m_serverIntendedCycle = -1.0f; |
|
|
|
view->SetScreenOverlayMaterial( NULL ); |
|
|
|
m_bPlayingFreezeCamSound = false; |
|
} |
|
|
|
|
|
C_CSPlayer::~C_CSPlayer() |
|
{ |
|
RemoveAddonModels(); |
|
|
|
ReleaseFlashlight(); |
|
|
|
m_PlayerAnimState->Release(); |
|
} |
|
|
|
|
|
bool C_CSPlayer::HasDefuser() const |
|
{ |
|
return m_bHasDefuser; |
|
} |
|
|
|
void C_CSPlayer::GiveDefuser() |
|
{ |
|
m_bHasDefuser = true; |
|
} |
|
|
|
void C_CSPlayer::RemoveDefuser() |
|
{ |
|
m_bHasDefuser = false; |
|
} |
|
|
|
bool C_CSPlayer::HasNightVision() const |
|
{ |
|
return m_bHasNightVision; |
|
} |
|
|
|
bool C_CSPlayer::IsVIP() const |
|
{ |
|
C_CS_PlayerResource *pCSPR = (C_CS_PlayerResource*)GameResources(); |
|
|
|
if ( !pCSPR ) |
|
return false; |
|
|
|
return pCSPR->IsVIP( entindex() ); |
|
} |
|
|
|
C_CSPlayer* C_CSPlayer::GetLocalCSPlayer() |
|
{ |
|
return (C_CSPlayer*)C_BasePlayer::GetLocalPlayer(); |
|
} |
|
|
|
|
|
CSPlayerState C_CSPlayer::State_Get() const |
|
{ |
|
return m_iPlayerState; |
|
} |
|
|
|
|
|
float C_CSPlayer::GetMinFOV() const |
|
{ |
|
// Min FOV for AWP. |
|
return 10; |
|
} |
|
|
|
|
|
int C_CSPlayer::GetAccount() const |
|
{ |
|
return m_iAccount; |
|
} |
|
|
|
|
|
int C_CSPlayer::PlayerClass() const |
|
{ |
|
return m_iClass; |
|
} |
|
|
|
bool C_CSPlayer::IsInBuyZone() |
|
{ |
|
return m_bInBuyZone; |
|
} |
|
|
|
bool C_CSPlayer::CanShowTeamMenu() const |
|
{ |
|
return true; |
|
} |
|
|
|
|
|
int C_CSPlayer::ArmorValue() const |
|
{ |
|
return m_ArmorValue; |
|
} |
|
|
|
bool C_CSPlayer::HasHelmet() const |
|
{ |
|
return m_bHasHelmet; |
|
} |
|
|
|
int C_CSPlayer::GetCurrentAssaultSuitPrice() |
|
{ |
|
// WARNING: This price logic also exists in CCSPlayer::AttemptToBuyAssaultSuit |
|
// and must be kept in sync if changes are made. |
|
|
|
int fullArmor = ArmorValue() >= 100 ? 1 : 0; |
|
if ( fullArmor && !HasHelmet() ) |
|
{ |
|
return HELMET_PRICE; |
|
} |
|
else if ( !fullArmor && HasHelmet() ) |
|
{ |
|
return KEVLAR_PRICE; |
|
} |
|
else |
|
{ |
|
// NOTE: This applies to the case where you already have both |
|
// as well as the case where you have neither. In the case |
|
// where you have both, the item should still have a price |
|
// and become disabled when you have little or no money left. |
|
return ASSAULTSUIT_PRICE; |
|
} |
|
} |
|
|
|
const QAngle& C_CSPlayer::GetRenderAngles() |
|
{ |
|
if ( IsRagdoll() ) |
|
{ |
|
return vec3_angle; |
|
} |
|
else |
|
{ |
|
return m_PlayerAnimState->GetRenderAngles(); |
|
} |
|
} |
|
|
|
|
|
float g_flFattenAmt = 4; |
|
void C_CSPlayer::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType ) |
|
{ |
|
if ( shadowType == SHADOWS_SIMPLE ) |
|
{ |
|
// Don't let the render bounds change when we're using blobby shadows, or else the shadow |
|
// will pop and stretch. |
|
mins = CollisionProp()->OBBMins(); |
|
maxs = CollisionProp()->OBBMaxs(); |
|
} |
|
else |
|
{ |
|
GetRenderBounds( mins, maxs ); |
|
|
|
// We do this because the normal bbox calculations don't take pose params into account, and |
|
// the rotation of the guy's upper torso can place his gun a ways out of his bbox, and |
|
// the shadow will get cut off as he rotates. |
|
// |
|
// Thus, we give it some padding here. |
|
mins -= Vector( g_flFattenAmt, g_flFattenAmt, 0 ); |
|
maxs += Vector( g_flFattenAmt, g_flFattenAmt, 0 ); |
|
} |
|
} |
|
|
|
|
|
void C_CSPlayer::GetRenderBounds( Vector& theMins, Vector& theMaxs ) |
|
{ |
|
// TODO POSTSHIP - this hack/fix goes hand-in-hand with a fix in CalcSequenceBoundingBoxes in utils/studiomdl/simplify.cpp. |
|
// When we enable the fix in CalcSequenceBoundingBoxes, we can get rid of this. |
|
// |
|
// What we're doing right here is making sure it only uses the bbox for our lower-body sequences since, |
|
// with the current animations and the bug in CalcSequenceBoundingBoxes, are WAY bigger than they need to be. |
|
C_BaseAnimating::GetRenderBounds( theMins, theMaxs ); |
|
|
|
// If we're ducking, we should reduce the render height by the difference in standing and ducking heights. |
|
// This prevents shadows from drawing above ducking players etc. |
|
if ( GetFlags() & FL_DUCKING ) |
|
{ |
|
theMaxs.z -= 18.5f; |
|
} |
|
} |
|
|
|
|
|
bool C_CSPlayer::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const |
|
{ |
|
if ( shadowType == SHADOWS_SIMPLE ) |
|
{ |
|
// Blobby shadows should sit directly underneath us. |
|
pDirection->Init( 0, 0, -1 ); |
|
return true; |
|
} |
|
else |
|
{ |
|
return BaseClass::GetShadowCastDirection( pDirection, shadowType ); |
|
} |
|
} |
|
|
|
|
|
void C_CSPlayer::VPhysicsUpdate( IPhysicsObject *pPhysics ) |
|
{ |
|
BaseClass::VPhysicsUpdate( pPhysics ); |
|
} |
|
|
|
|
|
int C_CSPlayer::GetIDTarget() const |
|
{ |
|
if ( !m_delayTargetIDTimer.IsElapsed() ) |
|
return 0; |
|
|
|
if ( m_iIDEntIndex ) |
|
{ |
|
return m_iIDEntIndex; |
|
} |
|
|
|
if ( m_iOldIDEntIndex && !m_holdTargetIDTimer.IsElapsed() ) |
|
{ |
|
return m_iOldIDEntIndex; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
void InitializeAddonModelFromWeapon( CWeaponCSBase *weapon, C_BreakableProp *addon ) |
|
{ |
|
if ( !weapon ) |
|
{ |
|
return; |
|
} |
|
|
|
const CCSWeaponInfo& weaponInfo = weapon->GetCSWpnData(); |
|
if ( weaponInfo.m_szAddonModel[0] == 0 ) |
|
{ |
|
addon->InitializeAsClientEntity( weaponInfo.szWorldModel, RENDER_GROUP_OPAQUE_ENTITY ); |
|
} |
|
else |
|
{ |
|
addon->InitializeAsClientEntity( weaponInfo.m_szAddonModel, RENDER_GROUP_OPAQUE_ENTITY ); |
|
} |
|
} |
|
|
|
void C_CSPlayer::CreateAddonModel( int i ) |
|
{ |
|
COMPILE_TIME_ASSERT( (sizeof( g_AddonInfo ) / sizeof( g_AddonInfo[0] )) == NUM_ADDON_BITS ); |
|
|
|
// Create the model entity. |
|
CAddonInfo *pAddonInfo = &g_AddonInfo[i]; |
|
|
|
int iAttachment = LookupAttachment( pAddonInfo->m_pAttachmentName ); |
|
if ( iAttachment <= 0 ) |
|
return; |
|
|
|
C_BreakableProp *pEnt = new C_BreakableProp; |
|
|
|
int addonType = (1<<i); |
|
if ( addonType == ADDON_PISTOL || addonType == ADDON_PRIMARY ) |
|
{ |
|
CCSWeaponInfo *weaponInfo = GetWeaponInfo( (CSWeaponID)((addonType == ADDON_PRIMARY) ? m_iPrimaryAddon.Get() : m_iSecondaryAddon.Get()) ); |
|
if ( !weaponInfo ) |
|
{ |
|
Warning( "C_CSPlayer::CreateAddonModel: Unable to get weapon info.\n" ); |
|
pEnt->Release(); |
|
return; |
|
} |
|
if ( weaponInfo->m_szAddonModel[0] == 0 ) |
|
{ |
|
pEnt->InitializeAsClientEntity( weaponInfo->szWorldModel, RENDER_GROUP_OPAQUE_ENTITY ); |
|
} |
|
else |
|
{ |
|
pEnt->InitializeAsClientEntity( weaponInfo->m_szAddonModel, RENDER_GROUP_OPAQUE_ENTITY ); |
|
} |
|
} |
|
else if( pAddonInfo->m_pModelName ) |
|
{ |
|
if ( addonType == ADDON_PISTOL2 && !(m_iAddonBits & ADDON_PISTOL ) ) |
|
{ |
|
pEnt->InitializeAsClientEntity( pAddonInfo->m_pHolsterName, RENDER_GROUP_OPAQUE_ENTITY ); |
|
} |
|
else |
|
{ |
|
pEnt->InitializeAsClientEntity( pAddonInfo->m_pModelName, RENDER_GROUP_OPAQUE_ENTITY ); |
|
} |
|
} |
|
else |
|
{ |
|
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( pAddonInfo->m_pWeaponClassName ); |
|
if ( hWpnInfo == GetInvalidWeaponInfoHandle() ) |
|
{ |
|
Assert( false ); |
|
return; |
|
} |
|
|
|
CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) ); |
|
if ( pWeaponInfo ) |
|
{ |
|
if ( pWeaponInfo->m_szAddonModel[0] == 0 ) |
|
pEnt->InitializeAsClientEntity( pWeaponInfo->szWorldModel, RENDER_GROUP_OPAQUE_ENTITY ); |
|
else |
|
pEnt->InitializeAsClientEntity( pWeaponInfo->m_szAddonModel, RENDER_GROUP_OPAQUE_ENTITY ); |
|
} |
|
else |
|
{ |
|
pEnt->Release(); |
|
Warning( "C_CSPlayer::CreateAddonModel: Unable to get weapon info for %s.\n", pAddonInfo->m_pWeaponClassName ); |
|
return; |
|
} |
|
} |
|
|
|
if ( Q_strcmp( pAddonInfo->m_pAttachmentName, "c4" ) ) |
|
{ |
|
// fade out all attached models except C4 |
|
pEnt->SetFadeMinMax( 400, 500 ); |
|
} |
|
|
|
// Create the addon. |
|
CAddonModel *pAddon = &m_AddonModels[m_AddonModels.AddToTail()]; |
|
|
|
pAddon->m_hEnt = pEnt; |
|
pAddon->m_iAddon = i; |
|
pAddon->m_iAttachmentPoint = iAttachment; |
|
pEnt->SetParent( this, pAddon->m_iAttachmentPoint ); |
|
pEnt->SetLocalOrigin( Vector( 0, 0, 0 ) ); |
|
pEnt->SetLocalAngles( QAngle( 0, 0, 0 ) ); |
|
if ( IsLocalPlayer() ) |
|
{ |
|
pEnt->SetSolid( SOLID_NONE ); |
|
pEnt->RemoveEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); |
|
} |
|
} |
|
|
|
|
|
void C_CSPlayer::UpdateAddonModels() |
|
{ |
|
int iCurAddonBits = m_iAddonBits; |
|
|
|
// Don't put addon models on the local player unless in third person. |
|
if ( IsLocalPlayer() && !C_BasePlayer::ShouldDrawLocalPlayer() ) |
|
iCurAddonBits = 0; |
|
|
|
// If the local player is observing this entity in first-person mode, get rid of its addons. |
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); |
|
if ( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_IN_EYE && pPlayer->GetObserverTarget() == this ) |
|
iCurAddonBits = 0; |
|
|
|
// Any changes to the attachments we should have? |
|
if ( m_iLastAddonBits == iCurAddonBits && |
|
m_iLastPrimaryAddon == m_iPrimaryAddon && |
|
m_iLastSecondaryAddon == m_iSecondaryAddon ) |
|
{ |
|
return; |
|
} |
|
|
|
bool rebuildPistol2Addon = false; |
|
if ( m_iSecondaryAddon == WEAPON_ELITE && ((m_iLastAddonBits ^ iCurAddonBits) & ADDON_PISTOL) != 0 ) |
|
{ |
|
rebuildPistol2Addon = true; |
|
} |
|
m_iLastAddonBits = iCurAddonBits; |
|
m_iLastPrimaryAddon = m_iPrimaryAddon; |
|
m_iLastSecondaryAddon = m_iSecondaryAddon; |
|
|
|
// Get rid of any old models. |
|
int i,iNext; |
|
for ( i=m_AddonModels.Head(); i != m_AddonModels.InvalidIndex(); i = iNext ) |
|
{ |
|
iNext = m_AddonModels.Next( i ); |
|
CAddonModel *pModel = &m_AddonModels[i]; |
|
|
|
int addonBit = 1<<pModel->m_iAddon; |
|
if ( !( iCurAddonBits & addonBit ) || (rebuildPistol2Addon && addonBit == ADDON_PISTOL2) ) |
|
{ |
|
if ( pModel->m_hEnt.Get() ) |
|
pModel->m_hEnt->Release(); |
|
|
|
m_AddonModels.Remove( i ); |
|
} |
|
} |
|
|
|
// Figure out which models we have now. |
|
int curModelBits = 0; |
|
FOR_EACH_LL( m_AddonModels, j ) |
|
{ |
|
curModelBits |= (1<<m_AddonModels[j].m_iAddon); |
|
} |
|
|
|
// Add any new models. |
|
for ( i=0; i < NUM_ADDON_BITS; i++ ) |
|
{ |
|
if ( (iCurAddonBits & (1<<i)) && !( curModelBits & (1<<i) ) ) |
|
{ |
|
// Ok, we're supposed to have this one. |
|
CreateAddonModel( i ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void C_CSPlayer::RemoveAddonModels() |
|
{ |
|
m_iAddonBits = 0; |
|
UpdateAddonModels(); |
|
} |
|
|
|
|
|
void C_CSPlayer::NotifyShouldTransmit( ShouldTransmitState_t state ) |
|
{ |
|
// Remove all addon models if we go out of the PVS. |
|
if ( state == SHOULDTRANSMIT_END ) |
|
{ |
|
RemoveAddonModels(); |
|
|
|
if( m_pFlashlightBeam != NULL ) |
|
{ |
|
ReleaseFlashlight(); |
|
} |
|
} |
|
|
|
BaseClass::NotifyShouldTransmit( state ); |
|
} |
|
|
|
|
|
void C_CSPlayer::UpdateSoundEvents() |
|
{ |
|
int iNext; |
|
for ( int i=m_SoundEvents.Head(); i != m_SoundEvents.InvalidIndex(); i = iNext ) |
|
{ |
|
iNext = m_SoundEvents.Next( i ); |
|
|
|
CCSSoundEvent *pEvent = &m_SoundEvents[i]; |
|
if ( gpGlobals->curtime >= pEvent->m_flEventTime ) |
|
{ |
|
CLocalPlayerFilter filter; |
|
EmitSound( filter, GetSoundSourceIndex(), STRING( pEvent->m_SoundName ) ); |
|
|
|
m_SoundEvents.Remove( i ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void C_CSPlayer::UpdateMinModels( void ) |
|
{ |
|
int modelIndex = m_nModelIndex; |
|
|
|
// cl_minmodels convar dependent on sv_allowminmodels convar |
|
|
|
if ( !IsVIP() && sv_allowminmodels.GetBool() && cl_minmodels.GetBool() && !IsLocalPlayer() ) |
|
{ |
|
if ( GetTeamNumber() == TEAM_CT ) |
|
{ |
|
int index = cl_min_ct.GetInt() - 1; |
|
if ( index >= 0 && index < CTPlayerModels.Count() ) |
|
{ |
|
modelIndex = modelinfo->GetModelIndex( CTPlayerModels[index] ); |
|
} |
|
} |
|
else if ( GetTeamNumber() == TEAM_TERRORIST ) |
|
{ |
|
int index = cl_min_t.GetInt() - 1; |
|
if ( index >= 0 && index < TerroristPlayerModels.Count() ) |
|
{ |
|
modelIndex = modelinfo->GetModelIndex( TerroristPlayerModels[index] ); |
|
} |
|
} |
|
} |
|
|
|
SetModelByIndex( modelIndex ); |
|
} |
|
|
|
// NVNT gate for spectating. |
|
static bool inSpectating_Haptics = false; |
|
//----------------------------------------------------------------------------- |
|
void C_CSPlayer::ClientThink() |
|
{ |
|
BaseClass::ClientThink(); |
|
|
|
UpdateSoundEvents(); |
|
|
|
UpdateAddonModels(); |
|
|
|
UpdateIDTarget(); |
|
|
|
if ( gpGlobals->curtime >= m_fNextThinkPushAway ) |
|
{ |
|
PerformObstaclePushaway( this ); |
|
m_fNextThinkPushAway = gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL; |
|
} |
|
|
|
// NVNT - check for spectating forces |
|
if ( IsLocalPlayer() ) |
|
{ |
|
if ( GetTeamNumber() == TEAM_SPECTATOR || !this->IsAlive() || GetLocalOrInEyeCSPlayer() != this ) |
|
{ |
|
if (!inSpectating_Haptics) |
|
{ |
|
if ( haptics ) |
|
haptics->SetNavigationClass("spectate"); |
|
|
|
inSpectating_Haptics = true; |
|
} |
|
} |
|
else |
|
{ |
|
if (inSpectating_Haptics) |
|
{ |
|
if ( haptics ) |
|
haptics->SetNavigationClass("on_foot"); |
|
|
|
inSpectating_Haptics = false; |
|
} |
|
} |
|
|
|
if ( m_iObserverMode == OBS_MODE_FREEZECAM ) |
|
{ |
|
//============================================================================= |
|
// HPE_BEGIN: |
|
// [Forrest] Added sv_disablefreezecam check |
|
//============================================================================= |
|
static ConVarRef sv_disablefreezecam( "sv_disablefreezecam" ); |
|
if ( !m_bPlayingFreezeCamSound && !cl_disablefreezecam.GetBool() && !sv_disablefreezecam.GetBool() ) |
|
//============================================================================= |
|
// HPE_END |
|
//============================================================================= |
|
{ |
|
// Play sound |
|
m_bPlayingFreezeCamSound = true; |
|
|
|
CLocalPlayerFilter filter; |
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_VOICE; |
|
ep.m_pSoundName = "UI/freeze_cam.wav"; |
|
ep.m_flVolume = VOL_NORM; |
|
ep.m_SoundLevel = SNDLVL_NORM; |
|
ep.m_bEmitCloseCaption = false; |
|
|
|
EmitSound( filter, GetSoundSourceIndex(), ep ); |
|
} |
|
} |
|
else |
|
{ |
|
m_bPlayingFreezeCamSound = false; |
|
} |
|
} |
|
} |
|
|
|
|
|
void C_CSPlayer::OnDataChanged( DataUpdateType_t type ) |
|
{ |
|
BaseClass::OnDataChanged( type ); |
|
|
|
if ( type == DATA_UPDATE_CREATED ) |
|
{ |
|
SetNextClientThink( CLIENT_THINK_ALWAYS ); |
|
|
|
if ( IsLocalPlayer() ) |
|
{ |
|
if ( CSGameRules() && CSGameRules()->IsBlackMarket() ) |
|
{ |
|
CSGameRules()->m_pPrices = NULL; |
|
CSGameRules()->m_StringTableBlackMarket = NULL; |
|
CSGameRules()->GetBlackMarketPriceList(); |
|
|
|
CSGameRules()->SetBlackMarketPrices( false ); |
|
} |
|
} |
|
} |
|
|
|
UpdateVisibility(); |
|
} |
|
|
|
|
|
void C_CSPlayer::ValidateModelIndex( void ) |
|
{ |
|
UpdateMinModels(); |
|
} |
|
|
|
|
|
void C_CSPlayer::PostDataUpdate( DataUpdateType_t updateType ) |
|
{ |
|
// C_BaseEntity assumes we're networking the entity's angles, so pretend that it |
|
// networked the same value we already have. |
|
SetNetworkAngles( GetLocalAngles() ); |
|
|
|
BaseClass::PostDataUpdate( updateType ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool C_CSPlayer::Interpolate( float currentTime ) |
|
{ |
|
if ( !BaseClass::Interpolate( currentTime ) ) |
|
return false; |
|
|
|
if ( CSGameRules()->IsFreezePeriod() ) |
|
{ |
|
// don't interpolate players position during freeze period |
|
SetAbsOrigin( GetNetworkOrigin() ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
int C_CSPlayer::GetMaxHealth() const |
|
{ |
|
return 100; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return the local player, or the player being spectated in-eye |
|
//----------------------------------------------------------------------------- |
|
C_CSPlayer* GetLocalOrInEyeCSPlayer( void ) |
|
{ |
|
C_CSPlayer *player = C_CSPlayer::GetLocalCSPlayer(); |
|
|
|
if( player && player->GetObserverMode() == OBS_MODE_IN_EYE ) |
|
{ |
|
C_BaseEntity *target = player->GetObserverTarget(); |
|
|
|
if( target && target->IsPlayer() ) |
|
{ |
|
return ToCSPlayer( target ); |
|
} |
|
} |
|
return player; |
|
} |
|
|
|
#define MAX_FLASHBANG_OPACITY 75.0f |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Update this client's targetid entity |
|
//----------------------------------------------------------------------------- |
|
void C_CSPlayer::UpdateIDTarget() |
|
{ |
|
if ( !IsLocalPlayer() ) |
|
return; |
|
|
|
// Clear old target and find a new one |
|
m_iIDEntIndex = 0; |
|
|
|
// don't show IDs if mp_playerid == 2 |
|
if ( mp_playerid.GetInt() == 2 ) |
|
return; |
|
|
|
// don't show IDs if mp_fadetoblack is on |
|
if ( mp_fadetoblack.GetBool() && !IsAlive() ) |
|
return; |
|
|
|
// don't show IDs in chase spec mode |
|
if ( GetObserverMode() == OBS_MODE_CHASE || |
|
GetObserverMode() == OBS_MODE_DEATHCAM ) |
|
return; |
|
|
|
//Check how much of a screen fade we have. |
|
//if it's more than 75 then we can't see what's going on so we don't display the id. |
|
byte color[4]; |
|
bool blend; |
|
vieweffects->GetFadeParams( &color[0], &color[1], &color[2], &color[3], &blend ); |
|
|
|
if ( color[3] > MAX_FLASHBANG_OPACITY && ( IsAlive() || GetObserverMode() == OBS_MODE_IN_EYE ) ) |
|
return; |
|
|
|
trace_t tr; |
|
Vector vecStart, vecEnd; |
|
VectorMA( MainViewOrigin(), 2500, MainViewForward(), vecEnd ); |
|
VectorMA( MainViewOrigin(), 10, MainViewForward(), vecStart ); |
|
UTIL_TraceLine( vecStart, vecEnd, MASK_VISIBLE_AND_NPCS, GetLocalOrInEyeCSPlayer(), COLLISION_GROUP_NONE, &tr ); |
|
if ( !tr.startsolid && !tr.DidHitNonWorldEntity() ) |
|
{ |
|
CTraceFilterSimple filter( GetLocalOrInEyeCSPlayer(), COLLISION_GROUP_NONE ); |
|
|
|
// Check for player hitboxes extending outside their collision bounds |
|
const float rayExtension = 40.0f; |
|
UTIL_ClipTraceToPlayers(vecStart, vecEnd + MainViewForward() * rayExtension, MASK_SOLID|CONTENTS_HITBOX, &filter, &tr ); |
|
} |
|
|
|
if ( !tr.startsolid && tr.DidHitNonWorldEntity() ) |
|
{ |
|
C_BaseEntity *pEntity = tr.m_pEnt; |
|
|
|
if ( pEntity && (pEntity != this) ) |
|
{ |
|
if ( mp_playerid.GetInt() == 1 ) // only show team names |
|
{ |
|
if ( pEntity->GetTeamNumber() != GetTeamNumber() ) |
|
{ |
|
return; |
|
} |
|
} |
|
|
|
//Adrian: If there's a smoke cloud in my way, don't display the name |
|
//We check this AFTER we found a player, just so we don't go thru this for nothing. |
|
for ( int i = 0; i < m_SmokeGrenades.Count(); i++ ) |
|
{ |
|
C_BaseParticleEntity *pSmokeGrenade = (C_BaseParticleEntity*)m_SmokeGrenades.Element( i ); |
|
|
|
if ( pSmokeGrenade ) |
|
{ |
|
float flHit1, flHit2; |
|
|
|
float flRadius = ( SMOKEGRENADE_PARTICLERADIUS * NUM_PARTICLES_PER_DIMENSION + 1 ) * 0.5f; |
|
|
|
Vector vPos = pSmokeGrenade->GetAbsOrigin(); |
|
|
|
/*debugoverlay->AddBoxOverlay( pSmokeGrenade->GetAbsOrigin(), Vector( flRadius, flRadius, flRadius ), |
|
Vector( -flRadius, -flRadius, -flRadius ), QAngle( 0, 0, 0 ), 255, 0, 0, 255, 0.2 );*/ |
|
|
|
if ( IntersectInfiniteRayWithSphere( MainViewOrigin(), MainViewForward(), vPos, flRadius, &flHit1, &flHit2 ) ) |
|
{ |
|
return; |
|
} |
|
} |
|
} |
|
|
|
if ( !GetIDTarget() && ( !m_iOldIDEntIndex || m_holdTargetIDTimer.IsElapsed() ) ) |
|
{ |
|
// track when we first mouse over the target |
|
m_delayTargetIDTimer.Start( mp_playerid_delay.GetFloat() ); |
|
} |
|
m_iIDEntIndex = pEntity->entindex(); |
|
|
|
m_iOldIDEntIndex = m_iIDEntIndex; |
|
m_holdTargetIDTimer.Start( mp_playerid_hold.GetFloat() ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handling |
|
//----------------------------------------------------------------------------- |
|
bool C_CSPlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd ) |
|
{ |
|
// Bleh... we will wind up needing to access bones for attachments in here. |
|
C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, true ); |
|
|
|
return BaseClass::CreateMove( flInputSampleTime, pCmd ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Flash this entity on the radar |
|
//----------------------------------------------------------------------------- |
|
bool C_CSPlayer::IsInHostageRescueZone() |
|
{ |
|
return m_bInHostageRescueZone; |
|
} |
|
|
|
CWeaponCSBase* C_CSPlayer::GetActiveCSWeapon() const |
|
{ |
|
return dynamic_cast< CWeaponCSBase* >( GetActiveWeapon() ); |
|
} |
|
|
|
CWeaponCSBase* C_CSPlayer::GetCSWeapon( CSWeaponID id ) const |
|
{ |
|
for (int i=0;i<MAX_WEAPONS;i++) |
|
{ |
|
CBaseCombatWeapon *weapon = GetWeapon( i ); |
|
if ( weapon ) |
|
{ |
|
CWeaponCSBase *csWeapon = dynamic_cast< CWeaponCSBase * >( weapon ); |
|
if ( csWeapon ) |
|
{ |
|
if ( id == csWeapon->GetWeaponID() ) |
|
{ |
|
return csWeapon; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//REMOVEME |
|
/* |
|
void C_CSPlayer::SetFireAnimation( PLAYER_ANIM playerAnim ) |
|
{ |
|
Activity idealActivity = ACT_WALK; |
|
|
|
// Figure out stuff about the current state. |
|
float speed = GetAbsVelocity().Length2D(); |
|
bool isMoving = ( speed != 0.0f ) ? true : false; |
|
bool isDucked = ( GetFlags() & FL_DUCKING ) ? true : false; |
|
bool isStillJumping = false; //!( GetFlags() & FL_ONGROUND ); |
|
bool isRunning = false; |
|
|
|
if ( speed > ARBITRARY_RUN_SPEED ) |
|
{ |
|
isRunning = true; |
|
} |
|
|
|
// Now figure out what to do based on the current state and the new state. |
|
switch ( playerAnim ) |
|
{ |
|
default: |
|
case PLAYER_RELOAD: |
|
case PLAYER_ATTACK1: |
|
case PLAYER_IDLE: |
|
case PLAYER_WALK: |
|
// Are we still jumping? |
|
// If so, keep playing the jump animation. |
|
if ( !isStillJumping ) |
|
{ |
|
idealActivity = ACT_WALK; |
|
|
|
if ( isDucked ) |
|
{ |
|
idealActivity = !isMoving ? ACT_CROUCHIDLE : ACT_RUN_CROUCH; |
|
} |
|
else |
|
{ |
|
if ( isRunning ) |
|
{ |
|
idealActivity = ACT_RUN; |
|
} |
|
else |
|
{ |
|
idealActivity = isMoving ? ACT_WALK : ACT_IDLE; |
|
} |
|
} |
|
|
|
// Allow body yaw to override for standing and turning in place |
|
idealActivity = m_PlayerAnimState.BodyYawTranslateActivity( idealActivity ); |
|
} |
|
break; |
|
|
|
case PLAYER_JUMP: |
|
idealActivity = ACT_HOP; |
|
break; |
|
|
|
case PLAYER_DIE: |
|
// Uses Ragdoll now??? |
|
idealActivity = ACT_DIESIMPLE; |
|
break; |
|
|
|
// FIXME: Use overlays for reload, start/leave aiming, attacking |
|
case PLAYER_START_AIMING: |
|
case PLAYER_LEAVE_AIMING: |
|
idealActivity = ACT_WALK; |
|
break; |
|
} |
|
|
|
CWeaponCSBase *pWeapon = GetActiveCSWeapon(); |
|
|
|
if ( pWeapon ) |
|
{ |
|
Activity aWeaponActivity = idealActivity; |
|
|
|
if ( playerAnim == PLAYER_ATTACK1 ) |
|
{ |
|
switch ( idealActivity ) |
|
{ |
|
case ACT_WALK: |
|
default: |
|
aWeaponActivity = ACT_PLAYER_WALK_FIRE; |
|
break; |
|
case ACT_RUN: |
|
aWeaponActivity = ACT_PLAYER_RUN_FIRE; |
|
break; |
|
case ACT_IDLE: |
|
aWeaponActivity = ACT_PLAYER_IDLE_FIRE; |
|
break; |
|
case ACT_CROUCHIDLE: |
|
aWeaponActivity = ACT_PLAYER_CROUCH_FIRE; |
|
break; |
|
case ACT_RUN_CROUCH: |
|
aWeaponActivity = ACT_PLAYER_CROUCH_WALK_FIRE; |
|
break; |
|
} |
|
} |
|
|
|
m_PlayerAnimState.SetWeaponLayerSequence( pWeapon->GetCSWpnData().m_szAnimExtension, aWeaponActivity ); |
|
} |
|
} |
|
*/ |
|
|
|
ShadowType_t C_CSPlayer::ShadowCastType( void ) |
|
{ |
|
if ( !IsVisible() ) |
|
return SHADOWS_NONE; |
|
|
|
return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns whether or not we can switch to the given weapon. |
|
// Input : pWeapon - |
|
//----------------------------------------------------------------------------- |
|
bool C_CSPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) |
|
{ |
|
if ( !pWeapon->CanDeploy() ) |
|
return false; |
|
|
|
if ( GetActiveWeapon() ) |
|
{ |
|
if ( !GetActiveWeapon()->CanHolster() ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
void C_CSPlayer::UpdateClientSideAnimation() |
|
{ |
|
// We do this in a different order than the base class. |
|
// We need our cycle to be valid for when we call the playeranimstate update code, |
|
// or else it'll synchronize the upper body anims with the wrong cycle. |
|
if ( GetSequence() != -1 ) |
|
{ |
|
// move frame forward |
|
FrameAdvance( 0.0f ); // 0 means to use the time we last advanced instead of a constant |
|
} |
|
|
|
// Update the animation data. It does the local check here so this works when using |
|
// a third-person camera (and we don't have valid player angles). |
|
if ( this == C_CSPlayer::GetLocalCSPlayer() ) |
|
m_PlayerAnimState->Update( EyeAngles()[YAW], m_angEyeAngles[PITCH] ); |
|
else |
|
m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] ); |
|
|
|
if ( GetSequence() != -1 ) |
|
{ |
|
// latch old values |
|
OnLatchInterpolatedVariables( LATCH_ANIMATION_VAR ); |
|
} |
|
} |
|
|
|
|
|
float g_flMuzzleFlashScale=1; |
|
|
|
void C_CSPlayer::ProcessMuzzleFlashEvent() |
|
{ |
|
CBasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); |
|
|
|
// Reenable when the weapons have muzzle flash attachments in the right spot. |
|
if ( this == pLocalPlayer ) |
|
return; // don't show own world muzzle flashs in for localplayer |
|
|
|
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE ) |
|
{ |
|
// also don't show in 1st person spec mode |
|
if ( pLocalPlayer->GetObserverTarget() == this ) |
|
return; |
|
} |
|
|
|
CWeaponCSBase *pWeapon = GetActiveCSWeapon(); |
|
|
|
if ( !pWeapon ) |
|
return; |
|
|
|
bool hasMuzzleFlash = (pWeapon->GetMuzzleFlashStyle() != CS_MUZZLEFLASH_NONE); |
|
|
|
Vector vector; |
|
QAngle angles; |
|
|
|
int iAttachment = LookupAttachment( "muzzle_flash" ); |
|
|
|
if ( iAttachment >= 0 ) |
|
{ |
|
bool bFoundAttachment = GetAttachment( iAttachment, vector, angles ); |
|
// If we have an attachment, then stick a light on it. |
|
if ( bFoundAttachment ) |
|
{ |
|
if ( hasMuzzleFlash ) |
|
{ |
|
dlight_t *el = effects->CL_AllocDlight( LIGHT_INDEX_MUZZLEFLASH + index ); |
|
el->origin = vector; |
|
el->radius = 70; |
|
el->decay = el->radius / 0.05f; |
|
el->die = gpGlobals->curtime + 0.05f; |
|
el->color.r = 255; |
|
el->color.g = 192; |
|
el->color.b = 64; |
|
el->color.exponent = 5; |
|
} |
|
|
|
int shellType = GetShellForAmmoType( pWeapon->GetCSWpnData().szAmmo1 ); |
|
|
|
QAngle playerAngle = EyeAngles(); |
|
Vector vForward, vRight, vUp; |
|
|
|
AngleVectors( playerAngle, &vForward, &vRight, &vUp ); |
|
|
|
QAngle angVelocity; |
|
Vector vVel = vRight * 100 + vUp * 20; |
|
VectorAngles( vVel, angVelocity ); |
|
|
|
if ( pWeapon->GetMaxClip1() > 0 ) |
|
{ |
|
tempents->CSEjectBrass( vector, angVelocity, 120, shellType, this ); |
|
} |
|
} |
|
} |
|
|
|
if ( hasMuzzleFlash ) |
|
{ |
|
iAttachment = pWeapon->GetMuzzleAttachment(); |
|
|
|
if ( iAttachment > 0 ) |
|
{ |
|
float flScale = pWeapon->GetCSWpnData().m_flMuzzleScale; |
|
flScale *= 0.75; |
|
FX_MuzzleEffectAttached( flScale, pWeapon->GetRefEHandle(), iAttachment, NULL, false ); |
|
|
|
} |
|
} |
|
} |
|
|
|
const QAngle& C_CSPlayer::EyeAngles() |
|
{ |
|
if ( IsLocalPlayer() && !g_nKillCamMode ) |
|
{ |
|
return BaseClass::EyeAngles(); |
|
} |
|
else |
|
{ |
|
return m_angEyeAngles; |
|
} |
|
} |
|
|
|
bool C_CSPlayer::ShouldDraw( void ) |
|
{ |
|
// If we're dead, our ragdoll will be drawn for us instead. |
|
if ( !IsAlive() ) |
|
return false; |
|
|
|
if( GetTeamNumber() == TEAM_SPECTATOR ) |
|
return false; |
|
|
|
if( IsLocalPlayer() ) |
|
{ |
|
if ( IsRagdoll() ) |
|
return true; |
|
} |
|
|
|
return BaseClass::ShouldDraw(); |
|
} |
|
|
|
|
|
bool FindWeaponAttachmentBone( C_BaseCombatWeapon *pWeapon, int &iWeaponBone ) |
|
{ |
|
if ( !pWeapon ) |
|
return false; |
|
|
|
CStudioHdr *pHdr = pWeapon->GetModelPtr(); |
|
if ( !pHdr ) |
|
return false; |
|
|
|
for ( iWeaponBone=0; iWeaponBone < pHdr->numbones(); iWeaponBone++ ) |
|
{ |
|
if ( stricmp( pHdr->pBone( iWeaponBone )->pszName(), "L_Hand_Attach" ) == 0 ) |
|
break; |
|
} |
|
|
|
return iWeaponBone != pHdr->numbones(); |
|
} |
|
|
|
|
|
bool FindMyAttachmentBone( C_BaseAnimating *pModel, int &iBone, CStudioHdr *pHdr ) |
|
{ |
|
if ( !pHdr ) |
|
return false; |
|
|
|
for ( iBone=0; iBone < pHdr->numbones(); iBone++ ) |
|
{ |
|
if ( stricmp( pHdr->pBone( iBone )->pszName(), "Valvebiped.Bip01_L_Hand" ) == 0 ) |
|
break; |
|
} |
|
|
|
return iBone != pHdr->numbones(); |
|
} |
|
|
|
|
|
inline bool IsBoneChildOf( CStudioHdr *pHdr, int iBone, int iParent ) |
|
{ |
|
if ( iBone == iParent ) |
|
return false; |
|
|
|
while ( iBone != -1 ) |
|
{ |
|
if ( iBone == iParent ) |
|
return true; |
|
|
|
iBone = pHdr->pBone( iBone )->parent; |
|
} |
|
return false; |
|
} |
|
|
|
void ApplyDifferenceTransformToChildren( |
|
C_BaseAnimating *pModel, |
|
const matrix3x4_t &mSource, |
|
const matrix3x4_t &mDest, |
|
int iParentBone ) |
|
{ |
|
CStudioHdr *pHdr = pModel->GetModelPtr(); |
|
if ( !pHdr ) |
|
return; |
|
|
|
// Build a matrix to go from mOriginalHand to mHand. |
|
// ( mDest * Inverse( mSource ) ) * mSource = mDest |
|
matrix3x4_t mSourceInverse, mToDest; |
|
MatrixInvert( mSource, mSourceInverse ); |
|
ConcatTransforms( mDest, mSourceInverse, mToDest ); |
|
|
|
// Now multiply iMyBone and all its children by mToWeaponBone. |
|
for ( int i=0; i < pHdr->numbones(); i++ ) |
|
{ |
|
if ( IsBoneChildOf( pHdr, i, iParentBone ) ) |
|
{ |
|
matrix3x4_t &mCur = pModel->GetBoneForWrite( i ); |
|
matrix3x4_t mNew; |
|
ConcatTransforms( mToDest, mCur, mNew ); |
|
mCur = mNew; |
|
} |
|
} |
|
} |
|
|
|
|
|
void GetCorrectionMatrices( |
|
const matrix3x4_t &mShoulder, |
|
const matrix3x4_t &mElbow, |
|
const matrix3x4_t &mHand, |
|
matrix3x4_t &mShoulderCorrection, |
|
matrix3x4_t &mElbowCorrection |
|
) |
|
{ |
|
extern void Studio_AlignIKMatrix( matrix3x4_t &mMat, const Vector &vAlignTo ); |
|
|
|
// Get the positions of each node so we can get the direction vectors. |
|
Vector vShoulder, vElbow, vHand; |
|
MatrixPosition( mShoulder, vShoulder ); |
|
MatrixPosition( mElbow, vElbow ); |
|
MatrixPosition( mHand, vHand ); |
|
|
|
// Get rid of the translation. |
|
matrix3x4_t mOriginalShoulder = mShoulder; |
|
matrix3x4_t mOriginalElbow = mElbow; |
|
MatrixSetColumn( Vector( 0, 0, 0 ), 3, mOriginalShoulder ); |
|
MatrixSetColumn( Vector( 0, 0, 0 ), 3, mOriginalElbow ); |
|
|
|
// Let the IK code align them like it would if we did IK on the joint. |
|
matrix3x4_t mAlignedShoulder = mOriginalShoulder; |
|
matrix3x4_t mAlignedElbow = mOriginalElbow; |
|
Studio_AlignIKMatrix( mAlignedShoulder, vElbow-vShoulder ); |
|
Studio_AlignIKMatrix( mAlignedElbow, vHand-vElbow ); |
|
|
|
// Figure out the transformation from the aligned bones to the original ones. |
|
matrix3x4_t mInvAlignedShoulder, mInvAlignedElbow; |
|
MatrixInvert( mAlignedShoulder, mInvAlignedShoulder ); |
|
MatrixInvert( mAlignedElbow, mInvAlignedElbow ); |
|
|
|
ConcatTransforms( mInvAlignedShoulder, mOriginalShoulder, mShoulderCorrection ); |
|
ConcatTransforms( mInvAlignedElbow, mOriginalElbow, mElbowCorrection ); |
|
} |
|
|
|
|
|
void C_CSPlayer::BuildTransformations( CStudioHdr *pHdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed ) |
|
{ |
|
// First, setup our model's transformations like normal. |
|
BaseClass::BuildTransformations( pHdr, pos, q, cameraTransform, boneMask, boneComputed ); |
|
|
|
if ( IsLocalPlayer() && !C_BasePlayer::ShouldDrawLocalPlayer() ) |
|
return; |
|
|
|
if ( !cl_left_hand_ik.GetInt() ) |
|
return; |
|
|
|
// If our current weapon has a bone named L_Hand_Attach, then we attach the player's |
|
// left hand (Valvebiped.Bip01_L_Hand) to it. |
|
C_BaseCombatWeapon *pWeapon = GetActiveWeapon(); |
|
|
|
if ( !pWeapon ) |
|
return; |
|
|
|
// Have the weapon setup its bones. |
|
pWeapon->SetupBones( NULL, 0, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); |
|
|
|
int iWeaponBone = 0; |
|
if ( FindWeaponAttachmentBone( pWeapon, iWeaponBone ) ) |
|
{ |
|
int iMyBone = 0; |
|
if ( FindMyAttachmentBone( this, iMyBone, pHdr ) ) |
|
{ |
|
int iHand = iMyBone; |
|
int iElbow = pHdr->pBone( iHand )->parent; |
|
int iShoulder = pHdr->pBone( iElbow )->parent; |
|
matrix3x4_t *pBones = &GetBoneForWrite( 0 ); |
|
|
|
// Store off the original hand position. |
|
matrix3x4_t mSource = pBones[iHand]; |
|
|
|
|
|
// Figure out the rotation offset from the current shoulder and elbow bone rotations |
|
// and what the IK code's alignment code is going to produce, because we'll have to |
|
// re-apply that offset after the IK runs. |
|
matrix3x4_t mShoulderCorrection, mElbowCorrection; |
|
GetCorrectionMatrices( pBones[iShoulder], pBones[iElbow], pBones[iHand], mShoulderCorrection, mElbowCorrection ); |
|
|
|
|
|
// Do the IK solution. |
|
Vector vHandTarget; |
|
MatrixPosition( pWeapon->GetBone( iWeaponBone ), vHandTarget ); |
|
Studio_SolveIK( iShoulder, iElbow, iHand, vHandTarget, pBones ); |
|
|
|
|
|
// Now reapply the rotation correction. |
|
matrix3x4_t mTempShoulder = pBones[iShoulder]; |
|
matrix3x4_t mTempElbow = pBones[iElbow]; |
|
ConcatTransforms( mTempShoulder, mShoulderCorrection, pBones[iShoulder] ); |
|
ConcatTransforms( mTempElbow, mElbowCorrection, pBones[iElbow] ); |
|
|
|
|
|
// Now apply the transformation on the hand to the fingers. |
|
matrix3x4_t &mDest = GetBoneForWrite( iHand ); |
|
ApplyDifferenceTransformToChildren( this, mSource, mDest, iHand ); |
|
} |
|
} |
|
} |
|
|
|
|
|
C_BaseAnimating * C_CSPlayer::BecomeRagdollOnClient() |
|
{ |
|
return NULL; |
|
} |
|
|
|
|
|
IRagdoll* C_CSPlayer::GetRepresentativeRagdoll() const |
|
{ |
|
if ( m_hRagdoll.Get() ) |
|
{ |
|
C_CSRagdoll *pRagdoll = (C_CSRagdoll*)m_hRagdoll.Get(); |
|
|
|
return pRagdoll->GetIRagdoll(); |
|
} |
|
else |
|
{ |
|
return NULL; |
|
} |
|
} |
|
|
|
|
|
void C_CSPlayer::PlayReloadEffect() |
|
{ |
|
// Only play the effect for other players. |
|
if ( this == C_CSPlayer::GetLocalCSPlayer() ) |
|
{ |
|
Assert( false ); // We shouldn't have been sent this message. |
|
return; |
|
} |
|
|
|
// Get the view model for our current gun. |
|
CWeaponCSBase *pWeapon = GetActiveCSWeapon(); |
|
if ( !pWeapon ) |
|
return; |
|
|
|
// The weapon needs two models, world and view, but can only cache one. Synthesize the other. |
|
const CCSWeaponInfo &info = pWeapon->GetCSWpnData(); |
|
const model_t *pModel = modelinfo->GetModel( modelinfo->GetModelIndex( info.szViewModel ) ); |
|
if ( !pModel ) |
|
return; |
|
CStudioHdr studioHdr( modelinfo->GetStudiomodel( pModel ), mdlcache ); |
|
if ( !studioHdr.IsValid() ) |
|
return; |
|
|
|
// Find the reload animation. |
|
for ( int iSeq=0; iSeq < studioHdr.GetNumSeq(); iSeq++ ) |
|
{ |
|
mstudioseqdesc_t *pSeq = &studioHdr.pSeqdesc( iSeq ); |
|
|
|
if ( pSeq->activity == ACT_VM_RELOAD ) |
|
{ |
|
float poseParameters[MAXSTUDIOPOSEPARAM]; |
|
memset( poseParameters, 0, sizeof( poseParameters ) ); |
|
float cyclesPerSecond = Studio_CPS( &studioHdr, *pSeq, iSeq, poseParameters ); |
|
|
|
// Now read out all the sound events with their timing |
|
for ( int iEvent=0; iEvent < pSeq->numevents; iEvent++ ) |
|
{ |
|
mstudioevent_t *pEvent = pSeq->pEvent( iEvent ); |
|
|
|
if ( pEvent->event == CL_EVENT_SOUND ) |
|
{ |
|
CCSSoundEvent event; |
|
event.m_SoundName = pEvent->options; |
|
event.m_flEventTime = gpGlobals->curtime + pEvent->cycle / cyclesPerSecond; |
|
m_SoundEvents.AddToTail( event ); |
|
} |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
void C_CSPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) |
|
{ |
|
if ( event == PLAYERANIMEVENT_THROW_GRENADE ) |
|
{ |
|
// Let the server handle this event. It will update m_iThrowGrenadeCounter and the client will |
|
// pick up the event in CCSPlayerAnimState. |
|
} |
|
else |
|
{ |
|
m_PlayerAnimState->DoAnimationEvent( event, nData ); |
|
} |
|
} |
|
|
|
void C_CSPlayer::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) |
|
{ |
|
if( event == 7001 ) |
|
{ |
|
bool bInWater = ( enginetrace->GetPointContents(origin) & CONTENTS_WATER ); |
|
|
|
//Msg( "run event ( %d )\n", bInWater ? 1 : 0 ); |
|
|
|
if( bInWater ) |
|
{ |
|
//run splash |
|
CEffectData data; |
|
|
|
//trace up from foot position to the water surface |
|
trace_t tr; |
|
Vector vecTrace(0,0,1024); |
|
UTIL_TraceLine( origin, origin + vecTrace, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.fractionleftsolid ) |
|
{ |
|
data.m_vOrigin = origin + (vecTrace * tr.fractionleftsolid); |
|
} |
|
else |
|
{ |
|
data.m_vOrigin = origin; |
|
} |
|
|
|
data.m_vNormal = Vector( 0,0,1 ); |
|
data.m_flScale = random->RandomFloat( 4.0f, 5.0f ); |
|
DispatchEffect( "watersplash", data ); |
|
} |
|
} |
|
else if( event == 7002 ) |
|
{ |
|
bool bInWater = ( enginetrace->GetPointContents(origin) & CONTENTS_WATER ); |
|
|
|
//Msg( "walk event ( %d )\n", bInWater ? 1 : 0 ); |
|
|
|
if( bInWater ) |
|
{ |
|
//walk ripple |
|
CEffectData data; |
|
|
|
//trace up from foot position to the water surface |
|
trace_t tr; |
|
Vector vecTrace(0,0,1024); |
|
UTIL_TraceLine( origin, origin + vecTrace, MASK_WATER, NULL, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.fractionleftsolid ) |
|
{ |
|
data.m_vOrigin = origin + (vecTrace * tr.fractionleftsolid); |
|
} |
|
else |
|
{ |
|
data.m_vOrigin = origin; |
|
} |
|
|
|
data.m_vNormal = Vector( 0,0,1 ); |
|
data.m_flScale = random->RandomFloat( 4.0f, 7.0f ); |
|
DispatchEffect( "waterripple", data ); |
|
} |
|
} |
|
else |
|
BaseClass::FireEvent( origin, angles, event, options ); |
|
} |
|
|
|
|
|
void C_CSPlayer::SetActivity( Activity eActivity ) |
|
{ |
|
m_Activity = eActivity; |
|
} |
|
|
|
|
|
Activity C_CSPlayer::GetActivity() const |
|
{ |
|
return m_Activity; |
|
} |
|
|
|
|
|
const Vector& C_CSPlayer::GetRenderOrigin( void ) |
|
{ |
|
if ( m_hRagdoll.Get() ) |
|
{ |
|
C_CSRagdoll *pRagdoll = (C_CSRagdoll*)m_hRagdoll.Get(); |
|
if ( pRagdoll->IsInitialized() ) |
|
return pRagdoll->GetRenderOrigin(); |
|
} |
|
|
|
return BaseClass::GetRenderOrigin(); |
|
} |
|
|
|
|
|
void C_CSPlayer::Simulate( void ) |
|
{ |
|
if( this != C_BasePlayer::GetLocalPlayer() ) |
|
{ |
|
if ( IsEffectActive( EF_DIMLIGHT ) ) |
|
{ |
|
QAngle eyeAngles = EyeAngles(); |
|
Vector vForward; |
|
AngleVectors( eyeAngles, &vForward ); |
|
|
|
int iAttachment = LookupAttachment( "muzzle_flash" ); |
|
|
|
if ( iAttachment < 0 ) |
|
return; |
|
|
|
Vector vecOrigin; |
|
QAngle dummy; |
|
GetAttachment( iAttachment, vecOrigin, dummy ); |
|
|
|
trace_t tr; |
|
UTIL_TraceLine( vecOrigin, vecOrigin + (vForward * 200), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if( !m_pFlashlightBeam ) |
|
{ |
|
BeamInfo_t beamInfo; |
|
beamInfo.m_nType = TE_BEAMPOINTS; |
|
beamInfo.m_vecStart = tr.startpos; |
|
beamInfo.m_vecEnd = tr.endpos; |
|
beamInfo.m_pszModelName = "sprites/glow01.vmt"; |
|
beamInfo.m_pszHaloName = "sprites/glow01.vmt"; |
|
beamInfo.m_flHaloScale = 3.0; |
|
beamInfo.m_flWidth = 8.0f; |
|
beamInfo.m_flEndWidth = 35.0f; |
|
beamInfo.m_flFadeLength = 300.0f; |
|
beamInfo.m_flAmplitude = 0; |
|
beamInfo.m_flBrightness = 60.0; |
|
beamInfo.m_flSpeed = 0.0f; |
|
beamInfo.m_nStartFrame = 0.0; |
|
beamInfo.m_flFrameRate = 0.0; |
|
beamInfo.m_flRed = 255.0; |
|
beamInfo.m_flGreen = 255.0; |
|
beamInfo.m_flBlue = 255.0; |
|
beamInfo.m_nSegments = 8; |
|
beamInfo.m_bRenderable = true; |
|
beamInfo.m_flLife = 0.5; |
|
beamInfo.m_nFlags = FBEAM_FOREVER | FBEAM_ONLYNOISEONCE | FBEAM_NOTILE | FBEAM_HALOBEAM; |
|
|
|
m_pFlashlightBeam = beams->CreateBeamPoints( beamInfo ); |
|
} |
|
|
|
if( m_pFlashlightBeam ) |
|
{ |
|
BeamInfo_t beamInfo; |
|
beamInfo.m_vecStart = tr.startpos; |
|
beamInfo.m_vecEnd = tr.endpos; |
|
beamInfo.m_flRed = 255.0; |
|
beamInfo.m_flGreen = 255.0; |
|
beamInfo.m_flBlue = 255.0; |
|
|
|
beams->UpdateBeamInfo( m_pFlashlightBeam, beamInfo ); |
|
|
|
dlight_t *el = effects->CL_AllocDlight( 0 ); |
|
el->origin = tr.endpos; |
|
el->radius = 50; |
|
el->color.r = 200; |
|
el->color.g = 200; |
|
el->color.b = 200; |
|
el->die = gpGlobals->curtime + 0.1; |
|
} |
|
} |
|
else if ( m_pFlashlightBeam ) |
|
{ |
|
ReleaseFlashlight(); |
|
} |
|
} |
|
|
|
BaseClass::Simulate(); |
|
} |
|
|
|
void C_CSPlayer::ReleaseFlashlight( void ) |
|
{ |
|
if( m_pFlashlightBeam ) |
|
{ |
|
m_pFlashlightBeam->flags = 0; |
|
m_pFlashlightBeam->die = gpGlobals->curtime - 1; |
|
|
|
m_pFlashlightBeam = NULL; |
|
} |
|
} |
|
|
|
bool C_CSPlayer::HasC4( void ) |
|
{ |
|
if( this == C_CSPlayer::GetLocalPlayer() ) |
|
{ |
|
return Weapon_OwnsThisType( "weapon_c4" ); |
|
} |
|
else |
|
{ |
|
C_CS_PlayerResource *pCSPR = (C_CS_PlayerResource*)GameResources(); |
|
|
|
return pCSPR->HasC4( entindex() ); |
|
} |
|
} |
|
|
|
void C_CSPlayer::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ) |
|
{ |
|
static ConVar *violence_hblood = cvar->FindVar( "violence_hblood" ); |
|
if ( violence_hblood && !violence_hblood->GetBool() ) |
|
return; |
|
|
|
BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void C_CSPlayer::CalcObserverView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ) |
|
{ |
|
/** |
|
* TODO: Fix this! |
|
// CS:S standing eyeheight is above the collision volume, so we need to pull it |
|
// down when we go into close quarters. |
|
float maxEyeHeightAboveBounds = VEC_VIEW_SCALED( this ).z - VEC_HULL_MAX_SCALED( this ).z; |
|
if ( GetObserverMode() == OBS_MODE_IN_EYE && |
|
maxEyeHeightAboveBounds > 0.0f && |
|
GetObserverTarget() && |
|
GetObserverTarget()->IsPlayer() ) |
|
{ |
|
const float eyeClearance = 12.0f; // eye pos must be this far below the ceiling |
|
|
|
C_CSPlayer *target = ToCSPlayer( GetObserverTarget() ); |
|
|
|
Vector offset = eyeOrigin - GetAbsOrigin(); |
|
|
|
Vector vHullMin = VEC_HULL_MIN_SCALED( this ); |
|
vHullMin.z = 0.0f; |
|
Vector vHullMax = VEC_HULL_MAX_SCALED( this ); |
|
|
|
Vector start = GetAbsOrigin(); |
|
start.z += vHullMax.z; |
|
Vector end = start; |
|
end.z += eyeClearance + VEC_VIEW_SCALED( this ).z - vHullMax_SCALED( this ).z; |
|
|
|
vHullMax.z = 0.0f; |
|
|
|
Vector fudge( 1, 1, 0 ); |
|
vHullMin += fudge; |
|
vHullMax -= fudge; |
|
|
|
trace_t trace; |
|
Ray_t ray; |
|
ray.Init( start, end, vHullMin, vHullMax ); |
|
UTIL_TraceRay( ray, MASK_PLAYERSOLID, target, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); |
|
|
|
if ( trace.fraction < 1.0f ) |
|
{ |
|
float est = start.z + trace.fraction * (end.z - start.z) - GetAbsOrigin().z - eyeClearance; |
|
if ( ( target->GetFlags() & FL_DUCKING ) == 0 && !target->GetFallVelocity() && !target->IsDucked() ) |
|
{ |
|
offset.z = est; |
|
} |
|
else |
|
{ |
|
offset.z = MIN( est, offset.z ); |
|
} |
|
eyeOrigin.z = GetAbsOrigin().z + offset.z; |
|
} |
|
} |
|
*/ |
|
|
|
BaseClass::CalcObserverView( eyeOrigin, eyeAngles, fov ); |
|
} |
|
|
|
//============================================================================= |
|
// HPE_BEGIN: |
|
//============================================================================= |
|
// [tj] checks if this player has another given player on their Steam friends list. |
|
bool C_CSPlayer::HasPlayerAsFriend(C_CSPlayer* player) |
|
{ |
|
if (!steamapicontext || !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() || !player) |
|
{ |
|
return false; |
|
} |
|
|
|
player_info_t pi; |
|
if ( !engine->GetPlayerInfo( player->entindex(), &pi ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( !pi.friendsID ) |
|
{ |
|
return false; |
|
} |
|
|
|
// check and see if they're on the local player's friends list |
|
CSteamID steamID( pi.friendsID, 1, steamapicontext->SteamUtils()->GetConnectedUniverse(), k_EAccountTypeIndividual ); |
|
return steamapicontext->SteamFriends()->HasFriend( steamID, k_EFriendFlagImmediate); |
|
} |
|
|
|
// [menglish] Returns whether this player is dominating or is being dominated by the specified player |
|
bool C_CSPlayer::IsPlayerDominated( int iPlayerIndex ) |
|
{ |
|
return m_bPlayerDominated.Get( iPlayerIndex ); |
|
} |
|
|
|
bool C_CSPlayer::IsPlayerDominatingMe( int iPlayerIndex ) |
|
{ |
|
return m_bPlayerDominatingMe.Get( iPlayerIndex ); |
|
} |
|
|
|
|
|
// helper interpolation functions |
|
namespace Interpolators |
|
{ |
|
inline float Linear( float t ) { return t; } |
|
|
|
inline float SmoothStep( float t ) |
|
{ |
|
t = 3 * t * t - 2.0f * t * t * t; |
|
return t; |
|
} |
|
|
|
inline float SmoothStep2( float t ) |
|
{ |
|
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); |
|
} |
|
|
|
inline float SmoothStepStart( float t ) |
|
{ |
|
t = 0.5f * t; |
|
t = 3 * t * t - 2.0f * t * t * t; |
|
t = t* 2.0f; |
|
return t; |
|
} |
|
|
|
inline float SmoothStepEnd( float t ) |
|
{ |
|
t = 0.5f * t + 0.5f; |
|
t = 3 * t * t - 2.0f * t * t * t; |
|
t = (t - 0.5f) * 2.0f; |
|
return t; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Calculate the view for the player while he's in freeze frame observer mode |
|
//----------------------------------------------------------------------------- |
|
void C_CSPlayer::CalcFreezeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ) |
|
{ |
|
C_BaseEntity *pTarget = GetObserverTarget(); |
|
|
|
//============================================================================= |
|
// HPE_BEGIN: |
|
// [Forrest] Added sv_disablefreezecam check |
|
//============================================================================= |
|
static ConVarRef sv_disablefreezecam( "sv_disablefreezecam" ); |
|
if ( !pTarget || cl_disablefreezecam.GetBool() || sv_disablefreezecam.GetBool() ) |
|
//============================================================================= |
|
// HPE_END |
|
//============================================================================= |
|
{ |
|
return CalcDeathCamView( eyeOrigin, eyeAngles, fov ); |
|
} |
|
|
|
// pick a zoom camera target |
|
Vector vLookAt = pTarget->GetObserverCamOrigin(); // Returns ragdoll origin if they're ragdolled |
|
vLookAt += GetChaseCamViewOffset( pTarget ); |
|
|
|
// look over ragdoll, not through |
|
if ( !pTarget->IsAlive() ) |
|
vLookAt.z += pTarget->GetBaseAnimating() ? VEC_DEAD_VIEWHEIGHT_SCALED( pTarget->GetBaseAnimating() ).z : VEC_DEAD_VIEWHEIGHT.z; |
|
|
|
// Figure out a view position in front of the target |
|
Vector vEyeOnPlane = eyeOrigin; |
|
vEyeOnPlane.z = vLookAt.z; |
|
Vector vToTarget = vLookAt - vEyeOnPlane; |
|
VectorNormalize( vToTarget ); |
|
|
|
// goal position of camera is pulled away from target by m_flFreezeFrameDistance |
|
Vector vTargetPos = vLookAt - (vToTarget * m_flFreezeFrameDistance); |
|
|
|
// Now trace out from the target, so that we're put in front of any walls |
|
trace_t trace; |
|
C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace |
|
UTIL_TraceHull( vLookAt, vTargetPos, WALL_MIN, WALL_MAX, MASK_SOLID, pTarget, COLLISION_GROUP_NONE, &trace ); |
|
C_BaseEntity::PopEnableAbsRecomputations(); |
|
if ( trace.fraction < 1.0 ) |
|
{ |
|
// The camera's going to be really close to the target. So we don't end up |
|
// looking at someone's chest, aim close freezecams at the target's eyes. |
|
vTargetPos = trace.endpos; |
|
|
|
// To stop all close in views looking up at character's chins, move the view up. |
|
vTargetPos.z += fabs(vLookAt.z - vTargetPos.z) * 0.85; |
|
C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace |
|
UTIL_TraceHull( vLookAt, vTargetPos, WALL_MIN, WALL_MAX, MASK_SOLID, pTarget, COLLISION_GROUP_NONE, &trace ); |
|
C_BaseEntity::PopEnableAbsRecomputations(); |
|
vTargetPos = trace.endpos; |
|
} |
|
|
|
// Look directly at the target |
|
vToTarget = vLookAt - vTargetPos; |
|
VectorNormalize( vToTarget ); |
|
VectorAngles( vToTarget, eyeAngles ); |
|
|
|
float fCurTime = gpGlobals->curtime - m_flFreezeFrameStartTime; |
|
float fInterpolant = clamp( fCurTime / spec_freeze_traveltime.GetFloat(), 0.0f, 1.0f ); |
|
fInterpolant = Interpolators::SmoothStepEnd( fInterpolant ); |
|
|
|
// move the eye toward our killer |
|
VectorLerp( m_vecFreezeFrameStart, vTargetPos, fInterpolant, eyeOrigin ); |
|
|
|
if ( fCurTime >= spec_freeze_traveltime.GetFloat() && !m_bSentFreezeFrame ) |
|
{ |
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "freezecam_started" ); |
|
if ( pEvent ) |
|
{ |
|
gameeventmanager->FireEventClientSide( pEvent ); |
|
} |
|
|
|
m_bSentFreezeFrame = true; |
|
view->FreezeFrame( spec_freeze_time.GetFloat() ); |
|
} |
|
} |
|
|
|
float C_CSPlayer::GetDeathCamInterpolationTime() |
|
{ |
|
static ConVarRef sv_disablefreezecam( "sv_disablefreezecam" ); |
|
if ( cl_disablefreezecam.GetBool() || sv_disablefreezecam.GetBool() || !GetObserverTarget() ) |
|
return spec_freeze_time.GetFloat(); |
|
else |
|
return CS_DEATH_ANIMATION_TIME; |
|
|
|
} |
|
|
|
|
|
//============================================================================= |
|
// HPE_END |
|
//============================================================================= |
|
|
|
|