Over the Shoulder View: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
(Rewrite Template:Lang to Template:LanguageBar (This action was performed by a bot.))
 
(38 intermediate revisions by 8 users not shown)
Line 1: Line 1:
{{cleanup}}
{{LanguageBar|Over_the_Shoulder_View}}


== Overview ==
This tutorial will elaborate on how to create an over-the-shoulder camera view with collision detection. It has been created for the template and HL2:MP SDK on the source 2007 engine branch, therefore additional modifications might be necessary to allow functionality with a different SDK.


This tutorial will teach you how to create an over-the-shoulder camera view with collision detection.


Code is also included for variable cross hair screen position that will adjust so the player can have true aim with
= Step 1: separating view and aiming angle =
his/her cross hairs since going third person breaks it.


== Steps ==
To allow correct aiming in this mode, the actual viewangle and the angle the player will shoot along have to be split up first. To compensate the difference between the angles of viewsetup and player, the movement input will be translated before it is being sent to the server and clientside prediction system.


=== Step 1 - Default the game to third-person view ===
== client.dll: iinput.h ==


The first step is to make the game default to third person view. This is an easy step. Find and open the file <code>in_camera.cpp</code>, it will be in the client .dll files.
Expand the input interface with these functions:


Now, find every instance of <code>sv_cheats</code> and either delete it or comment it out (even the <code>if</code> conditions with it). This will get rid of the game checking to see if the server has sv_cheats enabled, and
<source lang=cpp> virtual void const GetCamViewangles( QAngle &view ) = 0;
if it is not, kicking them out of third person view.
virtual void SetCamViewangles( QAngle const &view ) = 0;</source>


Go to the last function of the file at the very end, which is: <code>void CInput::Init_Camera( void )</code>
== client.dll: input.h ==
Inside this function add: <code>m_fCameraInThirdPerson = true;</code> :


void CInput::Init_Camera( void )
Add these to the public area of the actual input implementation:
{
m_CameraIsOrthographic = false;
'''m_fCameraInThirdPerson = true;'''
}


This will have the game defaulting to third person view at the very get go with no need for the player to do anything. This file receives no further changes and may be closed.
<source lang=cpp> virtual void const GetCamViewangles( QAngle &view ){ view = m_angViewAngle; };
virtual void SetCamViewangles( QAngle const &view );</source>


=== Step 2 - Creating the over-the-shoulder view ===
And these as private members:


This is the step that will create the necessary changes to make the camera be over-the-shoulder. Find and open the file <code>clientmode_shared.cpp</code>. It is also in the client dll files.
<source lang=cpp> QAngle m_angViewAngle;
void CalcPlayerAngle( CUserCmd *cmd );</source>


Now, find the function <code>void ClientModeShared::OverrideView( CViewSetup *pSetup )</code>. This is where all the changes will be made.
== client.dll: in_main.cpp ==


Replace everything inside the function with:
Add these includes at the top somewhere between cbase.h and tier0/memdbgon.h:
<source lang=cpp>#include "view.h"
#include "hud_macros.h"</source>


// Let the player override the view.
Look for the function named:
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
 
if(!pPlayer)
<source lang=cpp>void CInput::Init_All (void)</source>
      return;
 
Add these lines to the end:
pPlayer->OverrideView( pSetup );
 
