sof2-sdk/code/game/bg_pmove.c
2002-07-15 00:00:00 +00:00

3997 lines
86 KiB
C

// Copyright (C) 2001-2002 Raven Software
//
// bg_pmove.c -- both games player movement code
// takes a playerstate and a usercmd as input and returns a modifed playerstate
#include "q_shared.h"
#include "bg_public.h"
#include "bg_local.h"
pmove_t *pm;
pml_t pml;
// Speed scales
float pm_stopspeed = 100.0f;
float pm_duckScale = 0.25f;
float pm_swimScale = 0.50f;
float pm_wadeScale = 0.70f;
const float pm_ladderScale = 0.5f;
// Accelerations
float pm_accelerate = 6.0f;
float pm_airaccelerate = 1.0f;
float pm_wateraccelerate = 4.0f;
float pm_flyaccelerate = 8.0f;
// Frictions
float pm_headfriction = 0.0f; // Friction when on someones head
float pm_friction = 6.0f; // Friction when on the ground
float pm_waterfriction = 3.0f; // Friction when in water
float pm_ladderfriction = 6.0f; // Friction when on a ladder
float pm_spectatorfriction = 5.0f; // Friction when flying aroudn as a spectator
int c_pmove = 0;
ladder_t pm_ladders[MAX_LADDERS];
int pm_laddercount = 0;
static void PM_Weapon_AddInaccuracy ( attackType_t attack );
static void PM_Weapon_AddKickAngles ( vec3_t kickAngles );
static void PM_BeginZoomOut ( void );
/*
===============
PM_AddEvent
===============
*/
void PM_AddEvent( int newEvent )
{
BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps );
}
/*
===============
PM_AddEventWithParm
===============
*/
void PM_AddEventWithParm( int newEvent, int parm )
{
BG_AddPredictableEventToPlayerstate( newEvent, parm, pm->ps );
}
/*
===============
PM_AddTouchEnt
===============
*/
void PM_AddTouchEnt ( int entityNum )
{
int i;
// Cant add the world as a touch entity
if ( entityNum == ENTITYNUM_WORLD )
{
return;
}
// Ensure the max touch limit has not been reached
if ( pm->numtouch == MAXTOUCH )
{
return;
}
// see if it is already added
for ( i = 0 ; i < pm->numtouch ; i++ )
{
if ( pm->touchents[ i ] == entityNum )
{
return;
}
}
// add it
pm->touchents[pm->numtouch] = entityNum;
pm->numtouch++;
}
/*
==================
PM_ClipVelocity
Slide off of the impacting surface
==================
*/
void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce )
{
float backoff;
float change;
int i;
backoff = DotProduct (in, normal);
if ( backoff < 0 )
{
backoff *= overbounce;
}
else
{
backoff /= overbounce;
}
for ( i=0 ; i<3 ; i++ )
{
change = normal[i]*backoff;
out[i] = in[i] - change;
}
}
/*
==================
PM_Friction
Handles both ground friction and water friction
==================
*/
static void PM_Friction( void )
{
vec3_t vec;
float *vel;
float speed, newspeed, control;
float drop;
vel = pm->ps->velocity;
VectorCopy( vel, vec );
if ( pml.walking )
{
vec[2] = 0; // ignore slope movement
}
speed = VectorLength(vec);
if (speed < 1)
{
vel[0] = 0;
vel[1] = 0; // allow sinking underwater
// FIXME: still have z friction underwater?
return;
}
drop = 0;
// apply ground friction
if ( pm->waterlevel <= 1 )
{
if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) )
{
// if getting knocked back, no friction
if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) )
{
control = speed < pm_stopspeed ? pm_stopspeed : speed;
drop += control*pm_friction*pml.frametime;
}
}
}
// apply water friction even if just wading
if ( pm->ps->pm_flags & PMF_LADDER )
{
if ( !pml.groundPlane )
{
control = speed < pm_stopspeed ? pm_stopspeed : speed;
drop += control*pm_ladderfriction*pml.frametime;
}
}
else if ( pm->waterlevel > 1 )
{
drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime;
}
// If on someones head then use special friction
else if ( pm->ps->groundEntityNum < MAX_CLIENTS )
{
drop = speed*pm_headfriction*pml.frametime;
}
if ( pm->ps->pm_type == PM_SPECTATOR)
{
drop += speed*pm_spectatorfriction*pml.frametime;
}
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0)
{
newspeed = 0;
}
newspeed /= speed;
vel[0] = vel[0] * newspeed;
vel[1] = vel[1] * newspeed;
vel[2] = vel[2] * newspeed;
}
/*
==============
PM_Accelerate
Handles user intended acceleration
==============
*/
static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel )
{
#if 1
// q2 style
int i;
float addspeed, accelspeed, currentspeed;
currentspeed = DotProduct (pm->ps->velocity, wishdir);
addspeed = wishspeed - currentspeed;
if (addspeed <= 0) {
return;
}
accelspeed = accel*pml.frametime*wishspeed;
if (accelspeed > addspeed) {
accelspeed = addspeed;
}
for (i=0 ; i<3 ; i++) {
pm->ps->velocity[i] += accelspeed*wishdir[i];
}
#else
// proper way (avoids strafe jump maxspeed bug), but feels bad
vec3_t wishVelocity;
vec3_t pushDir;
float pushLen;
float canPush;
VectorScale( wishdir, wishspeed, wishVelocity );
VectorSubtract( wishVelocity, pm->ps->velocity, pushDir );
pushLen = VectorNormalize( pushDir );
canPush = accel*pml.frametime*wishspeed;
if (canPush > pushLen) {
canPush = pushLen;
}
VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity );
#endif
}
/*
============
PM_CmdScale
Returns the scale factor to apply to cmd movements
This allows the clients to use axial -127 to 127 values for all directions
without getting a sqrt(2) distortion in speed.
============
*/
static float PM_CmdScale( usercmd_t *cmd )
{
int max;
float total;
float scale;
max = abs( cmd->forwardmove );
if ( abs( cmd->rightmove ) > max )
{
max = abs( cmd->rightmove );
}
if ( abs( cmd->upmove ) > max )
{
max = abs( cmd->upmove );
}
if ( !max )
{
return 0;
}
total = sqrt( cmd->forwardmove * cmd->forwardmove
+ cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove );
scale = (float)pm->ps->speed * max / ( 127.0 * total );
return scale;
}
/*
================
PM_SetMovementDir
Determine the rotation of the legs reletive
to the facing dir
================
*/
static void PM_SetMovementDir( void )
{
if ( pm->cmd.forwardmove || pm->cmd.rightmove )
{
if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 )
{
pm->ps->movementDir = 0;
}
else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 )
{
pm->ps->movementDir = 1;
}
else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 )
{
pm->ps->movementDir = 2;
}
else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 )
{
pm->ps->movementDir = 3;
}
else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 )
{
pm->ps->movementDir = 4;
}
else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 )
{
pm->ps->movementDir = 5;
}
else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 )
{
pm->ps->movementDir = 6;
}
else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 )
{
pm->ps->movementDir = 7;
}
}
else
{
// if they aren't actively going directly sideways,
// change the animation to the diagonal so they
// don't stop too crooked
if ( pm->ps->movementDir == 2 )
{
pm->ps->movementDir = 1;
}
else if ( pm->ps->movementDir == 6 )
{
pm->ps->movementDir = 7;
}
}
}
/*
=============
PM_CheckJump
=============
*/
static qboolean PM_CheckJump( void )
{
if ( pm->ps->pm_time )
{
return qfalse;
}
// Cant jump when ducked
if ( pm->ps->pm_flags & PMF_DUCKED )
{
return qfalse;
}
// don't allow jump until all buttons are up
if ( pm->ps->pm_flags & PMF_RESPAWNED )
{
return qfalse;
}
// not holding jump
if ( pm->cmd.upmove < 10 )
{
return qfalse;
}
// must wait for jump to be released
if ( pm->ps->pm_debounce & PMD_JUMP )
{
// clear upmove so cmdscale doesn't lower running speed
pm->cmd.upmove = 0;
return qfalse;
}
pml.groundPlane = qfalse; // jumping away
pml.walking = qfalse;
pm->ps->pm_debounce |= PMD_JUMP;
pm->ps->pm_flags |= PMF_JUMPING;
pm->ps->groundEntityNum = ENTITYNUM_NONE;
if ( pm->cmd.forwardmove >= 0 )
{
PM_ForceLegsAnim( pm->ps, LEGS_JUMP );
}
else
{
PM_ForceLegsAnim( pm->ps, LEGS_JUMP_BACK );
pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
}
// Special case for ladders
if ( pm->ps->ladder != -1 )
{
vec3_t forward;
VectorCopy ( pm_ladders[pm->ps->ladder].fwd, forward );
forward[2] = 0;
VectorNormalize ( forward );
VectorMA ( pm->ps->velocity, -50, forward, pm->ps->velocity );
pm->ps->pm_flags |= PMF_LADDER_JUMP;
return qtrue;
}
pm->ps->velocity[2] = JUMP_VELOCITY;
return qtrue;
}
/*
=============
PM_CheckWaterJump
=============
*/
static qboolean PM_CheckWaterJump( void )
{
vec3_t spot;
int cont;
vec3_t flatforward;
if (pm->ps->pm_time)
{
return qfalse;
}
// check for water jump
if ( pm->waterlevel != 2 )
{
return qfalse;
}
flatforward[0] = pml.forward[0];
flatforward[1] = pml.forward[1];
flatforward[2] = 0;
VectorNormalize (flatforward);
VectorMA (pm->ps->origin, 30, flatforward, spot);
spot[2] += 4;
cont = pm->pointcontents (spot, pm->ps->clientNum );
if ( !(cont & CONTENTS_SOLID) )
{
return qfalse;
}
spot[2] += 16;
cont = pm->pointcontents (spot, pm->ps->clientNum );
if ( cont )
{
return qfalse;
}
// jump out of water
VectorScale (pml.forward, 200, pm->ps->velocity);
pm->ps->velocity[2] = 350;
pm->ps->pm_flags |= PMF_TIME_WATERJUMP;
pm->ps->pm_time = 2000;
return qtrue;
}
/*
===================
PM_WaterJumpMove
Flying out of the water
===================
*/
static void PM_WaterJumpMove( void )
{
// waterjump has no control, but falls
PM_StepSlideMove( qtrue );
pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime;
if (pm->ps->velocity[2] < 0)
{
// cancel as soon as we are falling down again
pm->ps->pm_flags &= ~PMF_ALL_TIMES;
pm->ps->pm_time = 0;
}
}
/*
===================
PM_WaterMove
===================
*/
static void PM_WaterMove( void )
{
int i;
vec3_t wishvel;
float wishspeed;
vec3_t wishdir;
float scale;
float vel;
if ( PM_CheckWaterJump() )
{
PM_WaterJumpMove();
return;
}
#if 0
// jump = head for surface
if ( pm->cmd.upmove >= 10 ) {
if (pm->ps->velocity[2] > -300) {
if ( pm->watertype == CONTENTS_WATER ) {
pm->ps->velocity[2] = 100;
} else {
pm->ps->velocity[2] = 50;
}
}
}
#endif
PM_Friction ();
scale = PM_CmdScale( &pm->cmd );
//
// user intentions
//
if ( !scale )
{
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = -60; // sink towards bottom
}
else
{
for (i=0 ; i<3 ; i++)
{
wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove;
}
wishvel[2] += scale * pm->cmd.upmove;
}
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
if ( wishspeed > pm->ps->speed * pm_swimScale )
{
wishspeed = pm->ps->speed * pm_swimScale;
}
PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate);
// make sure we can go up slopes easily under water
if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 )
{
vel = VectorLength(pm->ps->velocity);
// slide along the ground plane
PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal,
pm->ps->velocity, OVERCLIP );
VectorNormalize(pm->ps->velocity);
VectorScale(pm->ps->velocity, vel, pm->ps->velocity);
}
PM_SlideMove( qfalse );
}
/*
===================
PM_FlyMove
===================
*/
static void PM_FlyMove( void )
{
int i;
vec3_t wishvel;
float wishspeed;
vec3_t wishdir;
float scale;
// normal slowdown
PM_Friction ();
scale = PM_CmdScale( &pm->cmd );
//
// user intentions
//
if ( !scale )
{
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = 0;
}
else
{
for (i=0 ; i<3 ; i++)
{
wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove;
}
wishvel[2] += scale * pm->cmd.upmove;
}
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate);
PM_StepSlideMove( qfalse );
}
/*
===================
PM_AirMove
===================
*/
static void PM_AirMove( void )
{
int i;
vec3_t wishvel;
float fmove, smove;
vec3_t wishdir;
float wishspeed;
float scale;
usercmd_t cmd;
PM_Friction();
fmove = pm->cmd.forwardmove;
smove = pm->cmd.rightmove;
cmd = pm->cmd;
scale = PM_CmdScale( &cmd );
// set the movementDir so clients can rotate the legs for strafing
PM_SetMovementDir();
// project moves down to flat plane
pml.forward[2] = 0;
pml.right[2] = 0;
VectorNormalize (pml.forward);
VectorNormalize (pml.right);
for ( i = 0 ; i < 2 ; i++ ) {
wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
}
wishvel[2] = 0;
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
wishspeed *= scale;
// not on ground, so little effect on velocity
PM_Accelerate (wishdir, wishspeed, pm_airaccelerate);
// we may have a ground plane that is very steep, even
// though we don't have a groundentity
// slide along the steep plane
if ( pml.groundPlane ) {
PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal,
pm->ps->velocity, OVERCLIP );
}
PM_StepSlideMove ( qtrue );
}
/*
===================
PM_WalkMove
===================
*/
static void PM_WalkMove( void ) {
int i;
vec3_t wishvel;
float fmove, smove;
vec3_t wishdir;
float wishspeed;
float scale;
usercmd_t cmd;
float accelerate;
float vel;
if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 )
{
// begin swimming
PM_WaterMove();
return;
}
if ( PM_CheckJump () )
{
PM_BeginZoomOut ( );
// jumped away
if ( pm->waterlevel > 1 )
{
PM_WaterMove();
}
else
{
PM_AirMove();
}
return;
}
PM_Friction ();
fmove = pm->cmd.forwardmove;
smove = pm->cmd.rightmove;
cmd = pm->cmd;
scale = PM_CmdScale( &cmd );
// set the movementDir so clients can rotate the legs for strafing
PM_SetMovementDir();
// project moves down to flat plane
pml.forward[2] = 0;
pml.right[2] = 0;
// project the forward and right directions onto the ground plane
PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP );
PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP );
//
VectorNormalize (pml.forward);
VectorNormalize (pml.right);
for ( i = 0 ; i < 3 ; i++ ) {
wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
}
// when going up or down slopes the wish velocity should Not be zero
// wishvel[2] = 0;
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
wishspeed *= scale;
// clamp the speed lower if ducking
if ( pm->ps->pm_flags & PMF_DUCKED ) {
if ( wishspeed > pm->ps->speed * pm_duckScale ) {
wishspeed = pm->ps->speed * pm_duckScale;
}
}
// clamp the speed lower if wading or walking on the bottom
if ( pm->waterlevel && !(pm->watertype & CONTENTS_LADDER) ) {
float waterScale;
waterScale = pm->waterlevel / 3.0;
waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale;
if ( wishspeed > pm->ps->speed * waterScale ) {
wishspeed = pm->ps->speed * waterScale;
}
}
// when a player gets hit, they temporarily lose
// full control, which allows them to be moved a bit
if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK )
{
accelerate = pm_airaccelerate;
}
else
{
accelerate = pm_accelerate;
// Accelerate faster when ducked
if ( pm->ps->pm_flags & PMF_DUCKED )
{
accelerate *= 2;
}
}
PM_Accelerate (wishdir, wishspeed, accelerate);
if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK )
{
pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime;
}
vel = VectorLength(pm->ps->velocity);
// slide along the ground plane
PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal,
pm->ps->velocity, OVERCLIP );
// don't decrease velocity when going up or down a slope
VectorNormalize(pm->ps->velocity);
VectorScale(pm->ps->velocity, vel, pm->ps->velocity);
// don't do anything if standing still
if (!pm->ps->velocity[0] && !pm->ps->velocity[1])
{
return;
}
PM_StepSlideMove( qfalse );
}
/*
===================
PM_LadderMove
===================
*/
static void PM_LadderMove ( void )
{
float wishspeed;
float scale;
vec3_t wishdir;
vec3_t wishvel;
float accelerate;
if ( PM_CheckJump () )
{
return;
}
PM_Friction ();
scale = PM_CmdScale( &pm->cmd );
accelerate = pm_accelerate;
//
// user intentions
//
if ( !scale )
{
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = 0;
}
else
{
int i;
VectorNormalize ( pm_ladders[pm->ps->ladder].fwd );
VectorNormalize ( pml.forward );
if ( !pml.groundPlane )
{
vec3_t mins;
vec3_t maxs;
vec3_t offset = {1, 1, 1};
trace_t tr;
VectorCopy ( pm->mins, mins );
VectorSubtract ( mins, offset, mins );
VectorCopy ( pm->maxs, maxs );
VectorAdd ( maxs, offset, maxs );
pm->trace ( &tr, pm->ps->origin, mins, maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask );
if ( tr.fraction == 1.0f || !tr.startsolid )
{
if ( pm->cmd.forwardmove >= 0 )
{
VectorAdd ( pml.forward, pm_ladders[pm->ps->ladder].fwd, pml.forward );
}
else
{
VectorSubtract ( pml.forward, pm_ladders[pm->ps->ladder].fwd, pml.forward );
}
}
}
for ( i=0 ; i<3 ; i++ )
{
wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove * pm_ladderScale;
}
// Duck down ladders
if ( pm->cmd.upmove < 0 )
{
wishvel[2] += scale * pm->cmd.upmove;
}
}
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
PM_Accelerate( wishdir, wishspeed, accelerate );
PM_StepSlideMove( qfalse );
}
/*
==============
PM_DeadMove
==============
*/
static void PM_DeadMove( void ) {
float forward;
if ( !pml.walking ) {
return;
}
// extra friction
forward = VectorLength (pm->ps->velocity);
forward -= 20;
if ( forward <= 0 ) {
VectorClear (pm->ps->velocity);
} else {
VectorNormalize (pm->ps->velocity);
VectorScale (pm->ps->velocity, forward, pm->ps->velocity);
}
}
/*
===============
PM_NoclipMove
===============
*/
static void PM_NoclipMove( void ) {
float speed, drop, friction, control, newspeed;
int i;
vec3_t wishvel;
float fmove, smove;
vec3_t wishdir;
float wishspeed;
float scale;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
// friction
speed = VectorLength (pm->ps->velocity);
if (speed < 1)
{
VectorCopy (vec3_origin, pm->ps->velocity);
}
else
{
drop = 0;
friction = pm_friction*1.5; // extra friction
control = speed < pm_stopspeed ? pm_stopspeed : speed;
drop += control*friction*pml.frametime;
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0)
newspeed = 0;
newspeed /= speed;
VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity);
}
// accelerate
scale = PM_CmdScale( &pm->cmd );
scale *= 1.5f;
fmove = pm->cmd.forwardmove;
smove = pm->cmd.rightmove;
for (i=0 ; i<3 ; i++)
wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove;
wishvel[2] += pm->cmd.upmove;
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
wishspeed *= scale;
PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate );
// move
VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin);
}
//============================================================================
/*
==============
PM_Use
Generates a use event
==============
*/
#define USE_DELAY 2000
void PM_Use( void )
{
int useTime = 0;
// don't allow attack until all buttons are up
if ( pm->ps->pm_flags & PMF_RESPAWNED )
{
return;
}
// ignore if not a normal player
if ( pm->ps->pm_type != PM_NORMAL )
{
return;
}
// check for dead player
if ( pm->ps->stats[STAT_HEALTH] <= 0 )
{
pm->ps->weapon = WP_NONE;
return;
}
// Cant use so dont bother letting them try
if ( !(pm->ps->pm_flags & PMF_CAN_USE ) || !(pm->cmd.buttons & BUTTON_USE ) )
{
if ( pm->ps->stats[STAT_USEWEAPONDROP] )
{
pm->ps->stats[STAT_USEWEAPONDROP] -= pml.msec;
if ( pm->ps->stats[STAT_USEWEAPONDROP] < 0 )
{
pm->ps->stats[STAT_USEWEAPONDROP] = 0;
}
}
if ( pm->ps->pm_debounce & PMD_USE )
{
pm->ps->pm_debounce &= ~PMD_USE;
pm->ps->stats[STAT_USETIME] = 0;
}
return;
}
pm->ps->pm_debounce |= PMD_USE;
useTime = pm->ps->stats[STAT_USETIME_MAX];
if ( useTime )
{
int elapsedTime = pm->ps->stats[STAT_USETIME];
if ( elapsedTime < useTime )
{
elapsedTime += pml.msec;
}
pm->ps->stats[STAT_USEWEAPONDROP] += pml.msec;
if ( pm->ps->stats[STAT_USEWEAPONDROP] > 300 )
{
pm->ps->stats[STAT_USEWEAPONDROP] = 300;
}
if ( elapsedTime >= useTime )
{
pm->ps->stats[STAT_USETIME] = 0;
PM_AddEvent ( EV_USE );
}
else
{
pm->ps->stats[STAT_USETIME] = elapsedTime;
}
return;
}
if ( !(pm->ps->pm_debounce & PMD_USE) )
{
PM_AddEvent ( EV_USE );
}
}
/*
================
PM_FootstepForSurface
Returns an event number apropriate for the groundsurface
================
*/
static void PM_FootstepForSurface( void )
{
if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS )
{
return;
}
PM_AddEventWithParm(EV_FOOTSTEP, pml.groundTrace.surfaceFlags & MATERIAL_MASK);
}
/*
=================
PM_CrashLand
Check for hard landings that generate sound events
=================
*/
int minDeltaForDmg = 97;
int minDeltaForSmallPainSound = 30;
int minDeltaForBigPainSound = 97;
int minDeltaForSlowDown = 17;
static void PM_CrashLand( int impactMaterial, vec3_t impactNormal )
{
float delta;
float dist;
float vel, acc;
float t;
float a, b, c, den;
float f;
int scaleDelta;
qboolean jumped;
static vec3_t up = {0,0,1};
// were they juping?
jumped = (pm->ps->pm_flags&PMF_JUMPING) ? qtrue : qfalse;
pm->ps->pm_flags &= (~PMF_LADDER_JUMP);
pm->ps->pm_flags &= (~PMF_JUMPING);
// calculate the exact velocity on landing
dist = pm->ps->origin[2] - pml.previous_origin[2];
vel = pml.previous_velocity[2];
acc = -pm->ps->gravity;
a = acc / 2;
b = vel;
c = -dist;
den = b * b - 4 * a * c;
if ( den < 0 )
{
return;
}
t = (-b - sqrt( den ) ) / ( 2 * a );
delta = vel + t * acc;
delta = delta * delta * 0.000275f;
switch ( pm->waterlevel )
{
case 3:
// never take falling damage if completely underwater
return;
// reduce falling damage if there is standing water
case 2:
delta *= 0.25;
break;
// reduce falling damage if there is standing water
case 1:
delta *= 0.5;
break;
}
// Scale the delta based on the normal of the plane we hit
f = DotProduct ( up, impactNormal );
if ( f < .25 )
{
delta *= f;
}
// Just hit the ground, no more z velocity or we could bounce
pm->ps->velocity[2] = 0;
// start footstep cycle over if it wasnt a little jump
if ( delta > minDeltaForSlowDown )
{
pm->ps->bobCycle = 0;
}
if ( delta < 1 )
{
return;
}
else if ( jumped && delta >= minDeltaForSlowDown )
{
// Cut their forward velocity, this pretty much eliminates strafe jumping
pm->ps->velocity[0] *= 0.25f;
pm->ps->velocity[1] *= 0.25f;
pm->ps->pm_time = 500;
}
// create a local entity event to play the sound
// SURF_NODAMAGE is used for bounce pads where you don't ever
// want to take damage or play a crunch sound
scaleDelta = (int)delta;
if (scaleDelta > 100 + minDeltaForDmg)
{
scaleDelta = 100 + minDeltaForDmg;
}
scaleDelta -= minDeltaForDmg;
if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) )
{
if ( delta > minDeltaForBigPainSound )
{
PM_AddEventWithParm(EV_FALL_FAR, scaleDelta | ((impactMaterial & MATERIAL_MASK)<<8));
}
else if ( delta > minDeltaForDmg )
{
// this is a pain grunt, so don't play it if dead
if ( pm->ps->stats[STAT_HEALTH] > 0 )
{
PM_AddEventWithParm(EV_FALL_MEDIUM, scaleDelta | ((impactMaterial & MATERIAL_MASK)<<8));
}
}
else if ( delta > minDeltaForSlowDown )
{
PM_AddEventWithParm(EV_FALL_SHORT, impactMaterial & MATERIAL_MASK );
}
else if ( delta > 10 && (pm->cmd.buttons & BUTTON_WALKING) )
{
PM_FootstepForSurface();
}
}
}
/*
=============
PM_CorrectAllSolid
=============
*/
static int PM_CorrectAllSolid( trace_t *trace )
{
int i, j, k;
vec3_t point;
if ( pm->debugLevel )
{
Com_Printf("%i:allsolid\n", c_pmove);
}
// jitter around
for (i = -1; i <= 1; i++) {
for (j = -1; j <= 1; j++) {
for (k = -1; k <= 1; k++) {
VectorCopy(pm->ps->origin, point);
point[0] += (float) i;
point[1] += (float) j;
point[2] += (float) k;
pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask);
if ( !trace->allsolid ) {
point[0] = pm->ps->origin[0];
point[1] = pm->ps->origin[1];
point[2] = pm->ps->origin[2] - 0.25;
pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask);
pml.groundTrace = *trace;
return qtrue;
}
}
}
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
return qfalse;
}
/*
=============
PM_GroundTraceMissed
The ground trace didn't hit a surface, so we are in freefall
=============
*/
static void PM_GroundTraceMissed( void )
{
trace_t trace;
vec3_t point;
if ( pm->ps->groundEntityNum != ENTITYNUM_NONE )
{
// we just transitioned into freefall
if ( pm->debugLevel )
{
Com_Printf("%i:lift\n", c_pmove);
}
// if they aren't in a jumping animation and the ground is a ways away, force into it
// if we didn't do the trace, the player would be backflipping down staircases
VectorCopy( pm->ps->origin, point );
point[2] -= 64;
pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask);
if ( trace.fraction == 1.0 )
{
if ( pm->cmd.forwardmove >= 0 )
{
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
else
{
pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
}
}
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
}
/*
=============
PM_GroundTrace
=============
*/
static void PM_GroundTrace( void )
{
vec3_t point;
trace_t trace;
float minWalkNormal;
point[0] = pm->ps->origin[0];
point[1] = pm->ps->origin[1];
point[2] = pm->ps->origin[2] - 0.25;
pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask);
pml.groundTrace = trace;
// When stuck to antoher player set a flag to let the trigger code know so it can unstick the player
if ( (trace.allsolid || trace.startsolid) && trace.entityNum < MAX_CLIENTS )
{
pm->ps->pm_flags |= PMF_SIAMESETWINS;
}
// if the trace didn't hit anything, we are in free fall
if ( trace.fraction == 1.0 ) {
PM_GroundTraceMissed();
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
// check if getting thrown off the ground
if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) {
if ( pm->debugLevel ) {
Com_Printf("%i:kickoff\n", c_pmove);
}
// go into jump animation
if ( pm->cmd.forwardmove >= 0 ) {
PM_ForceLegsAnim( pm->ps, LEGS_JUMP );
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
} else {
PM_ForceLegsAnim( pm->ps, LEGS_JUMP_BACK );
pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
// slopes that are too steep will not be considered onground
if ( trace.contents & CONTENTS_TERRAIN )
{
minWalkNormal = MIN_WALK_NORMAL_TERRAIN;
}
else
{
minWalkNormal = MIN_WALK_NORMAL;
}
if ( trace.plane.normal[2] < minWalkNormal )
{
if ( pm->debugLevel )
{
Com_Printf("%i:steep\n", c_pmove);
}
// FIXME: if they can't slide down the slope, let them
// walk (sharp crevices)
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qtrue;
pml.walking = qfalse;
return;
}
pml.groundPlane = qtrue;
pml.walking = qtrue;
// hitting solid ground will end a waterjump
if (pm->ps->pm_flags & PMF_TIME_WATERJUMP)
{
pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND);
pm->ps->pm_time = 0;
}
if ( pm->ps->groundEntityNum == ENTITYNUM_NONE)
{
// just hit the ground
// if ((pml.groundTrace.contents & CONTENTS_TERRAIN) && pml.previous_velocity[2] > -200)
// {
// }
// else
{
if ( pm->debugLevel )
{
Com_Printf("%i:Land\n", c_pmove);
}
PM_CrashLand(trace.surfaceFlags & MATERIAL_MASK, trace.plane.normal );
// don't do landing time if we were just going down a slope
if ( pml.previous_velocity[2] < -200 )
{
// don't allow another jump for a little while
pm->ps->pm_flags |= PMF_TIME_LAND;
pm->ps->pm_time = 250;
}
}
}
pm->ps->groundEntityNum = trace.entityNum;
// don't reset the z velocity for slopes
// pm->ps->velocity[2] = 0;
PM_AddTouchEnt( trace.entityNum );
}
/*
=============
PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving
=============
*/
static void PM_SetWaterLevel( void )
{
vec3_t point;
int cont;
int sample1;
int sample2;
//
// get waterlevel, accounting for ducking
//
pm->waterlevel = 0;
pm->watertype = 0;
point[0] = pm->ps->origin[0];
point[1] = pm->ps->origin[1];
point[2] = pm->ps->origin[2] + pm->mins[2];
cont = pm->pointcontents( point, pm->ps->clientNum );
// See if we are on a ladder too
if ( !(pm->ps->pm_flags&PMF_LADDER_JUMP) && (cont & CONTENTS_LADDER) )
{
if ( pm->ps->ladder == -1 )
{
pm->ps->ladder = BG_FindLadder ( pm->ps->origin );
}
pm->ps->pm_flags |= PMF_LADDER;
}
else
{
pm->ps->ladder = -1;
pm->ps->pm_flags &= ~PMF_LADDER;
}
if ( cont & MASK_WATER )
{
sample2 = pm->ps->viewheight - MINS_Z;
sample1 = sample2 / 2;
pm->watertype = cont;
pm->waterlevel = 1;
point[2] = pm->ps->origin[2] + MINS_Z + sample1;
cont = pm->pointcontents (point, pm->ps->clientNum );
if ( cont & MASK_WATER )
{
pm->waterlevel = 2;
point[2] = pm->ps->origin[2] + MINS_Z + sample2;
cont = pm->pointcontents (point, pm->ps->clientNum );
if ( cont & MASK_WATER )
{
pm->waterlevel = 3;
}
}
}
}
/*
==============
PM_CheckCrouchJump
Handles crouch jumping
==============
*/
static void PM_CheckCrouchJump ( void )
{
// Already crouch jumping so check to see if its over
if ( pm->ps->pm_flags & PMF_CROUCH_JUMP )
{
// If they are on the ground the crouch jump is over
if ( pml.groundPlane )
{
pm->ps->pm_flags &= ~PMF_CROUCH_JUMP;
}
}
else
{
// If not on the ground and still heading up then crouch jump is possible.
if ( !pml.groundPlane && (pm->ps->pm_flags & PMF_JUMPING) && pm->cmd.upmove < 0 )
{
pm->ps->pm_flags |= PMF_CROUCH_JUMP;
}
}
// Check again if still crouch jumping and if so alter the view height
// so the client doesnt look like they are ducking in mid air
if ( (pm->ps->pm_flags & PMF_CROUCH_JUMP) && (pm->ps->pm_flags & PMF_JUMPING) )
{
// If still ducked look for windows
if ( pm->ps->pm_flags & PMF_DUCKED )
{
trace_t trace;
vec3_t maxs;
VectorCopy ( pm->maxs, maxs );
maxs[2] = DEFAULT_PLAYER_Z_MAX;
pm->trace (&trace, pm->ps->origin, pm->mins, maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask );
if ( !(trace.allsolid || trace.startsolid) )
{
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
}
}
}
}
/*
==============
PM_CheckDuck
Sets mins, maxs, and pm->ps->viewheight
==============
*/
static void PM_CheckDuck (void)
{
trace_t trace;
if ( (pm == 0) || (pm->ps == 0) )
{
return;
}
pm->mins[0] = -15;
pm->mins[1] = -15;
pm->maxs[0] = 15;
pm->maxs[1] = 15;
pm->mins[2] = MINS_Z;
if (pm->ps->pm_type == PM_DEAD)
{
pm->maxs[2] = DEAD_PLAYER_Z_MAX;
pm->ps->viewheight = DEAD_VIEWHEIGHT;
return;
}
// duck or prone
if (pm->cmd.upmove < 0)
{
// assume ducked at first
pm->ps->pm_flags |= PMF_DUCKED;
}
else
{ // stand up if possible
if ( pm->ps->pm_flags & PMF_DUCKED )
{
pm->maxs[2] = DEFAULT_PLAYER_Z_MAX;
pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask );
if (!trace.allsolid)
{
pm->ps->pm_flags &= ~PMF_DUCKED;
}
}
}
if ( (pm->ps->pm_flags & PMF_DUCKED) )
{
pm->maxs[2] = CROUCH_PLAYER_Z_MAX;
pm->ps->viewheight = CROUCH_VIEWHEIGHT;
}
else
{
pm->maxs[2] = DEFAULT_PLAYER_Z_MAX;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
}
}
/*
===============
PM_Footsteps
===============
*/
static void PM_Footsteps( void )
{
float bobmove;
int old;
qboolean footstep;
//
// calculate speed and cycle to be used for
// all cyclic walking effects
//
pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0]
+ pm->ps->velocity[1] * pm->ps->velocity[1] );
if ( pm->ps->groundEntityNum == ENTITYNUM_NONE )
{
if ( pm->ps->pm_flags & PMF_LADDER )
{
}
else
{
// airborne leaves position in cycle intact, but doesn't advance
if ( pm->waterlevel > 1 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_SWIM );
}
return;
}
}
// if not trying to move
if ( !pm->cmd.forwardmove && !pm->cmd.rightmove )
{
if ( pm->xyspeed < 5 )
{
pm->ps->bobCycle = 0; // start at beginning of cycle again
if ( pm->ps->pm_flags & PMF_DUCKED )
{
if ( pm->ps->leanTime - LEAN_TIME < 0 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEAN_CROUCH_LEFT );
}
else if ( pm->ps->leanTime - LEAN_TIME > 0 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEAN_CROUCH_RIGHT );
}
else
{
PM_ContinueLegsAnim( pm->ps, LEGS_IDLE_CROUCH );
}
}
else
{
if ( pm->ps->leanTime - LEAN_TIME < 0 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEAN_LEFT );
}
else if ( pm->ps->leanTime - LEAN_TIME > 0 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEAN_RIGHT );
}
else
{
PM_ContinueLegsAnim( pm->ps, TORSO_IDLE_PISTOL );
}
}
}
return;
}
footstep = qfalse;
if ( (pm->ps->pm_flags & PMF_DUCKED) && (pm->ps->groundEntityNum != ENTITYNUM_NONE ) )
{
bobmove = 0.25; // ducked characters bob much faster
if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN )
{
PM_ContinueLegsAnim( pm->ps, LEGS_WALK_CROUCH_BACK );
}
else
{
if ( pm->ps->leanTime - LEAN_TIME < 0 )
{
if ( pm->cmd.rightmove > 0 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEANLEFT_CROUCH_WALKRIGHT );
}
else
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEANLEFT_CROUCH_WALKLEFT );
}
}
else if ( pm->ps->leanTime - LEAN_TIME > 0 )
{
if ( pm->cmd.rightmove > 0 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEANRIGHT_CROUCH_WALKRIGHT );
}
else
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEANRIGHT_CROUCH_WALKLEFT );
}
}
else
{
PM_ContinueLegsAnim( pm->ps, LEGS_WALK_CROUCH );
}
}
}
else
{
if ( !( pm->cmd.buttons & BUTTON_WALKING ) )
{
PM_BeginZoomOut ( );
bobmove = 0.4f; // faster speeds bob faster
if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN )
{
PM_ContinueLegsAnim( pm->ps, LEGS_RUN_BACK );
}
else
{
PM_ContinueLegsAnim( pm->ps, LEGS_RUN );
}
footstep = qtrue;
}
else
{
bobmove = 0.3f; // walking bobs slow
if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN )
{
PM_ContinueLegsAnim( pm->ps, LEGS_WALK_BACK );
}
else
{
if ( pm->ps->leanTime - LEAN_TIME < 0 )
{
if ( pm->cmd.rightmove > 0 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEANLEFT_WALKRIGHT );
}
else
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEANLEFT_WALKLEFT );
}
}
else if ( pm->ps->leanTime - LEAN_TIME > 0 )
{
if ( pm->cmd.rightmove > 0 )
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEANRIGHT_WALKRIGHT );
}
else
{
PM_ContinueLegsAnim( pm->ps, LEGS_LEANRIGHT_WALKLEFT );
}
}
else
{
PM_ContinueLegsAnim( pm->ps, LEGS_WALK );
}
}
}
}
// check for footstep / splash sounds
old = pm->ps->bobCycle;
pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255;
// if we just crossed a cycle boundary, play an apropriate footstep event
if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 )
{
if ( pm->waterlevel == 0 )
{
// on ground will only play sounds if running
if ( footstep && !pm->noFootsteps )
{
PM_FootstepForSurface();
}
}
else if ( pm->waterlevel == 1 )
{
// splashing
PM_AddEvent( EV_WATER_FOOTSTEP );
}
else if ( pm->waterlevel == 2 )
{
// wading / swimming at surface
PM_AddEvent( EV_SWIM );
}
else if ( pm->waterlevel == 3 )
{
// no sound when completely underwater
}
}
}
/*
==============
PM_WaterEvents
Generate sound events for entering and leaving water
==============
*/
static void PM_WaterEvents( void )
{
if ( !pml.previous_waterlevel && pm->waterlevel == 1 )
{
PM_AddEvent( EV_WATER_TOUCH );
}
else if ( pml.previous_waterlevel <= 1 && pm->waterlevel > 1 )
{
if ( pm->ps->velocity[2] < -100 )
{
PM_AddEvent( EV_WATER_LAND );
}
}
//
// check for head just coming out of water
//
if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) {
PM_AddEvent( EV_WATER_CLEAR );
}
}
/*
===============
PM_SetWeaponTime
===============
*/
static void PM_SetWeaponTime ( TAnimWeapon *aW )
{
TAnimInfoWeapon *aIW;
if(!aW)
{
assert(0);
}
// Weapon model info tells us how long the anim is
aIW = aW->mWeaponModelInfo;
if ( !aIW )
{
return;
}
pm->ps->weaponTime = 1000.0f / aIW->mFPS[0] * aIW->mNumFrames[0] / aIW->mSpeed;
pm->ps->weaponAnimTime = pm->ps->weaponTime;
}
/*
===============
BG_GetWeaponNote
===============
*/
TNoteTrack *BG_GetWeaponNote( playerState_t* ps, int weapon, int anim, int animChoice, int callbackStep )
{
TAnimWeapon *aW;
TAnimInfoWeapon *aIW;
TNoteTrack *note;
int n=0;
note = NULL;
aW=BG_GetInviewAnimFromIndex( weapon, anim&~ANIM_TOGGLEBIT);
if (!aW)
{
return 0;
}
aIW = aW->mWeaponModelInfo;
if ( !aIW )
{
return 0;
}
// Find the callback for the given step
for ( note = aIW->mNoteTracks[ps->weaponAnimIdChoice], n=0; note && n < callbackStep; note = note->mNext, n++ )
{
// Do nothing, loop does it all
}
return(note);
}
/*
==============
PM_CheckWeaponNotes
==============
*/
void PM_CheckWeaponNotes ( void )
{
playerState_t *ps;
TAnimWeapon *aW;
TAnimInfoWeapon *aIW;
TNoteTrack *note;
int step;
int stepTime;
pm->ps->weaponCallbackTime += pml.msec;
// 1st note step.
step = 0;
ps = pm->ps;
aW = BG_GetInviewAnimFromIndex ( ps->weapon, (ps->weaponAnimId&~ANIM_TOGGLEBIT) );
assert ( aW );
if ( !aW )
{
return;
}
// Get the cached weapon model info
aIW = aW->mWeaponModelInfo;
if ( !aIW )
{
return;
}
stepTime = 1000.0f / aIW->mFPS[pm->ps->weaponAnimIdChoice] / aIW->mSpeed;;
note = aIW->mNoteTracks[pm->ps->weaponAnimIdChoice];
while(note)
{
if( pm->ps->weaponCallbackTime >= note->mFrame * stepTime )
{
if(step > pm->ps->weaponCallbackStep)
{
if( !Q_stricmp("fire",note->mNote) || !Q_stricmp("altfire",note->mNote) )
{
if(pm->ps->weaponstate==WEAPON_FIRING)
{
int seed;
// Update the seed
seed = pm->ps->stats[STAT_SEED];
Q_rand ( &seed );
seed = seed & 0xFFFF;
pm->ps->stats[STAT_SEED] = seed;
PM_AddEvent(EV_FIRE_WEAPON);
PM_Weapon_AddInaccuracy(ATTACK_NORMAL);
PM_Weapon_AddKickAngles(weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].maxKickAngles);
}
else if(pm->ps->weaponstate==WEAPON_FIRING_ALT)
{
PM_AddEvent(EV_ALT_FIRE);
PM_Weapon_AddKickAngles(weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].maxKickAngles);
}
}
PM_AddEventWithParm ( EV_WEAPON_CALLBACK,
((step&0xFF) << 24) +
((pm->ps->weaponAnimIdChoice&0xFF)<<16) +
(((ps->weaponAnimId&~ANIM_TOGGLEBIT)&0xFF)<<8) +
pm->ps->weapon);
pm->ps->weaponCallbackStep=step;
}
}
step++;
note=note->mNext;
}
}
/*
===============
PM_SetWeaponAnimChoice
===============
*/
void PM_SetWeaponAnimChoice(TAnimWeapon *aW)
{
TAnimInfoWeapon *aIW;
if(!aW)
{
assert(0);
return;
}
// Get the cached weapon model info
aIW = aW->mWeaponModelInfo;
if ( !aIW )
{
return;
}
pm->ps->weaponAnimIdChoice = rand()%aIW->mNumChoices;
}
/*
===============
PM_GetAnimFromName
===============
*/
TAnimWeapon* PM_GetAnimFromName ( char *animName, playerState_t *ps, int *animIndex )
{
TAnimWeapon *aW=0;
TAnimInfoWeapon *aIW=0;
char tempname[MAX_QPATH];
switch(ps->weapon)
{
case WP_KNIFE:
if(!strcmp(animName,"charge"))
{
// Get 'prefire' anim.
aW=BG_GetInviewAnim(pm->ps->weapon,"prefire",animIndex);
PM_SetWeaponAnimChoice(aW);
}
else if(!strcmp(animName,"fire"))
{
aW=BG_GetInviewAnimFromIndex(ps->weapon,ps->weaponAnimId&~ANIM_TOGGLEBIT);
if((!strcmp(aW->mName,"prefire"))||strstr(aW->mName,"firetrans"))
{
// Get 'fire' anim.
aW=BG_GetInviewAnim(ps->weapon,"fire",animIndex);
PM_SetWeaponAnimChoice(aW);
}
else if(!strcmp(aW->mName,"fire"))
{
// Get 'firetrans' anim. We don't call PM_SetWeaponAnimChoice()
// because the firetrans anims are matched to the fire anims.
aIW=BG_GetInviewModelAnim(ps->weapon,"weaponmodel","fire");
strcpy(tempname,aIW->mTransition[ps->weaponAnimIdChoice]);
aW=BG_GetInviewAnim(ps->weapon,tempname,animIndex);
}
else
{
// Get 'prefire' anim.
aW=BG_GetInviewAnim(pm->ps->weapon,"fire",animIndex);
PM_SetWeaponAnimChoice(aW);
}
}
else if(!strcmp(animName,"fireend"))
{
aW=BG_GetInviewAnim(ps->weapon,"fireend1",animIndex);
PM_SetWeaponAnimChoice(aW);
}
else
{
// Nothing clever about the other sequences.
aW=BG_GetInviewAnim(pm->ps->weapon,animName,animIndex);
PM_SetWeaponAnimChoice(aW);
}
break;
case WP_MM1_GRENADE_LAUNCHER:
case WP_M590_SHOTGUN:
if(!strcmp(animName,"reload"))
{
aW=BG_GetInviewAnimFromIndex(ps->weapon,ps->weaponAnimId&~ANIM_TOGGLEBIT);
if(!strcmp(aW->mName,"reloadbegin")||!strcmp(aW->mName,"reloadshell"))
{
// Get 'reloadshell' anim.
aW=BG_GetInviewAnim(ps->weapon,"reloadshell",animIndex);
}
else
{
// Get 'reloadbegin' anim.
aW=BG_GetInviewAnim(pm->ps->weapon,"reloadbegin",animIndex);
}
}
else if(!strcmp(animName,"reloadend"))
{
// Get 'reloadend' anim.
aW=BG_GetInviewAnim(ps->weapon,"reloadend",animIndex);
}
else
{
// Nothing clever about the other sequences.
aW=BG_GetInviewAnim(pm->ps->weapon,animName,animIndex);
}
PM_SetWeaponAnimChoice(aW);
break;
case WP_M84_GRENADE:
case WP_SMOHG92_GRENADE:
case WP_ANM14_GRENADE:
case WP_M15_GRENADE:
if(!strcmp(animName,"charge"))
{
// Get 'throwbegin' anim.
aW=BG_GetInviewAnim(ps->weapon,"throwbegin",animIndex);
}
else if(!strcmp(animName,"altcharge"))
{
// Get 'throwbegin' anim.
aW=BG_GetInviewAnim(ps->weapon,"altthrowbegin",animIndex);
}
else if(!strcmp(animName,"fire"))
{
// Get 'throwend' anim.
aW=BG_GetInviewAnim(ps->weapon,"throwend",animIndex);
}
else if(!strcmp(animName,"altfire"))
{
// Get 'throwend' anim.
aW=BG_GetInviewAnim(ps->weapon,"altthrowend",animIndex);
}
else
{
// Nothing clever about the other sequences.
aW=BG_GetInviewAnim(pm->ps->weapon,animName,animIndex);
}
PM_SetWeaponAnimChoice(aW);
break;
default:
// Other weapons don't do anything fancy.
aW=BG_GetInviewAnim(ps->weapon,animName,animIndex);
PM_SetWeaponAnimChoice(aW);
break;
}
return(aW);
}
/*
===============
BG_GetWeaponAnim
===============
*/
TAnimWeapon *BG_GetWeaponAnim(int weaponAction,playerState_t *ps,int *animIndex)
{
TAnimWeapon *aW=0;
switch(weaponAction)
{
case WACT_READY:
aW=PM_GetAnimFromName("ready",ps,animIndex);
break;
case WACT_IDLE:
aW=PM_GetAnimFromName("idle",ps,animIndex);
break;
case WACT_FIRE:
aW=PM_GetAnimFromName("fire",ps,animIndex);
break;
case WACT_FIRE_END:
aW=PM_GetAnimFromName("fireend",ps,animIndex);
break;
case WACT_ALTFIRE:
aW=PM_GetAnimFromName("altfire",ps,animIndex);
break;
case WACT_ALTFIRE_END:
aW=PM_GetAnimFromName("altfireend",ps,animIndex);
break;
case WACT_RELOAD:
aW=PM_GetAnimFromName("reload",ps,animIndex);
break;
case WACT_ALTRELOAD:
aW=PM_GetAnimFromName("altreload",ps,animIndex);
break;
case WACT_RELOAD_END:
aW=PM_GetAnimFromName("reloadend",ps,animIndex);
break;
case WACT_PUTAWAY:
aW=PM_GetAnimFromName("done",ps,animIndex);
break;
case WACT_ZOOMIN:
aW=PM_GetAnimFromName("zoomin",ps,animIndex);
break;
case WACT_ZOOMOUT:
aW=PM_GetAnimFromName("zoomout",ps,animIndex);
break;
case WACT_CHARGE:
aW=PM_GetAnimFromName("charge",ps,animIndex);
break;
case WACT_ALTCHARGE:
aW=PM_GetAnimFromName("altcharge",ps,animIndex);
break;
default:
Com_Printf("Anim unknown: %i\n",weaponAction);
break;
}
return(aW);
}
/*
===============
PM_HandleWeaponAction
===============
*/
static void PM_HandleWeaponAction(int weaponAction)
{
TAnimWeapon *aW;
int animIndex;
aW = BG_GetWeaponAnim ( weaponAction, pm->ps, &animIndex );
if(!aW)
{
return;
}
// Reset callback timer. We have to account for any remaining weapontime
// or we could miss off callbacks during sequences that are short in duration.
pm->ps->weaponCallbackTime = -pm->ps->weaponTime;
pm->ps->weaponCallbackStep = -1;
PM_SetWeaponTime(aW);
if((pm->ps->weaponAnimId&~ANIM_TOGGLEBIT)==animIndex)
{
animIndex=pm->ps->weaponAnimId^ANIM_TOGGLEBIT;
}
pm->ps->weaponAnimId = animIndex;
}
/*
===============
PM_BeginZoomIn
===============
*/
static void PM_BeginZoomIn(void)
{
// Reset the zom fov if not rezooming
if ( !(pm->ps->pm_flags & PMF_ZOOM_REZOOM) )
{
pm->ps->zoomFov = 0;
}
pm->ps->weaponstate=WEAPON_ZOOMIN;
PM_HandleWeaponAction(WACT_ZOOMIN);
}
/*
===============
PM_BeginZoomOut
===============
*/
static void PM_BeginZoomOut(void)
{
if ( !(pm->ps->pm_flags & PMF_ZOOMED) )
{
return;
}
if ( !(pm->ps->pm_flags & PMF_ZOOM_DEFER_RELOAD ) )
{
pm->ps->zoomFov = 0;
}
pm->ps->weaponstate=WEAPON_ZOOMOUT;
PM_HandleWeaponAction(WACT_ZOOMOUT);
pm->ps->zoomTime=pm->ps->commandTime;
pm->ps->pm_flags &= ~(PMF_ZOOM_LOCKED|PMF_ZOOM_REZOOM|PMF_ZOOMED);
}
/*
===============
PM_BeginWeaponChange
===============
*/
static void PM_BeginWeaponChange(int weapon)
{
if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS )
{
return;
}
if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) )
{
return;
}
if ( pm->ps->weaponstate == WEAPON_DROPPING )
{
return;
}
// Dont allow switching to the weapon the client is already using
if ( pm->ps->weapon == weapon )
{
PM_AddEvent(EV_CHANGE_WEAPON_CANCELLED );
if ( pm->ps->weaponTime <= 0 )
{
// Add a little delay so it doenst fire because this was caused by
// the menu selection
pm->ps->weaponTime = 150;
}
return;
}
// turn off any kind of zooming when weapon switching.
pm->ps->zoomFov = 0;
pm->ps->zoomTime = 0;
pm->ps->pm_flags &= ~(PMF_ZOOM_FLAGS);
// Clear the weapon time
pm->ps->weaponTime = 0;
pm->ps->weaponFireBurstCount = 0;
pm->ps->weaponAnimTime = 0;
PM_AddEvent(EV_CHANGE_WEAPON);
pm->ps->weaponstate = WEAPON_DROPPING;
if( pm->ps->weapon >= WP_M84_GRENADE && pm->ps->weapon <= WP_M15_GRENADE && pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon] <= 0 )
{
// We don't want to play the 'putaway' anim for the grenades if we are out of grenades!
return;
}
PM_HandleWeaponAction(WACT_PUTAWAY);
PM_StartTorsoAnim( pm->ps, weaponData[pm->ps->weapon].animDrop, pm->ps->weaponAnimTime );
}
/*
===============
PM_FinishWeaponChange
===============
*/
static void PM_FinishWeaponChange( void )
{
int weapon;
weapon = pm->cmd.weapon & ~WP_DELAYED_CHANGE_BIT;
if( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS )
{
weapon = WP_KNIFE;
}
if(!( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) )
{
weapon = WP_KNIFE;
}
PM_AddEvent(EV_READY_WEAPON);
pm->ps->weapon = weapon;
pm->ps->weaponstate = WEAPON_RAISING;
pm->ps->weaponTime = 0;
pm->ps->weaponAnimTime = 0;
// Default to auto (or next available fire mode).
if ( pm->ps->firemode[pm->ps->weapon] == WP_FIREMODE_NONE )
{
pm->ps->firemode[pm->ps->weapon] = BG_FindFireMode ( pm->ps->weapon, ATTACK_NORMAL, WP_FIREMODE_AUTO );
}
// We don't want to play the 'takeout' anim for the grenades if we are about to reload anyway
if( pm->ps->weapon >= WP_M84_GRENADE && pm->ps->weapon <= WP_M15_GRENADE && pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon] <=0 )
{
return;
}
PM_HandleWeaponAction(WACT_READY);
pm->ps->weaponTime = min(500,pm->ps->weaponTime);
PM_StartTorsoAnim( pm->ps, weaponData[pm->ps->weapon].animRaise, pm->ps->weaponAnimTime );
}
/*
==============
PM_LoadShell
Only used for the M590 shotgun.
==============
*/
void PM_LoadShell(void)
{
pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]++;
pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex]--;
}
/*
==============
PM_StartRefillClip
==============
*/
void PM_StartRefillClip ( attackType_t attack )
{
int extra;
assert ( attack >= ATTACK_NORMAL && attack < ATTACK_MAX );
// Sniper rifle should unzoom first before reloading.
if( pm->ps->pm_flags & PMF_ZOOMED )
{
pm->ps->pm_flags |= PMF_ZOOM_DEFER_RELOAD;
PM_BeginZoomOut();
return;
}
pm->ps->weaponstate=(attack==ATTACK_ALTERNATE)?WEAPON_RELOADING_ALT:WEAPON_RELOADING;
pm->ps->weaponFireBurstCount=0;
if(pm->ps->weapon!=WP_KNIFE)
{
// We don't want to play the reload anim for the knife, as it is part of
// the throw anim anyway.
if(attack==ATTACK_ALTERNATE)
{
PM_HandleWeaponAction(WACT_ALTRELOAD);
}
else
{
PM_HandleWeaponAction(WACT_RELOAD);
}
}
if( pm->ps->weapon==WP_M590_SHOTGUN || pm->ps->weapon == WP_MM1_GRENADE_LAUNCHER )
{
PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReloadStart, pm->ps->weaponTime );
return;
}
extra = weaponData[pm->ps->weapon].attack[attack].clipSize;
extra -= pm->ps->clip[attack][pm->ps->weapon];
if(pm->ps->ammo[weaponData[pm->ps->weapon].attack[attack].ammoIndex]<extra)
{
extra=pm->ps->ammo[weaponData[pm->ps->weapon].attack[attack].ammoIndex];
}
pm->ps->clip[attack][pm->ps->weapon]+=extra;
pm->ps->ammo[weaponData[pm->ps->weapon].attack[attack].ammoIndex]-=extra;
// Rezoom the sniper rifle, if it was zoomed before we started reloading.
if(pm->ps->pm_flags & PMF_ZOOM_DEFER_RELOAD )
{
pm->ps->pm_flags |= PMF_ZOOM_REZOOM;
pm->ps->pm_flags &= ~PMF_ZOOM_DEFER_RELOAD;
}
PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReload, pm->ps->weaponTime );
}
/*
==============
PM_EndRefillClip
==============
*/
void PM_EndRefillClip(void)
{
pm->ps->weaponstate=WEAPON_READY;
pm->ps->weaponFireBurstCount = 0;
}
/*
===============
PM_GetAttackButtons
===============
*/
int PM_GetAttackButtons(void)
{
int buttons=pm->cmd.buttons;
// Debounce firemode select button.
if ( buttons & BUTTON_FIREMODE )
{
if(!(pm->ps->pm_debounce & PMD_FIREMODE))
{
pm->ps->pm_debounce |= PMD_FIREMODE;
}
else
{
buttons &= ~BUTTON_FIREMODE;
}
}
else
{
pm->ps->pm_debounce &= ~PMD_FIREMODE;
}
// Handle firebutton in varous firemodes.
switch( pm->ps->firemode[pm->ps->weapon] )
{
case WP_FIREMODE_AUTO:
break;
case WP_FIREMODE_BURST:
// Debounce attack button and disable other buttons during burst fire.
if(buttons&BUTTON_ATTACK)
{
if( !(pm->ps->pm_debounce & PMD_ATTACK))
{
pm->ps->pm_debounce |= PMD_ATTACK;
if(!pm->ps->weaponFireBurstCount)
{
pm->ps->weaponFireBurstCount=3;
}
}
else
{
buttons &= ~BUTTON_ATTACK;
}
}
else
{
pm->ps->pm_debounce &= ~PMD_ATTACK;
}
if(pm->ps->weaponFireBurstCount)
{
buttons|=BUTTON_ATTACK;
buttons&=~BUTTON_ALT_ATTACK;
buttons&=~BUTTON_RELOAD;
buttons&=~BUTTON_ZOOMIN;
buttons&=~BUTTON_ZOOMOUT;
buttons&=~BUTTON_FIREMODE;
}
break;
case WP_FIREMODE_SINGLE:
// Debounce attack button.
if(buttons&BUTTON_ATTACK)
{
if(!(pm->ps->pm_debounce & PMD_ATTACK))
{
pm->ps->pm_debounce |= PMD_ATTACK;
}
else
{
buttons&=~BUTTON_ATTACK;
}
}
else
{
pm->ps->pm_debounce &= ~PMD_ATTACK;
}
break;
}
// Handle single fire alt fire attacks or the sniper zoom
if ( pm->ps->weapon == WP_MSG90A1 || (weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].weaponFlags & (1<<WP_FIREMODE_SINGLE)) )
{
if ( buttons & BUTTON_ALT_ATTACK )
{
if( !(pm->ps->pm_debounce & PMD_ALTATTACK ) )
{
pm->ps->pm_debounce |= PMD_ALTATTACK;
}
else
{
buttons &= ~BUTTON_ALT_ATTACK;
}
}
else
{
pm->ps->pm_debounce &= ~PMD_ALTATTACK;
}
}
return buttons;
}
/*
==============
PM_Weapon_AddInaccuracy
==============
*/
#define ACCURACY_FADERATE 0.2
#define RECOVER_TIME 800
#define RECOVER_TIME_SQ 200000.0
static void PM_Weapon_AddInaccuracy( attackType_t attack )
{
assert ( attack >= ATTACK_NORMAL && attack < ATTACK_MAX );
// Zoomed sniper weapons don't add innacuracy if ont hte ground
if( (pm->ps->pm_flags & PMF_ZOOMED) && pml.groundPlane )
{
return;
}
pm->ps->inaccuracy += weaponData[pm->ps->weapon].attack[attack].inaccuracy;
pm->ps->inaccuracyTime = RECOVER_TIME;
if ( pm->ps->inaccuracy > weaponData[pm->ps->weapon].attack[attack].maxInaccuracy )
pm->ps->inaccuracy = weaponData[pm->ps->weapon].attack[attack].maxInaccuracy;
}
/*
==============
PM_Weapon_UpdateInaccuracy
==============
*/
static void PM_Weapon_UpdateInaccuracy(void)
{
if( pm->ps->inaccuracy <= 0 )
{
pm->ps->inaccuracy = 0;
return;
}
if ( pm->ps->weaponstate == WEAPON_FIRING || pm->ps->weaponstate == WEAPON_FIRING_ALT)
{
pm->ps->inaccuracyTime -= (pml.msec * 3 / 4);
}
else
{
pm->ps->inaccuracyTime -= pml.msec;
}
if ( pm->ps->inaccuracyTime <= 0 )
{
pm->ps->inaccuracy = 0;
}
else
{
// decrement inaccuracy quadraticly to simulate the player recovering slowly at first, then rapidly
int diff = RECOVER_TIME - pm->ps->inaccuracyTime;
pm->ps->inaccuracy *= (1 - (diff*diff)/RECOVER_TIME_SQ);
}
}
/*
==============
PM_Weapon_AddKickAngles
==============
*/
static void PM_Weapon_AddKickAngles(vec3_t kickAngles)
{
// Throw the new kick angles into the integer versions
pm->ps->kickPitch += (int)(kickAngles[PITCH] * 500.0f);
if ( pm->ps->kickPitch > 180000 )
pm->ps->kickPitch = 180000;
}
/*
==============
PM_Weapon_UpdateKickAngles
==============
*/
static void PM_Weapon_UpdateKickAngles(void)
{
// bring our kickAngles back down to zero over time
int i;
float degreesCorrectedPerMSecond = 0.01f;
qboolean firing;
float degreesToCorrect = degreesCorrectedPerMSecond*pml.msec;
vec3_t kickAngles;
// Extract the kick angles from their integer versions
kickAngles[YAW] = kickAngles[ROLL] = 0;
kickAngles[PITCH] = (float)pm->ps->kickPitch / 1000.0f;
firing = qfalse;
// Determine if firing or not
if ( pm->ps->weaponstate == WEAPON_FIRING || pm->ps->weaponstate == WEAPON_FIRING_ALT)
{
firing = qtrue;
}
// If not firing then bring it down alot faster.
if (!firing)
{
// return a whole lot faster if not firing
VectorScale( kickAngles, 1.0f - (0.3f*((float)pml.msec/50.0f)), kickAngles );
for (i = 0; i < 3; i++)
{
if (kickAngles[i] >= 0 && kickAngles[i] < 0.05f)
{
kickAngles[i] = 0.0f;
}
else if (kickAngles[i] <= 0 && kickAngles[i] > -0.05f)
{
kickAngles[i] = 0.0f;
}
}
}
else
{
for (i = 0; i < 3; i++)
{
if (kickAngles[i] > 0)
{
if (kickAngles[i] < degreesToCorrect)
{
kickAngles[i] = 0;
}
else
{
kickAngles[i] -= degreesToCorrect;
}
}
else if (kickAngles[i] < 0)
{
if (kickAngles[i] > -degreesToCorrect)
{
kickAngles[i] = 0;
}
else
{
kickAngles[i] += degreesToCorrect;
}
}
}
}
// Throw the new kick angles into the integer versions
pm->ps->kickPitch = (int)(kickAngles[PITCH] * 1000.0f);
}
/*
==============
PM_Goggles
Handles turning goggles on and off
==============
*/
static void PM_Goggles ( void )
{
// ignore if not a normal player or dead or a ghost
if ( pm->ps->pm_type != PM_NORMAL || pm->ps->stats[STAT_HEALTH] <= 0 || (pm->ps->pm_flags & PMF_GHOST) )
{
return;
}
// See if they even have goggles
if ( pm->ps->stats[STAT_GOGGLES] == GOGGLES_NONE )
{
return;
}
// If the thermal goggles are on and the user has zoomed then turn them off
if ( pm->ps->stats[STAT_GOGGLES] == GOGGLES_INFRARED )
{
// If the player is zoomed then no goggles
if ( pm->ps->pm_flags & PMF_ZOOMED )
{
pm->ps->pm_flags &= ~PMF_GOGGLES_ON;
return;
}
}
// When goggles button isnt down there is nothing to do
if ( !(pm->cmd.buttons & BUTTON_GOGGLES ) )
{
pm->ps->pm_debounce &= ~PMD_GOGGLES;
return;
}
// Dont do anything if the goggles button is being held down
if ( pm->ps->pm_debounce & PMD_GOGGLES )
{
return;
}
// toggle the goggles
pm->ps->pm_debounce |= PMD_GOGGLES;
pm->ps->pm_flags ^= PMF_GOGGLES_ON;
// Play some noise
PM_AddEventWithParm(EV_GOGGLES, (pm->ps->pm_flags&PMF_GOGGLES_ON) ? qtrue : qfalse );
}
/*
==============
PM_WeaponIdle
Handles the progression of the looping idle animation
==============
*/
static void PM_WeaponIdle ( void )
{
pm->ps->weaponAnimTime -= pml.msec;
if ( pm->ps->weaponAnimTime <= 0 )
{
pm->ps->weaponAnimTime = 0;
if ( pm->ps->weaponTime <= 0 )
{
PM_HandleWeaponAction(WACT_IDLE);
pm->ps->weaponTime = 0;
}
}
}
/*
==============
PM_Weapon
Generates weapon events and modifes the weapon counter
==============
*/
static void PM_Weapon( void )
{
int *ammoSource;
int attackButtons;
attackData_t *attackData;
qboolean altFire;
// Get modifed attack buttons.
attackButtons = PM_GetAttackButtons();
// Gun goes away when using something
if ( pm->ps->stats[STAT_USEWEAPONDROP] )
{
return;
}
// don't allow attack until all buttons are up
if ( pm->ps->pm_flags & PMF_RESPAWNED )
{
return;
}
// ignore if not a normal player
if ( pm->ps->pm_type != PM_NORMAL )
{
return;
}
// check for dead player
if ( pm->ps->stats[STAT_HEALTH] <= 0 )
{
pm->ps->weapon = WP_NONE;
return;
}
// Update the weapon inaccuracies and recoil
PM_Weapon_UpdateInaccuracy();
PM_Weapon_UpdateKickAngles();
if( pm->ps->weaponTime > 0 )
{
pm->ps->weaponTime-=pml.msec;
}
// See if we've hit a note?
PM_CheckWeaponNotes();
// Check for weapon change.
if( pm->ps->weaponstate == WEAPON_CHARGING || pm->ps->weaponstate == WEAPON_CHARGING_ALT )
{
// Can't change if weapon is charging.
// Update the grenade timer
if ( pm->ps->grenadeTimer > 0 )
{
pm->ps->grenadeTimer -= pml.msec;
// Force it to go off if the timer has run out
if ( pm->ps->grenadeTimer <= 0 )
{
pm->ps->grenadeTimer = 1;
pm->ps->weaponTime = 0;
attackButtons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);
}
}
}
else if ( pm->ps->weaponstate == WEAPON_SPAWNING )
{
if ( pm->cmd.weapon != pm->ps->weapon )
{
return;
}
pm->ps->weaponstate = WEAPON_READY;
pm->ps->weaponTime = 0;
}
else if( pm->ps->weaponTime <= 0 || pm->ps->weaponstate < WEAPON_RELOADING )
{
// Dont change weapons if this is a weapon selection
if ( (pm->cmd.weapon & WP_DELAYED_CHANGE_BIT) && ((pm->cmd.buttons&BUTTON_ATTACK)||(pm->cmd.buttons&BUTTON_ALT_ATTACK)) )
{
PM_BeginWeaponChange( pm->cmd.weapon & ~WP_DELAYED_CHANGE_BIT );
}
else if ( !(pm->cmd.weapon & WP_DELAYED_CHANGE_BIT) && pm->ps->weapon != pm->cmd.weapon )
{
PM_BeginWeaponChange( pm->cmd.weapon );
}
}
if ( pm->ps->weaponTime > 0 )
{
// Handle the weapons idle animation
PM_WeaponIdle ( );
return;
}
// Reload the alt clip immediately
if( !pm->ps->clip[ATTACK_ALTERNATE][pm->ps->weapon] && pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].ammoIndex] > 0)
{
switch(weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].fireFromClip)
{
case 2:
// Reload altclip.
PM_StartRefillClip( ATTACK_ALTERNATE );
return;
}
}
// Select firemode.
if( attackButtons & BUTTON_FIREMODE )
{
pm->ps->firemode[pm->ps->weapon] = BG_FindFireMode( pm->ps->weapon, ATTACK_NORMAL, pm->ps->firemode[pm->ps->weapon] + 1 );
}
// Decrement burst fire counter if running.
if(pm->ps->weaponFireBurstCount)
{
pm->ps->weaponFireBurstCount--;
}
// change weapon if time
if ( pm->ps->weaponstate == WEAPON_DROPPING )
{
PM_FinishWeaponChange();
return;
}
// Zoom in animation complete... now set zoom parms.
if( pm->ps->weaponstate == WEAPON_ZOOMIN )
{
// The zoomfov may still be remembered from a reload while zooming
if ( !pm->ps->zoomFov )
{
pm->ps->zoomFov = 20;
}
pm->ps->pm_flags |= PMF_ZOOMED;
pm->ps->pm_flags |= PMF_ZOOM_LOCKED;
pm->ps->pm_flags &= ~PMF_ZOOM_REZOOM;
pm->ps->weaponstate=WEAPON_READY;
return;
}
if( pm->ps->weaponstate==WEAPON_CHARGING || pm->ps->weaponstate==WEAPON_CHARGING_ALT )
{
switch(pm->ps->weapon)
{
case WP_M84_GRENADE:
case WP_SMOHG92_GRENADE:
case WP_ANM14_GRENADE:
case WP_M15_GRENADE:
if(!(attackButtons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) )
{
if(pm->ps->weaponstate==WEAPON_CHARGING)
{
if ( pm->ps->grenadeTimer <= 1 )
{
PM_AddEvent(EV_FIRE_WEAPON);
pm->ps->weaponstate=WEAPON_FIRING;
pm->ps->weaponTime = 250;
}
else
{
pm->ps->weaponstate=WEAPON_FIRING;
PM_HandleWeaponAction(WACT_FIRE);
PM_StartTorsoAnim( pm->ps, TORSO_ATTACK_GRENADE_END, pm->ps->weaponTime);
}
}
else
{
if ( pm->ps->grenadeTimer <= 1 )
{
PM_AddEvent(EV_ALT_FIRE);
pm->ps->weaponstate=WEAPON_FIRING;
pm->ps->weaponTime = 250;
}
else
{
pm->ps->weaponstate=WEAPON_FIRING_ALT;
PM_HandleWeaponAction(WACT_ALTFIRE);
PM_StartTorsoAnim( pm->ps, TORSO_ATTACK_GRENADE_END, pm->ps->weaponTime);
}
}
return;
}
else
{
return;
}
break;
default:
break;
}
}
// Special end of fire animation for some weapons. Knife currently the only one.
if( pm->ps->weaponstate == WEAPON_FIRING )
{
switch(pm->ps->weapon)
{
case WP_KNIFE:
if(!(pm->cmd.buttons&BUTTON_ATTACK))
{
PM_HandleWeaponAction(WACT_FIRE_END);
pm->ps->weaponTime=0;
pm->ps->weaponstate=WEAPON_READY;
return;
}
default:
break;
}
}
// ************************************************************************
// Special reload behavior for warious weapons.
// ************************************************************************
if((pm->ps->weaponstate==WEAPON_RELOADING)||(pm->ps->weaponstate==WEAPON_RELOADING_ALT))
{
switch(pm->ps->weapon)
{
case WP_MM1_GRENADE_LAUNCHER:
case WP_M590_SHOTGUN:
// The M590 shotgun has a unique behavior in that when it is reloading,
// the reload can be interrupted by firing. This cancels the reload of
// course.
if(attackButtons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)&&(pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]>0))
{
// Allow normal fire operation and cancel reload. Note: in
// the interests of gameplay, I allow us to go right to the
// fire anim, although it doesn't look as smooth as going to
// reload end and then fire.
PM_HandleWeaponAction(WACT_RELOAD_END);
PM_EndRefillClip();
PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReloadEnd, pm->ps->weaponTime );
return;
}
else if( ( pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon] < weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].clipSize) &&
( pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex]>0))
{
// Load 1 more shell.
pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]++;
pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex]--;
PM_HandleWeaponAction(WACT_RELOAD);
PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReload, pm->ps->weaponTime );
return;
}
else
{
// Weapon fully loaded so play end reload sequence.
PM_HandleWeaponAction(WACT_RELOAD_END);
PM_EndRefillClip();
PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReloadEnd, pm->ps->weaponTime );
return;
}
break;
default:
PM_EndRefillClip();
return;
}
}
else if(pm->ps->pm_flags & PMF_ZOOM_DEFER_RELOAD )
{
PM_StartRefillClip( ATTACK_NORMAL );
return;
}
else if( pm->ps->weapon==WP_KNIFE || (pm->ps->weapon>=WP_RPG7_LAUNCHER && pm->ps->weapon<=WP_M15_GRENADE) )
{
if(pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]<1)
{
// Clip is now empty so see if we have enough ammo to reload this weapon?
if (pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex] > 0)
{
// Yes, so reload it.
PM_StartRefillClip( ATTACK_NORMAL );
return;
}
else
{
if(pm->ps->weapon!=WP_RPG7_LAUNCHER)
{
// Clear grenade type from inventory.
pm->ps->stats[STAT_WEAPONS]&=~(1<<pm->ps->weapon);
// Out of ammo so switch weapons.
PM_AddEventWithParm(EV_NOAMMO, pm->ps->weapon);
return;
}
}
}
}
// Handle zooming in/out for sniper rifle.
if(pm->ps->weapon==WP_MSG90A1)
{
if( (attackButtons&BUTTON_ALT_ATTACK) || (pm->ps->pm_flags & PMF_ZOOM_REZOOM) )
{
if( pm->ps->pm_flags & PMF_ZOOMED )
{
PM_BeginZoomOut();
}
else
{
PM_BeginZoomIn();
}
return;
}
else if( pm->ps->pm_flags & PMF_ZOOMED )
{
if(pm->cmd.buttons&BUTTON_ZOOMIN)
{
pm->ps->zoomFov = pm->ps->zoomFov >> 1;
if ( pm->ps->zoomFov < 5)
{
pm->ps->zoomFov = 5;
}
pm->ps->weaponTime=175;
return;
}
else if(pm->cmd.buttons&BUTTON_ZOOMOUT)
{
pm->ps->zoomFov = pm->ps->zoomFov << 1;
if(pm->ps->zoomFov > 20 )
{
pm->ps->zoomFov = 20;
}
pm->ps->weaponTime=175;
return;
}
}
}
// Reload weapon?
if ( attackButtons & BUTTON_RELOAD )
{
if(pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon] < weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].clipSize)
{
// No, so see if we have enough ammo to reload this weapon?
if (pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex] > 0)
{
// Yes, so reload it.
PM_StartRefillClip( ATTACK_NORMAL );
return;
}
}
}
// Start weapon when either frozen or not shooting
if( pm->ps->stats[STAT_FROZEN] || !(attackButtons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) )
{
// Handle the weapons idle animation
PM_WeaponIdle ( );
pm->ps->weaponstate = WEAPON_READY;
return;
}
// Determine whether to use the alternate or normal attack info
if ( attackButtons & BUTTON_ATTACK )
{
altFire = qfalse;
attackData = &weaponData[pm->ps->weapon].attack[ATTACK_NORMAL];
}
else
{
altFire = qtrue;
attackData = &weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE];
// Cant throw last knife
if( pm->ps->weapon==WP_KNIFE && pm->ps->ammo[attackData->ammoIndex] < 1 )
{
return;
}
}
// Ammo taken from pool, clip or altclip?
switch(attackData->fireFromClip)
{
case 0:
ammoSource = &pm->ps->ammo[attackData->ammoIndex];
break;
default:
case 1:
ammoSource = &pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon];
break;
case 2:
ammoSource = &pm->ps->clip[ATTACK_ALTERNATE][pm->ps->weapon];
break;
}
// Is there enough ammo to fire?
if ( (*ammoSource) - attackData->fireAmount < 0 )
{
// No, so reload if there is more ammo
if( pm->ps->ammo[ attackData->ammoIndex ] > 0 )
{
// If auto reloading is enabled then reload the gun
if ( pm->ps->pm_flags & PMF_AUTORELOAD )
{
switch ( attackData->fireFromClip)
{
case 1:
// Reload clip.
PM_StartRefillClip( ATTACK_NORMAL );
return;
case 2:
// Reload altclip.
PM_StartRefillClip( ATTACK_ALTERNATE );
return;
}
}
}
// Out of ammo, switch weapons if not an alt-attack
else if ( !altFire )
{
PM_AddEventWithParm (EV_NOAMMO, pm->ps->weapon);
}
// Handle the weapons idle animation
PM_WeaponIdle ( );
pm->ps->weaponstate = WEAPON_READY;
return;
}
// This attack doesnt exist
if ( !attackData->damage )
{
// Handle the weapons idle animation
PM_WeaponIdle ( );
pm->ps->weaponstate = WEAPON_READY;
return;
}
// Decrease the ammo
(*ammoSource) -= attackData->fireAmount;
// Handle charging cases
switch(pm->ps->weapon)
{
case WP_KNIFE:
if ( altFire )
{
if( pm->ps->weaponstate != WEAPON_FIRING && pm->ps->weaponstate != WEAPON_FIRING_ALT )
{
PM_HandleWeaponAction(WACT_ALTFIRE);
pm->ps->weaponstate=WEAPON_FIRING_ALT;
}
}
else
{
PM_HandleWeaponAction ( WACT_FIRE );
pm->ps->weaponstate = WEAPON_FIRING;
}
// Play the torso animation associated with the attack
PM_StartTorsoAnim ( pm->ps, attackData->animFire, pm->ps->weaponAnimTime );
break;
case WP_M84_GRENADE:
case WP_SMOHG92_GRENADE:
case WP_ANM14_GRENADE:
case WP_M15_GRENADE:
// Start the detonation timer on the grenade going.
pm->ps->grenadeTimer = attackData->projectileLifetime;
if ( altFire )
{
PM_HandleWeaponAction ( WACT_ALTCHARGE );
pm->ps->weaponstate = WEAPON_CHARGING_ALT;
}
else
{
PM_HandleWeaponAction ( WACT_CHARGE );
pm->ps->weaponstate = WEAPON_CHARGING;
}
PM_StartTorsoAnim( pm->ps, TORSO_ATTACK_GRENADE_START, pm->ps->weaponTime);
break;
default:
if ( altFire )
{
PM_HandleWeaponAction(WACT_ALTFIRE);
pm->ps->weaponstate=WEAPON_FIRING_ALT;
}
else
{
PM_HandleWeaponAction(WACT_FIRE);
pm->ps->weaponstate=WEAPON_FIRING;
}
pm->ps->weaponTime += attackData->fireDelay;
// Play the torso animation associated with the attack
if ( pm->ps->pm_flags & PMF_ZOOMED )
{
PM_StartTorsoAnim ( pm->ps, attackData->animFireZoomed, pm->ps->weaponTime );
}
else
{
PM_StartTorsoAnim ( pm->ps, attackData->animFire, pm->ps->weaponTime );
}
break;
}
}
/*
================
PM_DropTimers
================
*/
static void PM_DropTimers( void )
{
// drop misc timing counter
if ( pm->ps->pm_time )
{
if ( pml.msec >= pm->ps->pm_time )
{
pm->ps->pm_flags &= ~PMF_ALL_TIMES;
pm->ps->pm_time = 0;
}
else
{
pm->ps->pm_time -= pml.msec;
}
}
}
/*
==============
PM_CheckLean
==============
*/
static void PM_CheckLean( void )
{
trace_t trace;
qboolean canlean;
float leanTime;
if ( !pm || !pm->ps )
{
return;
}
// No leaning as a spectator or a ghost
if ( (pm->ps->pm_flags & PMF_GHOST) || pm->ps->pm_type == PM_SPECTATOR )
{
pm->ps->leanTime = LEAN_TIME;
pm->ps->pm_flags &= ~PMF_LEANING;
return;
}
leanTime = (float)pm->ps->leanTime - LEAN_TIME;
canlean = qfalse;
// If their lean button is being pressed and they are on the ground then perform the lean
if( (pm->cmd.buttons & (BUTTON_LEAN_RIGHT|BUTTON_LEAN_LEFT)) && (pm->ps->groundEntityNum != ENTITYNUM_NONE) )
{
vec3_t start, end, right, mins, maxs;
int leanDir;
if( pm->cmd.buttons & BUTTON_LEAN_RIGHT )
{
leanDir = 1;
}
else
{
leanDir = -1;
}
// check for collision
VectorCopy( pm->ps->origin, start );
start[2] += pm->ps->viewheight;
AngleVectors( pm->ps->viewangles, NULL, right, NULL );
VectorSet( mins, -6, -6, -8 );
VectorSet( maxs, 6, 6, 8 );
// since we're moving the camera over
// check that move
VectorMA( start, leanDir * LEAN_OFFSET * 1.25f, right, end );
pm->trace(&trace, start, mins, maxs, end, pm->ps->clientNum, pm->tracemask );
if ( trace.fraction < 0 || trace.fraction >= 1.0f )
{
leanTime += (leanDir * pml.msec);
if( leanTime > LEAN_TIME )
{
leanTime = LEAN_TIME;
}
else if( leanTime < -LEAN_TIME )
{
leanTime = -LEAN_TIME;
}
canlean = qtrue;
}
else if ( (pm->ps->pm_flags&PMF_LEANING) && trace.fraction < 1.0f )
{
int templeanTime = (float)leanDir * (float)LEAN_TIME * trace.fraction;
if ( fabs(templeanTime) < fabs(leanTime) )
{
leanTime = templeanTime;
}
}
}
if ( !canlean )
{
if( leanTime > 0 )
{
leanTime -= pml.msec;
if( leanTime < 0 )
{
leanTime = 0;
}
}
else if ( leanTime < 0 )
{
leanTime += pml.msec;
if( leanTime > 0 )
{
leanTime = 0;
}
}
}
// Set a pm flag for leaning for convienience
if ( leanTime != 0 )
{
pm->ps->pm_flags |= PMF_LEANING;
}
else
{
pm->ps->pm_flags &= ~PMF_LEANING;
}
// The lean time is kept positive by adding in the base lean time
pm->ps->leanTime = (int) (leanTime + LEAN_TIME);
}
/*
================
PM_UpdateViewAngles
This can be used as another entry point when only the viewangles
are being updated isntead of a full move
================
*/
void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd )
{
short temp;
int i;
vec3_t kickAngles;
if ( ps->pm_type == PM_INTERMISSION)
{
return; // no view changes at all
}
if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 )
{
return; // no view changes at all
}
// Extract the kcik angles
kickAngles[PITCH] = ((float)ps->kickPitch / 1000.0f);
kickAngles[YAW] = kickAngles[ROLL] = 0;
// circularly clamp the angles with deltas
for (i=0 ; i<3 ; i++)
{
temp = cmd->angles[i] + ps->delta_angles[i] - ANGLE2SHORT(kickAngles[i]);
if ( i == PITCH )
{
// don't let the player look up or down more than 90 degrees
if ( temp > 16000 )
{
ps->delta_angles[i] = 16000 - (cmd->angles[i]- ANGLE2SHORT(kickAngles[i]));
temp = 16000;
}
else if ( temp < -16000 )
{
ps->delta_angles[i] = -16000 - (cmd->angles[i] - ANGLE2SHORT(kickAngles[i]));
temp = -16000;
}
}
ps->viewangles[i] = SHORT2ANGLE(temp);
}
PM_CheckLean ( );
}
/*
================
PM_AdjustAttackStates
================
*/
void PM_AdjustAttackStates( pmove_t *pm )
{
int ammoOk;
// Check ammo after usage...
if(pm->cmd.buttons & BUTTON_ALT_ATTACK)
{
ammoOk = pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].ammoIndex] - weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].fireAmount;
}
else
{
ammoOk = pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon ] - weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].fireAmount;
}
// Set the firing flag.
if(!(pm->ps->pm_flags & PMF_RESPAWNED) && (pm->ps->pm_type!=PM_INTERMISSION) && (ammoOk>=0))
{
if((pm->cmd.buttons & BUTTON_ALT_ATTACK)||(pm->ps->weaponstate==WEAPON_FIRING_ALT))
{
pm->ps->eFlags |= EF_ALT_FIRING;
}
else if((pm->cmd.buttons & BUTTON_ATTACK)||(pm->ps->weaponstate==WEAPON_FIRING))
{
pm->ps->eFlags &= ~EF_ALT_FIRING;
}
// This flag should always get set, even when alt-firing
pm->ps->eFlags |= EF_FIRING;
}
else
{
// Clear 'em out
pm->ps->eFlags &= ~(EF_FIRING|EF_ALT_FIRING);
}
}
/*
================
PmoveSingle
================
*/
void trap_SnapVector( float *v );
void PmoveSingle (pmove_t *pmove) {
pm = pmove;
// this counter lets us debug movement problems with a journal
// by setting a conditional breakpoint fot the previous frame
c_pmove++;
// clear results
pm->numtouch = 0;
pm->watertype = 0;
pm->waterlevel = 0;
if ( pm->ps->stats[STAT_HEALTH] <= 0 ) {
pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies
}
// make sure walking button is clear if they are running, to avoid
// proxy no-footsteps cheats
if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) {
pm->cmd.buttons &= ~BUTTON_WALKING;
}
// When the lean modifier button is held the strafe left and right keys
// will act as lean left and right
if ( pm->cmd.buttons & BUTTON_LEAN )
{
pm->cmd.buttons &= ~(BUTTON_LEAN_LEFT|BUTTON_LEAN_RIGHT);
// Strafe left = lean left
if ( pm->cmd.rightmove < 0 )
{
pm->cmd.buttons |= BUTTON_LEAN_LEFT;
}
// Strafe right = lean right
else if ( pm->cmd.rightmove > 0 )
{
pm->cmd.buttons |= BUTTON_LEAN_RIGHT;
}
// NO strafing with lean button down
pm->cmd.rightmove = 0;
}
// Cant move when leaning
if ( (pm->cmd.buttons & (BUTTON_LEAN_LEFT|BUTTON_LEAN_RIGHT)))
{
// pm->cmd.rightmove = 0;
pm->cmd.forwardmove = 0;
// Cant jump when leaning
if ( pm->cmd.upmove > 0 )
{
pm->cmd.upmove = 0;
}
}
// Cant run when zoomed, leaning, or using something that takes time
if ( (pm->ps->pm_flags&PMF_ZOOMED) ||
(pm->ps->weaponstate == WEAPON_ZOOMIN) ||
(pm->cmd.buttons & (BUTTON_LEAN_LEFT|BUTTON_LEAN_RIGHT)) ||
(pm->ps->stats[STAT_USEWEAPONDROP]) )
{
if ( pm->cmd.forwardmove > 64 )
{
pm->cmd.forwardmove = 64;
}
else if ( pm->cmd.forwardmove < -64 )
{
pm->cmd.forwardmove = -64;
}
if ( pm->cmd.rightmove > 64 )
{
pm->cmd.rightmove = 64;
}
else if ( pm->cmd.rightmove < -64 )
{
pm->cmd.rightmove = -64;
}
pm->cmd.buttons |= BUTTON_WALKING;
}
// set the talk balloon flag
if ( pm->cmd.buttons & BUTTON_TALK )
{
pm->ps->eFlags |= EF_TALK;
}
else
{
pm->ps->eFlags &= ~EF_TALK;
}
// In certain situations, we may want to control which attack buttons are pressed and what kind of functionality
// is attached to them
PM_AdjustAttackStates( pm );
// clear the respawned flag if attack and use are cleared
if ( pm->ps->stats[STAT_HEALTH] > 0 && !( pm->cmd.buttons & BUTTON_ATTACK) )
{
pm->ps->pm_flags &= ~PMF_RESPAWNED;
}
// if talk button is down, dissallow all other input
// this is to prevent any possible intercept proxy from
// adding fake talk balloons
if ( pmove->cmd.buttons & BUTTON_TALK )
{
// keep the talk button set tho for when the cmd.serverTime > 66 msec
// and the same cmd is used multiple times in Pmove
pmove->cmd.buttons = BUTTON_TALK;
pmove->cmd.forwardmove = 0;
pmove->cmd.rightmove = 0;
pmove->cmd.upmove = 0;
}
// clear all pmove local vars
memset (&pml, 0, sizeof(pml));
// determine the time
pml.msec = pmove->cmd.serverTime - pm->ps->commandTime;
if ( pml.msec < 1 ) {
pml.msec = 1;
} else if ( pml.msec > 200 ) {
pml.msec = 200;
}
pm->ps->commandTime = pmove->cmd.serverTime;
// Frozen?
if ( pm->ps->stats[STAT_FROZEN] )
{
pm->ps->stats[STAT_FROZEN] -= pml.msec;
if ( pm->ps->stats[STAT_FROZEN] < 0 )
{
pm->ps->stats[STAT_FROZEN] = 0;
}
else
{
// pm->cmd.buttons = pm->cmd.buttons & (BUTTON_RELOAD|BUTTON_ATTACK|BUTTON_);
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
}
// save old org in case we get stuck
VectorCopy (pm->ps->origin, pml.previous_origin);
// save old velocity for crashlanding
VectorCopy (pm->ps->velocity, pml.previous_velocity);
pml.frametime = pml.msec * 0.001;
// update the viewangles
PM_UpdateViewAngles( pm->ps, &pm->cmd );
AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up);
if ( pm->cmd.upmove < 10 )
{
// not holding jump
pm->ps->pm_debounce &= ~PMD_JUMP;
}
// decide if backpedaling animations should be used
if ( pm->cmd.forwardmove < 0 ) {
pm->ps->pm_flags |= PMF_BACKWARDS_RUN;
} else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) {
pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN;
}
if ( pm->ps->pm_type >= PM_DEAD ) {
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
if ( pm->ps->pm_type == PM_SPECTATOR )
{
pm->mins[0] = -15;
pm->mins[1] = -15;
pm->maxs[0] = 15;
pm->maxs[1] = 15;
pm->mins[2] = MINS_Z;
pm->maxs[2] = DEFAULT_PLAYER_Z_MAX;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
// PM_FlyMove ();
PM_NoclipMove ( );
PM_DropTimers ();
return;
}
if ( pm->ps->pm_type == PM_NOCLIP ) {
PM_NoclipMove ();
PM_DropTimers ();
return;
}
if (pm->ps->pm_type == PM_FREEZE) {
return; // no movement at all
}
if ( pm->ps->pm_type == PM_INTERMISSION )
{
return; // no movement at all
}
// set mins, maxs, and viewheight
PM_CheckDuck ();
// set groundentity
PM_GroundTrace();
// set watertype, and waterlevel
PM_SetWaterLevel();
pml.previous_waterlevel = pmove->waterlevel;
PM_CheckCrouchJump ( );
if ( pm->ps->pm_type == PM_DEAD ) {
PM_DeadMove ();
}
PM_DropTimers();
if ( pm->ps->pm_flags & PMF_LADDER )
{
PM_LadderMove ( );
}
else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP)
{
PM_WaterJumpMove();
}
else if ( pm->waterlevel > 1 )
{
// swimming
PM_WaterMove();
}
else if ( pml.walking )
{
// walking on ground
PM_WalkMove();
}
else
{
// airborne
PM_AirMove();
}
// set groundentity, watertype, and waterlevel
if(!(VectorCompare(pm->ps->origin,pml.previous_origin)))
{
// If we didn't move at all, then why bother doing this again -MW.
PM_GroundTrace();
}
PM_SetWaterLevel();
// turn goggles on/off
PM_Goggles ( );
// weapons
PM_Weapon();
// Use
PM_Use ( );
// torso animation
PM_TorsoAnimation( pm->ps );
// footstep events / legs animations
PM_Footsteps();
// entering / leaving water splashes
PM_WaterEvents();
// snap some parts of playerstate to save network bandwidth
trap_SnapVector( pm->ps->velocity );
}
/*
================
PM_UpdatePVSOrigin
The pvs of the client is calculated using its own origin and this function
ensures that the origin is set correctly. The main reason for having a PVS
origin is that when leaning your can poke around corners which will in turn
change what you can see.
================
*/
void PM_UpdatePVSOrigin ( pmove_t *pmove )
{
pm = pmove;
// Set a pm flag for leaning and calculate the view origin for the lean
if ( pm->ps->leanTime - LEAN_TIME != 0 )
{
vec3_t right;
float leanOffset;
leanOffset = (float)(pm->ps->leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET;
AngleVectors( pm->ps->viewangles, NULL, right, NULL );
VectorMA( pm->ps->origin, leanOffset, right, pm->ps->pvsOrigin );
}
else
{
VectorCopy ( pm->ps->origin, pm->ps->pvsOrigin );
}
}
/*
================
Pmove
Can be called by either the server or the client
================
*/
void Pmove (pmove_t *pmove) {
int finalTime;
finalTime = pmove->cmd.serverTime;
if ( finalTime < pmove->ps->commandTime ) {
return; // should not happen
}
if ( finalTime > pmove->ps->commandTime + 1000 ) {
pmove->ps->commandTime = finalTime - 1000;
}
pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<<PS_PMOVEFRAMECOUNTBITS)-1);
// chop the move up if it is too long, to prevent framerate
// dependent behavior
while ( pmove->ps->commandTime != finalTime ) {
int msec;
msec = finalTime - pmove->ps->commandTime;
if ( pmove->pmove_fixed ) {
if ( msec > pmove->pmove_msec ) {
msec = pmove->pmove_msec;
}
}
else {
if ( msec > 66 ) {
msec = 66;
}
}
pmove->cmd.serverTime = pmove->ps->commandTime + msec;
PmoveSingle( pmove );
PM_UpdatePVSOrigin ( pmove );
if ( pmove->ps->pm_debounce & PMD_JUMP )
{
pmove->cmd.upmove = 20;
}
}
}
/*
================
BG_AddLadder
Adds a ladder to the ladder list
================
*/
void BG_AddLadder ( vec3_t absmin, vec3_t absmax, vec3_t fwd )
{
pm_ladders[pm_laddercount].origin[0] = (absmax[0] + absmin[0]) / 2;
pm_ladders[pm_laddercount].origin[1] = (absmax[1] + absmin[1]) / 2;
pm_ladders[pm_laddercount].origin[2] = (absmax[2] + absmin[2]) / 2;
VectorCopy ( fwd, pm_ladders[pm_laddercount].fwd );
pm_laddercount++;
}
/*
================
BG_FindLadder
Searches through the ladder list and finds the closes to the given origin
================
*/
int BG_FindLadder ( vec3_t pos )
{
int ladder;
int result;
float dist;
dist = 999999.0f;
result = -1;
for ( ladder = 0; ladder < pm_laddercount; ladder ++ )
{
float dist2 = DistanceSquared( pos, pm_ladders[ladder].origin );
if ( dist2 < dist )
{
vec3_t diff;
VectorSubtract ( pm_ladders[ladder].origin, pos, diff );
diff[2] = 0;
if ( VectorLengthSquared ( diff ) < 500 * 500 )
{
dist = dist2;
result = ladder;
}
}
}
return result;
}