Over the Shoulder View

From Valve Developer Community
Revision as of 09:27, 30 January 2011 by Biohazard 90 (talk | contribs) (moved net_fakelag hack back for hl2mp)
Jump to navigation Jump to search

Template:Otherlang2

This tutorial will teach you 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: 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 || !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;
}
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 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: usercmd.h

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;

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:

	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.

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 );

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();

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;
	if ( !::input->CAM_IsThirdPerson() )
		return false;
	return true;
}

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;
	}
}

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()

and 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 )

And 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

Find the function named void CFlashlightEffect::UpdateLightNew(const Vector &vecPos, const Vector &vecForward, const Vector &vecRight, const Vector &vecUp ) and 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 ) and 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 );
		}

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;

client.dll: input.h

Declare them in the implementation as public:

	virtual		bool		CAM_IsFreeAiming();
	virtual		Vector2D	CAM_GetFreeAimCursor();

Declare these members in a private block:

	Vector2D	m_vecFreeAimPos;
	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_speedmouse( "cam_ots_freeaim_speed_mouse", "0.05", FCVAR_ARCHIVE );
static ConVar cam_ots_freeaim_speedmouse_edge( "cam_ots_freeaim_speed_mouse_edgeFactor", "100", FCVAR_ARCHIVE );
static ConVar cam_ots_freeaim_speedturn( "cam_ots_freeaim_speed_turn", "0.8", FCVAR_ARCHIVE );

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

Initialize m_vecFreeAimPos in the CInput constructor:

m_vecFreeAimPos.Init();

Define the methods above:

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;
}
void CInput::TryCursorMove( QAngle& viewangles, CUserCmd *cmd, float x, float y )
{
	C_BasePlayer *pl = C_BasePlayer::GetLocalPlayer();
	float flScaleNormalizedRange = cam_ots_freeaim_speedmouse.GetFloat() / ( pl ? pl->GetFOV() : 1.0f );

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

	if ( cam_ots_freeaim_use_interval.GetInt() )
	{
		flTurnSpeed = cam_ots_freeaim_speedturn.GetFloat() * max( 0, ( 
			(flLength - flMoveVars[0]) / (flMoveVars[1] - flMoveVars[0]) 
			) );
	}
	else
	{
		flTurnSpeed = cam_ots_freeaim_speedturn.GetFloat() * max( 0, ( (flLength - flMoveVars[ 1 ]) * cam_ots_freeaim_speedmouse_edge.GetFloat() ) );
	}

	Vector2D moveDir = m_vecFreeAimPos;
	moveDir.NormalizeInPlace();
	viewangles += QAngle( moveDir.y * flTurnSpeed, moveDir.x * -flTurnSpeed, 0 );
	if ( viewangles.x > 89.0f )
		viewangles.x = 89.0f;
	else if ( viewangles.x < -89.0f )
		viewangles.x = -89.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->AllowOvertheShoulderView() )
	{
		engine->SetViewAngles( m_angViewAngle );
		cmd->viewangles_cam = m_angViewAngle;
		return;
	}

	trace_t tr;
	const Vector eyePos = pl->EyePosition();
	Vector vecForward = MainViewForward();
	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(), MainViewAngles(), 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 );
	cmd->viewangles_cam = m_angViewAngle;
}

client.dll: in_mouse.cpp

Go to void CInput::MouseMove( CUserCmd *cmd )

And 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

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

Add this include:

#include "iinput.h"

Find the function named 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;
	}

See also

Template:Otherlang:en Template:Otherlang:en:ru