Legs in First Person
This is a copy and paste tutorial that will allow you to see your legs in first person.
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 ¤t );
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 ¤t )
{
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".