//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Client-side CBasePlayer. // // - Manages the player's flashlight effect. // //===========================================================================// #include "cbase.h" #include "c_baseplayer.h" #include "flashlighteffect.h" #include "weapon_selection.h" #include "history_resource.h" #include "iinput.h" #include "input.h" #include "view.h" #include "iviewrender.h" #include "iclientmode.h" #include "in_buttons.h" #include "engine/IEngineSound.h" #include "c_soundscape.h" #include "usercmd.h" #include "c_playerresource.h" #include "iclientvehicle.h" #include "view_shared.h" #include "movevars_shared.h" #include "prediction.h" #include "tier0/vprof.h" #include "filesystem.h" #include "bitbuf.h" #include "KeyValues.h" #include "particles_simple.h" #include "fx_water.h" #include "hltvcamera.h" #include "toolframework/itoolframework.h" #include "toolframework_client.h" #include "view_scene.h" #include "c_vguiscreen.h" #include "datacache/imdlcache.h" #include "vgui/ISurface.h" #include "voice_status.h" #include "fx.h" #include "dt_utlvector_recv.h" #include "cam_thirdperson.h" #if defined( REPLAY_ENABLED ) #include "replay/replaycamera.h" #include "replay/ireplaysystem.h" #include "replay/ienginereplay.h" #endif #include "steam/steam_api.h" #include "sourcevr/isourcevirtualreality.h" #include "client_virtualreality.h" #ifdef TF_CLIENT_DLL #include "tf_gamerules.h" #endif #if defined USES_ECON_ITEMS #include "econ_wearable.h" #endif // NVNT haptics system interface #include "haptics/ihaptics.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // Don't alias here #if defined( CBasePlayer ) #undef CBasePlayer #endif int g_nKillCamMode = OBS_MODE_NONE; int g_nKillCamTarget1 = 0; int g_nKillCamTarget2 = 0; extern ConVar mp_forcecamera; // in gamevars_shared.h #define FLASHLIGHT_DISTANCE 1000 #define MAX_VGUI_INPUT_MODE_SPEED 30 #define MAX_VGUI_INPUT_MODE_SPEED_SQ (MAX_VGUI_INPUT_MODE_SPEED*MAX_VGUI_INPUT_MODE_SPEED) static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET); static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET); bool CommentaryModeShouldSwallowInput( C_BasePlayer *pPlayer ); extern ConVar default_fov; #ifndef _XBOX extern ConVar sensitivity; #endif static C_BasePlayer *s_pLocalPlayer = NULL; static ConVar cl_customsounds ( "cl_customsounds", "0", 0, "Enable customized player sound playback" ); static ConVar spec_track ( "spec_track", "0", 0, "Tracks an entity in spec mode" ); static ConVar cl_smooth ( "cl_smooth", "1", 0, "Smooth view/eye origin after prediction errors" ); static ConVar cl_smoothtime ( "cl_smoothtime", "0.1", 0, "Smooth client's view after prediction error over this many seconds", true, 0.01, // min/max is 0.01/2.0 true, 2.0 ); #ifdef CSTRIKE_DLL ConVar spec_freeze_time( "spec_freeze_time", "5.0", FCVAR_CHEAT | FCVAR_REPLICATED, "Time spend frozen in observer freeze cam." ); ConVar spec_freeze_traveltime( "spec_freeze_traveltime", "0.7", FCVAR_CHEAT | FCVAR_REPLICATED, "Time taken to zoom in to frame a target in observer freeze cam.", true, 0.01, false, 0 ); ConVar spec_freeze_distance_min( "spec_freeze_distance_min", "80", FCVAR_CHEAT, "Minimum random distance from the target to stop when framing them in observer freeze cam." ); ConVar spec_freeze_distance_max( "spec_freeze_distance_max", "90", FCVAR_CHEAT, "Maximum random distance from the target to stop when framing them in observer freeze cam." ); #else ConVar spec_freeze_time( "spec_freeze_time", "4.0", FCVAR_CHEAT | FCVAR_REPLICATED, "Time spend frozen in observer freeze cam." ); ConVar spec_freeze_traveltime( "spec_freeze_traveltime", "0.4", FCVAR_CHEAT | FCVAR_REPLICATED, "Time taken to zoom in to frame a target in observer freeze cam.", true, 0.01, false, 0 ); ConVar spec_freeze_distance_min( "spec_freeze_distance_min", "96", FCVAR_CHEAT, "Minimum random distance from the target to stop when framing them in observer freeze cam." ); ConVar spec_freeze_distance_max( "spec_freeze_distance_max", "200", FCVAR_CHEAT, "Maximum random distance from the target to stop when framing them in observer freeze cam." ); #endif static ConVar cl_first_person_uses_world_model ( "cl_first_person_uses_world_model", "0", FCVAR_NONE, "Causes the third person model to be drawn instead of the view model" ); ConVar demo_fov_override( "demo_fov_override", "0", FCVAR_CLIENTDLL | FCVAR_DONTRECORD, "If nonzero, this value will be used to override FOV during demo playback." ); // This only needs to be approximate - it just controls the distance to the pivot-point of the head ("the neck") of the in-game character, not the player's real-world neck length. // Ideally we would find this vector by subtracting the neutral-pose difference between the head bone (the pivot point) and the "eyes" attachment point. // However, some characters don't have this attachment point, and finding the neutral pose is a pain. // This value is found by hand, and a good value depends more on the in-game models than on actual human shapes. ConVar cl_meathook_neck_pivot_ingame_up( "cl_meathook_neck_pivot_ingame_up", "7.0" ); ConVar cl_meathook_neck_pivot_ingame_fwd( "cl_meathook_neck_pivot_ingame_fwd", "3.0" ); static ConVar cl_clean_textures_on_death( "cl_clean_textures_on_death", "0", FCVAR_DEVELOPMENTONLY, "If enabled, attempts to purge unused textures every time a freeze cam is shown" ); void RecvProxy_LocalVelocityX( const CRecvProxyData *pData, void *pStruct, void *pOut ); void RecvProxy_LocalVelocityY( const CRecvProxyData *pData, void *pStruct, void *pOut ); void RecvProxy_LocalVelocityZ( const CRecvProxyData *pData, void *pStruct, void *pOut ); void RecvProxy_ObserverTarget( const CRecvProxyData *pData, void *pStruct, void *pOut ); void RecvProxy_ObserverMode ( const CRecvProxyData *pData, void *pStruct, void *pOut ); // -------------------------------------------------------------------------------- // // RecvTable for CPlayerState. // -------------------------------------------------------------------------------- // BEGIN_RECV_TABLE_NOBASE(CPlayerState, DT_PlayerState) RecvPropInt (RECVINFO(deadflag)), END_RECV_TABLE() BEGIN_RECV_TABLE_NOBASE( CPlayerLocalData, DT_Local ) RecvPropArray3( RECVINFO_ARRAY(m_chAreaBits), RecvPropInt(RECVINFO(m_chAreaBits[0]))), RecvPropArray3( RECVINFO_ARRAY(m_chAreaPortalBits), RecvPropInt(RECVINFO(m_chAreaPortalBits[0]))), RecvPropInt(RECVINFO(m_iHideHUD)), // View RecvPropFloat(RECVINFO(m_flFOVRate)), RecvPropInt (RECVINFO(m_bDucked)), RecvPropInt (RECVINFO(m_bDucking)), RecvPropInt (RECVINFO(m_bInDuckJump)), RecvPropFloat (RECVINFO(m_flDucktime)), RecvPropFloat (RECVINFO(m_flDuckJumpTime)), RecvPropFloat (RECVINFO(m_flJumpTime)), RecvPropFloat (RECVINFO(m_flFallVelocity)), #if PREDICTION_ERROR_CHECK_LEVEL > 1 RecvPropFloat (RECVINFO_NAME( m_vecPunchAngle.m_Value[0], m_vecPunchAngle[0])), RecvPropFloat (RECVINFO_NAME( m_vecPunchAngle.m_Value[1], m_vecPunchAngle[1])), RecvPropFloat (RECVINFO_NAME( m_vecPunchAngle.m_Value[2], m_vecPunchAngle[2] )), RecvPropFloat (RECVINFO_NAME( m_vecPunchAngleVel.m_Value[0], m_vecPunchAngleVel[0] )), RecvPropFloat (RECVINFO_NAME( m_vecPunchAngleVel.m_Value[1], m_vecPunchAngleVel[1] )), RecvPropFloat (RECVINFO_NAME( m_vecPunchAngleVel.m_Value[2], m_vecPunchAngleVel[2] )), #else RecvPropVector (RECVINFO(m_vecPunchAngle)), RecvPropVector (RECVINFO(m_vecPunchAngleVel)), #endif RecvPropInt (RECVINFO(m_bDrawViewmodel)), RecvPropInt (RECVINFO(m_bWearingSuit)), RecvPropBool (RECVINFO(m_bPoisoned)), RecvPropFloat (RECVINFO(m_flStepSize)), RecvPropInt (RECVINFO(m_bAllowAutoMovement)), // 3d skybox data RecvPropInt(RECVINFO(m_skybox3d.scale)), RecvPropVector(RECVINFO(m_skybox3d.origin)), RecvPropInt(RECVINFO(m_skybox3d.area)), // 3d skybox fog data RecvPropInt( RECVINFO( m_skybox3d.fog.enable ) ), RecvPropInt( RECVINFO( m_skybox3d.fog.blend ) ), RecvPropVector( RECVINFO( m_skybox3d.fog.dirPrimary ) ), RecvPropInt( RECVINFO( m_skybox3d.fog.colorPrimary ) ), RecvPropInt( RECVINFO( m_skybox3d.fog.colorSecondary ) ), RecvPropFloat( RECVINFO( m_skybox3d.fog.start ) ), RecvPropFloat( RECVINFO( m_skybox3d.fog.end ) ), RecvPropFloat( RECVINFO( m_skybox3d.fog.maxdensity ) ), // fog data RecvPropEHandle( RECVINFO( m_PlayerFog.m_hCtrl ) ), // audio data RecvPropVector( RECVINFO( m_audio.localSound[0] ) ), RecvPropVector( RECVINFO( m_audio.localSound[1] ) ), RecvPropVector( RECVINFO( m_audio.localSound[2] ) ), RecvPropVector( RECVINFO( m_audio.localSound[3] ) ), RecvPropVector( RECVINFO( m_audio.localSound[4] ) ), RecvPropVector( RECVINFO( m_audio.localSound[5] ) ), RecvPropVector( RECVINFO( m_audio.localSound[6] ) ), RecvPropVector( RECVINFO( m_audio.localSound[7] ) ), RecvPropInt( RECVINFO( m_audio.soundscapeIndex ) ), RecvPropInt( RECVINFO( m_audio.localBits ) ), RecvPropEHandle( RECVINFO( m_audio.ent ) ), END_RECV_TABLE() // -------------------------------------------------------------------------------- // // This data only gets sent to clients that ARE this player entity. // -------------------------------------------------------------------------------- // BEGIN_RECV_TABLE_NOBASE( C_BasePlayer, DT_LocalPlayerExclusive ) RecvPropDataTable ( RECVINFO_DT(m_Local),0, &REFERENCE_RECV_TABLE(DT_Local) ), RecvPropFloat ( RECVINFO(m_vecViewOffset[0]) ), RecvPropFloat ( RECVINFO(m_vecViewOffset[1]) ), RecvPropFloat ( RECVINFO(m_vecViewOffset[2]) ), RecvPropFloat ( RECVINFO(m_flFriction) ), RecvPropArray3 ( RECVINFO_ARRAY(m_iAmmo), RecvPropInt( RECVINFO(m_iAmmo[0])) ), RecvPropInt ( RECVINFO(m_fOnTarget) ), RecvPropInt ( RECVINFO( m_nTickBase ) ), RecvPropInt ( RECVINFO( m_nNextThinkTick ) ), RecvPropEHandle ( RECVINFO( m_hLastWeapon ) ), RecvPropEHandle ( RECVINFO( m_hGroundEntity ) ), RecvPropFloat ( RECVINFO(m_vecVelocity[0]), 0, RecvProxy_LocalVelocityX ), RecvPropFloat ( RECVINFO(m_vecVelocity[1]), 0, RecvProxy_LocalVelocityY ), RecvPropFloat ( RECVINFO(m_vecVelocity[2]), 0, RecvProxy_LocalVelocityZ ), RecvPropVector ( RECVINFO( m_vecBaseVelocity ) ), RecvPropEHandle ( RECVINFO( m_hConstraintEntity)), RecvPropVector ( RECVINFO( m_vecConstraintCenter) ), RecvPropFloat ( RECVINFO( m_flConstraintRadius )), RecvPropFloat ( RECVINFO( m_flConstraintWidth )), RecvPropFloat ( RECVINFO( m_flConstraintSpeedFactor )), RecvPropFloat ( RECVINFO( m_flDeathTime )), RecvPropInt ( RECVINFO( m_nWaterLevel ) ), RecvPropFloat ( RECVINFO( m_flLaggedMovementValue )), END_RECV_TABLE() // -------------------------------------------------------------------------------- // // DT_BasePlayer datatable. // -------------------------------------------------------------------------------- // #if defined USES_ECON_ITEMS EXTERN_RECV_TABLE(DT_AttributeList); #endif IMPLEMENT_CLIENTCLASS_DT(C_BasePlayer, DT_BasePlayer, CBasePlayer) // We have both the local and nonlocal data in here, but the server proxies // only send one. RecvPropDataTable( "localdata", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalPlayerExclusive) ), #if defined USES_ECON_ITEMS RecvPropDataTable(RECVINFO_DT(m_AttributeList),0, &REFERENCE_RECV_TABLE(DT_AttributeList) ), #endif RecvPropDataTable(RECVINFO_DT(pl), 0, &REFERENCE_RECV_TABLE(DT_PlayerState), DataTableRecvProxy_StaticDataTable), RecvPropInt (RECVINFO(m_iFOV)), RecvPropInt (RECVINFO(m_iFOVStart)), RecvPropFloat (RECVINFO(m_flFOVTime)), RecvPropInt (RECVINFO(m_iDefaultFOV)), RecvPropEHandle (RECVINFO(m_hZoomOwner)), RecvPropEHandle( RECVINFO(m_hVehicle) ), RecvPropEHandle( RECVINFO(m_hUseEntity) ), RecvPropInt (RECVINFO(m_iHealth)), RecvPropInt (RECVINFO(m_lifeState)), RecvPropInt (RECVINFO(m_iBonusProgress)), RecvPropInt (RECVINFO(m_iBonusChallenge)), RecvPropFloat (RECVINFO(m_flMaxspeed)), RecvPropInt (RECVINFO(m_fFlags)), RecvPropInt (RECVINFO(m_iObserverMode), 0, RecvProxy_ObserverMode ), RecvPropEHandle (RECVINFO(m_hObserverTarget), RecvProxy_ObserverTarget ), RecvPropArray ( RecvPropEHandle( RECVINFO( m_hViewModel[0] ) ), m_hViewModel ), RecvPropString( RECVINFO(m_szLastPlaceName) ), #if defined USES_ECON_ITEMS RecvPropUtlVector( RECVINFO_UTLVECTOR( m_hMyWearables ), MAX_WEARABLES_SENT_FROM_SERVER, RecvPropEHandle(NULL, 0, 0) ), #endif END_RECV_TABLE() BEGIN_PREDICTION_DATA_NO_BASE( CPlayerState ) DEFINE_PRED_FIELD( deadflag, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), // DEFINE_FIELD( netname, string_t ), // DEFINE_FIELD( fixangle, FIELD_INTEGER ), // DEFINE_FIELD( anglechange, FIELD_FLOAT ), // DEFINE_FIELD( v_angle, FIELD_VECTOR ), END_PREDICTION_DATA() BEGIN_PREDICTION_DATA_NO_BASE( CPlayerLocalData ) // DEFINE_PRED_TYPEDESCRIPTION( m_skybox3d, sky3dparams_t ), // DEFINE_PRED_TYPEDESCRIPTION( m_fog, fogparams_t ), // DEFINE_PRED_TYPEDESCRIPTION( m_audio, audioparams_t ), DEFINE_FIELD( m_nStepside, FIELD_INTEGER ), DEFINE_PRED_FIELD( m_iHideHUD, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), #if PREDICTION_ERROR_CHECK_LEVEL > 1 DEFINE_PRED_FIELD( m_vecPunchAngle, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_vecPunchAngleVel, FIELD_VECTOR, FTYPEDESC_INSENDTABLE ), #else DEFINE_PRED_FIELD_TOL( m_vecPunchAngle, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.125f ), DEFINE_PRED_FIELD_TOL( m_vecPunchAngleVel, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.125f ), #endif DEFINE_PRED_FIELD( m_bDrawViewmodel, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bWearingSuit, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bPoisoned, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bAllowAutoMovement, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bDucked, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bDucking, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bInDuckJump, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_flDucktime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_flDuckJumpTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_flJumpTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD_TOL( m_flFallVelocity, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, 0.5f ), // DEFINE_PRED_FIELD( m_nOldButtons, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_FIELD( m_nOldButtons, FIELD_INTEGER ), DEFINE_FIELD( m_flOldForwardMove, FIELD_FLOAT ), DEFINE_PRED_FIELD( m_flStepSize, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), DEFINE_FIELD( m_flFOVRate, FIELD_FLOAT ), END_PREDICTION_DATA() BEGIN_PREDICTION_DATA( C_BasePlayer ) DEFINE_PRED_TYPEDESCRIPTION( m_Local, CPlayerLocalData ), DEFINE_PRED_TYPEDESCRIPTION( pl, CPlayerState ), DEFINE_PRED_FIELD( m_iFOV, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_hZoomOwner, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_flFOVTime, FIELD_FLOAT, 0 ), DEFINE_PRED_FIELD( m_iFOVStart, FIELD_INTEGER, 0 ), DEFINE_PRED_FIELD( m_hVehicle, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD_TOL( m_flMaxspeed, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, 0.5f ), DEFINE_PRED_FIELD( m_iHealth, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_iBonusProgress, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_iBonusChallenge, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_fOnTarget, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_nNextThinkTick, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_lifeState, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_nWaterLevel, FIELD_CHARACTER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD_TOL( m_vecBaseVelocity, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.05 ), DEFINE_FIELD( m_nButtons, FIELD_INTEGER ), DEFINE_FIELD( m_flWaterJumpTime, FIELD_FLOAT ), DEFINE_FIELD( m_nImpulse, FIELD_INTEGER ), DEFINE_FIELD( m_flStepSoundTime, FIELD_FLOAT ), DEFINE_FIELD( m_flSwimSoundTime, FIELD_FLOAT ), DEFINE_FIELD( m_vecLadderNormal, FIELD_VECTOR ), DEFINE_FIELD( m_flPhysics, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( m_szAnimExtension, FIELD_CHARACTER ), DEFINE_FIELD( m_afButtonLast, FIELD_INTEGER ), DEFINE_FIELD( m_afButtonPressed, FIELD_INTEGER ), DEFINE_FIELD( m_afButtonReleased, FIELD_INTEGER ), // DEFINE_FIELD( m_vecOldViewAngles, FIELD_VECTOR ), // DEFINE_ARRAY( m_iOldAmmo, FIELD_INTEGER, MAX_AMMO_TYPES ), //DEFINE_FIELD( m_hOldVehicle, FIELD_EHANDLE ), // DEFINE_FIELD( m_pModelLight, dlight_t* ), // DEFINE_FIELD( m_pEnvironmentLight, dlight_t* ), // DEFINE_FIELD( m_pBrightLight, dlight_t* ), DEFINE_PRED_FIELD( m_hLastWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_nTickBase, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_hGroundEntity, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_ARRAY( m_hViewModel, FIELD_EHANDLE, MAX_VIEWMODELS, FTYPEDESC_INSENDTABLE ), DEFINE_FIELD( m_surfaceFriction, FIELD_FLOAT ), END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( player, C_BasePlayer ); // -------------------------------------------------------------------------------- // // Functions. // -------------------------------------------------------------------------------- // C_BasePlayer::C_BasePlayer() : m_iv_vecViewOffset( "C_BasePlayer::m_iv_vecViewOffset" ) { AddVar( &m_vecViewOffset, &m_iv_vecViewOffset, LATCH_SIMULATION_VAR ); #ifdef _DEBUG m_vecLadderNormal.Init(); m_vecOldViewAngles.Init(); #endif m_pFlashlight = NULL; m_pCurrentVguiScreen = NULL; m_pCurrentCommand = NULL; m_flPredictionErrorTime = -100; m_StuckLast = 0; m_bWasFrozen = false; m_bResampleWaterSurface = true; ResetObserverMode(); m_vecPredictionError.Init(); m_flPredictionErrorTime = 0; m_surfaceProps = 0; m_pSurfaceData = NULL; m_surfaceFriction = 1.0f; m_chTextureType = 0; m_flNextAchievementAnnounceTime = 0; m_bFiredWeapon = false; m_nForceVisionFilterFlags = 0; m_nLocalPlayerVisionFlags = 0; ListenForGameEvent( "base_player_teleported" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_BasePlayer::~C_BasePlayer() { DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); if ( this == s_pLocalPlayer ) { s_pLocalPlayer = NULL; } delete m_pFlashlight; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BasePlayer::Spawn( void ) { // Clear all flags except for FL_FULLEDICT ClearFlags(); AddFlag( FL_CLIENT ); int fEffects = GetEffects() & EF_NOSHADOW; SetEffects( fEffects ); m_iFOV = 0; // init field of view. SetModel( "models/player.mdl" ); Precache(); SetThink(NULL); SharedSpawn(); m_bWasFreezeFraming = false; m_bFiredWeapon = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BasePlayer::AudioStateIsUnderwater( Vector vecMainViewOrigin ) { if ( IsObserver() ) { // Just check the view position int cont = enginetrace->GetPointContents ( vecMainViewOrigin ); return (cont & MASK_WATER); } return ( GetWaterLevel() >= WL_Eyes ); } bool C_BasePlayer::IsHLTV() const { return ( IsLocalPlayer() && engine->IsHLTV() ); } bool C_BasePlayer::IsReplay() const { #if defined( REPLAY_ENABLED ) return ( IsLocalPlayer() && g_pEngineClientReplay->IsPlayingReplayDemo() ); #else return false; #endif } CBaseEntity *C_BasePlayer::GetObserverTarget() const // returns players target or NULL { #ifndef _XBOX if ( IsHLTV() ) { return HLTVCamera()->GetPrimaryTarget(); } #if defined( REPLAY_ENABLED ) if ( IsReplay() ) { return ReplayCamera()->GetPrimaryTarget(); } #endif #endif if ( GetObserverMode() == OBS_MODE_ROAMING ) { return NULL; // no target in roaming mode } else { if ( IsLocalPlayer() && UseVR() ) { // In VR mode, certain views cause disorientation and nausea. So let's not. switch ( m_iObserverMode ) { case OBS_MODE_NONE: // not in spectator mode case OBS_MODE_FIXED: // view from a fixed camera position case OBS_MODE_IN_EYE: // follow a player in first person view case OBS_MODE_CHASE: // follow a player in third person view case OBS_MODE_POI: // PASSTIME point of interest - game objective, big fight, anything interesting case OBS_MODE_ROAMING: // free roaming return m_hObserverTarget; break; case OBS_MODE_DEATHCAM: // special mode for death cam animation case OBS_MODE_FREEZECAM: // zooms to a target, and freeze-frames on them // These are both terrible - they get overriden to chase, but here we change it to "chase" your own body (which will be ragdolled). return (const_cast(this))->GetBaseEntity(); break; default: assert ( false ); break; } } return m_hObserverTarget; } } // Called from Recv Proxy, mainly to reset tone map scale void C_BasePlayer::SetObserverTarget( EHANDLE hObserverTarget ) { // If the observer target is changing to an entity that the client doesn't know about yet, // it can resolve to NULL. If the client didn't have an observer target before, then // comparing EHANDLEs directly will see them as equal, since it uses Get(), and compares // NULL to NULL. To combat this, we need to check against GetEntryIndex() and // GetSerialNumber(). if ( hObserverTarget.GetEntryIndex() != m_hObserverTarget.GetEntryIndex() || hObserverTarget.GetSerialNumber() != m_hObserverTarget.GetSerialNumber()) { // Init based on the new handle's entry index and serial number, so that it's Get() // has a chance to become non-NULL even if it currently resolves to NULL. m_hObserverTarget.Init( hObserverTarget.GetEntryIndex(), hObserverTarget.GetSerialNumber() ); IGameEvent *event = gameeventmanager->CreateEvent( "spec_target_updated" ); if ( event ) { gameeventmanager->FireEventClientSide( event ); } if ( IsLocalPlayer() ) { ResetToneMapping(1.0); } // NVNT notify haptics of changed player if ( haptics ) haptics->OnPlayerChanged(); if ( IsLocalPlayer() ) { // On a change of viewing mode or target, we may want to reset both head and torso to point at the new target. g_ClientVirtualReality.AlignTorsoAndViewToWeapon(); } } } void C_BasePlayer::SetObserverMode ( int iNewMode ) { if ( m_iObserverMode != iNewMode ) { m_iObserverMode = iNewMode; if ( IsLocalPlayer() ) { // On a change of viewing mode or target, we may want to reset both head and torso to point at the new target. g_ClientVirtualReality.AlignTorsoAndViewToWeapon(); } } } int C_BasePlayer::GetObserverMode() const { #ifndef _XBOX if ( IsHLTV() ) { return HLTVCamera()->GetMode(); } #if defined( REPLAY_ENABLED ) if ( IsReplay() ) { return ReplayCamera()->GetMode(); } #endif #endif if ( IsLocalPlayer() && UseVR() ) { // IN VR mode, certain views cause disorientation and nausea. So let's not. switch ( m_iObserverMode ) { case OBS_MODE_NONE: // not in spectator mode case OBS_MODE_FIXED: // view from a fixed camera position case OBS_MODE_IN_EYE: // follow a player in first person view case OBS_MODE_CHASE: // follow a player in third person view case OBS_MODE_POI: // PASSTIME point of interest - game objective, big fight, anything interesting case OBS_MODE_ROAMING: // free roaming return m_iObserverMode; break; case OBS_MODE_DEATHCAM: // special mode for death cam animation case OBS_MODE_FREEZECAM: // zooms to a target, and freeze-frames on them // These are both terrible - just do chase of your ragdoll. return OBS_MODE_CHASE; break; default: assert ( false ); break; } } return m_iObserverMode; } bool C_BasePlayer::ViewModel_IsTransparent( void ) { return IsTransparent(); } bool C_BasePlayer::ViewModel_IsUsingFBTexture( void ) { return UsesPowerOfTwoFrameBufferTexture(); } //----------------------------------------------------------------------------- // Used by prediction, sets the view angles for the player //----------------------------------------------------------------------------- void C_BasePlayer::SetLocalViewAngles( const QAngle &viewAngles ) { pl.v_angle = viewAngles; } //----------------------------------------------------------------------------- // Purpose: // Input : ang - //----------------------------------------------------------------------------- void C_BasePlayer::SetViewAngles( const QAngle& ang ) { SetLocalAngles( ang ); SetNetworkAngles( ang ); } surfacedata_t* C_BasePlayer::GetGroundSurface() { // // Find the name of the material that lies beneath the player. // Vector start, end; VectorCopy( GetAbsOrigin(), start ); VectorCopy( start, end ); // Straight down end.z -= 64; // Fill in default values, just in case. Ray_t ray; ray.Init( start, end, GetPlayerMins(), GetPlayerMaxs() ); trace_t trace; UTIL_TraceRay( ray, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); if ( trace.fraction == 1.0f ) return NULL; // no ground return physprops->GetSurfaceData( trace.surface.surfaceProps ); } void C_BasePlayer::FireGameEvent( IGameEvent *event ) { if ( FStrEq( event->GetName(), "base_player_teleported" ) ) { const int index_ = event->GetInt( "entindex" ); if ( index_ == entindex() && IsLocalPlayer() ) { // In VR, we want to make sure our head and body // are aligned after we teleport. g_ClientVirtualReality.AlignTorsoAndViewToWeapon(); } } } //----------------------------------------------------------------------------- // returns the player name //----------------------------------------------------------------------------- const char * C_BasePlayer::GetPlayerName() { return g_PR ? g_PR->GetPlayerName( entindex() ) : ""; } //----------------------------------------------------------------------------- // Is the player dead? //----------------------------------------------------------------------------- bool C_BasePlayer::IsPlayerDead() { return pl.deadflag == true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BasePlayer::SetVehicleRole( int nRole ) { if ( !IsInAVehicle() ) return; // HL2 has only a player in a vehicle. if ( nRole > VEHICLE_ROLE_DRIVER ) return; char szCmd[64]; Q_snprintf( szCmd, sizeof( szCmd ), "vehicleRole %i\n", nRole ); engine->ServerCmd( szCmd ); } //----------------------------------------------------------------------------- // Purpose: Store original ammo data to see what has changed // Input : bnewentity - //----------------------------------------------------------------------------- void C_BasePlayer::OnPreDataChanged( DataUpdateType_t updateType ) { for (int i = 0; i < MAX_AMMO_TYPES; ++i) { m_iOldAmmo[i] = GetAmmoCount(i); } m_bWasFreezeFraming = (GetObserverMode() == OBS_MODE_FREEZECAM); m_hOldFogController = m_Local.m_PlayerFog.m_hCtrl; BaseClass::OnPreDataChanged( updateType ); } void C_BasePlayer::PreDataUpdate( DataUpdateType_t updateType ) { BaseClass::PreDataUpdate( updateType ); } //----------------------------------------------------------------------------- // Purpose: // Input : updateType - //----------------------------------------------------------------------------- void C_BasePlayer::PostDataUpdate( DataUpdateType_t updateType ) { // This has to occur here as opposed to OnDataChanged so that EHandles to the player created // on this same frame are not stomped because prediction thinks there // isn't a local player yet!!! if ( updateType == DATA_UPDATE_CREATED ) { // Make sure s_pLocalPlayer is correct int iLocalPlayerIndex = engine->GetLocalPlayer(); if ( g_nKillCamMode ) iLocalPlayerIndex = g_nKillCamTarget1; if ( iLocalPlayerIndex == index ) { Assert( s_pLocalPlayer == NULL ); s_pLocalPlayer = this; // Reset our sound mixed in case we were in a freeze cam when we // changed level, which would cause the snd_soundmixer to be left modified. ConVar *pVar = (ConVar *)cvar->FindVar( "snd_soundmixer" ); pVar->Revert(); } } bool bForceEFNoInterp = IsNoInterpolationFrame(); if ( IsLocalPlayer() ) { SetSimulatedEveryTick( true ); } else { SetSimulatedEveryTick( false ); // estimate velocity for non local players float flTimeDelta = m_flSimulationTime - m_flOldSimulationTime; if ( flTimeDelta > 0 && !( IsNoInterpolationFrame() || bForceEFNoInterp ) ) { Vector newVelo = (GetNetworkOrigin() - GetOldOrigin() ) / flTimeDelta; SetAbsVelocity( newVelo); } } BaseClass::PostDataUpdate( updateType ); // Only care about this for local player if ( IsLocalPlayer() ) { QAngle angles; engine->GetViewAngles( angles ); if ( updateType == DATA_UPDATE_CREATED ) { SetLocalViewAngles( angles ); m_flOldPlayerZ = GetLocalOrigin().z; // NVNT the local player has just been created. // set in the "on_foot" navigation. if ( haptics ) { haptics->LocalPlayerReset(); haptics->SetNavigationClass("on_foot"); haptics->ProcessHapticEvent(2,"Movement","BasePlayer"); } } SetLocalAngles( angles ); if ( !m_bWasFreezeFraming && GetObserverMode() == OBS_MODE_FREEZECAM ) { m_vecFreezeFrameStart = MainViewOrigin(); m_flFreezeFrameStartTime = gpGlobals->curtime; m_flFreezeFrameDistance = RandomFloat( spec_freeze_distance_min.GetFloat(), spec_freeze_distance_max.GetFloat() ); m_flFreezeZOffset = RandomFloat( -30, 20 ); m_bSentFreezeFrame = false; m_nForceVisionFilterFlags = 0; C_BaseEntity *target = GetObserverTarget(); if ( target && target->IsPlayer() ) { C_BasePlayer *player = ToBasePlayer( target ); if ( player ) { m_nForceVisionFilterFlags = player->GetVisionFilterFlags(); CalculateVisionUsingCurrentFlags(); } } IGameEvent *pEvent = gameeventmanager->CreateEvent( "show_freezepanel" ); if ( pEvent ) { pEvent->SetInt( "killer", target ? target->entindex() : 0 ); gameeventmanager->FireEventClientSide( pEvent ); } // Force the sound mixer to the freezecam mixer ConVar *pVar = (ConVar *)cvar->FindVar( "snd_soundmixer" ); pVar->SetValue( "FreezeCam_Only" ); // When we start, give unused textures an opportunity to unload if ( cl_clean_textures_on_death.GetBool() ) g_pMaterialSystem->UncacheUnusedMaterials( false ); } else if ( m_bWasFreezeFraming && GetObserverMode() != OBS_MODE_FREEZECAM ) { IGameEvent *pEvent = gameeventmanager->CreateEvent( "hide_freezepanel" ); if ( pEvent ) { gameeventmanager->FireEventClientSide( pEvent ); } view->FreezeFrame(0); ConVar *pVar = (ConVar *)cvar->FindVar( "snd_soundmixer" ); pVar->Revert(); m_nForceVisionFilterFlags = 0; CalculateVisionUsingCurrentFlags(); } // force calculate vision when the local vision flags changed int nCurrentLocalPlayerVisionFlags = GetLocalPlayerVisionFilterFlags(); if ( m_nLocalPlayerVisionFlags != nCurrentLocalPlayerVisionFlags ) { CalculateVisionUsingCurrentFlags(); m_nLocalPlayerVisionFlags = nCurrentLocalPlayerVisionFlags; } } // If we are updated while paused, allow the player origin to be snapped by the // server if we receive a packet from the server if ( engine->IsPaused() || bForceEFNoInterp ) { ResetLatched(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BasePlayer::CanSetSoundMixer( void ) { // Can't set sound mixers when we're in freezecam mode, since it has a code-enforced mixer return (GetObserverMode() != OBS_MODE_FREEZECAM); } void C_BasePlayer::ReceiveMessage( int classID, bf_read &msg ) { if ( classID != GetClientClass()->m_ClassID ) { // message is for subclass BaseClass::ReceiveMessage( classID, msg ); return; } int messageType = msg.ReadByte(); switch( messageType ) { case PLAY_PLAYER_JINGLE: PlayPlayerJingle(); break; } } void C_BasePlayer::OnRestore() { BaseClass::OnRestore(); if ( IsLocalPlayer() ) { // debounce the attack key, for if it was used for restore input->ClearInputButton( IN_ATTACK | IN_ATTACK2 ); // GetButtonBits() has to be called for the above to take effect input->GetButtonBits( 0 ); } // For ammo history icons to current value so they don't flash on level transtions for ( int i = 0; i < MAX_AMMO_TYPES; i++ ) { m_iOldAmmo[i] = GetAmmoCount(i); } } //----------------------------------------------------------------------------- // Purpose: Process incoming data //----------------------------------------------------------------------------- void C_BasePlayer::OnDataChanged( DataUpdateType_t updateType ) { #if !defined( NO_ENTITY_PREDICTION ) if ( IsLocalPlayer() ) { SetPredictionEligible( true ); } #endif BaseClass::OnDataChanged( updateType ); // Only care about this for local player if ( IsLocalPlayer() ) { // Reset engine areabits pointer render->SetAreaState( m_Local.m_chAreaBits, m_Local.m_chAreaPortalBits ); // Check for Ammo pickups. for ( int i = 0; i < MAX_AMMO_TYPES; i++ ) { if ( GetAmmoCount(i) > m_iOldAmmo[i] ) { // Don't add to ammo pickup if the ammo doesn't do it const FileWeaponInfo_t *pWeaponData = gWR.GetWeaponFromAmmo(i); if ( !pWeaponData || !( pWeaponData->iFlags & ITEM_FLAG_NOAMMOPICKUPS ) ) { // We got more ammo for this ammo index. Add it to the ammo history CHudHistoryResource *pHudHR = GET_HUDELEMENT( CHudHistoryResource ); if( pHudHR ) { pHudHR->AddToHistory( HISTSLOT_AMMO, i, abs(GetAmmoCount(i) - m_iOldAmmo[i]) ); } } } } Soundscape_Update( m_Local.m_audio ); if ( m_hOldFogController != m_Local.m_PlayerFog.m_hCtrl ) { FogControllerChanged( updateType == DATA_UPDATE_CREATED ); } } } //----------------------------------------------------------------------------- // Did we just enter a vehicle this frame? //----------------------------------------------------------------------------- bool C_BasePlayer::JustEnteredVehicle() { if ( !IsInAVehicle() ) return false; return ( m_hOldVehicle == m_hVehicle ); } //----------------------------------------------------------------------------- // Are we in VGUI input mode?. //----------------------------------------------------------------------------- bool C_BasePlayer::IsInVGuiInputMode() const { return (m_pCurrentVguiScreen.Get() != NULL); } //----------------------------------------------------------------------------- // Are we inputing to a view model vgui screen //----------------------------------------------------------------------------- bool C_BasePlayer::IsInViewModelVGuiInputMode() const { C_BaseEntity *pScreenEnt = m_pCurrentVguiScreen.Get(); if ( !pScreenEnt ) return false; Assert( dynamic_cast(pScreenEnt) ); C_VGuiScreen *pVguiScreen = static_cast(pScreenEnt); return ( pVguiScreen->IsAttachedToViewModel() && pVguiScreen->AcceptsInput() ); } //----------------------------------------------------------------------------- // Check to see if we're in vgui input mode... //----------------------------------------------------------------------------- void C_BasePlayer::DetermineVguiInputMode( CUserCmd *pCmd ) { // If we're dead, close down and abort! if ( !IsAlive() ) { DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); m_pCurrentVguiScreen.Set( NULL ); return; } // If we're in vgui mode *and* we're holding down mouse buttons, // stay in vgui mode even if we're outside the screen bounds if (m_pCurrentVguiScreen.Get() && (pCmd->buttons & (IN_ATTACK | IN_ATTACK2)) ) { SetVGuiScreenButtonState( m_pCurrentVguiScreen.Get(), pCmd->buttons ); // Kill all attack inputs if we're in vgui screen mode pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2); return; } // We're not in vgui input mode if we're moving, or have hit a key // that will make us move... // Not in vgui mode if we're moving too quickly // ROBIN: Disabled movement preventing VGUI screen usage //if (GetVelocity().LengthSqr() > MAX_VGUI_INPUT_MODE_SPEED_SQ) if ( 0 ) { DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); m_pCurrentVguiScreen.Set( NULL ); return; } // Don't enter vgui mode if we've got combat buttons held down bool bAttacking = false; if ( ((pCmd->buttons & IN_ATTACK) || (pCmd->buttons & IN_ATTACK2)) && !m_pCurrentVguiScreen.Get() ) { bAttacking = true; } // Not in vgui mode if we're pushing any movement key at all // Not in vgui mode if we're in a vehicle... // ROBIN: Disabled movement preventing VGUI screen usage //if ((pCmd->forwardmove > MAX_VGUI_INPUT_MODE_SPEED) || // (pCmd->sidemove > MAX_VGUI_INPUT_MODE_SPEED) || // (pCmd->upmove > MAX_VGUI_INPUT_MODE_SPEED) || // (pCmd->buttons & IN_JUMP) || // (bAttacking) ) if ( bAttacking || IsInAVehicle() ) { DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); m_pCurrentVguiScreen.Set( NULL ); return; } // Don't interact with world screens when we're in a menu if ( vgui::surface()->IsCursorVisible() ) { DeactivateVguiScreen( m_pCurrentVguiScreen.Get() ); m_pCurrentVguiScreen.Set( NULL ); return; } // Not in vgui mode if there are no nearby screens C_BaseEntity *pOldScreen = m_pCurrentVguiScreen.Get(); m_pCurrentVguiScreen = FindNearbyVguiScreen( EyePosition(), pCmd->viewangles, GetTeamNumber() ); if (pOldScreen != m_pCurrentVguiScreen) { DeactivateVguiScreen( pOldScreen ); ActivateVguiScreen( m_pCurrentVguiScreen.Get() ); } if (m_pCurrentVguiScreen.Get()) { SetVGuiScreenButtonState( m_pCurrentVguiScreen.Get(), pCmd->buttons ); // Kill all attack inputs if we're in vgui screen mode pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2); } } //----------------------------------------------------------------------------- // Purpose: Input handling //----------------------------------------------------------------------------- bool C_BasePlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd ) { // Allow the vehicle to clamp the view angles if ( IsInAVehicle() ) { IClientVehicle *pVehicle = m_hVehicle.Get()->GetClientVehicle(); if ( pVehicle ) { pVehicle->UpdateViewAngles( this, pCmd ); engine->SetViewAngles( pCmd->viewangles ); } } else { #ifndef _X360 if ( joy_autosprint.GetBool() ) #endif { if ( input->KeyState( &in_joyspeed ) != 0.0f ) { pCmd->buttons |= IN_SPEED; } } CBaseCombatWeapon *pWeapon = GetActiveWeapon(); if ( pWeapon ) { pWeapon->CreateMove( flInputSampleTime, pCmd, m_vecOldViewAngles ); } } // If the frozen flag is set, prevent view movement (server prevents the rest of the movement) if ( GetFlags() & FL_FROZEN ) { // Don't stomp the first time we get frozen if ( m_bWasFrozen ) { // Stomp the new viewangles with old ones pCmd->viewangles = m_vecOldViewAngles; engine->SetViewAngles( pCmd->viewangles ); } else { m_bWasFrozen = true; } } else { m_bWasFrozen = false; } m_vecOldViewAngles = pCmd->viewangles; // Check to see if we're in vgui input mode... DetermineVguiInputMode( pCmd ); return true; } //----------------------------------------------------------------------------- // Purpose: Player has changed to a new team //----------------------------------------------------------------------------- void C_BasePlayer::TeamChange( int iNewTeam ) { // Base class does nothing } //----------------------------------------------------------------------------- // Purpose: Creates, destroys, and updates the flashlight effect as needed. //----------------------------------------------------------------------------- void C_BasePlayer::UpdateFlashlight() { // The dim light is the flashlight. if ( IsEffectActive( EF_DIMLIGHT ) ) { if (!m_pFlashlight) { // Turned on the headlight; create it. m_pFlashlight = new CFlashlightEffect(index); if (!m_pFlashlight) return; m_pFlashlight->TurnOn(); } Vector vecForward, vecRight, vecUp; EyeVectors( &vecForward, &vecRight, &vecUp ); // Update the light with the new position and direction. m_pFlashlight->UpdateLight( EyePosition(), vecForward, vecRight, vecUp, FLASHLIGHT_DISTANCE ); } else if (m_pFlashlight) { // Turned off the flashlight; delete it. delete m_pFlashlight; m_pFlashlight = NULL; } } //----------------------------------------------------------------------------- // Purpose: Creates player flashlight if it's ative //----------------------------------------------------------------------------- void C_BasePlayer::Flashlight( void ) { UpdateFlashlight(); // Check for muzzle flash and apply to view model C_BaseAnimating *ve = this; if ( GetObserverMode() == OBS_MODE_IN_EYE ) { ve = dynamic_cast< C_BaseAnimating* >( GetObserverTarget() ); } } //----------------------------------------------------------------------------- // Purpose: Engine is asking whether to add this player to the visible entities list //----------------------------------------------------------------------------- void C_BasePlayer::AddEntity( void ) { // FIXME/UNDONE: Should the local player say yes to adding itself now // and then, when it ges time to render and it shouldn't still do the render with // STUDIO_EVENTS set so that its attachment points will get updated even if not // in third person? // Add in water effects if ( IsLocalPlayer() ) { CreateWaterEffects(); } // If set to invisible, skip. Do this before resetting the entity pointer so it has // valid data to decide whether it's visible. if ( !IsVisible() || !g_pClientMode->ShouldDrawLocalPlayer( this ) ) { return; } // Server says don't interpolate this frame, so set previous info to new info. if ( IsNoInterpolationFrame() || Teleported() ) { ResetLatched(); } // Add in lighting effects CreateLightEffects(); } extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BasePlayer::CreateWaterEffects( void ) { // Must be completely submerged to bother if ( GetWaterLevel() < 3 ) { m_bResampleWaterSurface = true; return; } // Do special setup if this is our first time back underwater if ( m_bResampleWaterSurface ) { // Reset our particle timer m_tWaterParticleTimer.Init( 32 ); // Find the surface of the water to clip against m_flWaterSurfaceZ = UTIL_WaterLevel( WorldSpaceCenter(), WorldSpaceCenter().z, WorldSpaceCenter().z + 256 ); m_bResampleWaterSurface = false; } // Make sure the emitter is setup if ( m_pWaterEmitter == NULL ) { if ( ( m_pWaterEmitter = WaterDebrisEffect::Create( "splish" ) ) == NULL ) return; } Vector vecVelocity; GetVectors( &vecVelocity, NULL, NULL ); Vector offset = WorldSpaceCenter(); m_pWaterEmitter->SetSortOrigin( offset ); SimpleParticle *pParticle; float curTime = gpGlobals->frametime; // Add as many particles as we need while ( m_tWaterParticleTimer.NextEvent( curTime ) ) { offset = WorldSpaceCenter() + ( vecVelocity * 128.0f ) + RandomVector( -128, 128 ); // Make sure we don't start out of the water! if ( offset.z > m_flWaterSurfaceZ ) { offset.z = ( m_flWaterSurfaceZ - 8.0f ); } pParticle = (SimpleParticle *) m_pWaterEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_Fleck_Cement[random->RandomInt(0,1)], offset ); if (pParticle == NULL) continue; pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = random->RandomFloat( 2.0f, 4.0f ); pParticle->m_vecVelocity = RandomVector( -2.0f, 2.0f ); //FIXME: We should tint these based on the water's fog value! float color = random->RandomInt( 32, 128 ); pParticle->m_uchColor[0] = color; pParticle->m_uchColor[1] = color; pParticle->m_uchColor[2] = color; pParticle->m_uchStartSize = 1; pParticle->m_uchEndSize = 1; pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 0; pParticle->m_flRoll = random->RandomInt( 0, 360 ); pParticle->m_flRollDelta = random->RandomFloat( -0.5f, 0.5f ); } } //----------------------------------------------------------------------------- // Called when not in tactical mode. Allows view to be overriden for things like driving a tank. //----------------------------------------------------------------------------- void C_BasePlayer::OverrideView( CViewSetup *pSetup ) { } bool C_BasePlayer::ShouldInterpolate() { // always interpolate myself if ( IsLocalPlayer() ) return true; #ifndef _XBOX // always interpolate entity if followed by HLTV if ( HLTVCamera()->GetCameraMan() == this ) return true; #endif return BaseClass::ShouldInterpolate(); } bool C_BasePlayer::ShouldDraw() { return ShouldDrawThisPlayer() && BaseClass::ShouldDraw(); } int C_BasePlayer::DrawModel( int flags ) { #ifndef PORTAL // In Portal this check is already performed as part of // C_Portal_Player::DrawModel() if ( !ShouldDrawThisPlayer() ) { return 0; } #endif return BaseClass::DrawModel( flags ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vector C_BasePlayer::GetChaseCamViewOffset( CBaseEntity *target ) { C_BasePlayer *player = ToBasePlayer( target ); if ( player ) { if ( player->IsAlive() ) { if ( player->GetFlags() & FL_DUCKING ) { return VEC_DUCK_VIEW_SCALED( player ); } return VEC_VIEW_SCALED( player ); } else { // assume it's the players ragdoll return VEC_DEAD_VIEWHEIGHT_SCALED( player ); } } // assume it's the players ragdoll return VEC_DEAD_VIEWHEIGHT; } void C_BasePlayer::CalcChaseCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) { C_BaseEntity *target = GetObserverTarget(); if ( !target ) { // just copy a save in-map position VectorCopy( EyePosition(), eyeOrigin ); VectorCopy( EyeAngles(), eyeAngles ); return; }; // If our target isn't visible, we're at a camera point of some kind. // Instead of letting the player rotate around an invisible point, treat // the point as a fixed camera. if ( !target->GetBaseAnimating() && !target->GetModel() ) { CalcRoamingView( eyeOrigin, eyeAngles, fov ); return; } // QAngle tmpangles; Vector forward, viewpoint; // GetObserverCamOrigin() returns ragdoll pos if player is ragdolled Vector origin = target->GetObserverCamOrigin(); VectorAdd( origin, GetChaseCamViewOffset( target ), origin ); QAngle viewangles; if ( GetObserverMode() == OBS_MODE_IN_EYE ) { viewangles = eyeAngles; } else if ( IsLocalPlayer() ) { engine->GetViewAngles( viewangles ); if ( UseVR() ) { // Don't let people play with the pitch - they drive it into the ground or into the air and // it's distracting at best, nauseating at worst (e.g. when it clips through the ground plane). viewangles[PITCH] = 20.0f; } } else { viewangles = EyeAngles(); } //============================================================================= // HPE_BEGIN: // [Forrest] Fix for (at least one potential case of) CSB-194. Spectating someone // who is headshotted by a teammate and then switching to chase cam leaves // you with a permanent roll to the camera that doesn't decay and is not reset // even when switching to different players or at the start of the next round // if you are still a spectator. (If you spawn as a player, the view is reset. // if you switch spectator modes, the view is reset.) //============================================================================= #ifdef CSTRIKE_DLL // The chase camera adopts the yaw and pitch of the previous camera, but the camera // should not roll. viewangles.z = 0; #endif //============================================================================= // HPE_END //============================================================================= m_flObserverChaseDistance += gpGlobals->frametime*48.0f; float flMinDistance = CHASE_CAM_DISTANCE_MIN; float flMaxDistance = CHASE_CAM_DISTANCE_MAX; if ( target && target->IsBaseTrain() ) { // if this is a train, we want to be back a little further so we can see more of it flMaxDistance *= 2.5f; } if ( target ) { C_BaseAnimating *pTargetAnimating = target->GetBaseAnimating(); if ( pTargetAnimating ) { float flScaleSquared = pTargetAnimating->GetModelScale() * pTargetAnimating->GetModelScale(); flMinDistance *= flScaleSquared; flMaxDistance *= flScaleSquared; m_flObserverChaseDistance = flMaxDistance; } } if ( target && !target->IsPlayer() && target->IsNextBot() ) { // if this is a boss, we want to be back a little further so we can see more of it flMaxDistance *= 2.5f; m_flObserverChaseDistance = flMaxDistance; } m_flObserverChaseDistance = clamp( m_flObserverChaseDistance, flMinDistance, flMaxDistance ); AngleVectors( viewangles, &forward ); VectorNormalize( forward ); VectorMA(origin, -m_flObserverChaseDistance, forward, viewpoint ); trace_t trace; CTraceFilterNoNPCsOrPlayer filter( target, COLLISION_GROUP_NONE ); C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace UTIL_TraceHull( origin, viewpoint, WALL_MIN, WALL_MAX, MASK_SOLID, &filter, &trace ); C_BaseEntity::PopEnableAbsRecomputations(); if (trace.fraction < 1.0) { viewpoint = trace.endpos; m_flObserverChaseDistance = VectorLength(origin - eyeOrigin); } VectorCopy( viewangles, eyeAngles ); VectorCopy( viewpoint, eyeOrigin ); fov = GetFOV(); } void C_BasePlayer::CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) { C_BaseEntity *target = GetObserverTarget(); if ( !target ) { target = this; } m_flObserverChaseDistance = 0.0; eyeOrigin = target->EyePosition(); eyeAngles = target->EyeAngles(); if ( spec_track.GetInt() > 0 ) { C_BaseEntity *pTarget = ClientEntityList().GetBaseEntity( spec_track.GetInt() ); if ( pTarget ) { Vector v = pTarget->GetAbsOrigin(); v.z += 54; QAngle a; VectorAngles( v - eyeOrigin, a ); NormalizeAngles( a ); eyeAngles = a; engine->SetViewAngles( a ); } } // Apply a smoothing offset to smooth out prediction errors. Vector vSmoothOffset; GetPredictionErrorSmoothingVector( vSmoothOffset ); eyeOrigin += vSmoothOffset; fov = GetFOV(); } //----------------------------------------------------------------------------- // Purpose: Calculate the view for the player while he's in freeze frame observer mode //----------------------------------------------------------------------------- void C_BasePlayer::CalcFreezeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ) { C_BaseEntity *pTarget = GetObserverTarget(); if ( !pTarget ) { CalcDeathCamView( eyeOrigin, eyeAngles, fov ); return; } // Zoom towards our target float flCurTime = (gpGlobals->curtime - m_flFreezeFrameStartTime); float flBlendPerc = clamp( flCurTime / spec_freeze_traveltime.GetFloat(), 0.f, 1.f ); flBlendPerc = SimpleSpline( flBlendPerc ); Vector vecCamDesired = pTarget->GetObserverCamOrigin(); // Returns ragdoll origin if they're ragdolled VectorAdd( vecCamDesired, GetChaseCamViewOffset( pTarget ), vecCamDesired ); Vector vecCamTarget = vecCamDesired; if ( pTarget->IsAlive() ) { // Look at their chest, not their head Vector maxs = pTarget->GetBaseAnimating() ? VEC_HULL_MAX_SCALED( pTarget->GetBaseAnimating() ) : VEC_HULL_MAX; vecCamTarget.z -= (maxs.z * 0.5); } else { vecCamTarget.z += pTarget->GetBaseAnimating() ? VEC_DEAD_VIEWHEIGHT_SCALED( pTarget->GetBaseAnimating() ).z : VEC_DEAD_VIEWHEIGHT.z; // look over ragdoll, not through } // Figure out a view position in front of the target Vector vecEyeOnPlane = eyeOrigin; vecEyeOnPlane.z = vecCamTarget.z; Vector vecTargetPos = vecCamTarget; Vector vecToTarget = vecTargetPos - vecEyeOnPlane; VectorNormalize( vecToTarget ); // Stop a few units away from the target, and shift up to be at the same height vecTargetPos = vecCamTarget - (vecToTarget * m_flFreezeFrameDistance); float flEyePosZ = pTarget->EyePosition().z; vecTargetPos.z = flEyePosZ + m_flFreezeZOffset; // 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( vecCamTarget, vecTargetPos, 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. vecTargetPos = trace.endpos; vecCamTarget = vecCamDesired; // To stop all close in views looking up at character's chins, move the view up. vecTargetPos.z += fabs(vecCamTarget.z - vecTargetPos.z) * 0.85; C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace UTIL_TraceHull( vecCamTarget, vecTargetPos, WALL_MIN, WALL_MAX, MASK_SOLID, pTarget, COLLISION_GROUP_NONE, &trace ); C_BaseEntity::PopEnableAbsRecomputations(); vecTargetPos = trace.endpos; } // Look directly at the target vecToTarget = vecCamTarget - vecTargetPos; VectorNormalize( vecToTarget ); VectorAngles( vecToTarget, eyeAngles ); VectorLerp( m_vecFreezeFrameStart, vecTargetPos, flBlendPerc, eyeOrigin ); if ( flCurTime >= 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() ); } } void C_BasePlayer::CalcInEyeCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) { C_BaseEntity *target = GetObserverTarget(); if ( !target ) { // just copy a save in-map position VectorCopy( EyePosition(), eyeOrigin ); VectorCopy( EyeAngles(), eyeAngles ); return; }; if ( !target->IsAlive() ) { // if dead, show from 3rd person CalcChaseCamView( eyeOrigin, eyeAngles, fov ); return; } fov = GetFOV(); // TODO use tragets FOV m_flObserverChaseDistance = 0.0; eyeAngles = target->EyeAngles(); eyeOrigin = target->GetAbsOrigin(); // Apply punch angle VectorAdd( eyeAngles, GetPunchAngle(), eyeAngles ); #if defined( REPLAY_ENABLED ) if( engine->IsHLTV() || g_pEngineClientReplay->IsPlayingReplayDemo() ) #else if( engine->IsHLTV() ) #endif { C_BaseAnimating *pTargetAnimating = target->GetBaseAnimating(); if ( target->GetFlags() & FL_DUCKING ) { eyeOrigin += pTargetAnimating ? VEC_DUCK_VIEW_SCALED( pTargetAnimating ) : VEC_DUCK_VIEW; } else { eyeOrigin += pTargetAnimating ? VEC_VIEW_SCALED( pTargetAnimating ) : VEC_VIEW; } } else { Vector offset = GetViewOffset(); #ifdef HL2MP offset = target->GetViewOffset(); #endif eyeOrigin += offset; // hack hack } engine->SetViewAngles( eyeAngles ); } float C_BasePlayer::GetDeathCamInterpolationTime() { return DEATH_ANIMATION_TIME; } void C_BasePlayer::CalcDeathCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov) { CBaseEntity * pKiller = NULL; if ( mp_forcecamera.GetInt() == OBS_ALLOW_ALL ) { // if mp_forcecamera is off let user see killer or look around pKiller = GetObserverTarget(); eyeAngles = EyeAngles(); } float interpolation = ( gpGlobals->curtime - m_flDeathTime ) / GetDeathCamInterpolationTime(); interpolation = clamp( interpolation, 0.0f, 1.0f ); m_flObserverChaseDistance += gpGlobals->frametime*48.0f; m_flObserverChaseDistance = clamp( m_flObserverChaseDistance, ( CHASE_CAM_DISTANCE_MIN * 2 ), CHASE_CAM_DISTANCE_MAX ); QAngle aForward = eyeAngles; Vector origin = EyePosition(); // NOTE: This will create the ragdoll in CSS if m_hRagdoll is set, but m_pRagdoll is not yet presetn IRagdoll *pRagdoll = GetRepresentativeRagdoll(); if ( pRagdoll ) { origin = pRagdoll->GetRagdollOrigin(); origin.z += VEC_DEAD_VIEWHEIGHT_SCALED( this ).z; } if ( pKiller && pKiller->IsPlayer() && (pKiller != this) ) { Vector vKiller = pKiller->EyePosition() - origin; QAngle aKiller; VectorAngles( vKiller, aKiller ); InterpolateAngles( aForward, aKiller, eyeAngles, interpolation ); }; Vector vForward; AngleVectors( eyeAngles, &vForward ); VectorNormalize( vForward ); VectorMA( origin, -m_flObserverChaseDistance, vForward, eyeOrigin ); trace_t trace; // clip against world C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace UTIL_TraceHull( origin, eyeOrigin, WALL_MIN, WALL_MAX, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); C_BaseEntity::PopEnableAbsRecomputations(); if (trace.fraction < 1.0) { eyeOrigin = trace.endpos; m_flObserverChaseDistance = VectorLength(origin - eyeOrigin); } fov = GetFOV(); } //----------------------------------------------------------------------------- // Purpose: Return the weapon to have open the weapon selection on, based upon our currently active weapon // Base class just uses the weapon that's currently active. //----------------------------------------------------------------------------- C_BaseCombatWeapon *C_BasePlayer::GetActiveWeaponForSelection( void ) { return GetActiveWeapon(); } C_BaseAnimating* C_BasePlayer::GetRenderedWeaponModel() { // Attach to either their weapon model or their view model. if ( ShouldDrawLocalPlayer() || !IsLocalPlayer() ) { return GetActiveWeapon(); } else { return GetViewModel(); } } //----------------------------------------------------------------------------- // Purpose: Gets a pointer to the local player, if it exists yet. // Output : C_BasePlayer //----------------------------------------------------------------------------- C_BasePlayer *C_BasePlayer::GetLocalPlayer( void ) { return s_pLocalPlayer; } //----------------------------------------------------------------------------- // Purpose: // Input : bThirdperson - //----------------------------------------------------------------------------- void C_BasePlayer::ThirdPersonSwitch( bool bThirdperson ) { // We've switch from first to third, or vice versa. UpdateVisibility(); // Update the visibility of anything bone attached to us. if ( IsLocalPlayer() ) { bool bShouldDrawLocalPlayer = ShouldDrawLocalPlayer(); for ( int i=0; iRemoveEffects( EF_NODRAW ); } else { pBoneAttachment->AddEffects( EF_NODRAW ); } } } } } //----------------------------------------------------------------------------- // Purpose: single place to decide whether the camera is in the first-person position // NOTE - ShouldDrawLocalPlayer() can be true even if the camera is in the first-person position, e.g. in VR. //----------------------------------------------------------------------------- /*static*/ bool C_BasePlayer::LocalPlayerInFirstPersonView() { C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); if ( pLocalPlayer == NULL ) { return false; } #ifdef TF_CLIENT_DLL if ( TFGameRules() && TFGameRules()->IsCompetitiveMode() && TFGameRules()->PlayersAreOnMatchSummaryStage() ) { return false; } #endif int ObserverMode = pLocalPlayer->GetObserverMode(); if ( ( ObserverMode == OBS_MODE_NONE ) || ( ObserverMode == OBS_MODE_IN_EYE ) ) { return !input->CAM_IsThirdPerson() && ( !ToolsEnabled() || !ToolFramework_IsThirdPersonCamera() ); } // Not looking at the local player, e.g. in a replay in third person mode or freelook. return false; } //----------------------------------------------------------------------------- // Purpose: single place to decide whether the local player should draw //----------------------------------------------------------------------------- /*static*/ bool C_BasePlayer::ShouldDrawLocalPlayer() { if ( !UseVR() ) { return !LocalPlayerInFirstPersonView() || cl_first_person_uses_world_model.GetBool(); } static ConVarRef vr_first_person_uses_world_model( "vr_first_person_uses_world_model" ); return !LocalPlayerInFirstPersonView() || vr_first_person_uses_world_model.GetBool(); } //----------------------------------------------------------------------------- // Purpose: single place to decide whether the camera is in the first-person position // NOTE - ShouldDrawLocalPlayer() can be true even if the camera is in the first-person position, e.g. in VR. //----------------------------------------------------------------------------- bool C_BasePlayer::InFirstPersonView() { if ( IsLocalPlayer() ) { return LocalPlayerInFirstPersonView(); } C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); if ( pLocalPlayer == NULL ) { return false; } // If this is who we're observing in first person, it's counted as the "local" player. if ( pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE && pLocalPlayer->GetObserverTarget() == ToBasePlayer(this) ) { return LocalPlayerInFirstPersonView(); } return false; } //----------------------------------------------------------------------------- // Purpose: single place to decide whether the player is being drawn with the standard model (i.e. not the viewmodel) // NOTE - ShouldDrawLocalPlayer() can be true even if the camera is in the first-person position, e.g. in VR. //----------------------------------------------------------------------------- bool C_BasePlayer::ShouldDrawThisPlayer() { if ( !InFirstPersonView() ) { return true; } if ( !UseVR() && cl_first_person_uses_world_model.GetBool() ) { return true; } if ( UseVR() ) { static ConVarRef vr_first_person_uses_world_model( "vr_first_person_uses_world_model" ); if ( vr_first_person_uses_world_model.GetBool() ) { return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool C_BasePlayer::IsLocalPlayer( void ) const { return ( GetLocalPlayer() == this ); } int C_BasePlayer::GetUserID( void ) { player_info_t pi; if ( !engine->GetPlayerInfo( entindex(), &pi ) ) return -1; return pi.userID; } // For weapon prediction void C_BasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) { // FIXME } void C_BasePlayer::UpdateClientData( void ) { // Update all the items for ( int i = 0; i < WeaponCount(); i++ ) { if ( GetWeapon(i) ) // each item updates it's successors GetWeapon(i)->UpdateClientData( this ); } } // Prediction stuff void C_BasePlayer::PreThink( void ) { #if !defined( NO_ENTITY_PREDICTION ) ItemPreFrame(); UpdateClientData(); UpdateUnderwaterState(); // Update the player's fog data if necessary. UpdateFogController(); if (m_lifeState >= LIFE_DYING) return; // // If we're not on the ground, we're falling. Update our falling velocity. // if ( !( GetFlags() & FL_ONGROUND ) ) { m_Local.m_flFallVelocity = -GetAbsVelocity().z; } #endif } void C_BasePlayer::PostThink( void ) { #if !defined( NO_ENTITY_PREDICTION ) MDLCACHE_CRITICAL_SECTION(); if ( IsAlive()) { // Need to do this on the client to avoid prediction errors if ( GetFlags() & FL_DUCKING ) { SetCollisionBounds( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); } else { SetCollisionBounds( VEC_HULL_MIN, VEC_HULL_MAX ); } if ( !CommentaryModeShouldSwallowInput( this ) ) { // do weapon stuff ItemPostFrame(); } if ( GetFlags() & FL_ONGROUND ) { m_Local.m_flFallVelocity = 0; } // Don't allow bogus sequence on player if ( GetSequence() == -1 ) { SetSequence( 0 ); } StudioFrameAdvance(); } // Even if dead simulate entities SimulatePlayerSimulatedEntities(); #endif } //----------------------------------------------------------------------------- // Purpose: send various tool messages - viewoffset, and base class messages (flex and bones) //----------------------------------------------------------------------------- void C_BasePlayer::GetToolRecordingState( KeyValues *msg ) { if ( !ToolsEnabled() ) return; VPROF_BUDGET( "C_BasePlayer::GetToolRecordingState", VPROF_BUDGETGROUP_TOOLS ); BaseClass::GetToolRecordingState( msg ); msg->SetInt( "baseplayer", 1 ); msg->SetInt( "localplayer", IsLocalPlayer() ? 1 : 0 ); msg->SetString( "playername", GetPlayerName() ); static CameraRecordingState_t state; state.m_flFOV = GetFOV(); float flZNear = view->GetZNear(); float flZFar = view->GetZFar(); CalcView( state.m_vecEyePosition, state.m_vecEyeAngles, flZNear, flZFar, state.m_flFOV ); state.m_bThirdPerson = !engine->IsPaused() && ::input->CAM_IsThirdPerson(); // this is a straight copy from ClientModeShared::OverrideView, // When that method is removed in favor of rolling it into CalcView, // then this code can (should!) be removed if ( state.m_bThirdPerson ) { const Vector& cam_ofs = g_ThirdPersonManager.GetCameraOffsetAngles(); QAngle camAngles; camAngles[ PITCH ] = cam_ofs[ PITCH ]; camAngles[ YAW ] = cam_ofs[ YAW ]; camAngles[ ROLL ] = 0; Vector camForward, camRight, camUp; AngleVectors( camAngles, &camForward, &camRight, &camUp ); VectorMA( state.m_vecEyePosition, -cam_ofs[ ROLL ], camForward, state.m_vecEyePosition ); // Override angles from third person camera VectorCopy( camAngles, state.m_vecEyeAngles ); } msg->SetPtr( "camera", &state ); } //----------------------------------------------------------------------------- // Purpose: Simulate the player for this frame //----------------------------------------------------------------------------- void C_BasePlayer::Simulate() { //Frame updates if ( this == C_BasePlayer::GetLocalPlayer() ) { //Update the flashlight Flashlight(); // Update the player's fog data if necessary. UpdateFogController(); } else { // update step sounds for all other players Vector vel; EstimateAbsVelocity( vel ); UpdateStepSound( GetGroundSurface(), GetAbsOrigin(), vel ); } BaseClass::Simulate(); if ( IsNoInterpolationFrame() || Teleported() ) { ResetLatched(); } } //----------------------------------------------------------------------------- // Purpose: // Output : CBaseViewModel // Consider using GetRenderedWeaponModel() instead - it will get the // viewmodel or the active weapon as appropriate. //----------------------------------------------------------------------------- C_BaseViewModel *C_BasePlayer::GetViewModel( int index_ /*= 0*/, bool bObserverOK ) { Assert( index_ >= 0 && index_ < MAX_VIEWMODELS ); C_BaseViewModel *vm = m_hViewModel[index_]; if ( bObserverOK && GetObserverMode() == OBS_MODE_IN_EYE ) { C_BasePlayer *target = ToBasePlayer( GetObserverTarget() ); // get the targets viewmodel unless the target is an observer itself if ( target && target != this && !target->IsObserver() ) { vm = target->GetViewModel( index_ ); } } return vm; } C_BaseCombatWeapon *C_BasePlayer::GetActiveWeapon( void ) const { const C_BasePlayer *fromPlayer = this; // if localplayer is in InEye spectator mode, return weapon on chased player if ( (fromPlayer == GetLocalPlayer()) && ( GetObserverMode() == OBS_MODE_IN_EYE) ) { C_BaseEntity *target = GetObserverTarget(); if ( target && target->IsPlayer() ) { fromPlayer = ToBasePlayer( target ); } } return fromPlayer->C_BaseCombatCharacter::GetActiveWeapon(); } //========================================================= // Autoaim // set crosshair position to point to enemey //========================================================= Vector C_BasePlayer::GetAutoaimVector( float flScale ) { // Never autoaim a predicted weapon (for now) Vector forward; AngleVectors( GetAbsAngles() + m_Local.m_vecPunchAngle, &forward ); return forward; } void C_BasePlayer::PlayPlayerJingle() { #ifndef _XBOX // Find player sound for shooter player_info_t info; engine->GetPlayerInfo( entindex(), &info ); if ( !cl_customsounds.GetBool() ) return; // Doesn't have a jingle sound if ( !info.customFiles[1] ) return; char soundhex[ 16 ]; Q_binarytohex( (byte *)&info.customFiles[1], sizeof( info.customFiles[1] ), soundhex, sizeof( soundhex ) ); // See if logo has been downloaded. char fullsoundname[ 512 ]; Q_snprintf( fullsoundname, sizeof( fullsoundname ), "sound/temp/%s.wav", soundhex ); if ( !filesystem->FileExists( fullsoundname ) ) { char custname[ 512 ]; Q_snprintf( custname, sizeof( custname ), "download/user_custom/%c%c/%s.dat", soundhex[0], soundhex[1], soundhex ); // it may have been downloaded but not copied under materials folder if ( !filesystem->FileExists( custname ) ) return; // not downloaded yet // copy from download folder to materials/temp folder // this is done since material system can access only materials/*.vtf files if ( !engine->CopyLocalFile( custname, fullsoundname) ) return; } Q_snprintf( fullsoundname, sizeof( fullsoundname ), "temp/%s.wav", soundhex ); CLocalPlayerFilter filter; EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = fullsoundname; ep.m_flVolume = VOL_NORM; ep.m_SoundLevel = SNDLVL_NORM; C_BaseEntity::EmitSound( filter, GetSoundSourceIndex(), ep ); #endif } // Stuff for prediction void C_BasePlayer::SetSuitUpdate(const char *name, int fgroup, int iNoRepeat) { // FIXME: Do something here? } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BasePlayer::ResetAutoaim( void ) { #if 0 if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) { m_vecAutoAim = QAngle( 0, 0, 0 ); engine->CrosshairAngle( edict(), 0, 0 ); } #endif m_fOnTarget = false; } bool C_BasePlayer::ShouldPredict( void ) { #if !defined( NO_ENTITY_PREDICTION ) // Do this before calling into baseclass so prediction data block gets allocated if ( IsLocalPlayer() ) { return true; } #endif return false; } //----------------------------------------------------------------------------- // Purpose: Special processing for player simulation // NOTE: Don't chain to BaseClass!!!! //----------------------------------------------------------------------------- void C_BasePlayer::PhysicsSimulate( void ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "C_BasePlayer::PhysicsSimulate" ); // If we've got a moveparent, we must simulate that first. CBaseEntity *pMoveParent = GetMoveParent(); if (pMoveParent) { pMoveParent->PhysicsSimulate(); } // Make sure not to simulate this guy twice per frame if (m_nSimulationTick == gpGlobals->tickcount) return; m_nSimulationTick = gpGlobals->tickcount; if ( !IsLocalPlayer() ) return; C_CommandContext *ctx = GetCommandContext(); Assert( ctx ); Assert( ctx->needsprocessing ); if ( !ctx->needsprocessing ) return; ctx->needsprocessing = false; // Handle FL_FROZEN. if(GetFlags() & FL_FROZEN) { ctx->cmd.forwardmove = 0; ctx->cmd.sidemove = 0; ctx->cmd.upmove = 0; ctx->cmd.buttons = 0; ctx->cmd.impulse = 0; //VectorCopy ( pl.v_angle, ctx->cmd.viewangles ); } // Run the next command prediction->RunCommand( this, &ctx->cmd, MoveHelper() ); #endif } const QAngle& C_BasePlayer::GetPunchAngle() { return m_Local.m_vecPunchAngle.Get(); } void C_BasePlayer::SetPunchAngle( const QAngle &angle ) { m_Local.m_vecPunchAngle = angle; } float C_BasePlayer::GetWaterJumpTime() const { return m_flWaterJumpTime; } void C_BasePlayer::SetWaterJumpTime( float flWaterJumpTime ) { m_flWaterJumpTime = flWaterJumpTime; } float C_BasePlayer::GetSwimSoundTime() const { return m_flSwimSoundTime; } void C_BasePlayer::SetSwimSoundTime( float flSwimSoundTime ) { m_flSwimSoundTime = flSwimSoundTime; } //----------------------------------------------------------------------------- // Purpose: Return true if this object can be +used by the player //----------------------------------------------------------------------------- bool C_BasePlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) { return false; } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float C_BasePlayer::GetFOV( void ) { // Allow users to override the FOV during demo playback bool bUseDemoOverrideFov = engine->IsPlayingDemo() && demo_fov_override.GetFloat() > 0.0f; #if defined( REPLAY_ENABLED ) bUseDemoOverrideFov = bUseDemoOverrideFov && !g_pEngineClientReplay->IsPlayingReplayDemo(); #endif if ( bUseDemoOverrideFov ) { return clamp( demo_fov_override.GetFloat(), 10.0f, 90.0f ); } if ( GetObserverMode() == OBS_MODE_IN_EYE ) { C_BasePlayer *pTargetPlayer = dynamic_cast( GetObserverTarget() ); // get fov from observer target. Not if target is observer itself if ( pTargetPlayer && !pTargetPlayer->IsObserver() ) { return pTargetPlayer->GetFOV(); } } // Allow our vehicle to override our FOV if it's currently at the default FOV. float flDefaultFOV; IClientVehicle *pVehicle = GetVehicle(); if ( pVehicle ) { CacheVehicleView(); flDefaultFOV = ( m_flVehicleViewFOV == 0 ) ? GetDefaultFOV() : m_flVehicleViewFOV; } else { flDefaultFOV = GetDefaultFOV(); } float fFOV = ( m_iFOV == 0 ) ? flDefaultFOV : m_iFOV; // Don't do lerping during prediction. It's only necessary when actually rendering, // and it'll cause problems due to prediction timing messiness. if ( !prediction->InPrediction() ) { // See if we need to lerp the values for local player if ( IsLocalPlayer() && ( fFOV != m_iFOVStart ) && (m_Local.m_flFOVRate > 0.0f ) ) { float deltaTime = (float)( gpGlobals->curtime - m_flFOVTime ) / m_Local.m_flFOVRate; #if !defined( NO_ENTITY_PREDICTION ) if ( GetPredictable() ) { // m_flFOVTime was set to a predicted time in the future, because the FOV change was predicted. deltaTime = (float)( GetFinalPredictedTime() - m_flFOVTime ); deltaTime += ( gpGlobals->interpolation_amount * TICK_INTERVAL ); deltaTime /= m_Local.m_flFOVRate; } #endif if ( deltaTime >= 1.0f ) { //If we're past the zoom time, just take the new value and stop lerping m_iFOVStart = fFOV; } else { fFOV = SimpleSplineRemapValClamped( deltaTime, 0.0f, 1.0f, (float) m_iFOVStart, fFOV ); } } } return fFOV; } void RecvProxy_LocalVelocityX( const CRecvProxyData *pData, void *pStruct, void *pOut ) { C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; Assert( pPlayer ); float flNewVel_x = pData->m_Value.m_Float; Vector vecVelocity = pPlayer->GetLocalVelocity(); if( vecVelocity.x != flNewVel_x ) // Should this use an epsilon check? { vecVelocity.x = flNewVel_x; pPlayer->SetLocalVelocity( vecVelocity ); } } void RecvProxy_LocalVelocityY( const CRecvProxyData *pData, void *pStruct, void *pOut ) { C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; Assert( pPlayer ); float flNewVel_y = pData->m_Value.m_Float; Vector vecVelocity = pPlayer->GetLocalVelocity(); if( vecVelocity.y != flNewVel_y ) { vecVelocity.y = flNewVel_y; pPlayer->SetLocalVelocity( vecVelocity ); } } void RecvProxy_LocalVelocityZ( const CRecvProxyData *pData, void *pStruct, void *pOut ) { C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; Assert( pPlayer ); float flNewVel_z = pData->m_Value.m_Float; Vector vecVelocity = pPlayer->GetLocalVelocity(); if( vecVelocity.z != flNewVel_z ) { vecVelocity.z = flNewVel_z; pPlayer->SetLocalVelocity( vecVelocity ); } } void RecvProxy_ObserverTarget( const CRecvProxyData *pData, void *pStruct, void *pOut ) { C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; Assert( pPlayer ); EHANDLE hTarget; RecvProxy_IntToEHandle( pData, pStruct, &hTarget ); pPlayer->SetObserverTarget( hTarget ); } void RecvProxy_ObserverMode( const CRecvProxyData *pData, void *pStruct, void *pOut ) { C_BasePlayer *pPlayer = (C_BasePlayer *) pStruct; Assert( pPlayer ); pPlayer->SetObserverMode ( pData->m_Value.m_Int ); } //----------------------------------------------------------------------------- // Purpose: Remove this player from a vehicle //----------------------------------------------------------------------------- void C_BasePlayer::LeaveVehicle( void ) { if ( NULL == m_hVehicle.Get() ) return; // Let server do this for now #if 0 IClientVehicle *pVehicle = GetVehicle(); Assert( pVehicle ); int nRole = pVehicle->GetPassengerRole( this ); Assert( nRole != VEHICLE_ROLE_NONE ); SetParent( NULL ); // Find the first non-blocked exit point: Vector vNewPos = GetAbsOrigin(); QAngle qAngles = GetAbsAngles(); pVehicle->GetPassengerExitPoint( nRole, &vNewPos, &qAngles ); OnVehicleEnd( vNewPos ); SetAbsOrigin( vNewPos ); SetAbsAngles( qAngles ); m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; RemoveEffects( EF_NODRAW ); SetMoveType( MOVETYPE_WALK ); SetCollisionGroup( COLLISION_GROUP_PLAYER ); qAngles[ROLL] = 0; SnapEyeAngles( qAngles ); m_hVehicle = NULL; pVehicle->SetPassenger(nRole, NULL); Weapon_Switch( m_hLastWeapon ); #endif } float C_BasePlayer::GetMinFOV() const { if ( gpGlobals->maxClients == 1 ) { // Let them do whatever they want, more or less, in single player return 5; } else { return 75; } } float C_BasePlayer::GetFinalPredictedTime() const { return ( m_nFinalPredictedTick * TICK_INTERVAL ); } void C_BasePlayer::NotePredictionError( const Vector &vDelta ) { // don't worry about prediction errors when dead if ( !IsAlive() ) return; #if !defined( NO_ENTITY_PREDICTION ) Vector vOldDelta; GetPredictionErrorSmoothingVector( vOldDelta ); // sum all errors within smoothing time m_vecPredictionError = vDelta + vOldDelta; // remember when last error happened m_flPredictionErrorTime = gpGlobals->curtime; ResetLatched(); #endif } // offset curtime and setup bones at that time using fake interpolation // fake interpolation means we don't have reliable interpolation history (the local player doesn't animate locally) // so we just modify cycle and origin directly and use that as a fake guess bool C_BasePlayer::ForceSetupBonesAtTimeFakeInterpolation( matrix3x4_t *pBonesOut, float curtimeOffset ) { // we don't have any interpolation data, so fake it float cycle = m_flCycle; Vector origin = GetLocalOrigin(); // blow the cached prev bones InvalidateBoneCache(); // reset root position to flTime Interpolate( gpGlobals->curtime + curtimeOffset ); // force cycle back by boneDt m_flCycle = fmod( 10 + cycle + m_flPlaybackRate * curtimeOffset, 1.0f ); SetLocalOrigin( origin + curtimeOffset * GetLocalVelocity() ); // Setup bone state to extrapolate physics velocity bool bSuccess = SetupBones( pBonesOut, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, gpGlobals->curtime + curtimeOffset ); m_flCycle = cycle; SetLocalOrigin( origin ); return bSuccess; } bool C_BasePlayer::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt ) { if ( !IsLocalPlayer() ) return BaseClass::GetRagdollInitBoneArrays(pDeltaBones0, pDeltaBones1, pCurrentBones, boneDt); bool bSuccess = true; if ( !ForceSetupBonesAtTimeFakeInterpolation( pDeltaBones0, -boneDt ) ) bSuccess = false; if ( !ForceSetupBonesAtTimeFakeInterpolation( pDeltaBones1, 0 ) ) bSuccess = false; float ragdollCreateTime = PhysGetSyncCreateTime(); if ( ragdollCreateTime != gpGlobals->curtime ) { if ( !ForceSetupBonesAtTimeFakeInterpolation( pCurrentBones, ragdollCreateTime - gpGlobals->curtime ) ) bSuccess = false; } else { if ( !SetupBones( pCurrentBones, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, gpGlobals->curtime ) ) bSuccess = false; } return bSuccess; } void C_BasePlayer::GetPredictionErrorSmoothingVector( Vector &vOffset ) { #if !defined( NO_ENTITY_PREDICTION ) if ( engine->IsPlayingDemo() || !cl_smooth.GetInt() || !cl_predict->GetInt() || engine->IsPaused() ) { vOffset.Init(); return; } float errorAmount = ( gpGlobals->curtime - m_flPredictionErrorTime ) / cl_smoothtime.GetFloat(); if ( errorAmount >= 1.0f ) { vOffset.Init(); return; } errorAmount = 1.0f - errorAmount; vOffset = m_vecPredictionError * errorAmount; #else vOffset.Init(); #endif } IRagdoll* C_BasePlayer::GetRepresentativeRagdoll() const { return m_pRagdoll; } IMaterial *C_BasePlayer::GetHeadLabelMaterial( void ) { if ( GetClientVoiceMgr() == NULL ) return NULL; return GetClientVoiceMgr()->GetHeadLabelMaterial(); } bool IsInFreezeCam( void ) { C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: Set the fog controller data per player. // Input : &inputdata - //----------------------------------------------------------------------------- void C_BasePlayer::FogControllerChanged( bool bSnap ) { if ( m_Local.m_PlayerFog.m_hCtrl ) { fogparams_t *pFogParams = &(m_Local.m_PlayerFog.m_hCtrl->m_fog); /* Msg("Updating Fog Target: (%d,%d,%d) %.0f,%.0f -> (%d,%d,%d) %.0f,%.0f (%.2f seconds)\n", m_CurrentFog.colorPrimary.GetR(), m_CurrentFog.colorPrimary.GetB(), m_CurrentFog.colorPrimary.GetG(), m_CurrentFog.start.Get(), m_CurrentFog.end.Get(), pFogParams->colorPrimary.GetR(), pFogParams->colorPrimary.GetB(), pFogParams->colorPrimary.GetG(), pFogParams->start.Get(), pFogParams->end.Get(), pFogParams->duration.Get() );*/ // Setup the fog color transition. m_Local.m_PlayerFog.m_OldColor = m_CurrentFog.colorPrimary; m_Local.m_PlayerFog.m_flOldStart = m_CurrentFog.start; m_Local.m_PlayerFog.m_flOldEnd = m_CurrentFog.end; m_Local.m_PlayerFog.m_NewColor = pFogParams->colorPrimary; m_Local.m_PlayerFog.m_flNewStart = pFogParams->start; m_Local.m_PlayerFog.m_flNewEnd = pFogParams->end; m_Local.m_PlayerFog.m_flTransitionTime = bSnap ? -1 : gpGlobals->curtime; m_CurrentFog = *pFogParams; // Update the fog player's local fog data with the fog controller's data if need be. UpdateFogController(); } } //----------------------------------------------------------------------------- // Purpose: Check to see that the controllers data is up to date. //----------------------------------------------------------------------------- void C_BasePlayer::UpdateFogController( void ) { if ( m_Local.m_PlayerFog.m_hCtrl ) { // Don't bother copying while we're transitioning, since it'll be stomped in UpdateFogBlend(); if ( m_Local.m_PlayerFog.m_flTransitionTime == -1 && (m_hOldFogController == m_Local.m_PlayerFog.m_hCtrl) ) { fogparams_t *pFogParams = &(m_Local.m_PlayerFog.m_hCtrl->m_fog); if ( m_CurrentFog != *pFogParams ) { /* Msg("FORCING UPDATE: (%d,%d,%d) %.0f,%.0f -> (%d,%d,%d) %.0f,%.0f (%.2f seconds)\n", m_CurrentFog.colorPrimary.GetR(), m_CurrentFog.colorPrimary.GetB(), m_CurrentFog.colorPrimary.GetG(), m_CurrentFog.start.Get(), m_CurrentFog.end.Get(), pFogParams->colorPrimary.GetR(), pFogParams->colorPrimary.GetB(), pFogParams->colorPrimary.GetG(), pFogParams->start.Get(), pFogParams->end.Get(), pFogParams->duration.Get() );*/ m_CurrentFog = *pFogParams; } } } else { if ( m_CurrentFog.farz != -1 || m_CurrentFog.enable != false ) { // No fog controller in this level. Use default fog parameters. m_CurrentFog.farz = -1; m_CurrentFog.enable = false; } } // Update the fog blending state - of necessary. UpdateFogBlend(); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void C_BasePlayer::UpdateFogBlend( void ) { // Transition. if ( m_Local.m_PlayerFog.m_flTransitionTime != -1 ) { float flTimeDelta = gpGlobals->curtime - m_Local.m_PlayerFog.m_flTransitionTime; if ( flTimeDelta < m_CurrentFog.duration ) { float flScale = flTimeDelta / m_CurrentFog.duration; m_CurrentFog.colorPrimary.SetR( ( m_Local.m_PlayerFog.m_NewColor.r * flScale ) + ( m_Local.m_PlayerFog.m_OldColor.r * ( 1.0f - flScale ) ) ); m_CurrentFog.colorPrimary.SetG( ( m_Local.m_PlayerFog.m_NewColor.g * flScale ) + ( m_Local.m_PlayerFog.m_OldColor.g * ( 1.0f - flScale ) ) ); m_CurrentFog.colorPrimary.SetB( ( m_Local.m_PlayerFog.m_NewColor.b * flScale ) + ( m_Local.m_PlayerFog.m_OldColor.b * ( 1.0f - flScale ) ) ); m_CurrentFog.start.Set( ( m_Local.m_PlayerFog.m_flNewStart * flScale ) + ( ( m_Local.m_PlayerFog.m_flOldStart * ( 1.0f - flScale ) ) ) ); m_CurrentFog.end.Set( ( m_Local.m_PlayerFog.m_flNewEnd * flScale ) + ( ( m_Local.m_PlayerFog.m_flOldEnd * ( 1.0f - flScale ) ) ) ); } else { // Slam the final fog values. m_CurrentFog.colorPrimary.SetR( m_Local.m_PlayerFog.m_NewColor.r ); m_CurrentFog.colorPrimary.SetG( m_Local.m_PlayerFog.m_NewColor.g ); m_CurrentFog.colorPrimary.SetB( m_Local.m_PlayerFog.m_NewColor.b ); m_CurrentFog.start.Set( m_Local.m_PlayerFog.m_flNewStart ); m_CurrentFog.end.Set( m_Local.m_PlayerFog.m_flNewEnd ); m_Local.m_PlayerFog.m_flTransitionTime = -1; /* Msg("Finished transition to (%d,%d,%d) %.0f,%.0f\n", m_CurrentFog.colorPrimary.GetR(), m_CurrentFog.colorPrimary.GetB(), m_CurrentFog.colorPrimary.GetG(), m_CurrentFog.start.Get(), m_CurrentFog.end.Get() );*/ } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BasePlayer::GetSteamID( CSteamID *pID ) { // try to make this a little more efficient player_info_t pi; if ( engine->GetPlayerInfo( entindex(), &pi ) ) { if ( pi.friendsID && steamapicontext && steamapicontext->SteamUtils() ) { pID->InstancedSet( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual ); return true; } } return false; } #if defined USES_ECON_ITEMS //----------------------------------------------------------------------------- // Purpose: Update the visibility of our worn items. //----------------------------------------------------------------------------- void C_BasePlayer::UpdateWearables( void ) { for ( int i=0; iValidateModelIndex(); pItem->UpdateVisibility(); pItem->CreateShadow(); } } } #endif // USES_ECON_ITEMS //----------------------------------------------------------------------------- // Purpose: In meathook mode, fix the bone transforms to hang the user's own // avatar under the camera. //----------------------------------------------------------------------------- void C_BasePlayer::BuildFirstPersonMeathookTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed, const char *pchHeadBoneName ) { // Handle meathook mode. If we aren't rendering, just use last frame's transforms if ( !InFirstPersonView() ) return; // If we're in third-person view, don't do anything special. // If we're in first-person view rendering the main view and using the viewmodel, we shouldn't have even got here! // If we're in first-person view rendering the main view(s), meathook and headless. // If we're in first-person view rendering shadowbuffers/reflections, don't do anything special either (we could do meathook but with a head?) if ( IsAboutToRagdoll() ) { // We're re-animating specifically to set up the ragdoll. // Meathook can push the player through the floor, which makes the ragdoll fall through the world, which is no good. // So do nothing. return; } if ( !DrawingMainView() ) { return; } // If we aren't drawing the player anyway, don't mess with the bones. This can happen in Portal. if( !ShouldDrawThisPlayer() ) { return; } m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING ); int iHead = LookupBone( pchHeadBoneName ); if ( iHead == -1 ) { return; } matrix3x4_t &mHeadTransform = GetBoneForWrite( iHead ); // "up" on the head bone is along the negative Y axis - not sure why. //Vector vHeadTransformUp ( -mHeadTransform[0][1], -mHeadTransform[1][1], -mHeadTransform[2][1] ); //Vector vHeadTransformFwd ( mHeadTransform[0][1], mHeadTransform[1][1], mHeadTransform[2][1] ); Vector vHeadTransformTranslation ( mHeadTransform[0][3], mHeadTransform[1][3], mHeadTransform[2][3] ); // Find out where the player's head (driven by the HMD) is in the world. // We can't move this with animations or effects without causing nausea, so we need to move // the whole body so that the animated head is in the right place to match the player-controlled head. Vector vHeadUp; Vector vRealPivotPoint; if( UseVR() ) { VMatrix mWorldFromMideye = g_ClientVirtualReality.GetWorldFromMidEye(); // What we do here is: // * Take the required eye pos+orn - the actual pose the player is controlling with the HMD. // * Go downwards in that space by cl_meathook_neck_pivot_ingame_* - this is now the neck-pivot in the game world of where the player is actually looking. // * Now place the body of the animated character so that the head bone is at that position. // The head bone is the neck pivot point of the in-game character. Vector vRealMidEyePos = mWorldFromMideye.GetTranslation(); vRealPivotPoint = vRealMidEyePos - ( mWorldFromMideye.GetUp() * cl_meathook_neck_pivot_ingame_up.GetFloat() ) - ( mWorldFromMideye.GetForward() * cl_meathook_neck_pivot_ingame_fwd.GetFloat() ); } else { // figure out where to put the body from the aim angles Vector vForward, vRight, vUp; AngleVectors( MainViewAngles(), &vForward, &vRight, &vUp ); vRealPivotPoint = MainViewOrigin() - ( vUp * cl_meathook_neck_pivot_ingame_up.GetFloat() ) - ( vForward * cl_meathook_neck_pivot_ingame_fwd.GetFloat() ); } Vector vDeltaToAdd = vRealPivotPoint - vHeadTransformTranslation; // Now add this offset to the entire skeleton. for (int i = 0; i < hdr->numbones(); i++) { // Only update bones reference by the bone mask. if ( !( hdr->boneFlags( i ) & boneMask ) ) { continue; } matrix3x4_t& bone = GetBoneForWrite( i ); Vector vBonePos; MatrixGetTranslation ( bone, vBonePos ); vBonePos += vDeltaToAdd; MatrixSetTranslation ( vBonePos, bone ); } // Then scale the head to zero, but leave its position - forms a "neck stub". // This prevents us rendering junk all over the screen, e.g. inside of mouth, etc. MatrixScaleByZero( mHeadTransform ); // TODO: right now we nuke the hats by shrinking them to nothing, // but it feels like we should do something more sensible. // For example, for one sniper taunt he takes his hat off and waves it - would be nice to see it then. int iHelm = LookupBone( "prp_helmet" ); if ( iHelm != -1 ) { // Scale the helmet. matrix3x4_t &transformhelmet = GetBoneForWrite( iHelm ); MatrixScaleByZero( transformhelmet ); } iHelm = LookupBone( "prp_hat" ); if ( iHelm != -1 ) { matrix3x4_t &transformhelmet = GetBoneForWrite( iHelm ); MatrixScaleByZero( transformhelmet ); } } void CC_DumpClientSoundscapeData( const CCommand& args ) { C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( !pPlayer ) return; Msg("Client Soundscape data dump:\n"); Msg(" Position: %.2f %.2f %.2f\n", pPlayer->GetAbsOrigin().x, pPlayer->GetAbsOrigin().y, pPlayer->GetAbsOrigin().z ); Msg(" soundscape index: %d\n", pPlayer->m_Local.m_audio.soundscapeIndex.Get() ); Msg(" entity index: %d\n", pPlayer->m_Local.m_audio.ent.Get() ? pPlayer->m_Local.m_audio.ent->entindex() : -1 ); if ( pPlayer->m_Local.m_audio.ent.Get() ) { Msg(" entity pos: %.2f %.2f %.2f\n", pPlayer->m_Local.m_audio.ent.Get()->GetAbsOrigin().x, pPlayer->m_Local.m_audio.ent.Get()->GetAbsOrigin().y, pPlayer->m_Local.m_audio.ent.Get()->GetAbsOrigin().z ); if ( pPlayer->m_Local.m_audio.ent.Get()->IsDormant() ) { Msg(" ENTITY IS DORMANT\n"); } } bool bFoundOne = false; for ( int i = 0; i < NUM_AUDIO_LOCAL_SOUNDS; i++ ) { if ( pPlayer->m_Local.m_audio.localBits & (1<m_Local.m_audio.localSound[i]; Msg(" %d: %.2f %.2f %.2f\n", i, vecPos.x,vecPos.y, vecPos.z ); } } Msg("End dump.\n"); } static ConCommand soundscape_dumpclient("soundscape_dumpclient", CC_DumpClientSoundscapeData, "Dumps the client's soundscape data.\n", FCVAR_CHEAT);