//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "player.h" #include "usercmd.h" #include "igamemovement.h" #include "mathlib/mathlib.h" #include "client.h" #include "player_command.h" #include "movehelper_server.h" #include "iservervehicle.h" #include "tier0/vprof.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern IGameMovement *g_pGameMovement; extern CMoveData *g_pMoveData; // This is a global because it is subclassed by each game. extern ConVar sv_noclipduringpause; ConVar sv_maxusrcmdprocessticks_warning( "sv_maxusrcmdprocessticks_warning", "-1", FCVAR_RELEASE, "Print a warning when user commands get dropped due to insufficient usrcmd ticks allocated, number of seconds to throttle, negative disabled" ); static ConVar sv_maxusrcmdprocessticks_holdaim( "sv_maxusrcmdprocessticks_holdaim", "1", FCVAR_RELEASE, "Hold client aim for multiple server sim ticks when client-issued usrcmd contains multiple actions (0: off; 1: hold this server tick; 2+: hold multiple ticks)" ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPlayerMove::CPlayerMove( void ) { } //----------------------------------------------------------------------------- // Purpose: We're about to run this usercmd for the specified player. We can set up groupinfo and masking here, etc. // This is the time to examine the usercmd for anything extra. This call happens even if think does not. // Input : *player - // *cmd - //----------------------------------------------------------------------------- void CPlayerMove::StartCommand( CBasePlayer *player, CUserCmd *cmd ) { VPROF( "CPlayerMove::StartCommand" ); #if !defined( NO_ENTITY_PREDICTION ) && defined( USE_PREDICTABLEID ) CPredictableId::ResetInstanceCounters(); #endif player->m_pCurrentCommand = cmd; CBaseEntity::SetPredictionRandomSeed( cmd ); CBaseEntity::SetPredictionPlayer( player ); #if defined (HL2_DLL) // pull out backchannel data and move this out int i; for (i = 0; i < cmd->entitygroundcontact.Count(); i++) { int entindex = cmd->entitygroundcontact[i].entindex; CBaseEntity *pEntity = CBaseEntity::Instance( INDEXENT( entindex) ); if (pEntity) { CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); if (pAnimating) { pAnimating->SetIKGroundContactInfo( cmd->entitygroundcontact[i].minheight, cmd->entitygroundcontact[i].maxheight ); } } } #endif } //----------------------------------------------------------------------------- // Purpose: We've finished running a user's command // Input : *player - //----------------------------------------------------------------------------- void CPlayerMove::FinishCommand( CBasePlayer *player ) { VPROF( "CPlayerMove::FinishCommand" ); player->m_pCurrentCommand = NULL; CBaseEntity::SetPredictionRandomSeed( NULL ); CBaseEntity::SetPredictionPlayer( NULL ); } //----------------------------------------------------------------------------- // Purpose: Checks if the player is standing on a moving entity and adjusts velocity and // basevelocity appropriately // Input : *player - // frametime - //----------------------------------------------------------------------------- void CPlayerMove::CheckMovingGround( CBasePlayer *player, double frametime ) { VPROF( "CPlayerMove::CheckMovingGround()" ); CBaseEntity *groundentity; if ( player->GetFlags() & FL_ONGROUND ) { groundentity = player->GetGroundEntity(); if ( groundentity && ( groundentity->GetFlags() & FL_CONVEYOR) ) { Vector vecNewVelocity; groundentity->GetGroundVelocityToApply( vecNewVelocity ); if ( player->GetFlags() & FL_BASEVELOCITY ) { vecNewVelocity += player->GetBaseVelocity(); } player->SetBaseVelocity( vecNewVelocity ); player->AddFlag( FL_BASEVELOCITY ); } } if ( !( player->GetFlags() & FL_BASEVELOCITY ) ) { // Apply momentum (add in half of the previous frame of velocity first) player->ApplyAbsVelocityImpulse( (1.0 + ( frametime * 0.5 )) * player->GetBaseVelocity() ); player->SetBaseVelocity( vec3_origin ); } player->RemoveFlag( FL_BASEVELOCITY ); } //----------------------------------------------------------------------------- // Purpose: Prepares for running movement // Input : *player - // *ucmd - // *pHelper - // *move - // time - //----------------------------------------------------------------------------- void CPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) { VPROF( "CPlayerMove::SetupMove" ); // Allow sound, etc. to be created by movement code move->m_bFirstRunOfFunctions = true; move->m_bGameCodeMovedPlayer = false; if ( player->GetPreviouslyPredictedOrigin() != player->GetAbsOrigin() ) { move->m_bGameCodeMovedPlayer = true; } // Prepare the usercmd fields move->m_nImpulseCommand = ucmd->impulse; move->m_vecViewAngles = ucmd->viewangles; CBaseEntity *pMoveParent = player->GetMoveParent(); if (!pMoveParent) { move->m_vecAbsViewAngles = move->m_vecViewAngles; } else { matrix3x4_t viewToParent, viewToWorld; AngleMatrix( move->m_vecViewAngles, viewToParent ); ConcatTransforms( pMoveParent->EntityToWorldTransform(), viewToParent, viewToWorld ); MatrixAngles( viewToWorld, move->m_vecAbsViewAngles ); } move->m_nButtons = ucmd->buttons; // Ingore buttons for movement if at controls if ( player->GetFlags() & FL_ATCONTROLS ) { move->m_flForwardMove = 0; move->m_flSideMove = 0; move->m_flUpMove = 0; } else { move->m_flForwardMove = ucmd->forwardmove; move->m_flSideMove = ucmd->sidemove; move->m_flUpMove = ucmd->upmove; } // Prepare remaining fields move->m_flClientMaxSpeed = player->m_flMaxspeed; move->m_nOldButtons = player->m_Local.m_nOldButtons; move->m_vecAngles = player->pl.v_angle; move->m_vecVelocity = player->GetAbsVelocity(); move->m_nPlayerHandle = player; move->SetAbsOrigin( player->GetAbsOrigin() ); // Copy constraint information if ( player->m_hConstraintEntity.Get() ) move->m_vecConstraintCenter = player->m_hConstraintEntity.Get()->GetAbsOrigin(); else move->m_vecConstraintCenter = player->m_vecConstraintCenter; move->m_flConstraintRadius = player->m_flConstraintRadius; move->m_flConstraintWidth = player->m_flConstraintWidth; move->m_flConstraintSpeedFactor = player->m_flConstraintSpeedFactor; move->m_bConstraintPastRadius = player->m_bConstraintPastRadius; // setup trace optimization g_pGameMovement->SetupMovementBounds( move ); } //----------------------------------------------------------------------------- // Purpose: Finishes running movement // Input : *player - // *move - // *ucmd - // time - //----------------------------------------------------------------------------- void CPlayerMove::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ) { VPROF( "CPlayerMove::FinishMove" ); player->m_flMaxspeed = move->m_flClientMaxSpeed; player->SetAbsOrigin( move->GetAbsOrigin() ); player->SetAbsVelocity( move->m_vecVelocity ); player->SetPreviouslyPredictedOrigin( move->GetAbsOrigin() ); player->m_Local.m_nOldButtons = move->m_nButtons; // Convert final pitch to body pitch float pitch = move->m_vecAngles[ PITCH ]; if ( pitch > 180.0f ) { pitch -= 360.0f; } pitch = clamp( pitch, -90, 90 ); move->m_vecAngles[ PITCH ] = pitch; player->SetBodyPitch( pitch ); // player->SetLocalAngles( move->m_vecAngles ); // The class had better not have changed during the move!! if ( player->m_hConstraintEntity ) Assert( move->m_vecConstraintCenter == player->m_hConstraintEntity.Get()->GetAbsOrigin() ); else Assert( move->m_vecConstraintCenter == player->m_vecConstraintCenter ); Assert( move->m_flConstraintRadius == player->m_flConstraintRadius ); Assert( move->m_flConstraintWidth == player->m_flConstraintWidth ); Assert( move->m_flConstraintSpeedFactor == player->m_flConstraintSpeedFactor ); Assert( move->m_bConstraintPastRadius == player->m_bConstraintPastRadius ); } //----------------------------------------------------------------------------- // Purpose: Called before player thinks // Input : *player - // thinktime - //----------------------------------------------------------------------------- void CPlayerMove::RunPreThink( CBasePlayer *player ) { VPROF( "CPlayerMove::RunPreThink" ); // Run think functions on the player VPROF_SCOPE_BEGIN( "player->PhysicsRunThink()" ); if ( !player->PhysicsRunThink() ) return; VPROF_SCOPE_END(); VPROF_SCOPE_BEGIN( "g_pGameRules->PlayerThink( player )" ); // Called every frame to let game rules do any specific think logic for the player g_pGameRules->PlayerThink( player ); VPROF_SCOPE_END(); VPROF_SCOPE_BEGIN( "player->PreThink()" ); player->PreThink(); VPROF_SCOPE_END(); } //----------------------------------------------------------------------------- // Purpose: Runs the PLAYER's thinking code if time. There is some play in the exact time the think // function will be called, because it is called before any movement is done // in a frame. Not used for pushmove objects, because they must be exact. // Returns false if the entity removed itself. // Input : *ent - // frametime - // clienttimebase - // Output : void CPlayerMove::RunThink //----------------------------------------------------------------------------- void CPlayerMove::RunThink (CBasePlayer *player, double frametime ) { VPROF( "CPlayerMove::RunThink" ); int thinktick = player->GetNextThinkTick(); if ( thinktick <= 0 || thinktick > player->m_nTickBase ) return; //gpGlobals->curtime = thinktime; player->SetNextThink( TICK_NEVER_THINK ); // Think player->Think(); } //----------------------------------------------------------------------------- // Purpose: Called after player movement // Input : *player - // thinktime - // frametime - //----------------------------------------------------------------------------- void CPlayerMove::RunPostThink( CBasePlayer *player ) { VPROF( "CPlayerMove::RunPostThink" ); // Run post-think player->PostThink(); } void CommentarySystem_PePlayerRunCommand( CBasePlayer *player, CUserCmd *ucmd ); //----------------------------------------------------------------------------- // Purpose: Runs movement commands for the player // Input : *player - // *ucmd - // *moveHelper - // Output : void CPlayerMove::RunCommand //----------------------------------------------------------------------------- void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *moveHelper ) { const float playerCurTime = player->m_nTickBase * TICK_INTERVAL; const float playerFrameTime = player->m_bGamePaused ? 0 : TICK_INTERVAL; const float flTimeAllowedForProcessing = player->ConsumeMovementTimeForUserCmdProcessing( playerFrameTime ); if ( !player->IsBot() && !player->IsHLTV() && ( flTimeAllowedForProcessing < playerFrameTime ) ) { // Make sure that the activity in command is erased because player cheated or dropped too many packets double dblWarningFrequencyThrottle = sv_maxusrcmdprocessticks_warning.GetFloat(); if ( dblWarningFrequencyThrottle >= 0 ) { static double s_dblLastWarningTime = 0; double dblTimeNow = Plat_FloatTime(); if ( !s_dblLastWarningTime || ( dblTimeNow - s_dblLastWarningTime >= dblWarningFrequencyThrottle ) ) { s_dblLastWarningTime = dblTimeNow; Warning( "sv_maxusrcmdprocessticks_warning at server tick %u: Ignored client %s usrcmd (%.6f < %.6f)!\n", gpGlobals->tickcount, player->GetPlayerName(), flTimeAllowedForProcessing, playerFrameTime ); } } return; // Don't process this command } StartCommand( player, ucmd ); // Set globals appropriately const float serverCurTime = gpGlobals->curtime; const float serverFrameTime = gpGlobals->frametime; gpGlobals->curtime = playerCurTime; gpGlobals->frametime = playerFrameTime; // Add and subtract buttons we're forcing on the player ucmd->buttons |= player->m_afButtonForced; ucmd->buttons &= ~player->m_afButtonDisabled; if ( player->m_bGamePaused ) { // If no clipping and cheats enabled and noclipduring game enabled, then leave // forwardmove and angles stuff in usercmd if ( player->GetMoveType() == MOVETYPE_NOCLIP && sv_cheats->GetBool() && sv_noclipduringpause.GetBool() ) { gpGlobals->frametime = TICK_INTERVAL; } } /* // TODO: We can check whether the player is sending more commands than elapsed real time cmdtimeremaining -= ucmd->msec; if ( cmdtimeremaining < 0 ) { // return; } */ g_pGameMovement->StartTrackPredictionErrors( player ); CommentarySystem_PePlayerRunCommand( player, ucmd ); // Do weapon selection if ( ucmd->weaponselect != 0 ) { CBaseCombatWeapon *weapon = dynamic_cast< CBaseCombatWeapon * >( CBaseEntity::Instance( ucmd->weaponselect ) ); if ( weapon ) { VPROF( "player->SelectItem()" ); player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); } } IServerVehicle *pVehicle = player->GetVehicle(); // Latch in impulse. if ( ucmd->impulse ) { // Discard impulse commands unless the vehicle allows them. // FIXME: UsingStandardWeapons seems like a bad filter for this. The flashlight is an impulse command, for example. if ( !pVehicle || player->UsingStandardWeaponsInVehicle() ) { player->m_nImpulse = ucmd->impulse; } } // Update player input button states VPROF_SCOPE_BEGIN( "player->UpdateButtonState" ); player->UpdateButtonState( ucmd->buttons ); VPROF_SCOPE_END(); CheckMovingGround( player, TICK_INTERVAL ); g_pMoveData->m_vecOldAngles = player->pl.v_angle; // Copy from command to player unless game .dll has set angle using fixangle if ( player->pl.fixangle == FIXANGLE_NONE ) { player->pl.v_angle = ucmd->viewangles; } else if( player->pl.fixangle == FIXANGLE_RELATIVE ) { player->pl.v_angle = ucmd->viewangles + player->pl.anglechange; } // TrackIR player->SetEyeAngleOffset(ucmd->headangles); player->SetEyeOffset(ucmd->headoffset); // TrackIR player->SetAimDirection(ucmd->aimdirection); // Call standard client pre-think RunPreThink( player ); // Call Think if one is set RunThink( player, TICK_INTERVAL ); // Setup input. SetupMove( player, ucmd, moveHelper, g_pMoveData ); // Let the game do the movement. if ( !pVehicle ) { VPROF( "g_pGameMovement->ProcessMovement()" ); Assert( g_pGameMovement ); g_pGameMovement->ProcessMovement( player, g_pMoveData ); } else { VPROF( "pVehicle->ProcessMovement()" ); pVehicle->ProcessMovement( player, g_pMoveData ); } // Copy output FinishMove( player, ucmd, g_pMoveData ); // If we have to restore the view angle then do so right now if ( !player->IsBot() && ( gpGlobals->tickcount - player->GetLockViewanglesTickNumber() < sv_maxusrcmdprocessticks_holdaim.GetInt() ) ) { player->pl.v_angle = player->GetLockViewanglesData(); } // Restore server time temporarily before processing impacts, // so timestamps are all in the same time space gpGlobals->curtime = serverCurTime; gpGlobals->frametime = serverFrameTime; // Let server invoke any needed impact functions VPROF_SCOPE_BEGIN( "moveHelper->ProcessImpacts" ); moveHelper->ProcessImpacts(); VPROF_SCOPE_END(); gpGlobals->curtime = playerCurTime; gpGlobals->frametime = playerFrameTime; RunPostThink( player ); g_pGameMovement->FinishTrackPredictionErrors( player ); g_pGameMovement->Reset(); FinishCommand( player ); // Let time pass if ( gpGlobals->frametime > 0 ) { player->m_nTickBase++; } }