<source lang=cpp> m_angViewAngle = vec3_angle;
if( ::input->CAM_IsThirdPerson() )
HOOK_MESSAGE( SetThirdpersonAngle );</source>
{
 
Vector camForward, camRight, camUp;
'''Above''' that function add this snippet:
 
        AngleVectors( pPlayer->EyeAngles(), &camForward, &camRight, &camUp );
<source lang=cpp>static void __MsgFunc_SetThirdpersonAngle( bf_read &msg )
 
{
trace_t tr, tr2;
bool bFPOnly;
Vector vecStart, vecStop, vecDirection, vecSetDirection;  
msg.ReadBits( &bFPOnly, 1 );
static float camCurrentY;  
if ( bFPOnly && ::input->CAM_IsThirdPerson() )
static float camCurrentX=16.0f; //used for fluid camera transfers
return;
float camDelta=0.5f;
 
vecStart=pSetup->origin;
QAngle angAbs;
msg.ReadBitAngles( angAbs );
AngleVectors(pPlayer->EyeAngles(), &vecDirection);
::input->SetCamViewangles( angAbs );
}</source>
vecSetDirection.Init(0,0,1.0f);
 
vecDirection=vecDirection.Cross(vecSetDirection);
Define the functions that you have declared previously in the header:
vecStop = vecStart + vecDirection*52.0f;
<source lang=cpp>// Update the actual eyeangles of the player entity and translate the movement input
void CInput::CalcPlayerAngle( CUserCmd *cmd )
UTIL_TraceLine( vecStart, vecStop, MASK_ALL, pPlayer, COLLISION_GROUP_NONE, &tr );
{
C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
if (tr.fraction == 1) //are we far enough away to not be hugging a wall with the camera?
if ( !pl || !pl->AllowOvertheShoulderView() )
{
{
if(camCurrentX < 16.0f)
engine->SetViewAngles( m_angViewAngle );
camCurrentX +=camDelta;
return;
if(camCurrentX >16.0f)
}
camCurrentX=16.0f;
 
VectorMA( pSetup->origin, camCurrentX, camRight, pSetup->origin); //set the right offset
trace_t tr;
VectorMA( pSetup->origin, 16.0f, camUp, pSetup->origin);
const Vector eyePos = pl->EyePosition();
vecStart=tr.endpos;
UTIL_TraceLine( MainViewOrigin(), MainViewOrigin() + MainViewForward() * MAX_TRACE_LENGTH, MASK_SHOT, pl, COLLISION_GROUP_NONE, &tr );
}
 
else
// ensure that the player entity does not shoot towards the camera, get dist to plane where the player is on and add a constant
{
float flMinForward = abs( DotProduct( MainViewForward(), eyePos - MainViewOrigin() ) ) + 32.0f;
//we weren't clear on the right, lets check the left
Vector vecTrace = tr.endpos - tr.startpos;
float flLenOld = vecTrace.NormalizeInPlace();
vecStop = vecStart + vecDirection * -52.0f;
float flLen = max( flMinForward, flLenOld );
vecTrace *= flLen;
UTIL_TraceLine( vecStart, vecStop, MASK_ALL, pPlayer, COLLISION_GROUP_NONE, &tr );
 
Vector vecFinalDir = MainViewOrigin() + vecTrace - eyePos; //eyePos;
if (tr.fraction == 1) //are we clear on the left?
 
{
QAngle playerangles;
if(camCurrentX > -16.0f)
VectorAngles( vecFinalDir, playerangles );
camCurrentX -=camDelta;
engine->SetViewAngles( playerangles );
if(camCurrentX < -16.0f)
 
camCurrentX=-16.0f;
QAngle angCam = m_angViewAngle;
VectorMA( pSetup->origin, camCurrentX, camRight, pSetup->origin);
playerangles.z = angCam.z = 0;
VectorMA( pSetup->origin, 16.0f, camUp, pSetup->origin);
playerangles.x = angCam.x = 0;
vecStart=tr.endpos;
Vector cFwd, cRight, pFwd, pRight;
}
AngleVectors( angCam, &cFwd, &cRight, NULL );
else //not clear, so set the camera behind the player and raise it more than normal to maintain clear view
AngleVectors( playerangles, &pFwd, &pRight, NULL );
{
 
//set camera behind player because left and right are not clear
float flMove[2] = { cmd->forwardmove, cmd->sidemove };
VectorMA( pSetup->origin, 0.0f, camRight, pSetup->origin);
cmd->forwardmove = DotProduct( cFwd, pFwd ) * flMove[ 0 ] + DotProduct( cRight, pFwd ) * flMove[ 1 ];
//check to see if there is enough room above
cmd->sidemove = DotProduct( cRight, pRight ) * flMove[ 1 ] + DotProduct( cFwd, pRight ) * flMove[ 0 ];
AngleVectors(pPlayer->EyeAngles(), &vecDirection);
}
vecSetDirection.Init(1.0f,0,0);
void CInput::SetCamViewangles( QAngle const &view )
vecDirection=vecDirection.Cross(vecSetDirection);
{
vecStop = vecStart +vecDirection*32.0f;
m_angViewAngle = view;
 
UTIL_TraceLine( vecStart, vecStop, MASK_ALL, pPlayer, COLLISION_GROUP_NONE, &tr);
if ( m_angViewAngle.x > 180.0f )
m_angViewAngle.x -= 360.0f;
if(tr.fraction == 1)
if ( m_angViewAngle.x < -180.0f )
{
m_angViewAngle.x += 360.0f;
VectorMA( pSetup->origin, 32.0f, camUp, pSetup->origin);
}</source>
vecStart=tr.endpos;
{{Note|At this point you are actually not supposed to access the view data, it still works fine, though.}}
}
 
else //not enough room on left, right, or above, so move the camera eye level
Search for the functions
//TODO: Add code to make player translucent as well so the player can see better
<source lang=cpp>void CInput::ExtraMouseSample( float frametime, bool active )</source>
{
<source lang=cpp>void CInput::CreateMove ( int sequence_number, float input_sample_frametime, bool active )</source>
VectorMA( pSetup->origin, 0.0f, camUp, pSetup->origin);
 
}
In '''both''' of them add the following line after the conditional block that starts with <code>if ( active [...]</code> ''right before'' <code>engine->GetViewAngles( viewangles );</code>
}
<source lang=cpp>CalcPlayerAngle( cmd );</source>
 
}
== client.dll: in_mouse.cpp ==
 
AngleVectors(pPlayer->EyeAngles(), &vecDirection);  
Search for the function:
vecStop = vecStart + vecDirection * -96;
<source lang=cpp>void CInput::MouseMove( CUserCmd *cmd )</source>
UTIL_TraceLine( vecStart, vecStop, MASK_ALL, pPlayer, COLLISION_GROUP_NONE, &tr );
 
Now ''comment'' or ''remove'' these lines:
vecStart=pSetup->origin;
 
<source lang=cpp>QAngle viewangles;
vecStop = vecStart+vecDirection*-96;
engine->GetViewAngles( viewangles );
UTIL_TraceLine( vecStart, vecStop, MASK_ALL, pPlayer, COLLISION_GROUP_NONE, &tr );
engine->SetViewAngles( viewangles );</source>
 
//multiply the default distance by the percentage the traceline traveled, should put the camera infront of the object
And change the call to <code>ApplyMouse(...)</code> to read this:
 
<source lang=cpp>ApplyMouse( m_angViewAngle, cmd, mouse_x, mouse_y );</source>
if(tr.fraction != 1)
 
{
== shared: x_usermessages.cpp ==
camCurrentY = -96 * tr.fraction + 10.0f;
 
}
Open <code>sdk_usermessages.cpp</code> for the template mod or <code>hl2_usermessages.cpp</code> for HL2:MP and add this line:
else
 
        {
<source lang=cpp>usermessages->Register( "SetThirdpersonAngle", -1 );</source>
camCurrentY=-96.0f;
 
VectorMA( pSetup->origin, camCurrentY, camForward, pSetup->origin);
to the bottom of the function:
}
 
}
<source lang=cpp>void RegisterUserMessages()</source>
 
== server.dll: player.h ==
 
Change this declaration:
 
<source lang=cpp> void SnapEyeAngles( const QAngle &viewAngles );</source>
 
To look like this:
 
<source lang=cpp> void SnapEyeAngles( const QAngle &viewAngles, bool bFirstPersonOnly = false );</source>
 
== server.dll: player.cpp ==
 
Find the function:
 
<source lang=cpp>void CBasePlayer::SnapEyeAngles( const QAngle &viewAngles )</source>
 
Change it to look like this:
 
<source lang=cpp>void CBasePlayer::SnapEyeAngles( const QAngle &viewAngles, bool bFirstPersonOnly )
{
pl.v_angle = viewAngles;
pl.fixangle = FIXANGLE_ABSOLUTE;
 
CSingleUserRecipientFilter user( this );
user.MakeReliable();
 
UserMessageBegin( user, "SetThirdpersonAngle" );
WRITE_BOOL( bFirstPersonOnly );
WRITE_ANGLES( viewAngles );
MessageEnd();
}</source>
 
== shared: weapon_357.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
Go to this function:
 
<source lang=cpp>void CWeapon357::PrimaryAttack( void )</source>
 
change this line:
 
<source lang=cpp>pPlayer->SnapEyeAngles( angles );</source>
 
To read this:
 
<source lang=cpp>pPlayer->SnapEyeAngles( angles, true );</source>
 
= Step 2: Setting up the new view mode =
 
The camera will be made to default to thirdperson mode and code will be added to calculate the view transformations.
 
== client.dll: in_camera.cpp ==
 
Comment or remove this snippet:
 
<source lang=cpp>
// If cheats have been disabled, pull us back out of third-person view.
if ( sv_cheats && !sv_cheats->GetBool() )
{
CAM_ToFirstPerson();
return;
}</source>
 
In the function:
 
<source lang=cpp>void CInput::Init_Camera( void )</source>
 
add this line to the end:
 
<source lang=cpp> m_fCameraInThirdPerson = true;</source>
 
Remove the FCVAR_CHEAT flag from [[ConCommand|<code>ConCommand thirdperson</code>]] at the end of the file, so it looks like this:
 
<source lang=cpp>static ConCommand thirdperson( "thirdperson", ::CAM_ToThirdPerson, "Switch to thirdperson camera." );</source>
 
== client.dll: clientmode_shared.cpp ==
 
Define these [[Cvar|cvars]] near the top:
 
<source lang=cpp>static ConVar cam_ots_offset( "cam_ots_offset", "20 -75 20", FCVAR_ARCHIVE );
static ConVar cam_ots_offsetlag( "cam_ots_offset_lag", "64.0", FCVAR_ARCHIVE );
static ConVar cam_ots_originlag( "cam_ots_origin_lag", "38.0", FCVAR_ARCHIVE );
static ConVar cam_ots_translucencythreshold( "cam_ots_translucencyThreshold", "32.0", FCVAR_ARCHIVE );
 
static ConVar cam_ots_shake_enable( "cam_ots_shake_enable", "1", FCVAR_ARCHIVE );
static ConVar cam_ots_shake_speed( "cam_ots_shake_speed", "400", FCVAR_ARCHIVE );
static ConVar cam_ots_shake_amount( "cam_ots_shake_amount", "2", FCVAR_ARCHIVE );
static ConVar cam_ots_shake_interpspeed( "cam_ots_shake_interpspeed", "5", FCVAR_ARCHIVE );</source>
 
Find the function named:
 
<source lang=cpp>void ClientModeShared::OverrideView( CViewSetup *pSetup )</source>
 
Replace its content with this:
 
<div style = "overflow: auto; max-height: 70em">
<source lang=cpp>{
QAngle camAngles;
 
// Let the player override the view.
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
if(!pPlayer)
return;
 
pPlayer->OverrideView( pSetup );
float flPlayerTranslucency = 0;
 
if( ::input->CAM_IsThirdPerson() )
{
if ( pPlayer->AllowOvertheShoulderView() )
{
// hack to hide weird interpolation issue for the origin of the listenserver host
static ConVarRef fakelag( "net_fakelag" );
if ( fakelag.GetInt() != 1 )
fakelag.SetValue( 1 );
 
enum // for readability
{
CAM_RIGHT = 0,
CAM_FORWARD,
CAM_UP
};
const Vector camHull( 10, 10, 10 ); // collision test hull
float idealcamShoulderOffset[3] = { 20, -75, 20 }; // ideal local offset; right, fwd, up
float idealcamShoulderOffset_ColTest[3] = { 30, -75, 20 }; // ideal local offset; right, fwd, up
const float camLag = cam_ots_offsetlag.GetFloat(); // smoothing speed
const float camOriginLag = cam_ots_originlag.GetFloat();
const bool bDoShake = cam_ots_shake_enable.GetBool();
QAngle angPunch = pPlayer->GetPunchAngle();
 
Vector velo;
const float flShake_Speed = cam_ots_shake_speed.GetFloat();
float flShake_Amt = cam_ots_shake_amount.GetFloat();
float flPlayerGroundSpeed = 0;
static float flInterpolate_GroundState = 1;
static float flInterpolate_AirSpeed = 0;
static float flTimer = 0;
 
float add = gpGlobals->frametime * flShake_Speed;
flTimer += add;
if ( flTimer > 360.0f )
flTimer -= 360.0f;
pPlayer->EstimateAbsVelocity(velo);
flInterpolate_AirSpeed = Approach( velo.z, flInterpolate_AirSpeed, gpGlobals->frametime * 1000.0f );
flPlayerGroundSpeed = velo.Length2DSqr() / 90000.0f;
bool bOnGround = (pPlayer->GetFlags() & FL_ONGROUND);
flInterpolate_GroundState = Approach( (bOnGround ? 1.0f : 0.0f), flInterpolate_GroundState, gpGlobals->frametime * cam_ots_shake_interpspeed.GetFloat() );
const float flInterpolate_AirState = 1.0f - flInterpolate_GroundState;
 
if ( Q_strlen( cam_ots_offset.GetString() ) > 1 )
{
CCommand cmd;
cmd.Tokenize( cam_ots_offset.GetString() );
if ( cmd.ArgC() >= 3 )
{
for ( int i = 0; i < 3; i++ )
idealcamShoulderOffset_ColTest[ i ] = idealcamShoulderOffset[ i ] = atoi( cmd[ i ] );
idealcamShoulderOffset_ColTest[ CAM_RIGHT ] += 10 * Sign( idealcamShoulderOffset_ColTest[ CAM_RIGHT ] );
}
}
 
flShake_Amt *= Vector( idealcamShoulderOffset[0], idealcamShoulderOffset[1], idealcamShoulderOffset[2] ).Length2DSqr()
/ 6425; // scale by difference to default length
 
const float eyeposlag_snap_threshold = 128;
static Vector eyepos_lag = vec3_origin;
const Vector eyepos = pPlayer->EyePosition();
float eyeposDist = (eyepos - eyepos_lag).Length();
if ( eyeposDist > eyeposlag_snap_threshold )
eyepos_lag = eyepos;
 
float speedVariety = eyeposDist / eyeposlag_snap_threshold;
if ( speedVariety )
{
Vector delta = eyepos - eyepos_lag;
float maxLength = delta.NormalizeInPlace();
delta *= min( maxLength, gpGlobals->frametime * (camOriginLag + camOriginLag * camOriginLag * speedVariety) );
eyepos_lag += delta;
}
 
QAngle viewAng;
Vector directions[ 3 ];
Vector idealCamPos;
static Vector lastLocalCamPos = vec3_origin;
trace_t tr;
 
::input->GetCamViewangles( viewAng );
if ( bDoShake )
{
viewAng.y += flInterpolate_GroundState * flPlayerGroundSpeed * FastCos( DEG2RAD( flTimer ) ) * flShake_Amt;
viewAng.x += flInterpolate_GroundState * flPlayerGroundSpeed * sin( DEG2RAD( flTimer ) * 2.0f ) * flShake_Amt * 0.5f;
 
viewAng.x -= flInterpolate_AirState * clamp( flInterpolate_AirSpeed, -1000.0f, 1000.0f ) / 25.0f;
}
AngleVectors( viewAng, &directions[CAM_FORWARD], &directions[CAM_RIGHT], &directions[CAM_UP] );
 
idealCamPos = eyepos_lag;
 
// set up possible cam positions to test for
Vector camPositions[3] = { idealCamPos, idealCamPos, idealCamPos };
const float idealPos_Dir[3][3] = { 1, 1, 1,
-1, 1, 1,
0, 1, 1 }; // three possible offsets
for ( int x = 0; x < 3; x++ )
for ( int y = 0; y < 3; y++ )
{
UTIL_TraceHull( camPositions[x], camPositions[x] + idealcamShoulderOffset_ColTest[ y ] * directions[y] * idealPos_Dir[x][y],
-camHull, camHull, MASK_SOLID, pPlayer, COLLISION_GROUP_DEBRIS, &tr );
camPositions[x] = tr.endpos;
}
 
// choose the camoffsets that give us the furthest distance
int bestDirection = 0;
float maxBack = (camPositions[ 0 ] - eyepos_lag).Length();
for ( int i = 1; i < 3; i++ )
{
float curBack = abs( (camPositions[ i ] - eyepos_lag).Length() ) - 5.0f * i;
if ( maxBack < curBack )
{
maxBack = curBack;
bestDirection = i;
}
}
 
const float sortFinalCollisionTest[3] = { CAM_FORWARD, CAM_UP, CAM_RIGHT }; // do collisiontest to the side at the end
// get the final cam position
Vector tmpidealCamPos = idealCamPos;
for ( int i = 0; i < 3; i++ )
{
int colTest = sortFinalCollisionTest[ i ];
 
// first check how far we can go actually
float maxShoulderOffset = idealcamShoulderOffset[colTest] * idealPos_Dir[ bestDirection ][ colTest ];
UTIL_TraceHull( tmpidealCamPos, tmpidealCamPos + maxShoulderOffset * directions[colTest],
-camHull, camHull, MASK_SOLID, pPlayer, COLLISION_GROUP_DEBRIS, &tr );
maxShoulderOffset = Sign( maxShoulderOffset ) * ( tr.endpos - tr.startpos ).Length();
tmpidealCamPos = tr.endpos;
 
// approach this position
float idealOffset = maxShoulderOffset;
if ( idealOffset != lastLocalCamPos[colTest] )
lastLocalCamPos[ colTest ] = Approach( idealOffset, lastLocalCamPos[ colTest ],
gpGlobals->frametime * camLag * abs(idealcamShoulderOffset[colTest] / idealcamShoulderOffset[CAM_RIGHT]) );
 
// don't punch through walls due to interpolation
UTIL_TraceHull( idealCamPos, idealCamPos + lastLocalCamPos[ colTest ] * directions[colTest],
-camHull, camHull, MASK_SOLID, pPlayer, COLLISION_GROUP_DEBRIS, &tr );
 
idealCamPos = tr.endpos;
}
 
// get rid of other unintended cam shaking
Vector localCamOffset = idealCamPos - eyepos_lag;
for ( int i = 0; i < 3; i++ )
{
float dot = DotProduct( directions[i], localCamOffset );
lastLocalCamPos[ i ] = ( min( idealPos_Dir[ bestDirection ][ i ], idealcamShoulderOffset[ i ] ) < 0) ?
max( lastLocalCamPos[i], dot ) : min( lastLocalCamPos[i], dot );
}
 
pSetup->origin = idealCamPos;
pSetup->angles = viewAng + angPunch;
 
const float zLimits_Norm[2] = { VEC_HULL_MIN.z, VEC_HULL_MAX.z };
const float zLimits_Ducked[2] = { VEC_DUCK_HULL_MIN.z, VEC_DUCK_HULL_MAX.z };
const float *zLimits = pPlayer->m_Local.m_bDucking ? zLimits_Ducked : zLimits_Norm;
Vector orig = pPlayer->GetAbsOrigin();
orig += zLimits[0];
 
const float minOpaqueDistSquared = cam_ots_translucencythreshold.GetFloat() * cam_ots_translucencythreshold.GetFloat();
Vector camDelta = idealCamPos - orig;
camDelta.z -= min( zLimits[1], max(0,camDelta.z) );
float distSqr = (camDelta).LengthSqr();
flPlayerTranslucency = 1.0f - ( minOpaqueDistSquared ? min( 1, distSqr / minOpaqueDistSquared ) : 1.0f );
}
else
{
Vector cam_ofs;
 
::input->CAM_GetCameraOffset( cam_ofs );
 
camAngles[ PITCH ] = cam_ofs[ PITCH ];
camAngles[ YAW ] = cam_ofs[ YAW ];
camAngles[ ROLL ] = 0;
 
Vector camForward, camRight, camUp;
AngleVectors( camAngles, &camForward, &camRight, &camUp );
 
VectorMA( pSetup->origin, -cam_ofs[ ROLL ], camForward, pSetup->origin );
 
// Override angles from third person camera
VectorCopy( camAngles, pSetup->angles );
}
}
else if (::input->CAM_IsOrthographic())
{
pSetup->m_bOrtho = true;
float w, h;
::input->CAM_OrthographicSize( w, h );
w *= 0.5f;
h *= 0.5f;
pSetup->m_OrthoLeft  = -w;
pSetup->m_OrthoTop    = -h;
pSetup->m_OrthoRight  = w;
pSetup->m_OrthoBottom = h;
}
 
// translucency will not work flawlessly on player models that use one of the eyeshaders
// since those shaders do not support alpha blending by default
bool bWasTransulcent = pPlayer->GetRenderMode() != kRenderNormal ||
( pPlayer->GetActiveWeapon() && pPlayer->GetActiveWeapon()->GetRenderMode() != kRenderNormal );
bool bShouldBeTranslucent = !!flPlayerTranslucency;
if ( bWasTransulcent != bShouldBeTranslucent || bShouldBeTranslucent )
{
if ( bShouldBeTranslucent )
{
unsigned char alpha = ( 1.0f - flPlayerTranslucency ) * 255;
pPlayer->SetRenderMode( kRenderTransTexture, true );
pPlayer->SetRenderColorA( alpha );
if ( pPlayer->GetActiveWeapon() )
{
pPlayer->GetActiveWeapon()->SetRenderMode( kRenderTransTexture, true );
pPlayer->GetActiveWeapon()->SetRenderColorA( alpha );
}
}
else // not really required because this will be reset due to networking anyway
// disabling networking for those may cause other issues though
{
pPlayer->SetRenderMode( kRenderNormal, true );
pPlayer->SetRenderColorA( 255 );
if ( pPlayer->GetActiveWeapon() )
{
pPlayer->GetActiveWeapon()->SetRenderMode( kRenderNormal, true );
pPlayer->GetActiveWeapon()->SetRenderColorA( 255 );
}
}
}
}</source>
</div>
 
== client.dll: c_baseplayer.h ==
 
Only allow this mode when we're alive and spawned; declare this function as public:
 
<source lang=cpp>virtual bool AllowOvertheShoulderView();</source>
 
<div style = "font-size:12pt; color:#666666">
'''template SDK:'''
</div>
 
To enable stencil shadows for the player again, find <code>virtual ShadowType_t ShadowCastType()</code> and change the return value to <code>SHADOWS_RENDER_TO_TEXTURE_DYNAMIC</code>.
 
== client.dll: c_baseplayer.cpp ==
 
Implement the function above:
 
<source lang=cpp>bool C_BasePlayer::AllowOvertheShoulderView()
{
if ( !IsAlive() )
return false;
if ( GetTeamNumber() == TEAM_SPECTATOR )
return false;
return ::input->CAM_IsThirdPerson();
}</source>
 
<div style = "font-size:12pt; color:#666666">
'''template SDK:'''
</div>
 
To fix the flashlight in the template mod, find this function:
 
<source lang=cpp>void C_BasePlayer::UpdateFlashlight()</source>
 
and replace its content with:
 
<source lang=cpp>{
// 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 vec_origin = EyePosition();
QAngle ang_FlashlightAngle = EyeAngles();
int dist = FLASHLIGHT_DISTANCE;
 
if ( GetActiveWeapon() && ::input->CAM_IsThirdPerson() )
{
C_BaseCombatWeapon *pWeap = GetActiveWeapon();
int iAttachment = pWeap->LookupAttachment( "muzzle_flash" );
if ( iAttachment > 0 )
pWeap->GetAttachment( iAttachment, vec_origin, ang_FlashlightAngle );
else
{
Vector aimFwd;
AngleVectors( ang_FlashlightAngle, &aimFwd );
vec_origin += aimFwd * (VEC_HULL_MAX).Length2D();
}
dist = 0;
}
 
Vector vecForward, vecRight, vecUp;
AngleVectors( ang_FlashlightAngle, &vecForward, &vecRight, &vecUp );
 
// Update the light with the new position and direction.
m_pFlashlight->UpdateLight( vec_origin, vecForward, vecRight, vecUp, dist );
}
else if (m_pFlashlight)
{
// Turned off the flashlight; delete it.
delete m_pFlashlight;
m_pFlashlight = NULL;
}
}</source>
 
== shared: hl2mp_playeranimstate.h ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
Declare this variable as private in <code>CHL2MPPlayerAnimState</code>:
 
<source lang=cpp> QAngle m_angAiming;</source>
 
== shared: hl2mp_playeranimstate.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
Search for the function named:
 
<source lang=cpp>void CHL2MPPlayerAnimState::InitHL2MPAnimState( CHL2MP_Player *pPlayer )</source>
 
Add this line to the end:
 
<source lang=cpp> m_angAiming.Init();</source>
 
Search for the function:
 
