reaction/code/game/bg_pmove.c
2013-01-04 13:27:22 +00:00

3158 lines
81 KiB
C

//-----------------------------------------------------------------------------
//
// $Id$
//
//-----------------------------------------------------------------------------
//
// $Log$
// Revision 1.96 2007/07/31 20:02:56 makro
// Quick hack disabling footstep sounds for ladder surfaces
//
// Revision 1.95 2005/09/07 20:27:41 makro
// Entity attachment trees
//
// Revision 1.94 2005/02/15 16:33:39 makro
// Tons of updates (entity tree attachment system, UI vectors)
//
// Revision 1.93 2003/03/09 19:47:48 niceass
// Support for torso pistol animations
//
// Revision 1.92 2003/02/05 04:29:14 niceass
// added support for akimbo animations
//
// Revision 1.91 2002/10/26 00:37:18 jbravo
// New multiple item code and added PB support to the UI
//
// Revision 1.90 2002/09/08 23:25:09 niceass
// made cg_rq3_predictweapons even more like quake 2, and it's simpler
//
// Revision 1.89 2002/09/04 00:16:17 makro
// Fixed 'unselectable grenade shown in the inventory if you switch weapons
// after pulling the pin' bug
//
// Revision 1.88 2002/08/03 07:04:18 jbravo
// Fixed constant gasping sound when you die in shallow water
//
// Revision 1.87 2002/07/22 06:33:58 niceass
// cleaned up the powerup code
//
// Revision 1.86 2002/07/21 18:52:39 niceass
// weapon prediction stuff
//
// Revision 1.85 2002/07/16 04:27:19 niceass
// physics back to 2.0
//
// Revision 1.84 2002/07/08 04:34:41 niceass
// oops, small mistake
//
// Revision 1.83 2002/07/08 04:34:15 niceass
// changes to gravity and stuff
//
// Revision 1.82 2002/06/29 02:50:58 niceass
// m4 kick fix and removed ladder stuff
//
// Revision 1.81 2002/06/18 06:15:30 niceass
// m4 kick now smooth
//
// Revision 1.80 2002/06/18 05:18:55 niceass
// adjustment to leg damage
//
// Revision 1.79 2002/06/16 20:06:14 jbravo
// Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap"
//
// Revision 1.78 2002/06/16 17:37:59 jbravo
// Removed the MISSIONPACK ifdefs and missionpack only code.
//
// Revision 1.77 2002/06/11 03:09:28 niceass
// +attack cancels reloads
//
// Revision 1.76 2002/06/07 19:42:44 niceass
// leg damage much closer to AQ2
//
// Revision 1.75 2002/05/27 06:48:02 niceass
// new leg damage
//
// Revision 1.74 2002/05/21 04:58:27 blaze
// kicked the reload bugs ass!
//
// Revision 1.73 2002/05/11 19:18:20 makro
// Sand surfaceparm
//
// Revision 1.72 2002/05/11 18:47:09 niceass
// last fire animation fixed
//
// Revision 1.71 2002/05/08 07:24:33 niceass
// Double jumping added. Everything feels very good.
//
// Revision 1.70 2002/05/07 05:07:32 niceass
// several physics fixes
//
// Revision 1.69 2002/05/01 21:14:59 jbravo
// Misc fixes
//
// Revision 1.68 2002/04/26 05:29:38 niceass
// PHYSICS, Take 2
//
// Revision 1.67 2002/04/20 15:03:47 makro
// More footstep sounds, a few other things
//
// Revision 1.66 2002/04/06 21:42:20 makro
// Changes to bot code. New surfaceparm system.
//
// Revision 1.65 2002/03/31 03:31:24 jbravo
// Compiler warning cleanups
//
// Revision 1.64 2002/03/27 17:20:38 jbravo
// Workaround to the bandage bug :)
//
// Revision 1.63 2002/03/23 05:17:42 jbravo
// Major cleanup of game -> cgame communication with LCA vars.
//
// Revision 1.62 2002/03/18 19:18:39 slicer
// Fixed bandage bugs ( i hope )
//
// Revision 1.61 2002/03/11 01:45:46 jbravo
// Bye bye backflips! :)
//
// Revision 1.60 2002/03/11 01:29:54 slicer
// Fixed Sniper zooming during LCA
//
// Revision 1.59 2002/03/02 22:02:09 niceass
// mk23 and akimbo last fire animation fixed
//
// Revision 1.58 2002/03/02 08:03:14 niceass
// look down movement bug fixed
//
// Revision 1.57 2002/02/27 01:54:28 jbravo
// More spectatorfixes and finally stopped all fallingdamage anims and
// sounds during LCA.
//
// Revision 1.56 2002/02/04 00:30:35 niceass
// New physics
//
// Revision 1.55 2002/01/24 14:20:53 jbravo
// Adding func_explosive and a few new surfaceparms
//
// Revision 1.54 2002/01/11 20:20:58 jbravo
// Adding TP to main branch
//
// Revision 1.53 2002/01/11 19:48:29 jbravo
// Formatted the source in non DOS format.
//
// Revision 1.52 2001/12/31 16:28:42 jbravo
// I made a Booboo with the Log tag.
//
//
//-----------------------------------------------------------------------------
// 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 "../qcommon/q_shared.h"
#include "bg_public.h"
#include "bg_local.h"
pmove_t *pm;
pml_t pml;
//Elder: reduce x-y speed on ladders by this factor so it 'feels' more like Q2
#define PM_LADDER_MOVE_REDUCTION 10
#define STOP_EPSILON 0.1f
//#define Q2PHYSICS
// movement parameters
float pm_stopspeed = 100.0f;
float pm_duckScale = 0.25f;
float pm_swimScale = 0.50f;
float pm_ladderScale = 0.75f; // more c3a ladders
float pm_maxspeed = 300;
float pm_accelerate = 10.0f;
float pm_airaccelerate = 1.0f;
float pm_wateraccelerate = 4.0f;
float pm_flyaccelerate = 8.0f;
float pm_ladderAccelerate = 3000; // same as ladder friction.
float pm_friction = 6.0f;
float pm_waterfriction = 1.0f;
float pm_flightfriction = 3.0f;
float pm_spectatorfriction = 5.0f;
float pm_ladderfriction = 3000;
int c_pmove = 0;
/* [QUARANTINE] - Weapon Animations
===================
PM_StartWeaponAnim, PM_ContinueWeaponAnim
===================
*/
static void PM_StartWeaponAnim(int anim)
{
if (pm->ps->pm_type >= PM_DEAD) {
return;
}
// NiceAss: Don't do client prediction of weapon animations
/*if ( !pm->predict && (anim == WP_ANIM_FIRE || anim == WP_ANIM_IDLE ||
anim == WP_ANIM_EXTRA1 || anim == WP_ANIM_THROWFIRE ) )
return;*/
pm->ps->generic1 = ((pm->ps->generic1 & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim;
}
static void PM_ContinueWeaponAnim(int anim)
{
if ((pm->ps->generic1 & ~ANIM_TOGGLEBIT) == anim) {
return;
}
// NiceAss: Don't do client prediction of weapon animations
/*
if ( !pm->predict && (anim == WP_ANIM_FIRE || anim == WP_ANIM_IDLE ||
anim == WP_ANIM_EXTRA1 || anim == WP_ANIM_THROWFIRE ) )
return;*/
PM_StartWeaponAnim(anim);
}
// END
/*
===============
PM_AddEvent
===============
*/
void PM_AddEvent(int newEvent)
{
BG_AddPredictableEventToPlayerstate(newEvent, 0, pm->ps);
}
/*
===============
PM_AddEvent
Elder: stuffs event parameters
Be careful because you are stuffing the player
===============
*/
void PM_AddEvent2(int newEvent, int eventParm)
{
BG_AddPredictableEventToPlayerstate(newEvent, eventParm, 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_StartTorsoAnim
===================
*/
static void PM_StartTorsoAnim(int anim)
{
if (pm->ps->pm_type >= PM_DEAD) {
return;
}
pm->ps->torsoAnim = ((pm->ps->torsoAnim & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT)
| anim;
}
static void PM_StartLegsAnim(int anim)
{
if (pm->ps->pm_type >= PM_DEAD) {
return;
}
if (pm->ps->legsTimer > 0) {
return; // a high priority animation is running
}
pm->ps->legsAnim = ((pm->ps->legsAnim & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT)
| anim;
}
static void PM_ContinueLegsAnim(int anim)
{
if ((pm->ps->legsAnim & ~ANIM_TOGGLEBIT) == anim) {
return;
}
if (pm->ps->legsTimer > 0) {
return; // a high priority animation is running
}
PM_StartLegsAnim(anim);
}
static void PM_ContinueTorsoAnim(int anim)
{
if ((pm->ps->torsoAnim & ~ANIM_TOGGLEBIT) == anim) {
return;
}
if (pm->ps->torsoTimer > 0) {
return; // a high priority animation is running
}
PM_StartTorsoAnim(anim);
}
static void PM_ForceLegsAnim(int anim)
{
pm->ps->legsTimer = 0;
PM_StartLegsAnim(anim);
}
/*
==================
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;
#ifdef Q2PHYSICS
backoff = DotProduct(in, normal) * overbounce;
for (i = 0; i < 3; i++) {
change = normal[i] * backoff;
out[i] = in[i] - change;
if ( out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
out[i] = 0;
}
#else
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;
}
#endif
}
/*
==================
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) && pm->ps->groundEntityNum) {
// 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 (pml.ladder) // If they're on a ladder...
{
drop += speed * pm_ladderfriction * pml.frametime; // Add ladder friction!
}
if (pm->ps->pm_type == PM_SPECTATOR) {
drop += speed * pm_spectatorfriction * pml.frametime;
}
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0) {
newspeed = 0;
}
newspeed /= speed;
vel[0] = vel[0] * newspeed;
vel[1] = vel[1] * newspeed;
vel[2] = vel[2] * newspeed;
}
/*
==============
PM_Accelerate
Handles user intended acceleration
==============
*/
static void PM_Accelerate(vec3_t wishdir, float wishspeed, float accel)
{
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];
}
}
/*
============
PM_CmdScale
Returns the scale factor to apply to cmd movements
This allows the clients to use axial -127 to 127 values for all directions
without getting a sqrt(2) distortion in speed.
============
*/
static float PM_CmdScale(usercmd_t * cmd)
{
int max;
float total;
float scale;
max = abs(cmd->forwardmove);
if (abs(cmd->rightmove) > max) {
max = abs(cmd->rightmove);
}
if (abs(cmd->upmove) > max) {
max = abs(cmd->upmove);
}
if (!max) {
return 0;
}
total = sqrt(cmd->forwardmove * cmd->forwardmove + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove);
scale = (float) pm->ps->speed * max / (127.0 * total);
return scale;
}
/*
================
PM_SetMovementDir
Determine the rotation of the legs relative
to the facing dir
================
*/
static void PM_SetMovementDir(void)
{
if (pm->cmd.forwardmove || pm->cmd.rightmove) {
if (pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0) {
pm->ps->movementDir = 0;
} else if (pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0) {
pm->ps->movementDir = 1;
} else if (pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0) {
pm->ps->movementDir = 2;
} else if (pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0) {
pm->ps->movementDir = 3;
} else if (pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0) {
pm->ps->movementDir = 4;
} else if (pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0) {
pm->ps->movementDir = 5;
} else if (pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0) {
pm->ps->movementDir = 6;
} else if (pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0) {
pm->ps->movementDir = 7;
}
} else {
// if they aren't actively going directly sideways,
// change the animation to the diagonal so they
// don't stop too crooked
if (pm->ps->movementDir == 2) {
pm->ps->movementDir = 1;
} else if (pm->ps->movementDir == 6) {
pm->ps->movementDir = 7;
}
}
}
/*
=============
PM_CheckJump
=============
*/
static qboolean PM_CheckJump(void)
{
if (pm->ps->pm_flags & PMF_RESPAWNED) {
return qfalse; // don't allow jump until all buttons are up
}
if (pm->cmd.upmove < 10) {
// not holding jump
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 (!pml.groundPlane && !pml.walking)
return qfalse;
if (pm->debugLevel) {
Com_Printf("%i:jump\n", c_pmove);
}
pml.groundPlane = qfalse; // jumping away
pml.walking = qfalse;
pm->ps->pm_flags |= PMF_JUMP_HELD;
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pm->ps->velocity[2] += JUMP_VELOCITY;
if (pm->ps->velocity[2] < JUMP_VELOCITY)
pm->ps->velocity[2] = JUMP_VELOCITY;
PM_AddEvent(EV_JUMP);
PM_ForceLegsAnim(LEGS_JUMP);
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
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 & (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY)) {
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
#ifdef Q2PHYSICS
Q2_PM_StepSlideMove(qtrue);
#else
PM_StepSlideMove(qtrue);
#endif
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;
}
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);
}
#ifdef Q2PHYSICS
Q2_PM_StepSlideMove(qfalse);
#else
PM_StepSlideMove(qfalse);
#endif
}
/*
===================
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);
//
// user intentions
//
if (!scale) {
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = 0;
} else {
for (i = 0; i < 3; i++) {
wishvel[i] =
scale * pml.forward[i] * pm->cmd.forwardmove + scale * pml.right[i] * pm->cmd.rightmove;
}
wishvel[2] += scale * pm->cmd.upmove;
}
VectorCopy(wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
PM_Accelerate(wishdir, wishspeed, pm_flyaccelerate);
#ifdef Q2PHYSICS
Q2_PM_StepSlideMove(qfalse);
#else
PM_StepSlideMove(qfalse);
#endif
}
/*
===================
PM_AirMove
===================
*/
static void PM_AirMove(void)
{
int i;
vec3_t wishvel;
float fmove, smove;
vec3_t wishdir;
float wishspeed;
float scale;
usercmd_t cmd;
PM_CheckJump();
PM_Friction();
fmove = pm->cmd.forwardmove;
smove = pm->cmd.rightmove;
cmd = pm->cmd;
scale = PM_CmdScale(&cmd);
// set the movementDir so clients can rotate the legs for strafing
PM_SetMovementDir();
// project moves down to flat plane
pml.forward[2] = 0;
pml.right[2] = 0;
VectorNormalize(pml.forward);
VectorNormalize(pml.right);
for (i = 0; i < 2; i++) {
wishvel[i] = pml.forward[i] * fmove * scale + pml.right[i] * smove * scale;
}
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 );
}
*/
#ifdef Q2PHYSICS
Q2_PM_StepSlideMove(qtrue);
#else
PM_StepSlideMove(qtrue);
#endif
}
/*
===================
PM_GrappleMove
===================
*/
static void PM_GrappleMove(void)
{
vec3_t vel, v;
float vlen;
VectorScale(pml.forward, -16, v);
VectorAdd(pm->ps->grapplePoint, v, v);
VectorSubtract(v, pm->ps->origin, vel);
vlen = VectorLength(vel);
VectorNormalize(vel);
if (vlen <= 100)
VectorScale(vel, 10 * vlen, vel);
else
VectorScale(vel, 800, vel);
VectorCopy(vel, pm->ps->velocity);
pml.groundPlane = qfalse;
}
/*
===================
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;
if (pm->waterlevel > 2 && DotProduct(pml.forward, pml.groundTrace.plane.normal) > 0) {
// begin swimming
PM_WaterMove();
return;
}
//Blaze: Cant jump while someone has leg damage
if (!((pm->ps->stats[STAT_RQ3] & RQ3_LEGDAMAGE) == RQ3_LEGDAMAGE) && 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 the forward and right directions onto the ground plane
pml.forward[2] = 0;
pml.right[2] = 0;
//Blaze - july 10, 2009
//The following two functions will lower your X/Y velocities depending on the plane you are walking on.
//As they are now(commented out), you will stay at the same speed walking up a ramp
//
//PM_ClipVelocity(pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP);
//PM_ClipVelocity(pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP);
VectorNormalize(pml.forward);
VectorNormalize(pml.right);
for (i = 0; i < 3; i++) {
wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove;
}
// when going up or down slopes the wish velocity should Not be zero
// wishvel[2] = 0;
VectorCopy(wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
wishspeed *= scale;
// clamp the speed lower if ducking
if (pm->ps->pm_flags & PMF_DUCKED) {
if (wishspeed > pm->ps->speed * pm_duckScale) {
wishspeed = pm->ps->speed * pm_duckScale;
}
}
// clamp the speed lower if wading or walking on the bottom
if (pm->waterlevel) {
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);
if (pm->ps->velocity[2] < 0)
pm->ps->velocity[2] = 0;
if ((pml.groundTrace.surfaceFlags & SURF_SLICK) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK) {
pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime;
}
// don't do anything if standing still
if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) {
return;
}
#ifdef Q2PHYSICS
Q2_PM_StepSlideMove(qfalse);
#else
PM_StepSlideMove(qfalse);
#endif
}
/*
==============
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);
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
Makro - changed prototype so that we can use it for other surfaces, too
(ladder footsteps)
================
*/
static int PM_FootstepForSurface(int surfaceFlags)
{
int Material = GetMaterialFromFlag(surfaceFlags);
//Makro: quick note: surfaceparm nosteps will cause footstep sounds
//NOT to be played, UNLESS surfaceparm ladder is also set.
//Pretty weird, I know, but that how they wanted it...
qboolean bLadder = (pml.groundTrace.surfaceFlags & SURF_LADDER)!=0;
qboolean bNoSteps = (pml.groundTrace.surfaceFlags & SURF_NOSTEPS)!=0;
//2007-07-31: getting rid of ladder footsteps
//if ((bLadder ^ bNoSteps) != 0) {
if (bNoSteps || bLadder) {
return 0;
}
//Makro - new surfaceparm system
//if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) {
if (Material == MAT_METALSTEPS) {
return EV_FOOTSTEP_METAL;
}
//Elder: added for footstep support
//if ( pml.groundTrace.surfaceFlags & SURF_GRASS ) {
if (Material == MAT_GRASS) {
return EV_FOOTSTEP_GRASS;
}
// JBravo: re-enables Gravel.
//Makro - new surfaceparm system
//if ( pml.groundTrace.surfaceFlags & SURF_GRAVEL ) {
if (Material == MAT_GRAVEL) {
return EV_FOOTSTEP_GRAVEL;
}
//if ( pml.groundTrace.surfaceFlags & SURF_WOOD ) {
if (Material == MAT_WOOD) {
return EV_FOOTSTEP_WOOD;
}
//if ( pml.groundTrace.surfaceFlags & SURF_CARPET ) {
if (Material == MAT_CARPET) {
return EV_FOOTSTEP_CARPET;
}
//if ( pml.groundTrace.surfaceFlags & SURF_METAL2 ) {
if (Material == MAT_METAL2) {
return EV_FOOTSTEP_METAL2;
}
// JBravo: Begin adding new sounds
//Makro - new surfaceparm system
//if ( pml.groundTrace.surfaceFlags & SURF_SNOW ) {
if (Material == MAT_SNOW) {
return EV_FOOTSTEP_SNOW;
}
//if ( pml.groundTrace.surfaceFlags & SURF_MUD ) {
if (Material == MAT_MUD) {
return EV_FOOTSTEP_MUD;
}
//if ( pml.groundTrace.surfaceFlags & SURF_WOOD2 ) {
if (Material == MAT_WOOD2) {
return EV_FOOTSTEP_WOOD2;
}
//if ( pml.groundTrace.surfaceFlags & SURF_HARDMETAL ) {
if (Material == MAT_HARDMETAL) {
return EV_FOOTSTEP_HARDMETAL;
}
// JBravo: end adding new sounds
// Makro - 5 new sounds
if (Material == MAT_LEAVES) {
return EV_FOOTSTEP_LEAVES;
}
if (Material == MAT_CEMENT) {
return EV_FOOTSTEP_CEMENT;
}
if (Material == MAT_MARBLE) {
return EV_FOOTSTEP_MARBLE;
}
if (Material == MAT_SNOW2) {
return EV_FOOTSTEP_SNOW2;
}
if (Material == MAT_HARDSTEPS) {
return EV_FOOTSTEP_HARDSTEPS;
}
if (Material == MAT_SAND) {
return EV_FOOTSTEP_SAND;
}
// Makro - end new sounds
return EV_FOOTSTEP;
}
/*
=================
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;
int damage;
// JBravo: no falling at all during LCA in Teamplay
if (pm->lca) {
return;
}
PM_ForceLegsAnim(LEGS_LAND);
// Elder: 300/320 factor included
delta = 0.9375f * (pm->ps->velocity[2] - pml.previous_velocity[2]);
delta = delta * delta * 0.0001;
// 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 (delta > 30) {
damage = (int) (((delta - 30) / 2));
if (damage < 1)
damage = 1;
damage *= 10;
if (!(pml.groundTrace.surfaceFlags & SURF_NODAMAGE)) {
//Blaze lots of changes to make it more like aq2
// this is a pain grunt, so don't play it if dead
if (pm->ps->stats[STAT_HEALTH] > 0 && damage > 0) {
// JBravo: new multiple itemcode
if (pm->ps->stats[STAT_HOLDABLE_ITEM] & (1 << HI_SLIPPERS))
PM_AddEvent(EV_FALL_FAR_NOSOUND);
else
PM_AddEvent(EV_FALL_FAR);
pm->ps->stats[STAT_FALLDAMAGE] = damage;
} else {
PM_AddEvent(PM_FootstepForSurface(pml.groundTrace.surfaceFlags));
//Elder: added? useful?
pm->ps->stats[STAT_FALLDAMAGE] = 0;
}
}
} else if (delta > 20) {
// JBravo: new multiple itemcode
if (pm->ps->stats[STAT_HOLDABLE_ITEM] & (1 << HI_SLIPPERS)) {
PM_AddEvent(EV_FALL_SHORT_NOSOUND);
//Elder: added? useful?
pm->ps->stats[STAT_FALLDAMAGE] = 0;
} else {
PM_AddEvent(EV_FALL_SHORT);
//Elder: added? useful?
pm->ps->stats[STAT_FALLDAMAGE] = 0;
}
// JBravo: new multiple itemcode
} else if (!(pm->ps->stats[STAT_HOLDABLE_ITEM] & (1 << HI_SLIPPERS))) {
// Elder: don't spam sound events going up -- more like Q2 ladders as well
//Makro - ladder footsteps are handled in PM_LadderMove, chaging || to &&
if (!pml.ladder && pm->ps->velocity[2] < 0)
PM_AddEvent(PM_FootstepForSurface(pml.groundTrace.surfaceFlags));
//Elder: added? useful?
pm->ps->stats[STAT_FALLDAMAGE] = 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;
if (pm->ps->groundEntityNum != ENTITYNUM_NONE) {
// we just transitioned into freefall
if (pm->debugLevel) {
Com_Printf("%i:lift\n", c_pmove);
}
// if they aren't in a jumping animation and the ground is a ways away, force into it
// if we didn't do the trace, the player would be backflipping down staircases
VectorCopy(pm->ps->origin, point);
point[2] -= 64;
pm->trace(&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask);
if (trace.fraction == 1.0) {
PM_ForceLegsAnim(LEGS_JUMP);
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
}
/*
=============
PM_GroundTrace
=============
*/
static void PM_GroundTrace(void)
{
vec3_t point;
trace_t trace;
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 the trace didn't hit anything and not on ladder, we are in free fall
if (trace.fraction == 1.0 && !pml.ladder) {
PM_GroundTraceMissed();
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
// check if getting thrown off the ground
/*
// NiceAss: Comment out and you get double jumping =D
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
PM_ForceLegsAnim( LEGS_JUMP );
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
*/
if (pm->ps->velocity[2] > 180) { // NiceAss: This is here for slope acceleration!
if (pm->debugLevel) {
Com_Printf("%i:slopeslide\n", c_pmove);
}
pml.groundPlane = qfalse;
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.walking = qfalse;
return;
}
// slopes that are too steep will not be considered onground
// Elder: added ladder check
if (trace.plane.normal[2] < MIN_WALK_NORMAL && !pml.ladder) {
if (pm->debugLevel) {
Com_Printf("%i:steep\n", c_pmove);
}
// FIXME: if they can't slide down the slope, let them
// walk (sharp crevices)
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse; //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();
if (pml.ladder) {
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
// 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_AddTouchEnt(trace.entityNum);
}
/*
=============
PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving
=============
*/
static void PM_SetWaterLevel(void)
{
vec3_t point;
int cont;
int sample1;
int sample2;
//
// get waterlevel, accounting for ducking
//
pm->waterlevel = 0;
pm->watertype = 0;
point[0] = pm->ps->origin[0];
point[1] = pm->ps->origin[1];
point[2] = pm->ps->origin[2] + 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;
vec3_t point; // NiceAss: Added for FUNKY CODE ALERT section
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;
}
// NiceAss: FUNKY CODE ALERT!
// Check to see if there is ground below. No? then you can't duck...
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);
if (trace.fraction == 1.0 && !pml.ladder && !(pm->ps->pm_flags & PMF_DUCKED)) {
pm->maxs[2] = 32;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
return;
}
// End of funky code alert
if (pm->cmd.upmove < 0) { // 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] = 32;
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] = 16;
pm->ps->viewheight = CROUCH_VIEWHEIGHT;
} else {
pm->maxs[2] = 32;
pm->ps->viewheight = DEFAULT_VIEWHEIGHT;
}
}
//===================================================================
/*
===============
PM_Footsteps
===============
*/
static void PM_Footsteps(void)
{
float bobmove;
int old;
qboolean footstep;
//
// calculate speed and cycle to be used for
// all cyclic walking effects
//
pm->xyspeed = sqrt(pm->ps->velocity[0] * pm->ps->velocity[0]
+ pm->ps->velocity[1] * pm->ps->velocity[1]);
if (pm->ps->groundEntityNum == ENTITYNUM_NONE) {
// airborne leaves position in cycle intact, but doesn't advance
if (pm->waterlevel > 1) {
PM_ContinueLegsAnim(LEGS_SWIM);
}
return;
}
// if not trying to move
//Makro - changed condition a bit
//if (!pm->cmd.forwardmove && !pm->cmd.rightmove) {
// if (pm->xyspeed < 5) {
if ( (!pm->cmd.forwardmove && !pm->cmd.rightmove) || pm->xyspeed < 5) {
pm->ps->bobCycle = 0; // start at beginning of cycle again
if (pm->ps->pm_flags & PMF_DUCKED) {
PM_ContinueLegsAnim(LEGS_IDLECR);
} else {
PM_ContinueLegsAnim(LEGS_IDLE);
}
return;
}
footstep = qfalse;
if (pm->ps->pm_flags & PMF_DUCKED) {
bobmove = 0.5; // ducked characters bob much faster
if (pm->ps->pm_flags & PMF_BACKWARDS_RUN) {
PM_ContinueLegsAnim(LEGS_BACKCR);
} else {
PM_ContinueLegsAnim(LEGS_WALKCR);
}
// ducked characters never play footsteps
/*
} else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) {
if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) {
bobmove = 0.4; // faster speeds bob faster
footstep = qtrue;
} else {
bobmove = 0.3;
}
PM_ContinueLegsAnim( LEGS_BACK );
*/
} else {
if (!(pm->cmd.buttons & BUTTON_WALKING)) {
bobmove = 0.4f; // faster speeds bob faster
if (pm->ps->pm_flags & PMF_BACKWARDS_RUN) {
PM_ContinueLegsAnim(LEGS_BACK);
} else {
PM_ContinueLegsAnim(LEGS_RUN);
}
footstep = qtrue;
} else {
bobmove = 0.3f; // walking bobs slow
if (pm->ps->pm_flags & PMF_BACKWARDS_RUN) {
PM_ContinueLegsAnim(LEGS_BACKWALK);
} else {
PM_ContinueLegsAnim(LEGS_WALK);
}
}
}
// check for footstep / splash sounds
old = pm->ps->bobCycle;
pm->ps->bobCycle = (int) (old + bobmove * pml.msec) & 255;
// if we just crossed a cycle boundary, play an apropriate footstep event
if (((old + 64) ^ (pm->ps->bobCycle + 64)) & 128) {
if (pm->waterlevel == 0) {
//Elder: we can check for slippers here!
// on ground will only play sounds if running
if ((footstep && !pm->noFootsteps)
// JBravo: new multiple itemcode
&& !(pm->ps->stats[STAT_HOLDABLE_ITEM] & (1 << HI_SLIPPERS))) {
PM_AddEvent(PM_FootstepForSurface(pml.groundTrace.surfaceFlags));
}
} else 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
===============
*/
static void PM_BeginWeaponChange(int weapon)
{
//G_Printf("CHANGING WEAP TO: %i\n", 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;
}
PM_AddEvent(EV_CHANGE_WEAPON);
//Elder: ignore disarm delays when throwing a weapon
if (pm->ps->stats[STAT_RQ3] & RQ3_THROWWEAPON) {
pm->ps->stats[STAT_RQ3] &= ~RQ3_THROWWEAPON;
pm->ps->weaponTime = 0;
} else {
//pm->ps->weaponTime += 200;
//Elder: dependent time for each weapon
switch (pm->ps->weapon) {
case WP_PISTOL:
pm->ps->weaponTime += RQ3_PISTOL_DISARM_DELAY;
break;
case WP_M3:
pm->ps->weaponTime += RQ3_M3_DISARM_DELAY;
break;
case WP_M4:
pm->ps->weaponTime += RQ3_M4_DISARM_DELAY;
break;
case WP_MP5:
pm->ps->weaponTime += RQ3_MP5_DISARM_DELAY;
break;
case WP_HANDCANNON:
pm->ps->weaponTime += RQ3_HANDCANNON_DISARM_DELAY;
break;
case WP_SSG3000:
pm->ps->weaponTime += RQ3_SSG3000_DISARM_DELAY;
break;
case WP_AKIMBO:
pm->ps->weaponTime += RQ3_AKIMBO_DISARM_DELAY;
break;
case WP_KNIFE:
pm->ps->weaponTime += RQ3_KNIFE_DISARM_DELAY;
break;
case WP_GRENADE:
pm->ps->weaponTime += RQ3_GRENADE_DISARM_DELAY;
break;
default:
//Elder: shouldn't be here
//G_Printf("PM_BeginWeaponChange: received bad weapon %d\n", pm->ps->weapon);
pm->ps->weaponTime += 600;
break;
}
if (pm->ps->weapon == WP_KNIFE && !(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE))
PM_StartWeaponAnim(WP_ANIM_THROWDISARM);
else {
PM_StartWeaponAnim(WP_ANIM_DISARM);
}
}
// Elder: cancel reload stuff here
pm->ps->stats[STAT_RELOADTIME] = 0;
pm->ps->stats[STAT_RELOADATTEMPTS] = 0;
pm->ps->stats[STAT_RQ3] &= ~RQ3_FASTRELOADS;
pm->ps->stats[STAT_RQ3] &= ~RQ3_LOCKRELOADS;
pm->ps->stats[STAT_RQ3] &= ~RQ3_QUEUERELOAD;
// Elder: cancel burst shots
pm->ps->stats[STAT_BURST] = 0;
// NiceAss: Added this as a fix for knifes and grenades when you throw the last one.
if (((pm->ps->weapon == WP_GRENADE || pm->ps->weapon == WP_KNIFE) && pm->ps->ammo[pm->ps->weapon] == 0) &&
pm->ps->weaponstate != WEAPON_FIRING) {
pm->ps->weaponstate = WEAPON_DROPPING;
pm->ps->weaponTime = 0;
return;
}
pm->ps->weaponstate = WEAPON_DROPPING;
if (pm->ps->weapon == WP_KNIFE && !(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE))
PM_StartWeaponAnim(WP_ANIM_THROWDISARM);
else {
PM_StartWeaponAnim(WP_ANIM_DISARM);
}
if ( pm->ps->weapon == WP_AKIMBO )
PM_StartTorsoAnim(TORSO_DROP2);
else
PM_StartTorsoAnim(TORSO_DROP);
}
/*
===============
PM_FinishWeaponChange
===============
*/
static void PM_FinishWeaponChange(void)
{
int weapon, savedWeap;
weapon = pm->cmd.weapon;
// JBravo: debug for the bandage bug
savedWeap = pm->ps->weapon;
if (weapon < WP_NONE || weapon >= WP_NUM_WEAPONS) {
// Com_Printf("BANDAGEBUG: (1) weapon is %d and cmd.weapon is %d. Reverting to previous weapon\n", pm->ps->weapon,
// weapon);
weapon = savedWeap;
// weapon = WP_NONE;
}
if (!(pm->ps->stats[STAT_WEAPONS] & (1 << weapon))) {
// Com_Printf("BANDAGEBUG: (2) weapon is %d and cmd.weapon is %d. Reverting to previous weapon\n", pm->ps->weapon,
// weapon);
weapon = savedWeap;
// weapon = WP_NONE;
}
//Remove grenade/knife if out of ammo
if ((weapon == WP_GRENADE || weapon == WP_KNIFE) && pm->ps->ammo[weapon] == 0) {
if (weapon == WP_GRENADE)
pm->ps->stats[STAT_WEAPONS] &= ~(1 << WP_GRENADE);
if (weapon == WP_KNIFE)
pm->ps->stats[STAT_WEAPONS] &= ~(1 << WP_KNIFE);
weapon = WP_PISTOL;
}
pm->ps->weapon = weapon;
pm->ps->weaponstate = WEAPON_RAISING;
//Elder: weapon-dependent timing
switch (pm->ps->weapon) {
case WP_PISTOL:
pm->ps->weaponTime += RQ3_PISTOL_ACTIVATE_DELAY;
break;
case WP_M3:
pm->ps->weaponTime += RQ3_M3_ACTIVATE_DELAY;
break;
case WP_M4:
pm->ps->weaponTime += RQ3_M4_ACTIVATE_DELAY;
break;
case WP_MP5:
pm->ps->weaponTime += RQ3_MP5_ACTIVATE_DELAY;
break;
case WP_HANDCANNON:
pm->ps->weaponTime += RQ3_HANDCANNON_ACTIVATE_DELAY;
break;
case WP_SSG3000:
pm->ps->weaponTime += RQ3_SSG3000_ACTIVATE_DELAY;
break;
case WP_AKIMBO:
pm->ps->weaponTime += RQ3_AKIMBO_ACTIVATE_DELAY;
break;
case WP_KNIFE:
pm->ps->weaponTime += RQ3_KNIFE_ACTIVATE_DELAY;
break;
case WP_GRENADE:
pm->ps->weaponTime += RQ3_GRENADE_ACTIVATE_DELAY;
break;
default:
//Elder: shouldn't be here
//G_Printf("PM_FinishWeaponChange: received bad weapon %d\n", pm->ps->weapon);
pm->ps->weaponTime += 750;
break;
}
if (pm->ps->weapon == WP_KNIFE && !(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE))
PM_StartWeaponAnim(WP_ANIM_THROWACTIVATE);
else
PM_StartWeaponAnim(WP_ANIM_ACTIVATE);
if ( pm->ps->weapon == WP_AKIMBO )
PM_StartTorsoAnim(TORSO_RAISE2);
else
PM_StartTorsoAnim(TORSO_RAISE);
}
/*
==============
PM_TorsoAnimation
==============
*/
static void PM_TorsoAnimation(void)
{
if (pm->ps->weaponstate == WEAPON_READY) {
if (pm->ps->weapon == WP_KNIFE)
PM_ContinueTorsoAnim(TORSO_STAND2);
else if ( pm->ps->weapon == WP_AKIMBO)
PM_ContinueTorsoAnim(TORSO_STAND3);
else if ( pm->ps->weapon == WP_PISTOL)
PM_ContinueTorsoAnim(TORSO_STAND4);
else
PM_ContinueTorsoAnim(TORSO_STAND);
// QUARANTINE - Weapon Animation
// Should always draw the weapon when it is just ready
if (pm->ps->ammo[pm->ps->weapon] == 0)
PM_ContinueWeaponAnim(WP_ANIM_EMPTY);
else if (pm->ps->weapon == WP_KNIFE && !(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE))
PM_ContinueWeaponAnim(WP_ANIM_THROWIDLE);
else
PM_ContinueWeaponAnim(WP_ANIM_IDLE);
// PM_ContinueWeaponAnim( WP_ANIM_READY );
return;
}
}
/*
==============
PM_Reload
Added by Elder
What a mess this is :/
FIXME: This is poorly implemented
Does reload stuff like fast-reloads, sound events,
some ammo synchronization, etc.
Clip management is handled on the server (ReloadWeapon)
because we can't store all the clips in a playerstate (nor should we)
It is triggered by BUTTON_AFFIRMATIVE (bind +button5)
==============
*/
static void PM_Reload(void)
{
// int weapon = pm->ps->weapon;
int truePress = 0;
// only normal/noclip players can reload
if (pm->ps->pm_type > PM_NOCLIP) {
pm->ps->pm_flags &= ~PMF_RELOAD_HELD;
pm->ps->stats[STAT_RELOADTIME] = 0;
pm->ps->stats[STAT_RELOADATTEMPTS] = 0;
pm->ps->stats[STAT_RQ3] &= ~RQ3_FASTRELOADS;
pm->ps->stats[STAT_RQ3] &= ~RQ3_LOCKRELOADS;
pm->ps->stats[STAT_RQ3] &= ~RQ3_QUEUERELOAD;
return;
}
// try to reload since it's queued
if (pm->ps->stats[STAT_RQ3] & RQ3_QUEUERELOAD) {
if ((pm->cmd.buttons & BUTTON_AFFIRMATIVE) && !(pm->ps->pm_flags & PMF_RELOAD_HELD))
truePress = 1;
pm->ps->stats[STAT_RQ3] &= ~RQ3_QUEUERELOAD;
pm->ps->pm_flags &= ~PMF_RELOAD_HELD;
pm->cmd.buttons |= BUTTON_AFFIRMATIVE;
}
// NiceAss: +Attack cancels fast reloads
if (pm->cmd.buttons & BUTTON_ATTACK && pm->ps->stats[STAT_RELOADATTEMPTS] > 1) {
pm->ps->stats[STAT_RELOADATTEMPTS] = 1;
}
if (pm->cmd.buttons & BUTTON_AFFIRMATIVE) {
if (!(pm->ps->pm_flags & PMF_RELOAD_HELD)) {
// prevent throttling
pm->ps->pm_flags |= PMF_RELOAD_HELD;
// check for bursting or weapon delay
if (pm->ps->stats[STAT_BURST] > 0 || pm->ps->weaponTime > 0) {
if (truePress && (pm->ps->weapon == WP_SSG3000 || pm->ps->weapon == WP_M3)) {
pm->ps->stats[STAT_RQ3] |= RQ3_FASTRELOADS;
pm->ps->stats[STAT_RELOADATTEMPTS]++;
}
pm->ps->stats[STAT_RQ3] |= RQ3_QUEUERELOAD;
return;
}
// check for bandaging
/* if (pm->ps->stats[STAT_RQ3] & RQ3_BANDAGE_WORK)
return;
*/
//Slicer
if (pm->ps->weaponstate == WEAPON_BANDAGING)
return;
// check for full clip or non-reloadable weapons
if (pm->ps->ammo[pm->ps->weapon] == ClipAmountForAmmo(pm->ps->weapon) ||
pm->ps->weapon == WP_KNIFE || pm->ps->weapon == WP_GRENADE) {
//Com_Printf("No need to reload.\n");
return;
}
// check for insufficient ammo
if (pm->ps->stats[STAT_CLIPS] <= 0) {
//Com_Printf("Out of ammo.\n");
return;
} else if (pm->ps->weapon == WP_HANDCANNON || pm->ps->weapon == WP_AKIMBO) {
if (pm->ps->stats[STAT_CLIPS] < 2) {
//Com_Printf("Not enough ammo.\n");
return;
}
}
// check for fast-reload interrupt
if (pm->ps->weapon == WP_M3 || pm->ps->weapon == WP_SSG3000) {
if (pm->ps->stats[STAT_RELOADTIME] > 0) // && (pm->ps->weapon == WP_M3 && pm->ps->stats[STAT_RELOADTIME] < RQ3_M3_RELOAD_DELAY - RQ3_M3_ALLOW_FAST_RELOAD_DELAY || pm->ps->weapon == WP_SSG3000 && pm->ps->stats[STAT_RELOADTIME] < RQ3_SSG3000_RELOAD_DELAY - RQ3_SSG3000_ALLOW_FAST_RELOAD_DELAY))
{
if (pm->ps->stats[STAT_RQ3] & RQ3_LOCKRELOADS) {
//Com_Printf("============= Locked out in fast-reload interrupt\n");
} else {
//if (pm->ps->ammo[pm->ps->weapon] + pm->ps->stats[STAT_RELOADATTEMPTS] < ClipAmountForAmmo(pm->ps->weapon) &&
//pm->ps->stats[STAT_RELOADATTEMPTS] < pm->ps->stats[STAT_CLIPS])
if (pm->ps->stats[STAT_RELOADATTEMPTS] < (ClipAmountForAmmo(pm->ps->weapon) - pm->ps->ammo[pm->ps->weapon])) //only let them cue up enough shots to fill the gun
{
//Com_Printf("Hit fast-reload entrance\n");
// add to reload queue and enable fast-reloads flag
pm->ps->stats[STAT_RQ3] |= RQ3_FASTRELOADS;
pm->ps->stats[STAT_RELOADATTEMPTS]++;
//Com_Printf("======== Reload attempts: %i ========\n", pm->ps->stats[STAT_RELOADATTEMPTS]);
}
return;
}
}
}
// fresh reload
if (pm->ps->stats[STAT_RELOADTIME] == 0) {
// set reload time according to weapon
switch (pm->ps->weapon) {
case WP_PISTOL:
pm->ps->stats[STAT_RELOADTIME] = RQ3_PISTOL_RELOAD_DELAY;
break;
case WP_AKIMBO:
pm->ps->stats[STAT_RELOADTIME] = RQ3_AKIMBO_RELOAD_DELAY;
break;
case WP_M3:
pm->ps->stats[STAT_RELOADTIME] = RQ3_M3_RELOAD_DELAY;
pm->ps->stats[STAT_RELOADATTEMPTS]++;
break;
case WP_M4:
pm->ps->stats[STAT_RELOADTIME] = RQ3_M4_RELOAD_DELAY;
break;
case WP_MP5:
pm->ps->stats[STAT_RELOADTIME] = RQ3_MP5_RELOAD_DELAY;
break;
case WP_HANDCANNON:
pm->ps->stats[STAT_RELOADTIME] = RQ3_HANDCANNON_RELOAD_DELAY;
break;
case WP_SSG3000:
pm->ps->stats[STAT_RELOADTIME] = RQ3_SSG3000_RELOAD_DELAY;
pm->ps->stats[STAT_RELOADATTEMPTS]++;
break;
}
// start the animation off
if (pm->ps->weaponstate != WEAPON_RELOADING)
PM_StartWeaponAnim(WP_ANIM_RELOAD);
pm->ps->weaponstate = WEAPON_RELOADING;
PM_AddEvent(EV_RELOAD_WEAPON0);
//Com_Printf("Starting reload\n");
return;
}
}
} else {
pm->ps->pm_flags &= ~PMF_RELOAD_HELD;
}
// in-progress reload
if (pm->ps->stats[STAT_RELOADTIME] > 0) {
pm->ps->stats[STAT_RELOADTIME] -= pml.msec;
// process any fast-reload stuff here
if (pm->ps->weapon == WP_M3 || pm->ps->weapon == WP_SSG3000) {
if ((pm->ps->stats[STAT_RQ3] & RQ3_FASTRELOADS) && pm->ps->stats[STAT_RELOADATTEMPTS] > 0) {
//Com_Printf("reloadtime (%d)\n",pm->ps->stats[STAT_RELOADTIME]);
if (pm->ps->weapon == WP_M3) {
// knock down reload time if doing fast-reloads
if (pm->ps->stats[STAT_RELOADTIME] < RQ3_M3_ALLOW_FAST_RELOAD_DELAY
&& pm->ps->stats[STAT_RELOADTIME] > RQ3_M3_FAST_RELOAD_DELAY) {
//Com_Printf("Reducing reload time\n");
pm->ps->stats[STAT_RELOADTIME] = RQ3_M3_FAST_RELOAD_DELAY;
}
} else {
// knock down reload time if doing fast-reloads
if (pm->ps->stats[STAT_RELOADTIME] < RQ3_SSG3000_ALLOW_FAST_RELOAD_DELAY
&& pm->ps->stats[STAT_RELOADTIME] > RQ3_SSG3000_FAST_RELOAD_DELAY) {
//Com_Printf("Reducing reload time\n");
pm->ps->stats[STAT_RELOADTIME] = RQ3_SSG3000_FAST_RELOAD_DELAY;
}
}
}
}
// insert stage 1 sound events here; check against the reload time
// Special handcannon shell ejection hack
if (pm->ps->weapon == WP_HANDCANNON &&
!(pm->ps->stats[STAT_RQ3] & RQ3_RELOADWEAPON1) && pm->ps->stats[STAT_RELOADTIME] < 1400) {
pm->ps->stats[STAT_RQ3] |= RQ3_RELOADWEAPON1;
PM_AddEvent(EV_RELOAD_WEAPON1);
}
// finished reload
if (pm->ps->stats[STAT_RELOADTIME] <= 0) {
int ammotoadd;
ammotoadd = ClipAmountForReload(pm->ps->weapon);
if (pm->ps->weapon == WP_M3 || pm->ps->weapon == WP_SSG3000) {
// need to also check here because of fast-reloads
if (ammotoadd + pm->ps->ammo[pm->ps->weapon] > ClipAmountForAmmo(pm->ps->weapon) ||
pm->ps->stats[STAT_CLIPS] <= 0) {
ammotoadd = pm->ps->ammo[pm->ps->weapon];
pm->ps->stats[STAT_RELOADATTEMPTS] = 0;
} else
ammotoadd += pm->ps->ammo[pm->ps->weapon];
}
// akimbo and MK23 synchronization
if (pm->ps->weapon == WP_AKIMBO) {
pm->ps->ammo[WP_PISTOL] = RQ3_PISTOL_AMMO;
} else if (pm->ps->weapon == WP_PISTOL && (pm->ps->stats[STAT_WEAPONS] & (1 << WP_AKIMBO))) {
// weird? That's because we gave one pistol a full clip
pm->ps->ammo[WP_AKIMBO] = pm->ps->ammo[WP_AKIMBO] - pm->ps->ammo[WP_PISTOL] + ammotoadd;
if (pm->ps->ammo[WP_AKIMBO] > RQ3_AKIMBO_AMMO)
pm->ps->ammo[WP_AKIMBO] = RQ3_AKIMBO_AMMO;
}
if (!(pm->ps->stats[STAT_RQ3] & RQ3_LOCKRELOADS)) {
pm->ps->ammo[pm->ps->weapon] = ammotoadd;
}
// handle continuous fast-reloads
if ((pm->ps->weapon == WP_M3 || pm->ps->weapon == WP_SSG3000) && (pm->ps->stats[STAT_RQ3] & RQ3_FASTRELOADS)) //&&
//pm->ps->stats[STAT_RELOADATTEMPTS] > 0)
{
if (!(pm->ps->stats[STAT_RQ3] & RQ3_LOCKRELOADS) &&
pm->ps->stats[STAT_RELOADATTEMPTS] > 0) {
//Com_Printf("Fast-reload cycle repeating\n");
if (pm->ps->weapon == WP_M3)
pm->ps->stats[STAT_RELOADTIME] += RQ3_M3_FAST_RELOAD_DELAY;
else
pm->ps->stats[STAT_RELOADTIME] += RQ3_SSG3000_FAST_RELOAD_DELAY;
pm->ps->stats[STAT_RELOADATTEMPTS]--;
if (pm->ps->stats[STAT_RELOADATTEMPTS] > 0) {
PM_StartWeaponAnim(WP_ANIM_EXTRA1);
//PM_StartWeaponAnim(WP_ANIM_RELOAD);
}
if (pm->ps->stats[STAT_CLIPS] > 0) {
//Com_Printf("Sending event from continuous fast-reloads\n");
PM_AddEvent(EV_RELOAD_WEAPON1);
//PM_AddEvent(EV_RELOAD_WEAPON0);
} else {
//Com_Printf("Negative event prevented\n");
pm->ps->stats[STAT_RELOADATTEMPTS] = 0;
}
} else {
//Com_Printf("============= Locked out in continuous fast-reloads\n");
}
// finishing up fast reloads
if ((pm->ps->stats[STAT_RQ3] & RQ3_FASTRELOADS) &&
pm->ps->stats[STAT_RELOADATTEMPTS] == 0) {
//Com_Printf("Fast-reload cycle ending\n");
if (pm->ps->weapon == WP_M3)
pm->ps->stats[STAT_RELOADTIME] += RQ3_M3_FINISH_RELOAD_DELAY;
else
pm->ps->stats[STAT_RELOADTIME] += RQ3_SSG3000_FINISH_RELOAD_DELAY;
pm->ps->stats[STAT_RQ3] &= ~RQ3_FASTRELOADS;
// lock fast-reloads during finish delay
pm->ps->stats[STAT_RQ3] |= RQ3_LOCKRELOADS;
//Com_Printf("<<<<<<<<<<<<< Locking\n");
PM_StartWeaponAnim(WP_ANIM_EXTRA2);
}
return;
}
// normal reload case
//else
//{
// unlock
if (pm->ps->stats[STAT_RQ3] & RQ3_LOCKRELOADS) {
//Com_Printf(">>>>>>>>>>>>> Unlocking\n");
pm->ps->stats[STAT_RQ3] &= ~RQ3_LOCKRELOADS;
} else {
//Com_Printf("Sending event from normal reload\n");
PM_AddEvent(EV_RELOAD_WEAPON2);
}
//Com_Printf("Finished reload\n");
pm->ps->stats[STAT_RELOADTIME] = 0;
pm->ps->stats[STAT_RELOADATTEMPTS] = 0;
pm->ps->stats[STAT_RQ3] &= ~RQ3_RELOADWEAPON1;
pm->ps->weaponstate = WEAPON_READY;
//}
} // end finish reload
} // end in-progress reload
}
/*
==============
PM_Weapon
Generates weapon events and modifes the weapon counter
==============
*/
static void PM_Weapon(void)
{
int addTime;
//Elder: PISTOL always gets set to 12 if akimbos have >11 based on AQ2 observation
if (pm->ps->weapon == WP_AKIMBO && pm->ps->ammo[WP_AKIMBO] > 11) {
pm->ps->ammo[WP_PISTOL] = 12;
}
// 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;
}
//Elder: New 3rb Code
//force fire button down if STAT_BURST is < proper amount
//Otherwise release the button
if ((pm->ps->weapon == WP_M4 &&
(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_M4MODE) == RQ3_M4MODE) ||
(pm->ps->weapon == WP_MP5 && (pm->ps->persistant[PERS_WEAPONMODES] & RQ3_MP5MODE) == RQ3_MP5MODE)) {
int weaponNum = pm->ps->weapon;
if (pm->ps->ammo[weaponNum] == 0) {
pm->ps->stats[STAT_BURST] = 0;
} else if (pm->cmd.buttons & BUTTON_ATTACK) // && client->ps.stats[STAT_BURST] > 0)
{
if (pm->ps->stats[STAT_BURST] >= 0 && pm->ps->stats[STAT_BURST] < 3)
pm->cmd.buttons |= BUTTON_ATTACK;
else
pm->cmd.buttons &= ~BUTTON_ATTACK;
} else if (pm->ps->stats[STAT_BURST] > 2) {
pm->ps->stats[STAT_BURST] = 0;
pm->ps->weaponTime += 300;
}
//Don't need?
else if (pm->ps->stats[STAT_BURST] > 0)
pm->cmd.buttons |= BUTTON_ATTACK;
}
//agt: Knife stupidity
//I'll hijack STAT_BURST for this one
if (pm->ps->weapon == WP_KNIFE && (pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE)) {
if (pm->cmd.buttons & BUTTON_ATTACK && pm->ps->stats[STAT_BURST] >= 0 && pm->ps->stats[STAT_BURST] < 5) {
pm->cmd.buttons |= BUTTON_ATTACK;
} else if (pm->ps->stats[STAT_BURST] > 4) {
pm->ps->stats[STAT_BURST] = 0;
pm->ps->weaponTime += 650; // NiceAss note: 30ms * 5 attacks = 150 ms + 650ms = 800ms.
} else if (pm->ps->stats[STAT_BURST] > 0) {
pm->cmd.buttons |= BUTTON_ATTACK;
}
}
//Elder: New semi-auto code
if (pm->ps->weapon == WP_PISTOL && (pm->ps->persistant[PERS_WEAPONMODES] & RQ3_MK23MODE) == RQ3_MK23MODE) {
if (pm->ps->ammo[WP_PISTOL] == 0) {
pm->ps->stats[STAT_BURST] = 0;
} else if ((pm->cmd.buttons & BUTTON_ATTACK) && pm->ps->stats[STAT_BURST]) {
pm->cmd.buttons &= ~BUTTON_ATTACK;
} else if (pm->ps->stats[STAT_BURST]) {
pm->ps->weaponTime += 100; // lowered again to 100 from 150 from 200
pm->ps->stats[STAT_BURST] = 0;
}
}
// Elder: new akimbo code
if (pm->ps->weapon == WP_AKIMBO) {
if (pm->ps->ammo[WP_AKIMBO] == 0 && pm->ps->stats[STAT_BURST]) {
pm->ps->stats[STAT_BURST] = 0;
pm->cmd.buttons &= ~BUTTON_ATTACK;
} else if (pm->ps->stats[STAT_BURST]) {
pm->cmd.buttons |= BUTTON_ATTACK;
}
}
//NiceAss: I added this for smooth M4 rise
if (pm->ps->weaponstate == WEAPON_FIRING && pm->ps->ammo[pm->ps->weapon] &&
pm->ps->weapon == WP_M4 && !pm->ps->stats[STAT_BURST] &&
!(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_M4MODE) /*&&
pm->predict*/ ) {
pm->ps->delta_angles[0] -= ANGLE2SHORT(0.13);
}
// make weapon function
if (pm->ps->weaponTime > 0) {
pm->ps->weaponTime -= pml.msec;
}
// Elder: make stall decrease
if (pm->ps->stats[STAT_WEAPONSTALLTIME] > 0) {
pm->ps->stats[STAT_WEAPONSTALLTIME] -= pml.msec;
}
// check for weapon change
// can't change if weapon is firing, but can change
// again if lowering or raising
// Elder: modified so that a dropweapon command is high precedence
//if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING) {
if ((pm->ps->stats[STAT_RQ3] & RQ3_THROWWEAPON) || (pm->ps->weaponTime <= 0 &&
!(pm->ps->weaponstate == WEAPON_FIRING
|| pm->ps->weaponstate == WEAPON_STALL))) {
if (pm->ps->weapon != pm->cmd.weapon) {
//Elder: if switching weapons, fire off the grenade "instantly"
if (pm->ps->weapon == WP_GRENADE && pm->ps->weaponstate == WEAPON_COCKED) {
pm->ps->weaponstate = WEAPON_FIRING;
pm->cmd.buttons &= ~BUTTON_ATTACK;
//if (pm->predict)
PM_AddEvent2(EV_FIRE_WEAPON, RQ3_WPMOD_GRENADEDROP);
pm->ps->ammo[WP_GRENADE]--;
//Makro - if this is the last grenade, remove the weapon from the inventory
if (pm->ps->ammo[WP_GRENADE] <= 0) {
pm->ps->stats[STAT_WEAPONS] &= ~(1 << WP_GRENADE);
}
}
PM_BeginWeaponChange(pm->cmd.weapon);
} else {
//Elder: temp hack
if (pm->ps->weaponstate == WEAPON_READY) {
if (pm->ps->ammo[pm->ps->weapon] == 0) {
PM_ContinueWeaponAnim(WP_ANIM_EMPTY);
} else if (pm->ps->weapon == WP_KNIFE &&
!(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE)) {
PM_ContinueWeaponAnim(WP_ANIM_THROWIDLE);
} else
PM_ContinueWeaponAnim(WP_ANIM_IDLE);
}
}
}
// Elder: added STAT_RELOADTIME and STAT_WEAPONSTALLTIME check
if (pm->ps->weaponTime > 0 || pm->ps->stats[STAT_RELOADTIME] > 0 || pm->ps->stats[STAT_WEAPONSTALLTIME] > 0) {
return;
}
// change weapon if time
if (pm->ps->weaponstate == WEAPON_DROPPING || pm->ps->weaponstate == WEAPON_BANDAGING) {
PM_FinishWeaponChange();
return;
}
//NiceAss: Attempt to have the knife/grenade "activate" after a throw if you have another one of them
if (pm->ps->weaponstate == WEAPON_FIRING && pm->ps->ammo[pm->ps->weapon] > 0 &&
(pm->ps->weapon == WP_KNIFE || pm->ps->weapon == WP_GRENADE)) {
if (pm->ps->weapon == WP_KNIFE && !(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE)) {
pm->ps->weaponTime = RQ3_KNIFE_ACTIVATE_DELAY;
PM_StartWeaponAnim(WP_ANIM_THROWACTIVATE);
pm->ps->weaponstate = WEAPON_RAISING;
PM_StartTorsoAnim(TORSO_RAISE);
return;
}
if (pm->ps->weapon == WP_GRENADE) {
pm->ps->weaponTime = RQ3_GRENADE_DELAY;
PM_StartWeaponAnim(WP_ANIM_ACTIVATE);
pm->ps->weaponstate = WEAPON_RAISING;
PM_StartTorsoAnim(TORSO_RAISE);
return;
}
}
if (pm->ps->weaponstate == WEAPON_RAISING &&
!((pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE) && pm->ps->stats[STAT_BURST])) {
pm->ps->weaponstate = WEAPON_READY;
if (pm->ps->weapon == WP_KNIFE)
PM_StartTorsoAnim(TORSO_STAND2);
else if ( pm->ps->weapon == WP_AKIMBO )
PM_StartTorsoAnim(TORSO_STAND3);
else if ( pm->ps->weapon == WP_PISTOL )
PM_StartTorsoAnim(TORSO_STAND4);
else
PM_StartTorsoAnim(TORSO_STAND);
// temp hack
if (pm->ps->ammo[pm->ps->weapon] == 0) {
PM_ContinueWeaponAnim(WP_ANIM_EMPTY);
}
else if (pm->ps->weapon == WP_KNIFE && !(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE))
PM_StartWeaponAnim(WP_ANIM_THROWIDLE);
else
PM_StartWeaponAnim(WP_ANIM_IDLE);
return;
}
// NiceAss: Added for knife animation switch.
// At the moment, this is only used for changing knife-throw modes.
if (pm->ps->weaponstate == WEAPON_MODECHANGE) {
if (pm->ps->weapon == WP_KNIFE)
pm->ps->weaponstate = WEAPON_READY;
}
// JBravo: no shooting during LCA
if (pm->lca) {
return;
}
// Elder: fire on release - based on code from inolen
// check for fire
// if they are pressing attack and their current weapon is the grenade
if (pm->cmd.buttons & 1) {
if (pm->ps->weapon == WP_GRENADE) {
if (pm->ps->weaponstate == WEAPON_READY) {
// NiceAss: Add a delay so the pin-pull animation can complete.
pm->ps->weaponTime = 800;
// put it in the "cocked" position and play the pin-pull animation
pm->ps->weaponstate = WEAPON_COCKED;
PM_ContinueWeaponAnim(WP_ANIM_EXTRA1);
return;
} else if (pm->ps->weaponstate == WEAPON_COCKED)
return;
}
// Elder: stall the thrown knife action
else if (pm->ps->weapon == WP_KNIFE && pm->ps->weaponstate != WEAPON_STALL &&
pm->ps->stats[STAT_WEAPONSTALLTIME] <= 0 &&
!(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE) && pm->ps->ammo[pm->ps->weapon]) {
pm->ps->weaponstate = WEAPON_STALL;
pm->ps->stats[STAT_WEAPONSTALLTIME] = 200;
PM_StartWeaponAnim(WP_ANIM_THROWFIRE);
return;
}
}
// check for fireA release
// if they aren't pressing attack
if (!(pm->cmd.buttons & 1)) {
// if we had them cocked and then they aren't pressing it then
// that means they released it
if (pm->ps->weapon == WP_GRENADE) {
if (pm->ps->weaponstate == WEAPON_COCKED) {
// Stall for the toss motion
pm->ps->weaponstate = WEAPON_STALL;
pm->ps->stats[STAT_WEAPONSTALLTIME] = 300;
PM_StartWeaponAnim(WP_ANIM_FIRE);
return;
} else if (pm->ps->weaponstate == WEAPON_STALL) {
// set to be able to fire
pm->ps->weaponstate = WEAPON_READY;
} else {
// else if they arn't pressing attack, then they just are running around
pm->ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
return;
}
}
else if (pm->ps->weapon == WP_KNIFE && !(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE)) {
if (pm->ps->weaponstate == WEAPON_STALL) {
pm->ps->stats[STAT_WEAPONSTALLTIME] = 0;
pm->ps->weaponstate = WEAPON_READY;
} else {
// else if they arn't pressing attack, then they just are running around
pm->ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
return;
}
}
else {
// else if they arn't pressing attack, then they just are running around
pm->ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
return;
}
}
// check for out of ammo
if (!pm->ps->ammo[pm->ps->weapon]) {
PM_AddEvent(EV_NOAMMO);
//NiceAss: Dirty hack:
if (pm->ps->weapon == WP_KNIFE || pm->ps->weapon == WP_GRENADE)
//PM_FinishWeaponChange();
pm->ps->weaponstate = WEAPON_DROPPING;
else
pm->ps->weaponTime += 500;
return;
}
//Elder: custom player model fire animations go here
// start the animation even if out of ammo -- Elder: NO WAY
if (pm->ps->weapon == WP_KNIFE || pm->ps->weapon == WP_GRENADE) {
if (pm->ps->weapon == WP_KNIFE && (pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE))
// Modified by agt for slash "bursting"
if (!pm->ps->stats[STAT_BURST])
PM_StartWeaponAnim(WP_ANIM_FIRE);
PM_StartTorsoAnim(TORSO_ATTACK2);
} else {
// Elder: don't repeat if semi-auto
if (!(pm->ps->weapon == WP_PISTOL && pm->ps->stats[STAT_BURST]) ||
!(pm->ps->weapon == WP_M4 && pm->ps->stats[STAT_BURST] > 2) ||
!(pm->ps->weapon == WP_MP5 && pm->ps->stats[STAT_BURST] > 2)) {
// NiceAss: Support for akimbo torso attack animations
if ( pm->ps->weapon == WP_AKIMBO )
PM_StartTorsoAnim(TORSO_ATTACK3);
else if ( pm->ps->weapon == WP_PISTOL )
PM_StartTorsoAnim(TORSO_ATTACK4);
else
PM_StartTorsoAnim(TORSO_ATTACK);
// QUARANTINE - Weapon animations
// This should change pm->ps->generic1 so we can animate
// Elder: don't repeat if on semi-auto
// temp hack
if (pm->ps->weapon == WP_AKIMBO) {
// don't repeat animation if on second akimbo shot
if (!pm->ps->stats[STAT_BURST]) {
if (pm->ps->ammo[pm->ps->weapon] > 2)
PM_StartWeaponAnim(WP_ANIM_FIRE);
else
PM_StartWeaponAnim(WP_ANIM_EXTRA1);
}
} else {
if (pm->ps->ammo[pm->ps->weapon] > 1 || pm->ps->weapon != WP_PISTOL)
PM_StartWeaponAnim(WP_ANIM_FIRE);
else
PM_StartWeaponAnim(WP_ANIM_EXTRA1); // Fix for last round fired
}
}
}
//NiceAss: Hack of the week!!!!
if (pm->ps->weaponstate == WEAPON_FIRING && pm->ps->weapon == WP_GRENADE)
return;
pm->ps->weaponstate = WEAPON_FIRING;
// Elder: increment stat if alt-fire mode --needs to be predicted as well
if ((pm->ps->weapon == WP_M4 &&
(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_M4MODE) == RQ3_M4MODE) ||
(pm->ps->weapon == WP_MP5 &&
(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_MP5MODE) == RQ3_MP5MODE) ||
(pm->ps->weapon == WP_PISTOL &&
(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_MK23MODE) == RQ3_MK23MODE) ||
(pm->ps->weapon == WP_KNIFE && (pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE) == RQ3_KNIFEMODE)) {
pm->ps->stats[STAT_BURST]++;
}
//Elder: M4 kick code
/*
if (pm->ps->weapon == WP_M4 && ((pm->ps->persistant[PERS_WEAPONMODES] & RQ3_M4MODE) != RQ3_M4MODE)) {
pm->ps->delta_angles[0] = ANGLE2SHORT(SHORT2ANGLE(pm->ps->delta_angles[0]) - 0.7);
}
*/
// take an ammo away if not infinite
if (pm->ps->ammo[pm->ps->weapon] != -1) {
//Elder: don't remove ammo if slashing knife
if (!
(pm->ps->weapon == WP_KNIFE
&& (pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE) == RQ3_KNIFEMODE)) {
pm->ps->ammo[pm->ps->weapon]--;
}
//Elder: special weapon case handling
//Elder: remove knives from inventory if out of ammo
if (pm->ps->weapon == WP_KNIFE && pm->ps->ammo[WP_KNIFE] == 0) {
pm->ps->stats[STAT_WEAPONS] &= ~(1 << WP_KNIFE);
}
//Elder: remove grenade from inventory if out of ammo
else if (pm->ps->weapon == WP_GRENADE && pm->ps->ammo[WP_GRENADE] == 0) {
pm->ps->stats[STAT_WEAPONS] &= ~(1 << WP_GRENADE);
}
//Elder: remove one more bullet/shell if handcannon
else if (pm->ps->weapon == WP_HANDCANNON) {
pm->ps->ammo[WP_HANDCANNON]--;
}
//Elder: sync bullets a la AQ2 style
if (pm->ps->weapon == WP_AKIMBO && pm->ps->ammo[WP_AKIMBO] < 12) {
pm->ps->ammo[WP_PISTOL] = pm->ps->ammo[WP_AKIMBO];
} else if (pm->ps->weapon == WP_PISTOL && pm->ps->ammo[WP_AKIMBO] > 0) {
pm->ps->ammo[WP_AKIMBO]--;
}
}
// fire weapon
// NiceAss: Check to see if this is game or cgame
//if (pm->predict) {
//Elder: check for silencer
// JBravo: new multiple itemcode
if ((pm->ps->stats[STAT_HOLDABLE_ITEM] & (1 << HI_SILENCER)) &&
(pm->ps->weapon == WP_PISTOL || pm->ps->weapon == WP_MP5 || pm->ps->weapon == WP_SSG3000)) {
PM_AddEvent2(EV_FIRE_WEAPON, RQ3_WPMOD_SILENCER);
} else if (pm->ps->stats[STAT_BURST] > 1 && pm->ps->weapon == WP_KNIFE &&
(pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE) == RQ3_KNIFEMODE) {
// NiceAss: Prevent the client from doing stuff after the first "slash".
PM_AddEvent2(EV_FIRE_WEAPON, RQ3_WPMOD_KNIFENOMARK);
} else
PM_AddEvent(EV_FIRE_WEAPON);
//}
switch (pm->ps->weapon) {
default:
//Elder: weapon delay times using constants
case WP_SSG3000:
addTime = RQ3_SSG3000_DELAY;
break;
case WP_PISTOL:
addTime = RQ3_PISTOL_DELAY;
break;
case WP_M4:
addTime = RQ3_M4_DELAY;
break;
case WP_MP5:
addTime = RQ3_MP5_DELAY;
break;
case WP_KNIFE:
if ((pm->ps->persistant[PERS_WEAPONMODES] & RQ3_KNIFEMODE) == RQ3_KNIFEMODE) {
//knife slash
//agt: hacking it up a little. The alternate time value should be
//a constant, but I have a headache and I'm tired of screwing around
//with the knives. [NiceAss: I'll do it for you!]
if (pm->ps->stats[STAT_BURST]) {
addTime = RQ3_KNIFE_SLASH_BURST; //NiceAss: Not really a burst, but it fits with everything else =D
} else {
addTime = RQ3_KNIFE_DELAY;
}
} else {
//knife throw
addTime = RQ3_THROW_DELAY;
}
break;
case WP_HANDCANNON:
addTime = RQ3_HANDCANNON_DELAY;
break;
case WP_M3:
addTime = RQ3_M3_DELAY;
break;
case WP_AKIMBO:
if (pm->ps->stats[STAT_BURST]) {
addTime = RQ3_AKIMBO_DELAY2;
pm->ps->stats[STAT_BURST] = 0;
} else {
pm->ps->stats[STAT_BURST] = 1;
addTime = RQ3_AKIMBO_DELAY;
}
break;
case WP_GRENADE:
addTime = RQ3_GRENADE_DELAY;
break;
}
pm->ps->weaponTime += addTime;
//Auto-switch when out of ammo with grenade or knife
if ((pm->ps->weapon == WP_KNIFE || pm->ps->weapon == WP_GRENADE) && !pm->ps->ammo[pm->ps->weapon]) {
PM_AddEvent(EV_NOAMMO);
pm->ps->weaponTime += 150;
}
}
/*
================
PM_Animate
================
*/
static void PM_Animate(void)
{
if (pm->cmd.buttons & BUTTON_GESTURE) {
if (pm->ps->torsoTimer == 0) {
PM_StartTorsoAnim(TORSO_GESTURE);
pm->ps->torsoTimer = TIMER_GESTURE;
PM_AddEvent(EV_TAUNT);
}
}
}
/*
================
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);
}
}
/*
===================
PM_LadderMove()
by: Calrathan [Arthur Tomlin]
Right now all I know is that this works for VERTICAL ladders.
Ladders with angles on them (urban2 for AQ2) haven't been tested.
===================
*/
static void PM_LadderMove(void)
{
int i, old;
vec3_t wishvel;
float wishspeed;
vec3_t wishdir;
float scale;
float vel;
// Elder: ladder jump crap
trace_t tr;
vec3_t lookAhead;
vec3_t trEndTest;
// New experimental jump code -- not too good
#if 0
// Elder: ladder jump crap
lookAhead[0] = pml.forward[0];
lookAhead[1] = pml.forward[1];
lookAhead[2] = 0;
VectorNormalize(lookAhead);
// Calculate end point
VectorMA(pm->ps->origin, 1, lookAhead, trEndTest);
//trEndTest[2] += 20;
trEndTest[2] += 6;
// Calculate start point
VectorCopy(pm->ps->origin, lookAhead);
//lookAhead[2] += 16;
lookAhead[2] += 2;
pm->trace(&tr, lookAhead, pm->mins, pm->maxs, trEndTest, pm->ps->clientNum, MASK_PLAYERSOLID);
if (tr.fraction == 1 || !(tr.surfaceFlags & SURF_LADDER)) {
// good conditions -- now we can set up a double jump on the ladder
if (pm->debugLevel)
Com_Printf("Ladder jump conditions met...\n");
if (pm->ps->stats[STAT_JUMPTIME] > 0 && PM_CheckJump()) {
if (pm->debugLevel)
Com_Printf("Trying ladder jump...\n");
pml.ladder = qfalse;
}
}
// End ladder jump crap
#endif
PM_Friction();
//pml.ladder = qtrue;
scale = PM_CmdScale(&pm->cmd);
// user intentions [what the user is attempting to do]
if (!scale) {
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = 0;
} else { // if they're trying to move... lets calculate it
for (i = 0; i < 3; i++)
wishvel[i] = scale * pml.forward[i] * pm->cmd.forwardmove +
scale * pml.right[i] * pm->cmd.rightmove;
//Elder: changed from a factor of 2 to 10
wishvel[0] /= PM_LADDER_MOVE_REDUCTION;
wishvel[1] /= PM_LADDER_MOVE_REDUCTION;
wishvel[2] += scale * pm->cmd.upmove;
}
VectorCopy(wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
if (wishspeed > pm->ps->speed * pm_ladderScale) {
wishspeed = pm->ps->speed * pm_ladderScale;
}
// Old ladder jump code -- right now it works better
#if 1
// Elder: ladder jump crap
lookAhead[0] = pml.forward[0];
lookAhead[1] = pml.forward[1];
lookAhead[2] = 0;
VectorNormalize(lookAhead);
// Calculate end point
VectorMA(pm->ps->origin, 1, lookAhead, trEndTest);
trEndTest[2] += 20;
// Calculate start point
VectorCopy(pm->ps->origin, lookAhead);
lookAhead[2] += 16;
pm->trace(&tr, lookAhead, pm->mins, pm->maxs, trEndTest, pm->ps->clientNum, MASK_PLAYERSOLID);
if (tr.fraction == 1 || !(tr.surfaceFlags & SURF_LADDER)) {
// good conditions -- now we can set up a double jump on the ladder
if (pm->debugLevel)
Com_Printf("Ladder jump conditions met...\n");
if (PM_CheckJump()) {
if (pm->debugLevel)
Com_Printf("Trying ladder jump...\n");
}
}
// End ladder jump crap
#endif
PM_Accelerate(wishdir, wishspeed, pm_ladderAccelerate);
// This SHOULD help us with sloped ladders, but it remains untested.
if (pml.groundPlane && DotProduct(pm->ps->velocity, pml.groundTrace.plane.normal) < 0) {
vel = VectorLength(pm->ps->velocity);
// slide along the ground plane [the ladder section under our feet]
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_StepSlideMove(qfalse); // move without gravity
// Elder: stop legs from animating
//PM_ForceLegsAnim(LEGS_JUMP);
//Makro - changed to LEGS_IDLE
PM_ForceLegsAnim(LEGS_IDLE);
//Makro - play footstep sound
//...only if we don't have slippers!
if (pm->ps->velocity[2] && !(pm->ps->stats[STAT_HOLDABLE_ITEM] && (1 << HI_SLIPPERS)) )
{
old = pm->ps->bobCycle;
//the faster we move, the more frequent the footsteps
pm->ps->bobCycle = (int) (old + 0.45 * fabs(pm->ps->velocity[2]) / 225.0f * pml.msec) & 255;
// if we just crossed a cycle boundary, play an apropriate footstep event
if (((old + 64) ^ (pm->ps->bobCycle + 64)) & 128)
{
//PM_AddEvent(PM_FootstepForSurface(pml.ladderSurface));
}
} else {
pm->ps->bobCycle = 0;
}
}
/*
=============
CheckLadder [ ARTHUR TOMLIN ]
=============
*/
void CheckLadder(void)
{
vec3_t flatforward, spot;
trace_t trace;
pml.ladder = qfalse;
// check for ladder
flatforward[0] = pml.forward[0];
flatforward[1] = pml.forward[1];
flatforward[2] = 0;
VectorNormalize(flatforward);
VectorMA(pm->ps->origin, 1, flatforward, spot);
pm->trace(&trace, pm->ps->origin, pm->mins, pm->maxs, spot, pm->ps->clientNum, MASK_PLAYERSOLID);
//Makro - save the surface flags so we can play the appropriate sound
pml.ladderSurface = trace.surfaceFlags;
if ((trace.fraction < 1) && (trace.surfaceFlags & SURF_LADDER))
pml.ladder = qtrue;
// Elder: does this work?
/*
if (pml.ladder && pml.previous_ladder == qfalse)
{
if (pm->debugLevel)
Com_Printf("Hit ladder hard\n");
PM_CrashLand();
}
*/
}
/*
================
PmoveSingle
================
*/
void trap_SnapVector(float *v);
void PmoveSingle(pmove_t * pmove)
{
pm = pmove;
// this counter lets us debug movement problems with a journal
// by setting a conditional breakpoint fot the previous frame
c_pmove++;
// clear results
pm->numtouch = 0;
pm->watertype = 0;
pm->waterlevel = 0;
if (pm->ps->stats[STAT_HEALTH] <= 0) {
pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies
}
// make sure walking button is clear if they are running, to avoid
// proxy no-footsteps cheats
if (abs(pm->cmd.forwardmove) > 64 || abs(pm->cmd.rightmove) > 64) {
pm->cmd.buttons &= ~BUTTON_WALKING;
}
// set the talk balloon flag
if (pm->cmd.buttons & BUTTON_TALK) {
pm->ps->eFlags |= EF_TALK;
} else {
pm->ps->eFlags &= ~EF_TALK;
}
// set the firing flag for continuous beam weapons
if (!(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION && pm->ps->pm_type != PM_NOCLIP
&& (pm->cmd.buttons & BUTTON_ATTACK) && pm->ps->ammo[pm->ps->weapon]) {
pm->ps->eFlags |= EF_FIRING;
} else {
pm->ps->eFlags &= ~EF_FIRING;
}
// 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;
// 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->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
}
// 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->pm_type == PM_DEAD) {
PM_DeadMove();
}
PM_DropTimers();
CheckLadder(); // ARTHUR TOMLIN check and see if they're on a ladder
if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) {
PM_GrappleMove();
// We can wiggle a bit
PM_AirMove();
} else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) {
PM_WaterJumpMove();
} else if (pm->waterlevel > 1 && !pml.ladder) { //Blaze: wtf, need to add a comment to make it a big enough change for cvs to notice
// swimming
PM_WaterMove();
// Elder: Added !(...) check below to prevent crouch spasms at bottom of ladders
} else if (pml.ladder
&& !(pm->ps->viewheight == CROUCH_VIEWHEIGHT && pm->ps->groundEntityNum != ENTITYNUM_NONE)) {
PM_LadderMove();
} else if (pml.walking) {
// walking on ground
PM_WalkMove();
// NiceAss: New leg damage, based on the AQ2 leg damage code.
if (pm->ps->stats[STAT_RQ3] & RQ3_LEGDAMAGE && pm->ps->groundEntityNum != ENTITYNUM_NONE) {
int i;
if ( (pm->cmd.serverTime % 600) < 250 ) {
for (i = 0; i < 3; i++) {
if ( i < 2 || pm->ps->velocity[2] > 0 )
pm->ps->velocity[i] /= 4;
}
}
}
} else {
// airborne
PM_AirMove();
}
PM_Animate();
// set groundentity, watertype, and waterlevel
PM_GroundTrace();
PM_SetWaterLevel();
// weapons
if (pm->predict) {
PM_Reload();
PM_Weapon();
}
// torso animation
PM_TorsoAnimation();
// footstep events / legs animations
//Makro - ladder footsteps already handled in PM_LadderMove
if (!pml.ladder)
PM_Footsteps();
// entering / leaving water splashes
// JBravo: only if you are alive
if (pm->ps->stats[STAT_HEALTH] > 0)
PM_WaterEvents();
// snap some parts of playerstate to save network bandwidth
trap_SnapVector(pm->ps->velocity);
}
/*
================
Pmove
Can be called by either the server or the client
================
*/
void Pmove(pmove_t * pmove)
{
int finalTime;
finalTime = pmove->cmd.serverTime;
if (finalTime < pmove->ps->commandTime) {
return; // should not happen
}
if (finalTime > pmove->ps->commandTime + 1000) {
pmove->ps->commandTime = finalTime - 1000;
}
pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount + 1) & ((1 << PS_PMOVEFRAMECOUNTBITS) - 1);
// chop the move up if it is too long, to prevent framerate
// dependent behavior
while (pmove->ps->commandTime != finalTime) {
int msec;
msec = finalTime - pmove->ps->commandTime;
if (pmove->pmove_fixed) {
if (msec > pmove->pmove_msec) {
msec = pmove->pmove_msec;
}
} else {
if (msec > 66) {
msec = 66;
}
}
pmove->cmd.serverTime = pmove->ps->commandTime + msec;
PmoveSingle(pmove);
if (pmove->ps->pm_flags & PMF_JUMP_HELD) {
pmove->cmd.upmove = 20;
}
}
//PM_CheckStuck();
}