Legs in First Person

From Valve Developer Community
Jump to navigation Jump to search
English (en)Русский (ru)Translate (Translate)

This is a copy and paste tutorial that will allow you to see your legs in first person.

Note.pngNote:This only works in Single Player 2007 Engine (OB). It will not work in a multiplayer mod because it replaces the actual third person model to a new model without arms or head.
Note.pngNote:This currently does not work in Source SDK 2013.

Requirements

  • A single player mod running on Source SDK 2007
  • A 3D modeling application (XSI, 3ds Max, Blender, etc.)
  • First person animation model pack (available here)
  • Knowledge of C++

Animation state system implementation

You will need to follow the code by Malortie to get this to work properly.

Create these two files in the src/game/shared/ folder of your mod.

singleplayer_animstate.h

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Single Player animation state 'handler'. This utility is used
//            to evaluate the pose parameter value based on the direction
//            and speed of the player.
//
//====================================================================================//

#ifndef SINGLEPLAYER_ANIMSTATE_H
#define SINGLEPLAYER_ANIMSTATE_H
#ifdef _WIN32
#pragma once
#endif

#include "cbase.h"

#ifdef CLIENT_DLL
	#include "c_baseplayer.h"
#else
	#include "player.h"
#endif

class CSinglePlayerAnimState
{
public:
	enum
	{
		TURN_NONE = 0,
		TURN_LEFT,
		TURN_RIGHT
	};

	CSinglePlayerAnimState( CBasePlayer *pPlayer );

	void				Init( CBasePlayer *pPlayer );

	Activity			BodyYawTranslateActivity( Activity activity );

	void				Update();

	const QAngle		&GetRenderAngles();

	void				GetPoseParameters( CStudioHdr *pStudioHdr, float poseParameter[MAXSTUDIOPOSEPARAM] );

	CBasePlayer			*GetBasePlayer();

	void				Release();

private:
	void				GetOuterAbsVelocity( Vector &vel );

	int					ConvergeAngles( float goal, float maxrate, float dt, float &current );

	void				EstimateYaw( void );
	void				ComputePoseParam_BodyYaw( void );
	void				ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr );
	void				ComputePoseParam_BodyLookYaw( void );
	void				ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr );
	void				ComputePlaybackRate();

	CBasePlayer			*m_pPlayer;

	float				m_flGaitYaw;
	float				m_flStoredCycle;

	float				m_flGoalFeetYaw;
	float				m_flCurrentFeetYaw;

	float				m_flCurrentTorsoYaw;

	float				m_flLastYaw;
	float				m_flLastTurnTime;

	int					m_nTurningInPlace;

	QAngle				m_angRender;

	float				m_flTurnCorrectionTime;
};

CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer );

#endif // SINGLEPLAYER_ANIMSTATE_H


singleplayer_animstate.cpp

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Single Player animation state 'handler'. This utility is used
//            to evaluate the pose parameter value based on the direction
//            and speed of the player.
//
//====================================================================================//

#include "cbase.h"
#include "singleplayer_animstate.h"
#include "tier0/vprof.h"
#include "animation.h"
#include "studio.h"
#include "apparent_velocity_helper.h"
#include "utldict.h"
#include "filesystem.h"

extern ConVar mp_facefronttime, mp_feetyawrate, mp_ik;

#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f

CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer )
{
	MDLCACHE_CRITICAL_SECTION();

	CSinglePlayerAnimState *pState = new CSinglePlayerAnimState( pPlayer );
	pState->Init(pPlayer);

	return pState;
}

// Below this many degrees, slow down turning rate linearly
#define FADE_TURN_DEGREES 45.0f
// After this, need to start turning feet
#define MAX_TORSO_ANGLE 90.0f
// Below this amount, don't play a turning animation/perform IK
#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f

//static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." );
extern ConVar sv_backspeed;
extern ConVar mp_feetyawrate;
extern ConVar mp_facefronttime;
extern ConVar mp_ik;

CSinglePlayerAnimState::CSinglePlayerAnimState( CBasePlayer *pPlayer ) : m_pPlayer( pPlayer )
{
	m_flGaitYaw				= 0.0f;
	m_flGoalFeetYaw			= 0.0f;
	m_flCurrentFeetYaw		= 0.0f;
	m_flCurrentTorsoYaw		= 0.0f;
	m_flLastYaw				= 0.0f;
	m_flLastTurnTime		= 0.0f;
	m_flTurnCorrectionTime	= 0.0f;

	m_pPlayer = NULL;
};

