diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 4dd5c9d..23b8026 100644 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1156,6 +1156,40 @@ extern vmCvar_t cg_smoothClients; extern vmCvar_t pmove_fixed; extern vmCvar_t pmove_msec; //extern vmCvar_t cg_pmove_fixed; +extern vmCvar_t pmove_JumpVelocity; +extern vmCvar_t pmove_JumpVelocityMax; +extern vmCvar_t pmove_JumpVelocityScaleAdd; +extern vmCvar_t pmove_JumpVelocityTimeThreshold; +extern vmCvar_t pmove_JumpVelocityTimeThresholdOffset; +extern vmCvar_t pmove_JumpTimeDeltaMin; +extern vmCvar_t pmove_ChainJump; +extern vmCvar_t pmove_ChainJumpVelocity; +extern vmCvar_t pmove_StepJumpVelocity; +extern vmCvar_t pmove_RampJumpScale; +extern vmCvar_t pmove_StepHeight; +extern vmCvar_t pmove_WalkAccel; +extern vmCvar_t pmove_WalkFriction; +extern vmCvar_t pmove_StrafeAccel; +extern vmCvar_t pmove_CircleStrafeFriction; +extern vmCvar_t pmove_CrouchSlideFriction; +extern vmCvar_t pmove_CrouchSlideTime; +extern vmCvar_t pmove_WaterSwimScale; +extern vmCvar_t pmove_WaterWadeScale; +extern vmCvar_t pmove_WeaponDropTime; +extern vmCvar_t pmove_WeaponRaiseTime; +extern vmCvar_t pmove_WishSpeed; +extern vmCvar_t pmove_AirSteps; +extern vmCvar_t pmove_AirAccel; +extern vmCvar_t pmove_AirStopAccel; +extern vmCvar_t pmove_AirControl; +extern vmCvar_t pmove_AutoHop; +extern vmCvar_t pmove_BunnyHop; +extern vmCvar_t pmove_StepJump; +extern vmCvar_t pmove_CrouchStepJump; +extern vmCvar_t pmove_RampJump; +extern vmCvar_t pmove_DoubleJump; +extern vmCvar_t pmove_CrouchSlide; +extern vmCvar_t pmove_noPlayerClip; extern vmCvar_t cg_cameraOrbit; extern vmCvar_t cg_cameraOrbitDelay; extern vmCvar_t cg_timescaleFadeEnd; diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c index aa9ec27..08d01cf 100644 --- a/code/cgame/cg_main.c +++ b/code/cgame/cg_main.c @@ -168,6 +168,42 @@ vmCvar_t pmove_fixed; //vmCvar_t cg_pmove_fixed; vmCvar_t pmove_msec; vmCvar_t cg_pmove_msec; + +// QL pmove cvars (synced from server) +vmCvar_t pmove_JumpVelocity; +vmCvar_t pmove_JumpVelocityMax; +vmCvar_t pmove_JumpVelocityScaleAdd; +vmCvar_t pmove_JumpVelocityTimeThreshold; +vmCvar_t pmove_JumpVelocityTimeThresholdOffset; +vmCvar_t pmove_JumpTimeDeltaMin; +vmCvar_t pmove_ChainJump; +vmCvar_t pmove_ChainJumpVelocity; +vmCvar_t pmove_StepJumpVelocity; +vmCvar_t pmove_RampJumpScale; +vmCvar_t pmove_StepHeight; +vmCvar_t pmove_WalkAccel; +vmCvar_t pmove_WalkFriction; +vmCvar_t pmove_StrafeAccel; +vmCvar_t pmove_CircleStrafeFriction; +vmCvar_t pmove_CrouchSlideFriction; +vmCvar_t pmove_CrouchSlideTime; +vmCvar_t pmove_WaterSwimScale; +vmCvar_t pmove_WaterWadeScale; +vmCvar_t pmove_WeaponDropTime; +vmCvar_t pmove_WeaponRaiseTime; +vmCvar_t pmove_WishSpeed; +vmCvar_t pmove_AirSteps; +vmCvar_t pmove_AirAccel; +vmCvar_t pmove_AirStopAccel; +vmCvar_t pmove_AirControl; +vmCvar_t pmove_AutoHop; +vmCvar_t pmove_BunnyHop; +vmCvar_t pmove_StepJump; +vmCvar_t pmove_CrouchStepJump; +vmCvar_t pmove_RampJump; +vmCvar_t pmove_DoubleJump; +vmCvar_t pmove_CrouchSlide; +vmCvar_t pmove_noPlayerClip; vmCvar_t cg_cameraMode; vmCvar_t cg_cameraOrbit; vmCvar_t cg_cameraOrbitDelay; @@ -307,6 +343,41 @@ static cvarTable_t cvarTable[] = { // bk001129 { &pmove_fixed, "pmove_fixed", "0", 0}, { &pmove_msec, "pmove_msec", "8", 0}, + // QL pmove cvars + { &pmove_JumpVelocity, "pmove_JumpVelocity", "270", 0 }, + { &pmove_JumpVelocityMax, "pmove_JumpVelocityMax", "700", 0 }, + { &pmove_JumpVelocityScaleAdd, "pmove_JumpVelocityScaleAdd", "0", 0 }, + { &pmove_JumpVelocityTimeThreshold, "pmove_JumpVelocityTimeThreshold", "500", 0 }, + { &pmove_JumpVelocityTimeThresholdOffset, "pmove_JumpVelocityTimeThresholdOffset", "0.5", 0 }, + { &pmove_JumpTimeDeltaMin, "pmove_JumpTimeDeltaMin", "100", 0 }, + { &pmove_ChainJump, "pmove_ChainJump", "1", 0 }, + { &pmove_ChainJumpVelocity, "pmove_ChainJumpVelocity", "110", 0 }, + { &pmove_StepJumpVelocity, "pmove_StepJumpVelocity", "48", 0 }, + { &pmove_RampJumpScale, "pmove_RampJumpScale", "1", 0 }, + { &pmove_StepHeight, "pmove_StepHeight", "22", 0 }, + { &pmove_WalkAccel, "pmove_WalkAccel", "10", 0 }, + { &pmove_WalkFriction, "pmove_WalkFriction", "6", 0 }, + { &pmove_StrafeAccel, "pmove_StrafeAccel", "1", 0 }, + { &pmove_CircleStrafeFriction, "pmove_CircleStrafeFriction", "6", 0 }, + { &pmove_CrouchSlideFriction, "pmove_CrouchSlideFriction", "0.5", 0 }, + { &pmove_CrouchSlideTime, "pmove_CrouchSlideTime", "2", 0 }, + { &pmove_WaterSwimScale, "pmove_WaterSwimScale", "0.5", 0 }, + { &pmove_WaterWadeScale, "pmove_WaterWadeScale", "0.7", 0 }, + { &pmove_WeaponDropTime, "pmove_WeaponDropTime", "200", 0 }, + { &pmove_WeaponRaiseTime, "pmove_WeaponRaiseTime", "250", 0 }, + { &pmove_WishSpeed, "pmove_WishSpeed", "400", 0 }, + { &pmove_AirSteps, "pmove_AirSteps", "1", 0 }, + { &pmove_AirAccel, "pmove_AirAccel", "1", 0 }, + { &pmove_AirStopAccel, "pmove_AirStopAccel", "1", 0 }, + { &pmove_AirControl, "pmove_AirControl", "0", 0 }, + { &pmove_AutoHop, "pmove_AutoHop", "1", 0 }, + { &pmove_BunnyHop, "pmove_BunnyHop", "1", 0 }, + { &pmove_StepJump, "pmove_StepJump", "1", 0 }, + { &pmove_CrouchStepJump, "pmove_CrouchStepJump", "1", 0 }, + { &pmove_RampJump, "pmove_RampJump", "0", 0 }, + { &pmove_DoubleJump, "pmove_DoubleJump", "1", 0 }, + { &pmove_CrouchSlide, "pmove_CrouchSlide", "0", 0 }, + { &pmove_noPlayerClip, "pmove_noPlayerClip", "0", 0 }, { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index fec2cfb..17e9a39 100644 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // It also handles local physics interaction, like fragments bouncing off walls #include "cg_local.h" +#include "../game/bg_local.h" static pmove_t cg_pmove; @@ -499,6 +500,42 @@ void CG_PredictPlayerState( void ) { cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; cg_pmove.pmove_msec = pmove_msec.integer; + // sync QL pmove globals for client prediction + pm_jumpVelocity = pmove_JumpVelocity.value; + pm_jumpVelocityMax = pmove_JumpVelocityMax.value; + pm_jumpVelocityScaleAdd = pmove_JumpVelocityScaleAdd.value; + pm_jumpVelocityTimeThreshold = pmove_JumpVelocityTimeThreshold.value; + pm_jumpVelocityTimeThresholdOffset = pmove_JumpVelocityTimeThresholdOffset.value; + pm_jumpTimeDeltaMin = pmove_JumpTimeDeltaMin.value; + pm_chainJump = pmove_ChainJump.integer; + pm_chainJumpVelocity = pmove_ChainJumpVelocity.value; + pm_stepJumpVelocity = pmove_StepJumpVelocity.value; + pm_rampJumpScale = pmove_RampJumpScale.value; + pm_stepHeight = pmove_StepHeight.value; + pm_walkAccel = pmove_WalkAccel.value; + pm_walkFriction = pmove_WalkFriction.value; + pm_strafeAccel = pmove_StrafeAccel.value; + pm_circleStrafeFriction = pmove_CircleStrafeFriction.value; + pm_crouchSlideFriction = pmove_CrouchSlideFriction.value; + pm_crouchSlideTime = pmove_CrouchSlideTime.integer; + pm_waterSwimScale = pmove_WaterSwimScale.value; + pm_waterWadeScale = pmove_WaterWadeScale.value; + pm_weaponDropTime = pmove_WeaponDropTime.integer; + pm_weaponRaiseTime = pmove_WeaponRaiseTime.integer; + pm_wishSpeed = pmove_WishSpeed.value; + pm_airSteps = pmove_AirSteps.integer; + pm_airAccel = pmove_AirAccel.value; + pm_airStopAccel = pmove_AirStopAccel.value; + pm_airControl = pmove_AirControl.value; + pm_autoHopEnabled = pmove_AutoHop.integer; + pm_bunnyHop = pmove_BunnyHop.integer; + pm_stepJumpEnabled = pmove_StepJump.integer; + pm_crouchStepJump = pmove_CrouchStepJump.integer; + pm_rampJumpEnabled = pmove_RampJump.integer; + pm_noPlayerClip = pmove_noPlayerClip.integer; + pmove_rampJumpFlag = pmove_RampJump.integer; + pmove_stepJumpFlag = pmove_StepJump.integer; + // run cmds moved = qfalse; for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { diff --git a/code/game/bg_local.h b/code/game/bg_local.h index 223c688..3708795 100644 --- a/code/game/bg_local.h +++ b/code/game/bg_local.h @@ -1,83 +1,159 @@ -/* -=========================================================================== -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_local.h -- local definitions for the bg (both games) files - -#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes - -#define STEPSIZE 18 - -#define JUMP_VELOCITY 270 - -#define TIMER_LAND 130 -#define TIMER_GESTURE (34*66+50) - -#define OVERCLIP 1.001f - -// all of the locals will be zeroed before each -// pmove, just to make damn sure we don't have -// any differences when running on client or server -typedef struct { - vec3_t forward, right, up; - float frametime; - - int msec; - - qboolean walking; - qboolean groundPlane; - trace_t groundTrace; - - float impactSpeed; - - vec3_t previous_origin; - vec3_t previous_velocity; - int previous_waterlevel; -} pml_t; - -extern pmove_t *pm; -extern pml_t pml; - -// movement parameters -extern float pm_stopspeed; -extern float pm_duckScale; -extern float pm_swimScale; -extern float pm_wadeScale; - -extern float pm_accelerate; -extern float pm_airaccelerate; -extern float pm_wateraccelerate; -extern float pm_flyaccelerate; - -extern float pm_friction; -extern float pm_waterfriction; -extern float pm_flightfriction; - -extern int c_pmove; - -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); -void PM_AddTouchEnt( int entityNum ); -void PM_AddEvent( int newEvent ); - -qboolean PM_SlideMove( qboolean gravity ); -void PM_StepSlideMove( qboolean gravity ); - - +/* +=========================================================================== +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_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes + +#define STEPSIZE 18 + +#define JUMP_VELOCITY 270 + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) + +#define OVERCLIP 1.001f + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server +typedef struct { + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + // QL additions between groundTrace and impactSpeed + int isJumppad; // set during crouch step jump + int wallContact; // PM_CheckWallContact result + int isStepJump; // set during normal step jump + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +// movement parameters +extern float pm_stopspeed; +extern float pm_duckScale; +extern float pm_swimScale; +extern float pm_wadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_flightfriction; +extern float pm_spectatorfriction; + +extern int c_pmove; + +// QL cached cvar globals +extern float pm_jumpVelocity; +extern float pm_jumpVelocityMax; +extern float pm_jumpVelocityScaleAdd; +extern float pm_jumpVelocityTimeThreshold; +extern float pm_jumpVelocityTimeThresholdOffset; +extern float pm_jumpTimeDeltaMin; +extern int pm_chainJump; +extern float pm_chainJumpVelocity; +extern float pm_stepJumpVelocity; +extern float pm_rampJumpScale; +extern float pm_stepHeight; +extern float pm_walkAccel; +extern float pm_walkFriction; +extern float pm_strafeAccel; +extern float pm_circleStrafeFriction; +extern float pm_crouchSlideFriction; +extern int pm_crouchSlideTime; +extern float pm_waterSwimScale; +extern float pm_waterWadeScale; +extern int pm_weaponDropTime; +extern int pm_weaponRaiseTime; +extern float pm_wishSpeed; +extern int pm_airSteps; +extern float pm_airStepFriction; +extern float pm_airAccel; +extern float pm_airStopAccel; +extern float pm_airControl; +extern int pm_autoHopEnabled; +extern int pm_bunnyHop; +extern int pm_stepJumpEnabled; +extern int pm_crouchStepJump; +extern int pm_rampJumpEnabled; +extern int pm_noPlayerClip; +extern float pm_velocityGh; + +// QL extern flags +extern int pmove_globalFlag; +extern int pmove_rampJumpFlag; +extern int pmove_stepJumpFlag; +extern float _DAT_003cfe90; +extern int DAT_003cfe30; + +// pmove functions +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + +// QL new functions +void PM_SetupPhysicsOverrides( void ); +void PM_CheckWallContact( void ); +void PM_CheckDuck( void ); +void PM_DropTimers( void ); +void PM_SetWaterLevel( void ); +void PM_SetMovementDir( void ); +void PM_Friction( void ); +void PM_Accelerate( float wishspeed, float accel, vec3_t wishdir ); +void PM_CmdScale( usercmd_t *cmd, float *scale ); +int PM_BuildWishVelocity( usercmd_t *cmd, float *wishspeed, vec3_t wishdir ); +void PM_Jump( void ); +int PM_CheckJump( int checkDoubleJump ); +int PM_CheckGrapple( void ); +void PM_GrappleMove( void ); +void PM_WaterJumpMove( void ); +void PM_WaterMove( void ); +void PM_AirMove( void ); +void PM_AirControl( void ); +void PM_FlyMove( void ); +void PM_GroundTrace( void ); +void PM_CrashLand( void ); +int PM_FootstepForSurface( void ); +void PM_ContinueLegsAnim( int anim ); +void PM_ForceLegsAnim( int anim ); +int PM_CanJump( void ); +int PM_CanEdgeGrab( void ); diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index 3711869..50a4b21 100644 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -1,2069 +1,2522 @@ -/* -=========================================================================== -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 - -#include "q_shared.h" -#include "bg_public.h" -#include "bg_local.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; - - -/* -=============== -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; -} -static void PM_StartLegsAnim( int anim ) { - if ( pm->ps->pm_type >= PM_DEAD ) { - return; - } - if ( pm->ps->legsTimer > 0 ) { - return; // a high priority animation is running - } - pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) - | anim; -} - -static void PM_ContinueLegsAnim( int anim ) { - if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) { - return; - } - if ( pm->ps->legsTimer > 0 ) { - return; // a high priority animation is running - } - PM_StartLegsAnim( anim ); -} - -static void PM_ContinueTorsoAnim( int anim ) { - if ( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) { - return; - } - if ( pm->ps->torsoTimer > 0 ) { - return; // a high priority animation is running - } - PM_StartTorsoAnim( anim ); -} - -static void PM_ForceLegsAnim( int anim ) { - pm->ps->legsTimer = 0; - PM_StartLegsAnim( anim ); -} - - -/* -================== -PM_ClipVelocity - -Slide off of the impacting surface -================== -*/ -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { - float backoff; - float change; - int i; - - backoff = DotProduct (in, normal); - - if ( backoff < 0 ) { - backoff *= overbounce; - } else { - backoff /= overbounce; - } - - for ( i=0 ; i<3 ; i++ ) { - change = normal[i]*backoff; - out[i] = in[i] - change; - } -} - - -/* -================== -PM_Friction - -Handles both ground friction and water friction -================== -*/ -static void PM_Friction( void ) { - vec3_t vec; - float *vel; - float speed, newspeed, control; - float drop; - - vel = pm->ps->velocity; - - VectorCopy( vel, vec ); - if ( pml.walking ) { - vec[2] = 0; // ignore slope movement - } - - speed = VectorLength(vec); - if (speed < 1) { - vel[0] = 0; - vel[1] = 0; // allow sinking underwater - // FIXME: still have z friction underwater? - return; - } - - drop = 0; - - // apply ground friction - if ( pm->waterlevel <= 1 ) { - if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) { - // if getting knocked back, no friction - if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { - control = speed < pm_stopspeed ? pm_stopspeed : speed; - drop += control*pm_friction*pml.frametime; - } - } - } - - // apply water friction even if just wading - if ( pm->waterlevel ) { - drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; - } - - // apply flying friction - if ( pm->ps->powerups[PW_FLIGHT]) { - drop += speed*pm_flightfriction*pml.frametime; - } - - if ( pm->ps->pm_type == PM_SPECTATOR) { - drop += speed*pm_spectatorfriction*pml.frametime; - } - - // scale the velocity - newspeed = speed - drop; - if (newspeed < 0) { - newspeed = 0; - } - newspeed /= speed; - - vel[0] = vel[0] * newspeed; - vel[1] = vel[1] * newspeed; - vel[2] = vel[2] * newspeed; -} - - -/* -============== -PM_Accelerate - -Handles user intended acceleration -============== -*/ -static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { -#if 1 - // q2 style - int i; - float addspeed, accelspeed, currentspeed; - - currentspeed = DotProduct (pm->ps->velocity, wishdir); - addspeed = wishspeed - currentspeed; - if (addspeed <= 0) { - return; - } - accelspeed = accel*pml.frametime*wishspeed; - if (accelspeed > addspeed) { - accelspeed = addspeed; - } - - for (i=0 ; i<3 ; i++) { - pm->ps->velocity[i] += accelspeed*wishdir[i]; - } -#else - // proper way (avoids strafe jump maxspeed bug), but feels bad - vec3_t wishVelocity; - vec3_t pushDir; - float pushLen; - float canPush; - - VectorScale( wishdir, wishspeed, wishVelocity ); - VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); - pushLen = VectorNormalize( pushDir ); - - canPush = accel*pml.frametime*wishspeed; - if (canPush > pushLen) { - canPush = pushLen; - } - - VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); -#endif -} - - - -/* -============ -PM_CmdScale - -Returns the scale factor to apply to cmd movements -This allows the clients to use axial -127 to 127 values for all directions -without getting a sqrt(2) distortion in speed. -============ -*/ -static float PM_CmdScale( usercmd_t *cmd ) { - int max; - float total; - float scale; - - max = abs( cmd->forwardmove ); - if ( abs( cmd->rightmove ) > max ) { - max = abs( cmd->rightmove ); - } - if ( abs( cmd->upmove ) > max ) { - max = abs( cmd->upmove ); - } - if ( !max ) { - return 0; - } - - total = sqrt( cmd->forwardmove * cmd->forwardmove - + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); - scale = (float)pm->ps->speed * max / ( 127.0 * total ); - - return scale; -} - - -/* -================ -PM_SetMovementDir - -Determine the rotation of the legs reletive -to the facing dir -================ -*/ -static void PM_SetMovementDir( void ) { - if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { - if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 0; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 1; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { - pm->ps->movementDir = 2; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 3; - } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 4; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 5; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { - pm->ps->movementDir = 6; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 7; - } - } else { - // if they aren't actively going directly sideways, - // change the animation to the diagonal so they - // don't stop too crooked - if ( pm->ps->movementDir == 2 ) { - pm->ps->movementDir = 1; - } else if ( pm->ps->movementDir == 6 ) { - pm->ps->movementDir = 7; - } - } -} - - -/* -============= -PM_CheckJump -============= -*/ -static qboolean PM_CheckJump( void ) { - if ( pm->ps->pm_flags & PMF_RESPAWNED ) { - return qfalse; // don't allow jump until all buttons are up - } - - if ( pm->cmd.upmove < 10 ) { - // not holding jump - return qfalse; - } - - // must wait for jump to be released - if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { - // clear upmove so cmdscale doesn't lower running speed - pm->cmd.upmove = 0; - return qfalse; - } - - pml.groundPlane = qfalse; // jumping away - pml.walking = qfalse; - pm->ps->pm_flags |= PMF_JUMP_HELD; - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pm->ps->velocity[2] = JUMP_VELOCITY; - PM_AddEvent( EV_JUMP ); - - 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; - } - - return qtrue; -} - -/* -============= -PM_CheckWaterJump -============= -*/ -static qboolean PM_CheckWaterJump( void ) { - vec3_t spot; - int cont; - vec3_t flatforward; - - if (pm->ps->pm_time) { - return qfalse; - } - - // check for water jump - if ( pm->waterlevel != 2 ) { - return qfalse; - } - - flatforward[0] = pml.forward[0]; - flatforward[1] = pml.forward[1]; - flatforward[2] = 0; - VectorNormalize (flatforward); - - VectorMA (pm->ps->origin, 30, flatforward, spot); - spot[2] += 4; - cont = pm->pointcontents (spot, pm->ps->clientNum ); - if ( !(cont & CONTENTS_SOLID) ) { - return qfalse; - } - - spot[2] += 16; - cont = pm->pointcontents (spot, pm->ps->clientNum ); - if ( cont ) { - return qfalse; - } - - // jump out of water - VectorScale (pml.forward, 200, pm->ps->velocity); - pm->ps->velocity[2] = 350; - - pm->ps->pm_flags |= PMF_TIME_WATERJUMP; - pm->ps->pm_time = 2000; - - return qtrue; -} - -//============================================================================ - - -/* -=================== -PM_WaterJumpMove - -Flying out of the water -=================== -*/ -static void PM_WaterJumpMove( void ) { - // waterjump has no control, but falls - - PM_StepSlideMove( qtrue ); - - pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; - if (pm->ps->velocity[2] < 0) { - // cancel as soon as we are falling down again - pm->ps->pm_flags &= ~PMF_ALL_TIMES; - pm->ps->pm_time = 0; - } -} - -/* -=================== -PM_WaterMove - -=================== -*/ -static void PM_WaterMove( void ) { - int i; - vec3_t wishvel; - float wishspeed; - vec3_t wishdir; - float scale; - float vel; - - if ( PM_CheckWaterJump() ) { - PM_WaterJumpMove(); - return; - } -#if 0 - // jump = head for surface - if ( pm->cmd.upmove >= 10 ) { - if (pm->ps->velocity[2] > -300) { - if ( pm->watertype == CONTENTS_WATER ) { - pm->ps->velocity[2] = 100; - } else if (pm->watertype == CONTENTS_SLIME) { - pm->ps->velocity[2] = 80; - } else { - pm->ps->velocity[2] = 50; - } - } - } -#endif - PM_Friction (); - - scale = PM_CmdScale( &pm->cmd ); - // - // user intentions - // - if ( !scale ) { - wishvel[0] = 0; - wishvel[1] = 0; - wishvel[2] = -60; // sink towards bottom - } else { - for (i=0 ; i<3 ; i++) - wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; - - wishvel[2] += scale * pm->cmd.upmove; - } - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - - if ( wishspeed > pm->ps->speed * pm_swimScale ) { - wishspeed = pm->ps->speed * pm_swimScale; - } - - PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); - - // make sure we can go up slopes easily under water - if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { - vel = VectorLength(pm->ps->velocity); - // slide along the ground plane - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - - VectorNormalize(pm->ps->velocity); - VectorScale(pm->ps->velocity, vel, pm->ps->velocity); - } - - PM_SlideMove( qfalse ); -} - -#ifdef MISSIONPACK -/* -=================== -PM_InvulnerabilityMove - -Only with the invulnerability powerup -=================== -*/ -static void PM_InvulnerabilityMove( void ) { - pm->cmd.forwardmove = 0; - pm->cmd.rightmove = 0; - pm->cmd.upmove = 0; - VectorClear(pm->ps->velocity); -} -#endif - -/* -=================== -PM_FlyMove - -Only with the flight powerup -=================== -*/ -static void PM_FlyMove( void ) { - int i; - vec3_t wishvel; - float wishspeed; - vec3_t wishdir; - float scale; - - // normal slowdown - PM_Friction (); - - scale = PM_CmdScale( &pm->cmd ); - // - // user intentions - // - if ( !scale ) { - wishvel[0] = 0; - wishvel[1] = 0; - wishvel[2] = 0; - } else { - for (i=0 ; i<3 ; i++) { - wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; - } - - wishvel[2] += scale * pm->cmd.upmove; - } - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - - PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); - - PM_StepSlideMove( qfalse ); -} - - -/* -=================== -PM_AirMove - -=================== -*/ -static void PM_AirMove( void ) { - int i; - vec3_t wishvel; - float fmove, smove; - vec3_t wishdir; - float wishspeed; - float scale; - usercmd_t cmd; - - PM_Friction(); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); - - // set the movementDir so clients can rotate the legs for strafing - PM_SetMovementDir(); - - // project moves down to flat plane - pml.forward[2] = 0; - pml.right[2] = 0; - VectorNormalize (pml.forward); - VectorNormalize (pml.right); - - for ( i = 0 ; i < 2 ; i++ ) { - wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; - } - wishvel[2] = 0; - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - // not on ground, so little effect on velocity - PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); - - // we may have a ground plane that is very steep, even - // though we don't have a groundentity - // slide along the steep plane - if ( pml.groundPlane ) { - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - } - -#if 0 - //ZOID: If we are on the grapple, try stair-stepping - //this allows a player to use the grapple to pull himself - //over a ledge - if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) - PM_StepSlideMove ( qtrue ); - else - PM_SlideMove ( qtrue ); -#endif - - PM_StepSlideMove ( qtrue ); -} - -/* -=================== -PM_GrappleMove - -=================== -*/ -static void PM_GrappleMove( void ) { - vec3_t vel, v; - float vlen; - - VectorScale(pml.forward, -16, v); - VectorAdd(pm->ps->grapplePoint, v, v); - VectorSubtract(v, pm->ps->origin, vel); - vlen = VectorLength(vel); - VectorNormalize( vel ); - - if (vlen <= 100) - VectorScale(vel, 10 * vlen, vel); - else - VectorScale(vel, 800, vel); - - VectorCopy(vel, pm->ps->velocity); - - pml.groundPlane = qfalse; -} - -/* -=================== -PM_WalkMove - -=================== -*/ -static void PM_WalkMove( void ) { - int i; - vec3_t wishvel; - float fmove, smove; - vec3_t wishdir; - float wishspeed; - float scale; - usercmd_t cmd; - float accelerate; - float vel; - - if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { - // begin swimming - PM_WaterMove(); - return; - } - - - if ( PM_CheckJump () ) { - // jumped away - if ( pm->waterlevel > 1 ) { - PM_WaterMove(); - } else { - PM_AirMove(); - } - return; - } - - PM_Friction (); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); - - // set the movementDir so clients can rotate the legs for strafing - PM_SetMovementDir(); - - // project moves down to flat plane - pml.forward[2] = 0; - pml.right[2] = 0; - - // project the forward and right directions onto the ground plane - PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); - PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); - // - VectorNormalize (pml.forward); - VectorNormalize (pml.right); - - for ( i = 0 ; i < 3 ; i++ ) { - wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; - } - // when going up or down slopes the wish velocity should Not be zero -// wishvel[2] = 0; - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - // clamp the speed lower if ducking - if ( pm->ps->pm_flags & PMF_DUCKED ) { - if ( wishspeed > pm->ps->speed * pm_duckScale ) { - wishspeed = pm->ps->speed * pm_duckScale; - } - } - - // clamp the speed lower if wading or walking on the bottom - if ( pm->waterlevel ) { - float waterScale; - - waterScale = pm->waterlevel / 3.0; - waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; - if ( wishspeed > pm->ps->speed * waterScale ) { - wishspeed = pm->ps->speed * waterScale; - } - } - - // when a player gets hit, they temporarily lose - // full control, which allows them to be moved a bit - if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { - accelerate = pm_airaccelerate; - } else { - accelerate = pm_accelerate; - } - - PM_Accelerate (wishdir, wishspeed, accelerate); - - //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); - //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); - - if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { - pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; - } else { - // don't reset the z velocity for slopes -// pm->ps->velocity[2] = 0; - } - - vel = VectorLength(pm->ps->velocity); - - // slide along the ground plane - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - - // don't decrease velocity when going up or down a slope - VectorNormalize(pm->ps->velocity); - VectorScale(pm->ps->velocity, vel, pm->ps->velocity); - - // don't do anything if standing still - if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) { - return; - } - - PM_StepSlideMove( qfalse ); - - //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); - -} - - -/* -============== -PM_DeadMove -============== -*/ -static void PM_DeadMove( void ) { - float forward; - - if ( !pml.walking ) { - return; - } - - // extra friction - - forward = VectorLength (pm->ps->velocity); - forward -= 20; - if ( forward <= 0 ) { - VectorClear (pm->ps->velocity); - } else { - VectorNormalize (pm->ps->velocity); - VectorScale (pm->ps->velocity, forward, pm->ps->velocity); - } -} - - -/* -=============== -PM_NoclipMove -=============== -*/ -static void PM_NoclipMove( void ) { - float speed, drop, friction, control, newspeed; - int i; - vec3_t wishvel; - float fmove, smove; - vec3_t wishdir; - float wishspeed; - float scale; - - pm->ps->viewheight = DEFAULT_VIEWHEIGHT; - - // friction - - speed = VectorLength (pm->ps->velocity); - if (speed < 1) - { - VectorCopy (vec3_origin, pm->ps->velocity); - } - else - { - drop = 0; - - friction = pm_friction*1.5; // extra friction - control = speed < pm_stopspeed ? pm_stopspeed : speed; - drop += control*friction*pml.frametime; - - // scale the velocity - newspeed = speed - drop; - if (newspeed < 0) - newspeed = 0; - newspeed /= speed; - - VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); - } - - // accelerate - scale = PM_CmdScale( &pm->cmd ); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - for (i=0 ; i<3 ; i++) - wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; - wishvel[2] += pm->cmd.upmove; - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - PM_Accelerate( wishdir, wishspeed, pm_accelerate ); - - // move - VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); -} - -//============================================================================ - -/* -================ -PM_FootstepForSurface - -Returns an event number apropriate for the groundsurface -================ -*/ -static int PM_FootstepForSurface( void ) { - if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { - return 0; - } - if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { - return EV_FOOTSTEP_METAL; - } - return EV_FOOTSTEP; -} - - -/* -================= -PM_CrashLand - -Check for hard landings that generate sound events -================= -*/ -static void PM_CrashLand( void ) { - float delta; - float dist; - float vel, acc; - float t; - float a, b, c, den; - - // decide which landing animation to use - if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { - PM_ForceLegsAnim( LEGS_LANDB ); - } else { - PM_ForceLegsAnim( LEGS_LAND ); - } - - pm->ps->legsTimer = TIMER_LAND; - - // calculate the exact velocity on landing - dist = pm->ps->origin[2] - pml.previous_origin[2]; - vel = pml.previous_velocity[2]; - acc = -pm->ps->gravity; - - a = acc / 2; - b = vel; - c = -dist; - - den = b * b - 4 * a * c; - if ( den < 0 ) { - return; - } - t = (-b - sqrt( den ) ) / ( 2 * a ); - - delta = vel + t * acc; - delta = delta*delta * 0.0001; - - // ducking while falling doubles damage - if ( pm->ps->pm_flags & PMF_DUCKED ) { - delta *= 2; - } - - // never take falling damage if completely underwater - if ( pm->waterlevel == 3 ) { - return; - } - - // reduce falling damage if there is standing water - if ( pm->waterlevel == 2 ) { - delta *= 0.25; - } - if ( pm->waterlevel == 1 ) { - delta *= 0.5; - } - - if ( delta < 1 ) { - return; - } - - // create a local entity event to play the sound - - // SURF_NODAMAGE is used for bounce pads where you don't ever - // want to take damage or play a crunch sound - if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { - if ( delta > 60 ) { - PM_AddEvent( EV_FALL_FAR ); - } else if ( delta > 40 ) { - // this is a pain grunt, so don't play it if dead - if ( pm->ps->stats[STAT_HEALTH] > 0 ) { - PM_AddEvent( EV_FALL_MEDIUM ); - } - } else if ( delta > 7 ) { - PM_AddEvent( EV_FALL_SHORT ); - } else { - PM_AddEvent( PM_FootstepForSurface() ); - } - } - - // start footstep cycle over - pm->ps->bobCycle = 0; -} - -/* -============= -PM_CheckStuck -============= -*/ -/* -void PM_CheckStuck(void) { - trace_t trace; - - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); - if (trace.allsolid) { - //int shit = qtrue; - } -} -*/ - -/* -============= -PM_CorrectAllSolid -============= -*/ -static int PM_CorrectAllSolid( trace_t *trace ) { - int i, j, k; - vec3_t point; - - if ( pm->debugLevel ) { - Com_Printf("%i:allsolid\n", c_pmove); - } - - // jitter around - for (i = -1; i <= 1; i++) { - for (j = -1; j <= 1; j++) { - for (k = -1; k <= 1; k++) { - VectorCopy(pm->ps->origin, point); - point[0] += (float) i; - point[1] += (float) j; - point[2] += (float) k; - pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - if ( !trace->allsolid ) { - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] - 0.25; - - pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - pml.groundTrace = *trace; - return qtrue; - } - } - } - } - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; - - return qfalse; -} - - -/* -============= -PM_GroundTraceMissed - -The ground trace didn't hit a surface, so we are in freefall -============= -*/ -static void PM_GroundTraceMissed( void ) { - trace_t trace; - vec3_t point; - - if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) { - // we just transitioned into freefall - if ( pm->debugLevel ) { - Com_Printf("%i:lift\n", c_pmove); - } - - // if they aren't in a jumping animation and the ground is a ways away, force into it - // if we didn't do the trace, the player would be backflipping down staircases - VectorCopy( pm->ps->origin, point ); - point[2] -= 64; - - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - if ( trace.fraction == 1.0 ) { - 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->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; -} - - -/* -============= -PM_GroundTrace -============= -*/ -static void PM_GroundTrace( void ) { - vec3_t point; - trace_t trace; - - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] - 0.25; - - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - pml.groundTrace = trace; - - // do something corrective if the trace starts in a solid... - if ( trace.allsolid ) { - if ( !PM_CorrectAllSolid(&trace) ) - return; - } - - // if the trace didn't hit anything, we are in free fall - if ( trace.fraction == 1.0 ) { - PM_GroundTraceMissed(); - pml.groundPlane = qfalse; - pml.walking = qfalse; - return; - } - - // check if getting thrown off the ground - if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { - if ( pm->debugLevel ) { - Com_Printf("%i:kickoff\n", c_pmove); - } - // go into jump animation - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; - return; - } - - // slopes that are too steep will not be considered onground - if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { - if ( pm->debugLevel ) { - Com_Printf("%i:steep\n", c_pmove); - } - // FIXME: if they can't slide down the slope, let them - // walk (sharp crevices) - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qtrue; - pml.walking = qfalse; - return; - } - - pml.groundPlane = qtrue; - pml.walking = qtrue; - - // hitting solid ground will end a waterjump - if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) - { - pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); - pm->ps->pm_time = 0; - } - - if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { - // just hit the ground - if ( pm->debugLevel ) { - Com_Printf("%i:Land\n", c_pmove); - } - - PM_CrashLand(); - - // don't do landing time if we were just going down a slope - if ( pml.previous_velocity[2] < -200 ) { - // don't allow another jump for a little while - pm->ps->pm_flags |= PMF_TIME_LAND; - pm->ps->pm_time = 250; - } - } - - pm->ps->groundEntityNum = trace.entityNum; - - // don't reset the z velocity for slopes -// pm->ps->velocity[2] = 0; - - PM_AddTouchEnt( trace.entityNum ); -} - - -/* -============= -PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving -============= -*/ -static void PM_SetWaterLevel( void ) { - vec3_t point; - int cont; - int sample1; - int sample2; - - // - // get waterlevel, accounting for ducking - // - pm->waterlevel = 0; - pm->watertype = 0; - - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] + MINS_Z + 1; - cont = pm->pointcontents( point, pm->ps->clientNum ); - - if ( cont & MASK_WATER ) { - sample2 = pm->ps->viewheight - MINS_Z; - sample1 = sample2 / 2; - - pm->watertype = cont; - pm->waterlevel = 1; - point[2] = pm->ps->origin[2] + MINS_Z + sample1; - cont = pm->pointcontents (point, pm->ps->clientNum ); - if ( cont & MASK_WATER ) { - pm->waterlevel = 2; - point[2] = pm->ps->origin[2] + MINS_Z + sample2; - cont = pm->pointcontents (point, pm->ps->clientNum ); - if ( cont & MASK_WATER ){ - pm->waterlevel = 3; - } - } - } - -} - -/* -============== -PM_CheckDuck - -Sets mins, maxs, and pm->ps->viewheight -============== -*/ -static void PM_CheckDuck (void) -{ - trace_t trace; - - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - if ( pm->ps->pm_flags & PMF_INVULEXPAND ) { - // invulnerability sphere has a 42 units radius - VectorSet( pm->mins, -42, -42, -42 ); - VectorSet( pm->maxs, 42, 42, 42 ); - } - else { - VectorSet( pm->mins, -15, -15, MINS_Z ); - VectorSet( pm->maxs, 15, 15, 16 ); - } - pm->ps->pm_flags |= PMF_DUCKED; - pm->ps->viewheight = CROUCH_VIEWHEIGHT; - return; - } - pm->ps->pm_flags &= ~PMF_INVULEXPAND; - - pm->mins[0] = -15; - pm->mins[1] = -15; - - pm->maxs[0] = 15; - pm->maxs[1] = 15; - - pm->mins[2] = MINS_Z; - - if (pm->ps->pm_type == PM_DEAD) - { - pm->maxs[2] = -8; - pm->ps->viewheight = DEAD_VIEWHEIGHT; - return; - } - - if (pm->cmd.upmove < 0) - { // duck - pm->ps->pm_flags |= PMF_DUCKED; - } - else - { // stand up if possible - if (pm->ps->pm_flags & PMF_DUCKED) - { - // try to stand up - pm->maxs[2] = 32; - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); - if (!trace.allsolid) - pm->ps->pm_flags &= ~PMF_DUCKED; - } - } - - if (pm->ps->pm_flags & PMF_DUCKED) - { - pm->maxs[2] = 16; - pm->ps->viewheight = CROUCH_VIEWHEIGHT; - } - else - { - pm->maxs[2] = 32; - pm->ps->viewheight = DEFAULT_VIEWHEIGHT; - } -} - - - -//=================================================================== - - -/* -=============== -PM_Footsteps -=============== -*/ -static void PM_Footsteps( void ) { - float bobmove; - int old; - qboolean footstep; - - // - // calculate speed and cycle to be used for - // all cyclic walking effects - // - pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] - + pm->ps->velocity[1] * pm->ps->velocity[1] ); - - if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { - - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - PM_ContinueLegsAnim( LEGS_IDLECR ); - } - // airborne leaves position in cycle intact, but doesn't advance - if ( pm->waterlevel > 1 ) { - PM_ContinueLegsAnim( LEGS_SWIM ); - } - return; - } - - // if not trying to move - if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { - if ( pm->xyspeed < 5 ) { - pm->ps->bobCycle = 0; // start at beginning of cycle again - if ( pm->ps->pm_flags & PMF_DUCKED ) { - PM_ContinueLegsAnim( LEGS_IDLECR ); - } else { - PM_ContinueLegsAnim( LEGS_IDLE ); - } - } - return; - } - - - footstep = qfalse; - - if ( pm->ps->pm_flags & PMF_DUCKED ) { - bobmove = 0.5; // ducked characters bob much faster - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACKCR ); - } - else { - PM_ContinueLegsAnim( LEGS_WALKCR ); - } - // ducked characters never play footsteps - /* - } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { - bobmove = 0.4; // faster speeds bob faster - footstep = qtrue; - } else { - bobmove = 0.3; - } - PM_ContinueLegsAnim( LEGS_BACK ); - */ - } else { - if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { - bobmove = 0.4f; // faster speeds bob faster - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACK ); - } - else { - PM_ContinueLegsAnim( LEGS_RUN ); - } - footstep = qtrue; - } else { - bobmove = 0.3f; // walking bobs slow - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACKWALK ); - } - else { - PM_ContinueLegsAnim( LEGS_WALK ); - } - } - } - - // check for footstep / splash sounds - old = pm->ps->bobCycle; - pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; - - // if we just crossed a cycle boundary, play an apropriate footstep event - if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) { - if ( pm->waterlevel == 0 ) { - // on ground will only play sounds if running - if ( footstep && !pm->noFootsteps ) { - PM_AddEvent( PM_FootstepForSurface() ); - } - } else if ( pm->waterlevel == 1 ) { - // splashing - PM_AddEvent( EV_FOOTSPLASH ); - } else if ( pm->waterlevel == 2 ) { - // wading / swimming at surface - PM_AddEvent( EV_SWIM ); - } else if ( pm->waterlevel == 3 ) { - // no sound when completely underwater - - } - } -} - -/* -============== -PM_WaterEvents - -Generate sound events for entering and leaving water -============== -*/ -static void PM_WaterEvents( void ) { // FIXME? - // - // if just entered a water volume, play a sound - // - if (!pml.previous_waterlevel && pm->waterlevel) { - PM_AddEvent( EV_WATER_TOUCH ); - } - - // - // if just completely exited a water volume, play a sound - // - if (pml.previous_waterlevel && !pm->waterlevel) { - PM_AddEvent( EV_WATER_LEAVE ); - } - - // - // check for head just going under water - // - if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { - PM_AddEvent( EV_WATER_UNDER ); - } - - // - // check for head just coming out of water - // - if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { - PM_AddEvent( EV_WATER_CLEAR ); - } -} - - -/* -=============== -PM_BeginWeaponChange -=============== -*/ -static void PM_BeginWeaponChange( int weapon ) { - if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { - return; - } - - if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { - return; - } - - if ( pm->ps->weaponstate == WEAPON_DROPPING ) { - return; - } - - PM_AddEvent( EV_CHANGE_WEAPON ); - pm->ps->weaponstate = WEAPON_DROPPING; - pm->ps->weaponTime += 200; - PM_StartTorsoAnim( TORSO_DROP ); -} - - -/* -=============== -PM_FinishWeaponChange -=============== -*/ -static void PM_FinishWeaponChange( void ) { - int weapon; - - weapon = pm->cmd.weapon; - if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { - weapon = WP_NONE; - } - - if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { - weapon = WP_NONE; - } - - pm->ps->weapon = weapon; - pm->ps->weaponstate = WEAPON_RAISING; - pm->ps->weaponTime += 250; - PM_StartTorsoAnim( TORSO_RAISE ); -} - - -/* -============== -PM_TorsoAnimation - -============== -*/ -static void PM_TorsoAnimation( void ) { - if ( pm->ps->weaponstate == WEAPON_READY ) { - if ( pm->ps->weapon == WP_GAUNTLET ) { - PM_ContinueTorsoAnim( TORSO_STAND2 ); - } else { - PM_ContinueTorsoAnim( TORSO_STAND ); - } - return; - } -} - - -/* -============== -PM_Weapon - -Generates weapon events and modifes the weapon counter -============== -*/ -static void PM_Weapon( void ) { - int addTime; - - // don't allow attack until all buttons are up - if ( pm->ps->pm_flags & PMF_RESPAWNED ) { - return; - } - - // ignore if spectator - if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - return; - } - - // check for dead player - if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { - pm->ps->weapon = WP_NONE; - return; - } - - // check for item using - if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { - if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { - if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT - && pm->ps->stats[STAT_HEALTH] >= (pm->ps->stats[STAT_MAX_HEALTH] + 25) ) { - // don't use medkit if at max health - } else { - pm->ps->pm_flags |= PMF_USE_ITEM_HELD; - PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); - pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; - } - return; - } - } else { - pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; - } - - - // make weapon function - if ( pm->ps->weaponTime > 0 ) { - pm->ps->weaponTime -= pml.msec; - } - - // check for weapon change - // can't change if weapon is firing, but can change - // again if lowering or raising - if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { - if ( pm->ps->weapon != pm->cmd.weapon ) { - PM_BeginWeaponChange( pm->cmd.weapon ); - } - } - - if ( pm->ps->weaponTime > 0 ) { - return; - } - - // change weapon if time - if ( pm->ps->weaponstate == WEAPON_DROPPING ) { - PM_FinishWeaponChange(); - return; - } - - if ( pm->ps->weaponstate == WEAPON_RAISING ) { - pm->ps->weaponstate = WEAPON_READY; - if ( pm->ps->weapon == WP_GAUNTLET ) { - PM_StartTorsoAnim( TORSO_STAND2 ); - } else { - PM_StartTorsoAnim( TORSO_STAND ); - } - return; - } - - // check for fire - if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; - return; - } - - // start the animation even if out of ammo - if ( pm->ps->weapon == WP_GAUNTLET ) { - // the guantlet only "fires" when it actually hits something - if ( !pm->gauntletHit ) { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; - return; - } - PM_StartTorsoAnim( TORSO_ATTACK2 ); - } else { - PM_StartTorsoAnim( TORSO_ATTACK ); - } - - pm->ps->weaponstate = WEAPON_FIRING; - - // check for out of ammo - if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { - PM_AddEvent( EV_NOAMMO ); - pm->ps->weaponTime += 500; - return; - } - - // take an ammo away if not infinite - if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) { - pm->ps->ammo[ pm->ps->weapon ]--; - } - - // fire weapon - PM_AddEvent( EV_FIRE_WEAPON ); - - switch( pm->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; -#ifdef MISSIONPACK - case WP_NAILGUN: - addTime = 1000; - break; - case WP_PROX_LAUNCHER: - addTime = 800; - break; - case WP_CHAINGUN: - addTime = 30; - break; -#endif - } - -#ifdef MISSIONPACK - if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - addTime /= 1.5; - } - else - if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { - addTime /= 1.3; - } - else -#endif - if ( pm->ps->powerups[PW_HASTE] ) { - addTime /= 1.3; - } - - pm->ps->weaponTime += addTime; -} - -/* -================ -PM_Animate -================ -*/ - -static void PM_Animate( void ) { - if ( pm->cmd.buttons & BUTTON_GESTURE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GESTURE ); - pm->ps->torsoTimer = TIMER_GESTURE; - PM_AddEvent( EV_TAUNT ); - } -#ifdef MISSIONPACK - } else if ( pm->cmd.buttons & BUTTON_GETFLAG ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GETFLAG ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GUARDBASE ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_PATROL ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_PATROL ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_FOLLOWME ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_AFFIRMATIVE); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_NEGATIVE ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } -#endif - } -} - - -/* -================ -PM_DropTimers -================ -*/ -static void PM_DropTimers( void ) { - // drop misc timing counter - if ( pm->ps->pm_time ) { - if ( pml.msec >= pm->ps->pm_time ) { - pm->ps->pm_flags &= ~PMF_ALL_TIMES; - pm->ps->pm_time = 0; - } else { - pm->ps->pm_time -= pml.msec; - } - } - - // drop animation counter - if ( pm->ps->legsTimer > 0 ) { - pm->ps->legsTimer -= pml.msec; - if ( pm->ps->legsTimer < 0 ) { - pm->ps->legsTimer = 0; - } - } - - if ( pm->ps->torsoTimer > 0 ) { - pm->ps->torsoTimer -= pml.msec; - if ( pm->ps->torsoTimer < 0 ) { - pm->ps->torsoTimer = 0; - } - } -} - -/* -================ -PM_UpdateViewAngles - -This can be used as another entry point when only the viewangles -are being updated isntead of a full move -================ -*/ -void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { - short temp; - int i; - - if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { - return; // no view changes at all - } - - if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { - return; // no view changes at all - } - - // circularly clamp the angles with deltas - for (i=0 ; i<3 ; i++) { - temp = cmd->angles[i] + ps->delta_angles[i]; - if ( i == PITCH ) { - // don't let the player look up or down more than 90 degrees - if ( temp > 16000 ) { - ps->delta_angles[i] = 16000 - cmd->angles[i]; - temp = 16000; - } else if ( temp < -16000 ) { - ps->delta_angles[i] = -16000 - cmd->angles[i]; - temp = -16000; - } - } - ps->viewangles[i] = SHORT2ANGLE(temp); - } - -} - - -/* -================ -PmoveSingle - -================ -*/ -void trap_SnapVector( float *v ); - -void PmoveSingle (pmove_t *pmove) { - pm = pmove; - - // this counter lets us debug movement problems with a journal - // by setting a conditional breakpoint fot the previous frame - c_pmove++; - - // clear results - pm->numtouch = 0; - pm->watertype = 0; - pm->waterlevel = 0; - - if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { - pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies - } - - // make sure walking button is clear if they are running, to avoid - // proxy no-footsteps cheats - if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { - pm->cmd.buttons &= ~BUTTON_WALKING; - } - - // set the talk balloon flag - if ( pm->cmd.buttons & BUTTON_TALK ) { - pm->ps->eFlags |= EF_TALK; - } else { - pm->ps->eFlags &= ~EF_TALK; - } - - // set the firing flag for continuous beam weapons - if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION - && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) { - pm->ps->eFlags |= EF_FIRING; - } else { - pm->ps->eFlags &= ~EF_FIRING; - } - - // clear the respawned flag if attack and use are cleared - if ( pm->ps->stats[STAT_HEALTH] > 0 && - !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { - pm->ps->pm_flags &= ~PMF_RESPAWNED; - } - - // if talk button is down, dissallow all other input - // this is to prevent any possible intercept proxy from - // adding fake talk balloons - if ( pmove->cmd.buttons & BUTTON_TALK ) { - // keep the talk button set tho for when the cmd.serverTime > 66 msec - // and the same cmd is used multiple times in Pmove - pmove->cmd.buttons = BUTTON_TALK; - pmove->cmd.forwardmove = 0; - pmove->cmd.rightmove = 0; - pmove->cmd.upmove = 0; - } - - // clear all pmove local vars - memset (&pml, 0, sizeof(pml)); - - // determine the time - pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; - if ( pml.msec < 1 ) { - pml.msec = 1; - } else if ( pml.msec > 200 ) { - pml.msec = 200; - } - pm->ps->commandTime = pmove->cmd.serverTime; - - // save old org in case we get stuck - VectorCopy (pm->ps->origin, pml.previous_origin); - - // save old velocity for crashlanding - VectorCopy (pm->ps->velocity, pml.previous_velocity); - - pml.frametime = pml.msec * 0.001; - - // update the viewangles - PM_UpdateViewAngles( pm->ps, &pm->cmd ); - - AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); - - if ( pm->cmd.upmove < 10 ) { - // not holding jump - pm->ps->pm_flags &= ~PMF_JUMP_HELD; - } - - // decide if backpedaling animations should be used - if ( pm->cmd.forwardmove < 0 ) { - pm->ps->pm_flags |= PMF_BACKWARDS_RUN; - } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { - pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; - } - - if ( pm->ps->pm_type >= PM_DEAD ) { - pm->cmd.forwardmove = 0; - pm->cmd.rightmove = 0; - pm->cmd.upmove = 0; - } - - if ( pm->ps->pm_type == PM_SPECTATOR ) { - PM_CheckDuck (); - PM_FlyMove (); - PM_DropTimers (); - return; - } - - if ( pm->ps->pm_type == PM_NOCLIP ) { - PM_NoclipMove (); - PM_DropTimers (); - return; - } - - if (pm->ps->pm_type == PM_FREEZE) { - return; // no movement at all - } - - if ( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION) { - return; // no movement at all - } - - // set watertype, and waterlevel - PM_SetWaterLevel(); - pml.previous_waterlevel = pmove->waterlevel; - - // set mins, maxs, and viewheight - PM_CheckDuck (); - - // set groundentity - PM_GroundTrace(); - - if ( pm->ps->pm_type == PM_DEAD ) { - PM_DeadMove (); - } - - PM_DropTimers(); - -#ifdef MISSIONPACK - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - PM_InvulnerabilityMove(); - } else -#endif - if ( pm->ps->powerups[PW_FLIGHT] ) { - // flight powerup doesn't allow jump and has different friction - PM_FlyMove(); - } else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) { - PM_GrappleMove(); - // We can wiggle a bit - PM_AirMove(); - } else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { - PM_WaterJumpMove(); - } else if ( pm->waterlevel > 1 ) { - // swimming - PM_WaterMove(); - } else if ( pml.walking ) { - // walking on ground - PM_WalkMove(); - } else { - // airborne - PM_AirMove(); - } - - PM_Animate(); - - // set groundentity, watertype, and waterlevel - PM_GroundTrace(); - PM_SetWaterLevel(); - - // weapons - PM_Weapon(); - - // torso animation - PM_TorsoAnimation(); - - // footstep events / legs animations - PM_Footsteps(); - - // entering / leaving water splashes - PM_WaterEvents(); - - // snap some parts of playerstate to save network bandwidth - trap_SnapVector( pm->ps->velocity ); -} - - -/* -================ -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->commandTime != finalTime ) { - int msec; - - msec = finalTime - pmove->ps->commandTime; - - if ( pmove->pmove_fixed ) { - if ( msec > pmove->pmove_msec ) { - msec = pmove->pmove_msec; - } - } - else { - if ( msec > 66 ) { - msec = 66; - } - } - pmove->cmd.serverTime = pmove->ps->commandTime + msec; - PmoveSingle( pmove ); - - if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { - pmove->cmd.upmove = 20; - } - } - - //PM_CheckStuck(); - -} - +/* +=========================================================================== +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 + +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; + } + } +} diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 0dd59db..d0b3095 100644 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -139,22 +139,35 @@ typedef enum { WEAPON_FIRING } weaponstate_t; -// pmove->pm_flags -#define PMF_DUCKED 1 -#define PMF_JUMP_HELD 2 -#define PMF_BACKWARDS_JUMP 8 // go into backwards land -#define PMF_BACKWARDS_RUN 16 // coast down to backwards run -#define PMF_TIME_LAND 32 // pm_time is time before rejump -#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time -#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump -#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up -#define PMF_USE_ITEM_HELD 1024 -#define PMF_GRAPPLE_PULL 2048 // pull towards grapple location -#define PMF_FOLLOW 4096 // spectate following another player -#define PMF_SCOREBOARD 8192 // spectate as a scoreboard -#define PMF_INVULEXPAND 16384 // invulnerability sphere set to full size +// pmove->pm_flags (QL remapped values — NOT the same as Q3/Q3TA!) +#define PMF_DUCKED 0x0001 +#define PMF_JUMP_HELD 0x0002 +#define PMF_ATTACK_HELD 0x0004 // QL: blocks weapon fire (warmup/transition) +#define PMF_BACKWARDS_JUMP 0x0008 +#define PMF_BACKWARDS_RUN 0x0010 +#define PMF_TIME_KNOCKBACK 0x0020 // pm_time is knockback stun +#define PMF_TIME_WATERJUMP 0x0040 // pm_time is waterjump +#define PMF_TIME_WATERJUMP_INIT 0x0080 // QL: waterjump initiation +#define PMF_USE_ITEM_HELD 0x0100 // freezes player (intermission) +#define PMF_RESPAWNED 0x0200 // clear after attack and jump buttons come up +#define PMF_GRAPPLE_PULL 0x0400 // grapple hook active +#define PMF_GRAPPLE_ACTIVE 0x0800 // QL: grapple point tracking +#define PMF_FOLLOW_SPECTATE 0x1000 // spectate following another player +#define PMF_GRAPPLE_TARGET 0x2000 // QL: player is grappled by another +#define PMF_INVUL_EXPAND 0x4000 // invulnerability sphere set to full size +#define PMF_GRAPPLE_RELEASED 0x8000 // QL: grapple just released (blocks fire) +#define PMF_PROMODE 0x10000 // QL: PQL air control / promode physics +#define PMF_DOUBLE_JUMP 0x20000 // QL: double jump enabled +#define PMF_AUTOHOP_HELD 0x40000 // QL: per-player autohop disabled (inverted) +#define PMF_CA_TEAM1 0x80000 // QL: Clan Arena team 1 +#define PMF_CROUCH_SLIDE 0x100000 // QL: crouch slide mechanic enabled -#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_KNOCKBACK|PMF_TIME_WATERJUMP_INIT) + +// Q3TA compatibility aliases +#define PMF_FOLLOW PMF_FOLLOW_SPECTATE +#define PMF_INVULEXPAND PMF_INVUL_EXPAND +#define PMF_SCOREBOARD PMF_FOLLOW_SPECTATE // Q3TA used separate bit; QL merged into FOLLOW #define MAXTOUCH 32 typedef struct { @@ -166,9 +179,7 @@ typedef struct { int tracemask; // collide against these types of surfaces int debugLevel; // if set, diagnostic output will be printed qboolean noFootsteps; // if the game is setup for no footsteps by the server - qboolean gauntletHit; // true if a gauntlet attack would actually hit something - - int framecount; + qboolean gauntletHit; // Q3TA compat: true if a gauntlet attack would actually hit something // results (out) int numtouch; @@ -181,14 +192,22 @@ typedef struct { float xyspeed; - // for fixed msec Pmove - int pmove_fixed; - int pmove_msec; + // QL additions after xyspeed + float field_0dc; // step height delta + int field_0e0; // cmd.serverTime of step event + int field_0e4; // unconfirmed // callbacks to test the world // these will be different functions during game and cgame void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); int (*pointcontents)( const vec3_t point, int passEntityNum ); + + int field_0f8; // QL: linked entity boolean + int pad_0fc; + + // Q3TA compat fields (not in QL pmove_t but used by game code) + int pmove_fixed; + int pmove_msec; } pmove_t; // if a full pmove isn't done on the client, you can just update the angles diff --git a/code/game/bg_slidemove.c b/code/game/bg_slidemove.c index ed1349f..0f4bcec 100644 --- a/code/game/bg_slidemove.c +++ b/code/game/bg_slidemove.c @@ -1,325 +1,397 @@ -/* -=========================================================================== -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_slidemove.c -- part of bg_pmove functionality - -#include "q_shared.h" -#include "bg_public.h" -#include "bg_local.h" - -/* - -input: origin, velocity, bounds, groundPlane, trace function - -output: origin, velocity, impacts, stairup boolean - -*/ - -/* -================== -PM_SlideMove - -Returns qtrue if the velocity was clipped in some way -================== -*/ -#define MAX_CLIP_PLANES 5 -qboolean PM_SlideMove( qboolean gravity ) { - int bumpcount, numbumps; - vec3_t dir; - float d; - int numplanes; - vec3_t planes[MAX_CLIP_PLANES]; - vec3_t primal_velocity; - vec3_t clipVelocity; - int i, j, k; - trace_t trace; - vec3_t end; - float time_left; - float into; - vec3_t endVelocity; - vec3_t endClipVelocity; - - numbumps = 4; - - VectorCopy (pm->ps->velocity, primal_velocity); - - if ( gravity ) { - VectorCopy( pm->ps->velocity, endVelocity ); - endVelocity[2] -= pm->ps->gravity * pml.frametime; - pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; - primal_velocity[2] = endVelocity[2]; - if ( pml.groundPlane ) { - // slide along the ground plane - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - } - } - - time_left = pml.frametime; - - // never turn against the ground plane - if ( pml.groundPlane ) { - numplanes = 1; - VectorCopy( pml.groundTrace.plane.normal, planes[0] ); - } else { - numplanes = 0; - } - - // never turn against original velocity - VectorNormalize2( pm->ps->velocity, planes[numplanes] ); - numplanes++; - - for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { - - // calculate position we are trying to move to - VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); - - // see if we can make it there - pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); - - if (trace.allsolid) { - // entity is completely trapped in another solid - pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration - return qtrue; - } - - if (trace.fraction > 0) { - // actually covered some distance - VectorCopy (trace.endpos, pm->ps->origin); - } - - if (trace.fraction == 1) { - break; // moved the entire distance - } - - // save entity for contact - PM_AddTouchEnt( trace.entityNum ); - - time_left -= time_left * trace.fraction; - - if (numplanes >= MAX_CLIP_PLANES) { - // this shouldn't really happen - VectorClear( pm->ps->velocity ); - return qtrue; - } - - // - // if this is the same plane we hit before, nudge velocity - // out along it, which fixes some epsilon issues with - // non-axial planes - // - for ( i = 0 ; i < numplanes ; i++ ) { - if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { - VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); - break; - } - } - if ( i < numplanes ) { - continue; - } - VectorCopy (trace.plane.normal, planes[numplanes]); - numplanes++; - - // - // modify velocity so it parallels all of the clip planes - // - - // find a plane that it enters - for ( i = 0 ; i < numplanes ; i++ ) { - into = DotProduct( pm->ps->velocity, planes[i] ); - if ( into >= 0.1 ) { - continue; // move doesn't interact with the plane - } - - // see how hard we are hitting things - if ( -into > pml.impactSpeed ) { - pml.impactSpeed = -into; - } - - // slide along the plane - PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); - - // slide along the plane - PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); - - // see if there is a second plane that the new move enters - for ( j = 0 ; j < numplanes ; j++ ) { - if ( j == i ) { - continue; - } - if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { - continue; // move doesn't interact with the plane - } - - // try clipping the move to the plane - PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); - PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); - - // see if it goes back into the first clip plane - if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { - continue; - } - - // slide the original velocity along the crease - CrossProduct (planes[i], planes[j], dir); - VectorNormalize( dir ); - d = DotProduct( dir, pm->ps->velocity ); - VectorScale( dir, d, clipVelocity ); - - CrossProduct (planes[i], planes[j], dir); - VectorNormalize( dir ); - d = DotProduct( dir, endVelocity ); - VectorScale( dir, d, endClipVelocity ); - - // see if there is a third plane the the new move enters - for ( k = 0 ; k < numplanes ; k++ ) { - if ( k == i || k == j ) { - continue; - } - if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { - continue; // move doesn't interact with the plane - } - - // stop dead at a tripple plane interaction - VectorClear( pm->ps->velocity ); - return qtrue; - } - } - - // if we have fixed all interactions, try another move - VectorCopy( clipVelocity, pm->ps->velocity ); - VectorCopy( endClipVelocity, endVelocity ); - break; - } - } - - if ( gravity ) { - VectorCopy( endVelocity, pm->ps->velocity ); - } - - // don't change velocity if in a timer (FIXME: is this correct?) - if ( pm->ps->pm_time ) { - VectorCopy( primal_velocity, pm->ps->velocity ); - } - - return ( bumpcount != 0 ); -} - -/* -================== -PM_StepSlideMove - -================== -*/ -void PM_StepSlideMove( qboolean gravity ) { - vec3_t start_o, start_v; - vec3_t down_o, down_v; - trace_t trace; -// float down_dist, up_dist; -// vec3_t delta, delta2; - vec3_t up, down; - float stepSize; - - VectorCopy (pm->ps->origin, start_o); - VectorCopy (pm->ps->velocity, start_v); - - if ( PM_SlideMove( gravity ) == 0 ) { - return; // we got exactly where we wanted to go first try - } - - VectorCopy(start_o, down); - down[2] -= STEPSIZE; - pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); - VectorSet(up, 0, 0, 1); - // never step up when you still have up velocity - if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || - DotProduct(trace.plane.normal, up) < 0.7)) { - return; - } - - VectorCopy (pm->ps->origin, down_o); - VectorCopy (pm->ps->velocity, down_v); - - VectorCopy (start_o, up); - up[2] += STEPSIZE; - - // test the player position if they were a stepheight higher - pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); - if ( trace.allsolid ) { - if ( pm->debugLevel ) { - Com_Printf("%i:bend can't step\n", c_pmove); - } - return; // can't step up - } - - stepSize = trace.endpos[2] - start_o[2]; - // try slidemove from this position - VectorCopy (trace.endpos, pm->ps->origin); - VectorCopy (start_v, pm->ps->velocity); - - PM_SlideMove( gravity ); - - // push down the final amount - VectorCopy (pm->ps->origin, down); - down[2] -= stepSize; - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); - if ( !trace.allsolid ) { - VectorCopy (trace.endpos, pm->ps->origin); - } - if ( trace.fraction < 1.0 ) { - PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); - } - -#if 0 - // if the down trace can trace back to the original position directly, don't step - pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); - if ( trace.fraction == 1.0 ) { - // use the original move - VectorCopy (down_o, pm->ps->origin); - VectorCopy (down_v, pm->ps->velocity); - if ( pm->debugLevel ) { - Com_Printf("%i:bend\n", c_pmove); - } - } else -#endif - { - // use the step move - float delta; - - delta = pm->ps->origin[2] - start_o[2]; - if ( delta > 2 ) { - if ( delta < 7 ) { - PM_AddEvent( EV_STEP_4 ); - } else if ( delta < 11 ) { - PM_AddEvent( EV_STEP_8 ); - } else if ( delta < 15 ) { - PM_AddEvent( EV_STEP_12 ); - } else { - PM_AddEvent( EV_STEP_16 ); - } - } - if ( pm->debugLevel ) { - Com_Printf("%i:stepped\n", c_pmove); - } - } -} - +/* +=========================================================================== +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_slidemove.c -- part of bg_pmove functionality +// +// Reconstructed from Quake Live x86_64 binary. +// PM_SlideMove at 0x00155ed0, PM_StepSlideMove at 0x00156850. + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +extern float _DAT_003cfe90; // velocity damping factor for step-up +extern int DAT_003cfe30; // edge grab enabled +extern int pmove_stepJumpFlag; // step jump enabled + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +qboolean PM_SlideMove( qboolean gravity ) { + int bumpcount; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t clipVelocity; + vec3_t endClipVelocity; + vec3_t endVelocity; + vec3_t end; + vec3_t dir; + trace_t trace; + float primal_vel_x, primal_vel_y, primal_vel_z; + float time_left; + float into; + float d; + int i, j, k; + + primal_vel_x = pm->ps->velocity[0]; + primal_vel_y = pm->ps->velocity[1]; + primal_vel_z = pm->ps->velocity[2]; + + if ( !gravity ) { + endVelocity[0] = 0.0f; + endVelocity[1] = 0.0f; + endVelocity[2] = 0.0f; + } else { + endVelocity[0] = primal_vel_x; + endVelocity[1] = pm->ps->velocity[1]; + primal_vel_z = pm->ps->velocity[2] - (float)pm->ps->gravity * pml.frametime; + pm->ps->velocity[2] = (pm->ps->velocity[2] + primal_vel_z) * 0.5f; + endVelocity[2] = primal_vel_z; + if ( pml.groundPlane ) { + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) { + numplanes = 2; + VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + VectorNormalize2( pm->ps->velocity, planes[1] ); + } else { + numplanes = 1; + VectorNormalize2( pm->ps->velocity, planes[0] ); + } + + for ( bumpcount = 0; bumpcount < 4; bumpcount++ ) { + + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end, + pm->ps->clientNum, pm->tracemask ); + + if ( trace.allsolid ) { + // entity is completely trapped in another solid + pm->ps->velocity[2] = 0.0f; + return qtrue; + } + + if ( trace.fraction > 0.0f ) { + // actually covered some distance + VectorCopy( trace.endpos, pm->ps->origin ); + } + + if ( trace.fraction == 1.0f ) { + break; // moved the entire distance + } + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if ( numplanes >= MAX_CLIP_PLANES ) { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0; i < numplanes; i++ ) { + if ( (double)DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { + VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + VectorCopy( trace.plane.normal, planes[numplanes] ); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0; i < numplanes; i++ ) { + into = DotProduct( pm->ps->velocity, planes[i] ); + if ( (double)into >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) { + pml.impactSpeed = -into; + } + + // slide along the plane + PM_ClipVelocity( pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity( endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0; j < numplanes; j++ ) { + if ( j == i ) { + continue; + } + if ( (double)DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0.0f ) { + continue; + } + + // slide the original velocity along the crease + CrossProduct( planes[i], planes[j], dir ); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct( planes[i], planes[j], dir ); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the new move enters + for ( k = 0; k < numplanes; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( (double)DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a triple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravity ) { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) { + pm->ps->velocity[0] = primal_vel_x; + pm->ps->velocity[1] = primal_vel_y; + pm->ps->velocity[2] = primal_vel_z; + } + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepSlideMove + +QL version: significantly expanded from Q3 with air-step support, +step-jump logic, edge grab, and velocity damping on step-ups. +================== +*/ +void PM_StepSlideMove( qboolean gravity ) { + vec3_t start_o; + float start_vx, start_vy, start_vz; + vec3_t up, down; + vec3_t projected; + vec3_t stepStart, stepEnd; + vec3_t edgeMins, edgeMaxs, edgeEnd; + trace_t trace; + float stepSize; + float delta; + float dotNV; + + VectorCopy( pm->ps->origin, start_o ); + start_vx = pm->ps->velocity[0]; + start_vy = pm->ps->velocity[1]; + start_vz = pm->ps->velocity[2]; + + if ( PM_SlideMove( gravity ) == 0 ) { + return; // we got exactly where we wanted to go first try + } + + // project where we would have gone without obstruction + projected[0] = start_vx * pml.frametime + start_o[0]; + projected[1] = start_vy * pml.frametime + start_o[1]; + projected[2] = pml.frametime * start_vz + start_o[2]; + + if ( pm_airSteps == 0 ) { + // trace forward to projected position, then down to check for ground + pm->trace( &trace, start_o, pm->mins, pm->maxs, projected, + pm->ps->clientNum, pm->tracemask ); + VectorCopy( trace.endpos, projected ); + down[0] = trace.endpos[0]; + down[1] = trace.endpos[1]; + down[2] = trace.endpos[2] - (float)pm_stepHeight; + pm->trace( &trace, projected, pm->mins, pm->maxs, down, + pm->ps->clientNum, pm->tracemask ); + // never step up when you still have up velocity + if ( start_vz > 0.0f ) { + if ( trace.fraction == 1.0f ) { + return; + } + if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { + return; + } + } + } + + // try stepping up + up[0] = start_o[0]; + up[1] = start_o[1]; + up[2] = start_o[2] + (float)pm_stepHeight; + + // test the player position if they were a stepheight higher + pm->trace( &trace, start_o, pm->mins, pm->maxs, up, + pm->ps->clientNum, pm->tracemask ); + if ( trace.allsolid ) { + if ( pm->debugLevel ) { + Com_Printf( "%i:bend can't step\n", c_pmove ); + } + return; // can't step up + } + + stepSize = trace.endpos[2] - start_o[2]; + // try slidemove from this position + VectorCopy( trace.endpos, pm->ps->origin ); + pm->ps->velocity[0] = start_vx; + pm->ps->velocity[1] = start_vy; + pm->ps->velocity[2] = start_vz; + + PM_SlideMove( gravity ); + + // push down the final amount + VectorCopy( pm->ps->origin, down ); + down[2] -= stepSize; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, down, + pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid ) { + VectorCopy( trace.endpos, pm->ps->origin ); + } + if ( trace.fraction < 1.0f ) { + dotNV = DotProduct( trace.plane.normal, pm->ps->velocity ); + if ( dotNV >= 0.0f && fabs( dotNV ) >= 0.001f ) { + // velocity is moving away from the surface, skip clipping + goto skipClip; + } + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + +skipClip: + // trace from start to final position to check if we actually stepped + pm->trace( &trace, start_o, pm->mins, pm->maxs, pm->ps->origin, + pm->ps->clientNum, pm->tracemask ); + if ( trace.fraction < 1.0f ) { + // didn't make it all the way -- use the step move result + delta = pm->ps->origin[2] - start_o[2]; + + if ( delta > 2.0f ) { + pm->field_0dc = delta; + pm->field_0e0 = pm->cmd.serverTime; + } + + // apply velocity damping when stepping up while airborne + if ( !pml.groundPlane && delta > 0.0f && start_vz > 0.0f ) { + float dampFactor = 1.0f - _DAT_003cfe90; + pm->ps->velocity[0] *= dampFactor; + pm->ps->velocity[1] *= dampFactor; + } + + // step jump / edge grab logic + if ( pmove_stepJumpFlag && pm->ps->pm_type == PM_NORMAL + && delta > 0.0f && (int)pm->waterlevel < 2 ) { + if ( PM_CanJump() || ( DAT_003cfe30 && PM_CanEdgeGrab() ) ) { + // check if there is walkable ground at the projected position + stepStart[0] = projected[0]; + stepStart[1] = projected[1]; + stepStart[2] = projected[2] + (float)pm_stepHeight; + stepEnd[0] = projected[0]; + stepEnd[1] = projected[1]; + stepEnd[2] = projected[2] - (float)pm_stepHeight; + pm->trace( &trace, stepStart, pm->mins, pm->maxs, stepEnd, + pm->ps->clientNum, pm->tracemask ); + if ( !trace.startsolid && !trace.allsolid + && trace.plane.normal[2] >= MIN_WALK_NORMAL ) { + if ( PM_CanJump() ) { + // normal step jump + pml.isStepJump = 1; + PM_Jump(); + pml.isStepJump = 0; + } else if ( DAT_003cfe30 && PM_CanEdgeGrab() ) { + // edge grab: check for open air below with shrunk bbox + edgeMins[0] = pm->mins[0] + 1.0f; + edgeMins[1] = pm->mins[1] + 1.0f; + edgeMins[2] = pm->mins[2]; + edgeMaxs[0] = pm->maxs[0] - 1.0f; + edgeMaxs[1] = pm->maxs[1] - 1.0f; + edgeMaxs[2] = pm->maxs[2]; + edgeEnd[0] = pm->ps->origin[0]; + edgeEnd[1] = pm->ps->origin[1]; + edgeEnd[2] = pm->ps->origin[2] - 64.0f; + pm->trace( &trace, pm->ps->origin, edgeMins, edgeMaxs, edgeEnd, + pm->ps->clientNum, pm->tracemask ); + if ( trace.fraction == 1.0f ) { + // over an edge -- do a crouch step jump + pml.isJumppad = 1; + PM_Jump(); + pml.isJumppad = 0; + } + } + } + } + } + + if ( pm->debugLevel ) { + Com_Printf( "%i:stepped %f\n", c_pmove, delta ); + } + } +} diff --git a/code/game/g_main.c b/code/game/g_main.c index 9f60272..b4c8c96 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // #include "g_local.h" +#include "bg_local.h" level_locals_t level; @@ -95,6 +96,42 @@ vmCvar_t g_enableBreath; vmCvar_t g_proxMineTimeout; #endif +// QL pmove cvars +vmCvar_t pmove_JumpVelocity; +vmCvar_t pmove_JumpVelocityMax; +vmCvar_t pmove_JumpVelocityScaleAdd; +vmCvar_t pmove_JumpVelocityTimeThreshold; +vmCvar_t pmove_JumpVelocityTimeThresholdOffset; +vmCvar_t pmove_JumpTimeDeltaMin; +vmCvar_t pmove_ChainJump; +vmCvar_t pmove_ChainJumpVelocity; +vmCvar_t pmove_StepJumpVelocity; +vmCvar_t pmove_RampJumpScale; +vmCvar_t pmove_StepHeight; +vmCvar_t pmove_WalkAccel; +vmCvar_t pmove_WalkFriction; +vmCvar_t pmove_StrafeAccel; +vmCvar_t pmove_CircleStrafeFriction; +vmCvar_t pmove_CrouchSlideFriction; +vmCvar_t pmove_CrouchSlideTime; +vmCvar_t pmove_WaterSwimScale; +vmCvar_t pmove_WaterWadeScale; +vmCvar_t pmove_WeaponDropTime; +vmCvar_t pmove_WeaponRaiseTime; +vmCvar_t pmove_WishSpeed; +vmCvar_t pmove_AirSteps; +vmCvar_t pmove_AirAccel; +vmCvar_t pmove_AirStopAccel; +vmCvar_t pmove_AirControl; +vmCvar_t pmove_AutoHop; +vmCvar_t pmove_BunnyHop; +vmCvar_t pmove_StepJump; +vmCvar_t pmove_CrouchStepJump; +vmCvar_t pmove_RampJump; +vmCvar_t pmove_DoubleJump; +vmCvar_t pmove_CrouchSlide; +vmCvar_t pmove_noPlayerClip; + // bk001129 - made static to avoid aliasing static cvarTable_t gameCvarTable[] = { // don't override the cheat state set by the system @@ -178,8 +215,43 @@ static cvarTable_t gameCvarTable[] = { { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse}, { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, - { &g_rankings, "g_rankings", "0", 0, 0, qfalse} + { &g_rankings, "g_rankings", "0", 0, 0, qfalse}, + // QL pmove cvars + { &pmove_JumpVelocity, "pmove_JumpVelocity", "270", 0, 0, qfalse }, + { &pmove_JumpVelocityMax, "pmove_JumpVelocityMax", "700", 0, 0, qfalse }, + { &pmove_JumpVelocityScaleAdd, "pmove_JumpVelocityScaleAdd", "0", 0, 0, qfalse }, + { &pmove_JumpVelocityTimeThreshold, "pmove_JumpVelocityTimeThreshold", "500", 0, 0, qfalse }, + { &pmove_JumpVelocityTimeThresholdOffset, "pmove_JumpVelocityTimeThresholdOffset", "0.5", 0, 0, qfalse }, + { &pmove_JumpTimeDeltaMin, "pmove_JumpTimeDeltaMin", "100", 0, 0, qfalse }, + { &pmove_ChainJump, "pmove_ChainJump", "1", 0, 0, qfalse }, + { &pmove_ChainJumpVelocity, "pmove_ChainJumpVelocity", "110", 0, 0, qfalse }, + { &pmove_StepJumpVelocity, "pmove_StepJumpVelocity", "48", 0, 0, qfalse }, + { &pmove_RampJumpScale, "pmove_RampJumpScale", "1", 0, 0, qfalse }, + { &pmove_StepHeight, "pmove_StepHeight", "22", 0, 0, qfalse }, + { &pmove_WalkAccel, "pmove_WalkAccel", "10", 0, 0, qfalse }, + { &pmove_WalkFriction, "pmove_WalkFriction", "6", 0, 0, qfalse }, + { &pmove_StrafeAccel, "pmove_StrafeAccel", "1", 0, 0, qfalse }, + { &pmove_CircleStrafeFriction, "pmove_CircleStrafeFriction", "6", 0, 0, qfalse }, + { &pmove_CrouchSlideFriction, "pmove_CrouchSlideFriction", "0.5", 0, 0, qfalse }, + { &pmove_CrouchSlideTime, "pmove_CrouchSlideTime", "2", 0, 0, qfalse }, + { &pmove_WaterSwimScale, "pmove_WaterSwimScale", "0.5", 0, 0, qfalse }, + { &pmove_WaterWadeScale, "pmove_WaterWadeScale", "0.7", 0, 0, qfalse }, + { &pmove_WeaponDropTime, "pmove_WeaponDropTime", "200", 0, 0, qfalse }, + { &pmove_WeaponRaiseTime, "pmove_WeaponRaiseTime", "250", 0, 0, qfalse }, + { &pmove_WishSpeed, "pmove_WishSpeed", "400", 0, 0, qfalse }, + { &pmove_AirSteps, "pmove_AirSteps", "1", 0, 0, qfalse }, + { &pmove_AirAccel, "pmove_AirAccel", "1", 0, 0, qfalse }, + { &pmove_AirStopAccel, "pmove_AirStopAccel", "1", 0, 0, qfalse }, + { &pmove_AirControl, "pmove_AirControl", "0", 0, 0, qfalse }, + { &pmove_AutoHop, "pmove_AutoHop", "1", 0, 0, qfalse }, + { &pmove_BunnyHop, "pmove_BunnyHop", "1", 0, 0, qfalse }, + { &pmove_StepJump, "pmove_StepJump", "1", 0, 0, qfalse }, + { &pmove_CrouchStepJump, "pmove_CrouchStepJump", "1", 0, 0, qfalse }, + { &pmove_RampJump, "pmove_RampJump", "0", 0, 0, qfalse }, + { &pmove_DoubleJump, "pmove_DoubleJump", "1", 0, 0, qfalse }, + { &pmove_CrouchSlide, "pmove_CrouchSlide", "0", 0, 0, qfalse }, + { &pmove_noPlayerClip, "pmove_noPlayerClip", "0", 0, 0, qfalse }, }; // bk001129 - made static to avoid aliasing @@ -397,6 +469,42 @@ void G_UpdateCvars( void ) { if (remapped) { G_RemapTeamShaders(); } + + // sync pmove cvar globals + pm_jumpVelocity = pmove_JumpVelocity.value; + pm_jumpVelocityMax = pmove_JumpVelocityMax.value; + pm_jumpVelocityScaleAdd = pmove_JumpVelocityScaleAdd.value; + pm_jumpVelocityTimeThreshold = pmove_JumpVelocityTimeThreshold.value; + pm_jumpVelocityTimeThresholdOffset = pmove_JumpVelocityTimeThresholdOffset.value; + pm_jumpTimeDeltaMin = pmove_JumpTimeDeltaMin.value; + pm_chainJump = pmove_ChainJump.integer; + pm_chainJumpVelocity = pmove_ChainJumpVelocity.value; + pm_stepJumpVelocity = pmove_StepJumpVelocity.value; + pm_rampJumpScale = pmove_RampJumpScale.value; + pm_stepHeight = pmove_StepHeight.value; + pm_walkAccel = pmove_WalkAccel.value; + pm_walkFriction = pmove_WalkFriction.value; + pm_strafeAccel = pmove_StrafeAccel.value; + pm_circleStrafeFriction = pmove_CircleStrafeFriction.value; + pm_crouchSlideFriction = pmove_CrouchSlideFriction.value; + pm_crouchSlideTime = pmove_CrouchSlideTime.integer; + pm_waterSwimScale = pmove_WaterSwimScale.value; + pm_waterWadeScale = pmove_WaterWadeScale.value; + pm_weaponDropTime = pmove_WeaponDropTime.integer; + pm_weaponRaiseTime = pmove_WeaponRaiseTime.integer; + pm_wishSpeed = pmove_WishSpeed.value; + pm_airSteps = pmove_AirSteps.integer; + pm_airAccel = pmove_AirAccel.value; + pm_airStopAccel = pmove_AirStopAccel.value; + pm_airControl = pmove_AirControl.value; + pm_autoHopEnabled = pmove_AutoHop.integer; + pm_bunnyHop = pmove_BunnyHop.integer; + pm_stepJumpEnabled = pmove_StepJump.integer; + pm_crouchStepJump = pmove_CrouchStepJump.integer; + pm_rampJumpEnabled = pmove_RampJump.integer; + pm_noPlayerClip = pmove_noPlayerClip.integer; + pmove_rampJumpFlag = pmove_RampJump.integer; + pmove_stepJumpFlag = pmove_StepJump.integer; } /* diff --git a/code/game/q_shared.h b/code/game/q_shared.h index eed5689..03f73b5 100644 --- a/code/game/q_shared.h +++ b/code/game/q_shared.h @@ -1186,8 +1186,14 @@ typedef struct playerState_s { int externalEventTime; int clientNum; // ranges from 0 to MAX_CLIENTS-1 + + // QL additions: 3 new fields inserted here (shifts weapon onward +12 bytes) + int field_08c; // team entity reference (team gametypes only) + int weapon; // copied to entityState_t->weapon + int field_094; // cmd.field_15 copy (weapon-like index, 1-14) int weaponstate; + int field_09c; // cmd.field_16 copy (index, 10-136) vec3_t viewangles; // for fixed views int viewheight; @@ -1207,7 +1213,17 @@ typedef struct playerState_s { int loopSound; int jumppad_ent; // jumppad entity hit this frame + int lastJumpTime; // QL: last jump timestamp for chain jump timing + int jumped; // QL: double jump consumed flag + int field_1d4; // QL: crouch start time + int crouchSlideTimer; // QL: crouch slide charge/decay timer + // not communicated over the net at all + signed char forwardmove; // QL: copied from cmd each frame + signed char rightmove; + signed char upmove; + byte pad_1df; + int ping; // server to game info for scoreboard int pmove_framecount; // FIXME: don't transmit over the network int jumppad_frame; @@ -1249,8 +1265,12 @@ typedef struct usercmd_s { int serverTime; int angles[3]; int buttons; - byte weapon; // weapon - signed char forwardmove, rightmove, upmove; + byte weapon; + byte field_15; // QL: weapon-like index (1-14), copied to ps->field_094 + byte field_16; // QL: index (10-136), copied to ps->field_09c + signed char forwardmove, rightmove, upmove; + byte pad_1a; + byte pad_1b; } usercmd_t; //=================================================================== diff --git a/code/qcommon/msg.c b/code/qcommon/msg.c index 27891cc..739e3ad 100644 --- a/code/qcommon/msg.c +++ b/code/qcommon/msg.c @@ -655,6 +655,8 @@ void MSG_WriteDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { MSG_WriteDelta( msg, from->upmove, to->upmove, 8 ); MSG_WriteDelta( msg, from->buttons, to->buttons, 16 ); MSG_WriteDelta( msg, from->weapon, to->weapon, 8 ); + MSG_WriteDelta( msg, from->field_15, to->field_15, 8 ); + MSG_WriteDelta( msg, from->field_16, to->field_16, 8 ); } @@ -677,6 +679,8 @@ void MSG_ReadDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { to->upmove = MSG_ReadDelta( msg, from->upmove, 8); to->buttons = MSG_ReadDelta( msg, from->buttons, 16); to->weapon = MSG_ReadDelta( msg, from->weapon, 8); + to->field_15 = MSG_ReadDelta( msg, from->field_15, 8); + to->field_16 = MSG_ReadDelta( msg, from->field_16, 8); } /* @@ -699,7 +703,9 @@ void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t * from->rightmove == to->rightmove && from->upmove == to->upmove && from->buttons == to->buttons && - from->weapon == to->weapon) { + from->weapon == to->weapon && + from->field_15 == to->field_15 && + from->field_16 == to->field_16) { MSG_WriteBits( msg, 0, 1 ); // no change oldsize += 7; return; @@ -714,6 +720,8 @@ void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t * MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 ); MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 ); MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 ); + MSG_WriteDeltaKey( msg, key, from->field_15, to->field_15, 8 ); + MSG_WriteDeltaKey( msg, key, from->field_16, to->field_16, 8 ); } @@ -738,6 +746,8 @@ void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *t to->upmove = MSG_ReadDeltaKey( msg, key, from->upmove, 8); to->buttons = MSG_ReadDeltaKey( msg, key, from->buttons, 16); to->weapon = MSG_ReadDeltaKey( msg, key, from->weapon, 8); + to->field_15 = MSG_ReadDeltaKey( msg, key, from->field_15, 8); + to->field_16 = MSG_ReadDeltaKey( msg, key, from->field_16, 8); } else { to->angles[0] = from->angles[0]; to->angles[1] = from->angles[1]; @@ -747,6 +757,8 @@ void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *t to->upmove = from->upmove; to->buttons = from->buttons; to->weapon = from->weapon; + to->field_15 = from->field_15; + to->field_16 = from->field_16; } } @@ -1119,7 +1131,7 @@ netField_t playerStateFields[] = { PSF(events[0]), 8 }, { PSF(legsAnim), 8 }, { PSF(events[1]), 8 }, -{ PSF(pm_flags), 16 }, +{ PSF(pm_flags), 21 }, { PSF(groundEntityNum), GENTITYNUM_BITS }, { PSF(weaponstate), 4 }, { PSF(eFlags), 16 }, @@ -1147,7 +1159,18 @@ netField_t playerStateFields[] = { PSF(grapplePoint[1]), 0 }, { PSF(grapplePoint[2]), 0 }, { PSF(jumppad_ent), 10 }, -{ PSF(loopSound), 16 } +{ PSF(loopSound), 16 }, +// QL additions +{ PSF(field_08c), 32 }, +{ PSF(field_094), 8 }, +{ PSF(field_09c), 8 }, +{ PSF(lastJumpTime), 32 }, +{ PSF(jumped), 1 }, +{ PSF(field_1d4), 32 }, +{ PSF(crouchSlideTimer), 32 }, +{ PSF(forwardmove), 8 }, +{ PSF(rightmove), 8 }, +{ PSF(upmove), 8 }, }; /*