jedioutcast/CODE-mp/game/bg_pmove.c
2013-04-04 17:24:29 -05:00

4653 lines
117 KiB
C

// Copyright (C) 1999-2000 Id Software, Inc.
//
// 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"
#define MAX_WEAPON_CHARGE_TIME 5000
pmove_t *pm;
pml_t pml;
qboolean gPMDoSlowFall = qfalse;
// movement parameters
float pm_stopspeed = 100.0f;
float pm_duckScale = 0.50f;
float pm_swimScale = 0.50f;
float pm_wadeScale = 0.70f;
float pm_accelerate = 10.0f;
float pm_airaccelerate = 1.0f;
float pm_wateraccelerate = 4.0f;
float pm_flyaccelerate = 8.0f;
float pm_friction = 6.0f;
float pm_waterfriction = 1.0f;
float pm_flightfriction = 3.0f;
float pm_spectatorfriction = 5.0f;
int c_pmove = 0;
float forceSpeedLevels[4] =
{
1, //rank 0?
1.25,
1.5,
1.75
};
int forcePowerNeeded[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS] =
{
{ //nothing should be usable at rank 0..
999,//FP_HEAL,//instant
999,//FP_LEVITATION,//hold/duration
999,//FP_SPEED,//duration
999,//FP_PUSH,//hold/duration
999,//FP_PULL,//hold/duration
999,//FP_TELEPATHY,//instant
999,//FP_GRIP,//hold/duration
999,//FP_LIGHTNING,//hold/duration
999,//FP_RAGE,//duration
999,//FP_PROTECT,//duration
999,//FP_ABSORB,//duration
999,//FP_TEAM_HEAL,//instant
999,//FP_TEAM_FORCE,//instant
999,//FP_DRAIN,//hold/duration
999,//FP_SEE,//duration
999,//FP_SABERATTACK,
999,//FP_SABERDEFEND,
999//FP_SABERTHROW,
//NUM_FORCE_POWERS
},
{
65,//FP_HEAL,//instant //was 25, but that was way too little
10,//FP_LEVITATION,//hold/duration
50,//FP_SPEED,//duration
20,//FP_PUSH,//hold/duration
20,//FP_PULL,//hold/duration
20,//FP_TELEPATHY,//instant
30,//FP_GRIP,//hold/duration
1,//FP_LIGHTNING,//hold/duration
50,//FP_RAGE,//duration
50,//FP_PROTECT,//duration
50,//FP_ABSORB,//duration
50,//FP_TEAM_HEAL,//instant
50,//FP_TEAM_FORCE,//instant
20,//FP_DRAIN,//hold/duration
20,//FP_SEE,//duration
0,//FP_SABERATTACK,
2,//FP_SABERDEFEND,
20//FP_SABERTHROW,
//NUM_FORCE_POWERS
},
{
60,//FP_HEAL,//instant
10,//FP_LEVITATION,//hold/duration
50,//FP_SPEED,//duration
20,//FP_PUSH,//hold/duration
20,//FP_PULL,//hold/duration
20,//FP_TELEPATHY,//instant
30,//FP_GRIP,//hold/duration
1,//FP_LIGHTNING,//hold/duration
50,//FP_RAGE,//duration
25,//FP_PROTECT,//duration
25,//FP_ABSORB,//duration
33,//FP_TEAM_HEAL,//instant
33,//FP_TEAM_FORCE,//instant
20,//FP_DRAIN,//hold/duration
20,//FP_SEE,//duration
0,//FP_SABERATTACK,
1,//FP_SABERDEFEND,
20//FP_SABERTHROW,
//NUM_FORCE_POWERS
},
{
50,//FP_HEAL,//instant //You get 5 points of health.. for 50 force points!
10,//FP_LEVITATION,//hold/duration
50,//FP_SPEED,//duration
20,//FP_PUSH,//hold/duration
20,//FP_PULL,//hold/duration
20,//FP_TELEPATHY,//instant
60,//FP_GRIP,//hold/duration
1,//FP_LIGHTNING,//hold/duration
50,//FP_RAGE,//duration
10,//FP_PROTECT,//duration
10,//FP_ABSORB,//duration
25,//FP_TEAM_HEAL,//instant
25,//FP_TEAM_FORCE,//instant
20,//FP_DRAIN,//hold/duration
20,//FP_SEE,//duration
0,//FP_SABERATTACK,
0,//FP_SABERDEFEND,
20//FP_SABERTHROW,
//NUM_FORCE_POWERS
}
};
float forceJumpHeight[NUM_FORCE_POWER_LEVELS] =
{
32,//normal jump (+stepheight+crouchdiff = 66)
96,//(+stepheight+crouchdiff = 130)
192,//(+stepheight+crouchdiff = 226)
384//(+stepheight+crouchdiff = 418)
};
float forceJumpStrength[NUM_FORCE_POWER_LEVELS] =
{
JUMP_VELOCITY,//normal jump
420,
590,
840
};
int PM_GetSaberStance(void)
{
if (pm->ps->fd.saberAnimLevel == FORCE_LEVEL_2)
{ //medium
return BOTH_STAND2;
}
if (pm->ps->fd.saberAnimLevel == FORCE_LEVEL_3)
{ //strong
return BOTH_SABERSLOW_STANCE;
}
//fast
return BOTH_SABERFAST_STANCE;
}
qboolean PM_DoSlowFall(void)
{
if ( ( (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_RIGHT || (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_LEFT ) && pm->ps->legsTimer > 500 )
{
return qtrue;
}
return qfalse;
}
/*
===============
PM_AddEvent
===============
*/
void PM_AddEvent( int newEvent ) {
BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps );
}
void PM_AddEventWithParm( int newEvent, int parm )
{
BG_AddPredictableEventToPlayerstate( newEvent, parm, pm->ps );
}
/*
===============
PM_AddTouchEnt
===============
*/
void PM_AddTouchEnt( int entityNum ) {
int i;
if ( entityNum == ENTITYNUM_WORLD ) {
return;
}
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->waterlevel ) {
drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime;
}
if ( pm->ps->pm_type == PM_SPECTATOR || pm->ps->pm_type == PM_FLOAT )
{
if (pm->ps->pm_type == PM_FLOAT)
{ //almost no friction while floating
drop += speed*0.1*pml.frametime;
}
else
{
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;
int umove = 0; //cmd->upmove;
//don't factor upmove into scaling speed
max = abs( cmd->forwardmove );
if ( abs( cmd->rightmove ) > max ) {
max = abs( cmd->rightmove );
}
if ( abs( umove ) > max ) {
max = abs( umove );
}
if ( !max ) {
return 0;
}
total = sqrt( cmd->forwardmove * cmd->forwardmove
+ cmd->rightmove * cmd->rightmove + umove * umove );
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;
}
}
}
#define METROID_JUMP 1
qboolean PM_ForceJumpingUp(void)
{
if ( !(pm->ps->fd.forcePowersActive&(1<<FP_LEVITATION)) && pm->ps->fd.forceJumpCharge )
{//already jumped and let go
return qfalse;
}
if ( BG_InSpecialJump( pm->ps->legsAnim ) )
{
return qfalse;
}
if (BG_SaberInSpecial(pm->ps->saberMove))
{
return qfalse;
}
if (BG_SaberInSpecialAttack(pm->ps->legsAnim))
{
return qfalse;
}
if (BG_HasYsalamiri(pm->gametype, pm->ps))
{
return qfalse;
}
if (!BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION))
{
return qfalse;
}
if ( pm->ps->groundEntityNum == ENTITYNUM_NONE && //in air
(pm->ps->pm_flags & PMF_JUMP_HELD) && //jumped
pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 && //force-jump capable
pm->ps->velocity[2] > 0 )//going up
{
return qtrue;
}
return qfalse;
}
static void PM_JumpForDir( void )
{
int anim = BOTH_JUMP1;
if ( pm->cmd.forwardmove > 0 )
{
anim = BOTH_JUMP1;
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
else if ( pm->cmd.forwardmove < 0 )
{
anim = BOTH_JUMPBACK1;
pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
}
else if ( pm->cmd.rightmove > 0 )
{
anim = BOTH_JUMPRIGHT1;
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
else if ( pm->cmd.rightmove < 0 )
{
anim = BOTH_JUMPLEFT1;
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
else
{
anim = BOTH_JUMP1;
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
if(!BG_InDeathAnim(pm->ps->legsAnim))
{
PM_SetAnim(SETANIM_LEGS,anim,SETANIM_FLAG_OVERRIDE, 100);
}
}
void PM_SetPMViewAngle(playerState_t *ps, vec3_t angle, usercmd_t *ucmd)
{
int i;
for (i=0 ; i<3 ; i++)
{ // set the delta angle
int cmdAngle;
cmdAngle = ANGLE2SHORT(angle[i]);
ps->delta_angles[i] = cmdAngle - ucmd->angles[i];
}
VectorCopy (angle, ps->viewangles);
}
qboolean PM_AdjustAngleForWallRun( playerState_t *ps, usercmd_t *ucmd, qboolean doMove )
{
if (( (ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_RIGHT || (ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_LEFT ) && ps->legsTimer > 500 )
{//wall-running and not at end of anim
//stick to wall, if there is one
vec3_t rt, traceTo, mins, maxs, fwdAngles;
trace_t trace;
float dist, yawAdjust;
VectorSet(mins, -15, -15, 0);
VectorSet(maxs, 15, 15, 24);
VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0);
AngleVectors( fwdAngles, NULL, rt, NULL );
if ( (ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_RIGHT )
{
dist = 128;
yawAdjust = -90;
}
else
{
dist = -128;
yawAdjust = 90;
}
VectorMA( ps->origin, dist, rt, traceTo );
pm->trace( &trace, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID );
if ( trace.fraction < 1.0f )
{//still a wall there
if ( (ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_RIGHT )
{
ucmd->rightmove = 127;
}
else
{
ucmd->rightmove = -127;
}
if ( ucmd->upmove < 0 )
{
ucmd->upmove = 0;
}
//make me face perpendicular to the wall
ps->viewangles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust;
PM_SetPMViewAngle(ps, ps->viewangles, ucmd);
ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW];
if ( doMove )
{
//push me forward
vec3_t fwd;
float zVel = ps->velocity[2];
if ( ps->legsTimer > 500 )
{//not at end of anim yet
float speed = 175;
fwdAngles[YAW] = ps->viewangles[YAW];
AngleVectors( fwdAngles, fwd, NULL, NULL );
if ( ucmd->forwardmove < 0 )
{//slower
speed = 100;
}
else if ( ucmd->forwardmove > 0 )
{
speed = 250;//running speed
}
VectorScale( fwd, speed, ps->velocity );
}
ps->velocity[2] = zVel;//preserve z velocity
//pull me toward the wall, too
VectorMA( ps->velocity, dist, rt, ps->velocity );
}
ucmd->forwardmove = 0;
return qtrue;
}
else if ( doMove )
{//stop it
if ( (ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_RIGHT )
{
PM_SetAnim(SETANIM_BOTH, BOTH_WALL_RUN_RIGHT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
}
else if ( (ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_LEFT )
{
PM_SetAnim(SETANIM_BOTH, BOTH_WALL_RUN_LEFT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
}
}
}
return qfalse;
}
//Set the height for when a force jump was started. If it's 0, nuge it up (slight hack to prevent holding jump over slopes)
void PM_SetForceJumpZStart(float value)
{
pm->ps->fd.forceJumpZStart = value;
if (!pm->ps->fd.forceJumpZStart)
{
pm->ps->fd.forceJumpZStart -= 0.1;
}
}
/*
=============
PM_CheckJump
=============
*/
static qboolean PM_CheckJump( void )
{
if (pm->ps->usingATST)
{
return qfalse;
}
if (pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN)
{
return qfalse;
}
//Don't allow jump until all buttons are up
if ( pm->ps->pm_flags & PMF_RESPAWNED ) {
return qfalse;
}
if ( PM_InKnockDown( pm->ps ) || BG_InRoll( pm->ps, pm->ps->legsAnim ) )
{//in knockdown
return qfalse;
}
if (pm->ps->groundEntityNum != ENTITYNUM_NONE || pm->ps->origin[2] < pm->ps->fd.forceJumpZStart)
{
pm->ps->fd.forcePowersActive &= ~(1<<FP_LEVITATION);
}
if (pm->ps->fd.forcePowersActive & (1 << FP_LEVITATION))
{ //Force jump is already active.. continue draining power appropriately until we land.
if (pm->ps->fd.forcePowerDebounce[FP_LEVITATION] < pm->cmd.serverTime)
{
BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 5 );
if (pm->ps->fd.forcePowerLevel[FP_LEVITATION] >= FORCE_LEVEL_2)
{
pm->ps->fd.forcePowerDebounce[FP_LEVITATION] = pm->cmd.serverTime + 300;
}
else
{
pm->ps->fd.forcePowerDebounce[FP_LEVITATION] = pm->cmd.serverTime + 200;
}
}
}
if (pm->ps->forceJumpFlip)
{ //Forced jump anim
int anim = BOTH_FORCEINAIR1;
int parts = SETANIM_BOTH;
if ( pm->cmd.forwardmove > 0 )
{
anim = BOTH_FLIP_F;
}
else if ( pm->cmd.forwardmove < 0 )
{
anim = BOTH_FLIP_B;
}
else if ( pm->cmd.rightmove > 0 )
{
anim = BOTH_FLIP_R;
}
else if ( pm->cmd.rightmove < 0 )
{
anim = BOTH_FLIP_L;
}
if ( pm->ps->weaponTime )
{//FIXME: really only care if we're in a saber attack anim...
parts = SETANIM_LEGS;
}
PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 );
pm->ps->forceJumpFlip = qfalse;
return qtrue;
}
#if METROID_JUMP
if ( pm->waterlevel < 2 )
{
if ( pm->ps->gravity > 0 )
{//can't do this in zero-G
if ( PM_ForceJumpingUp() )
{//holding jump in air
float curHeight = pm->ps->origin[2] - pm->ps->fd.forceJumpZStart;
//check for max force jump level and cap off & cut z vel
if ( ( curHeight<=forceJumpHeight[0] ||//still below minimum jump height
(pm->ps->fd.forcePower&&pm->cmd.upmove>=10) ) &&////still have force power available and still trying to jump up
curHeight < forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] &&
pm->ps->fd.forceJumpZStart)//still below maximum jump height
{//can still go up
if ( curHeight > forceJumpHeight[0] )
{//passed normal jump height *2?
if ( !(pm->ps->fd.forcePowersActive&(1<<FP_LEVITATION)) )//haven't started forcejump yet
{
//start force jump
pm->ps->fd.forcePowersActive |= (1<<FP_LEVITATION);
pm->ps->fd.forceJumpSound = 1;
//play flip
if ((pm->cmd.forwardmove || pm->cmd.rightmove) && //pushing in a dir
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_FLIP_F &&//not already flipping
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_FLIP_B &&
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_FLIP_R &&
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_FLIP_L )
{
int anim = BOTH_FORCEINAIR1;
int parts = SETANIM_BOTH;
if ( pm->cmd.forwardmove > 0 )
{
anim = BOTH_FLIP_F;
}
else if ( pm->cmd.forwardmove < 0 )
{
anim = BOTH_FLIP_B;
}
else if ( pm->cmd.rightmove > 0 )
{
anim = BOTH_FLIP_R;
}
else if ( pm->cmd.rightmove < 0 )
{
anim = BOTH_FLIP_L;
}
if ( pm->ps->weaponTime )
{
parts = SETANIM_LEGS;
}
PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 );
}
else if ( pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 )
{
vec3_t facingFwd, facingRight, facingAngles;
int anim = -1;
float dotR, dotF;
VectorSet(facingAngles, 0, pm->ps->viewangles[YAW], 0);
AngleVectors( facingAngles, facingFwd, facingRight, NULL );
dotR = DotProduct( facingRight, pm->ps->velocity );
dotF = DotProduct( facingFwd, pm->ps->velocity );
if ( fabs(dotR) > fabs(dotF) * 1.5 )
{
if ( dotR > 150 )
{
anim = BOTH_FORCEJUMPRIGHT1;
}
else if ( dotR < -150 )
{
anim = BOTH_FORCEJUMPLEFT1;
}
}
else
{
if ( dotF > 150 )
{
anim = BOTH_FORCEJUMP1;
}
else if ( dotF < -150 )
{
anim = BOTH_FORCEJUMPBACK1;
}
}
if ( anim != -1 )
{
int parts = SETANIM_BOTH;
if ( pm->ps->weaponTime )
{//FIXME: really only care if we're in a saber attack anim...
parts = SETANIM_LEGS;
}
PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 );
}
}
}
else
{ //jump is already active (the anim has started)
if ( pm->ps->legsTimer < 1 )
{//not in the middle of a legsAnim
int anim = (pm->ps->legsAnim&~ANIM_TOGGLEBIT);
int newAnim = -1;
switch ( anim )
{
case BOTH_FORCEJUMP1:
newAnim = BOTH_FORCELAND1;//BOTH_FORCEINAIR1;
break;
case BOTH_FORCEJUMPBACK1:
newAnim = BOTH_FORCELANDBACK1;//BOTH_FORCEINAIRBACK1;
break;
case BOTH_FORCEJUMPLEFT1:
newAnim = BOTH_FORCELANDLEFT1;//BOTH_FORCEINAIRLEFT1;
break;
case BOTH_FORCEJUMPRIGHT1:
newAnim = BOTH_FORCELANDRIGHT1;//BOTH_FORCEINAIRRIGHT1;
break;
}
if ( newAnim != -1 )
{
int parts = SETANIM_BOTH;
if ( pm->ps->weaponTime )
{
parts = SETANIM_LEGS;
}
PM_SetAnim( parts, newAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 );
}
}
}
}
//need to scale this down, start with height velocity (based on max force jump height) and scale down to regular jump vel
pm->ps->velocity[2] = (forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]]-curHeight)/forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]]*forceJumpStrength[pm->ps->fd.forcePowerLevel[FP_LEVITATION]];//JUMP_VELOCITY;
pm->ps->velocity[2] /= 10;
pm->ps->velocity[2] += JUMP_VELOCITY;
pm->ps->pm_flags |= PMF_JUMP_HELD;
}
else if ( curHeight > forceJumpHeight[0] && curHeight < forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] - forceJumpHeight[0] )
{//still have some headroom, don't totally stop it
if ( pm->ps->velocity[2] > JUMP_VELOCITY )
{
pm->ps->velocity[2] = JUMP_VELOCITY;
}
}
else
{
//pm->ps->velocity[2] = 0;
//rww - changed for the sake of balance in multiplayer
if ( pm->ps->velocity[2] > JUMP_VELOCITY )
{
pm->ps->velocity[2] = JUMP_VELOCITY;
}
}
pm->cmd.upmove = 0;
return qfalse;
}
}
}
#endif
//Not jumping
if ( pm->cmd.upmove < 10 && pm->ps->groundEntityNum != ENTITYNUM_NONE) {
return qfalse;
}
// must wait for jump to be released
if ( pm->ps->pm_flags & PMF_JUMP_HELD )
{
// clear upmove so cmdscale doesn't lower running speed
pm->cmd.upmove = 0;
return qfalse;
}
if ( pm->ps->gravity <= 0 )
{//in low grav, you push in the dir you're facing as long as there is something behind you to shove off of
vec3_t forward, back;
trace_t trace;
AngleVectors( pm->ps->viewangles, forward, NULL, NULL );
VectorMA( pm->ps->origin, -8, forward, back );
pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, back, pm->ps->clientNum, pm->tracemask );
if ( trace.fraction <= 1.0f )
{
VectorMA( pm->ps->velocity, JUMP_VELOCITY*2, forward, pm->ps->velocity );
PM_SetAnim(SETANIM_LEGS,BOTH_FORCEJUMP1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART, 150);
}//else no surf close enough to push off of
pm->cmd.upmove = 0;
}
else if ( pm->cmd.upmove > 0 && pm->waterlevel < 2 &&
pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 &&
!(pm->ps->pm_flags&PMF_JUMP_HELD) &&
pm->ps->weapon == WP_SABER &&
!BG_HasYsalamiri(pm->gametype, pm->ps) &&
BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION) )
{
if ( pm->ps->groundEntityNum != ENTITYNUM_NONE )
{//on the ground
//check for left-wall and right-wall special jumps
int anim = -1;
float vertPush = 0;
if ( pm->cmd.rightmove > 0 && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 )
{//strafing right
if ( pm->cmd.forwardmove > 0 )
{//wall-run
vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f;
anim = BOTH_WALL_RUN_RIGHT;
}
else if ( pm->cmd.forwardmove == 0 )
{//wall-flip
vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
anim = BOTH_WALL_FLIP_RIGHT;
}
}
else if ( pm->cmd.rightmove < 0 && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 )
{//strafing left
if ( pm->cmd.forwardmove > 0 )
{//wall-run
vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f;
anim = BOTH_WALL_RUN_LEFT;
}
else if ( pm->cmd.forwardmove == 0 )
{//wall-flip
vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f;
anim = BOTH_WALL_FLIP_LEFT;
}
}
else if ( pm->cmd.forwardmove < 0 && !(pm->cmd.buttons&BUTTON_ATTACK) )
{//backflip
vertPush = JUMP_VELOCITY;
anim = BOTH_FLIP_BACK1;//PM_PickAnim( BOTH_FLIP_BACK1, BOTH_FLIP_BACK3 );
}
vertPush += 128; //give them an extra shove
if ( anim != -1 )
{
vec3_t fwd, right, traceto, mins, maxs, fwdAngles;
vec3_t idealNormal;
trace_t trace;
qboolean doTrace = qfalse;
int contents = MASK_PLAYERSOLID;
VectorSet(mins, pm->mins[0],pm->mins[1],0);
VectorSet(maxs, pm->maxs[0],pm->maxs[1],24);
VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0);
memset(&trace, 0, sizeof(trace)); //to shut the compiler up
AngleVectors( fwdAngles, fwd, right, NULL );
//trace-check for a wall, if necc.
switch ( anim )
{
case BOTH_WALL_FLIP_LEFT:
//NOTE: purposely falls through to next case!
case BOTH_WALL_RUN_LEFT:
doTrace = qtrue;
VectorMA( pm->ps->origin, -16, right, traceto );
break;
case BOTH_WALL_FLIP_RIGHT:
//NOTE: purposely falls through to next case!
case BOTH_WALL_RUN_RIGHT:
doTrace = qtrue;
VectorMA( pm->ps->origin, 16, right, traceto );
break;
case BOTH_WALL_FLIP_BACK1:
doTrace = qtrue;
VectorMA( pm->ps->origin, 16, fwd, traceto );
break;
}
if ( doTrace )
{
pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents );
VectorSubtract( pm->ps->origin, traceto, idealNormal );
VectorNormalize( idealNormal );
}
if ( !doTrace || (trace.fraction < 1.0f && (trace.entityNum < MAX_CLIENTS || DotProduct(trace.plane.normal,idealNormal) > 0.7)) )
{//there is a wall there.. or hit a client
int parts;
//move me to side
if ( anim == BOTH_WALL_FLIP_LEFT )
{
pm->ps->velocity[0] = pm->ps->velocity[1] = 0;
VectorMA( pm->ps->velocity, 150, right, pm->ps->velocity );
}
else if ( anim == BOTH_WALL_FLIP_RIGHT )
{
pm->ps->velocity[0] = pm->ps->velocity[1] = 0;
VectorMA( pm->ps->velocity, -150, right, pm->ps->velocity );
}
else if ( anim == BOTH_FLIP_BACK1
|| anim == BOTH_FLIP_BACK2
|| anim == BOTH_FLIP_BACK3
|| anim == BOTH_WALL_FLIP_BACK1 )
{
pm->ps->velocity[0] = pm->ps->velocity[1] = 0;
VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity );
}
if ( doTrace && anim != BOTH_WALL_RUN_LEFT && anim != BOTH_WALL_RUN_RIGHT )
{
if (trace.entityNum < MAX_CLIENTS)
{
pm->ps->forceKickFlip = trace.entityNum+1; //let the server know that this person gets kicked by this client
}
}
//up
if ( vertPush )
{
pm->ps->velocity[2] = vertPush;
pm->ps->fd.forcePowersActive |= (1 << FP_LEVITATION);
}
//animate me
parts = SETANIM_LEGS;
if ( anim == BOTH_BUTTERFLY_LEFT )
{
parts = SETANIM_BOTH;
pm->cmd.buttons&=~BUTTON_ATTACK;
pm->ps->saberMove = LS_NONE;
}
else if ( !pm->ps->weaponTime )
{
parts = SETANIM_BOTH;
}
PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
if ( anim == BOTH_BUTTERFLY_LEFT )
{
pm->ps->weaponTime = pm->ps->torsoTimer;
}
PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height
pm->ps->pm_flags |= PMF_JUMP_HELD;
pm->cmd.upmove = 0;
pm->ps->fd.forceJumpSound = 1;
}
}
}
else
{//in the air
int legsAnim = (pm->ps->legsAnim&~ANIM_TOGGLEBIT);
if ( legsAnim == BOTH_WALL_RUN_LEFT || legsAnim == BOTH_WALL_RUN_RIGHT )
{//running on a wall
vec3_t right, traceto, mins, maxs, fwdAngles;
trace_t trace;
int anim = -1;
VectorSet(mins, pm->mins[0], pm->mins[0], 0);
VectorSet(maxs, pm->maxs[0], pm->maxs[0], 24);
VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0);
AngleVectors( fwdAngles, NULL, right, NULL );
if ( legsAnim == BOTH_WALL_RUN_LEFT )
{
if ( pm->ps->legsTimer > 400 )
{//not at the end of the anim
float animLen = PM_AnimLength( 0, (animNumber_t)BOTH_WALL_RUN_LEFT );
if ( pm->ps->legsTimer < animLen - 400 )
{//not at start of anim
VectorMA( pm->ps->origin, -16, right, traceto );
anim = BOTH_WALL_RUN_LEFT_FLIP;
}
}
}
else if ( legsAnim == BOTH_WALL_RUN_RIGHT )
{
if ( pm->ps->legsTimer > 400 )
{//not at the end of the anim
float animLen = PM_AnimLength( 0, (animNumber_t)BOTH_WALL_RUN_RIGHT );
if ( pm->ps->legsTimer < animLen - 400 )
{//not at start of anim
VectorMA( pm->ps->origin, 16, right, traceto );
anim = BOTH_WALL_RUN_RIGHT_FLIP;
}
}
}
if ( anim != -1 )
{
pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID|CONTENTS_BODY );
if ( trace.fraction < 1.0f )
{//flip off wall
int parts = 0;
if ( anim == BOTH_WALL_RUN_LEFT_FLIP )
{
pm->ps->velocity[0] *= 0.5f;
pm->ps->velocity[1] *= 0.5f;
VectorMA( pm->ps->velocity, 150, right, pm->ps->velocity );
}
else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP )
{
pm->ps->velocity[0] *= 0.5f;
pm->ps->velocity[1] *= 0.5f;
VectorMA( pm->ps->velocity, -150, right, pm->ps->velocity );
}
parts = SETANIM_LEGS;
if ( !pm->ps->weaponTime )
{
parts = SETANIM_BOTH;
}
PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
pm->cmd.upmove = 0;
}
}
if ( pm->cmd.upmove != 0 )
{//jump failed, so don't try to do normal jump code, just return
return qfalse;
}
}
else if ( pm->cmd.forwardmove > 0 //pushing forward
&& pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1
&& pm->ps->velocity[2] > 200
&& PM_GroundDistance() <= 80 //unfortunately we do not have a happy ground timer like SP (this would use up more bandwidth if we wanted prediction workign right), so we'll just use the actual ground distance.
&& !BG_InSpecialJump(pm->ps->legsAnim))
{//run up wall, flip backwards
vec3_t fwd, traceto, mins, maxs, fwdAngles;
trace_t trace;
vec3_t idealNormal;
VectorSet(mins, pm->mins[0],pm->mins[1],pm->mins[2]);
VectorSet(maxs, pm->maxs[0],pm->maxs[1],pm->maxs[2]);
VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0);
AngleVectors( fwdAngles, fwd, NULL, NULL );
VectorMA( pm->ps->origin, 32, fwd, traceto );
pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, MASK_PLAYERSOLID );//FIXME: clip brushes too?
VectorSubtract( pm->ps->origin, traceto, idealNormal );
VectorNormalize( idealNormal );
if ( trace.fraction < 1.0f )
{//there is a wall there
int parts = SETANIM_LEGS;
pm->ps->velocity[0] = pm->ps->velocity[1] = 0;
VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity );
pm->ps->velocity[2] += 128;
if ( !pm->ps->weaponTime )
{
parts = SETANIM_BOTH;
}
PM_SetAnim( parts, BOTH_WALL_FLIP_BACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
pm->ps->legsTimer -= 600; //I force this anim to play to the end to prevent landing on your head and suddenly flipping over.
//It is a bit too long at the end though, so I'll just shorten it.
PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height
pm->cmd.upmove = 0;
pm->ps->fd.forceJumpSound = 1;
BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 5 );
if (trace.entityNum < MAX_CLIENTS)
{
pm->ps->forceKickFlip = trace.entityNum+1; //let the server know that this person gets kicked by this client
}
}
}
}
}
if ( pm->cmd.upmove > 0
&& pm->ps->weapon == WP_SABER
&& (pm->ps->weaponTime > 0||pm->cmd.buttons&BUTTON_ATTACK) )
{//okay, we just jumped and we're in an attack
if ( !BG_InRoll( pm->ps, pm->ps->legsAnim )
&& !PM_InKnockDown( pm->ps )
&& !BG_InDeathAnim(pm->ps->legsAnim)
&& !BG_FlippingAnim( pm->ps->legsAnim )
&& !PM_SpinningAnim( pm->ps->legsAnim )
&& !BG_SaberInSpecialAttack( pm->ps->torsoAnim )
&& ( BG_SaberInAttack( pm->ps->saberMove ) ) )
{//not in an anim we shouldn't interrupt
//see if it's not too late to start a special jump-attack
float animLength = PM_AnimLength( 0, (animNumber_t)pm->ps->torsoAnim );
if ( animLength - pm->ps->torsoTimer < 500 )
{//just started the saberMove
//check for special-case jump attacks
if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_2 )
{//using medium attacks
if (PM_GroundDistance() < 32 &&
!BG_InSpecialJump(pm->ps->legsAnim))
{ //FLIP AND DOWNWARD ATTACK
trace_t tr;
if (PM_SomeoneInFront(&tr))
{
PM_SetSaberMove(PM_SaberFlipOverAttackMove(&tr));
pml.groundPlane = qfalse;
pml.walking = qfalse;
pm->ps->pm_flags |= PMF_JUMP_HELD;
pm->ps->groundEntityNum = ENTITYNUM_NONE;
VectorClear(pml.groundTrace.plane.normal);
pm->ps->weaponTime = pm->ps->torsoTimer;
}
}
}
else if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_3 )
{//using strong attacks
if ( pm->cmd.forwardmove > 0 && //going forward
(pm->cmd.buttons & BUTTON_ATTACK) && //must be holding attack still
PM_GroundDistance() < 32 &&
!BG_InSpecialJump(pm->ps->legsAnim))
{//strong attack: jump-hack
PM_SetSaberMove( PM_SaberJumpAttackMove() );
pml.groundPlane = qfalse;
pml.walking = qfalse;
pm->ps->pm_flags |= PMF_JUMP_HELD;
pm->ps->groundEntityNum = ENTITYNUM_NONE;
VectorClear(pml.groundTrace.plane.normal);
pm->ps->weaponTime = pm->ps->torsoTimer;
}
}
}
}
}
if ( pm->ps->groundEntityNum == ENTITYNUM_NONE )
{
return qfalse;
}
if ( pm->cmd.upmove > 0 )
{//no special jumps
pm->ps->velocity[2] = JUMP_VELOCITY;
PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height
pm->ps->pm_flags |= PMF_JUMP_HELD;
}
//Jumping
pml.groundPlane = qfalse;
pml.walking = qfalse;
pm->ps->pm_flags |= PMF_JUMP_HELD;
pm->ps->groundEntityNum = ENTITYNUM_NONE;
PM_SetForceJumpZStart(pm->ps->origin[2]);
PM_AddEvent( EV_JUMP );
//Set the animations
if ( pm->ps->gravity > 0 && !BG_InSpecialJump( pm->ps->legsAnim ) )
{
PM_JumpForDir();
}
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 if (pm->watertype == CONTENTS_SLIME) {
pm->ps->velocity[2] = 80;
} 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
Only with the flight powerup
===================
*/
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 );
if ( pm->ps->pm_type == PM_SPECTATOR && pm->cmd.buttons & BUTTON_ALT_ATTACK) {
//turbo boost
scale *= 10;
}
//
// user intentions
//
if ( !scale ) {
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = pm->ps->speed * (pm->cmd.upmove/127.0f);
} 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;
if (pm->ps->pm_type != PM_SPECTATOR)
{
#if METROID_JUMP
PM_CheckJump();
#else
if (pm->ps->fd.forceJumpZStart &&
pm->ps->forceJumpFlip)
{
PM_CheckJump();
}
#endif
}
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);
if ( gPMDoSlowFall )
{//no air-control
VectorClear( wishvel );
}
else
{
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;
float totalVel;
if (pm->ps->velocity[0] < 0)
{
totalVel = -pm->ps->velocity[0];
}
else
{
totalVel = pm->ps->velocity[0];
}
if (pm->ps->velocity[1] < 0)
{
totalVel += -pm->ps->velocity[1];
}
else
{
totalVel += pm->ps->velocity[1];
}
if (totalVel < 200)
{
pm->ps->fd.forceSpeedSmash = 1;
}
if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) {
// begin swimming
PM_WaterMove();
return;
}
if (pm->ps->pm_type != PM_SPECTATOR)
{
if ( PM_CheckJump () ) {
// 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
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;
}
}
else if ( (pm->ps->pm_flags & PMF_ROLLING) && !BG_InRoll(pm->ps, pm->ps->legsAnim) &&
!PM_InRollComplete(pm->ps, pm->ps->legsAnim))
{
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 ) {
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;
}
PM_Accelerate (wishdir, wishspeed, accelerate);
//Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]);
//Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity));
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]) {
pm->ps->fd.forceSpeedSmash = 1;
return;
}
PM_StepSlideMove( qfalse );
//Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity));
}
/*
==============
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 );
if (pm->cmd.buttons & BUTTON_ATTACK) { //turbo boost
scale *= 10;
}
if (pm->cmd.buttons & BUTTON_ALT_ATTACK) { //turbo boost
scale *= 10;
}
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_accelerate );
// move
VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin);
}
//============================================================================
/*
================
PM_FootstepForSurface
Returns an event number apropriate for the groundsurface
================
*/
static int PM_FootstepForSurface( void )
{
if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS )
{
return 0;
}
if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS )
{
return EV_FOOTSTEP_METAL;
}
return EV_FOOTSTEP;
}
static int PM_TryRoll( void )
{
trace_t trace;
int anim = -1;
vec3_t fwd, right, traceto, mins, maxs, fwdAngles;
if ( BG_SaberInAttack( pm->ps->saberMove ) || BG_SaberInSpecialAttack( pm->ps->torsoAnim )
|| BG_SpinningSaberAnim( pm->ps->legsAnim )
|| PM_SaberInStart( pm->ps->saberMove ) )
{//attacking or spinning (or, if player, starting an attack)
return 0;
}
if (pm->ps->weapon != WP_SABER || BG_HasYsalamiri(pm->gametype, pm->ps) ||
!BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION))
{ //Not using saber, or can't use jump
return 0;
}
VectorSet(mins, pm->mins[0],pm->mins[1],pm->mins[2]+STEPSIZE);
VectorSet(maxs, pm->maxs[0],pm->maxs[1],CROUCH_MAXS_2);
VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0);
AngleVectors( fwdAngles, fwd, right, NULL );
if ( pm->cmd.forwardmove )
{ //check forward/backward rolls
if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN )
{
anim = BOTH_ROLL_B;
VectorMA( pm->ps->origin, -64, fwd, traceto );
}
else
{
anim = BOTH_ROLL_F;
VectorMA( pm->ps->origin, 64, fwd, traceto );
}
}
else if ( pm->cmd.rightmove > 0 )
{ //right
anim = BOTH_ROLL_R;
VectorMA( pm->ps->origin, 64, right, traceto );
}
else if ( pm->cmd.rightmove < 0 )
{ //left
anim = BOTH_ROLL_L;
VectorMA( pm->ps->origin, -64, right, traceto );
}
if ( anim != -1 )
{ //We want to roll. Perform a trace to see if we can, and if so, send us into one.
pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID );
if ( trace.fraction >= 1.0f )
{
pm->ps->saberMove = LS_NONE;
return anim;
}
}
return 0;
}
/*
=================
PM_CrashLand
Check for hard landings that generate sound events
=================
*/
static void PM_CrashLand( void ) {
float delta;
float dist;
float vel, acc;
float t;
float a, b, c, den;
qboolean didRoll = qfalse;
// 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 ) {
pm->ps->inAirAnim = qfalse;
return;
}
t = (-b - sqrt( den ) ) / ( 2 * a );
delta = vel + t * acc;
delta = delta*delta * 0.0001;
// ducking while falling doubles damage
if ( pm->ps->pm_flags & PMF_DUCKED ) {
delta *= 2;
}
// decide which landing animation to use
if (!BG_InRoll(pm->ps, pm->ps->legsAnim) && pm->ps->inAirAnim)
{ //only play a land animation if we transitioned into an in-air animation while off the ground
if (!BG_SaberInSpecial(pm->ps->saberMove))
{
if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) {
PM_ForceLegsAnim( BOTH_LANDBACK1 );
} else {
PM_ForceLegsAnim( BOTH_LAND1 );
}
}
}
if (pm->ps->weapon != WP_SABER)
{ //saber handles its own anims
//This will push us back into our weaponready stance from the land anim.
if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1)
{
PM_StartTorsoAnim( TORSO_WEAPONREADY4 );
}
else
{
if (pm->ps->weapon == WP_EMPLACED_GUN)
{
PM_StartTorsoAnim( BOTH_GUNSIT1 );
}
else
{
PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] );
}
}
}
if (!BG_InSpecialJump(pm->ps->legsAnim) ||
pm->ps->legsTimer < 1 ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_LEFT ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_WALL_RUN_RIGHT)
{ //Only set the timer if we're in an anim that can be interrupted (this would not be, say, a flip)
if (!BG_InRoll(pm->ps, pm->ps->legsAnim) && pm->ps->inAirAnim)
{
if (!BG_SaberInSpecial(pm->ps->saberMove) || pm->ps->weapon != WP_SABER)
{
pm->ps->legsTimer = TIMER_LAND;
}
}
}
pm->ps->inAirAnim = qfalse;
// never take falling damage if completely underwater
if ( pm->waterlevel == 3 ) {
return;
}
// reduce falling damage if there is standing water
if ( pm->waterlevel == 2 ) {
delta *= 0.25;
}
if ( pm->waterlevel == 1 ) {
delta *= 0.5;
}
if ( delta < 1 ) {
return;
}
if ( pm->ps->pm_flags & PMF_DUCKED )
{
if( delta >= 2 && !PM_InOnGroundAnim( pm->ps->legsAnim ) && !PM_InKnockDown( pm->ps ) && !BG_InRoll(pm->ps, pm->ps->legsAnim) &&
pm->ps->forceHandExtend == HANDEXTEND_NONE )
{//roll!
int anim = PM_TryRoll();
if (PM_InRollComplete(pm->ps, pm->ps->legsAnim))
{
anim = 0;
pm->ps->legsTimer = 0;
pm->ps->legsAnim = 0;
PM_SetAnim(SETANIM_BOTH,BOTH_LAND1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150);
pm->ps->legsTimer = TIMER_LAND;
}
if ( anim )
{//absorb some impact
pm->ps->legsTimer = 0;
delta /= 3; // /= 2 just cancels out the above delta *= 2 when landing while crouched, the roll itself should absorb a little damage
pm->ps->legsAnim = 0;
PM_SetAnim(SETANIM_BOTH,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150);
didRoll = qtrue;
}
}
}
// SURF_NODAMAGE is used for bounce pads where you don't ever
// want to take damage or play a crunch sound
if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) {
if (delta > 7)
{
int delta_send = (int)delta;
if (delta_send > 600)
{ //will never need to know any value above this
delta_send = 600;
}
if (pm->ps->fd.forceJumpZStart)
{
if ((int)pm->ps->origin[2] >= (int)pm->ps->fd.forceJumpZStart)
{ //was force jumping, landed on higher or same level as when force jump was started
if (delta_send > 8)
{
delta_send = 8;
}
}
else
{
if (delta_send > 8)
{
int dif = ((int)pm->ps->fd.forceJumpZStart - (int)pm->ps->origin[2]);
int dmgLess = (forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] - dif);
if (dmgLess < 0)
{
dmgLess = 0;
}
delta_send -= (dmgLess*0.3);
if (delta_send < 8)
{
delta_send = 8;
}
//Com_Printf("Damage sub: %i\n", (int)((dmgLess*0.1)));
}
}
}
if (didRoll)
{ //Add the appropriate event..
PM_AddEventWithParm( EV_ROLL, delta_send );
}
else
{
PM_AddEventWithParm( EV_FALL, delta_send );
}
}
else
{
if (didRoll)
{
PM_AddEventWithParm( EV_ROLL, 0 );
}
else
{
PM_AddEvent( PM_FootstepForSurface() );
}
}
}
// make sure velocity resets so we don't bounce back up again in case we miss the clear elsewhere
pm->ps->velocity[2] = 0;
// start footstep cycle over
pm->ps->bobCycle = 0;
}
/*
=============
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;
//rww - don't want to do this when handextend_choke, because you can be standing on the ground
//while still holding your throat.
if ( pm->ps->pm_type == PM_FLOAT )
{
//we're assuming this is because you're being choked
int parts = SETANIM_LEGS;
//rww - also don't use SETANIM_FLAG_HOLD, it will cause the legs to float around a bit before going into
//a proper anim even when on the ground.
PM_SetAnim(parts, BOTH_CHOKE3, SETANIM_FLAG_OVERRIDE, 100);
}
//If the anim is choke3, act like we just went into the air because we aren't in a float
else if ( pm->ps->groundEntityNum != ENTITYNUM_NONE || (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_CHOKE3 )
{
// 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 || pm->ps->pm_type == PM_FLOAT ) {
if ( pm->ps->velocity[2] <= 0 && !(pm->ps->pm_flags&PMF_JUMP_HELD))
{
PM_SetAnim(SETANIM_LEGS,BOTH_INAIR1,SETANIM_FLAG_OVERRIDE, 100);
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
else if ( pm->cmd.forwardmove >= 0 )
{
PM_SetAnim(SETANIM_LEGS,BOTH_JUMP1,SETANIM_FLAG_OVERRIDE, 100);
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
else
{
PM_SetAnim(SETANIM_LEGS,BOTH_JUMPBACK1,SETANIM_FLAG_OVERRIDE, 100);
pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
}
pm->ps->inAirAnim = qtrue;
}
}
else if (!pm->ps->inAirAnim)
{
// 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 || pm->ps->pm_type == PM_FLOAT )
{
pm->ps->inAirAnim = qtrue;
}
}
if (PM_InRollComplete(pm->ps, pm->ps->legsAnim))
{ //Client won't catch an animation restart because it only checks frame against incoming frame, so if you roll when you land after rolling
//off of something it won't replay the roll anim unless we switch it off in the air. This fixes that.
PM_SetAnim(SETANIM_BOTH,BOTH_INAIR1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150);
pm->ps->inAirAnim = qtrue;
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
}
/*
=============
PM_GroundTrace
=============
*/
static void PM_GroundTrace( void ) {
vec3_t point;
trace_t trace;
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;
// do something corrective if the trace starts in a solid...
if ( trace.allsolid ) {
if ( !PM_CorrectAllSolid(&trace) )
return;
}
if (pm->ps->pm_type == PM_FLOAT)
{
PM_GroundTraceMissed();
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
// 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( BOTH_JUMP1 );
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
} else {
PM_ForceLegsAnim( BOTH_JUMPBACK1 );
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.plane.normal[2] < MIN_WALK_NORMAL ) {
if ( pm->debugLevel ) {
Com_Printf("%i:steep\n", c_pmove);
}
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 ( pm->debugLevel ) {
Com_Printf("%i:Land\n", c_pmove);
}
PM_CrashLand();
// 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;
pm->ps->lastOnGround = pm->cmd.serverTime;
PM_AddTouchEnt( trace.entityNum );
}
/*
=============
PM_SetWaterLevel
=============
*/
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] + MINS_Z + 1;
cont = pm->pointcontents( point, pm->ps->clientNum );
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_CheckDuck
Sets mins, maxs, and pm->ps->viewheight
==============
*/
static void PM_CheckDuck (void)
{
trace_t trace;
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] = -8;
pm->ps->viewheight = DEAD_VIEWHEIGHT;
return;
}
if (pm->ps->usingATST)
{
if (pm->cmd.upmove < 0)
{
pm->cmd.upmove = 0;
}
}
if (BG_InRoll(pm->ps, pm->ps->legsAnim))
{
pm->maxs[2] = CROUCH_MAXS_2;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
pm->ps->pm_flags &= ~PMF_DUCKED;
pm->ps->pm_flags |= PMF_ROLLING;
return;
}
else if (pm->ps->pm_flags & PMF_ROLLING)
{
// try to stand up
pm->maxs[2] = DEFAULT_MAXS_2;
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_ROLLING;
}
else if (pm->cmd.upmove < 0 ||
pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN)
{ // duck
pm->ps->pm_flags |= PMF_DUCKED;
}
else
{ // stand up if possible
if (pm->ps->pm_flags & PMF_DUCKED)
{
// try to stand up
pm->maxs[2] = DEFAULT_MAXS_2;
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_MAXS_2;
pm->ps->viewheight = CROUCH_VIEWHEIGHT;
}
else if (pm->ps->pm_flags & PMF_ROLLING)
{
pm->maxs[2] = CROUCH_MAXS_2;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
}
else
{
pm->maxs[2] = DEFAULT_MAXS_2;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
}
if (pm->ps->usingATST)
{
pm->mins[0] = ATST_MINS0;
pm->mins[1] = ATST_MINS1;
pm->mins[2] = ATST_MINS2;
pm->maxs[0] = ATST_MAXS0;
pm->maxs[1] = ATST_MAXS1;
pm->maxs[2] = ATST_MAXS2;
}
}
//===================================================================
/*
==============
PM_Use
Generates a use event
==============
*/
#define USE_DELAY 2000
void PM_Use( void )
{
if ( pm->ps->useTime > 0 )
pm->ps->useTime -= 100;//pm->cmd.msec;
if ( pm->ps->useTime > 0 ) {
return;
}
if ( ! (pm->cmd.buttons & BUTTON_USE ) )
{
pm->useEvent = 0;
pm->ps->useTime = 0;
return;
}
pm->useEvent = EV_USE;
pm->ps->useTime = USE_DELAY;
}
qboolean PM_RunningAnim( int anim )
{
switch ( (anim&~ANIM_TOGGLEBIT) )
{
case BOTH_RUN1:
case BOTH_RUN2:
case BOTH_RUNBACK1:
case BOTH_RUNBACK2:
case BOTH_RUNAWAY1:
return qtrue;
break;
}
return qfalse;
}
/*
===============
PM_Footsteps
===============
*/
static void PM_Footsteps( void ) {
float bobmove;
int old;
qboolean footstep;
int setAnimFlags = 0;
if ( (PM_InSaberAnim( (pm->ps->legsAnim&~ANIM_TOGGLEBIT) ) && !BG_SpinningSaberAnim( (pm->ps->legsAnim&~ANIM_TOGGLEBIT) ))
|| (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_STAND1
|| (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_STAND1TO2
|| (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_STAND2TO1
|| (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_STAND2
|| (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_SABERFAST_STANCE
|| (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_SABERSLOW_STANCE
|| (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_BUTTON_HOLD
|| (pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_BUTTON_RELEASE
|| PM_LandingAnim( (pm->ps->legsAnim&~ANIM_TOGGLEBIT) )
|| PM_PainAnim( (pm->ps->legsAnim&~ANIM_TOGGLEBIT) ))
{//legs are in a saber anim, and not spinning, be sure to override it
setAnimFlags |= SETANIM_FLAG_OVERRIDE;
}
//
// 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 ) {
// airborne leaves position in cycle intact, but doesn't advance
if ( pm->waterlevel > 1 )
{
if (pm->xyspeed > 60)
{
PM_ContinueLegsAnim( BOTH_SWIMFORWARD );
}
else
{
PM_ContinueLegsAnim( BOTH_SWIM_IDLE1 );
}
}
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) || (pm->ps->pm_flags & PMF_ROLLING) ) {
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_CROUCH1IDLE)
{
PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1IDLE, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_CROUCH1IDLE );
}
} else {
if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1)
{
PM_ContinueLegsAnim( TORSO_WEAPONREADY4 );
}
else
{
if (pm->ps->weapon == WP_SABER && pm->ps->saberHolstered)
{
PM_ContinueLegsAnim( BOTH_STAND1 );
}
else
{
PM_ContinueLegsAnim( WeaponReadyAnim[pm->ps->weapon] );
}
}
}
}
return;
}
footstep = qfalse;
if ( pm->ps->pm_flags & PMF_DUCKED )
{
int rolled = 0;
bobmove = 0.5; // ducked characters bob much faster
if ( PM_RunningAnim( pm->ps->legsAnim ) && !BG_InRoll(pm->ps, pm->ps->legsAnim) )
{//roll!
rolled = PM_TryRoll();
}
if ( !rolled )
{ //if the roll failed or didn't attempt, do standard crouching anim stuff.
if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) {
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_CROUCH1WALKBACK)
{
PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALKBACK, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_CROUCH1WALKBACK );
}
}
else {
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_CROUCH1WALK)
{
PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALK, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_CROUCH1WALK );
}
}
}
else
{ //otherwise send us into the roll
pm->ps->legsTimer = 0;
pm->ps->legsAnim = 0;
PM_SetAnim(SETANIM_BOTH,rolled,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150);
PM_AddEventWithParm( EV_ROLL, 0 );
pm->maxs[2] = CROUCH_MAXS_2;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
pm->ps->pm_flags &= ~PMF_DUCKED;
pm->ps->pm_flags |= PMF_ROLLING;
}
}
else if ((pm->ps->pm_flags & PMF_ROLLING) && !BG_InRoll(pm->ps, pm->ps->legsAnim) &&
!PM_InRollComplete(pm->ps, pm->ps->legsAnim))
{
bobmove = 0.5; // ducked characters bob much faster
if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN )
{
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_CROUCH1WALKBACK)
{
PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALKBACK, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_CROUCH1WALKBACK );
}
}
else
{
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_CROUCH1WALK)
{
PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALK, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_CROUCH1WALK );
}
}
}
else
{
if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) {
bobmove = 0.4f; // faster speeds bob faster
if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) {
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_RUNBACK1)
{
PM_SetAnim(SETANIM_LEGS, BOTH_RUNBACK1, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_RUNBACK1 );
}
}
else {
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_RUN1)
{
PM_SetAnim(SETANIM_LEGS, BOTH_RUN1, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_RUN1 );
}
}
footstep = qtrue;
} else {
bobmove = 0.2f; // walking bobs slow
if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) {
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_WALKBACK1)
{
PM_SetAnim(SETANIM_LEGS, BOTH_WALKBACK1, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_WALKBACK1 );
}
}
else {
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) != BOTH_WALK1)
{
PM_SetAnim(SETANIM_LEGS, BOTH_WALK1, setAnimFlags, 100);
}
else
{
PM_ContinueLegsAnim( BOTH_WALK1 );
}
}
}
}
// 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 )
{
pm->ps->footstepTime = pm->cmd.serverTime + 300;
if ( pm->waterlevel == 1 ) {
// splashing
PM_AddEvent( EV_FOOTSPLASH );
} 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 ) { // FIXME?
//
// if just entered a water volume, play a sound
//
if (!pml.previous_waterlevel && pm->waterlevel) {
PM_AddEvent( EV_WATER_TOUCH );
}
//
// if just completely exited a water volume, play a sound
//
if (pml.previous_waterlevel && !pm->waterlevel) {
PM_AddEvent( EV_WATER_LEAVE );
}
//
// check for head just going under water
//
if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) {
PM_AddEvent( EV_WATER_UNDER );
}
//
// check for head just coming out of water
//
if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) {
PM_AddEvent( EV_WATER_CLEAR );
}
}
/*
===============
PM_BeginWeaponChange
===============
*/
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;
}
// turn of any kind of zooming when weapon switching.
if (pm->ps->zoomMode)
{
pm->ps->zoomMode = 0;
pm->ps->zoomTime = pm->ps->commandTime;
}
PM_AddEvent( EV_CHANGE_WEAPON );
pm->ps->weaponstate = WEAPON_DROPPING;
pm->ps->weaponTime += 200;
PM_StartTorsoAnim( TORSO_DROPWEAP1 );
}
/*
===============
PM_FinishWeaponChange
===============
*/
void PM_FinishWeaponChange( void ) {
int weapon;
weapon = pm->cmd.weapon;
if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) {
weapon = WP_NONE;
}
if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
weapon = WP_NONE;
}
if (weapon == WP_SABER)
{
PM_SetSaberMove(LS_DRAW);
}
else
{
PM_StartTorsoAnim( TORSO_RAISEWEAP1);
}
pm->ps->weapon = weapon;
pm->ps->weaponstate = WEAPON_RAISING;
pm->ps->weaponTime += 250;
}
//---------------------------------------
static qboolean PM_DoChargedWeapons( void )
//---------------------------------------
{
vec3_t ang;
trace_t tr;
qboolean charging = qfalse,
altFire = qfalse;
// If you want your weapon to be a charging weapon, just set this bit up
switch( pm->ps->weapon )
{
//------------------
case WP_BRYAR_PISTOL:
// alt-fire charges the weapon
if ( pm->cmd.buttons & BUTTON_ALT_ATTACK )
{
charging = qtrue;
altFire = qtrue;
}
break;
//------------------
case WP_BOWCASTER:
// primary fire charges the weapon
if ( pm->cmd.buttons & BUTTON_ATTACK )
{
charging = qtrue;
}
break;
//------------------
case WP_ROCKET_LAUNCHER:
// Not really a charge weapon, but we still want to delay fire until the button comes up so that we can
// implement our alt-fire locking stuff
if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) && pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] >= weaponData[pm->ps->weapon].altEnergyPerShot )
{
vec3_t muzzleOffPoint, muzzlePoint, forward, right, up;
AngleVectors( pm->ps->viewangles, forward, right, up );
charging = qtrue;
altFire = qtrue;
AngleVectors(pm->ps->viewangles, ang, NULL, NULL);
VectorCopy( pm->ps->origin, muzzlePoint );
VectorCopy(WP_MuzzlePoint[WP_ROCKET_LAUNCHER], muzzleOffPoint);
VectorMA(muzzlePoint, muzzleOffPoint[0], forward, muzzlePoint);
VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint);
muzzlePoint[2] += pm->ps->viewheight + muzzleOffPoint[2];
ang[0] = muzzlePoint[0] + ang[0]*2048;
ang[1] = muzzlePoint[1] + ang[1]*2048;
ang[2] = muzzlePoint[2] + ang[2]*2048;
pm->trace(&tr, muzzlePoint, NULL, NULL, ang, pm->ps->clientNum, MASK_PLAYERSOLID);
if (tr.fraction != 1 && tr.entityNum < MAX_CLIENTS && tr.entityNum != pm->ps->clientNum)
{
if (pm->ps->rocketLockIndex == MAX_CLIENTS)
{
pm->ps->rocketLockIndex = tr.entityNum;
pm->ps->rocketLockTime = pm->cmd.serverTime;
}
else if (pm->ps->rocketLockIndex != tr.entityNum && pm->ps->rocketTargetTime < pm->cmd.serverTime)
{
pm->ps->rocketLockIndex = tr.entityNum;
pm->ps->rocketLockTime = pm->cmd.serverTime;
}
else if (pm->ps->rocketLockIndex == tr.entityNum)
{
if (pm->ps->rocketLockTime == -1)
{
pm->ps->rocketLockTime = pm->ps->rocketLastValidTime;
}
}
if (pm->ps->rocketLockIndex == tr.entityNum)
{
pm->ps->rocketTargetTime = pm->cmd.serverTime + 500;
}
}
else if (pm->ps->rocketTargetTime < pm->cmd.serverTime)
{
pm->ps->rocketLockIndex = MAX_CLIENTS;
pm->ps->rocketLockTime = 0;
}
else
{
if (pm->ps->rocketLockTime != -1)
{
pm->ps->rocketLastValidTime = pm->ps->rocketLockTime;
}
pm->ps->rocketLockTime = -1;
}
}
break;
//------------------
case WP_THERMAL:
if ( pm->cmd.buttons & BUTTON_ALT_ATTACK )
{
altFire = qtrue; // override default of not being an alt-fire
charging = qtrue;
}
else if ( pm->cmd.buttons & BUTTON_ATTACK )
{
charging = qtrue;
}
break;
case WP_DEMP2:
if ( pm->cmd.buttons & BUTTON_ALT_ATTACK )
{
altFire = qtrue; // override default of not being an alt-fire
charging = qtrue;
}
break;
case WP_DISRUPTOR:
if ((pm->cmd.buttons & BUTTON_ATTACK) &&
pm->ps->zoomMode == 1 &&
pm->ps->zoomLocked)
{
charging = qtrue;
altFire = qtrue;
}
if (pm->ps->zoomMode != 1 &&
pm->ps->weaponstate == WEAPON_CHARGING_ALT)
{
pm->ps->weaponstate = WEAPON_READY;
charging = qfalse;
altFire = qfalse;
}
} // end switch
// set up the appropriate weapon state based on the button that's down.
// Note that we ALWAYS return if charging is set ( meaning the buttons are still down )
if ( charging )
{
if ( altFire )
{
if ( pm->ps->weaponstate != WEAPON_CHARGING_ALT )
{
// charge isn't started, so do it now
pm->ps->weaponstate = WEAPON_CHARGING_ALT;
pm->ps->weaponChargeTime = pm->cmd.serverTime;
pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].altChargeSubTime;
#ifdef _DEBUG
Com_Printf("Starting charge\n");
#endif
assert(pm->ps->weapon > WP_NONE);
BG_AddPredictableEventToPlayerstate(EV_WEAPON_CHARGE_ALT, pm->ps->weapon, pm->ps);
}
if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < (weaponData[pm->ps->weapon].altChargeSub+weaponData[pm->ps->weapon].altEnergyPerShot))
{
pm->ps->weaponstate = WEAPON_CHARGING_ALT;
goto rest;
}
else if ((pm->cmd.serverTime - pm->ps->weaponChargeTime) < weaponData[pm->ps->weapon].altMaxCharge)
{
if (pm->ps->weaponChargeSubtractTime < pm->cmd.serverTime)
{
pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= weaponData[pm->ps->weapon].altChargeSub;
pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].altChargeSubTime;
}
}
}
else
{
if ( pm->ps->weaponstate != WEAPON_CHARGING )
{
// charge isn't started, so do it now
pm->ps->weaponstate = WEAPON_CHARGING;
pm->ps->weaponChargeTime = pm->cmd.serverTime;
pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].chargeSubTime;
#ifdef _DEBUG
Com_Printf("Starting charge\n");
#endif
BG_AddPredictableEventToPlayerstate(EV_WEAPON_CHARGE, pm->ps->weapon, pm->ps);
}
if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < (weaponData[pm->ps->weapon].chargeSub+weaponData[pm->ps->weapon].energyPerShot))
{
pm->ps->weaponstate = WEAPON_CHARGING;
goto rest;
}
else if ((pm->cmd.serverTime - pm->ps->weaponChargeTime) < weaponData[pm->ps->weapon].maxCharge)
{
if (pm->ps->weaponChargeSubtractTime < pm->cmd.serverTime)
{
pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= weaponData[pm->ps->weapon].chargeSub;
pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].chargeSubTime;
}
}
}
return qtrue; // short-circuit rest of weapon code
}
rest:
// Only charging weapons should be able to set these states...so....
// let's see which fire mode we need to set up now that the buttons are up
if ( pm->ps->weaponstate == WEAPON_CHARGING )
{
// weapon has a charge, so let us do an attack
#ifdef _DEBUG
Com_Printf("Firing. Charge time=%d\n", pm->cmd.serverTime - pm->ps->weaponChargeTime);
#endif
// dumb, but since we shoot a charged weapon on button-up, we need to repress this button for now
pm->cmd.buttons |= BUTTON_ATTACK;
pm->ps->eFlags |= EF_FIRING;
}
else if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT )
{
// weapon has a charge, so let us do an alt-attack
#ifdef _DEBUG
Com_Printf("Firing. Charge time=%d\n", pm->cmd.serverTime - pm->ps->weaponChargeTime);
#endif
// dumb, but since we shoot a charged weapon on button-up, we need to repress this button for now
pm->cmd.buttons |= BUTTON_ALT_ATTACK;
pm->ps->eFlags |= (EF_FIRING|EF_ALT_FIRING);
}
return qfalse; // continue with the rest of the weapon code
}
#define BOWCASTER_CHARGE_UNIT 200.0f // bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in g_weapon
#define BRYAR_CHARGE_UNIT 200.0f // bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in g_weapon
int PM_ItemUsable(playerState_t *ps, int forcedUse)
{
vec3_t fwd, fwdorg, dest, pos;
vec3_t yawonly;
vec3_t mins, maxs;
vec3_t trtest;
trace_t tr;
if (ps->usingATST)
{
return 0;
}
if (ps->pm_flags & PMF_USE_ITEM_HELD)
{ //force to let go first
return 0;
}
if (ps->duelInProgress)
{ //not allowed to use holdables while in a private duel.
return 0;
}
if (!forcedUse)
{
forcedUse = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag;
}
switch (forcedUse)
{
case HI_MEDPAC:
if (ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH])
{
return 0;
}
if (ps->stats[STAT_HEALTH] <= 0 ||
(ps->eFlags & EF_DEAD))
{
return 0;
}
return 1;
case HI_SEEKER:
if (ps->eFlags & EF_SEEKERDRONE)
{
PM_AddEventWithParm(EV_ITEMUSEFAIL, SEEKER_ALREADYDEPLOYED);
return 0;
}
return 1;
case HI_SENTRY_GUN:
if (ps->fd.sentryDeployed)
{
PM_AddEventWithParm(EV_ITEMUSEFAIL, SENTRY_ALREADYPLACED);
return 0;
}
yawonly[ROLL] = 0;
yawonly[PITCH] = 0;
yawonly[YAW] = ps->viewangles[YAW];
VectorSet( mins, -8, -8, 0 );
VectorSet( maxs, 8, 8, 24 );
AngleVectors(yawonly, fwd, NULL, NULL);
fwdorg[0] = ps->origin[0] + fwd[0]*64;
fwdorg[1] = ps->origin[1] + fwd[1]*64;
fwdorg[2] = ps->origin[2] + fwd[2]*64;
trtest[0] = fwdorg[0] + fwd[0]*16;
trtest[1] = fwdorg[1] + fwd[1]*16;
trtest[2] = fwdorg[2] + fwd[2]*16;
pm->trace(&tr, ps->origin, mins, maxs, trtest, ps->clientNum, MASK_PLAYERSOLID);
if ((tr.fraction != 1 && tr.entityNum != ps->clientNum) || tr.startsolid || tr.allsolid)
{
PM_AddEventWithParm(EV_ITEMUSEFAIL, SENTRY_NOROOM);
return 0;
}
return 1;
case HI_SHIELD:
mins[0] = -8;
mins[1] = -8;
mins[2] = 0;
maxs[0] = 8;
maxs[1] = 8;
maxs[2] = 8;
AngleVectors (ps->viewangles, fwd, NULL, NULL);
fwd[2] = 0;
VectorMA(ps->origin, 64, fwd, dest);
pm->trace(&tr, ps->origin, mins, maxs, dest, ps->clientNum, MASK_SHOT );
if (tr.fraction > 0.9 && !tr.startsolid && !tr.allsolid)
{
VectorCopy(tr.endpos, pos);
VectorSet( dest, pos[0], pos[1], pos[2] - 4096 );
pm->trace( &tr, pos, mins, maxs, dest, ps->clientNum, MASK_SOLID );
if ( !tr.startsolid && !tr.allsolid )
{
return 1;
}
}
PM_AddEventWithParm(EV_ITEMUSEFAIL, SHIELD_NOROOM);
return 0;
default:
return 1;
}
}
/*
==============
PM_Weapon
Generates weapon events and modifes the weapon counter
==============
*/
static void PM_Weapon( void )
{
int addTime;
int amount;
int killAfterItem = 0;
if (pm->ps->usingATST)
{
if ( pm->ps->weaponTime > 0 )
{
pm->ps->weaponTime -= pml.msec;
}
if (pm->ps->weaponTime < 1 && (pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)))
{
pm->ps->weaponTime += 500;
if (pm->ps->atstAltFire)
{
PM_AddEvent( EV_ALT_FIRE );
pm->ps->atstAltFire = qfalse;
}
else
{
PM_AddEvent( EV_FIRE_WEAPON );
pm->ps->atstAltFire = qtrue;
}
}
return;
}
if (pm->ps->weapon != WP_DISRUPTOR && pm->ps->weapon != WP_ROCKET_LAUNCHER)
{ //check for exceeding max charge time if not using disruptor or rocket launcher
if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT )
{
int timeDif = (pm->cmd.serverTime - pm->ps->weaponChargeTime);
if (timeDif > MAX_WEAPON_CHARGE_TIME)
{
pm->cmd.buttons &= ~BUTTON_ALT_ATTACK;
}
}
if ( pm->ps->weaponstate == WEAPON_CHARGING )
{
int timeDif = (pm->cmd.serverTime - pm->ps->weaponChargeTime);
if (timeDif > MAX_WEAPON_CHARGE_TIME)
{
pm->cmd.buttons &= ~BUTTON_ATTACK;
}
}
}
if (pm->ps->forceHandExtend == HANDEXTEND_WEAPONREADY)
{ //reset into weapon stance
if (pm->ps->weapon != WP_SABER)
{ //saber handles its own anims
if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1)
{
//PM_StartTorsoAnim( TORSO_WEAPONREADY4 );
PM_StartTorsoAnim( TORSO_RAISEWEAP1);
}
else
{
if (pm->ps->weapon == WP_EMPLACED_GUN)
{
PM_StartTorsoAnim( BOTH_GUNSIT1 );
}
else
{
//PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] );
PM_StartTorsoAnim( TORSO_RAISEWEAP1);
}
}
}
//we now go into a weapon raise anim after every force hand extend.
//this is so that my holster-view-weapon-when-hand-extend stuff works.
pm->ps->weaponstate = WEAPON_RAISING;
pm->ps->weaponTime += 250;
pm->ps->forceHandExtend = HANDEXTEND_NONE;
}
else if (pm->ps->forceHandExtend != HANDEXTEND_NONE)
{ //nothing else should be allowed to happen during this time, including weapon fire
int desiredAnim = 0;
qboolean seperateOnTorso = qfalse;
int desiredOnTorso = 0;
switch(pm->ps->forceHandExtend)
{
case HANDEXTEND_FORCEPUSH:
desiredAnim = BOTH_FORCEPUSH;
break;
case HANDEXTEND_FORCEPULL:
desiredAnim = BOTH_FORCEPULL;
break;
case HANDEXTEND_FORCEGRIP:
desiredAnim = BOTH_FORCEGRIP_HOLD;
break;
case HANDEXTEND_SABERPULL:
desiredAnim = BOTH_SABERPULL;
break;
case HANDEXTEND_CHOKE:
desiredAnim = BOTH_CHOKE3; //left-handed choke
break;
case HANDEXTEND_DODGE:
desiredAnim = pm->ps->forceDodgeAnim;
break;
case HANDEXTEND_KNOCKDOWN:
if (pm->ps->forceDodgeAnim)
{
if (pm->ps->forceDodgeAnim > 4)
{ //this means that we want to play a sepereate anim on the torso
int originalDAnim = pm->ps->forceDodgeAnim-8; //-8 is the original legs anim
if (originalDAnim == 2)
{
desiredAnim = BOTH_FORCE_GETUP_B1;
}
else if (originalDAnim == 3)
{
desiredAnim = BOTH_FORCE_GETUP_B3;
}
else
{
desiredAnim = BOTH_GETUP1;
}
//now specify the torso anim
seperateOnTorso = qtrue;
desiredOnTorso = BOTH_FORCEPUSH;
}
else if (pm->ps->forceDodgeAnim == 2)
{
desiredAnim = BOTH_FORCE_GETUP_B1;
}
else if (pm->ps->forceDodgeAnim == 3)
{
desiredAnim = BOTH_FORCE_GETUP_B3;
}
else
{
desiredAnim = BOTH_GETUP1;
}
}
else
{
desiredAnim = BOTH_KNOCKDOWN1;
}
break;
case HANDEXTEND_DUELCHALLENGE:
desiredAnim = BOTH_ENGAGETAUNT;
break;
case HANDEXTEND_TAUNT:
desiredAnim = pm->ps->forceDodgeAnim;
break;
//Hmm... maybe use these, too?
//BOTH_FORCEHEAL_QUICK //quick heal (SP level 2 & 3)
//BOTH_MINDTRICK1 // wave (maybe for mind trick 2 & 3 - whole area, and for force seeing)
//BOTH_MINDTRICK2 // tap (maybe for mind trick 1 - one person)
//BOTH_FORCEGRIP_START //start grip
//BOTH_FORCEGRIP_HOLD //hold grip
//BOTH_FORCEGRIP_RELEASE //release grip
//BOTH_FORCELIGHTNING //quick lightning burst (level 1)
//BOTH_FORCELIGHTNING_START //start lightning
//BOTH_FORCELIGHTNING_HOLD //hold lightning
//BOTH_FORCELIGHTNING_RELEASE //release lightning
default:
desiredAnim = BOTH_FORCEPUSH;
break;
}
if (!seperateOnTorso)
{ //of seperateOnTorso, handle it after setting the legs
PM_SetAnim(SETANIM_TORSO, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100);
pm->ps->torsoTimer = 1;
}
if (pm->ps->forceHandExtend == HANDEXTEND_DODGE || pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN ||
(pm->ps->forceHandExtend == HANDEXTEND_CHOKE && pm->ps->groundEntityNum == ENTITYNUM_NONE) )
{ //special case, play dodge anim on whole body, choke anim too if off ground
if (seperateOnTorso)
{
PM_SetAnim(SETANIM_LEGS, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100);
pm->ps->legsTimer = 1;
PM_SetAnim(SETANIM_TORSO, desiredOnTorso, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100);
pm->ps->torsoTimer = 1;
}
else
{
PM_SetAnim(SETANIM_LEGS, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100);
pm->ps->legsTimer = 1;
}
}
return;
}
if (BG_InSpecialJump(pm->ps->legsAnim) ||
BG_InRoll(pm->ps, pm->ps->legsAnim) ||
PM_InRollComplete(pm->ps, pm->ps->legsAnim))
{
pm->cmd.weapon = WP_SABER;
pm->ps->weapon = WP_SABER;
}
if (pm->ps->duelInProgress)
{
pm->cmd.weapon = WP_SABER;
pm->ps->weapon = WP_SABER;
if (pm->ps->duelTime >= pm->cmd.serverTime)
{
pm->cmd.upmove = 0;
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
}
}
if (pm->ps->weapon == WP_SABER && pm->ps->saberMove != LS_READY && pm->ps->saberMove != LS_NONE)
{
pm->cmd.weapon = WP_SABER; //don't allow switching out mid-attack
}
if (pm->ps->weapon == WP_SABER)
{
//rww - we still need the item stuff, so we won't return immediately
PM_WeaponLightsaber();
killAfterItem = 1;
}
else
{
pm->ps->saberHolstered = qfalse;
}
if (pm->ps->weapon == WP_THERMAL ||
pm->ps->weapon == WP_TRIP_MINE ||
pm->ps->weapon == WP_DET_PACK)
{
if (pm->ps->weapon == WP_THERMAL)
{
if ((pm->ps->torsoAnim&~ANIM_TOGGLEBIT) == WeaponAttackAnim[pm->ps->weapon] &&
(pm->ps->weaponTime-200) <= 0)
{
PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] );
}
}
else
{
if ((pm->ps->torsoAnim&~ANIM_TOGGLEBIT) == WeaponAttackAnim[pm->ps->weapon] &&
(pm->ps->weaponTime-700) <= 0)
{
PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] );
}
}
}
// don't allow attack until all buttons are up
if ( pm->ps->pm_flags & PMF_RESPAWNED ) {
return;
}
// ignore if spectator
if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
return;
}
// check for dead player
if ( pm->ps->stats[STAT_HEALTH] <= 0 ) {
pm->ps->weapon = WP_NONE;
return;
}
// check for item using
if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) {
if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) {
if (!pm->ps->stats[STAT_HOLDABLE_ITEM])
{
return;
}
if (!PM_ItemUsable(pm->ps, 0))
{
pm->ps->pm_flags |= PMF_USE_ITEM_HELD;
return;
}
else
{
if (pm->ps->stats[STAT_HOLDABLE_ITEMS] & (1 << bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag))
{
if (bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_BINOCULARS)
{ //never use up the binoculars
pm->ps->stats[STAT_HOLDABLE_ITEMS] -= (1 << bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag);
}
}
else
{
return; //this should not happen...
}
pm->ps->pm_flags |= PMF_USE_ITEM_HELD;
PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag );
if (bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_BINOCULARS)
{
pm->ps->stats[STAT_HOLDABLE_ITEM] = 0;
BG_CycleInven(pm->ps, 1);
}
}
return;
}
} else {
pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD;
}
if (pm->ps->weapon == WP_SABER)
{ //we can't toggle zoom while using saber (for obvious reasons) so make sure it's always off
pm->ps->zoomMode = 0;
pm->ps->zoomFov = 0;
pm->ps->zoomLocked = qfalse;
pm->ps->zoomLockTime = 0;
}
if (killAfterItem)
{
return;
}
// make weapon function
if ( pm->ps->weaponTime > 0 ) {
pm->ps->weaponTime -= pml.msec;
}
if (pm->ps->isJediMaster && pm->ps->emplacedIndex)
{
pm->ps->emplacedIndex = 0;
}
if (pm->ps->duelInProgress && pm->ps->emplacedIndex)
{
pm->ps->emplacedIndex = 0;
}
if (pm->ps->weapon == WP_EMPLACED_GUN && pm->ps->emplacedIndex)
{
pm->cmd.weapon = WP_EMPLACED_GUN; //No switch for you!
PM_StartTorsoAnim( BOTH_GUNSIT1 );
}
if (pm->ps->isJediMaster || pm->ps->duelInProgress || pm->ps->trueJedi)
{
pm->cmd.weapon = WP_SABER;
pm->ps->weapon = WP_SABER;
if (pm->ps->isJediMaster || pm->ps->trueJedi)
{
pm->ps->stats[STAT_WEAPONS] = (1 << WP_SABER);
}
}
amount = weaponData[pm->ps->weapon].energyPerShot;
// take an ammo away if not infinite
if ( pm->ps->weapon != WP_NONE &&
pm->ps->weapon == pm->cmd.weapon &&
(pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING) )
{
if ( pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 )
{
// enough energy to fire this weapon?
if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < weaponData[pm->ps->weapon].energyPerShot &&
pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < weaponData[pm->ps->weapon].altEnergyPerShot)
{ //the weapon is out of ammo essentially because it cannot fire primary or secondary, so do the switch
//regardless of if the player is attacking or not
PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon );
if (pm->ps->weaponTime < 500)
{
pm->ps->weaponTime += 500;
}
return;
}
if (pm->ps->weapon == WP_DET_PACK && !pm->ps->hasDetPackPlanted && pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < 1)
{
PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon );
if (pm->ps->weaponTime < 500)
{
pm->ps->weaponTime += 500;
}
return;
}
}
}
// check for weapon change
// can't change if weapon is firing, but can change
// again if lowering or raising
if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) {
if ( pm->ps->weapon != pm->cmd.weapon ) {
PM_BeginWeaponChange( pm->cmd.weapon );
}
}
if ( pm->ps->weaponTime > 0 ) {
return;
}
// change weapon if time
if ( pm->ps->weaponstate == WEAPON_DROPPING ) {
PM_FinishWeaponChange();
return;
}
if ( pm->ps->weaponstate == WEAPON_RAISING ) {
pm->ps->weaponstate = WEAPON_READY;
if ( pm->ps->weapon == WP_SABER ) {
PM_StartTorsoAnim( PM_GetSaberStance() );
} else {
if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1)
{
PM_StartTorsoAnim( TORSO_WEAPONREADY4 );
}
else
{
if (pm->ps->weapon == WP_EMPLACED_GUN)
{
PM_StartTorsoAnim( BOTH_GUNSIT1 );
}
else
{
PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] );
}
}
}
return;
}
if (((pm->ps->torsoAnim & ~ANIM_TOGGLEBIT) == TORSO_WEAPONREADY4 ||
(pm->ps->torsoAnim & ~ANIM_TOGGLEBIT) == BOTH_ATTACK4) &&
(pm->ps->weapon != WP_DISRUPTOR || pm->ps->zoomMode != 1))
{
if (pm->ps->weapon == WP_EMPLACED_GUN)
{
PM_StartTorsoAnim( BOTH_GUNSIT1 );
}
else
{
PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] );
}
}
else if (((pm->ps->torsoAnim & ~ANIM_TOGGLEBIT) != TORSO_WEAPONREADY4 &&
(pm->ps->torsoAnim & ~ANIM_TOGGLEBIT) != BOTH_ATTACK4) &&
(pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1))
{
PM_StartTorsoAnim( TORSO_WEAPONREADY4 );
}
if (pm->ps->weapon != WP_ROCKET_LAUNCHER)
{
pm->ps->rocketLockIndex = MAX_CLIENTS;
pm->ps->rocketLockTime = 0;
pm->ps->rocketTargetTime = 0;
}
if ( PM_DoChargedWeapons())
{
// In some cases the charged weapon code may want us to short circuit the rest of the firing code
return;
}
// check for fire
if ( ! (pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)))
{
pm->ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
return;
}
if (pm->ps->weapon == WP_EMPLACED_GUN)
{
addTime = weaponData[pm->ps->weapon].fireTime;
pm->ps->weaponTime += addTime;
PM_AddEvent( EV_FIRE_WEAPON );
return;
}
if (pm->ps->weapon == WP_DISRUPTOR &&
(pm->cmd.buttons & BUTTON_ALT_ATTACK) &&
!pm->ps->zoomLocked)
{
return;
}
if (pm->ps->weapon == WP_DISRUPTOR &&
(pm->cmd.buttons & BUTTON_ALT_ATTACK) &&
pm->ps->zoomMode == 2)
{ //can't use disruptor secondary while zoomed binoculars
return;
}
if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1)
{
PM_StartTorsoAnim( BOTH_ATTACK4 );
}
else
{
PM_StartTorsoAnim( WeaponAttackAnim[pm->ps->weapon] );
}
if ( pm->cmd.buttons & BUTTON_ALT_ATTACK )
{
amount = weaponData[pm->ps->weapon].altEnergyPerShot;
}
else
{
amount = weaponData[pm->ps->weapon].energyPerShot;
}
pm->ps->weaponstate = WEAPON_FIRING;
// take an ammo away if not infinite
if ( pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 )
{
// enough energy to fire this weapon?
if ((pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - amount) >= 0)
{
pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= amount;
}
else // Not enough energy
{
// Switch weapons
if (pm->ps->weapon != WP_DET_PACK || !pm->ps->hasDetPackPlanted)
{
PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon );
if (pm->ps->weaponTime < 500)
{
pm->ps->weaponTime += 500;
}
}
return;
}
}
if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) {
if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode != 1)
{
PM_AddEvent( EV_FIRE_WEAPON );
addTime = weaponData[pm->ps->weapon].fireTime;
}
else
{
PM_AddEvent( EV_ALT_FIRE );
addTime = weaponData[pm->ps->weapon].altFireTime;
}
}
else {
PM_AddEvent( EV_FIRE_WEAPON );
addTime = weaponData[pm->ps->weapon].fireTime;
}
if ( pm->ps->powerups[PW_HASTE] ) {
addTime /= 1.3;
}
if (pm->ps->fd.forcePowersActive & (1 << FP_RAGE))
{
addTime *= 0.75;
}
else if (pm->ps->fd.forceRageRecoveryTime > pm->cmd.serverTime)
{
addTime *= 1.5;
}
pm->ps->weaponTime += addTime;
}
/*
================
PM_Animate
================
*/
static void PM_Animate( void ) {
if ( pm->cmd.buttons & BUTTON_GESTURE ) {
if ( pm->ps->torsoTimer < 1 && pm->ps->forceHandExtend == HANDEXTEND_NONE &&
pm->ps->legsTimer < 1 && pm->ps->weaponTime < 1 && pm->ps->saberLockTime < pm->cmd.serverTime) {
pm->ps->forceHandExtend = HANDEXTEND_TAUNT;
//FIXME: random taunt anims?
pm->ps->forceDodgeAnim = BOTH_ENGAGETAUNT;
pm->ps->forceHandExtendTime = pm->cmd.serverTime + 1000;
pm->ps->weaponTime = 100;
PM_AddEvent( EV_TAUNT );
}
#if 0
// Here's an interesting bit. The bots in TA used buttons to do additional gestures.
// I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2.
// We can always add some back in if we want though.
} else if ( pm->cmd.buttons & BUTTON_GETFLAG ) {
if ( pm->ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_GETFLAG );
pm->ps->torsoTimer = 600; //TIMER_GESTURE;
}
} else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) {
if ( pm->ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_GUARDBASE );
pm->ps->torsoTimer = 600; //TIMER_GESTURE;
}
} else if ( pm->cmd.buttons & BUTTON_PATROL ) {
if ( pm->ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_PATROL );
pm->ps->torsoTimer = 600; //TIMER_GESTURE;
}
} else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) {
if ( pm->ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_FOLLOWME );
pm->ps->torsoTimer = 600; //TIMER_GESTURE;
}
} else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) {
if ( pm->ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_AFFIRMATIVE);
pm->ps->torsoTimer = 600; //TIMER_GESTURE;
}
} else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) {
if ( pm->ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_NEGATIVE );
pm->ps->torsoTimer = 600; //TIMER_GESTURE;
}
#endif //
}
}
/*
================
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;
}
}
// drop animation counter
if ( pm->ps->legsTimer > 0 ) {
pm->ps->legsTimer -= pml.msec;
if ( pm->ps->legsTimer < 0 ) {
pm->ps->legsTimer = 0;
}
}
if ( pm->ps->torsoTimer > 0 ) {
pm->ps->torsoTimer -= pml.msec;
if ( pm->ps->torsoTimer < 0 ) {
pm->ps->torsoTimer = 0;
}
}
}
/*
================
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;
if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) {
return; // no view changes at all
}
if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) {
return; // no view changes at all
}
// circularly clamp the angles with deltas
for (i=0 ; i<3 ; i++) {
temp = cmd->angles[i] + ps->delta_angles[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];
temp = 16000;
} else if ( temp < -16000 ) {
ps->delta_angles[i] = -16000 - cmd->angles[i];
temp = -16000;
}
}
ps->viewangles[i] = SHORT2ANGLE(temp);
}
}
//-------------------------------------------
void PM_AdjustAttackStates( pmove_t *pm )
//-------------------------------------------
{
int amount;
// get ammo usage
if ( pm->cmd.buttons & BUTTON_ALT_ATTACK )
{
amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].altEnergyPerShot;
}
else
{
amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].energyPerShot;
}
// disruptor alt-fire should toggle the zoom mode, but only bother doing this for the player?
if ( pm->ps->weapon == WP_DISRUPTOR && pm->ps->weaponstate == WEAPON_READY )
{
if ( !(pm->ps->eFlags & EF_ALT_FIRING) && (pm->cmd.buttons & BUTTON_ALT_ATTACK) /*&&
pm->cmd.upmove <= 0 && !pm->cmd.forwardmove && !pm->cmd.rightmove*/)
{
// We just pressed the alt-fire key
if ( !pm->ps->zoomMode )
{
// not already zooming, so do it now
pm->ps->zoomMode = 1;
pm->ps->zoomLocked = qfalse;
pm->ps->zoomFov = 80.0f;//cg_fov.value;
pm->ps->zoomLockTime = pm->cmd.serverTime + 50;
PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND);
}
else if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime)
{ //check for == 1 so we can't turn binoculars off with disruptor alt fire
// already zooming, so must be wanting to turn it off
pm->ps->zoomMode = 0;
pm->ps->zoomTime = pm->ps->commandTime;
pm->ps->zoomLocked = qfalse;
PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND);
}
}
else if ( !(pm->cmd.buttons & BUTTON_ALT_ATTACK ) && pm->ps->zoomLockTime < pm->cmd.serverTime)
{
// Not pressing zoom any more
if ( pm->ps->zoomMode )
{
if (pm->ps->zoomMode == 1 && !pm->ps->zoomLocked)
{ //approximate what level the client should be zoomed at based on how long zoom was held
pm->ps->zoomFov = ((pm->cmd.serverTime+50) - pm->ps->zoomLockTime) * 0.035f;
if (pm->ps->zoomFov > 50)
{
pm->ps->zoomFov = 50;
}
if (pm->ps->zoomFov < 1)
{
pm->ps->zoomFov = 1;
}
}
// were zooming in, so now lock the zoom
pm->ps->zoomLocked = qtrue;
}
}
//This seemed like a good idea, but apparently it confuses people. So disabled for now.
/*
else if (!(pm->ps->eFlags & EF_ALT_FIRING) && (pm->cmd.buttons & BUTTON_ALT_ATTACK) &&
(pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove))
{ //if you try to zoom while moving, just convert it into a primary attack
pm->cmd.buttons &= ~BUTTON_ALT_ATTACK;
pm->cmd.buttons |= BUTTON_ATTACK;
}
*/
if (pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove)
{
if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime)
{ //check for == 1 so we can't turn binoculars off with disruptor alt fire
pm->ps->zoomMode = 0;
pm->ps->zoomTime = pm->ps->commandTime;
pm->ps->zoomLocked = qfalse;
PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND);
}
}
if ( pm->cmd.buttons & BUTTON_ATTACK )
{
// If we are zoomed, we should switch the ammo usage to the alt-fire, otherwise, we'll
// just use whatever ammo was selected from above
if ( pm->ps->zoomMode )
{
amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] -
weaponData[pm->ps->weapon].altEnergyPerShot;
}
}
else
{
// alt-fire button pressing doesn't use any ammo
amount = 0;
}
}
else if (pm->ps->weapon == WP_DISRUPTOR) //still perform certain checks, even if the weapon is not ready
{
if (pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove)
{
if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime)
{ //check for == 1 so we can't turn binoculars off with disruptor alt fire
pm->ps->zoomMode = 0;
pm->ps->zoomTime = pm->ps->commandTime;
pm->ps->zoomLocked = qfalse;
PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND);
}
}
}
// set the firing flag for continuous beam weapons, saber will fire even if out of ammo
if ( !(pm->ps->pm_flags & PMF_RESPAWNED) &&
pm->ps->pm_type != PM_INTERMISSION &&
( pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) &&
( amount >= 0 || pm->ps->weapon == WP_SABER ))
{
if ( pm->cmd.buttons & BUTTON_ALT_ATTACK )
{
pm->ps->eFlags |= EF_ALT_FIRING;
}
else
{
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);
}
// disruptor should convert a main fire to an alt-fire if the gun is currently zoomed
if ( pm->ps->weapon == WP_DISRUPTOR)
{
if ( pm->cmd.buttons & BUTTON_ATTACK && pm->ps->zoomMode == 1 && pm->ps->zoomLocked)
{
// converting the main fire to an alt-fire
pm->cmd.buttons |= BUTTON_ALT_ATTACK;
pm->ps->eFlags |= EF_ALT_FIRING;
}
else if ( pm->cmd.buttons & BUTTON_ALT_ATTACK && pm->ps->zoomMode == 1 && pm->ps->zoomLocked)
{
pm->cmd.buttons &= ~BUTTON_ALT_ATTACK;
pm->ps->eFlags &= ~EF_ALT_FIRING;
}
}
}
void BG_CmdForRoll( int anim, usercmd_t *pCmd )
{
switch ( (anim&~ANIM_TOGGLEBIT) )
{
case BOTH_ROLL_F:
pCmd->forwardmove = 127;
pCmd->rightmove = 0;
break;
case BOTH_ROLL_B:
pCmd->forwardmove = -127;
pCmd->rightmove = 0;
break;
case BOTH_ROLL_R:
pCmd->forwardmove = 0;
pCmd->rightmove = 127;
break;
case BOTH_ROLL_L:
pCmd->forwardmove = 0;
pCmd->rightmove = -127;
break;
}
pCmd->upmove = 0;
}
qboolean PM_SaberInTransition( int move );
void BG_AdjustClientSpeed(playerState_t *ps, usercmd_t *cmd, int svTime)
{
//For prediction, always reset speed back to the last known server base speed
//If we didn't do this, under lag we'd eventually dwindle speed down to 0 even though
//that would not be the correct predicted value.
ps->speed = ps->basespeed;
if (ps->forceHandExtend == HANDEXTEND_DODGE)
{
ps->speed = 0;
}
if (ps->forceHandExtend == HANDEXTEND_KNOCKDOWN)
{
ps->speed = 0;
}
if (ps->usingATST && (cmd->rightmove ||
cmd->forwardmove))
{
if (!ps->holdMoveTime)
{
ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
| BOTH_RUN1START;
ps->holdMoveTime = svTime;
}
}
else
{
ps->holdMoveTime = 0;
if (ps->usingATST)
{
ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
| BOTH_STAND1;
}
}
if (ps->usingATST &&
((svTime - ps->holdMoveTime) < 500 ||
!ps->holdMoveTime))
{
ps->speed = 0;
}
else if (ps->usingATST)
{
if ((svTime - ps->holdMoveTime) < 600)
{
ps->speed *= 0.4;
}
else if ((svTime - ps->holdMoveTime) < 1000)
{
ps->speed *= 0.5;
}
else if ((svTime - ps->holdMoveTime) < 1400)
{
ps->speed *= 0.6;
}
else if ((svTime - ps->holdMoveTime) < 1700)
{
ps->speed *= 0.7;
}
else if ((svTime - ps->holdMoveTime) < 1900)
{
ps->speed *= 0.8;
}
if (cmd->forwardmove < 0)
{
ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
| BOTH_WALKBACK1;
ps->speed *= 0.6;
}
else
{
ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT )
| BOTH_RUN1;
}
}
else if ( cmd->forwardmove < 0 && !(cmd->buttons&BUTTON_WALKING) && pm->ps->groundEntityNum != ENTITYNUM_NONE )
{//running backwards is slower than running forwards (like SP)
ps->speed *= 0.75;
}
if (ps->fd.forcePowersActive & (1 << FP_GRIP))
{
ps->speed *= 0.4;
}
if (ps->fd.forcePowersActive & (1 << FP_SPEED))
{
if (ps->fd.forceSpeedSmash < 1.2)
{
ps->fd.forceSpeedSmash = 1.2;
}
if (ps->fd.forceSpeedSmash > forceSpeedLevels[ps->fd.forcePowerLevel[FP_SPEED]]) //2.8
{
ps->fd.forceSpeedSmash = forceSpeedLevels[ps->fd.forcePowerLevel[FP_SPEED]];
}
ps->speed *= ps->fd.forceSpeedSmash;
ps->fd.forceSpeedSmash += 0.005f;
}
if (ps->fd.forcePowersActive & (1 << FP_RAGE))
{
ps->speed *= 1.3;
}
else if (ps->fd.forceRageRecoveryTime > svTime)
{
ps->speed *= 0.75;
}
if (ps->fd.forceGripCripple)
{
if (ps->fd.forcePowersActive & (1 << FP_RAGE))
{
ps->speed *= 0.9;
}
else if (ps->fd.forcePowersActive & (1 << FP_SPEED))
{ //force speed will help us escape
ps->speed *= 0.8;
}
else
{
ps->speed *= 0.2;
}
}
if ( BG_SaberInAttack( ps->saberMove ) && cmd->forwardmove < 0 )
{//if running backwards while attacking, don't run as fast.
switch( ps->fd.saberAnimLevel )
{
case FORCE_LEVEL_1:
ps->speed *= 0.75f;
break;
case FORCE_LEVEL_2:
ps->speed *= 0.60f;
break;
case FORCE_LEVEL_3:
ps->speed *= 0.45f;
break;
default:
break;
}
}
else if ( BG_SpinningSaberAnim( ps->legsAnim ) )
{
if (ps->fd.saberAnimLevel == FORCE_LEVEL_3)
{
ps->speed *= 0.3f;
}
else
{
ps->speed *= 0.5f;
}
}
else if ( ps->weapon == WP_SABER && BG_SaberInAttack( ps->saberMove ) )
{//if attacking with saber while running, drop your speed
switch( ps->fd.saberAnimLevel )
{
case FORCE_LEVEL_2:
ps->speed *= 0.85f;
break;
case FORCE_LEVEL_3:
ps->speed *= 0.55f;
break;
default:
break;
}
}
else if (ps->weapon == WP_SABER && ps->fd.saberAnimLevel == FORCE_LEVEL_3 &&
PM_SaberInTransition(ps->saberMove))
{ //Now, we want to even slow down in transitions for level 3 (since it has chains and stuff now)
if (cmd->forwardmove < 0)
{
ps->speed *= 0.4f;
}
else
{
ps->speed *= 0.6f;
}
}
if ( BG_InRoll( ps, ps->legsAnim ) && ps->speed > 200 )
{ //can't roll unless you're able to move normally
BG_CmdForRoll( ps->legsAnim, cmd );
if ((ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_ROLL_B)
{ //backwards roll is pretty fast, should also be slower
ps->speed = ps->legsTimer/2.5;
}
else
{
ps->speed = ps->legsTimer/1.5;//450;
}
if (ps->speed > 600)
{
ps->speed = 600;
}
//Automatically slow down as the roll ends.
}
}
/*
================
PmoveSingle
================
*/
void trap_SnapVector( float *v );
void PmoveSingle (pmove_t *pmove) {
pm = pmove;
gPMDoSlowFall = PM_DoSlowFall();
// 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->pm_type == PM_FLOAT)
{ //You get no control over where you go in grip movement
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
if (pm->ps->eFlags & EF_DISINTEGRATION)
{
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
if ( pm->ps->saberMove == LS_A_LUNGE )
{//can't move during lunge
pm->cmd.rightmove = pm->cmd.upmove = 0;
if ( pm->ps->legsTimer > 500 )
{
pm->cmd.forwardmove = 127;
}
else
{
pm->cmd.forwardmove = 0;
}
}
if ( pm->ps->saberMove == LS_A_JUMP_T__B_ )
{//can't move during leap
if ( pm->ps->groundEntityNum != ENTITYNUM_NONE )
{//hit the ground
pm->cmd.forwardmove = 0;
}
pm->cmd.rightmove = pm->cmd.upmove = 0;
}
if ( pm->ps->saberMove == LS_A_BACK || pm->ps->saberMove == LS_A_BACK_CR
|| pm->ps->saberMove == LS_A_BACKSTAB || pm->ps->saberMove == LS_A_FLIP_STAB ||
pm->ps->saberMove == LS_A_FLIP_SLASH || pm->ps->saberMove == LS_A_JUMP_T__B_ )
{
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) == (BOTH_A2_STABBACK1) ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == (BOTH_ATTACK_BACK) ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == (BOTH_CROUCHATTACKBACK1) ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == (BOTH_FORCELEAP2_T__B_) ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == (BOTH_JUMPFLIPSTABDOWN) ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == (BOTH_JUMPFLIPSLASHDOWN1))
{
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_KISSER1LOOP ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_KISSEE1LOOP)
{
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
if (pm->ps->emplacedIndex)
{
if (pm->cmd.forwardmove < 0)
{
pm->ps->emplacedIndex = 0;
}
else
{
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
}
if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->weaponstate == WEAPON_CHARGING_ALT)
{ //not allowed to move while charging the disruptor
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
if (pm->cmd.upmove > 0)
{
pm->cmd.upmove = 0;
}
}
BG_AdjustClientSpeed(pm->ps, &pm->cmd, pm->cmd.serverTime);
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;
}
// 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 | BUTTON_USE_HOLDABLE) ) ) {
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;
// 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;
PM_AdjustAngleForWallRun(pm->ps, &pm->cmd, qtrue);
if (pm->ps->saberMove == LS_A_JUMP_T__B_ || pm->ps->saberMove == LS_A_LUNGE ||
pm->ps->saberMove == LS_A_BACK_CR || pm->ps->saberMove == LS_A_BACK ||
pm->ps->saberMove == LS_A_BACKSTAB)
{
PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd);
}
if ((pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_KISSER1LOOP ||
(pm->ps->legsAnim&~ANIM_TOGGLEBIT) == BOTH_KISSEE1LOOP)
{
pm->ps->viewangles[PITCH] = 0;
PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd);
}
// 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_flags &= ~PMF_JUMP_HELD;
}
// 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->saberLockTime >= pm->cmd.serverTime)
{
pm->cmd.upmove = 0;
pm->cmd.forwardmove = 50;
pm->cmd.rightmove = 0;//*= 0.1;
}
if ( pm->ps->pm_type == PM_SPECTATOR ) {
PM_CheckDuck ();
PM_FlyMove ();
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 || pm->ps->pm_type == PM_SPINTERMISSION) {
return; // no movement at all
}
if (gPMDoSlowFall)
{
pm->ps->gravity *= 0.5;
}
// set watertype, and waterlevel
PM_SetWaterLevel();
pml.previous_waterlevel = pmove->waterlevel;
// set mins, maxs, and viewheight
PM_CheckDuck ();
// set groundentity
PM_GroundTrace();
if ( pm->ps->groundEntityNum != ENTITYNUM_NONE )
{//on ground
pm->ps->fd.forceJumpZStart = 0;
}
if ( pm->ps->pm_type == PM_DEAD ) {
PM_DeadMove ();
}
PM_DropTimers();
if (pm->ps->pm_type == PM_FLOAT)
{
PM_FlyMove ();
}
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();
}
}
PM_Animate();
// set groundentity, watertype, and waterlevel
PM_GroundTrace();
PM_SetWaterLevel();
if (pm->cmd.forcesel != -1 && (pm->ps->fd.forcePowersKnown & (1 << pm->cmd.forcesel)))
{
pm->ps->fd.forcePowerSelected = pm->cmd.forcesel;
}
if (pm->cmd.invensel != -1 && (pm->ps->stats[STAT_HOLDABLE_ITEMS] & (1 << pm->cmd.invensel)))
{
pm->ps->stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(pm->cmd.invensel, IT_HOLDABLE);
}
// weapons
PM_Weapon();
PM_Use();
// 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 );
if (gPMDoSlowFall)
{
pm->ps->gravity *= 2;
}
}
/*
================
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;
}
if (pmove->ps->fallingToDeath)
{
pmove->cmd.forwardmove = 0;
pmove->cmd.rightmove = 0;
pmove->cmd.upmove = 0;
pmove->cmd.buttons = 0;
}
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 );
if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) {
pmove->cmd.upmove = 20;
}
}
}