<source lang=cpp>void CHL2MPPlayerAnimState::ComputePoseParam_AimPitch( CStudioHdr *pStudioHdr )</source>
 
Replace this line:
 
<source lang=cpp> GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimPitch, flAimPitch );</source>
 
with these:
 
<source lang=cpp> m_angAiming.x = Approach( flAimPitch, m_angAiming.x, gpGlobals->frametime * 110.0f );
GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimPitch, m_angAiming.x );</source>
 
Go to:
 
<source lang=cpp>void CHL2MPPlayerAnimState::ComputePoseParam_AimYaw( CStudioHdr *pStudioHdr )</source>
 
Replace this line:
 
<source lang=cpp> GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimYaw, flAimYaw );</source>
 
with these:
 
<source lang=cpp> m_angAiming.y = Approach( flAimYaw, m_angAiming.y, gpGlobals->frametime * 110.0f );
GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimYaw, m_angAiming.y );</source>
 
Find this function:
 
<source lang=cpp>void CHL2MPPlayerAnimState::Update( float eyeYaw, float eyePitch )</source>
 
Replace this block at the end:
 
<source lang=cpp>#ifdef CLIENT_DLL
if ( C_BasePlayer::ShouldDrawLocalPlayer() )
{
m_pHL2MPPlayer->SetPlaybackRate( 1.0f );
}
#endif</source>
 
With this snippet:
 
<source lang=cpp>#ifdef CLIENT_DLL
//if ( !m_pPlayer->IsLocalPlayer() || C_BasePlayer::ShouldDrawLocalPlayer() )
{
float flSeqSpeed = m_pPlayer->GetSequenceGroundSpeed( m_pPlayer->GetSequence() );
 
Vector vecVelocity;
GetOuterAbsVelocity( vecVelocity );
float flSpeed = vecVelocity.Length2DSqr();
bool bMoving_OnGround = flSpeed > 0.01f && m_pPlayer->GetGroundEntity();
 
flSpeed = bMoving_OnGround ? clamp( (vecVelocity.Length2DSqr() / (flSeqSpeed*flSeqSpeed)), 0.2f, 2.0f ) : 1.0f;
m_pHL2MPPlayer->SetPlaybackRate( flSpeed );
}
#endif</source>
 
Search for the function named:
 
<source lang=cpp>bool CHL2MPPlayerAnimState::HandleDucking( Activity &idealActivity )</source>
 
Replace this line:
 
<source lang=cpp>if ( m_pHL2MPPlayer->GetFlags() & FL_DUCKING )</source>
 
with these:
 
<source lang=cpp> if ( m_pHL2MPPlayer->GetFlags() & FL_DUCKING
#ifdef CLIENT_DLL
|| ( m_pHL2MPPlayer->IsLocalPlayer() && m_pHL2MPPlayer->m_Local.m_bDucking )
#endif
)</source>
 
Look for this function:
 
<source lang=cpp>void CHL2MPPlayerAnimState::ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr )</source>
 
After this line:
 
<source lang=cpp>QAngle angles = GetBasePlayer()->GetLocalAngles();</source>
 
Add this snippet:
 
<source lang=cpp>#ifdef CLIENT_DLL
if ( !m_pPlayer->IsLocalPlayer() )
angles = GetBasePlayer()->EyeAngles();
#endif</source>
 
== client.dll: fx_tracer.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
Add this include:
 
<source lang=cpp>#include "iinput.h"</source>
 
Find this function:
 
<source lang=cpp>Vector GetTracerOrigin( const CEffectData &data )</source>
 
Replace the line:
 
<source lang=cpp>if ( pWpn && pWpn->IsCarriedByLocalPlayer() )</source>
 
with this one:
 
<source lang=cpp>if ( pWpn && pWpn->IsCarriedByLocalPlayer() && !::input->CAM_IsThirdPerson() )</source>
 
== client.dll: c_hl2mp_player.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
To fix the flashlight in a HL2:MP mod, find the function named:
 
<source lang=cpp>void C_HL2MP_Player::UpdateFlashlight()</source>
 
Replace its content with:
 
<source lang=cpp>{
// The dim light is the flashlight.
if ( IsEffectActive( EF_DIMLIGHT ) )
{
if (!m_pHL2MPFlashLightEffect)
{
// Turned on the headlight; create it.
m_pHL2MPFlashLightEffect = new CHL2MPFlashlightEffect(index);
 
if (!m_pHL2MPFlashLightEffect)
return;
 
m_pHL2MPFlashLightEffect->TurnOn();
}
 
Vector vecForward, vecRight, vecUp;
Vector position = EyePosition();
 
if ( ::input->CAM_IsThirdPerson() )
{
if ( GetActiveWeapon() )
{
C_BaseCombatWeapon *pWeap = GetActiveWeapon();
int iAttachment = pWeap->LookupAttachment( "muzzle" );
if ( iAttachment > 0 )
{
QAngle ang;
pWeap->GetAttachment( iAttachment, position, ang );
AngleVectors( ang, &vecForward, &vecRight, &vecUp );
position += vecForward * 6.0f;
}
else
{
EyeVectors( &vecForward, &vecRight, &vecUp );
position += vecForward * (VEC_HULL_MAX).Length2D();
}
}
}
else
EyeVectors( &vecForward, &vecRight, &vecUp );
 
 
// Update the light with the new position and direction.
m_pHL2MPFlashLightEffect->UpdateLight( position, vecForward, vecRight, vecUp, FLASHLIGHT_DISTANCE );
}
else if (m_pHL2MPFlashLightEffect)
{
// Turned off the flashlight; delete it.
delete m_pHL2MPFlashLightEffect;
m_pHL2MPFlashLightEffect = NULL;
}
}</source>
 
Go to the function named:
 
<source lang=cpp>void C_HL2MP_Player::AddEntity( void )</source>
 
Replace these lines:
 
