4653 lines
117 KiB
C
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;
|
|
}
|
|
}
|
|
}
|
|
|