void CSinglePlayerAnimState::Init( CBasePlayer *pPlayer )
{
	m_pPlayer = pPlayer;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSinglePlayerAnimState::Update()
{
	m_angRender = GetBasePlayer()->GetLocalAngles();

	ComputePoseParam_BodyYaw();
	ComputePoseParam_BodyPitch( GetBasePlayer()->GetModelPtr() );
	ComputePoseParam_BodyLookYaw();
	ComputePoseParam_HeadPitch( GetBasePlayer()->GetModelPtr() );

	ComputePlaybackRate();
}

void CSinglePlayerAnimState::Release()
{
	delete this;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSinglePlayerAnimState::ComputePlaybackRate()
{
	// Determine ideal playback rate
	Vector vel;
	GetOuterAbsVelocity( vel );

	float speed = vel.Length2D();

	bool isMoving = (speed > 0.5f) ? true : false;

	float maxspeed = GetBasePlayer()->GetSequenceGroundSpeed( GetBasePlayer()->GetSequence() );

	if ( isMoving && (maxspeed > 0.0f) )
	{
		float flFactor = 1.0f;

		// Note this gets set back to 1.0 if sequence changes due to ResetSequenceInfo below
		GetBasePlayer()->SetPlaybackRate( (speed * flFactor) / maxspeed );

		// BUG BUG:
		// This stuff really should be m_flPlaybackRate = speed / m_flGroundSpeed
	}
	else
	{
		GetBasePlayer()->SetPlaybackRate( 1.0f );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : CBasePlayer
//-----------------------------------------------------------------------------
CBasePlayer *CSinglePlayerAnimState::GetBasePlayer()
{
	return m_pPlayer;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : dt -
//-----------------------------------------------------------------------------
void CSinglePlayerAnimState::EstimateYaw( void )
{
	float dt = gpGlobals->frametime;

	if ( !dt )
		return;

	Vector est_velocity;
	QAngle angles;

	GetOuterAbsVelocity( est_velocity );

	angles = GetBasePlayer()->GetLocalAngles();

	if ( est_velocity[1] == 0 && est_velocity[0] == 0 )
	{
		float flYawDiff = angles[YAW] - m_flGaitYaw;
		
		flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360;
		if ( flYawDiff > 180 )
			flYawDiff -= 360;
		if ( flYawDiff < -180 )
			flYawDiff += 360;

		if (dt < 0.25)
			flYawDiff *= dt * 4;
		else
			flYawDiff *= dt;

		m_flGaitYaw += flYawDiff;
		m_flGaitYaw = m_flGaitYaw - (int)(m_flGaitYaw / 360) * 360;
	}
	else
	{
		m_flGaitYaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI);

		if ( m_flGaitYaw > 180 )
			m_flGaitYaw = 180;
		else if ( m_flGaitYaw < -180 )
			m_flGaitYaw = -180;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Override for backpedaling
// Input  : dt -
//-----------------------------------------------------------------------------
void CSinglePlayerAnimState::ComputePoseParam_BodyYaw( void )
{
	int iYaw = GetBasePlayer()->LookupPoseParameter( "move_yaw" );

	if ( iYaw < 0 )
		return;

	// View direction relative to movement
	float flYaw;

	EstimateYaw();

	QAngle angles = GetBasePlayer()->GetLocalAngles();
	float ang = angles[YAW];
	if ( ang > 180.0f )
		ang -= 360.0f;
	else if ( ang < -180.0f )
		ang += 360.0f;

	// Calculate side to side turning
	flYaw = ang - m_flGaitYaw;

	// Invert for mapping into 8way blend
	flYaw = -flYaw;
	flYaw = flYaw - (int)(flYaw / 360) * 360;

	if ( flYaw < -180 )
		flYaw = flYaw + 360;
	else if ( flYaw > 180 )
		flYaw = flYaw - 360;

	GetBasePlayer()->SetPoseParameter( iYaw, flYaw );

#ifndef CLIENT_DLL
	// Adrian: Make the model's angle match the legs so the hitboxes match on both sides.
	GetBasePlayer()->SetLocalAngles( QAngle( GetBasePlayer()->EyeAngles().x, m_flCurrentFeetYaw, 0 ) );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSinglePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr )
{
	// Get pitch from v_angle
	float flPitch = GetBasePlayer()->GetLocalAngles()[PITCH];

	if ( flPitch > 180.0f )
	{
		flPitch -= 360.0f;
	}
	flPitch = clamp( flPitch, -90, 90 );

	QAngle absangles = GetBasePlayer()->GetAbsAngles();
	absangles.x = 0.0f;
	m_angRender = absangles;

	// See if we have a blender for pitch
	GetBasePlayer()->SetPoseParameter( pStudioHdr, "aim_pitch", flPitch );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : goal -
//          maxrate -
//          dt -
//          current -
// Output : int
//-----------------------------------------------------------------------------
int CSinglePlayerAnimState::ConvergeAngles( float goal, float maxrate, float dt, float &current )
{
	int direction = TURN_NONE;

	float anglediff = goal - current;
	float anglediffabs = fabs( anglediff );

	anglediff = AngleNormalize( anglediff );

	float scale = 1.0f;
	if ( anglediffabs <= FADE_TURN_DEGREES )
	{
		scale = anglediffabs / FADE_TURN_DEGREES;

		// Always do at least a bit of the turn ( 1% )
		scale = clamp( scale, 0.01f, 1.0f );
	}

	float maxmove = maxrate * dt * scale;

	if ( fabs(anglediff) < maxmove )
	{
		current = goal;
	}
	else
	{
		if ( anglediff > 0 )
		{
			current += maxmove;
			direction = TURN_LEFT;
		}
		else
		{
			current -= maxmove;
			direction = TURN_RIGHT;
		}
	}

	current = AngleNormalize( current );

	return direction;
}

void CSinglePlayerAnimState::ComputePoseParam_BodyLookYaw( void )
{
	QAngle absangles = GetBasePlayer()->GetAbsAngles();
	absangles.y = AngleNormalize( absangles.y );
	m_angRender = absangles;

	// See if we even have a blender for pitch
	int upper_body_yaw = GetBasePlayer()->LookupPoseParameter( "aim_yaw" );

	if ( upper_body_yaw < 0 )
		return;

	// Assume upper and lower bodies are aligned and that we're not turning
	float flGoalTorsoYaw = 0.0f;
	int turning = TURN_NONE;
	float turnrate = 360.0f;

	Vector vel;

	GetOuterAbsVelocity( vel );

	bool isMoving = (vel.Length() > 1.0f) ? true : false;
	if ( !isMoving )
	{
		// Just stopped moving, try and clamp feet
		if ( m_flLastTurnTime <= 0.0f )
		{
			m_flLastTurnTime	= gpGlobals->curtime;
			m_flLastYaw			= GetBasePlayer()->EyeAngles().y;

			// Snap feet to be perfectly aligned with torso/eyes
			m_flGoalFeetYaw		= GetBasePlayer()->EyeAngles().y;
			m_flCurrentFeetYaw	= m_flGoalFeetYaw;
			m_nTurningInPlace	= TURN_NONE;
		}

		// If rotating in place, update stasis timer
		if ( m_flLastYaw != GetBasePlayer()->EyeAngles().y )
		{
			m_flLastTurnTime	= gpGlobals->curtime;
			m_flLastYaw			= GetBasePlayer()->EyeAngles().y;
		}

		if ( m_flGoalFeetYaw != m_flCurrentFeetYaw )
			m_flLastTurnTime = gpGlobals->curtime;

		turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw );

		QAngle eyeAngles = GetBasePlayer()->EyeAngles();
		QAngle vAngle = GetBasePlayer()->GetLocalAngles();

		// See how far off current feetyaw is from true yaw
		float yawdelta = GetBasePlayer()->EyeAngles().y - m_flCurrentFeetYaw;
		yawdelta = AngleNormalize( yawdelta );

		bool rotated_too_far = false;

		// If too far, then need to turn in place
		float yawmagnitude = fabs( yawdelta );
		if ( yawmagnitude > 45 )
			rotated_too_far = true;

		// Standing still for a while, rotate feet around to face forward or rotated too far
		// FIXME: Play an in place turning animation
		if ( rotated_too_far || (gpGlobals->curtime > m_flLastTurnTime + mp_facefronttime.GetFloat()) )
		{
			m_flGoalFeetYaw  = GetBasePlayer()->EyeAngles().y;
			m_flLastTurnTime = gpGlobals->curtime;

			/*
			float yd = m_flCurrentFeetYaw - m_flGoalFeetYaw;
			if ( yd > 0 )
			{
				m_nTurningInPlace = TURN_RIGHT;
			}
			else if ( yd < 0 )
			{
				m_nTurningInPlace = TURN_LEFT;
			}
			else
			{
				m_nTurningInPlace = TURN_NONE;
			}

			turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw );
			yawdelta = GetBasePlayer()->EyeAngles().y - m_flCurrentFeetYaw;
			*/
		}

		// Snap upper body into position since the delta is already smoothed for the feet
		flGoalTorsoYaw		= yawdelta;
		m_flCurrentTorsoYaw	= flGoalTorsoYaw;
	}
	else
	{
		m_flLastTurnTime	= 0.0f;
		m_nTurningInPlace	= TURN_NONE;
		m_flCurrentFeetYaw	= m_flGoalFeetYaw = GetBasePlayer()->EyeAngles().y;
		flGoalTorsoYaw		= 0.0f;
		m_flCurrentTorsoYaw	= GetBasePlayer()->EyeAngles().y - m_flCurrentFeetYaw;
	}

	if ( turning == TURN_NONE )
		m_nTurningInPlace = turning;

	if ( m_nTurningInPlace != TURN_NONE )
	{
		// If we're close to finishing the turn, then turn off the turning animation
		if ( fabs(m_flCurrentFeetYaw - m_flGoalFeetYaw) < MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION )
			m_nTurningInPlace = TURN_NONE;
	}

	// Rotate entire body into position
	absangles	= GetBasePlayer()->GetAbsAngles();
	absangles.y	= m_flCurrentFeetYaw;
	m_angRender	= absangles;

	GetBasePlayer()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -60.0f, 60.0f ) );

	/*
	// FIXME: Adrian, what is this?
	int body_yaw = GetBasePlayer()->LookupPoseParameter( "body_yaw" );
	if ( body_yaw >= 0 )
		GetBasePlayer()->SetPoseParameter( body_yaw, 30 );
	*/
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSinglePlayerAnimState::ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr )
{
	// Get pitch from v_angle
	int iHeadPitch = GetBasePlayer()->LookupPoseParameter( "head_pitch" );

	float flPitch = GetBasePlayer()->EyeAngles()[PITCH];

	if ( flPitch > 180.0f )
	{
		flPitch -= 360.0f;
	}
	flPitch = clamp( flPitch, -90, 90 );

	QAngle absangles = GetBasePlayer()->GetAbsAngles();
	absangles.x = 0.0f;
	m_angRender = absangles;

	GetBasePlayer()->SetPoseParameter( pStudioHdr, iHeadPitch, flPitch );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : activity -
// Output : Activity
//-----------------------------------------------------------------------------
Activity CSinglePlayerAnimState::BodyYawTranslateActivity( Activity activity )
{
	// Not even standing still, sigh
	if ( activity != ACT_IDLE )
		return activity;

	// Not turning
	switch ( m_nTurningInPlace )
	{
		default:
		case TURN_NONE:
			return activity;
		/*
		case TURN_RIGHT:
			return ACT_TURNRIGHT45;
		case TURN_LEFT:
			return ACT_TURNLEFT45;
		*/
		case TURN_RIGHT:
		case TURN_LEFT:
			return mp_ik.GetBool() ? ACT_TURN : activity;
	}

	Assert( 0 );
	return activity;
}

const QAngle &CSinglePlayerAnimState::GetRenderAngles()
{
	return m_angRender;
}

void CSinglePlayerAnimState::GetOuterAbsVelocity( Vector &vel )
{
#if defined( CLIENT_DLL )
	GetBasePlayer()->EstimateAbsVelocity( vel );
#else
	vel = GetBasePlayer()->GetAbsVelocity();
#endif
}

Full implementation

c_baseplayer.h

Search for bool IsInFreezeCam( void ); and under it add:

bool IsInEye( void );

Search for virtual int DrawModel( int flags ); and under it add:

const Vector &C_BasePlayer::GetRenderOrigin();

c_baseplayer.cpp

Search for the function bool IsInFreezeCam( void ) and under it add:

bool IsInEye( void )
{
	C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
	if ( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
		return true;
}

Next, search for bool C_BasePlayer::ShouldDraw() and make it look like this:

bool C_BasePlayer::ShouldDraw()
{
	return BaseClass::ShouldDraw();
}

then and under that add:

static ConVar cl_legs( "cl_legs", "1", FCVAR_CHEAT, "Enable or disable player leg rendering", true, 0, true, 1 );
static ConVar cl_legs_origin_shift( "cl_legs_origin_shift", "-17", FCVAR_CHEAT, "Amount in game units to shift the player model relative to the direction the player is facing" );
static ConVar cl_legs_clip_height( "cl_legs_clip_height", "0", FCVAR_CHEAT, "Amount in game units of the player model to render up to [0 = disable]", true, 0, false, 0 );

const Vector &C_BasePlayer::GetRenderOrigin( void )
{
	// If we're not observing this player, or if we're not drawing it at the
	// moment then use the normal absolute origin.
	// NOTE: the GetCurrentlyDrawingEntity check is here to make sure the
	// shadow is rendered from the correct origin
	if( !IsInEye() || view->GetCurrentlyDrawingEntity() != this )
		return BaseClass::GetRenderOrigin();

	// Get the forward vector
	static Vector forward; // static because this method returns a reference
	AngleVectors(GetRenderAngles(), &forward);

	// Shift the render origin by a fixed amount
	forward *= cl_legs_origin_shift.GetFloat();
	forward += GetAbsOrigin();

	return forward;
}

Now, in the int C_BasePlayer::DrawModel( int flags ) function add:

	CMatRenderContextPtr context( materials );

	if ( cl_legs_clip_height.GetInt() > 0 )
	{
		context->SetHeightClipMode( MATERIAL_HEIGHTCLIPMODE_RENDER_BELOW_HEIGHT );
		context->SetHeightClipZ( GetAbsOrigin().z + cl_legs_clip_height.GetFloat() );
	}

Now, search for the void C_BasePlayer::AddEntity( void ) function and under

	// Add in lighting effects
	CreateLightEffects();

add:

	SetLocalAnglesDim( X_INDEX, 0 );

hl2_player.h

Add #include "singleplayer_animstate.h" to the top of the file.


Now, go to the end of the public: section in the class CHL2_Player class and add

void SetAnimation( PLAYER_ANIM playerAnim );


Now, go right to the end of the class, in the private: section and add:

	CSinglePlayerAnimState *m_pPlayerAnimState;
	QAngle m_angEyeAngles;

hl2_player.cpp

Add

#define PLAYER_MODEL "models/player.mdl"

with all the other #define's


Now, search for

void CHL2_Player::Precache( void )

and add:

PrecacheModel( PLAYER_MODEL );


Now, search for

void CHL2_Player::Spawn(void)

and find the following line

SetModel( "models/player.mdl" );

and set it to:

SetModel( PLAYER_MODEL );


Now, search for CHL2_Player::~CHL2_Player( void ) and make it look like this:

CHL2_Player::~CHL2_Player( void )
{
	// Clears the animation state.
	if ( m_pPlayerAnimState != NULL )
	{
	    m_pPlayerAnimState->Release();
	    m_pPlayerAnimState = NULL;
	}
}

Now, search for CHL2_Player::CHL2_Player() and make it look like:

CHL2_Player::CHL2_Player()
{
	m_pPlayerAnimState = CreatePlayerAnimationState( this );
	m_angEyeAngles.Init();

	m_nNumMissPositions = 0;
	m_pPlayerAISquad = 0;
	m_bSprintEnabled = true;

	m_flArmorReductionTime = 0.0f;
	m_iArmorReductionFrom = 0;
}

Now, head up to void CHL2_Player::PostThink( void ) and make it look like this:

void CHL2_Player::PostThink( void )
{
	BaseClass::PostThink();

	if ( !g_fGameOver && !IsPlayerLockedInPlace() && IsAlive() )
	{
		HandleAdmireGlovesAnimation();
	}

	m_angEyeAngles = EyeAngles();
	QAngle angles = GetLocalAngles();
	angles[PITCH] = 0;
	SetLocalAngles( angles );
	m_pPlayerAnimState->Update();
}

Now, add this to the end of the file:

void CHL2_Player::SetAnimation( PLAYER_ANIM playerAnim )
{
	int animDesired;
	float speed = GetAbsVelocity().Length2D();

	if ( GetFlags() & (FL_FROZEN | FL_ATCONTROLS) )
	{
		speed = 0;
		playerAnim = PLAYER_IDLE;
	}

	Activity idealActivity = ACT_HL2MP_RUN;

	if ( playerAnim == PLAYER_JUMP )
	{
		if ( HasWeapons() )
			idealActivity = ACT_HL2MP_JUMP;
		else
			idealActivity = ACT_JUMP;
	}
	else if ( playerAnim == PLAYER_DIE )
	{
		if ( m_lifeState == LIFE_ALIVE )
			return;
	}
	else if ( playerAnim == PLAYER_ATTACK1 )
	{
		if ( GetActivity() == ACT_HOVER      || 
			GetActivity() == ACT_SWIM        || 
			GetActivity() == ACT_HOP         || 
			GetActivity() == ACT_LEAP        || 
			GetActivity() == ACT_DIESIMPLE )
		{
			idealActivity = GetActivity();
		}
		else
		{
			idealActivity = ACT_HL2MP_GESTURE_RANGE_ATTACK;
		}
	}
	else if ( playerAnim == PLAYER_RELOAD )
	{
		idealActivity = ACT_HL2MP_GESTURE_RELOAD;
	}
	else if ( playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK )
	{
		if ( !(GetFlags() & FL_ONGROUND) && (GetActivity() == ACT_HL2MP_JUMP || GetActivity() == ACT_JUMP) ) // Still jumping
		{
			idealActivity = GetActivity( );
		}
		else if ( GetWaterLevel() > 1 )
		{
			if ( speed == 0 )
			{
				if ( HasWeapons() )
					idealActivity = ACT_HL2MP_IDLE;
				else
					idealActivity = ACT_IDLE;
			}
			else
			{
				if ( HasWeapons() )
					idealActivity = ACT_HL2MP_RUN;
				else
					idealActivity = ACT_RUN;
			}
		}
		else
		{
			if ( GetFlags() & FL_DUCKING )
			{
				if ( speed > 0 )
				{
					if ( HasWeapons() )
						idealActivity = ACT_HL2MP_WALK_CROUCH;
					else
						idealActivity = ACT_WALK_CROUCH;
				}
				else
				{
					if ( HasWeapons() )
						idealActivity = ACT_HL2MP_IDLE_CROUCH;
					else
						idealActivity = ACT_COVER_LOW;
				}
			}
			else
			{
				if ( speed > 0 )
				{
					{
						if ( HasWeapons() )
							idealActivity = ACT_HL2MP_RUN;
						else
						{
							if ( speed > HL2_WALK_SPEED + 20.0f )
								idealActivity = ACT_RUN;
							else
								idealActivity = ACT_WALK;
						}
					}
				}
				else
				{
					if ( HasWeapons() )
						idealActivity = ACT_HL2MP_IDLE;
					else
						idealActivity = ACT_IDLE;
				}
			}
		}

		//idealActivity = TranslateTeamActivity( idealActivity );
	}

	if ( IsInAVehicle() )
		idealActivity = ACT_COVER_LOW;

	if ( idealActivity == ACT_HL2MP_GESTURE_RANGE_ATTACK )
	{
		RestartGesture( Weapon_TranslateActivity( idealActivity ) );

		// FIXME: this seems a bit whacked
		Weapon_SetActivity( Weapon_TranslateActivity( ACT_RANGE_ATTACK1 ), 0 );

		return;
	}
	else if ( idealActivity == ACT_HL2MP_GESTURE_RELOAD )
	{
		RestartGesture( Weapon_TranslateActivity( idealActivity ) );
		return;
	}
	else
	{
		SetActivity( idealActivity );

		animDesired = SelectWeightedSequence( Weapon_TranslateActivity( idealActivity ) );

		if ( animDesired == -1 )
		{
			animDesired = SelectWeightedSequence( idealActivity );

			if ( animDesired == -1 )
				animDesired = 0;
		}

		// Already using the desired animation?
		if ( GetSequence() == animDesired )
			return;

		m_flPlaybackRate = 1.0f;
		ResetSequence( animDesired );
		SetCycle( 0 );

		return;
	}

	// Already using the desired animation?
	if ( GetSequence() == animDesired )
		return;

	//Msg( "Set animation to %d\n", animDesired );
	// Reset to first frame of desired animation
	ResetSequence( animDesired );
	SetCycle( 0 );
}

Weapon activity list tables

weapon_357.cpp

In the class description, under the macro DECLARE_DATADESC(), add this line:

DECLARE_ACTTABLE();

Under the class definition around line 50, add this activity table list:

acttable_t CWeapon357::m_acttable[] =
{
	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_PISTOL,					false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_PISTOL,					false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_PISTOL,			false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_PISTOL,			false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL,	false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_PISTOL,		false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_PISTOL,					false },
	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_PISTOL,				false },
};

IMPLEMENT_ACTTABLE( CWeapon357 );

weapon_ar2.cpp

There is already an activity list. At it's foot, after //{ ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true }, add the following list:

	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_AR2,						false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_AR2,						false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_AR2,				false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_AR2,				false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2,		false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_GESTURE_RELOAD_SMG1,				false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_AR2,						false },
	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_AR2,					false },

weapon_crossbow.cpp

In the class description, around line 450, under the macro DECLARE_DATADESC(), add this line:

DECLARE_ACTTABLE();

Under the class definition around line 489, add this activity table list:

acttable_t CWeaponCrossbow::m_acttable[] =
{
	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_CROSSBOW,					false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_CROSSBOW,						false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_CROSSBOW,				false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_CROSSBOW,				false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW,	false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_CROSSBOW,			false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_CROSSBOW,					false },
	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_SHOTGUN,					false },
};