<source lang=cpp> else if( this != C_BasePlayer::GetLocalPlayer() || ::input->CAM_IsThirdPerson() )
{
int iAttachment = LookupAttachment( "anim_attachment_RH" );
 
if ( iAttachment < 0 )
return;
 
Vector vecOrigin;
//Tony; EyeAngles will return proper whether it's local player or not.
QAngle eyeAngles = EyeAngles();
 
GetAttachment( iAttachment, vecOrigin, eyeAngles );
 
Vector vForward;
AngleVectors( eyeAngles, &vForward );
 
trace_t tr;
UTIL_TraceLine( vecOrigin, vecOrigin + (vForward * 200), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );</source>
 
With this snippet:
 
<source lang=cpp> else if( this != C_BasePlayer::GetLocalPlayer() || ::input->CAM_IsThirdPerson() )
{
C_BaseCombatWeapon *pWeap = GetActiveWeapon();
int iAttachment = pWeap ? pWeap->LookupAttachment( "muzzle" ) : LookupAttachment( "anim_attachment_RH" );
 
if ( iAttachment < 0 )
return;
 
Vector vecOrigin;
//Tony; EyeAngles will return proper whether it's local player or not.
QAngle eyeAngles = EyeAngles();
 
if ( pWeap )
{
if ( iAttachment < 1 )
vecOrigin = EyePosition() - Vector( 0, 0, 32 );
else
pWeap->GetAttachment( iAttachment, vecOrigin, eyeAngles );
}
else
GetAttachment( iAttachment, vecOrigin, eyeAngles );
 
Vector vForward;
AngleVectors( eyeAngles, &vForward );
 
trace_t tr;
UTIL_TraceLine( vecOrigin, vecOrigin + (vForward * 200), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );</source>
 
== client.dll: flashlighteffect.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
Add this include:
 
<source lang=cpp>#include "iinput.h"</source>
 
Find the function named:
 
<source lang=cpp>void CFlashlightEffect::UpdateLightNew(const Vector &vecPos, const Vector &vecForward, const Vector &vecRight, const Vector &vecUp )</source>
 
Now search for the line:
 
<source lang=cpp>float flDist = (pmDirectionTrace.endpos - vOrigin).Length();</source>
 
Replace the following block with this:
 
<source lang=cpp>
float flDist = (pmDirectionTrace.endpos - vOrigin).Length();
float flFov = r_flashlightfov.GetFloat();
if ( flDist < flDistCutoff )
{
// We have an intersection with our cutoff range
// Determine how far to pull back, then trace to see if we are clear
float flPullBackDist = bPlayerOnLadder ? r_flashlightladderdist.GetFloat() : flDistCutoff - flDist; // Fixed pull-back distance if on ladder
float flDistModTmp = Lerp( flDistDrag, m_flDistMod, flPullBackDist );
if ( !bPlayerOnLadder )
{
trace_t pmBackTrace;
UTIL_TraceHull( vOrigin, vOrigin - vDir*(flPullBackDist-flEpsilon), Vector( -4, -4, -4 ), Vector( 4, 4, 4 ), iMask, &traceFilter, &pmBackTrace );
if( pmBackTrace.DidHit() )
{
// We have an intersection behind us as well, so limit our m_flDistMod
float flMaxDist = (pmBackTrace.endpos - vOrigin).Length() - flEpsilon;
if( flDistModTmp > flMaxDist )
flDistModTmp = flMaxDist;
}
}
if ( ::input->CAM_IsThirdPerson() )
{
flFov += min( 100, (abs( flDistModTmp ) * 4) );
m_flDistMod = Lerp( flDistDrag, m_flDistMod, 0.0f );
}
else
m_flDistMod = flDistModTmp;
}
else
{
m_flDistMod = Lerp( flDistDrag, m_flDistMod, 0.0f );
}
vOrigin = vOrigin - vDir * m_flDistMod;</source>
 
Replace this part:
 
<source lang=cpp> if ( bFlicker == false )
{
state.m_fLinearAtten = r_flashlightlinear.GetFloat();
state.m_fHorizontalFOVDegrees = r_flashlightfov.GetFloat();
state.m_fVerticalFOVDegrees = r_flashlightfov.GetFloat();
}</source>
 
with these lines:
 
<source lang=cpp> if ( bFlicker == false )
{
state.m_fLinearAtten = r_flashlightlinear.GetFloat();
state.m_fHorizontalFOVDegrees = flFov;
state.m_fVerticalFOVDegrees = flFov;
}</source>
 
== client.dll: c_baseanimating.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''template SDK:'''
</div>
 
Muzzleflash particles are broken for thirdperson in the template mod; to disable them find this function:
 
<source lang=cpp>void C_BaseAnimating::FireObsoleteEvent( const Vector& origin, const QAngle& angles, int event, const char *options )</source>
 
Right after this snippet:
 
<source lang=cpp> case CL_EVENT_MUZZLEFLASH0:
case CL_EVENT_MUZZLEFLASH1:
case CL_EVENT_MUZZLEFLASH2:
case CL_EVENT_MUZZLEFLASH3:
case CL_EVENT_NPC_MUZZLEFLASH0:
case CL_EVENT_NPC_MUZZLEFLASH1:
case CL_EVENT_NPC_MUZZLEFLASH2:
case CL_EVENT_NPC_MUZZLEFLASH3:
{</source>
 
add these lines:
 
<source lang=cpp> C_BaseEntity *follow = GetFollowedEntity();
if ( follow && follow->IsPlayer() && ::input->CAM_IsThirdPerson() )
break;</source>
 
== shared: weapon_physcannon.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
Go to the function named:
 
<source lang=cpp>void CallbackPhyscannonImpact( const CEffectData &data )</source>
 
Search for these lines:
 
<source lang=cpp> if ( pPlayer )
{
pEnt = pPlayer->GetViewModel();
}
 
// Format attachment for first-person view!
::FormatViewModelAttachment( vecAttachment, true );</source>
 
Replace them with this snippet:
 
<source lang=cpp> if ( pPlayer && !::input->CAM_IsThirdPerson() )
{
pEnt = pPlayer->GetViewModel();
 
// Format attachment for first-person view!
::FormatViewModelAttachment( vecAttachment, true );
}</source>
 
== client.dll: c_sprite.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
Add this include:
 
<source lang=cpp>#include "iinput.h"</source>
 
Find this function:
 
<source lang=cpp>int C_SpriteRenderer::DrawSprite(...)</source>
 
Find the block that begins with <code>if ( attachedto )</code> and replace it with this snippet:
 
<source lang=cpp> if ( attachedto )
{
C_BaseEntity *ent = attachedto->GetBaseEntity();
if ( ent )
{
if ( ent->GetBaseAnimating() && ent->GetBaseAnimating()->IsViewModel() && ::input->CAM_IsThirdPerson() )
{
C_BaseViewModel *pVm = (C_BaseViewModel*)ent;
C_BasePlayer *pOwner = ( pVm->GetOwner() && pVm->GetOwner()->IsPlayer() ) ? (C_BasePlayer*)pVm->GetOwner() : NULL;
if ( pOwner && pOwner->GetActiveWeapon() )
return 0; //worldmodels don't have the same attachments, so just get out (crossbow)
}
// don't draw viewmodel effects in reflections
if ( CurrentViewID() == VIEW_REFLECTION )
{
int group = ent->GetRenderGroup();
if ( group == RENDER_GROUP_VIEW_MODEL_TRANSLUCENT || group == RENDER_GROUP_VIEW_MODEL_OPAQUE )
return 0;
}
QAngle temp;
ent->GetAttachment( attachmentindex, effect_origin, temp );
}
}</source>
 
= OPTIONAL: Adding free aim =
 
The following code can be added to allow for toggleable free aiming. The mouse input will move the crosshair on the screen instead of rotating the view; moving the crosshair outside of a certain circular area will turn the view instead. The cvar <code>cam_ots_freeaim_interval_enable</code> will enable the use of an interval for view turning.
 
== client.dll: iinput.h ==
 
Add these methods to the interface:
 
<source lang=cpp> virtual bool CAM_IsFreeAiming() = 0;
virtual Vector2D CAM_GetFreeAimCursor() = 0;
virtual void CAM_UpdateAngleByFreeAiming( bool bUser = false ) = 0;
virtual void CAM_UpdateAngle180() = 0;</source>
 
== client.dll: input.h ==
 
Declare them in the implementation as public:
 
<source lang=cpp> virtual bool CAM_IsFreeAiming();
virtual Vector2D CAM_GetFreeAimCursor();
virtual void CAM_UpdateAngleByFreeAiming( bool bUser );
virtual void CAM_UpdateAngle180();</source>
 
Declare these members in a private block:
 
<source lang=cpp> QAngle m_angViewAngle_Delta;
Vector2D m_vecFreeAimPos;
Vector2D m_vecFreeAimPos_Delta;
void TryCursorMove( QAngle& viewangles, CUserCmd *cmd, float x, float y );</source>
 
== client.dll: in_main.cpp ==
 
Add these lines near the top of the file:
 
<source lang=cpp>static ConVar cam_ots_freeaim( "cam_ots_freeaim_enable", "1", FCVAR_ARCHIVE );
static ConVar cam_ots_freeaim_use_interval( "cam_ots_freeaim_interval_enable", "0", FCVAR_ARCHIVE ); // use an interval for view turning
static ConVar cam_ots_freeaim_movethreshold( "cam_ots_freeaim_move_threshold", "0.7", FCVAR_ARCHIVE );
static ConVar cam_ots_freeaim_movemax( "cam_ots_freeaim_move_max", "0.85", FCVAR_ARCHIVE );
 
static ConVar cam_ots_freeaim_speedturn( "cam_ots_freeaim_speed_turn", "1", FCVAR_ARCHIVE );
static ConVar cam_ots_freeaim_speed_evenyaw( "cam_ots_freeaim_speed_evenYawSpeed", "1", FCVAR_ARCHIVE );
 
static ConVar cam_ots_freeaim_autoturn_speed( "cam_ots_freeaim_autoturn_speed", "250", FCVAR_ARCHIVE );
 
extern void ScreenToWorld( int mousex, int mousey, float fov, const Vector& vecRenderOrigin, const QAngle& vecRenderAngles, Vector& vecPickingRay );</source>
 
Add these ConCommands:
 
<source lang=cpp>CON_COMMAND( cam_ots_TurnAuto, "" )
{
::input->CAM_UpdateAngleByFreeAiming(true);
}
CON_COMMAND( cam_ots_Turn180, "" )
{
::input->CAM_UpdateAngle180();
}</source>
 
Find this function:
 
<source lang=cpp>void CInput::Init_All (void)</source>
 
Add these lines to the end:
 
<source lang=cpp> m_vecFreeAimPos.Init();
m_vecFreeAimPos_Delta.Init();
m_angViewAngle_Delta.Init();</source>
 
Define the methods above:
 
<div style = "overflow: auto; max-height: 70em">
<source lang=cpp>void CInput::CAM_UpdateAngle180()
{
m_angViewAngle_Delta.y = 180.0f;
if ( CAM_IsFreeAiming() )
m_angViewAngle_Delta.y *= -Sign( m_vecFreeAimPos.x );
}
bool CInput::CAM_IsFreeAiming()
{
C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
return cam_ots_freeaim.GetBool() && pl && pl->AllowOvertheShoulderView();
}
Vector2D CInput::CAM_GetFreeAimCursor()
{
return m_vecFreeAimPos;
}
 
// Allows code to manipulate the view angles
void CInput::CAM_UpdateAngleByFreeAiming( bool bUser )
{
if ( prediction->InPrediction() && !prediction->IsFirstTimePredicted() )
return;
 
if ( !CAM_IsFreeAiming() )
return;
 
C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
if ( !pl )
return;
 
Vector vecForward, vecForwardCam;
int screen_x, screen_y;
engine->GetScreenSize( screen_x, screen_y );
screen_x *= m_vecFreeAimPos.x * 0.5f + 0.5f;
screen_y *= m_vecFreeAimPos.y * 0.5f + 0.5f;
ScreenToWorld( screen_x, screen_y, pl->GetFOV(), MainViewOrigin(), MainViewAngles(), vecForward );
 
trace_t tr;
UTIL_TraceLine( MainViewOrigin(), MainViewOrigin() + vecForward * MAX_TRACE_LENGTH, MASK_SHOT, pl, COLLISION_GROUP_NONE, &tr );
Vector vecWorldTarget = tr.endpos;
Vector vecDir_PlayerViewTarget = vecWorldTarget - pl->EyePosition();
AngleVectors( m_angViewAngle, &vecForwardCam, 0, 0 );
Vector vecDir_PlayerCamPlane = MainViewOrigin() - pl->EyePosition();
float flPlaneFwdComponent = DotProduct( vecDir_PlayerCamPlane, vecForwardCam );
vecDir_PlayerCamPlane -= flPlaneFwdComponent * vecForwardCam;
const float lengthCamToImaginaryTarget = FastSqrt( vecDir_PlayerViewTarget.LengthSqr() - vecDir_PlayerCamPlane.LengthSqr() );
Vector vecDir_PlayerTmpViewTarget = vecDir_PlayerCamPlane + vecForwardCam * lengthCamToImaginaryTarget;
Vector vecDir_PlayerCamPlaneDst;
QAngle ang1, ang2, angDelta;
VectorAngles( vecDir_PlayerTmpViewTarget, ang1 );
VectorAngles( vecDir_PlayerViewTarget, ang2 );
RotationDelta( ang1, ang2, &angDelta );
VectorRotate( vecDir_PlayerCamPlane, angDelta, vecDir_PlayerCamPlaneDst );
Vector vecDstDirImaginaryTarget_NoOffset = vecWorldTarget - vecDir_PlayerCamPlaneDst;
vecDstDirImaginaryTarget_NoOffset -= pl->EyePosition();
if ( !bUser )
{
m_angViewAngle_Delta.Init();
m_vecFreeAimPos_Delta.Init();
QAngle tmp;
VectorAngles( vecDstDirImaginaryTarget_NoOffset, tmp );
if ( tmp.IsValid() )
{
m_angViewAngle = tmp;
if ( m_angViewAngle.x > 180.0f )
m_angViewAngle.x -= 360.0f;
else if ( m_angViewAngle.x < -180.0f )
m_angViewAngle.x += 360.0f;
m_vecFreeAimPos.Init();
}
}
else
{
QAngle angTarget;
VectorAngles( vecDstDirImaginaryTarget_NoOffset, angTarget );
for ( int i = 0; i < 2; i++ )
m_angViewAngle_Delta[i] = AngleDiff( angTarget[i], m_angViewAngle[i] );
m_angViewAngle_Delta.z = 0;
if ( m_angViewAngle_Delta.y > 180.0f )
m_angViewAngle_Delta.y = 180.0f;
else if ( m_angViewAngle_Delta.y < -180.0f )
m_angViewAngle_Delta.y = -180.0f;
if ( !m_angViewAngle_Delta.IsValid() )
m_angViewAngle_Delta.Init();
else
m_vecFreeAimPos_Delta = -m_vecFreeAimPos;
}
}
// Defines crosshair position and rotates the view if appropriate
void CInput::TryCursorMove( QAngle& viewangles, CUserCmd *cmd, float x, float y )
{
static ConVarRef m_yaw( "m_yaw" );
static ConVarRef m_pitch( "m_pitch" );


This will create an over-the-shoulder view that is defaulted to be over the right shoulder and it is offset 16 units right, 16 units up, and 96 units back. At time of creation this function is still undergoing tweaking and bug fixing and will be updated as it is fixed, but for most it will be more than enough to get someone started with their mod and they can make changes as they see fit. The camera also has collision detection built in by way of tracelines. The process has the camera checking if there is enough room on the right, and if there is it will set the x and z location to be +16/+16 of the player's origin. It then goes to the end where the distance behind the player is checked, where another two tracelines are done, one going back at the +16/+16 location, and one going straight back from the player. This is to prevent the camera from going into objects.  
C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
float flFOV = ( pl ? pl->GetFOV() : 1.0f );
float flScaleNormalizedRange_Yaw = m_yaw.GetFloat() / flFOV;
float flScaleNormalizedRange_Pitch = m_pitch.GetFloat() / flFOV;
float flScale_Pitch = 1;
if ( cam_ots_freeaim_speed_evenyaw.GetInt() )
flScale_Pitch = engine->GetScreenAspectRatio();


If there is no room on the right, it will check the left and if there is room it will set x and z to -16/+16 and then check distance behind. If there is no room on the left or the right, z is made +32 and x 0. This will put the camera above the player's model's head so the player can still see. If there is not enough room above, the camera will be placed at eye level and whatever distance is determined. This is one area of tweaking needed still as it should make the model become around 75% transparent so the player can still see forward. This would only occur in crawl spaces such as air ducts or very small hallways.
m_vecFreeAimPos += Vector2D( x * flScaleNormalizedRange_Yaw, y * flScale_Pitch * flScaleNormalizedRange_Pitch );
float flLength = m_vecFreeAimPos.NormalizeInPlace();
float flMoveVars[ 2 ] = { cam_ots_freeaim_movethreshold.GetFloat(), cam_ots_freeaim_movemax.GetFloat() };
float flTurnSpeed;


=== Step 3 - Fixing a Small Bug ===
Vector2D moveDir = m_vecFreeAimPos;
There is one small bug that must be fixed and that is jittery animations when you move the camera up and down in the game with the mouse while looking around.
moveDir.NormalizeInPlace();


Find and open the file <code>c_sdk_player.cpp</code>. This should be in the client/sdk folder.
if ( cam_ots_freeaim_use_interval.GetInt() )
{
flTurnSpeed = cam_ots_freeaim_speedturn.GetFloat() * max( 0, (
(flLength - flMoveVars[0]) / (flMoveVars[1] - flMoveVars[0])
) );
viewangles += QAngle( moveDir.y * flTurnSpeed, moveDir.x * -flTurnSpeed, 0 );
}
else
{
flTurnSpeed = max( 0, ( flLength - flMoveVars[ 1 ] ) );
viewangles += QAngle( moveDir.y * flTurnSpeed * flFOV, moveDir.x * -flTurnSpeed * flFOV, 0 );
}


Find the function <code>void C_SDKPlayer::UpdateClientSideAnimation()</code> and replace its body with:
if ( viewangles.x > 89.0f )
viewangles.x = 89.0f;
else if ( viewangles.x < -89.0f )
viewangles.x = -89.0f;


// Update the animation data. It does the local check here so this works when using
if ( viewangles.y > 180.0f )
// a third-person camera (and we don't have valid player angles).
viewangles.y -= 360.0f;
if ( this == C_SDKPlayer::GetLocalSDKPlayer() )
else if ( viewangles.y < -180.0f )
m_PlayerAnimState->Update( EyeAngles()[YAW], EyeAngles()[PITCH] );
viewangles.y += 360.0f;
else
m_PlayerAnimState->Update( m_angEyeAngles[YAW], EyeAngles()[PITCH] );
BaseClass::UpdateClientSideAnimation();


Now animations should be perfectly smooth in scratch built mods.
flLength = min( flMoveVars[1], flLength );
m_vecFreeAimPos *= flLength;


For HL2MP SDK, the steps are a bit different.
cmd->mousedx = (int)x;
cmd->mousedy = (int)y;
}</source>
</div>


Find and open the file <code>hl2mp_player.cpp</code>. This should be in the server/hl2mp folder.
Change our previously defined function named:


Find the function <code>void CHL2MP_Player::PostThink( void )</code> and replace its body with:
<source lang=cpp>void CInput::CalcPlayerAngle( CUserCmd *cmd )</source>


BaseClass::PostThink();
to look like this:
if ( GetFlags() & FL_DUCKING )
{
SetCollisionBounds( VEC_CROUCH_TRACE_MIN, VEC_CROUCH_TRACE_MAX );
}
m_PlayerAnimState.Update();
QAngle angles = GetLocalAngles();
// We need to see if this is the client that we're dealing with.
CBasePlayer *pPlayer = dynamic_cast<CBasePlayer*>(this);
if ( pPlayer )
{
angles[PITCH] = EyeAngles()[PITCH];
angles[YAW] = EyeAngles()[YAW];
}
else
{
angles[PITCH] = EyeAngles()[PITCH];
angles[YAW] = m_angEyeAngles[YAW];
}
SetLocalAngles( angles );


Now there should be no jitterness at all when moving your mouse in the thirdperson view.
<div style = "overflow: auto; max-height: 70em">
<source lang=cpp>void CInput::CalcPlayerAngle( CUserCmd *cmd )
{
C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
if ( !pl || !pl->AllowOvertheShoulderView() )
{
engine->SetViewAngles( m_angViewAngle );
m_angViewAngle_Delta.Init();
return;
}


=== Step 4 - Self-adjusting crosshair ===
float deltaLen = m_angViewAngle_Delta.Length();
if ( deltaLen )
{
QAngle angDelta_New = m_angViewAngle_Delta;
for ( int i = 0; i < 2; i++ )
angDelta_New[i] = angDelta_New[i] - Approach( 0, angDelta_New[i],
gpGlobals->frametime * cam_ots_freeaim_autoturn_speed.GetFloat() * abs(m_angViewAngle_Delta[i]/deltaLen)
);


This step will initialize self-adjusting cross hairs that will be drawn on the spot the player will actually shoot since the change of camera breaks the center-screen default of the cross hairs.
m_angViewAngle += angDelta_New;
m_angViewAngle_Delta -= angDelta_New;
}
float deltaCursor = m_vecFreeAimPos_Delta.Length();
if ( deltaCursor )
{
Vector2D vecAimPos_New = m_vecFreeAimPos_Delta;
for ( int i = 0; i < 2; i++ )
vecAimPos_New[i] = vecAimPos_New[i] - Approach( 0, vecAimPos_New[i],
gpGlobals->frametime * cam_ots_freeaim_autoturn_speed.GetFloat() *
abs(m_vecFreeAimPos_Delta[i]/deltaCursor) * (engine->GetScreenAspectRatio() / pl->GetFOV())
);


Find and open the file <code>hud_crosshair.cpp</code>. This is in the client files.
m_vecFreeAimPos += vecAimPos_New;
m_vecFreeAimPos_Delta -= vecAimPos_New;
}


Now, find the fucntion <code>void CHudCrosshair::Paint( void )</code>.
trace_t tr;
const Vector eyePos = pl->EyePosition();
Vector vecForward = MainViewForward();
QAngle angCam = m_angViewAngle; //MainViewAngles();
if ( CAM_IsFreeAiming() )
{
int screen_x, screen_y;
engine->GetScreenSize( screen_x, screen_y );
screen_x *= m_vecFreeAimPos.x * 0.5f + 0.5f;
screen_y *= m_vecFreeAimPos.y * 0.5f + 0.5f;
ScreenToWorld( screen_x, screen_y, pl->GetFOV(), MainViewOrigin(), angCam, vecForward );
}
UTIL_TraceLine( MainViewOrigin(), MainViewOrigin() + vecForward * MAX_TRACE_LENGTH, MASK_SHOT, pl, COLLISION_GROUP_NONE, &tr );


Replace the inside of this function with:
// ensure that the player entity does not shoot towards the camera, get dist to plane where the player is on and add a constant
float flMinForward = abs( DotProduct( MainViewForward(), eyePos - MainViewOrigin() ) ) + 32.0f;
Vector vecTrace = tr.endpos - tr.startpos;
float flLenOld = vecTrace.NormalizeInPlace();
float flLen = max( flMinForward, flLenOld );
vecTrace *= flLen;


if ( !m_pCrosshair )
Vector vecFinalDir = MainViewOrigin() + vecTrace - eyePos; //eyePos;
return;
if ( !IsCurrentViewAccessAllowed() )
return;
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
Vector vecStart, vecStop, vecDirection, vecCrossPos;
trace_t tr;
AngleVectors(pPlayer->EyeAngles(), &vecDirection);
vecStart= pPlayer->EyePosition();
vecStop = vecStart + vecDirection * MAX_TRACE_LENGTH;
UTIL_TraceLine( vecStart, vecStop, MASK_ALL, pPlayer , COLLISION_GROUP_NONE, &tr );
ScreenTransform(tr.endpos, vecCrossPos);
m_pCrosshair->DrawSelf( 0.5f*ScreenWidth()+0.5f*ScreenWidth()*vecCrossPos[0]-0.5f*m_pCrosshair->Width(),
0.5f*ScreenHeight()+(0.5f*ScreenHeight()*-vecCrossPos[1])-0.5f*m_pCrosshair->Height(),
m_clrCrosshair );


QAngle playerangles;
VectorAngles( vecFinalDir, playerangles );
engine->SetViewAngles( playerangles );


This function will now have the cross hairs be drawn on the exact spot the player is actually going to hit, minus cone of fire spread. What it does is draw a traceline out pretty much exactly as it would if the player were to fire a weapon, get the exact world coordinates of the spot it hits, turn those coordinates into screen coordinates (not exactly though, read below for the full explanation), and then adjust those values to be the REAL screen coordinates of where the crosshair should be while painting it on the screen.
playerangles.z = angCam.z = 0;
playerangles.x = angCam.x = 0;
Vector cFwd, cRight, pFwd, pRight;
AngleVectors( angCam, &cFwd, &cRight, NULL );
AngleVectors( playerangles, &pFwd, &pRight, NULL );


== Conclusion ==
float flMove[2] = { cmd->forwardmove, cmd->sidemove };
Now you should have an overview-the-shoulder view that will adjust based on the player's surroundings and adjusting cross hairs so the players can aim properly.
cmd->forwardmove = DotProduct( cFwd, pFwd ) * flMove[ 0 ] + DotProduct( cRight, pFwd ) * flMove[ 1 ];
cmd->sidemove = DotProduct( cRight, pRight ) * flMove[ 1 ] + DotProduct( cFwd, pRight ) * flMove[ 0 ];
}</source>
</div>


== See also ==
Find the function named:
 
<source lang=cpp>void CInput::SetCamViewangles( QAngle const &view )</source>
 
Add this line to the end:
 
<source lang=cpp>m_vecFreeAimPos.Init();</source>
 
== client.dll: in_mouse.cpp ==
 
Go to
 
<source lang=cpp>void CInput::MouseMove( CUserCmd *cmd )</source>
 
Replace this line:
 
<source lang=cpp>ApplyMouse( m_angViewAngle, cmd, mouse_x, mouse_y );</source>
 
with these:
 
<source lang=cpp> if ( CAM_IsFreeAiming() )
TryCursorMove( m_angViewAngle, cmd, mouse_x, mouse_y );
else
ApplyMouse( m_angViewAngle, cmd, mouse_x, mouse_y );</source>
 
== client.dll: hud_crosshair.cpp ==
 
This code actually moves the crosshair by the mouse input we evaluated in <code>TryCursorMove()</code>.
 
Add this include:
 
<source lang=cpp>#include "iinput.h"</source>
 
Find the function named:
 
<source lang=cpp>void CHudCrosshair::Paint( void )</source>
 
Right ''after'' this snippet:
 
<source lang=cpp> float x, y;
x = ScreenWidth()/2;
y = ScreenHeight()/2;</source>
 
add these lines:
 
<source lang=cpp> if ( ::input->CAM_IsFreeAiming() )
{
Vector2D vec_AimPos = ::input->CAM_GetFreeAimCursor();
x *= vec_AimPos.x + 1.0f;
y *= vec_AimPos.y + 1.0f;
}</source>
 
== client.dll: hud_quickinfo.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
The following modifications ensure that the quickinfo icons move along with the crosshair.
 
Add this include:
 
<source lang=cpp>#include "iinput.h"</source>
 
Find this function:
 
<source lang=cpp>void CHUDQuickInfo::Paint()</source>
 
Search for these lines:
 
<source lang=cpp> int xCenter = ( ScreenWidth() - m_icon_c->Width() ) / 2;
int yCenter = ( ScreenHeight() - m_icon_c->Height() ) / 2;
float scalar  = 138.0f/255.0f;</source>
 
Add this snippet below:
 
<source lang=cpp> if ( ::input->CAM_IsFreeAiming() )
{
Vector2D vec_AimPos = ::input->CAM_GetFreeAimCursor();
xCenter = (ScreenWidth() / 2) * ( vec_AimPos.x + 1.0f );
xCenter -= m_icon_lb->Width() * 0.5f;
yCenter = (ScreenHeight() / 2) * ( vec_AimPos.y + 1.0f );
yCenter -= m_icon_lb->Height() * 0.5f;
}</source>
 
Scroll down to these lines:
 
<source lang=cpp> xCenter = ScreenWidth() / 2;
yCenter = ( ScreenHeight() - m_icon_lb->Height() ) / 2;</source>
 
Add these below:
 
<source lang=cpp> if ( ::input->CAM_IsFreeAiming() )
{
Vector2D vec_AimPos = ::input->CAM_GetFreeAimCursor();
xCenter *= ( vec_AimPos.x + 1.0f );
yCenter = (ScreenHeight() / 2) * ( vec_AimPos.y + 1.0f );
yCenter -= m_icon_lb->Height() * 0.5f;
}</source>
 
== shared: weapon_crossbow.cpp ==
 
<div style = "font-size:12pt; color:#666666">
'''HL2:MP SDK:'''
</div>
 
Reorient the aiming angle while using free aim and zooming; find the function named:
 
<source lang=cpp>void CWeaponCrossbow::ToggleZoom( void )</source>
 
Before <code>#endif</code> add this:
 
<source lang=cpp>#else
::input->CAM_UpdateAngleByFreeAiming();</source>
 
= See also =
*[[2D Fighter/Arcade game]]
*[[2D Fighter/Arcade game]]
*[[Third Person Camera]]
*[[Third Person Camera]]
[[Category:Free_source_code]]
[[Category:Tutorials]]

Latest revision as of 17:19, 18 July 2025

English (en)Русский (ru)Translate (Translate)

This tutorial will elaborate on how to create an over-the-shoulder camera view with collision detection. It has been created for the template and HL2:MP SDK on the source 2007 engine branch, therefore additional modifications might be necessary to allow functionality with a different SDK.


Step 1: separating view and aiming angle

To allow correct aiming in this mode, the actual viewangle and the angle the player will shoot along have to be split up first. To compensate the difference between the angles of viewsetup and player, the movement input will be translated before it is being sent to the server and clientside prediction system.

client.dll: iinput.h

Expand the input interface with these functions:

	virtual void		const GetCamViewangles( QAngle &view ) = 0;
	virtual void		SetCamViewangles( QAngle const &view ) = 0;

client.dll: input.h

Add these to the public area of the actual input implementation:

	virtual		void		const GetCamViewangles( QAngle &view ){ view = m_angViewAngle; };
	virtual		void		SetCamViewangles( QAngle const &view );

And these as private members:

	QAngle		m_angViewAngle;
	void		CalcPlayerAngle( CUserCmd *cmd );

client.dll: in_main.cpp

Add these includes at the top somewhere between cbase.h and tier0/memdbgon.h:

#include "view.h"
#include "hud_macros.h"

Look for the function named:

void CInput::Init_All (void)

Add these lines to the end:

	m_angViewAngle = vec3_angle;
	HOOK_MESSAGE( SetThirdpersonAngle );

Above that function add this snippet:

static void __MsgFunc_SetThirdpersonAngle( bf_read &msg )
{
	bool bFPOnly;
	msg.ReadBits( &bFPOnly, 1 );
	if ( bFPOnly && ::input->CAM_IsThirdPerson() )
		return;

	QAngle angAbs;
	msg.ReadBitAngles( angAbs );
	::input->SetCamViewangles( angAbs );
}

Define the functions that you have declared previously in the header:

// Update the actual eyeangles of the player entity and translate the movement input
void CInput::CalcPlayerAngle( CUserCmd *cmd )
{
	C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
	if ( !pl || !pl->AllowOvertheShoulderView() )
	{
		engine->SetViewAngles( m_angViewAngle );
		return;
	}

	trace_t tr;
	const Vector eyePos = pl->EyePosition();
	UTIL_TraceLine( MainViewOrigin(), MainViewOrigin() + MainViewForward() * MAX_TRACE_LENGTH, MASK_SHOT, pl, COLLISION_GROUP_NONE, &tr );

	// ensure that the player entity does not shoot towards the camera, get dist to plane where the player is on and add a constant
	float flMinForward = abs( DotProduct( MainViewForward(), eyePos - MainViewOrigin() ) ) + 32.0f;
	Vector vecTrace = tr.endpos - tr.startpos;
	float flLenOld = vecTrace.NormalizeInPlace();
	float flLen = max( flMinForward, flLenOld );
	vecTrace *= flLen;

	Vector vecFinalDir = MainViewOrigin() + vecTrace - eyePos; //eyePos;

	QAngle playerangles;
	VectorAngles( vecFinalDir, playerangles );
	engine->SetViewAngles( playerangles );

	QAngle angCam = m_angViewAngle;
	playerangles.z = angCam.z = 0;
	playerangles.x = angCam.x = 0;
	Vector cFwd, cRight, pFwd, pRight;
	AngleVectors( angCam, &cFwd, &cRight, NULL );
	AngleVectors( playerangles, &pFwd, &pRight, NULL );

	float flMove[2] = { cmd->forwardmove, cmd->sidemove };
	cmd->forwardmove = DotProduct( cFwd, pFwd ) * flMove[ 0 ] + DotProduct( cRight, pFwd ) * flMove[ 1 ];
	cmd->sidemove = DotProduct( cRight, pRight ) * flMove[ 1 ] + DotProduct( cFwd, pRight ) * flMove[ 0 ];
}
void CInput::SetCamViewangles( QAngle const &view )
{
	m_angViewAngle = view;

	if ( m_angViewAngle.x > 180.0f )
		m_angViewAngle.x -= 360.0f;
	if ( m_angViewAngle.x < -180.0f )
		m_angViewAngle.x += 360.0f;
}
Note.pngNote:At this point you are actually not supposed to access the view data, it still works fine, though.

Search for the functions

void CInput::ExtraMouseSample( float frametime, bool active )
void CInput::CreateMove ( int sequence_number, float input_sample_frametime, bool active )

In both of them add the following line after the conditional block that starts with if ( active [...] right before engine->GetViewAngles( viewangles );

CalcPlayerAngle( cmd );

client.dll: in_mouse.cpp

Search for the function:

void CInput::MouseMove( CUserCmd *cmd )

Now comment or remove these lines:

QAngle	viewangles;
engine->GetViewAngles( viewangles );
engine->SetViewAngles( viewangles );

And change the call to ApplyMouse(...) to read this:

ApplyMouse( m_angViewAngle, cmd, mouse_x, mouse_y );

shared: x_usermessages.cpp

Open sdk_usermessages.cpp for the template mod or hl2_usermessages.cpp for HL2:MP and add this line:

usermessages->Register( "SetThirdpersonAngle", -1 );

to the bottom of the function:

void RegisterUserMessages()

server.dll: player.h

Change this declaration:

	void					SnapEyeAngles( const QAngle &viewAngles );

To look like this:

	void					SnapEyeAngles( const QAngle &viewAngles, bool bFirstPersonOnly = false );

server.dll: player.cpp

Find the function:

void CBasePlayer::SnapEyeAngles( const QAngle &viewAngles )

Change it to look like this:

void CBasePlayer::SnapEyeAngles( const QAngle &viewAngles, bool bFirstPersonOnly )
{
	pl.v_angle = viewAngles;
	pl.fixangle = FIXANGLE_ABSOLUTE;

	CSingleUserRecipientFilter user( this );
	user.MakeReliable();

	UserMessageBegin( user, "SetThirdpersonAngle" );
		WRITE_BOOL( bFirstPersonOnly );
		WRITE_ANGLES( viewAngles );
	MessageEnd();
}

shared: weapon_357.cpp

HL2:MP SDK:

Go to this function:

void CWeapon357::PrimaryAttack( void )

change this line:

pPlayer->SnapEyeAngles( angles );

To read this:

pPlayer->SnapEyeAngles( angles, true );

Step 2: Setting up the new view mode

The camera will be made to default to thirdperson mode and code will be added to calculate the view transformations.

client.dll: in_camera.cpp

Comment or remove this snippet:

	// If cheats have been disabled, pull us back out of third-person view.
	if ( sv_cheats && !sv_cheats->GetBool() )
	{
		CAM_ToFirstPerson();
		return;
	}

In the function:

void CInput::Init_Camera( void )

add this line to the end:

	m_fCameraInThirdPerson = true;

Remove the FCVAR_CHEAT flag from ConCommand thirdperson at the end of the file, so it looks like this:

static ConCommand thirdperson( "thirdperson", ::CAM_ToThirdPerson, "Switch to thirdperson camera." );

client.dll: clientmode_shared.cpp

Define these cvars near the top:

static ConVar cam_ots_offset( "cam_ots_offset", "20 -75 20", FCVAR_ARCHIVE );
static ConVar cam_ots_offsetlag( "cam_ots_offset_lag", "64.0", FCVAR_ARCHIVE );
static ConVar cam_ots_originlag( "cam_ots_origin_lag", "38.0", FCVAR_ARCHIVE );
static ConVar cam_ots_translucencythreshold( "cam_ots_translucencyThreshold", "32.0", FCVAR_ARCHIVE );

static ConVar cam_ots_shake_enable( "cam_ots_shake_enable", "1", FCVAR_ARCHIVE );
static ConVar cam_ots_shake_speed( "cam_ots_shake_speed", "400", FCVAR_ARCHIVE );
static ConVar cam_ots_shake_amount( "cam_ots_shake_amount", "2", FCVAR_ARCHIVE );
static ConVar cam_ots_shake_interpspeed( "cam_ots_shake_interpspeed", "5", FCVAR_ARCHIVE );

Find the function named:

void ClientModeShared::OverrideView( CViewSetup *pSetup )

Replace its content with this:

{
	QAngle camAngles;

	// Let the player override the view.
	C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
	if(!pPlayer)
		return;

	pPlayer->OverrideView( pSetup );
	float flPlayerTranslucency = 0;

	if( ::input->CAM_IsThirdPerson() )
	{
		if ( pPlayer->AllowOvertheShoulderView() )
		{
			// hack to hide weird interpolation issue for the origin of the listenserver host
			static ConVarRef fakelag( "net_fakelag" );
			if ( fakelag.GetInt() != 1 )
				fakelag.SetValue( 1 );

			enum // for readability
			{
				CAM_RIGHT = 0,
				CAM_FORWARD,
				CAM_UP
			};
			const Vector camHull( 10, 10, 10 ); // collision test hull
			float idealcamShoulderOffset[3] = { 20, -75, 20 }; // ideal local offset; right, fwd, up
			float idealcamShoulderOffset_ColTest[3] = { 30, -75, 20 }; // ideal local offset; right, fwd, up
			const float camLag = cam_ots_offsetlag.GetFloat(); // smoothing speed
			const float camOriginLag = cam_ots_originlag.GetFloat();
			const bool bDoShake = cam_ots_shake_enable.GetBool();
			QAngle angPunch = pPlayer->GetPunchAngle();

			Vector velo;
			const float flShake_Speed = cam_ots_shake_speed.GetFloat();
			float flShake_Amt = cam_ots_shake_amount.GetFloat();
			float flPlayerGroundSpeed = 0;
			static float flInterpolate_GroundState = 1;
			static float flInterpolate_AirSpeed = 0;
			static float flTimer = 0;

			float add = gpGlobals->frametime * flShake_Speed;
			flTimer += add;
			if ( flTimer > 360.0f )
				flTimer -= 360.0f;
			pPlayer->EstimateAbsVelocity(velo);
			flInterpolate_AirSpeed = Approach( velo.z, flInterpolate_AirSpeed, gpGlobals->frametime * 1000.0f );
			flPlayerGroundSpeed = velo.Length2DSqr() / 90000.0f;
			bool bOnGround = (pPlayer->GetFlags() & FL_ONGROUND);
			flInterpolate_GroundState = Approach( (bOnGround ? 1.0f : 0.0f), flInterpolate_GroundState, gpGlobals->frametime * cam_ots_shake_interpspeed.GetFloat() );
			const float flInterpolate_AirState = 1.0f - flInterpolate_GroundState;

			if ( Q_strlen( cam_ots_offset.GetString() ) > 1 )
			{
				CCommand cmd;
				cmd.Tokenize( cam_ots_offset.GetString() );
				if ( cmd.ArgC() >= 3 )
				{
					for ( int i = 0; i < 3; i++ )
						idealcamShoulderOffset_ColTest[ i ] = idealcamShoulderOffset[ i ] = atoi( cmd[ i ] );
					idealcamShoulderOffset_ColTest[ CAM_RIGHT ] += 10 * Sign( idealcamShoulderOffset_ColTest[ CAM_RIGHT ] );
				}
			}

			flShake_Amt *= Vector( idealcamShoulderOffset[0], idealcamShoulderOffset[1], idealcamShoulderOffset[2] ).Length2DSqr()
				/ 6425; // scale by difference to default length

			const float eyeposlag_snap_threshold = 128;
			static Vector eyepos_lag = vec3_origin;
			const Vector eyepos = pPlayer->EyePosition();
			float eyeposDist = (eyepos - eyepos_lag).Length();
			if ( eyeposDist > eyeposlag_snap_threshold )
				eyepos_lag = eyepos;

			float speedVariety = eyeposDist / eyeposlag_snap_threshold;
			if ( speedVariety )
			{
				Vector delta = eyepos - eyepos_lag;
				float maxLength = delta.NormalizeInPlace();
				delta *= min( maxLength, gpGlobals->frametime * (camOriginLag + camOriginLag * camOriginLag * speedVariety) );
				eyepos_lag += delta;
			}

			QAngle viewAng;
			Vector directions[ 3 ];
			Vector idealCamPos;
			static Vector lastLocalCamPos = vec3_origin;
			trace_t tr;

			::input->GetCamViewangles( viewAng );
			if ( bDoShake )
			{
				viewAng.y += flInterpolate_GroundState * flPlayerGroundSpeed * FastCos( DEG2RAD( flTimer ) ) * flShake_Amt;
				viewAng.x += flInterpolate_GroundState * flPlayerGroundSpeed * sin( DEG2RAD( flTimer ) * 2.0f ) * flShake_Amt * 0.5f;

				viewAng.x -= flInterpolate_AirState * clamp( flInterpolate_AirSpeed, -1000.0f, 1000.0f ) / 25.0f;
			}
			AngleVectors( viewAng, &directions[CAM_FORWARD], &directions[CAM_RIGHT], &directions[CAM_UP] );

			idealCamPos = eyepos_lag;

			// set up possible cam positions to test for
			Vector camPositions[3] = { idealCamPos, idealCamPos, idealCamPos };
			const float idealPos_Dir[3][3] =	{	1, 1, 1,
													-1, 1, 1,
													0, 1, 1		}; // three possible offsets
			for ( int x = 0; x < 3; x++ )
				for ( int y = 0; y < 3; y++ )
				{
					UTIL_TraceHull( camPositions[x], camPositions[x] + idealcamShoulderOffset_ColTest[ y ] * directions[y] * idealPos_Dir[x][y],
						-camHull, camHull, MASK_SOLID, pPlayer, COLLISION_GROUP_DEBRIS, &tr );
					camPositions[x] = tr.endpos;
				}

			// choose the camoffsets that give us the furthest distance
			int bestDirection = 0;
			float maxBack = (camPositions[ 0 ] - eyepos_lag).Length();
			for ( int i = 1; i < 3; i++ )
			{
				float curBack = abs( (camPositions[ i ] - eyepos_lag).Length() ) - 5.0f * i;
				if ( maxBack < curBack )
				{
					maxBack = curBack;
					bestDirection = i;
				}
			}

			const float sortFinalCollisionTest[3] = { CAM_FORWARD, CAM_UP, CAM_RIGHT }; // do collisiontest to the side at the end
			// get the final cam position
			Vector tmpidealCamPos = idealCamPos;
			for ( int i = 0; i < 3; i++ )
			{
				int colTest = sortFinalCollisionTest[ i ];

				// first check how far we can go actually
				float maxShoulderOffset = idealcamShoulderOffset[colTest] * idealPos_Dir[ bestDirection ][ colTest ];
				UTIL_TraceHull( tmpidealCamPos, tmpidealCamPos + maxShoulderOffset * directions[colTest],
					-camHull, camHull, MASK_SOLID, pPlayer, COLLISION_GROUP_DEBRIS, &tr );
				maxShoulderOffset = Sign( maxShoulderOffset ) * ( tr.endpos - tr.startpos ).Length();
				tmpidealCamPos = tr.endpos;

				// approach this position
				float idealOffset = maxShoulderOffset;
				if ( idealOffset != lastLocalCamPos[colTest] )
					lastLocalCamPos[ colTest ] = Approach( idealOffset, lastLocalCamPos[ colTest ],
					gpGlobals->frametime * camLag * abs(idealcamShoulderOffset[colTest] / idealcamShoulderOffset[CAM_RIGHT]) );

				// don't punch through walls due to interpolation
				UTIL_TraceHull( idealCamPos, idealCamPos + lastLocalCamPos[ colTest ] * directions[colTest],
					-camHull, camHull, MASK_SOLID, pPlayer, COLLISION_GROUP_DEBRIS, &tr );

				idealCamPos = tr.endpos;
			}

			// get rid of other unintended cam shaking
			Vector localCamOffset = idealCamPos - eyepos_lag;
			for ( int i = 0; i < 3; i++ )
			{
				float dot = DotProduct( directions[i], localCamOffset );
				lastLocalCamPos[ i ] = ( min( idealPos_Dir[ bestDirection ][ i ], idealcamShoulderOffset[ i ] ) < 0) ?
					max( lastLocalCamPos[i], dot ) : min( lastLocalCamPos[i], dot );
			}

			pSetup->origin = idealCamPos;
			pSetup->angles = viewAng + angPunch;

			const float zLimits_Norm[2] = { VEC_HULL_MIN.z, VEC_HULL_MAX.z };
			const float zLimits_Ducked[2] = { VEC_DUCK_HULL_MIN.z, VEC_DUCK_HULL_MAX.z };
			const float *zLimits = pPlayer->m_Local.m_bDucking ? zLimits_Ducked : zLimits_Norm;
			Vector orig = pPlayer->GetAbsOrigin();
			orig += zLimits[0];

			const float minOpaqueDistSquared = cam_ots_translucencythreshold.GetFloat() * cam_ots_translucencythreshold.GetFloat();
			Vector camDelta = idealCamPos - orig;
			camDelta.z -= min( zLimits[1], max(0,camDelta.z) );
			float distSqr = (camDelta).LengthSqr();
			flPlayerTranslucency = 1.0f - ( minOpaqueDistSquared ? min( 1, distSqr / minOpaqueDistSquared ) : 1.0f );
		}
		else
		{
			Vector cam_ofs;

			::input->CAM_GetCameraOffset( cam_ofs );

			camAngles[ PITCH ] = cam_ofs[ PITCH ];
			camAngles[ YAW ] = cam_ofs[ YAW ];
			camAngles[ ROLL ] = 0;

			Vector camForward, camRight, camUp;
			AngleVectors( camAngles, &camForward, &camRight, &camUp );

			VectorMA( pSetup->origin, -cam_ofs[ ROLL ], camForward, pSetup->origin );

			// Override angles from third person camera
			VectorCopy( camAngles, pSetup->angles );
		}
	}
	else if (::input->CAM_IsOrthographic())
	{
		pSetup->m_bOrtho = true;
		float w, h;
		::input->CAM_OrthographicSize( w, h );
		w *= 0.5f;
		h *= 0.5f;
		pSetup->m_OrthoLeft   = -w;
		pSetup->m_OrthoTop    = -h;
		pSetup->m_OrthoRight  = w;
		pSetup->m_OrthoBottom = h;
	}

	// translucency will not work flawlessly on player models that use one of the eyeshaders
	// since those shaders do not support alpha blending by default
	bool bWasTransulcent = pPlayer->GetRenderMode() != kRenderNormal || 
		( pPlayer->GetActiveWeapon() && pPlayer->GetActiveWeapon()->GetRenderMode() != kRenderNormal );
	bool bShouldBeTranslucent = !!flPlayerTranslucency;
	if ( bWasTransulcent != bShouldBeTranslucent || bShouldBeTranslucent )
	{
		if ( bShouldBeTranslucent )
		{
			unsigned char alpha = ( 1.0f - flPlayerTranslucency ) * 255;
			pPlayer->SetRenderMode( kRenderTransTexture, true );
			pPlayer->SetRenderColorA( alpha );
			if ( pPlayer->GetActiveWeapon() )
			{
				pPlayer->GetActiveWeapon()->SetRenderMode( kRenderTransTexture, true );
				pPlayer->GetActiveWeapon()->SetRenderColorA( alpha );
			}
		}
		else	// not really required because this will be reset due to networking anyway
				// disabling networking for those may cause other issues though
		{
			pPlayer->SetRenderMode( kRenderNormal, true );
			pPlayer->SetRenderColorA( 255 );
			if ( pPlayer->GetActiveWeapon() )
			{
				pPlayer->GetActiveWeapon()->SetRenderMode( kRenderNormal, true );
				pPlayer->GetActiveWeapon()->SetRenderColorA( 255 );
			}
		}
	}
}

client.dll: c_baseplayer.h

Only allow this mode when we're alive and spawned; declare this function as public:

virtual bool			AllowOvertheShoulderView();

template SDK:

To enable stencil shadows for the player again, find virtual ShadowType_t ShadowCastType() and change the return value to SHADOWS_RENDER_TO_TEXTURE_DYNAMIC.

client.dll: c_baseplayer.cpp

Implement the function above:

bool C_BasePlayer::AllowOvertheShoulderView()
{
	if ( !IsAlive() )
		return false;
	if ( GetTeamNumber() == TEAM_SPECTATOR )
		return false;
	return ::input->CAM_IsThirdPerson();
}

template SDK:

To fix the flashlight in the template mod, find this function:

void C_BasePlayer::UpdateFlashlight()

and replace its content with:

{
	// 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 vec_origin = EyePosition();
		QAngle ang_FlashlightAngle = EyeAngles();
		int dist = FLASHLIGHT_DISTANCE;

		if ( GetActiveWeapon() && ::input->CAM_IsThirdPerson() )
		{
			C_BaseCombatWeapon *pWeap = GetActiveWeapon();
			int iAttachment = pWeap->LookupAttachment( "muzzle_flash" );
			if ( iAttachment > 0 )
				pWeap->GetAttachment( iAttachment, vec_origin, ang_FlashlightAngle );
			else
			{
				Vector aimFwd;
				AngleVectors( ang_FlashlightAngle, &aimFwd );
				vec_origin += aimFwd * (VEC_HULL_MAX).Length2D();
			}
			dist = 0;
		}

		Vector vecForward, vecRight, vecUp;
		AngleVectors( ang_FlashlightAngle, &vecForward, &vecRight, &vecUp );

		// Update the light with the new position and direction.		
		m_pFlashlight->UpdateLight( vec_origin, vecForward, vecRight, vecUp, dist );
	}
	else if (m_pFlashlight)
	{
		// Turned off the flashlight; delete it.
		delete m_pFlashlight;
		m_pFlashlight = NULL;
	}
}

shared: hl2mp_playeranimstate.h

HL2:MP SDK:

Declare this variable as private in CHL2MPPlayerAnimState:

	QAngle	m_angAiming;

shared: hl2mp_playeranimstate.cpp

HL2:MP SDK:

Search for the function named:

void CHL2MPPlayerAnimState::InitHL2MPAnimState( CHL2MP_Player *pPlayer )

Add this line to the end:

	m_angAiming.Init();

Search for the function:

void CHL2MPPlayerAnimState::ComputePoseParam_AimPitch( CStudioHdr *pStudioHdr )

Replace this line:

	GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimPitch, flAimPitch );

with these:

	m_angAiming.x = Approach( flAimPitch, m_angAiming.x, gpGlobals->frametime * 110.0f );
	GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimPitch, m_angAiming.x );

Go to:

void CHL2MPPlayerAnimState::ComputePoseParam_AimYaw( CStudioHdr *pStudioHdr )

Replace this line:

	GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimYaw, flAimYaw );

with these:

	m_angAiming.y = Approach( flAimYaw, m_angAiming.y, gpGlobals->frametime * 110.0f );
	GetBasePlayer()->SetPoseParameter( pStudioHdr, m_PoseParameterData.m_iAimYaw, m_angAiming.y );

Find this function:

void CHL2MPPlayerAnimState::Update( float eyeYaw, float eyePitch )

Replace this block at the end:

#ifdef CLIENT_DLL 
	if ( C_BasePlayer::ShouldDrawLocalPlayer() )
	{
		m_pHL2MPPlayer->SetPlaybackRate( 1.0f );
	}
#endif

With this snippet:

#ifdef CLIENT_DLL 
	//if ( !m_pPlayer->IsLocalPlayer() || C_BasePlayer::ShouldDrawLocalPlayer() )
	{
		float flSeqSpeed = m_pPlayer->GetSequenceGroundSpeed( m_pPlayer->GetSequence() );

		Vector vecVelocity;
		GetOuterAbsVelocity( vecVelocity );
		float flSpeed = vecVelocity.Length2DSqr();
		bool bMoving_OnGround = flSpeed > 0.01f && m_pPlayer->GetGroundEntity();

		flSpeed = bMoving_OnGround ? clamp( (vecVelocity.Length2DSqr() / (flSeqSpeed*flSeqSpeed)), 0.2f, 2.0f ) : 1.0f;
		m_pHL2MPPlayer->SetPlaybackRate( flSpeed );
	}
#endif

Search for the function named:

bool CHL2MPPlayerAnimState::HandleDucking( Activity &idealActivity )

Replace this line:

if ( m_pHL2MPPlayer->GetFlags() & FL_DUCKING )

with these:

	if ( m_pHL2MPPlayer->GetFlags() & FL_DUCKING
#ifdef CLIENT_DLL
		|| ( m_pHL2MPPlayer->IsLocalPlayer() && m_pHL2MPPlayer->m_Local.m_bDucking )
#endif
		)

Look for this function:

void CHL2MPPlayerAnimState::ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr )

After this line:

QAngle	angles = GetBasePlayer()->GetLocalAngles();

Add this snippet:

#ifdef CLIENT_DLL
	if ( !m_pPlayer->IsLocalPlayer() )
		angles = GetBasePlayer()->EyeAngles();
#endif

client.dll: fx_tracer.cpp

HL2:MP SDK:

Add this include:

#include "iinput.h"

Find this function:

Vector GetTracerOrigin( const CEffectData &data )

Replace the line:

if ( pWpn && pWpn->IsCarriedByLocalPlayer() )

with this one:

if ( pWpn && pWpn->IsCarriedByLocalPlayer() && !::input->CAM_IsThirdPerson() )

client.dll: c_hl2mp_player.cpp

HL2:MP SDK:

To fix the flashlight in a HL2:MP mod, find the function named:

void C_HL2MP_Player::UpdateFlashlight()

Replace its content with:

{
	// The dim light is the flashlight.
	if ( IsEffectActive( EF_DIMLIGHT ) )
	{
		if (!m_pHL2MPFlashLightEffect)
		{
			// Turned on the headlight; create it.
			m_pHL2MPFlashLightEffect = new CHL2MPFlashlightEffect(index);

			if (!m_pHL2MPFlashLightEffect)
				return;

			m_pHL2MPFlashLightEffect->TurnOn();
		}

		Vector vecForward, vecRight, vecUp;
		Vector position = EyePosition();

		if ( ::input->CAM_IsThirdPerson() )
		{
			if ( GetActiveWeapon() )
			{
				C_BaseCombatWeapon *pWeap = GetActiveWeapon();
				int iAttachment = pWeap->LookupAttachment( "muzzle" );
				if ( iAttachment > 0 )
				{
					QAngle ang;
					pWeap->GetAttachment( iAttachment, position, ang );
					AngleVectors( ang, &vecForward, &vecRight, &vecUp );
					position += vecForward * 6.0f;
				}
				else
				{
					EyeVectors( &vecForward, &vecRight, &vecUp );
					position += vecForward * (VEC_HULL_MAX).Length2D();
				}
			}
		}
		else
			EyeVectors( &vecForward, &vecRight, &vecUp );


		// Update the light with the new position and direction.		
		m_pHL2MPFlashLightEffect->UpdateLight( position, vecForward, vecRight, vecUp, FLASHLIGHT_DISTANCE );
	}
	else if (m_pHL2MPFlashLightEffect)
	{
		// Turned off the flashlight; delete it.
		delete m_pHL2MPFlashLightEffect;
		m_pHL2MPFlashLightEffect = NULL;
	}
}

Go to the function named:

void C_HL2MP_Player::AddEntity( void )

Replace these lines:

		else if( this != C_BasePlayer::GetLocalPlayer() || ::input->CAM_IsThirdPerson() )
		{
			int iAttachment = LookupAttachment( "anim_attachment_RH" );

			if ( iAttachment < 0 )
				return;

			Vector vecOrigin;
			//Tony; EyeAngles will return proper whether it's local player or not.
			QAngle eyeAngles = EyeAngles();

			GetAttachment( iAttachment, vecOrigin, eyeAngles );

			Vector vForward;
			AngleVectors( eyeAngles, &vForward );

			trace_t tr;
			UTIL_TraceLine( vecOrigin, vecOrigin + (vForward * 200), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );

With this snippet:

		else if( this != C_BasePlayer::GetLocalPlayer() || ::input->CAM_IsThirdPerson() )
		{
			C_BaseCombatWeapon *pWeap = GetActiveWeapon();
			int iAttachment = pWeap ? pWeap->LookupAttachment( "muzzle" ) : LookupAttachment( "anim_attachment_RH" );

			if ( iAttachment < 0 )
				return;

			Vector vecOrigin;
			//Tony; EyeAngles will return proper whether it's local player or not.
			QAngle eyeAngles = EyeAngles();

			if ( pWeap )
			{
				if ( iAttachment < 1 )
					vecOrigin = EyePosition() - Vector( 0, 0, 32 );
				else
					pWeap->GetAttachment( iAttachment, vecOrigin, eyeAngles );
			}
			else
				GetAttachment( iAttachment, vecOrigin, eyeAngles );

			Vector vForward;
			AngleVectors( eyeAngles, &vForward );

			trace_t tr;
			UTIL_TraceLine( vecOrigin, vecOrigin + (vForward * 200), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );

client.dll: flashlighteffect.cpp

HL2:MP SDK:

Add this include:

#include "iinput.h"

Find the function named:

void CFlashlightEffect::UpdateLightNew(const Vector &vecPos, const Vector &vecForward, const Vector &vecRight, const Vector &vecUp )

Now search for the line:

float flDist = (pmDirectionTrace.endpos - vOrigin).Length();

Replace the following block with this:

	float flDist = (pmDirectionTrace.endpos - vOrigin).Length();
	float flFov = r_flashlightfov.GetFloat();
	if ( flDist < flDistCutoff )
	{
		// We have an intersection with our cutoff range
		// Determine how far to pull back, then trace to see if we are clear
		float flPullBackDist = bPlayerOnLadder ? r_flashlightladderdist.GetFloat() : flDistCutoff - flDist;	// Fixed pull-back distance if on ladder
		float flDistModTmp = Lerp( flDistDrag, m_flDistMod, flPullBackDist );
		
		if ( !bPlayerOnLadder )
		{
			trace_t pmBackTrace;
			UTIL_TraceHull( vOrigin, vOrigin - vDir*(flPullBackDist-flEpsilon), Vector( -4, -4, -4 ), Vector( 4, 4, 4 ), iMask, &traceFilter, &pmBackTrace );
			if( pmBackTrace.DidHit() )
			{
				// We have an intersection behind us as well, so limit our m_flDistMod
				float flMaxDist = (pmBackTrace.endpos - vOrigin).Length() - flEpsilon;
				if( flDistModTmp > flMaxDist )
					flDistModTmp = flMaxDist;
			}
		}
		if ( ::input->CAM_IsThirdPerson() )
		{
			flFov += min( 100, (abs( flDistModTmp ) * 4) );
			m_flDistMod = Lerp( flDistDrag, m_flDistMod, 0.0f );
		}
		else
			m_flDistMod = flDistModTmp;
	}
	else
	{
		m_flDistMod = Lerp( flDistDrag, m_flDistMod, 0.0f );
	}
	vOrigin = vOrigin - vDir * m_flDistMod;

Replace this part:

	if ( bFlicker == false )
	{
		state.m_fLinearAtten = r_flashlightlinear.GetFloat();
		state.m_fHorizontalFOVDegrees = r_flashlightfov.GetFloat();
		state.m_fVerticalFOVDegrees = r_flashlightfov.GetFloat();
	}

with these lines:

	if ( bFlicker == false )
	{
		state.m_fLinearAtten = r_flashlightlinear.GetFloat();
		state.m_fHorizontalFOVDegrees = flFov;
		state.m_fVerticalFOVDegrees = flFov;
	}

client.dll: c_baseanimating.cpp

template SDK:

Muzzleflash particles are broken for thirdperson in the template mod; to disable them find this function:

void C_BaseAnimating::FireObsoleteEvent( const Vector& origin, const QAngle& angles, int event, const char *options )

Right after this snippet:

	case CL_EVENT_MUZZLEFLASH0:
	case CL_EVENT_MUZZLEFLASH1:
	case CL_EVENT_MUZZLEFLASH2:
	case CL_EVENT_MUZZLEFLASH3:
	case CL_EVENT_NPC_MUZZLEFLASH0:
	case CL_EVENT_NPC_MUZZLEFLASH1:
	case CL_EVENT_NPC_MUZZLEFLASH2:
	case CL_EVENT_NPC_MUZZLEFLASH3:
		{

add these lines:

			C_BaseEntity *follow = GetFollowedEntity();
			if ( follow && follow->IsPlayer() && ::input->CAM_IsThirdPerson() )
				break;

shared: weapon_physcannon.cpp

HL2:MP SDK:

Go to the function named:

void CallbackPhyscannonImpact( const CEffectData &data )

Search for these lines:

		if ( pPlayer )
		{
			pEnt = pPlayer->GetViewModel();
		}

		// Format attachment for first-person view!
		::FormatViewModelAttachment( vecAttachment, true );

Replace them with this snippet:

		if ( pPlayer && !::input->CAM_IsThirdPerson() )
		{
			pEnt = pPlayer->GetViewModel();

			// Format attachment for first-person view!
			::FormatViewModelAttachment( vecAttachment, true );
		}

client.dll: c_sprite.cpp

HL2:MP SDK:

Add this include:

#include "iinput.h"

Find this function:

int C_SpriteRenderer::DrawSprite(...)

Find the block that begins with if ( attachedto ) and replace it with this snippet:

	if ( attachedto )
	{
		C_BaseEntity *ent = attachedto->GetBaseEntity();
		if ( ent )
		{
			if ( ent->GetBaseAnimating() && ent->GetBaseAnimating()->IsViewModel() && ::input->CAM_IsThirdPerson() )
			{
				C_BaseViewModel *pVm = (C_BaseViewModel*)ent;
				C_BasePlayer *pOwner = ( pVm->GetOwner() && pVm->GetOwner()->IsPlayer() ) ? (C_BasePlayer*)pVm->GetOwner() : NULL;
				if ( pOwner && pOwner->GetActiveWeapon() )
					return 0; //worldmodels don't have the same attachments, so just get out (crossbow)
			}
			// don't draw viewmodel effects in reflections
			if ( CurrentViewID() == VIEW_REFLECTION )
			{
				int group = ent->GetRenderGroup();
				if ( group == RENDER_GROUP_VIEW_MODEL_TRANSLUCENT || group == RENDER_GROUP_VIEW_MODEL_OPAQUE )
					return 0;
			}
			QAngle temp;
			ent->GetAttachment( attachmentindex, effect_origin, temp );
		}
	}

OPTIONAL: Adding free aim

The following code can be added to allow for toggleable free aiming. The mouse input will move the crosshair on the screen instead of rotating the view; moving the crosshair outside of a certain circular area will turn the view instead. The cvar cam_ots_freeaim_interval_enable will enable the use of an interval for view turning.

client.dll: iinput.h

Add these methods to the interface:

	virtual bool		CAM_IsFreeAiming() = 0;
	virtual Vector2D	CAM_GetFreeAimCursor() = 0;
	virtual void		CAM_UpdateAngleByFreeAiming( bool bUser = false ) = 0;
	virtual void		CAM_UpdateAngle180() = 0;

client.dll: input.h

Declare them in the implementation as public:

	virtual		bool		CAM_IsFreeAiming();
	virtual		Vector2D	CAM_GetFreeAimCursor();
	virtual		void		CAM_UpdateAngleByFreeAiming( bool bUser );
	virtual		void		CAM_UpdateAngle180();

Declare these members in a private block:

	QAngle		m_angViewAngle_Delta;
	Vector2D	m_vecFreeAimPos;
	Vector2D	m_vecFreeAimPos_Delta;
	void		TryCursorMove( QAngle& viewangles, CUserCmd *cmd, float x, float y );

client.dll: in_main.cpp

Add these lines near the top of the file:

static ConVar cam_ots_freeaim( "cam_ots_freeaim_enable", "1", FCVAR_ARCHIVE );
static ConVar cam_ots_freeaim_use_interval( "cam_ots_freeaim_interval_enable", "0", FCVAR_ARCHIVE ); // use an interval for view turning
static ConVar cam_ots_freeaim_movethreshold( "cam_ots_freeaim_move_threshold", "0.7", FCVAR_ARCHIVE );
static ConVar cam_ots_freeaim_movemax( "cam_ots_freeaim_move_max", "0.85", FCVAR_ARCHIVE );

static ConVar cam_ots_freeaim_speedturn( "cam_ots_freeaim_speed_turn", "1", FCVAR_ARCHIVE );
static ConVar cam_ots_freeaim_speed_evenyaw( "cam_ots_freeaim_speed_evenYawSpeed", "1", FCVAR_ARCHIVE );

static ConVar cam_ots_freeaim_autoturn_speed( "cam_ots_freeaim_autoturn_speed", "250", FCVAR_ARCHIVE );

extern void ScreenToWorld( int mousex, int mousey, float fov, const Vector& vecRenderOrigin, const QAngle& vecRenderAngles, Vector& vecPickingRay );

Add these ConCommands:

CON_COMMAND( cam_ots_TurnAuto, "" )
{
	::input->CAM_UpdateAngleByFreeAiming(true);
}
CON_COMMAND( cam_ots_Turn180, "" )
{
	::input->CAM_UpdateAngle180();
}

Find this function:

void CInput::Init_All (void)

Add these lines to the end:

	m_vecFreeAimPos.Init();
	m_vecFreeAimPos_Delta.Init();
	m_angViewAngle_Delta.Init();

Define the methods above:

void CInput::CAM_UpdateAngle180()
{
	m_angViewAngle_Delta.y = 180.0f;
	if ( CAM_IsFreeAiming() )
		m_angViewAngle_Delta.y *= -Sign( m_vecFreeAimPos.x );
}
bool CInput::CAM_IsFreeAiming()
{
	C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
	return cam_ots_freeaim.GetBool() && pl && pl->AllowOvertheShoulderView();
}
Vector2D CInput::CAM_GetFreeAimCursor()
{
	return m_vecFreeAimPos;
}

// Allows code to manipulate the view angles
void CInput::CAM_UpdateAngleByFreeAiming( bool bUser )
{
	if ( prediction->InPrediction() && !prediction->IsFirstTimePredicted() )
		return;

	if ( !CAM_IsFreeAiming() )
		return;

	C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
	if ( !pl )
		return;

	Vector vecForward, vecForwardCam;
	int screen_x, screen_y;
	engine->GetScreenSize( screen_x, screen_y );
	screen_x *= m_vecFreeAimPos.x * 0.5f + 0.5f;
	screen_y *= m_vecFreeAimPos.y * 0.5f + 0.5f;
	ScreenToWorld( screen_x, screen_y, pl->GetFOV(), MainViewOrigin(), MainViewAngles(), vecForward );

	trace_t tr;
	UTIL_TraceLine( MainViewOrigin(), MainViewOrigin() + vecForward * MAX_TRACE_LENGTH, MASK_SHOT, pl, COLLISION_GROUP_NONE, &tr );
	Vector vecWorldTarget = tr.endpos;
	Vector vecDir_PlayerViewTarget = vecWorldTarget - pl->EyePosition();
	
	AngleVectors( m_angViewAngle, &vecForwardCam, 0, 0 );

	Vector vecDir_PlayerCamPlane = MainViewOrigin() - pl->EyePosition();
	float flPlaneFwdComponent = DotProduct( vecDir_PlayerCamPlane, vecForwardCam );
	vecDir_PlayerCamPlane -= flPlaneFwdComponent * vecForwardCam;

	const float lengthCamToImaginaryTarget = FastSqrt( vecDir_PlayerViewTarget.LengthSqr() - vecDir_PlayerCamPlane.LengthSqr() );
	Vector vecDir_PlayerTmpViewTarget = vecDir_PlayerCamPlane + vecForwardCam * lengthCamToImaginaryTarget;

	Vector vecDir_PlayerCamPlaneDst;
	QAngle ang1, ang2, angDelta;
	VectorAngles( vecDir_PlayerTmpViewTarget, ang1 );
	VectorAngles( vecDir_PlayerViewTarget, ang2 );
	RotationDelta( ang1, ang2, &angDelta );
	VectorRotate( vecDir_PlayerCamPlane, angDelta, vecDir_PlayerCamPlaneDst );

	Vector vecDstDirImaginaryTarget_NoOffset = vecWorldTarget - vecDir_PlayerCamPlaneDst;

	vecDstDirImaginaryTarget_NoOffset -= pl->EyePosition();

	if ( !bUser )
	{
		m_angViewAngle_Delta.Init();
		m_vecFreeAimPos_Delta.Init();

		QAngle tmp;
		VectorAngles( vecDstDirImaginaryTarget_NoOffset, tmp );
		if ( tmp.IsValid() )
		{
			m_angViewAngle = tmp;
			if ( m_angViewAngle.x > 180.0f )
				m_angViewAngle.x -= 360.0f;
			else if ( m_angViewAngle.x < -180.0f )
				m_angViewAngle.x += 360.0f;
			m_vecFreeAimPos.Init();
		}
	}
	else
	{
		QAngle angTarget;
		VectorAngles( vecDstDirImaginaryTarget_NoOffset, angTarget );
		for ( int i = 0; i < 2; i++ )
			m_angViewAngle_Delta[i] = AngleDiff( angTarget[i], m_angViewAngle[i] );
		m_angViewAngle_Delta.z = 0;

		if ( m_angViewAngle_Delta.y > 180.0f )
			m_angViewAngle_Delta.y = 180.0f;
		else if ( m_angViewAngle_Delta.y < -180.0f )
			m_angViewAngle_Delta.y = -180.0f;

		if ( !m_angViewAngle_Delta.IsValid() )
			m_angViewAngle_Delta.Init();
		else
			m_vecFreeAimPos_Delta = -m_vecFreeAimPos;
	}
}

// Defines crosshair position and rotates the view if appropriate
void CInput::TryCursorMove( QAngle& viewangles, CUserCmd *cmd, float x, float y )
{
	static ConVarRef m_yaw( "m_yaw" );
	static ConVarRef m_pitch( "m_pitch" );

	C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
	float flFOV = ( pl ? pl->GetFOV() : 1.0f );
	float flScaleNormalizedRange_Yaw = m_yaw.GetFloat() / flFOV;
	float flScaleNormalizedRange_Pitch = m_pitch.GetFloat() / flFOV;
	float flScale_Pitch = 1;
	if ( cam_ots_freeaim_speed_evenyaw.GetInt() )
		flScale_Pitch = engine->GetScreenAspectRatio();

	m_vecFreeAimPos += Vector2D( x * flScaleNormalizedRange_Yaw, y * flScale_Pitch * flScaleNormalizedRange_Pitch );
	float flLength = m_vecFreeAimPos.NormalizeInPlace();
	float flMoveVars[ 2 ] = { cam_ots_freeaim_movethreshold.GetFloat(), cam_ots_freeaim_movemax.GetFloat() };
	float flTurnSpeed;

	Vector2D moveDir = m_vecFreeAimPos;
	moveDir.NormalizeInPlace();

	if ( cam_ots_freeaim_use_interval.GetInt() )
	{
		flTurnSpeed = cam_ots_freeaim_speedturn.GetFloat() * max( 0, ( 
			(flLength - flMoveVars[0]) / (flMoveVars[1] - flMoveVars[0]) 
			) );
		viewangles += QAngle( moveDir.y * flTurnSpeed, moveDir.x * -flTurnSpeed, 0 );
	}
	else
	{
		flTurnSpeed = max( 0, ( flLength - flMoveVars[ 1 ] ) );
		viewangles += QAngle( moveDir.y * flTurnSpeed * flFOV, moveDir.x * -flTurnSpeed * flFOV, 0 );
	}

	if ( viewangles.x > 89.0f )
		viewangles.x = 89.0f;
	else if ( viewangles.x < -89.0f )
		viewangles.x = -89.0f;

	if ( viewangles.y > 180.0f )
		viewangles.y -= 360.0f;
	else if ( viewangles.y < -180.0f )
		viewangles.y += 360.0f;

	flLength = min( flMoveVars[1], flLength );
	m_vecFreeAimPos *= flLength;

	cmd->mousedx = (int)x;
	cmd->mousedy = (int)y;
}

Change our previously defined function named:

void CInput::CalcPlayerAngle( CUserCmd *cmd )

to look like this:

void CInput::CalcPlayerAngle( CUserCmd *cmd )
{
	C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
	if ( !pl || !pl->AllowOvertheShoulderView() )
	{
		engine->SetViewAngles( m_angViewAngle );
		m_angViewAngle_Delta.Init();
		return;
	}

	float deltaLen = m_angViewAngle_Delta.Length();
	if ( deltaLen )
	{
		QAngle angDelta_New = m_angViewAngle_Delta;
		for ( int i = 0; i < 2; i++ )
			angDelta_New[i] = angDelta_New[i] - Approach( 0, angDelta_New[i],
			gpGlobals->frametime * cam_ots_freeaim_autoturn_speed.GetFloat() * abs(m_angViewAngle_Delta[i]/deltaLen)
			);

		m_angViewAngle += angDelta_New;
		m_angViewAngle_Delta -= angDelta_New;
	}
	float deltaCursor = m_vecFreeAimPos_Delta.Length();
	if ( deltaCursor )
	{
		Vector2D vecAimPos_New = m_vecFreeAimPos_Delta;
		for ( int i = 0; i < 2; i++ )
			vecAimPos_New[i] = vecAimPos_New[i] - Approach( 0, vecAimPos_New[i],
			gpGlobals->frametime * cam_ots_freeaim_autoturn_speed.GetFloat() * 
			abs(m_vecFreeAimPos_Delta[i]/deltaCursor) * (engine->GetScreenAspectRatio() / pl->GetFOV())
			);

		m_vecFreeAimPos += vecAimPos_New;
		m_vecFreeAimPos_Delta -= vecAimPos_New;
	}

	trace_t tr;
	const Vector eyePos = pl->EyePosition();
	Vector vecForward = MainViewForward();
	QAngle angCam = m_angViewAngle; //MainViewAngles();
	if ( CAM_IsFreeAiming() )
	{
		int screen_x, screen_y;
		engine->GetScreenSize( screen_x, screen_y );
		screen_x *= m_vecFreeAimPos.x * 0.5f + 0.5f;
		screen_y *= m_vecFreeAimPos.y * 0.5f + 0.5f;
		ScreenToWorld( screen_x, screen_y, pl->GetFOV(), MainViewOrigin(), angCam, vecForward );
	}
	UTIL_TraceLine( MainViewOrigin(), MainViewOrigin() + vecForward * MAX_TRACE_LENGTH, MASK_SHOT, pl, COLLISION_GROUP_NONE, &tr );

	// ensure that the player entity does not shoot towards the camera, get dist to plane where the player is on and add a constant
	float flMinForward = abs( DotProduct( MainViewForward(), eyePos - MainViewOrigin() ) ) + 32.0f;
	Vector vecTrace = tr.endpos - tr.startpos;
	float flLenOld = vecTrace.NormalizeInPlace();
	float flLen = max( flMinForward, flLenOld );
	vecTrace *= flLen;

	Vector vecFinalDir = MainViewOrigin() + vecTrace - eyePos; //eyePos;

	QAngle playerangles;
	VectorAngles( vecFinalDir, playerangles );
	engine->SetViewAngles( playerangles );

	playerangles.z = angCam.z = 0;
	playerangles.x = angCam.x = 0;
	Vector cFwd, cRight, pFwd, pRight;
	AngleVectors( angCam, &cFwd, &cRight, NULL );
	AngleVectors( playerangles, &pFwd, &pRight, NULL );

	float flMove[2] = { cmd->forwardmove, cmd->sidemove };
	cmd->forwardmove = DotProduct( cFwd, pFwd ) * flMove[ 0 ] + DotProduct( cRight, pFwd ) * flMove[ 1 ];
	cmd->sidemove = DotProduct( cRight, pRight ) * flMove[ 1 ] + DotProduct( cFwd, pRight ) * flMove[ 0 ];
}

Find the function named:

void CInput::SetCamViewangles( QAngle const &view )

Add this line to the end:

m_vecFreeAimPos.Init();

client.dll: in_mouse.cpp

Go to

void CInput::MouseMove( CUserCmd *cmd )

Replace this line:

ApplyMouse( m_angViewAngle, cmd, mouse_x, mouse_y );

with these:

		if ( CAM_IsFreeAiming() )
			TryCursorMove( m_angViewAngle, cmd, mouse_x, mouse_y );
		else
			ApplyMouse( m_angViewAngle, cmd, mouse_x, mouse_y );

client.dll: hud_crosshair.cpp

This code actually moves the crosshair by the mouse input we evaluated in TryCursorMove().

Add this include:

#include "iinput.h"

Find the function named:

void CHudCrosshair::Paint( void )

Right after this snippet:

	float x, y;
	x = ScreenWidth()/2;
	y = ScreenHeight()/2;

add these lines:

	if ( ::input->CAM_IsFreeAiming() )
	{
		Vector2D vec_AimPos = ::input->CAM_GetFreeAimCursor();
		x *= vec_AimPos.x + 1.0f;
		y *= vec_AimPos.y + 1.0f;
	}

client.dll: hud_quickinfo.cpp

HL2:MP SDK:

The following modifications ensure that the quickinfo icons move along with the crosshair.

Add this include:

#include "iinput.h"

Find this function:

void CHUDQuickInfo::Paint()

Search for these lines:

	int		xCenter	= ( ScreenWidth() - m_icon_c->Width() ) / 2;
	int		yCenter = ( ScreenHeight() - m_icon_c->Height() ) / 2;
	float	scalar  = 138.0f/255.0f;

Add this snippet below:

	if ( ::input->CAM_IsFreeAiming() )
	{
		Vector2D vec_AimPos = ::input->CAM_GetFreeAimCursor();
		xCenter = (ScreenWidth() / 2) * ( vec_AimPos.x + 1.0f );
		xCenter -= m_icon_lb->Width() * 0.5f;
		yCenter = (ScreenHeight() / 2) * ( vec_AimPos.y + 1.0f );
		yCenter -= m_icon_lb->Height() * 0.5f;
	}

Scroll down to these lines:

	xCenter	= ScreenWidth() / 2;
	yCenter = ( ScreenHeight() - m_icon_lb->Height() ) / 2;

Add these below:

	if ( ::input->CAM_IsFreeAiming() )
	{
		Vector2D vec_AimPos = ::input->CAM_GetFreeAimCursor();
		xCenter *= ( vec_AimPos.x + 1.0f );
		yCenter = (ScreenHeight() / 2) * ( vec_AimPos.y + 1.0f );
		yCenter -= m_icon_lb->Height() * 0.5f;
	}

shared: weapon_crossbow.cpp

HL2:MP SDK:

Reorient the aiming angle while using free aim and zooming; find the function named:

void CWeaponCrossbow::ToggleZoom( void )

Before #endif add this:

#else
	::input->CAM_UpdateAngleByFreeAiming();

See also