Over the Shoulder View: Difference between revisions
m (Fixed little error) |
Biohazard 90 (talk | contribs) No edit summary |
||
Line 1: | Line 1: | ||
This tutorial will teach you how to create an over-the-shoulder camera view with collision detection. It has been created for the template SDK on the source 2007 engine branch, therefore additional modifications might be necessary to allow functionality with a different SDK. | |||
== Seperating 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. The new viewangle will also be used to calculate the movement vectors, that's why it has to be networked to the server as well. | |||
== | === client.dll: iinput.h === | ||
Expand the input interface with these functions: | |||
<source lang=cpp> virtual void const GetCamViewangles( QAngle &view ) = 0; | |||
virtual void SetCamViewangles( QAngle const &view ) = 0; // in case you need to update the viewangles from the server | |||
// you will require this alongside of, for example, a new user message</source> | |||
=== client.dll: input.h === | |||
Add these to the public area of the actual input implementation: | |||
<source lang=cpp> virtual void const GetCamViewangles( QAngle &view ){ view = m_angViewAngle; }; | |||
virtual void SetCamViewangles( QAngle const &view ){ m_angViewAngle = view; };</source> | |||
And these as private members: | |||
= | <source lang=cpp> QAngle m_angViewAngle; | ||
void CalcPlayerAngle( CUserCmd *cmd );</source> | |||
=== client.dll: in_main.cpp === | |||
Add this include: | |||
<source lang=cpp>#include "view.h"</source> | |||
Initialize the new QAngle in the constructor of CInput: | |||
< | <source lang=cpp>m_angViewAngle = vec3_angle;</source> | ||
Define the function that you have just declared: | |||
<source lang=cpp>void CInput::CalcPlayerAngle( CUserCmd *cmd ) | |||
{ | |||
C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer(); | |||
if ( !pl->AllowOvertheShoulderView() ) | |||
{ | |||
engine->SetViewAngles( m_angViewAngle ); | |||
cmd->viewangles_cam = 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 ); | |||
cmd->viewangles_cam = m_angViewAngle; | |||
}</source> | |||
{{Note|At this point you are actually not supposed to access the view data, it still works fine, though.}} | |||
Search for the functions | |||
<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> | |||
In '''both''' of them add the following line after the <code>if ( active )</code> right before <code>engine->GetViewAngles( viewangles );</code> | |||
<source lang=cpp>CalcPlayerAngle( cmd );</source> | |||
=== client.dll: in_mouse.cpp === | |||
Search for the function: | |||
<source lang=cpp>void CInput::MouseMove( CUserCmd *cmd )</source> | |||
Now ''comment'' or ''remove'' these lines: | |||
<source lang=cpp>QAngle viewangles; | |||
engine->GetViewAngles( viewangles ); | |||
engine->SetViewAngles( viewangles );</source> | |||
And change the call to <code>ApplyMouse(...)</code> to read this: | |||
<source lang=cpp>ApplyMouse( m_angViewAngle, cmd, mouse_x, mouse_y );</source> | |||
=== shared: usercmd.h === | |||
Search for all occurances of <code>viewangles</code> in this file; duplicate each and rename the var to <code>viewangles_cam</code>. You should end up adding these lines at their respective places: | |||
== | <source lang=cpp>viewangles_cam.Init(); | ||
viewangles_cam = src.viewangles_cam; | |||
CRC32_ProcessBuffer( &crc, &viewangles_cam, sizeof( viewangles_cam ) ); | |||
QAngle viewangles_cam;</source> | |||
=== shared: usercmd.cpp === | |||
To properly network the new QAngle through the command stream, you need to make sure that you write and read it in the exact same order. | |||
Look for this snippet: | |||
<source lang=cpp> if ( to->viewangles[ 2 ] != from->viewangles[ 2 ] ) | |||
{ | |||
buf->WriteOneBit( 1 ); | |||
buf->WriteFloat( to->viewangles[ 2 ] ); | |||
} | |||
else | |||
{ | |||
buf->WriteOneBit( 0 ); | |||
}</source> | |||
Add this below: | |||
<source lang=cpp> if ( to->viewangles_cam[ 0 ] != from->viewangles_cam[ 0 ] ) | |||
{ | |||
buf->WriteOneBit( 1 ); | |||
buf->WriteFloat( to->viewangles_cam[ 0 ] ); | |||
} | |||
else | |||
{ | |||
buf->WriteOneBit( 0 ); | |||
} | |||
if ( to->viewangles_cam[ 1 ] != from->viewangles_cam[ 1 ] ) | |||
{ | |||
buf->WriteOneBit( 1 ); | |||
buf->WriteFloat( to->viewangles_cam[ 1 ] ); | |||
} | |||
else | |||
{ | |||
buf->WriteOneBit( 0 ); | |||
} | |||
== | if ( to->viewangles_cam[ 2 ] != from->viewangles_cam[ 2 ] ) | ||
{ | |||
buf->WriteOneBit( 1 ); | |||
buf->WriteFloat( to->viewangles_cam[ 2 ] ); | |||
} | |||
else | |||
{ | |||
buf->WriteOneBit( 0 ); | |||
}</source> | |||
Afer this snippet: | |||
<source lang=cpp> if ( buf->ReadOneBit() ) | |||
{ | |||
move->viewangles[2] = buf->ReadFloat(); | |||
}</source> | |||
Add these lines: | |||
<source lang=cpp> if ( buf->ReadOneBit() ) | |||
{ | |||
move->viewangles_cam[0] = buf->ReadFloat(); | |||
} | |||
if ( buf->ReadOneBit() ) | |||
{ | |||
move->viewangles_cam[1] = buf->ReadFloat(); | |||
} | |||
if ( buf->ReadOneBit() ) | |||
{ | |||
move->viewangles_cam[2] = buf->ReadFloat(); | |||
}</source> | |||
=== client.dll: prediction.cpp === | |||
Find the line: | |||
<source lang=cpp>move->m_vecViewAngles = ucmd->viewangles;</source> | |||
and replace it with: | |||
<source lang=cpp>move->m_vecViewAngles = ucmd->viewangles_cam;</source> | |||
This allows us to move relative to the view, not the aiming direction. | |||
=== server.dll: player_command.cpp === | |||
Find the line: | |||
<source lang=cpp>move->m_vecViewAngles = ucmd->viewangles;</source> | |||
and replace it with: | |||
<source lang=cpp>move->m_vecViewAngles = ucmd->viewangles_cam;</source> | |||
The same as above, just for the server. | |||
== 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> | |||
=== client.dll: clientmode_shared.cpp === | |||
Define these cvars near the top: | |||
<source lang=cpp>static ConVar cam_ots_offset( "cam_ots_offset", "20 -75 20" ); | |||
static ConVar cam_ots_offsetlag( "cam_ots_offset_lag", "64.0" ); | |||
static ConVar cam_ots_originlag( "cam_ots_origin_lag", "38.0" ); | |||
static ConVar cam_ots_translucencythreshold( "cam_ots_translucencyThreshold", "32.0" );</source> | |||
Find the function named: | |||
<source lang=cpp>void ClientModeShared::OverrideView( CViewSetup *pSetup )</source> | |||
Replace its content with this: | |||
<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_FORWRAD, | |||
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(); | |||
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 ] ); | |||
} | |||
} | |||
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; | |||
// Approach eyeorigin | |||
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 ); | |||
AngleVectors( viewAng, &directions[CAM_FORWRAD], &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_FORWRAD, 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; | |||
const float minOpaqueDistSquared = cam_ots_translucencythreshold.GetFloat() * cam_ots_translucencythreshold.GetFloat(); | |||
float distSqr = (idealCamPos - eyepos_lag).LengthSqr(); | |||
flPlayerTranslucency = 1.0f - min( 1, distSqr / minOpaqueDistSquared ); | |||
} | |||
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 ) | |||
{ | |||
if ( bShouldBeTranslucent ) | |||
{ | |||
unsigned char alpha = ( 1.0f - flPlayerTranslucency ) * 255; | |||
pPlayer->SetRenderMode( kRenderTransTexture, true ); | |||
pPlayer->SetRenderColorA( alpha ); | |||
if ( pPlayer->GetActiveWeapon() ) | |||
{ | |||
pPlayer->GetActiveWeapon()->SetRenderMode( kRenderTransTexture ); | |||
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 ); | |||
pPlayer->GetActiveWeapon()->SetRenderColorA( 255 ); | |||
} | |||
} | |||
} | |||
}</source> | |||
=== 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> | |||
=== 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 true; | |||
}</source> | |||
=== client.dll c_baseanimating.cpp === | |||
Muzzleflash particles are broken for thirdperson; 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> | |||
== See also == | == See also == |
Revision as of 14:54, 29 January 2011
This tutorial will teach you how to create an over-the-shoulder camera view with collision detection. It has been created for the template SDK on the source 2007 engine branch, therefore additional modifications might be necessary to allow functionality with a different SDK.
Seperating 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. The new viewangle will also be used to calculate the movement vectors, that's why it has to be networked to the server as well.
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; // in case you need to update the viewangles from the server
// you will require this alongside of, for example, a new user message
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 ){ m_angViewAngle = view; };
And these as private members:
QAngle m_angViewAngle;
void CalcPlayerAngle( CUserCmd *cmd );
client.dll: in_main.cpp
Add this include:
#include "view.h"
Initialize the new QAngle in the constructor of CInput:
m_angViewAngle = vec3_angle;
Define the function that you have just declared:
void CInput::CalcPlayerAngle( CUserCmd *cmd )
{
C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
if ( !pl->AllowOvertheShoulderView() )
{
engine->SetViewAngles( m_angViewAngle );
cmd->viewangles_cam = 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 );
cmd->viewangles_cam = m_angViewAngle;
}

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 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 );
Search for all occurances of viewangles
in this file; duplicate each and rename the var to viewangles_cam
. You should end up adding these lines at their respective places:
viewangles_cam.Init();
viewangles_cam = src.viewangles_cam;
CRC32_ProcessBuffer( &crc, &viewangles_cam, sizeof( viewangles_cam ) );
QAngle viewangles_cam;
To properly network the new QAngle through the command stream, you need to make sure that you write and read it in the exact same order.
Look for this snippet:
if ( to->viewangles[ 2 ] != from->viewangles[ 2 ] )
{
buf->WriteOneBit( 1 );
buf->WriteFloat( to->viewangles[ 2 ] );
}
else
{
buf->WriteOneBit( 0 );
}
Add this below:
if ( to->viewangles_cam[ 0 ] != from->viewangles_cam[ 0 ] )
{
buf->WriteOneBit( 1 );
buf->WriteFloat( to->viewangles_cam[ 0 ] );
}
else
{
buf->WriteOneBit( 0 );
}
if ( to->viewangles_cam[ 1 ] != from->viewangles_cam[ 1 ] )
{
buf->WriteOneBit( 1 );
buf->WriteFloat( to->viewangles_cam[ 1 ] );
}
else
{
buf->WriteOneBit( 0 );
}
if ( to->viewangles_cam[ 2 ] != from->viewangles_cam[ 2 ] )
{
buf->WriteOneBit( 1 );
buf->WriteFloat( to->viewangles_cam[ 2 ] );
}
else
{
buf->WriteOneBit( 0 );
}
Afer this snippet:
if ( buf->ReadOneBit() )
{
move->viewangles[2] = buf->ReadFloat();
}
Add these lines:
if ( buf->ReadOneBit() )
{
move->viewangles_cam[0] = buf->ReadFloat();
}
if ( buf->ReadOneBit() )
{
move->viewangles_cam[1] = buf->ReadFloat();
}
if ( buf->ReadOneBit() )
{
move->viewangles_cam[2] = buf->ReadFloat();
}
client.dll: prediction.cpp
Find the line:
move->m_vecViewAngles = ucmd->viewangles;
and replace it with:
move->m_vecViewAngles = ucmd->viewangles_cam;
This allows us to move relative to the view, not the aiming direction.
server.dll: player_command.cpp
Find the line:
move->m_vecViewAngles = ucmd->viewangles;
and replace it with:
move->m_vecViewAngles = ucmd->viewangles_cam;
The same as above, just for the server.
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;
Define these cvars near the top:
static ConVar cam_ots_offset( "cam_ots_offset", "20 -75 20" );
static ConVar cam_ots_offsetlag( "cam_ots_offset_lag", "64.0" );
static ConVar cam_ots_originlag( "cam_ots_origin_lag", "38.0" );
static ConVar cam_ots_translucencythreshold( "cam_ots_translucencyThreshold", "32.0" );
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_FORWRAD,
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();
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 ] );
}
}
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;
// Approach eyeorigin
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 );
AngleVectors( viewAng, &directions[CAM_FORWRAD], &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_FORWRAD, 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;
const float minOpaqueDistSquared = cam_ots_translucencythreshold.GetFloat() * cam_ots_translucencythreshold.GetFloat();
float distSqr = (idealCamPos - eyepos_lag).LengthSqr();
flPlayerTranslucency = 1.0f - min( 1, distSqr / minOpaqueDistSquared );
}
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 )
{
if ( bShouldBeTranslucent )
{
unsigned char alpha = ( 1.0f - flPlayerTranslucency ) * 255;
pPlayer->SetRenderMode( kRenderTransTexture, true );
pPlayer->SetRenderColorA( alpha );
if ( pPlayer->GetActiveWeapon() )
{
pPlayer->GetActiveWeapon()->SetRenderMode( kRenderTransTexture );
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 );
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();
client.dll: c_baseplayer.cpp
Implement the function above:
bool C_BasePlayer::AllowOvertheShoulderView()
{
if ( !IsAlive() )
return false;
if ( GetTeamNumber() == TEAM_SPECTATOR )
return false;
return true;
}
client.dll c_baseanimating.cpp
Muzzleflash particles are broken for thirdperson; 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;