IMPLEMENT_ACTTABLE( CWeaponCrossbow );

weapon_crowbar.cpp

There is already an activity list. At it's foot, after { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, false }, add the following list:

	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_SLAM,					true },
	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_MELEE,					false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_MELEE,					false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_MELEE,			false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_MELEE,			false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE,	false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_MELEE,			false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_MELEE,					false },

weapon_frag.cpp

There is already an activity list. At it's foot, after { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, add the following list:

	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_GRENADE,						false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_GRENADE,						false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_GRENADE,				false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_GRENADE,				false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE,		false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_GRENADE,			false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_GRENADE,						false },

weapon_physcannon.cpp

In the class description, around line 1203, under the macro DECLARE_DATADESC(), add this line:

DECLARE_ACTTABLE();

Under the class definition around line 1374, add this activity table list:

acttable_t CWeaponPhysCannon::m_acttable[] =
{
	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_PHYSGUN,						false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_PHYSGUN,						false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_PHYSGUN,				false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_PHYSGUN,				false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN,		false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_PHYSGUN,			false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_PHYSGUN,						false },
	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_SLAM,						false },
};

IMPLEMENT_ACTTABLE( CWeaponPhysCannon );

weapon_pistol.cpp

There is already an activity list. At it's foot, after { ACT_RUN, ACT_RUN_PISTOL, false }, add the following list:

	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_PISTOL,					false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_PISTOL,					false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_PISTOL,			false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_PISTOL,			false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL,	false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_PISTOL,		false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_PISTOL,					false },
	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_PISTOL,				false },

