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>
2522 lines
57 KiB
C
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;
|
|
}
|
|
}
|
|
}
|