Legs in First Person

From Valve Developer Community
Revision as of 05:02, 13 August 2020 by GamerDude27 (talk | contribs) (Updated links (moved host))
Jump to navigation Jump to search

Template:Otherlang2

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, download the original player.mdl SMD files for reference here.

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

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