Legs in Firstperson: Difference between revisions
(Created page with "This is a copy and paste tutorial will allow you to see your legs in first person. NOTE: This only works in Single Player 2007 Engine ( OB ) It will not work in a multiplayer mo...") |
No edit summary |
||
(15 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
{{lang|Legs in Firstperson}} | |||
This is a copy and paste tutorial that will allow you to see your legs in first person. | |||
It will not work in a multiplayer mod because | |||
{{Note|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|This currently does not work in Source SDK 2013.}} | |||
For a Source 2013 version use the guide created by KYPT1: [[Legs in Firstperson (2013)|Click here.]] | |||
== Requirements == | == Requirements == | ||
* | *A single player mod running on Source SDK 2007 | ||
*A 3D | *A 3D modeling application (XSI, 3ds Max, Blender, etc.) | ||
*First | *First person animation model pack (available [http://www.mediafire.com/?9sgpy5m6pracb7g here]) | ||
* | *Knowledge of C++ | ||
==Animation state system implementation | == Animation state system implementation == | ||
You will need to follow the code by Malortie to get this to work properly. | You will need to follow the code by [[User:Malortie|Malortie]] to get this to work properly. | ||
Create these two files in the shared folder of your mod. | Create these two files in the '''src/game/shared/''' folder of your mod. | ||
=== singleplayer_animstate.h === | |||
<source lang=cpp> | <source lang=cpp> | ||
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// | //========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// | ||
// | // | ||
Line 37: | Line 41: | ||
#ifdef CLIENT_DLL | #ifdef CLIENT_DLL | ||
#include "c_baseplayer.h" | #include "c_baseplayer.h" | ||
#else | #else | ||
#include "player.h" | #include "player.h" | ||
#endif | #endif | ||
Line 45: | Line 49: | ||
{ | { | ||
public: | 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: | 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; | |||
}; | }; | ||
Line 106: | Line 110: | ||
=== singleplayer_animstate.cpp === | |||
<source lang=cpp> | <source lang=cpp> | ||
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// | //========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// | ||
Line 129: | Line 131: | ||
extern ConVar mp_facefronttime, mp_feetyawrate, mp_ik; | extern ConVar mp_facefronttime, mp_feetyawrate, mp_ik; | ||
#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION | #define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f | ||
CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ) | 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 | // Below this many degrees, slow down turning rate linearly | ||
#define FADE_TURN_DEGREES | #define FADE_TURN_DEGREES 45.0f | ||
// After this, need to start turning feet | // After this, need to start turning feet | ||
#define MAX_TORSO_ANGLE | #define MAX_TORSO_ANGLE 90.0f | ||
// Below this amount, don't play a turning animation/perform IK | // Below this amount, don't play a turning animation/perform IK | ||
#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION | #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." ); | //static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." ); | ||
Line 154: | Line 156: | ||
extern ConVar mp_ik; | extern ConVar mp_ik; | ||
CSinglePlayerAnimState::CSinglePlayerAnimState( CBasePlayer *pPlayer ): m_pPlayer( pPlayer ) | 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 ) | void CSinglePlayerAnimState::Init( CBasePlayer *pPlayer ) | ||
{ | { | ||
m_pPlayer = pPlayer; | |||
} | } | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
// Purpose: | // Purpose: | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
void CSinglePlayerAnimState::Update() | void CSinglePlayerAnimState::Update() | ||
{ | { | ||
m_angRender = GetBasePlayer()->GetLocalAngles(); | |||
ComputePoseParam_BodyYaw(); | |||
ComputePoseParam_BodyPitch( GetBasePlayer()->GetModelPtr() ); | |||
ComputePoseParam_BodyLookYaw(); | |||
ComputePoseParam_HeadPitch( GetBasePlayer()->GetModelPtr() ); | |||
ComputePlaybackRate(); | |||
} | } | ||
void CSinglePlayerAnimState::Release() | void CSinglePlayerAnimState::Release() | ||
{ | { | ||
delete this; | |||
} | } | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
// Purpose: | // Purpose: | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
void CSinglePlayerAnimState::ComputePlaybackRate() | 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: | // Purpose: | ||
// Output : CBasePlayer | // Output : CBasePlayer | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
CBasePlayer *CSinglePlayerAnimState::GetBasePlayer() | CBasePlayer *CSinglePlayerAnimState::GetBasePlayer() | ||
{ | { | ||
return m_pPlayer; | |||
} | } | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
// Purpose: | // Purpose: | ||
// Input : dt - | // Input : dt - | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
void CSinglePlayerAnimState::EstimateYaw( void ) | 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 | // Purpose: Override for backpedaling | ||
// Input : dt - | // Input : dt - | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
void CSinglePlayerAnimState::ComputePoseParam_BodyYaw( void ) | 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 | #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 | #endif | ||
} | } | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
// Purpose: | // Purpose: | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
void CSinglePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) | 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: | // Purpose: | ||
// Input : goal - | // Input : goal - | ||
// | // maxrate - | ||
// | // dt - | ||
// | // current - | ||
// Output : int | // Output : int | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
int CSinglePlayerAnimState::ConvergeAngles( float goal,float maxrate, float dt, float& current ) | 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 ) | 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: | // Purpose: | ||
//----------------------------------------------------------------------------- | //----------------------------------------------------------------------------- | ||
void CSinglePlayerAnimState::ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr ) | 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: | // Purpose: | ||
// Input : activity - | // Input : activity - | ||
// Output : Activity | // Output : Activity | ||
Line 575: | Line 554: | ||
Activity CSinglePlayerAnimState::BodyYawTranslateActivity( Activity 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() | const QAngle &CSinglePlayerAnimState::GetRenderAngles() | ||
{ | { | ||
return m_angRender; | |||
} | } | ||
void CSinglePlayerAnimState::GetOuterAbsVelocity( Vector& vel ) | void CSinglePlayerAnimState::GetOuterAbsVelocity( Vector &vel ) | ||
{ | { | ||
#if defined( CLIENT_DLL ) | #if defined( CLIENT_DLL ) | ||
GetBasePlayer()->EstimateAbsVelocity( vel ); | |||
#else | #else | ||
vel = GetBasePlayer()->GetAbsVelocity(); | |||
#endif | #endif | ||
} | } | ||
</source> | </source> | ||
== Full implementation == | |||
== | === c_baseplayer.h === | ||
Search for <code>bool IsInFreezeCam( void );</code> and under it add: | |||
<source lang=cpp>bool IsInEye( void );</source> | |||
<source | Search for <code>virtual int DrawModel( int flags );</code> and under it add: | ||
<source lang=cpp>const Vector &C_BasePlayer::GetRenderOrigin();</source> | |||
=== c_baseplayer.cpp === | |||
Search for the function <code>bool IsInFreezeCam( void )</code> and under it add: | |||
<source lang=cpp> | <source lang=cpp> | ||
bool IsInEye( void ) | bool IsInEye( void ) | ||
Line 630: | Line 612: | ||
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); | C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); | ||
if ( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_IN_EYE ) | if ( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_IN_EYE ) | ||
return true; | |||
} | } | ||
</source> | </source> | ||
Next, search for <code>bool C_BasePlayer::ShouldDraw()</code> and make it look like this: | |||
Next | |||
< | |||
and make it look like this: | |||
<source lang=cpp> | <source lang=cpp> | ||
bool C_BasePlayer::ShouldDraw() | bool C_BasePlayer::ShouldDraw() | ||
Line 646: | Line 622: | ||
return BaseClass::ShouldDraw(); | return BaseClass::ShouldDraw(); | ||
} | } | ||
</source> | |||
static ConVar cl_legs("cl_legs", "1", FCVAR_CHEAT, "Enable or disable player leg rendering", true, 0, true, 1); | then and under that add: | ||
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"); | <source lang=cpp> | ||
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); | 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 ) | const Vector &C_BasePlayer::GetRenderOrigin( void ) | ||
{ | { | ||
// If we're not observing this player, or if we're not drawing it at the | // If we're not observing this player, or if we're not drawing it at the | ||
Line 657: | Line 636: | ||
// NOTE: the GetCurrentlyDrawingEntity check is here to make sure the | // NOTE: the GetCurrentlyDrawingEntity check is here to make sure the | ||
// shadow is rendered from the correct origin | // shadow is rendered from the correct origin | ||
if(!IsInEye() || view->GetCurrentlyDrawingEntity() != this) | if( !IsInEye() || view->GetCurrentlyDrawingEntity() != this ) | ||
return BaseClass::GetRenderOrigin(); | return BaseClass::GetRenderOrigin(); | ||
// Get the forward vector | // Get the forward vector | ||
static Vector forward; // static because this method returns a reference | static Vector forward; // static because this method returns a reference | ||
AngleVectors(GetRenderAngles(), &forward); | AngleVectors(GetRenderAngles(), &forward); | ||
// Shift the render origin by a fixed amount | // Shift the render origin by a fixed amount | ||
forward *= cl_legs_origin_shift.GetFloat(); | forward *= cl_legs_origin_shift.GetFloat(); | ||
forward += GetAbsOrigin(); | forward += GetAbsOrigin(); | ||
return forward; | return forward; | ||
} | } | ||
</source> | </source> | ||
Now, in the <code>int C_BasePlayer::DrawModel( int flags )</code> function add: | |||
<source lang=cpp> | |||
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() ); | |||
} | |||
</source> | </source> | ||
Now, search for the <code>void C_BasePlayer::AddEntity( void )</code> function and under | |||
Now, search for the | <source lang=cpp> | ||
// Add in lighting effects | |||
and under <source lang=cpp> | CreateLightEffects(); | ||
// Add in lighting effects | |||
CreateLightEffects(); | |||
</source> | </source> | ||
add | add: | ||
<source lang=cpp> | <source lang=cpp> | ||
SetLocalAnglesDim(X_INDEX, 0 ); | SetLocalAnglesDim( X_INDEX, 0 ); | ||
</source> | </source> | ||
=== hl2_player.h === | |||
Add <code>#include "singleplayer_animstate.h"</code> to the top of the file. | |||
<source lang=cpp> | Now, go to the end of the <code>public:</code> section in the <code>class CHL2_Player</code> class and add | ||
<source lang=cpp>void SetAnimation( PLAYER_ANIM playerAnim );</source> | |||
<source lang=cpp> | Now, go right to the end of the class, in the <code>private:</code> section and add: | ||
<source lang=cpp> | |||
CSinglePlayerAnimState *m_pPlayerAnimState; | |||
QAngle m_angEyeAngles; | |||
</source> | |||
=== hl2_player.cpp === | |||
Add <source lang=cpp>#define PLAYER_MODEL "models/player.mdl"</source> | |||
with all the other <code>#define</code>'s | |||
Now, search for <source lang=cpp>void CHL2_Player::Precache( void )</source> | |||
and add: | and add: | ||
<source lang=cpp>PrecacheModel( PLAYER_MODEL );</source> | |||
<source lang=cpp>PrecacheModel(PLAYER_MODEL);</source> | |||
Line 730: | Line 702: | ||
SetModel( "models/player.mdl" ); | SetModel( "models/player.mdl" ); | ||
</source> | </source> | ||
and set it to | and set it to: | ||
<source lang=cpp> | <source lang=cpp> | ||
SetModel( PLAYER_MODEL ); | SetModel( PLAYER_MODEL ); | ||
Line 736: | Line 708: | ||
Now, search for | Now, search for <code>CHL2_Player::~CHL2_Player( void )</code> and make it look like this: | ||
< | <source lang=cpp highlight=3-8> | ||
and make it look like | |||
<source lang=cpp> | |||
CHL2_Player::~CHL2_Player( void ) | CHL2_Player::~CHL2_Player( void ) | ||
{ | { | ||
// Clears the animation state. | // Clears the animation state. | ||
if ( m_pPlayerAnimState != NULL ) | if ( m_pPlayerAnimState != NULL ) | ||
Line 752: | Line 721: | ||
</source> | </source> | ||
Now, search for <code>CHL2_Player::CHL2_Player()</code> and make it look like: | |||
Now, search for | <source lang=cpp highlight=3-4> | ||
< | |||
and make it look like: | |||
<source lang=cpp> | |||
CHL2_Player::CHL2_Player() | CHL2_Player::CHL2_Player() | ||
{ | { | ||
m_pPlayerAnimState = CreatePlayerAnimationState( this ); | |||
m_pPlayerAnimState = CreatePlayerAnimationState(this); | |||
m_angEyeAngles.Init(); | m_angEyeAngles.Init(); | ||
m_nNumMissPositions = 0; | m_nNumMissPositions = 0; | ||
m_pPlayerAISquad = 0; | m_pPlayerAISquad = 0; | ||
m_bSprintEnabled = true; | m_bSprintEnabled = true; | ||
Line 773: | Line 737: | ||
</source> | </source> | ||
Now, head up to <code>void CHL2_Player::PostThink( void )</code> and make it look like this: | |||
Now, head up to < | <source lang=cpp highlight=10-14> | ||
and make it look like: | |||
<source lang=cpp> | |||
void CHL2_Player::PostThink( void ) | void CHL2_Player::PostThink( void ) | ||
{ | { | ||
Line 783: | Line 745: | ||
if ( !g_fGameOver && !IsPlayerLockedInPlace() && IsAlive() ) | if ( !g_fGameOver && !IsPlayerLockedInPlace() && IsAlive() ) | ||
{ | { | ||
HandleAdmireGlovesAnimation(); | |||
} | } | ||
m_angEyeAngles = EyeAngles(); | m_angEyeAngles = EyeAngles(); | ||
QAngle angles = GetLocalAngles(); | QAngle angles = GetLocalAngles(); | ||
Line 794: | Line 755: | ||
} | } | ||
</source> | </source> | ||
Now, add this to the end of the file: | Now, add this to the end of the file: | ||
Line 800: | Line 760: | ||
void CHL2_Player::SetAnimation( PLAYER_ANIM playerAnim ) | 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 ); | |||
} | |||
</source> | </source> | ||
==Weapon activity list tables== | == Weapon activity list tables == | ||
=== weapon_357.cpp === | |||
In the class description, under the macro | In the class description, under the macro <code>DECLARE_DATADESC()</code>, add this line: | ||
<source lang=cpp>DECLARE_ACTTABLE();</source> | <source lang=cpp>DECLARE_ACTTABLE();</source> | ||
Line 1,000: | Line 937: | ||
acttable_t CWeapon357::m_acttable[] = | 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 }, | |||
}; | }; | ||
Line 1,013: | Line 950: | ||
</source> | </source> | ||
=== weapon_ar2.cpp === | |||
There is already an activity list. At it's foot, after <code>//{ ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true },</code> add the following list: | |||
There is already an activity list. At it's foot, after // | |||
add the following list: | |||
<source lang=cpp> | <source lang=cpp> | ||
{ ACT_HL2MP_IDLE, | { 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 }, | |||
</source> | </source> | ||
=== weapon_crossbow.cpp === | |||
In the class description, around line 450, under the macro | In the class description, around line 450, under the macro <code>DECLARE_DATADESC()</code>, add this line: | ||
<source lang=cpp>DECLARE_ACTTABLE();</source> | <source lang=cpp>DECLARE_ACTTABLE();</source> | ||
Line 1,039: | Line 973: | ||
acttable_t CWeaponCrossbow::m_acttable[] = | 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 }, | |||
}; | }; | ||
Line 1,052: | Line 986: | ||
</source> | </source> | ||
=== weapon_crowbar.cpp === | |||
There is already an activity list. At it's foot, after <code>{ ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, false },</code> add the following list: | |||
There is already an activity list. At it's foot, after | |||
{ ACT_IDLE_ANGRY, | |||
add the following list: | |||
<source lang=cpp> | <source lang=cpp> | ||
{ ACT_RANGE_ATTACK1, | { 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 }, | |||
</source> | </source> | ||
=== weapon_frag.cpp === | |||
There is already an activity list. At it's foot, after | There is already an activity list. At it's foot, after <code>{ ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true },</code> add the following list: | ||
{ ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, | |||
add the following list: | |||
<source lang=cpp> | <source lang=cpp> | ||
{ ACT_HL2MP_IDLE, | { 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 }, | |||
</source> | </source> | ||
=== weapon_physcannon.cpp === | |||
In the class description, around line 1203, under the macro | In the class description, around line 1203, under the macro <code>DECLARE_DATADESC()</code>, add this line: | ||
<source lang=cpp>DECLARE_ACTTABLE();</source> | <source lang=cpp>DECLARE_ACTTABLE();</source> | ||
Line 1,095: | Line 1,022: | ||
acttable_t CWeaponPhysCannon::m_acttable[] = | 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 }, | |||
}; | }; | ||
Line 1,108: | Line 1,035: | ||
</source> | </source> | ||
=== weapon_pistol.cpp === | |||
There is already an activity list. At it's foot, after <code>{ ACT_RUN, ACT_RUN_PISTOL, false },</code> add the following list: | |||
There is already an activity list. At it's foot, after | |||
{ ACT_RUN, | |||
add the following list: | |||
<source lang=cpp> | <source lang=cpp> | ||
{ ACT_HL2MP_IDLE, | { 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 }, | |||
</source> | </source> | ||
=== weapon_rpg.cpp === | |||
add the following list: | There is already an activity list. At it's foot, after <code>{ ACT_COVER_LOW, ACT_COVER_LOW_RPG, true },</code> add the following list: | ||
<source lang=cpp> | <source lang=cpp> | ||
{ ACT_HL2MP_IDLE, | { 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 }, | |||
</source> | </source> | ||
=== weapon_shotgun.cpp === | |||
There is already an activity list. At it's foot, after <code>{ ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SHOTGUN, false },</code> add the following list: | |||
There is already an activity list. At it's foot, after | |||
{ ACT_GESTURE_RELOAD, | |||
add the following list: | |||
<source lang=cpp> | <source lang=cpp> | ||
{ ACT_HL2MP_IDLE, | { 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 }, | |||
</source> | </source> | ||
=== weapon_smg1.cpp === | |||
There is already an activity list. At it's foot, after | There is already an activity list. At it's foot, after <code>{ ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true },</code> add the following list: | ||
{ ACT_GESTURE_RELOAD, | |||
add the following list: | |||
<source lang=cpp> | <source lang=cpp> | ||
{ ACT_HL2MP_IDLE, | { 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 }, | |||
</source> | </source> | ||
==Creating the | == 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 [http://img689.imageshack.us/img689/7449/part1s.jpg here] (REMOVED). | |||
==Finished | == Finished product == | ||
View the finished product [http://youtu.be/GiPg3-QJeic here] on | View the finished product [http://youtu.be/GiPg3-QJeic here] on YouTube (REMOVED). | ||
==Acknowledgments== | == Acknowledgments == | ||
*Singleplayer | *Singleplayer animation code - Malortie | ||
*Other | *Weapon activity tables - Malortie | ||
*Other stuff - Zombie Killa | |||
*Gordon Freeman - VALVe and DPFilms | *Gordon Freeman - VALVe and DPFilms | ||
{{DISPLAYTITLE:Legs in First Person}} | |||
[[Category: Programming]] | |||
[[Category: Tutorials]] |
Latest revision as of 08:55, 2 March 2025
This is a copy and paste tutorial that will allow you to see your legs in first person.


For a Source 2013 version use the guide created by KYPT1: Click here.
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".