weapon_rpg.cpp

There is already an activity list. At it's foot, after { ACT_COVER_LOW, ACT_COVER_LOW_RPG, true }, add the following list:

	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_RPG,						false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_RPG,						false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_RPG,				false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_RPG,				false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG,		false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_RPG,			false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_RPG,						false },
	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_RPG,					false },

weapon_shotgun.cpp

There is already an activity list. At it's foot, after { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SHOTGUN, false }, add the following list:

	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_SHOTGUN,						false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_SHOTGUN,						false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_SHOTGUN,				false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_SHOTGUN,				false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN,		false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_SHOTGUN,			false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_SHOTGUN,						false },
	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_SHOTGUN,					false },

weapon_smg1.cpp

There is already an activity list. At it's foot, after { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, add the following list:

	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_SMG1,					false },
	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_SMG1,						false },
	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_SMG1,				false },
	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_SMG1,				false },
	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG1,	false },
	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_GESTURE_RELOAD_SMG1,				false },
	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_SMG1,					false },
	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_SMG1,					false },

Creating the model

If you do create a custom model for this, look at the original player.mdl SMD files found in the provided archive for reference.

When creating the actual model make it look like the one in gordon_body_ref.smd or like in the picture here (REMOVED).

Finished product

View the finished product here on YouTube (REMOVED).

Acknowledgments

  • Singleplayer animation code - Malortie
  • Weapon activity tables - Malortie
  • Other stuff - Zombie Killa
  • Gordon Freeman - VALVe and DPFilms

Warning: Display title "Legs in First Person" overrides earlier display title "Legs in Firstperson".