quake3live/code/game/bg_pmove.c
serge_shubin ba2dbb803e Implant QL pmove system into Q3 Team Arena
Replace Q3TA player movement with Quake Live's pmove system,
decompiled from qagamex64.so via Ghidra MCP.

Struct changes:
- playerState_t: +12 bytes (field_08c, field_094, field_09c,
  lastJumpTime, jumped, crouchSlideTimer, forwardmove/rightmove/upmove)
- usercmd_t: +4 bytes (field_15, field_16 between weapon and forwardmove)
- pmove_t: +16 bytes (field_0dc/0e0/0e4/0f8, removed framecount)
- pml_t: +12 bytes (isJumppad, wallContact, isStepJump)
- pm_flags: remapped to QL values (21 flags, several moved/new)

New physics systems:
- Dual VQ3/PQL physics (PMF_PROMODE)
- Double jump, crouch slide, chain/ramp/step jump
- Wall contact movement, grapple hook physics
- 34 server-tunable pmove_* cvars with cgame prediction sync

Network protocol updated for new struct fields and usercmd bytes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 02:57:53 +08:00

2522 lines
57 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
//
// bg_pmove.c -- both games player movement code
// takes a playerstate and a usercmd as input and returns a modifed playerstate
//
// Reconstructed from Quake Live binary — replaces the Q3TA pmove.
#include "q_shared.h"
#include "bg_public.h"
#include "bg_local.h"
#include <math.h>
pmove_t *pm;
pml_t pml;
// movement parameters
float pm_stopspeed = 100.0f;
float pm_duckScale = 0.25f;
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;
// QL cached cvar globals
float pm_jumpVelocity = 270.0f;
float pm_jumpVelocityMax = 400.0f;
float pm_jumpVelocityScaleAdd = 0.5f;
float pm_jumpVelocityTimeThreshold = 400.0f;
float pm_jumpVelocityTimeThresholdOffset = 0.75f;
float pm_jumpTimeDeltaMin = 0.0f;
int pm_chainJump = 1;
float pm_chainJumpVelocity = 50.0f;
float pm_stepJumpVelocity = 50.0f;
float pm_rampJumpScale = 0.5f;
float pm_stepHeight = 18.0f;
float pm_walkAccel = 10.0f;
float pm_walkFriction = 6.0f;
float pm_strafeAccel = 1.0f;
float pm_circleStrafeFriction = 6.0f;
float pm_crouchSlideFriction = 1.0f;
int pm_crouchSlideTime = 1000;
float pm_waterSwimScale = 0.50f;
float pm_waterWadeScale = 0.70f;
int pm_weaponDropTime = 200;
int pm_weaponRaiseTime = 250;
float pm_wishSpeed = 400.0f;
int pm_airSteps = 0;
float pm_airStepFriction = 1.0f;
float pm_airAccel = 1.0f;
float pm_airStopAccel = 1.0f;
float pm_airControl = 0.0f;
int pm_autoHopEnabled = 0;
int pm_bunnyHop = 0; /* DAT_001ab5c4 related */
int pm_stepJumpEnabled = 0;
int pm_crouchStepJump = 0;
int pm_rampJumpEnabled = 0;
int pm_noPlayerClip = 0;
float pm_velocityGh = 0.0f;
// QL promode locals
static float pm_airstopaccelerate = 1.0f;
static float pmove_crouchSlideVal = 0.0f;
static int pm_bunnyHopEnabled = 0;
// QL extern flags
int pmove_globalFlag = 0;
int pmove_rampJumpFlag = 0;
int pmove_stepJumpFlag = 0;
float _DAT_003cfe90 = 0.0f; // step-up velocity damping factor
int DAT_003cfe30 = 0; // edge grab enabled
// Forward declarations for functions in this file
static void PM_StartTorsoAnim( int anim );
static void PM_StartLegsAnim( int anim );
/*
===============
PM_AddEvent
===============
*/
void PM_AddEvent( int newEvent ) {
BG_AddPredictableEventToPlayerstate( newEvent, 0, 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;
}
/*
===================
PM_StartLegsAnim
===================
*/
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;
}
/*
===================
PM_ContinueLegsAnim
===================
*/
void PM_ContinueLegsAnim( int anim ) {
playerState_t *ps;
ps = pm->ps;
if ( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) != anim
&& ps->legsTimer < 1
&& ps->pm_type < PM_DEAD ) {
ps->legsAnim = ( ~ps->legsAnim & ANIM_TOGGLEBIT ) | anim;
}
}
static void PM_ContinueTorsoAnim( int anim ) {
if ( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) {
return;
}
if ( pm->ps->torsoTimer > 0 ) {
return;
}
PM_StartTorsoAnim( anim );
}
/*
===================
PM_ForceLegsAnim
===================
*/
void PM_ForceLegsAnim( int anim ) {
playerState_t *ps;
pm->ps->legsTimer = 0;
ps = pm->ps;
if ( ps->pm_type < PM_DEAD && ps->legsTimer < 1 ) {
ps->legsAnim = ( ~ps->legsAnim & ANIM_TOGGLEBIT ) | anim;
}
}
/*
==================
PM_ClipVelocity
Slide off of the impacting surface
QL version: no overbounce parameter, uses OVERCLIP constant internally
==================
*/
void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) {
float backoff;
backoff = DotProduct( in, normal );
if ( backoff < 0.0f ) {
backoff *= overbounce;
} else {
backoff /= overbounce;
}
// negate backoff (Ghidra: xor with sign bit)
backoff = -backoff;
out[0] = normal[0] * backoff + in[0];
out[1] = normal[1] * backoff + in[1];
out[2] = backoff * normal[2] + in[2];
}
/*
===============
PM_SetupPhysicsOverrides
Sets up physics parameters based on PMF_PROMODE flag
===============
*/
void PM_SetupPhysicsOverrides( void ) {
if ( ( pm->ps->pm_flags & PMF_PROMODE ) == 0 ) {
pmove_crouchSlideVal = 0.0f;
pm_airaccelerate = 1.0f;
pm_airstopaccelerate = 1.0f;
pm_chainJump = 1;
pm_circleStrafeFriction = 6.0f;
pm_strafeAccel = 1.0f;
pm_walkAccel = 10.0f;
pm_wishSpeed = 400.0f;
return;
}
pmove_crouchSlideVal = 150.0f;
pm_airaccelerate = 1.1f;
pm_airstopaccelerate = 2.5f;
pm_chainJump = 3;
pm_circleStrafeFriction = 5.5f;
pm_strafeAccel = 70.0f;
pm_walkAccel = 15.0f;
pm_wishSpeed = 35.0f;
}
/*
==============
PM_CheckDuck
Sets mins, maxs, and pm->ps->viewheight
QL version: adds crouch slide support
==============
*/
void PM_CheckDuck( void ) {
trace_t tr;
playerState_t *ps;
int pm_flags;
ps = pm->ps;
if ( ps->powerups[PW_INVULNERABILITY] != 0 ) {
if ( ( ps->pm_flags & PMF_INVUL_EXPAND ) == 0 ) {
pm->mins[0] = -15.0f;
pm->mins[1] = -15.0f;
pm->mins[2] = MINS_Z;
pm->maxs[0] = 15.0f;
pm->maxs[1] = 15.0f;
pm->maxs[2] = 16.0f;
} else {
pm->mins[0] = -42.0f;
pm->mins[1] = -42.0f;
pm->mins[2] = -42.0f;
pm->maxs[0] = 42.0f;
pm->maxs[1] = 42.0f;
pm->maxs[2] = 42.0f;
}
ps->pm_flags |= PMF_DUCKED;
pm->ps->viewheight = CROUCH_VIEWHEIGHT;
return;
}
ps->pm_flags &= ~PMF_INVUL_EXPAND;
pm->mins[0] = -15.0f;
pm->mins[1] = -15.0f;
pm->mins[2] = MINS_Z;
pm->maxs[0] = 15.0f;
pm->maxs[1] = 15.0f;
pm->maxs[2] = 32.0f;
ps = pm->ps;
if ( ps->pm_type == PM_DEAD ) {
pm->maxs[2] = -8.0f;
ps->viewheight = DEAD_VIEWHEIGHT;
return;
}
if ( pm->cmd.upmove < 0 ) {
ps->pm_flags |= PMF_DUCKED;
ps = pm->ps;
if ( ps->field_1d4 == 0 ) {
ps->field_1d4 = pm->cmd.serverTime;
ps = pm->ps;
}
pm_flags = ps->pm_flags;
} else {
if ( ( ps->pm_flags & PMF_DUCKED ) == 0 ) {
goto skip;
}
pm->trace( &tr, ps->origin, pm->mins, pm->maxs, ps->origin,
ps->clientNum, pm->tracemask );
if ( tr.allsolid == 0 ) {
pm->ps->pm_flags &= ~PMF_DUCKED;
}
ps = pm->ps;
pm_flags = ps->pm_flags;
}
if ( ( pm_flags & PMF_DUCKED ) != 0 ) {
if ( ps->crouchSlideTimer == 0
&& ( pm_flags & PMF_CROUCH_SLIDE ) != 0
&& (float)ps->speed < 400.0f ) { /* DAT_001a8988 = 400.0f */
ps->speed = 400;
ps = pm->ps;
}
pm->maxs[2] = 16.0f;
ps->viewheight = CROUCH_VIEWHEIGHT;
return;
}
skip:
ps->viewheight = DEFAULT_VIEWHEIGHT;
ps = pm->ps;
if ( ( ps->pm_flags & PMF_CROUCH_SLIDE ) != 0
&& ps->crouchSlideTimer != 0
&& pml.groundPlane != 0 ) {
ps->crouchSlideTimer = 0;
}
}
/*
================
PM_DropTimers
================
*/
void PM_DropTimers( void ) {
playerState_t *ps;
int pm_time;
ps = pm->ps;
pm_time = ps->pm_time;
if ( pm_time != 0 ) {
if ( pml.msec < pm_time ) {
ps->pm_time = pm_time - pml.msec;
} else {
ps->pm_flags &= ~PMF_ALL_TIMES;
pm->ps->pm_time = 0;
}
ps = pm->ps;
}
if ( ps->legsTimer > 0 ) {
ps->legsTimer -= pml.msec;
ps = pm->ps;
if ( ps->legsTimer < 0 ) {
ps->legsTimer = 0;
ps = pm->ps;
}
}
if ( ps->torsoTimer > 0 ) {
ps->torsoTimer -= pml.msec;
ps = pm->ps;
if ( ps->torsoTimer < 0 ) {
ps->torsoTimer = 0;
ps = pm->ps;
}
}
// crouch slide timer
if ( ( ps->pm_flags & PMF_CROUCH_SLIDE ) != 0
&& pml.groundPlane != 0
&& ps->crouchSlideTimer != 0 ) {
if ( ps->crouchSlideTimer <= pml.msec ) {
ps->crouchSlideTimer = 0;
} else {
ps->crouchSlideTimer = ps->crouchSlideTimer - pml.msec;
}
}
}
/*
==============
PM_Accelerate
QL version: includes bunny hop forward boost logic
==============
*/
void PM_Accelerate( float wishspeed, float accel, vec3_t wishdir ) {
float addspeed, accelspeed, currentspeed;
float airControlScale;
float speed, doubleSpeed;
playerState_t *ps;
ps = pm->ps;
currentspeed = DotProduct( ps->velocity, wishdir );
addspeed = wishspeed - currentspeed;
speed = sqrt( ps->velocity[0] * ps->velocity[0] + ps->velocity[1] * ps->velocity[1] );
if ( addspeed <= 0.0f ) {
// bunny hop: forward boost when moving forward (movementDir == 0)
if ( pm_bunnyHopEnabled != 0 && ps->movementDir == 0 ) {
float playerSpeed = (float)ps->speed;
doubleSpeed = playerSpeed + playerSpeed;
if ( speed <= doubleSpeed ) {
airControlScale = pm_velocityGh; /* DAT_001ab5c4 */
if ( playerSpeed < speed ) {
if ( doubleSpeed <= speed ) {
speed = doubleSpeed;
}
airControlScale = ( ( doubleSpeed - speed ) / playerSpeed ) * pm_velocityGh;
}
ps->velocity[0] += wishdir[0] * airControlScale;
pm->ps->velocity[1] += wishdir[1] * airControlScale;
pm->ps->velocity[2] += wishdir[2] * airControlScale;
}
}
return;
}
accelspeed = wishspeed * pml.frametime * accel;
if ( addspeed <= accelspeed ) {
accelspeed = addspeed;
}
ps->velocity[0] += wishdir[0] * accelspeed;
pm->ps->velocity[1] += wishdir[1] * accelspeed;
pm->ps->velocity[2] += wishdir[2] * accelspeed;
}
/*
============
PM_CmdScale
Returns the scale factor to apply to cmd movements
QL version: takes forwardmove, rightmove, upmove as separate params
============
*/
void PM_CmdScale( usercmd_t *cmd, float *scale ) {
int max;
float total;
int fm, rm, um;
fm = abs( cmd->forwardmove );
rm = abs( cmd->rightmove );
um = abs( cmd->upmove );
max = fm;
if ( rm > max ) {
max = rm;
}
if ( um > max ) {
max = um;
}
if ( max == 0 ) {
*scale = 0.0f;
return;
}
total = sqrt( (float)( fm * fm + rm * rm + um * um ) );
*scale = (float)pm->ps->speed * (float)max / ( 127.0f * total );
}
// Internal helper: returns scale as float (matches Ghidra's undefined8 return)
static float PM_CmdScaleValue( signed char forwardmove, signed char rightmove, signed char upmove ) {
int max;
float total;
int fm, rm, um;
fm = abs( forwardmove );
rm = abs( rightmove );
um = abs( upmove );
max = fm;
if ( rm > max ) {
max = rm;
}
if ( um > max ) {
max = um;
}
if ( max == 0 ) {
return 0.0f;
}
total = sqrt( (float)( fm * fm + rm * rm + um * um ) );
return (float)pm->ps->speed * (float)max / ( 127.0f * total );
}
/*
==============
PM_BuildWishVelocity
Builds the wish velocity vector from the command
Returns 1 if the player is moving, 0 if not
==============
*/
int PM_BuildWishVelocity( usercmd_t *cmd, float *wishspeed, vec3_t wishdir ) {
int i;
float scale;
float wishspeedNorm;
signed char fmove, smove;
scale = PM_CmdScaleValue( cmd->forwardmove, cmd->rightmove, cmd->upmove );
fmove = pm->cmd.forwardmove;
smove = pm->cmd.rightmove;
if ( scale != 0.0f ) {
for ( i = 0; i < 3; i++ ) {
wishdir[i] = pml.forward[i] * (float)(int)fmove
+ pml.right[i] * (float)(int)smove;
}
wishdir[2] += (float)(int)pm->cmd.upmove;
wishspeedNorm = VectorNormalize( wishdir );
*wishspeed = wishspeedNorm * scale;
return 1;
}
wishdir[0] = 0.0f;
wishdir[1] = 0.0f;
wishdir[2] = 0.0f;
*wishspeed = 0.0f;
return 0;
}
/*
================
PM_SetMovementDir
Determine the rotation of the legs relative to the facing dir
================
*/
void PM_SetMovementDir( void ) {
signed char fmove, rmove;
fmove = pm->cmd.forwardmove;
rmove = pm->cmd.rightmove;
if ( fmove == 0 && rmove == 0 ) {
if ( pm->ps->movementDir == 2 ) {
pm->ps->movementDir = 1;
} else if ( pm->ps->movementDir == 6 ) {
pm->ps->movementDir = 7;
}
return;
}
if ( fmove == 0 ) {
if ( rmove > 0 ) {
pm->ps->movementDir = 6;
} else {
pm->ps->movementDir = 2;
}
return;
}
if ( rmove == 0 ) {
if ( fmove > 0 ) {
pm->ps->movementDir = 0;
} else {
pm->ps->movementDir = 4;
}
return;
}
if ( rmove > 0 ) {
if ( fmove < 0 ) {
pm->ps->movementDir = 5;
} else if ( fmove > 0 ) {
pm->ps->movementDir = 7;
} else {
pm->ps->movementDir = 6;
}
} else {
if ( fmove > 0 ) {
pm->ps->movementDir = 1;
} else if ( fmove < 0 ) {
pm->ps->movementDir = 3;
} else {
pm->ps->movementDir = 2;
}
}
}
/*
==================
PM_Friction
Handles ground, water, air, and wall contact friction
QL version: adds promode circle strafe friction, crouch slide friction,
wall contact friction, and flight/freeze friction
==================
*/
void PM_Friction( void ) {
float speed, newspeed, drop, control;
float vel_z_sq;
playerState_t *ps;
int waterlvl;
int pm_flags;
ps = pm->ps;
vel_z_sq = 0.0f;
if ( pml.walking == 0 ) {
vel_z_sq = ps->velocity[2] * ps->velocity[2];
}
speed = sqrt( ps->velocity[0] * ps->velocity[0]
+ ps->velocity[1] * ps->velocity[1] + vel_z_sq );
if ( speed < 1.0f ) {
ps->velocity[0] = 0.0f;
pm->ps->velocity[1] = 0.0f;
return;
}
waterlvl = pm->waterlevel;
drop = 0.0f;
if ( waterlvl < 2 ) {
if ( pml.walking == 0
|| ( pml.groundTrace.surfaceFlags & SURF_SLICK )
|| ( ps->pm_flags & PMF_TIME_WATERJUMP ) ) {
drop = 0.0f;
} else {
control = pm_stopspeed;
if ( pm_stopspeed <= speed ) {
control = speed;
}
pm_flags = ps->pm_flags;
if ( ( pm_flags & PMF_CROUCH_SLIDE ) != 0
&& pm->cmd.upmove < 0
&& ps->crouchSlideTimer > 0 ) {
// crouch slide friction
drop = control * pml.frametime * pm_crouchSlideFriction;
} else if ( ( pm_flags & PMF_PROMODE ) != 0
&& pm->cmd.forwardmove != 0
&& pm->cmd.rightmove != 0
&& ( ps->movementDir == 1 || ps->movementDir == 5 ) ) {
// promode circle strafe friction
drop = control * pml.frametime * pm_circleStrafeFriction;
} else {
// normal walk friction
drop = control * pml.frametime * pm_walkFriction;
}
}
// water friction when waterlevel == 1
if ( waterlvl != 0 ) {
if ( pml.wallContact == 0 ) {
// apply water friction based on waterlevel
drop += speed * (float)waterlvl * pml.frametime;
}
}
} else {
// deep water
if ( pml.wallContact == 0 ) {
drop += speed * (float)waterlvl * pml.frametime;
}
}
// spectator friction
if ( ps->pm_type == PM_SPECTATOR ) {
drop += speed * pm_spectatorfriction * pml.frametime;
}
// flight powerup friction
if ( ps->powerups[PW_FLIGHT] ) {
drop += speed * pm_flightfriction * pml.frametime;
}
// PM_FREEZE doubles friction
if ( ps->pm_type == PM_FREEZE ) {
drop = drop * 2.0f;
}
// wall contact friction
if ( pml.wallContact != 0 ) {
drop += pm_airStepFriction * speed * pml.frametime; /* DAT_001a8968 */
}
newspeed = speed - drop;
if ( newspeed <= 0.0f ) {
newspeed = 0.0f;
}
newspeed /= speed;
ps->velocity[0] *= newspeed;
pm->ps->velocity[1] *= newspeed;
pm->ps->velocity[2] *= newspeed;
}
/*
=============
PM_Jump
Performs the actual jump: sets velocity, events, animations, lastJumpTime
QL version: chain jump, step jump, ramp jump logic
=============
*/
#define JUMPMODE_CHAIN 1
#define JUMPMODE_RAMP 2
#define JUMPMODE_SCALED 3
void PM_Jump( void ) {
playerState_t *ps;
float jumpvel;
float duration;
jumpvel = pm_jumpVelocity;
pml.groundPlane = qfalse;
pml.walking = qfalse;
duration = (float)( pm->cmd.serverTime - pm->ps->lastJumpTime );
pm->ps->pm_flags |= PMF_JUMP_HELD;
if ( duration > 0.0f && duration < pm_jumpVelocityTimeThreshold ) {
if ( pm_chainJump == JUMPMODE_CHAIN ) {
if ( pml.isStepJump == 1 ) {
jumpvel += pm_stepJumpVelocity;
} else {
jumpvel += pm_chainJumpVelocity;
}
} else if ( pm_chainJump == JUMPMODE_RAMP ) {
float threshold = pm_jumpVelocityTimeThreshold * pm_jumpVelocityTimeThresholdOffset;
if ( duration < threshold ) {
// below threshold: full chain jump
if ( pml.isStepJump == 1 ) {
jumpvel += pm_stepJumpVelocity;
} else {
jumpvel += pm_chainJumpVelocity;
}
} else {
// ramp down
jumpvel += ( ( threshold - duration ) / threshold ) * pm_chainJumpVelocity
+ pm_chainJumpVelocity;
}
} else if ( pm_chainJump == JUMPMODE_SCALED ) {
jumpvel *= ( ( ( pm_jumpVelocityTimeThreshold - duration )
/ pm_jumpVelocityTimeThreshold ) * pm_jumpVelocityScaleAdd + 1.0f );
}
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
ps = pm->ps;
if ( pmove_rampJumpFlag != 0 && pml.isJumppad != 1 ) {
ps->velocity[2] = ps->velocity[2] * pm_rampJumpScale;
pm->ps->velocity[2] += jumpvel;
ps = pm->ps;
if ( ps->velocity[2] < jumpvel ) {
ps->velocity[2] = jumpvel;
ps = pm->ps;
}
if ( ps->velocity[2] <= pm_jumpVelocityMax ) {
goto do_event;
}
}
ps->velocity[2] = jumpvel;
ps = pm->ps;
do_event:
BG_AddPredictableEventToPlayerstate( EV_JUMP, 0, ps );
if ( pm->cmd.forwardmove >= 0 ) {
PM_ForceLegsAnim( LEGS_JUMP );
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
} else {
PM_ForceLegsAnim( LEGS_JUMPB );
pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
}
pm->ps->lastJumpTime = pm->cmd.serverTime;
}
/*
=============
PM_CheckJump
Returns 1 if the player jumped, 0 otherwise
QL version: adds autohop, jump time delta, double jump support
=============
*/
int PM_CheckJump( int checkDoubleJump ) {
playerState_t *ps;
int pm_flags;
ps = pm->ps;
pm_flags = ps->pm_flags;
// autohop + respawned check
if ( ( pm_autoHopEnabled == 0 || ( pm_flags & PMF_AUTOHOP_HELD ) != 0 )
&& ( pm_flags & PMF_RESPAWNED ) != 0 ) {
return 0;
}
if ( pm->cmd.upmove < 10 ) {
return 0;
}
// jump time delta minimum
if ( (float)( pm->cmd.serverTime - ps->lastJumpTime ) < pm_jumpTimeDeltaMin ) {
return 0;
}
// standard jump held check
if ( pm_autoHopEnabled == 0 && ( pm_flags & PMF_JUMP_HELD ) != 0 ) {
pm->cmd.upmove = 0;
return 0;
}
pm_flags = ps->pm_flags;
// autohop + jump held: no jump
if ( ( pm_flags & ( PMF_AUTOHOP_HELD | PMF_JUMP_HELD ) )
== ( PMF_AUTOHOP_HELD | PMF_JUMP_HELD ) ) {
pm->cmd.upmove = 0;
return 0;
}
// double jump checks
if ( checkDoubleJump != 0 ) {
if ( ( pm_flags & ( PMF_DOUBLE_JUMP | PMF_JUMP_HELD ) )
== ( PMF_DOUBLE_JUMP | PMF_JUMP_HELD ) ) {
pm->cmd.upmove = 0;
return 0;
}
if ( ( pm_flags & PMF_DOUBLE_JUMP ) != 0 && ps->jumped != 0 ) {
return 0;
}
}
// clear crouch slide timer
if ( ( pm_flags & PMF_CROUCH_SLIDE ) != 0 ) {
ps->crouchSlideTimer = 0;
}
PM_Jump();
if ( checkDoubleJump != 0 && ( pm->ps->pm_flags & PMF_DOUBLE_JUMP ) != 0 ) {
pm->ps->jumped = 1;
}
return 1;
}
/*
=============
PM_CanJump
Returns qtrue if the player is able to jump
Used by step slide move for step-jump logic
=============
*/
int PM_CanJump( void ) {
playerState_t *ps;
int pm_flags;
ps = pm->ps;
pm_flags = ps->pm_flags;
if ( ( pm_autoHopEnabled != 0 && ( pm_flags & PMF_AUTOHOP_HELD ) == 0 )
|| ( pm_flags & PMF_RESPAWNED ) == 0 ) {
if ( pm->cmd.upmove > 9
&& pm_jumpTimeDeltaMin <= (float)( pm->cmd.serverTime - ps->lastJumpTime ) ) {
if ( pm_autoHopEnabled == 0 && ( pm_flags & PMF_JUMP_HELD ) != 0 ) {
pm->cmd.upmove = 0;
return qfalse;
}
if ( ( ps->pm_flags & ( PMF_AUTOHOP_HELD | PMF_JUMP_HELD ) )
== ( PMF_AUTOHOP_HELD | PMF_JUMP_HELD ) ) {
pm->cmd.upmove = 0;
return qfalse;
}
return qtrue;
}
}
return qfalse;
}
/*
=============
PM_CanEdgeGrab (actually PM_CanAirStep in QL binary)
Returns qtrue if the player can air-step (crouch + airborne + not falling + past jump threshold)
=============
*/
int PM_CanEdgeGrab( void ) {
playerState_t *ps;
ps = pm->ps;
if ( ( ps->pm_flags & PMF_DUCKED ) != 0
&& pml.groundPlane == 0
&& ps->velocity[2] >= 0.0f ) {
return pm_jumpVelocityTimeThreshold <= (float)( pm->cmd.serverTime - ps->lastJumpTime );
}
return qfalse;
}
/*
=============
PM_CheckGrapple
Returns 1 if the grapple is active and pulling
=============
*/
int PM_CheckGrapple( void ) {
playerState_t *ps;
ps = pm->ps;
if ( bg_itemlist[ ps->stats[STAT_HOLDABLE_ITEM] ].giType == IT_HOLDABLE /* entity type check: giTag == 6 */
&& ( ps->pm_flags & PMF_GRAPPLE_PULL ) != 0
&& ps->pm_type != PM_DEAD
&& ( ps->stats[12] == 0 || ps->stats[11] != 0 ) ) {
return 1;
}
return 0;
}
/*
===================
PM_GrappleMove
QL version: uses stats array for grapple parameters
===================
*/
void PM_GrappleMove( void ) {
int grappleActive;
playerState_t *ps;
grappleActive = PM_CheckGrapple();
if ( grappleActive == 0 ) {
pm->ps->powerups[PW_NEUTRALFLAG] = 0; /* powerups[9] */
return;
}
pm->ps->powerups[PW_NEUTRALFLAG] = 0x7fffffff;
ps = pm->ps;
ps->velocity[2] += (float)(int)( (float)ps->stats[9] * pml.frametime );
ps = pm->ps;
if ( ps->stats[11] < 0x7d01 ) {
ps->stats[11] -= pml.msec;
ps = pm->ps;
if ( ps->stats[11] < 1 ) {
ps->stats[11] = 0;
ps = pm->ps;
if ( ps->stats[12] == 0 ) {
ps->stats[STAT_HOLDABLE_ITEM] = 0;
ps = pm->ps;
}
}
}
if ( ( ps->pm_flags & PMF_DUCKED ) != 0 ) {
PM_ForceLegsAnim( LEGS_IDLECR );
} else if ( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_JUMP ) {
PM_ForceLegsAnim( LEGS_JUMP );
}
}
/*
===================
PM_WaterJumpMove
Flying out of the water
===================
*/
void PM_WaterJumpMove( void ) {
playerState_t *ps;
PM_StepSlideMove( qtrue );
ps = pm->ps;
ps->velocity[2] -= (float)ps->gravity * pml.frametime;
ps = pm->ps;
if ( ps->velocity[2] < 0.0f ) {
ps->pm_flags &= ~PMF_ALL_TIMES;
pm->ps->pm_time = 0;
}
}
/*
===================
PM_WaterMove
QL version: uses PM_BuildWishVelocity, water swim/wade scale cvars,
waterjump uses PMF_TIME_WATERJUMP_INIT
===================
*/
void PM_WaterMove( void ) {
float wishspeed;
vec3_t wishdir;
int result;
float swimScale;
int contents;
vec3_t spot;
vec3_t flatforward;
playerState_t *ps;
// Check for water jump
if ( pm->ps->pm_time == 0 && pm->waterlevel == 2 ) {
flatforward[0] = pml.forward[0];
flatforward[1] = pml.forward[1];
flatforward[2] = 0.0f;
VectorNormalize( flatforward );
spot[0] = flatforward[0] * 30.0f + pm->ps->origin[0]; /* DAT_001a89d4 = 30.0f */
spot[1] = flatforward[1] * 30.0f + pm->ps->origin[1];
spot[2] = 30.0f * flatforward[2] + pm->ps->origin[2] + 4.0f; /* DAT_001a8984 = 4.0f */
contents = pm->pointcontents( spot, pm->ps->clientNum );
if ( contents & CONTENTS_SOLID ) {
spot[2] += 16.0f; /* DAT_001a89b0 = 16.0f */
result = pm->pointcontents( spot, pm->ps->clientNum );
if ( result == 0 ) {
pm->ps->velocity[0] = pml.forward[0] * 200.0f; /* DAT_001a91bc = 200.0f */
pm->ps->velocity[1] = pml.forward[1] * 200.0f;
pm->ps->velocity[2] = 350.0f;
pm->ps->pm_flags |= PMF_TIME_WATERJUMP_INIT;
pm->ps->pm_time = 2000;
PM_WaterJumpMove();
return;
}
}
}
PM_Friction();
result = PM_BuildWishVelocity( &pm->cmd, &wishspeed, wishdir );
if ( result == 0 ) {
wishdir[0] = 0.0f;
wishdir[1] = 0.0f;
wishdir[2] = -60.0f;
wishspeed = VectorNormalize( wishdir );
}
swimScale = pm_waterWadeScale;
if ( pm->waterlevel != 1 ) {
swimScale = pm_waterSwimScale;
}
swimScale *= (float)pm->ps->speed;
if ( swimScale < wishspeed ) {
wishspeed = swimScale;
}
PM_Accelerate( wishspeed, 4.0f, wishdir ); /* DAT_001a8984 = 4.0f for water accel */
if ( pml.groundPlane != 0 ) {
ps = pm->ps;
if ( DotProduct( ps->velocity, pml.groundTrace.plane.normal ) < 0.0f ) {
PM_ClipVelocity( ps->velocity, pml.groundTrace.plane.normal,
ps->velocity, OVERCLIP );
}
}
PM_StepSlideMove( qfalse );
}
/*
===================
PM_AirMove
QL version: adds promode air strafe, air stop, and air control
===================
*/
void PM_AirMove( void ) {
vec3_t wishvel;
float fmove, smove;
float wishspeed;
float scale;
float accel;
playerState_t *ps;
unsigned int movedir;
// crouch slide charge in air
if ( ( pm->ps->pm_flags & PMF_CROUCH_SLIDE ) != 0 && pm->cmd.upmove >= 0 ) {
pm->ps->crouchSlideTimer += pml.msec * 5;
if ( pm_crouchSlideTime < pm->ps->crouchSlideTimer ) {
pm->ps->crouchSlideTimer = pm_crouchSlideTime;
}
}
PM_Friction();
PM_SetMovementDir();
pml.forward[2] = 0.0f;
pml.right[2] = 0.0f;
VectorNormalize( pml.forward );
VectorNormalize( pml.right );
wishvel[2] = 0.0f;
fmove = (float)(int)pm->cmd.forwardmove;
smove = (float)(int)pm->cmd.rightmove;
wishvel[0] = pml.forward[0] * fmove + pml.right[0] * smove;
wishvel[1] = fmove * pml.forward[1] + smove * pml.right[1];
wishspeed = VectorNormalize( wishvel );
scale = PM_CmdScaleValue( pm->cmd.forwardmove, pm->cmd.rightmove, pm->cmd.upmove );
ps = pm->ps;
wishspeed *= scale;
accel = pm_airaccelerate;
// promode air control logic
if ( ( ps->pm_flags & PMF_PROMODE ) != 0 ) {
if ( DotProduct( ps->velocity, wishvel ) < 0.0f ) {
accel = pm_airstopaccelerate;
movedir = ps->movementDir;
} else {
movedir = ps->movementDir;
}
// strafing (movementDir 2 or 6)
if ( ( movedir & ~4 ) == 2 ) {
accel = pm_strafeAccel;
if ( pm_wishSpeed <= wishspeed ) {
scale = pm_wishSpeed; // cap strafe wish speed
} else {
scale = wishspeed;
}
} else {
scale = wishspeed;
}
} else {
scale = wishspeed;
}
PM_Accelerate( scale, accel, wishvel );
ps = pm->ps;
// promode air control (forward movement)
if ( ( ps->pm_flags & PMF_PROMODE ) != 0
&& ( ps->movementDir & ~4 ) == 0
&& wishspeed != 0.0f ) {
float savedZ, speed, dot;
savedZ = ps->velocity[2];
ps->velocity[2] = 0.0f;
speed = VectorNormalize( pm->ps->velocity );
ps = pm->ps;
dot = DotProduct( ps->velocity, wishvel );
if ( dot > 0.0f ) {
float k = pmove_crouchSlideVal * dot * dot * pml.frametime * pm_airControl; /* DAT_001a7200 */
ps->velocity[0] = ps->velocity[0] * speed + wishvel[0] * k;
pm->ps->velocity[1] = speed * pm->ps->velocity[1] + k * wishvel[1];
VectorNormalize( pm->ps->velocity );
ps = pm->ps;
}
ps->velocity[0] *= speed;
pm->ps->velocity[1] *= speed;
pm->ps->velocity[2] = savedZ;
}
// clip to ground plane
if ( pml.groundPlane != 0 ) {
PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal,
pm->ps->velocity, OVERCLIP );
}
PM_StepSlideMove( qtrue );
// ducking animation
if ( ( pm->ps->pm_flags & PMF_DUCKED ) == 0 ) {
if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLECR ) {
PM_ForceLegsAnim( LEGS_IDLE );
}
} else {
PM_ForceLegsAnim( LEGS_IDLECR );
}
PM_GrappleMove();
// double jump in air
if ( ( pm->ps->pm_flags & PMF_DOUBLE_JUMP ) != 0 ) {
PM_CheckJump( 1 );
}
}
/*
===================
PM_FlyMove (used for spectator)
QL version in PmoveSingle handles spectator inline
===================
*/
void PM_FlyMove( void ) {
float wishspeed;
vec3_t wishdir;
PM_Friction();
PM_BuildWishVelocity( &pm->cmd, &wishspeed, wishdir );
PM_Accelerate( wishspeed, pm_flyaccelerate, wishdir );
PM_StepSlideMove( qfalse );
}
/*
================
PM_FootstepForSurface
Returns an event number appropriate for the groundsurface
QL version: adds extra surface flag checks (0x80000, 0x100000)
================
*/
int PM_FootstepForSurface( void ) {
if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) {
return 0;
}
if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) {
return EV_FOOTSTEP_METAL;
}
/* QL adds extra surface type checks */
if ( pml.groundTrace.surfaceFlags & 0x80000 ) {
return 0x51; /* custom footstep sound */
}
if ( pml.groundTrace.surfaceFlags & 0x100000 ) {
return 0x52; /* custom footstep sound 2 */
}
return EV_FOOTSTEP;
}
/*
===================
PM_CheckWallContact
QL: Checks if the player is touching a wall ahead of them
===================
*/
void PM_CheckWallContact( void ) {
vec3_t dir;
vec3_t end;
trace_t tr;
pml.wallContact = 0;
dir[0] = pml.forward[0];
dir[1] = pml.forward[1];
dir[2] = 0.0f;
VectorNormalize( dir );
end[0] = pm->ps->origin[0] + dir[0];
end[1] = pm->ps->origin[1] + dir[1];
end[2] = pm->ps->origin[2] + dir[2];
pm->trace( &tr, pm->ps->origin, pm->mins, pm->maxs, end,
pm->ps->clientNum, CONTENTS_SOLID | CONTENTS_BODY | CONTENTS_PLAYERCLIP );
if ( tr.fraction < 1.0f && ( tr.surfaceFlags & SURF_NOIMPACT ) ) {
pml.wallContact = 1;
}
}
/*
=============
PM_SetWaterLevel
QL version: simplified water level test
=============
*/
void PM_SetWaterLevel( void ) {
vec3_t point;
int cont;
pm->waterlevel = 0;
point[0] = pm->ps->origin[0];
point[1] = pm->ps->origin[1];
point[2] = pm->ps->origin[2] + MINS_Z + 1.0f;
cont = pm->pointcontents( point, pm->ps->clientNum );
pm->watertype = cont;
if ( ( cont & MASK_WATER ) != 0 ) {
pm->waterlevel = 1;
point[2] = pm->ps->origin[2];
cont = pm->pointcontents( point, pm->ps->clientNum );
if ( ( cont & MASK_WATER ) != 0 ) {
pm->waterlevel = 2;
point[2] = (float)pm->ps->viewheight + pm->ps->origin[2];
cont = pm->pointcontents( point, pm->ps->clientNum );
if ( ( cont & MASK_WATER ) != 0 ) {
pm->waterlevel = 3;
}
}
}
}
/*
=============
PM_GroundTrace
QL version: inlined PM_CorrectAllSolid, PM_GroundTraceMissed,
PM_CrashLand; handles double jump reset
=============
*/
void PM_GroundTrace( void ) {
vec3_t point;
vec3_t point2; // used in PM_CorrectAllSolid jitter loop
vec3_t traceEnd;
trace_t tr;
trace_t tr2; // second trace for GroundTraceMissed staircase check
float delta;
float dist, vel, acc, a, b, c, den, t; // crashland quadratic
playerState_t *ps;
int jitter_x, jitter_y, jitter_z;
int event;
point[0] = pm->ps->origin[0];
point[1] = pm->ps->origin[1];
point[2] = pm->ps->origin[2] - 2.0f; /* DAT_001a7214 = 2.0f */
pm->trace( &tr, pm->ps->origin, pm->mins, pm->maxs, point,
pm->ps->clientNum, pm->tracemask );
pml.groundTrace = tr;
// allsolid correction (inlined PM_CorrectAllSolid)
if ( tr.allsolid != 0 ) {
if ( pm->debugLevel ) {
Com_Printf( "%i:allsolid\n", c_pmove );
}
for ( jitter_x = -1; jitter_x <= 1; jitter_x++ ) {
for ( jitter_y = -1; jitter_y <= 1; jitter_y++ ) {
for ( jitter_z = -1; jitter_z <= 1; jitter_z++ ) {
point2[0] = (float)jitter_x + pm->ps->origin[0];
point2[1] = (float)jitter_y + pm->ps->origin[1];
point2[2] = (float)jitter_z + pm->ps->origin[2];
pm->trace( &tr, point2, pm->mins, pm->maxs, point2,
pm->ps->clientNum, pm->tracemask );
if ( tr.allsolid == 0 ) {
point2[0] = pm->ps->origin[0];
point2[1] = pm->ps->origin[1];
point2[2] = pm->ps->origin[2] - 2.0f;
pm->trace( &tr, pm->ps->origin, pm->mins, pm->maxs, point2,
pm->ps->clientNum, pm->tracemask );
pml.groundTrace = tr;
goto corrected;
}
}
}
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
corrected:
// free fall check
if ( tr.fraction == 1.0f ) {
ps = pm->ps;
if ( ps->groundEntityNum != ENTITYNUM_NONE ) {
if ( pm->debugLevel ) {
Com_Printf( "%i:lift\n", c_pmove );
ps = pm->ps;
}
traceEnd[0] = ps->origin[0];
traceEnd[1] = pm->ps->origin[1];
traceEnd[2] = pm->ps->origin[2] - 64.0f; /* DAT_001a91f8 = 64.0f */
pm->trace( &tr2, pm->ps->origin, pm->mins, pm->maxs, traceEnd,
pm->ps->clientNum, pm->tracemask );
if ( tr2.fraction == 1.0f ) {
if ( pm->cmd.forwardmove < 0 ) {
PM_ForceLegsAnim( LEGS_JUMPB );
pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
} else {
PM_ForceLegsAnim( LEGS_JUMP );
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
}
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
// check if getting thrown off the ground
ps = pm->ps;
if ( ps->velocity[2] > 0.0f
&& DotProduct( ps->velocity, tr.plane.normal ) > 10.0f ) {
if ( pm->debugLevel ) {
Com_Printf( "%i:kickoff\n", c_pmove );
}
if ( pm->cmd.forwardmove < 0 ) {
PM_ForceLegsAnim( LEGS_JUMPB );
pm->ps->pm_flags |= PMF_BACKWARDS_JUMP;
} else {
PM_ForceLegsAnim( LEGS_JUMP );
pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP;
}
pm->ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qfalse;
pml.walking = qfalse;
return;
}
// steep slope
if ( tr.plane.normal[2] < MIN_WALK_NORMAL ) {
if ( pm->debugLevel ) {
Com_Printf( "%i:steep\n", c_pmove );
ps = pm->ps;
}
ps->groundEntityNum = ENTITYNUM_NONE;
pml.groundPlane = qtrue;
pml.walking = qfalse;
return;
}
pml.groundPlane = qtrue;
pml.walking = qtrue;
ps = pm->ps;
// hitting solid ground will end a waterjump
if ( ( ps->pm_flags & PMF_TIME_WATERJUMP_INIT ) != 0 ) {
ps->pm_flags &= ~( PMF_TIME_WATERJUMP_INIT | PMF_TIME_KNOCKBACK );
pm->ps->pm_time = 0;
ps = pm->ps;
}
if ( ps->groundEntityNum == ENTITYNUM_NONE ) {
// just hit the ground — inlined PM_CrashLand
if ( pm->debugLevel ) {
Com_Printf( "%i:Land\n", c_pmove );
ps = pm->ps;
}
// landing animation
if ( ( ps->pm_flags & PMF_BACKWARDS_JUMP ) == 0 ) {
PM_ForceLegsAnim( LEGS_LAND );
} else {
PM_ForceLegsAnim( LEGS_LANDB );
}
pm->ps->legsTimer = TIMER_LAND;
ps = pm->ps;
// Q3 PM_CrashLand quadratic: solve for exact landing velocity
dist = ps->origin[2] - pml.previous_origin[2];
vel = pml.previous_velocity[2];
acc = -(float)ps->gravity;
a = acc / 2.0f;
b = vel;
c = -dist;
den = b * b - 4.0f * a * c;
if ( den >= 0.0f && pm->waterlevel != 3 ) {
t = ( -b - (float)sqrt( den ) ) / ( 2.0f * a );
delta = vel + t * acc;
delta = delta * delta * 0.0001f;
// reduce falling damage if there is standing water
if ( pm->waterlevel == 2 ) {
delta *= 0.25f;
} else if ( pm->waterlevel == 1 ) {
delta *= 0.5f;
}
if ( delta >= 1.0f ) {
// landing events
if ( !( pml.groundTrace.surfaceFlags & SURF_NODAMAGE )
&& ps->stats[STAT_HEALTH] > -40 ) {
if ( delta > 60.0f ) {
BG_AddPredictableEventToPlayerstate( EV_FALL_FAR, 0, ps );
ps = pm->ps;
} else if ( delta > 40.0f ) {
if ( ps->stats[STAT_HEALTH] > 0 ) {
BG_AddPredictableEventToPlayerstate( EV_FALL_MEDIUM, 0, ps );
ps = pm->ps;
}
} else if ( delta > 7.0f ) {
BG_AddPredictableEventToPlayerstate( EV_FALL_SHORT, 0, ps );
ps = pm->ps;
} else {
event = 0;
if ( pm->noFootsteps == 0 ) {
event = PM_FootstepForSurface();
}
BG_AddPredictableEventToPlayerstate( event, 0, ps );
ps = pm->ps;
}
}
ps->bobCycle = 0;
ps = pm->ps;
// reset double jump
if ( ( ps->pm_flags & PMF_DOUBLE_JUMP ) != 0 ) {
ps->jumped = 0;
ps = pm->ps;
}
}
}
if ( pml.previous_velocity[2] < -200.0f ) {
ps->pm_flags |= PMF_TIME_KNOCKBACK;
pm->ps->pm_time = 250;
ps = pm->ps;
}
}
ps->groundEntityNum = tr.entityNum;
PM_AddTouchEnt( tr.entityNum );
}
/*
================
PM_UpdateViewAngles
This can be used as another entry point when only the viewangles
are being updated instead 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->pm_type != PM_FREEZE
&& 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 );
}
}
/*
================
PmoveSingle
QL version: massive inlined function that contains movement dispatch,
weapon handling, animation, footsteps, and water events all inline.
================
*/
void trap_SnapVector( float *v );
void PmoveSingle( pmove_t *pmove ) {
playerState_t *ps;
int pm_flags;
int oldTime;
int msec;
int buttons;
signed char fmove, rmove;
float speed, accel, wishspd, cmdScale;
float wishspeed;
vec3_t wishvel, wishdir;
int waterlvl;
qboolean playFootstep;
int i;
pm = pmove;
ps = pm->ps;
// this counter lets us debug movement problems with a journal
c_pmove++;
// set up physics overrides based on promode flag
PM_SetupPhysicsOverrides();
ps = pm->ps;
// clear results
pm->numtouch = 0;
pm->watertype = 0;
pm->waterlevel = 0;
if ( ps->stats[STAT_HEALTH] < 1 && ps->powerups[PW_NUM_POWERUPS - 1] == 0 ) {
pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies
}
if ( pmove_globalFlag == 1 ) {
pm->tracemask &= ~CONTENTS_BODY;
}
// make sure walking button is clear if they are running
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 ) {
ps->eFlags |= EF_TALK;
} else {
ps->eFlags &= ~EF_TALK;
}
// clear firing flag
ps->eFlags &= ~EF_FIRING;
// clear the respawned flag if attack and use are cleared
buttons = pm->cmd.buttons;
if ( pm->ps->stats[STAT_HEALTH] > 0 && ( buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) == 0 ) {
pm->ps->pm_flags &= ~PMF_RESPAWNED;
buttons = pm->cmd.buttons;
}
// if talk button is down, disallow all other input
if ( buttons & BUTTON_TALK ) {
pm->cmd.buttons = BUTTON_TALK;
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
}
// clear all pmove local vars
memset( &pml, 0, sizeof( pml ) );
// determine the time
pml.msec = pm->cmd.serverTime - pm->ps->commandTime;
if ( pml.msec < 1 ) {
pml.msec = 1;
} else if ( pml.msec > 200 ) {
pml.msec = 200;
}
pm->ps->commandTime = pm->cmd.serverTime;
ps = pm->ps;
// PMF_USE_ITEM_HELD freezes player (intermission)
if ( ( ps->pm_flags & PMF_USE_ITEM_HELD ) != 0 ) {
return;
}
// save old org in case we get stuck
VectorCopy( ps->origin, pml.previous_origin );
// save old velocity for crashlanding
VectorCopy( pm->ps->velocity, pml.previous_velocity );
pml.frametime = (float)( (double)pml.msec * 0.001 );
// update the viewangles
PM_UpdateViewAngles( pm->ps, &pm->cmd );
// sync cmd fields to playerstate
ps = pm->ps;
if ( pm->cmd.forwardmove != ps->forwardmove ) {
ps->forwardmove = pm->cmd.forwardmove;
}
if ( pm->cmd.rightmove != ps->rightmove ) {
ps->rightmove = pm->cmd.rightmove;
}
if ( pm->cmd.upmove != ps->upmove ) {
ps->upmove = pm->cmd.upmove;
}
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
fmove = pm->cmd.forwardmove;
if ( fmove < 0 ) {
pm->ps->pm_flags |= PMF_BACKWARDS_RUN;
} else if ( fmove != 0 || pm->cmd.rightmove != 0 ) {
pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN;
}
ps = pm->ps;
oldTime = ps->pm_type;
if ( oldTime > PM_SPECTATOR ) {
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
oldTime = ps->pm_type;
}
//
// Movement type dispatch
//
if ( oldTime == PM_SPECTATOR ) {
PM_CheckDuck();
PM_Friction();
PM_BuildWishVelocity( &pm->cmd, wishvel, wishdir );
PM_Accelerate( wishvel[0], pm_flyaccelerate, wishdir );
PM_StepSlideMove( qfalse );
PM_DropTimers();
goto done_movement;
}
if ( oldTime == PM_NOCLIP ) {
// noclip move
ps->viewheight = DEFAULT_VIEWHEIGHT;
speed = sqrt( ps->velocity[0] * ps->velocity[0]
+ ps->velocity[1] * ps->velocity[1]
+ ps->velocity[2] * ps->velocity[2] );
if ( speed < 1.0f ) {
VectorClear( ps->velocity );
} else {
wishspd = pm_stopspeed;
if ( pm_stopspeed <= speed ) {
wishspd = speed;
}
wishspd = speed - 10.0f * pml.frametime * wishspd; /* DAT_001a7208 = 10.0 */
if ( wishspd <= 0.0f ) {
wishspd = 0.0f;
}
wishspd /= speed;
ps->velocity[0] *= wishspd;
pm->ps->velocity[1] *= wishspd;
pm->ps->velocity[2] *= wishspd;
}
PM_BuildWishVelocity( &pm->cmd, wishvel, wishdir );
PM_Accelerate( wishvel[0], pm_walkAccel, wishdir );
ps = pm->ps;
ps->origin[0] += ps->velocity[0] * pml.frametime;
ps = pm->ps;
ps->origin[1] += ps->velocity[1] * pml.frametime;
ps = pm->ps;
ps->origin[2] += ps->velocity[2] * pml.frametime;
PM_DropTimers();
goto done_movement;
}
if ( oldTime == PM_FREEZE ) {
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
oldTime = ps->pm_type;
}
if ( oldTime == PM_INTERMISSION || oldTime == PM_SPINTERMISSION ) {
return; // no movement at all
}
// set watertype, and waterlevel
PM_SetWaterLevel();
pml.previous_waterlevel = pm->waterlevel;
// set mins, maxs, and viewheight
PM_CheckDuck();
// set groundentity
PM_GroundTrace();
// dead move
ps = pm->ps;
if ( ps->pm_type == PM_DEAD && pml.walking != 0 ) {
speed = sqrt( ps->velocity[0] * ps->velocity[0]
+ ps->velocity[1] * ps->velocity[1]
+ ps->velocity[2] * ps->velocity[2] ) - 20.0f; /* DAT_001a8970 = 20.0f */
if ( speed <= 0.0f ) {
ps->velocity[0] = 0.0f;
ps->velocity[1] = 0.0f;
ps->velocity[2] = 0.0f;
} else {
VectorNormalize( ps->velocity );
pm->ps->velocity[0] *= speed;
pm->ps->velocity[1] *= speed;
pm->ps->velocity[2] *= speed;
}
}
PM_DropTimers();
PM_CheckWallContact();
// clear grapple powerup counter
pm->ps->powerups[PW_NEUTRALFLAG] = 0; /* powerups[9] */
ps = pm->ps;
// invulnerability sphere
if ( ps->powerups[PW_INVULNERABILITY] != 0 ) {
pm->cmd.forwardmove = 0;
pm->cmd.rightmove = 0;
pm->cmd.upmove = 0;
ps->velocity[0] = 0.0f;
ps->velocity[1] = 0.0f;
ps->velocity[2] = 0.0f;
goto after_move;
}
// grapple active (non-pulling)
if ( ( ps->pm_flags & PMF_GRAPPLE_ACTIVE ) != 0 ) {
/* QL grapple move uses DAT_003cfe24 and grapple point */
/* This is a simplified reconstruction */
float grappleSpeed; /* DAT_003cfe24 */
grappleSpeed = pm_velocityGh; /* placeholder — actual cvar unknown */
if ( grappleSpeed != 0.0f ) {
vec3_t gdir;
float dist;
VectorScale( pml.forward, -16.0f, gdir ); /* DAT_001a89cc = -16.0f */
gdir[0] = gdir[0] + ps->grapplePoint[0] - ps->origin[0];
gdir[1] = gdir[1] + ps->grapplePoint[1] - ps->origin[1];
gdir[2] = gdir[2] + ps->grapplePoint[2] - ps->origin[2];
dist = VectorLength( gdir );
VectorNormalize( gdir );
gdir[0] *= grappleSpeed;
gdir[1] *= grappleSpeed;
gdir[2] *= grappleSpeed;
if ( (double)dist <= (double)grappleSpeed * 0.1 ) { /* _DAT_001a7230 = 0.1 */
float s = ( dist * 10.0f ) / grappleSpeed;
gdir[0] *= s;
gdir[1] *= s;
gdir[2] *= s;
}
if ( pm->field_0f8 == 0 ) {
pml.groundPlane = qfalse;
} else {
gdir[0] *= 2.0f;
gdir[1] *= 2.0f;
gdir[2] *= 2.0f;
}
if ( (int)pm->waterlevel > 1 ) {
float wscale = 0.75f; /* DAT_001a91b8 = 0.75f */
gdir[0] *= wscale;
gdir[1] *= wscale;
gdir[2] *= wscale;
}
pm->ps->velocity[0] = gdir[0];
pm->ps->velocity[1] = gdir[1];
pm->ps->velocity[2] = gdir[2];
}
PM_AirMove();
goto after_move;
}
// waterjump
if ( ( ps->pm_flags & PMF_TIME_WATERJUMP_INIT ) != 0 ) {
PM_WaterJumpMove();
goto after_move;
}
// swimming
if ( (int)pm->waterlevel > 1 ) {
if ( pml.wallContact == 0 ) {
PM_WaterMove();
goto after_move;
}
/* wall contact in water: fall through to walking/air */
}
// walking on ground
if ( pml.walking != 0 ) {
int grapple = PM_CheckGrapple();
if ( grapple != 0 ) {
PM_StepSlideMove( qfalse );
PM_GrappleMove();
goto after_move;
}
oldTime = PM_CheckJump( 0 );
if ( oldTime != 0 ) {
if ( (int)pm->waterlevel > 1 ) {
PM_WaterMove();
} else {
PM_AirMove();
}
goto after_move;
}
// ground walk
PM_Friction();
PM_SetMovementDir();
pml.forward[2] = 0.0f;
pml.right[2] = 0.0f;
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 );
fmove = pm->cmd.forwardmove;
rmove = pm->cmd.rightmove;
for ( i = 0; i < 3; i++ ) {
wishdir[i] = pml.forward[i] * (float)(int)fmove
+ pml.right[i] * (float)(int)rmove;
}
accel = VectorNormalize( wishdir );
wishspd = PM_CmdScaleValue( pm->cmd.forwardmove, pm->cmd.rightmove, pm->cmd.upmove );
wishspd *= accel;
pm_flags = pm->ps->pm_flags;
cmdScale = (float)pm->ps->speed;
// crouch slide speed scale
accel = 2.0f; /* DAT_001a7214 = 2.0f — but for duck scale it's probably 0.25f */
if ( ( pm_flags & PMF_CROUCH_SLIDE ) != 0 ) {
accel = 0.75f; /* DAT_001a91b8 = 0.75f */
}
accel *= cmdScale;
if ( accel < wishspd && ( pm_flags & PMF_DUCKED ) != 0 ) {
wishspd = accel;
}
// water scaling
waterlvl = pm->waterlevel;
if ( waterlvl != 0 ) {
float wscale;
wscale = pm_waterSwimScale;
if ( waterlvl == 1 ) {
wscale = pm_waterWadeScale;
}
cmdScale = (float)( 1.0 - ( 1.0 - (double)wscale ) * ( (double)waterlvl / 3.0 ) )
* cmdScale;
if ( cmdScale <= wishspd ) {
wishspd = cmdScale;
}
}
// acceleration
speed = pm_walkAccel;
if ( pml.groundTrace.surfaceFlags & SURF_SLICK ) {
speed = pm_airaccelerate; /* DAT_001ab5d8 for slick+ducked */
if ( ( pm_flags & PMF_DUCKED ) == 0 ) {
speed = 1.0f;
}
} else if ( ( pm_flags & PMF_TIME_WATERJUMP ) != 0 ) {
speed = 1.0f;
}
PM_Accelerate( wishspd, speed, wishdir );
ps = pm->ps;
if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK )
|| ( ps->pm_flags & PMF_TIME_WATERJUMP ) ) {
ps->velocity[2] -= (float)ps->gravity * pml.frametime;
ps = pm->ps;
}
PM_ClipVelocity( ps->velocity, pml.groundTrace.plane.normal,
ps->velocity, OVERCLIP );
if ( pm->ps->velocity[0] != 0.0f || pm->ps->velocity[1] != 0.0f ) {
PM_StepSlideMove( qfalse );
}
goto after_move;
}
// airborne
PM_AirMove();
after_move:
// grapple stat tracking
ps = pm->ps;
if ( ps->stats[12] != 0 && ( ps->pm_flags & PMF_GRAPPLE_PULL ) == 0 ) {
int val = (int)( (float)ps->stats[12] * pml.frametime ) + ps->stats[11];
if ( ps->stats[10] < val ) {
val = ps->stats[10];
}
ps->stats[11] = val;
}
//
// Animate (gesture buttons)
//
buttons = pm->cmd.buttons;
ps = pm->ps;
if ( buttons & BUTTON_GESTURE ) {
if ( ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_GESTURE );
ps->torsoTimer = TIMER_GESTURE;
BG_AddPredictableEventToPlayerstate( EV_TAUNT, 0, pm->ps );
}
} else if ( buttons & 0x80 ) {
if ( ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_GUARDBASE );
ps->torsoTimer = 600;
}
} else if ( buttons & 0x100 ) {
if ( ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_PATROL );
ps->torsoTimer = 600;
}
} else if ( buttons & 0x200 ) {
if ( ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_FOLLOWME );
ps->torsoTimer = 600;
}
} else if ( buttons & 0x400 ) {
if ( ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_AFFIRMATIVE );
ps->torsoTimer = 600;
}
} else if ( buttons & 0x20 ) {
if ( ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_NEGATIVE );
ps->torsoTimer = 600;
}
} else if ( buttons & 0x40 ) {
if ( ps->torsoTimer == 0 ) {
PM_StartTorsoAnim( TORSO_GETFLAG );
ps->torsoTimer = 600;
}
}
//
// Second ground trace + water level
//
PM_GroundTrace();
PM_SetWaterLevel();
//
// Weapon handling
//
ps = pm->ps;
pm_flags = ps->pm_flags;
if ( pm_flags & PMF_RESPAWNED ) {
goto after_weapon;
}
if ( ps->stats[STAT_HEALTH] < 1 ) {
ps->weapon = WP_NONE;
goto torso_anim;
}
// set firing flag
buttons = pm->cmd.buttons;
if ( ( buttons & BUTTON_ATTACK ) != 0
&& ( pm_flags & PMF_ATTACK_HELD ) == 0
&& ( ps->ammo[ ps->weapon ] != 0 || ps->weapon == WP_GRAPPLING_HOOK ) ) {
ps->eFlags |= EF_FIRING;
ps = pm->ps;
buttons = pm->cmd.buttons;
pm_flags = ps->pm_flags;
}
// holdable item use
if ( ( buttons & BUTTON_USE_HOLDABLE ) == 0 ) {
ps->pm_flags &= ~PMF_GRAPPLE_PULL;
ps = pm->ps;
} else if ( ( pm_flags & PMF_GRAPPLE_PULL ) == 0 ) {
/* QL holdable item use logic */
ps->pm_flags |= PMF_GRAPPLE_PULL;
/* simplified: consume item */
BG_AddPredictableEventToPlayerstate(
EV_USE_ITEM0 + bg_itemlist[ ps->stats[STAT_HOLDABLE_ITEM] ].giTag, 0, pm->ps );
pm->ps->stats[STAT_HOLDABLE_ITEM] = 0;
ps = pm->ps;
goto torso_anim;
}
// weapon time countdown
if ( ps->weaponTime > 0 ) {
ps->weaponTime -= pml.msec;
ps = pm->ps;
if ( ps->weaponTime > 0 && ps->weaponstate == WEAPON_FIRING ) {
goto weapon_change_check;
}
}
weapon_change_check:
// check for weapon change
{
int cmdWeapon = pm->cmd.weapon;
if ( ps->weapon != cmdWeapon
&& cmdWeapon >= 1 && cmdWeapon <= WP_NUM_WEAPONS
&& ( ps->stats[STAT_WEAPONS] & ( 1 << cmdWeapon ) ) ) {
if ( ps->weaponstate == WEAPON_DROPPING ) {
// already dropping
} else {
BG_AddPredictableEventToPlayerstate( EV_CHANGE_WEAPON, 0, ps );
pm->ps->weaponstate = WEAPON_DROPPING;
pm->ps->weaponTime += pm_weaponDropTime;
ps = pm->ps;
PM_StartTorsoAnim( TORSO_DROP );
ps = pm->ps;
}
}
}
if ( ps->weaponTime > 0 ) {
goto torso_anim;
}
// finish weapon change
if ( ps->weaponstate == WEAPON_DROPPING ) {
int weapon = pm->cmd.weapon;
if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) {
weapon = WP_NONE;
}
if ( !( ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
weapon = WP_NONE;
}
ps->weapon = weapon;
pm->ps->weaponstate = WEAPON_RAISING;
pm->ps->weaponTime += pm_weaponRaiseTime;
pm->ps->stats[8] = 0;
ps = pm->ps;
PM_StartTorsoAnim( TORSO_RAISE );
ps = pm->ps;
goto torso_anim;
}
if ( ps->weaponstate == WEAPON_RAISING ) {
ps->weaponstate = WEAPON_READY;
ps = pm->ps;
goto torso_anim;
}
// check for fire
if ( ps->weapon == WP_GRAPPLING_HOOK ) {
/* grapple weapon has special handling */
goto torso_anim;
}
if ( !( pm->cmd.buttons & BUTTON_ATTACK )
|| ( ps->pm_flags & PMF_ATTACK_HELD ) ) {
ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
ps = pm->ps;
goto torso_anim;
}
// grapple released blocks fire
if ( ( ps->pm_flags & PMF_GRAPPLE_RELEASED ) != 0 ) {
ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
pm->ps->eFlags &= ~EF_FIRING;
pm->ps->pm_flags &= ~PMF_GRAPPLE_RELEASED;
ps = pm->ps;
goto torso_anim;
}
// gauntlet special
if ( ps->weapon == WP_GAUNTLET ) {
if ( pm->gauntletHit == 0 ) {
ps->weaponTime = 0;
pm->ps->weaponstate = WEAPON_READY;
ps = pm->ps;
goto torso_anim;
}
PM_StartTorsoAnim( TORSO_ATTACK2 );
ps = pm->ps;
} else {
PM_StartTorsoAnim( TORSO_ATTACK );
ps = pm->ps;
}
ps->weaponstate = WEAPON_FIRING;
ps = pm->ps;
// check for out of ammo
if ( ps->ammo[ ps->weapon ] == 0 ) {
BG_AddPredictableEventToPlayerstate( EV_NOAMMO, 0, ps );
pm->ps->weaponTime += 500;
ps = pm->ps;
goto torso_anim;
}
// take an ammo away if not infinite
if ( ps->ammo[ ps->weapon ] != -1 ) {
ps->ammo[ ps->weapon ]--;
ps = pm->ps;
}
// fire weapon
BG_AddPredictableEventToPlayerstate( EV_FIRE_WEAPON, 0, ps );
ps = pm->ps;
{
int addTime;
/* QL uses a table lookup for weapon fire times */
/* Simplified inline version */
switch ( ps->weapon ) {
default:
case WP_GAUNTLET: addTime = 400; break;
case WP_LIGHTNING: addTime = 50; break;
case WP_SHOTGUN: addTime = 1000; break;
case WP_MACHINEGUN: addTime = 100; break;
case WP_GRENADE_LAUNCHER: addTime = 800; break;
case WP_ROCKET_LAUNCHER: addTime = 800; break;
case WP_PLASMAGUN: addTime = 100; break;
case WP_RAILGUN: addTime = 1500; break;
case WP_BFG: addTime = 200; break;
case WP_GRAPPLING_HOOK: addTime = 400; break;
}
// haste powerup
if ( ps->powerups[PW_HASTE] ) {
addTime = (int)( (double)addTime * 0.75 );
}
ps->weaponTime += addTime;
}
ps = pm->ps;
torso_anim:
// torso animation
if ( ps->weaponstate == WEAPON_READY ) {
int anim;
if ( ps->weapon == WP_GRAPPLING_HOOK || ps->weapon == WP_GAUNTLET ) {
anim = TORSO_STAND2;
} else {
anim = TORSO_STAND;
}
PM_ContinueTorsoAnim( anim );
}
after_weapon:
//
// Footsteps / legs animations
//
ps = pm->ps;
pm->xyspeed = sqrt( ps->velocity[0] * ps->velocity[0]
+ ps->velocity[1] * ps->velocity[1] );
speed = pm->xyspeed;
if ( ps->groundEntityNum == ENTITYNUM_NONE ) {
if ( ps->powerups[PW_INVULNERABILITY] != 0 ) {
PM_ContinueLegsAnim( LEGS_IDLECR );
}
waterlvl = pm->waterlevel;
if ( waterlvl > 1 ) {
PM_ContinueLegsAnim( LEGS_SWIM );
waterlvl = pm->waterlevel;
}
ps = pm->ps;
goto water_events;
}
// on ground
if ( pm->cmd.forwardmove == 0 ) {
pm_flags = ps->pm_flags;
if ( pm->cmd.rightmove != 0
|| ( ( pm_flags & PMF_CROUCH_SLIDE ) != 0
&& pm->cmd.upmove < 0
&& ps->crouchSlideTimer > 0 ) ) {
// moving sideways or crouch sliding
if ( pm_flags & PMF_DUCKED ) {
goto ducked_move;
}
goto running_move;
}
// not moving
if ( speed < 5.0f ) {
ps->bobCycle = 0;
if ( pm->ps->pm_flags & PMF_DUCKED ) {
PM_ContinueLegsAnim( LEGS_IDLECR );
} else {
PM_ContinueLegsAnim( LEGS_IDLE );
}
waterlvl = pm->waterlevel;
ps = pm->ps;
goto water_events;
}
waterlvl = pm->waterlevel;
goto water_events;
}
pm_flags = ps->pm_flags;
if ( pm_flags & PMF_DUCKED ) {
ducked_move:
playFootstep = qfalse;
if ( pm_flags & PMF_BACKWARDS_RUN ) {
PM_ContinueLegsAnim( LEGS_BACKCR );
} else {
PM_ContinueLegsAnim( LEGS_WALKCR );
}
speed = 0.5f;
goto bob_cycle;
}
running_move:
if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) {
if ( ps->velocity[0] >= pm_airControl || ps->velocity[0] <= -pm_airControl
|| ps->velocity[1] >= pm_airControl || ps->velocity[1] <= -pm_airControl ) {
playFootstep = ( ps->stats[STAT_WEAPONS] != 1 );
if ( pm_flags & PMF_BACKWARDS_RUN ) {
PM_ContinueLegsAnim( LEGS_BACK );
} else {
PM_ContinueLegsAnim( LEGS_RUN );
}
speed = 0.4f;
} else {
playFootstep = qfalse;
if ( pm_flags & PMF_BACKWARDS_RUN ) {
PM_ContinueLegsAnim( LEGS_BACKWALK );
} else {
PM_ContinueLegsAnim( LEGS_WALK );
}
speed = 0.3f;
}
} else {
playFootstep = qfalse;
if ( pm_flags & PMF_BACKWARDS_RUN ) {
PM_ContinueLegsAnim( LEGS_BACKWALK );
} else {
PM_ContinueLegsAnim( LEGS_WALK );
}
speed = 0.3f;
}
bob_cycle:
{
int old = pm->ps->bobCycle;
pm->ps->bobCycle = (int)( (float)old + (float)pml.msec * speed ) & 255;
ps = pm->ps;
if ( ( ( ps->bobCycle + 64 ) ^ ( old + 64 ) ) & 128 ) {
waterlvl = pm->waterlevel;
if ( waterlvl == 0 ) {
if ( playFootstep ) {
int evt = 0;
if ( pm->noFootsteps == 0 ) {
evt = PM_FootstepForSurface();
}
BG_AddPredictableEventToPlayerstate( evt, 0, ps );
waterlvl = pm->waterlevel;
ps = pm->ps;
}
goto water_events;
} else if ( waterlvl == 1 ) {
BG_AddPredictableEventToPlayerstate( EV_FOOTSPLASH, 0, ps );
waterlvl = pm->waterlevel;
ps = pm->ps;
goto water_events;
} else if ( waterlvl == 2 ) {
BG_AddPredictableEventToPlayerstate( EV_SWIM, 0, ps );
waterlvl = pm->waterlevel;
ps = pm->ps;
goto water_events;
}
// waterlevel 3: no sound
waterlvl = pm->waterlevel;
ps = pm->ps;
} else {
waterlvl = pm->waterlevel;
}
}
water_events:
//
// Water events
//
if ( pml.previous_waterlevel == 0 && waterlvl != 0 ) {
BG_AddPredictableEventToPlayerstate( EV_WATER_TOUCH, 0, ps );
ps = pm->ps;
waterlvl = pm->waterlevel;
}
if ( pml.previous_waterlevel != 0 && waterlvl == 0 ) {
BG_AddPredictableEventToPlayerstate( EV_WATER_LEAVE, 0, ps );
ps = pm->ps;
}
// head underwater events
if ( ps->pm_type != PM_DEAD ) {
if ( pml.previous_waterlevel != 3 && pm->waterlevel == 3 ) {
BG_AddPredictableEventToPlayerstate( EV_WATER_UNDER, 0, ps );
ps = pm->ps;
}
if ( ps->powerups[PW_FLIGHT] == 0
&& pml.previous_waterlevel == 3 && pm->waterlevel != 3 ) {
BG_AddPredictableEventToPlayerstate( EV_WATER_CLEAR, 0, ps );
}
}
done_movement:
// snap velocity
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;
}
}
}