1
0
Fork 0
forked from fte/fteqw
fteqw/engine/common/q2pmove.c
Shpoike d781018df3 Add all the junk for network compat with Q2E.
Defaults to using Q2E's protocol 2023 (but not netchan).
FTEQ2 servers can host both vanilla and Q2E clients simultaneously, but its recommend to use the vanilla gamecode to avoid localisation issues.
2024-07-14 19:58:24 +01:00

1370 lines
28 KiB
C

/*
Copyright (C) 1997-2001 Id Software, Inc.
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "quakedef.h"
float pm_q2stepheight = PM_DEFAULTSTEPHEIGHT;
#if defined(Q2CLIENT) || defined(Q2SERVER)
#define Q2PMF_DUCKED 1
#define Q2PMF_JUMP_HELD 2
#define Q2PMF_ON_GROUND 4
#define Q2PMF_TIME_WATERJUMP 8 // pm_time is waterjump
#define Q2PMF_TIME_LAND 16 // pm_time is time before rejump
#define Q2PMF_TIME_TELEPORT 32 // pm_time is non-moving time
#define Q2PMF_NO_PREDICTION 64 // temporarily disables prediction (used for grappling hook)
// 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 origin; // full float precision
vec3_t velocity; // full float precision
vec3_t forward, right, up;
float frametime;
const q2csurface_t *groundsurface;
cplane_t groundplane;
int groundcontents;
vec3_t previous_origin;
qboolean ladder;
} q2pml_t;
static q2pmove_t *q2pm;
static q2pml_t q2pml;
// movement parameters
static float pm_stopspeed = 100;
static float pm_maxspeed = 300;
static float pm_duckspeed = 100;
static float pm_accelerate = 10;
float pm_airaccelerate = 0;
static float pm_wateraccelerate = 10;
static float pm_friction = 6;
static float pm_waterfriction = 1;
static float pm_waterspeed = 400;
//float pm_stepheight;
/*
walking up a step should kill some velocity
*/
/*
==================
PMQ2_ClipVelocity
Slide off of the impacting object
returns the blocked flags (1 = floor, 2 = step / wall)
==================
*/
#define STOP_EPSILON 0.1
void PMQ2_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
{
float backoff;
float change;
int i;
backoff = DotProduct (in, normal) * overbounce;
for (i=0 ; i<3 ; i++)
{
change = normal[i]*backoff;
out[i] = in[i] - change;
if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
out[i] = 0;
}
}
/*
==================
PMQ2_StepSlideMove
Each intersection will try to step over the obstruction instead of
sliding along it.
Returns a new origin, velocity, and contact entity
Does not modify any world state?
==================
*/
#define MIN_STEP_NORMAL 0.7 // can't step up onto very steep slopes
#define MAX_CLIP_PLANES 5
void PMQ2_StepSlideMove_ (void)
{
int bumpcount, numbumps;
vec3_t dir;
float d;
int numplanes;
vec3_t planes[MAX_CLIP_PLANES];
vec3_t primal_velocity;
int i, j;
q2trace_t trace;
vec3_t end;
float time_left;
numbumps = 4;
VectorCopy (q2pml.velocity, primal_velocity);
numplanes = 0;
time_left = q2pml.frametime;
for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
{
for (i=0 ; i<3 ; i++)
end[i] = q2pml.origin[i] + time_left * q2pml.velocity[i];
trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, end);
if (trace.allsolid)
{ // entity is trapped in another solid
q2pml.velocity[2] = 0; // don't build up falling damage
return;
}
if (trace.fraction > 0)
{ // actually covered some distance
VectorCopy (trace.endpos, q2pml.origin);
numplanes = 0;
}
if (trace.fraction == 1)
break; // moved the entire distance
// save entity for contact
if (q2pm->numtouch < MAXTOUCH && trace.ent)
{
q2pm->touchents[q2pm->numtouch] = trace.ent;
q2pm->numtouch++;
}
time_left -= time_left * trace.fraction;
// slide along this plane
if (numplanes >= MAX_CLIP_PLANES)
{ // this shouldn't really happen
VectorClear (q2pml.velocity);
break;
}
VectorCopy (trace.plane.normal, planes[numplanes]);
numplanes++;
#if 0
float rub;
//
// modify velocity so it parallels all of the clip planes
//
if (numplanes == 1)
{ // go along this plane
VectorCopy (q2pml.velocity, dir);
VectorNormalize (dir);
rub = 1.0 + 0.5 * DotProduct (dir, planes[0]);
// slide along the plane
PMQ2_ClipVelocity (q2pml.velocity, planes[0], q2pml.velocity, 1.01);
// rub some extra speed off on xy axis
// not on Z, or you can scrub down walls
q2pml.velocity[0] *= rub;
q2pml.velocity[1] *= rub;
q2pml.velocity[2] *= rub;
}
else if (numplanes == 2)
{ // go along the crease
VectorCopy (q2pml.velocity, dir);
VectorNormalize (dir);
rub = 1.0 + 0.5 * DotProduct (dir, planes[0]);
// slide along the plane
CrossProduct (planes[0], planes[1], dir);
d = DotProduct (dir, q2pml.velocity);
VectorScale (dir, d, q2pml.velocity);
// rub some extra speed off
VectorScale (q2pml.velocity, rub, q2pml.velocity);
}
else
{
// Con_Printf ("clip velocity, numplanes == %i\n",numplanes);
VectorClear (q2pml.velocity);
break;
}
#else
//
// modify original_velocity so it parallels all of the clip planes
//
for (i=0 ; i<numplanes ; i++)
{
PMQ2_ClipVelocity (q2pml.velocity, planes[i], q2pml.velocity, 1.01);
for (j=0 ; j<numplanes ; j++)
if (j != i)
{
if (DotProduct (q2pml.velocity, planes[j]) < 0)
break; // not ok
}
if (j == numplanes)
break;
}
if (i != numplanes)
{ // go along this plane
}
else
{ // go along the crease
if (numplanes != 2)
{
// Con_Printf ("clip velocity, numplanes == %i\n",numplanes);
VectorClear (q2pml.velocity);
break;
}
CrossProduct (planes[0], planes[1], dir);
d = DotProduct (dir, q2pml.velocity);
VectorScale (dir, d, q2pml.velocity);
}
#endif
//
// if velocity is against the original velocity, stop dead
// to avoid tiny occilations in sloping corners
//
if (DotProduct (q2pml.velocity, primal_velocity) <= 0)
{
VectorClear (q2pml.velocity);
break;
}
}
if (q2pm->s.pm_time)
{
VectorCopy (primal_velocity, q2pml.velocity);
}
}
/*
==================
PMQ2_StepSlideMove
==================
*/
void PMQ2_StepSlideMove (void)
{
vec3_t start_o, start_v;
vec3_t down_o, down_v;
q2trace_t trace;
float down_dist, up_dist;
// vec3_t delta;
vec3_t up, down;
VectorCopy (q2pml.origin, start_o);
VectorCopy (q2pml.velocity, start_v);
PMQ2_StepSlideMove_ ();
VectorCopy (q2pml.origin, down_o);
VectorCopy (q2pml.velocity, down_v);
VectorCopy (start_o, up);
up[2] += pm_q2stepheight;
trace = q2pm->trace (up, q2pm->mins, q2pm->maxs, up);
if (trace.allsolid)
return; // can't step up
// try sliding above
VectorCopy (up, q2pml.origin);
VectorCopy (start_v, q2pml.velocity);
PMQ2_StepSlideMove_ ();
// push down the final amount
VectorCopy (q2pml.origin, down);
down[2] -= pm_q2stepheight;
trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, down);
if (!trace.allsolid)
{
VectorCopy (trace.endpos, q2pml.origin);
}
#if 0
VectorSubtract (q2pml.origin, up, delta);
up_dist = DotProduct (delta, start_v);
VectorSubtract (down_o, start_o, delta);
down_dist = DotProduct (delta, start_v);
#else
VectorCopy(q2pml.origin, up);
// decide which one went farther
down_dist = (down_o[0] - start_o[0])*(down_o[0] - start_o[0])
+ (down_o[1] - start_o[1])*(down_o[1] - start_o[1]);
up_dist = (up[0] - start_o[0])*(up[0] - start_o[0])
+ (up[1] - start_o[1])*(up[1] - start_o[1]);
#endif
if (down_dist > up_dist || trace.plane.normal[2] < MIN_STEP_NORMAL)
{
VectorCopy (down_o, q2pml.origin);
VectorCopy (down_v, q2pml.velocity);
return;
}
//!! Special case
// if we were walking along a plane, then we need to copy the Z over
q2pml.velocity[2] = down_v[2];
}
/*
==================
PMQ2_Friction
Handles both ground friction and water friction
==================
*/
void PMQ2_Friction (void)
{
float *vel;
float speed, newspeed, control;
float friction;
float drop;
vel = q2pml.velocity;
speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]);
if (speed < 1)
{
vel[0] = 0;
vel[1] = 0;
return;
}
drop = 0;
// apply ground friction
if ((q2pm->groundentity && q2pml.groundsurface && !(q2pml.groundsurface->flags & TI_SLICK) ) || (q2pml.ladder) )
{
friction = pm_friction;
control = speed < pm_stopspeed ? pm_stopspeed : speed;
drop += control*friction*q2pml.frametime;
}
// apply water friction
if (q2pm->waterlevel && !q2pml.ladder)
drop += speed*pm_waterfriction*q2pm->waterlevel*q2pml.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;
}
/*
==============
PMQ2_Accelerate
Handles user intended acceleration
==============
*/
void PMQ2_Accelerate (vec3_t wishdir, float wishspeed, float accel)
{
int i;
float addspeed, accelspeed, currentspeed;
currentspeed = DotProduct (q2pml.velocity, wishdir);
addspeed = wishspeed - currentspeed;
if (addspeed <= 0)
return;
accelspeed = accel*q2pml.frametime*wishspeed;
if (accelspeed > addspeed)
accelspeed = addspeed;
for (i=0 ; i<3 ; i++)
q2pml.velocity[i] += accelspeed*wishdir[i];
}
void PMQ2_AirAccelerate (vec3_t wishdir, float wishspeed, float accel)
{
int i;
float addspeed, accelspeed, currentspeed, wishspd = wishspeed;
if (wishspd > 30)
wishspd = 30;
currentspeed = DotProduct (q2pml.velocity, wishdir);
addspeed = wishspd - currentspeed;
if (addspeed <= 0)
return;
accelspeed = accel * wishspeed * q2pml.frametime;
if (accelspeed > addspeed)
accelspeed = addspeed;
for (i=0 ; i<3 ; i++)
q2pml.velocity[i] += accelspeed*wishdir[i];
}
/*
=============
PMQ2_AddCurrents
=============
*/
void PMQ2_AddCurrents (vec3_t wishvel)
{
vec3_t v;
float s;
//
// account for ladders
//
if (q2pml.ladder && fabs(q2pml.velocity[2]) <= 200)
{
if ((q2pm->viewangles[PITCH] <= -15) && (q2pm->cmd.forwardmove > 0))
wishvel[2] = 200;
else if ((q2pm->viewangles[PITCH] >= 15) && (q2pm->cmd.forwardmove > 0))
wishvel[2] = -200;
else if (q2pm->cmd.upmove > 0)
wishvel[2] = 200;
else if (q2pm->cmd.upmove < 0)
wishvel[2] = -200;
else
wishvel[2] = 0;
// limit horizontal speed when on a ladder
if (wishvel[0] < -25)
wishvel[0] = -25;
else if (wishvel[0] > 25)
wishvel[0] = 25;
if (wishvel[1] < -25)
wishvel[1] = -25;
else if (wishvel[1] > 25)
wishvel[1] = 25;
}
//
// add water currents
//
if (q2pm->watertype & Q2MASK_CURRENT) /*FIXME: q3bsp*/
{
memset(v, 0, sizeof(vec3_t));
if (q2pm->watertype & Q2CONTENTS_CURRENT_0)
v[0] += 1;
if (q2pm->watertype & Q2CONTENTS_CURRENT_90)
v[1] += 1;
if (q2pm->watertype & Q2CONTENTS_CURRENT_180)
v[0] -= 1;
if (q2pm->watertype & Q2CONTENTS_CURRENT_270)
v[1] -= 1;
if (q2pm->watertype & Q2CONTENTS_CURRENT_UP)
v[2] += 1;
if (q2pm->watertype & Q2CONTENTS_CURRENT_DOWN)
v[2] -= 1;
s = pm_waterspeed;
if ((q2pm->waterlevel == 1) && (q2pm->groundentity))
s /= 2;
VectorMA (wishvel, s, v, wishvel);
}
//
// add conveyor belt velocities
//
if (q2pm->groundentity)
{
memset(v, 0, sizeof(vec3_t));
if (q2pml.groundcontents & Q2CONTENTS_CURRENT_0)
v[0] += 1;
if (q2pml.groundcontents & Q2CONTENTS_CURRENT_90)
v[1] += 1;
if (q2pml.groundcontents & Q2CONTENTS_CURRENT_180)
v[0] -= 1;
if (q2pml.groundcontents & Q2CONTENTS_CURRENT_270)
v[1] -= 1;
if (q2pml.groundcontents & Q2CONTENTS_CURRENT_UP)
v[2] += 1;
if (q2pml.groundcontents & Q2CONTENTS_CURRENT_DOWN)
v[2] -= 1;
VectorMA (wishvel, 100 /* q2pm->groundentity->speed */, v, wishvel);
}
}
/*
===================
PMQ2_WaterMove
===================
*/
void PMQ2_WaterMove (void)
{
int i;
vec3_t wishvel;
float wishspeed;
vec3_t wishdir;
//
// user intentions
//
for (i=0 ; i<3 ; i++)
wishvel[i] = q2pml.forward[i]*q2pm->cmd.forwardmove + q2pml.right[i]*q2pm->cmd.sidemove;
if (!q2pm->cmd.forwardmove && !q2pm->cmd.sidemove && !q2pm->cmd.upmove)
wishvel[2] -= 60; // drift towards bottom
else
wishvel[2] += q2pm->cmd.upmove;
PMQ2_AddCurrents (wishvel);
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
if (wishspeed > pm_maxspeed)
{
VectorScale (wishvel, pm_maxspeed/wishspeed, wishvel);
wishspeed = pm_maxspeed;
}
wishspeed *= 0.5;
PMQ2_Accelerate (wishdir, wishspeed, pm_wateraccelerate);
PMQ2_StepSlideMove ();
}
/*
===================
PMQ2_AirMove
===================
*/
void PMQ2_AirMove (void)
{
int i;
vec3_t wishvel;
float fmove, smove;
vec3_t wishdir;
float wishspeed;
float maxspeed;
fmove = q2pm->cmd.forwardmove;
smove = q2pm->cmd.sidemove;
//!!!!! pitch should be 1/3 so this isn't needed??!
#if 0
q2pml.forward[2] = 0;
q2pml.right[2] = 0;
VectorNormalize (q2pml.forward);
VectorNormalize (q2pml.right);
#endif
for (i=0 ; i<2 ; i++)
wishvel[i] = q2pml.forward[i]*fmove + q2pml.right[i]*smove;
wishvel[2] = 0;
PMQ2_AddCurrents (wishvel);
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
//
// clamp to server defined max speed
//
maxspeed = (q2pm->s.pm_flags & Q2PMF_DUCKED) ? pm_duckspeed : pm_maxspeed;
if (wishspeed > maxspeed)
{
VectorScale (wishvel, maxspeed/wishspeed, wishvel);
wishspeed = maxspeed;
}
if ( q2pml.ladder )
{
PMQ2_Accelerate (wishdir, wishspeed, pm_accelerate);
if (!wishvel[2])
{
if (q2pml.velocity[2] > 0)
{
q2pml.velocity[2] -= q2pm->s.gravity * q2pml.frametime;
if (q2pml.velocity[2] < 0)
q2pml.velocity[2] = 0;
}
else
{
q2pml.velocity[2] += q2pm->s.gravity * q2pml.frametime;
if (q2pml.velocity[2] > 0)
q2pml.velocity[2] = 0;
}
}
PMQ2_StepSlideMove ();
}
else if ( q2pm->groundentity )
{ // walking on ground
q2pml.velocity[2] = 0; //!!! this is before the accel
PMQ2_Accelerate (wishdir, wishspeed, pm_accelerate);
// PGM -- fix for negative trigger_gravity fields
// q2pml.velocity[2] = 0;
if(q2pm->s.gravity > 0)
q2pml.velocity[2] = 0;
else
q2pml.velocity[2] -= q2pm->s.gravity * q2pml.frametime;
// PGM
if (!q2pml.velocity[0] && !q2pml.velocity[1])
return;
PMQ2_StepSlideMove ();
}
else
{ // not on ground, so little effect on velocity
if (pm_airaccelerate)
PMQ2_AirAccelerate (wishdir, wishspeed, pm_accelerate);
else
PMQ2_Accelerate (wishdir, wishspeed, 1);
// add gravity
q2pml.velocity[2] -= q2pm->s.gravity * q2pml.frametime;
PMQ2_StepSlideMove ();
}
}
/*
=============
PMQ2_CatagorizePosition
=============
*/
void PMQ2_CatagorizePosition (void)
{
vec3_t point;
int cont;
q2trace_t trace;
int sample1;
int sample2;
// if the player hull point one unit down is solid, the player
// is on ground
// see if standing on something solid
point[0] = q2pml.origin[0];
point[1] = q2pml.origin[1];
point[2] = q2pml.origin[2] - 0.25;
if (q2pml.velocity[2] > 180) //!!ZOID changed from 100 to 180 (ramp accel)
{
q2pm->s.pm_flags &= ~Q2PMF_ON_GROUND;
q2pm->groundentity = NULL;
}
else
{
trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, point);
q2pml.groundplane = trace.plane;
q2pml.groundsurface = trace.surface;
q2pml.groundcontents = trace.contents;
if (!trace.ent || (trace.plane.normal[2] < 0.7 && !trace.startsolid) )
{
q2pm->groundentity = NULL;
q2pm->s.pm_flags &= ~Q2PMF_ON_GROUND;
}
else
{
q2pm->groundentity = trace.ent;
// hitting solid ground will end a waterjump
if (q2pm->s.pm_flags & Q2PMF_TIME_WATERJUMP)
{
q2pm->s.pm_flags &= ~(Q2PMF_TIME_WATERJUMP | Q2PMF_TIME_LAND | Q2PMF_TIME_TELEPORT);
q2pm->s.pm_time = 0;
}
if (! (q2pm->s.pm_flags & Q2PMF_ON_GROUND) )
{ // just hit the ground
q2pm->s.pm_flags |= Q2PMF_ON_GROUND;
// don't do landing time if we were just going down a slope
if (q2pml.velocity[2] < -200)
{
q2pm->s.pm_flags |= Q2PMF_TIME_LAND;
// don't allow another jump for a little while
if (q2pml.velocity[2] < -400)
q2pm->s.pm_time = 25;
else
q2pm->s.pm_time = 18;
}
}
}
#if 0
if (trace.fraction < 1.0 && trace.ent && q2pml.velocity[2] < 0)
q2pml.velocity[2] = 0;
#endif
if (q2pm->numtouch < MAXTOUCH && trace.ent)
{
q2pm->touchents[q2pm->numtouch] = trace.ent;
q2pm->numtouch++;
}
}
//
// get waterlevel, accounting for ducking
//
q2pm->waterlevel = 0;
q2pm->watertype = 0;
sample2 = q2pm->viewheight - q2pm->mins[2];
sample1 = sample2 / 2;
point[2] = q2pml.origin[2] + q2pm->mins[2] + 1;
cont = q2pm->pointcontents (point);
if (cont & MASK_WATER)
{
q2pm->watertype = cont;
q2pm->waterlevel = 1;
point[2] = q2pml.origin[2] + q2pm->mins[2] + sample1;
cont = q2pm->pointcontents (point);
if (cont & MASK_WATER)
{
q2pm->waterlevel = 2;
point[2] = q2pml.origin[2] + q2pm->mins[2] + sample2;
cont = q2pm->pointcontents (point);
if (cont & MASK_WATER)
q2pm->waterlevel = 3;
}
}
}
/*
=============
PMQ2_CheckJump
=============
*/
void PMQ2_CheckJump (void)
{
if (q2pm->s.pm_flags & Q2PMF_TIME_LAND)
{ // hasn't been long enough since landing to jump again
return;
}
if (q2pm->cmd.upmove < 10)
{ // not holding jump
q2pm->s.pm_flags &= ~Q2PMF_JUMP_HELD;
return;
}
// must wait for jump to be released
if (q2pm->s.pm_flags & Q2PMF_JUMP_HELD)
return;
if (q2pm->s.pm_type == Q2PM_DEAD)
return;
if (q2pm->waterlevel >= 2)
{ // swimming, not jumping
q2pm->groundentity = NULL;
if (q2pml.velocity[2] <= -300)
return;
if (q2pm->watertype == Q2CONTENTS_WATER)
q2pml.velocity[2] = 100;
else if (q2pm->watertype == Q2CONTENTS_SLIME)
q2pml.velocity[2] = 80;
else
q2pml.velocity[2] = 50;
return;
}
if (q2pm->groundentity == NULL)
return; // in air, so no effect
q2pm->s.pm_flags |= Q2PMF_JUMP_HELD;
q2pm->groundentity = NULL;
q2pml.velocity[2] += 270;
if (q2pml.velocity[2] < 270)
q2pml.velocity[2] = 270;
}
/*
=============
PMQ2_CheckSpecialMovement
=============
*/
void PMQ2_CheckSpecialMovement (void)
{
vec3_t spot;
int cont;
vec3_t flatforward;
q2trace_t trace;
if (q2pm->s.pm_time)
return;
q2pml.ladder = false;
// check for ladder
flatforward[0] = q2pml.forward[0];
flatforward[1] = q2pml.forward[1];
flatforward[2] = 0;
VectorNormalize (flatforward);
VectorMA (q2pml.origin, 1, flatforward, spot);
trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, spot);
if ((trace.fraction < 1) && (trace.contents & Q2CONTENTS_LADDER))
q2pml.ladder = true;
// check for water jump
if (q2pm->waterlevel != 2)
return;
VectorMA (q2pml.origin, 30, flatforward, spot);
spot[2] += 4;
cont = q2pm->pointcontents (spot);
if (!(cont & Q2CONTENTS_SOLID))
return;
spot[2] += 16;
cont = q2pm->pointcontents (spot);
if (cont)
return;
// jump out of water
VectorScale (flatforward, 50, q2pml.velocity);
q2pml.velocity[2] = 350;
q2pm->s.pm_flags |= Q2PMF_TIME_WATERJUMP;
q2pm->s.pm_time = 255;
}
/*
===============
PMQ2_FlyMove
===============
*/
void PMQ2_FlyMove (qboolean doclip)
{
float speed, drop, friction, control, newspeed;
float currentspeed, addspeed, accelspeed;
int i;
vec3_t wishvel;
float fmove, smove;
vec3_t wishdir;
float wishspeed;
vec3_t end;
q2trace_t trace;
q2pm->viewheight = DEFAULT_VIEWHEIGHT;
// friction
speed = Length (q2pml.velocity);
if (speed < 1)
{
VectorClear (q2pml.velocity);
}
else
{
drop = 0;
friction = pm_friction*1.5; // extra friction
control = speed < pm_stopspeed ? pm_stopspeed : speed;
drop += control*friction*q2pml.frametime;
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0)
newspeed = 0;
newspeed /= speed;
VectorScale (q2pml.velocity, newspeed, q2pml.velocity);
}
// accelerate
fmove = q2pm->cmd.forwardmove;
smove = q2pm->cmd.sidemove;
VectorNormalize (q2pml.forward);
VectorNormalize (q2pml.right);
for (i=0 ; i<3 ; i++)
wishvel[i] = q2pml.forward[i]*fmove + q2pml.right[i]*smove;
wishvel[2] += q2pm->cmd.upmove;
VectorCopy (wishvel, wishdir);
wishspeed = VectorNormalize(wishdir);
//
// clamp to server defined max speed
//
if (wishspeed > pm_maxspeed)
{
VectorScale (wishvel, pm_maxspeed/wishspeed, wishvel);
wishspeed = pm_maxspeed;
}
currentspeed = DotProduct(q2pml.velocity, wishdir);
addspeed = wishspeed - currentspeed;
if (addspeed <= 0)
return;
accelspeed = pm_accelerate*q2pml.frametime*wishspeed;
if (accelspeed > addspeed)
accelspeed = addspeed;
for (i=0 ; i<3 ; i++)
q2pml.velocity[i] += accelspeed*wishdir[i];
if (doclip) {
for (i=0 ; i<3 ; i++)
end[i] = q2pml.origin[i] + q2pml.frametime * q2pml.velocity[i];
trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, end);
VectorCopy (trace.endpos, q2pml.origin);
} else {
// move
VectorMA (q2pml.origin, q2pml.frametime, q2pml.velocity, q2pml.origin);
}
}
/*
==============
PMQ2_CheckDuck
Sets mins, maxs, and q2pm->viewheight
==============
*/
void PMQ2_CheckDuck (void)
{
q2trace_t trace;
q2pm->mins[0] = -16;
q2pm->mins[1] = -16;
q2pm->maxs[0] = 16;
q2pm->maxs[1] = 16;
if (q2pm->s.pm_type == Q2PM_GIB)
{
q2pm->mins[2] = 0;
q2pm->maxs[2] = 16;
q2pm->viewheight = 8;
return;
}
q2pm->mins[2] = -24;
if (q2pm->s.pm_type == Q2PM_DEAD)
{
q2pm->s.pm_flags |= Q2PMF_DUCKED;
}
else if (q2pm->cmd.upmove < 0 && (q2pm->s.pm_flags & Q2PMF_ON_GROUND) )
{ // duck
q2pm->s.pm_flags |= Q2PMF_DUCKED;
}
else
{ // stand up if possible
if (q2pm->s.pm_flags & Q2PMF_DUCKED)
{
// try to stand up
q2pm->maxs[2] = 32;
trace = q2pm->trace (q2pml.origin, q2pm->mins, q2pm->maxs, q2pml.origin);
if (!trace.allsolid)
q2pm->s.pm_flags &= ~Q2PMF_DUCKED;
}
}
if (q2pm->s.pm_flags & Q2PMF_DUCKED)
{
q2pm->maxs[2] = 4;
q2pm->viewheight = -2;
}
else
{
q2pm->maxs[2] = 32;
q2pm->viewheight = DEFAULT_VIEWHEIGHT;
}
}
/*
==============
PMQ2_DeadMove
==============
*/
void PMQ2_DeadMove (void)
{
float forward;
if (!q2pm->groundentity)
return;
// extra friction
forward = Length (q2pml.velocity);
forward -= 20;
if (forward <= 0)
{
memset(q2pml.velocity, 0, sizeof(vec3_t));
}
else
{
VectorNormalize (q2pml.velocity);
VectorScale (q2pml.velocity, forward, q2pml.velocity);
}
}
qboolean PMQ2_GoodPosition (void)
{
q2trace_t trace;
vec3_t origin, end;
int i;
if (q2pm->s.pm_type == Q2PM_SPECTATOR)
return true;
for (i=0 ; i<3 ; i++)
origin[i] = end[i] = q2pm->s.origin[i]*0.125;
trace = q2pm->trace (origin, q2pm->mins, q2pm->maxs, end);
return !trace.allsolid;
}
/*
================
PMQ2_SnapPosition
On exit, the origin will have a value that is pre-quantized to the 0.125
precision of the network channel and in a valid position.
================
*/
void PMQ2_SnapPosition (void)
{
int sign[3];
int i, j, bits;
short base[3];
// try all single bits first
static int jitterbits[8] = {0,4,1,2,3,5,6,7};
// snap velocity to eigths
for (i=0 ; i<3 ; i++)
q2pm->s.velocity[i] = (int)(q2pml.velocity[i]*8);
for (i=0 ; i<3 ; i++)
{
if (q2pml.origin[i] >= 0)
sign[i] = 1;
else
sign[i] = -1;
q2pm->s.origin[i] = (int)(q2pml.origin[i]*8);
if (q2pm->s.origin[i]*0.125 == q2pml.origin[i])
sign[i] = 0;
}
VectorCopy (q2pm->s.origin, base);
// try all combinations
for (j=0 ; j<8 ; j++)
{
bits = jitterbits[j];
VectorCopy (base, q2pm->s.origin);
for (i=0 ; i<3 ; i++)
if (bits & (1<<i) )
q2pm->s.origin[i] += sign[i];
if (PMQ2_GoodPosition ())
return;
}
// go back to the last position
VectorCopy (q2pml.previous_origin, q2pm->s.origin);
// Con_DPrintf ("using previous_origin\n");
}
#if 0
//NO LONGER USED
/*
================
PMQ2_InitialSnapPosition
================
*/
void PMQ2_InitialSnapPosition (void)
{
int x, y, z;
short base[3];
VectorCopy (q2pm->s.origin, base);
for (z=1 ; z>=-1 ; z--)
{
q2pm->s.origin[2] = base[2] + z;
for (y=1 ; y>=-1 ; y--)
{
q2pm->s.origin[1] = base[1] + y;
for (x=1 ; x>=-1 ; x--)
{
q2pm->s.origin[0] = base[0] + x;
if (PMQ2_GoodPosition ())
{
q2pml.origin[0] = q2pm->s.origin[0]*0.125;
q2pml.origin[1] = q2pm->s.origin[1]*0.125;
q2pml.origin[2] = q2pm->s.origin[2]*0.125;
VectorCopy (q2pm->s.origin, q2pml.previous_origin);
return;
}
}
}
}
Con_DPrintf ("Bad InitialSnapPosition\n");
}
#else
/*
================
PMQ2_InitialSnapPosition
================
*/
void PMQ2_InitialSnapPosition(void)
{
int x, y, z;
short base[3];
static int offset[3] = { 0, -1, 1 };
VectorCopy (q2pm->s.origin, base);
for ( z = 0; z < 3; z++ ) {
q2pm->s.origin[2] = base[2] + offset[ z ];
for ( y = 0; y < 3; y++ ) {
q2pm->s.origin[1] = base[1] + offset[ y ];
for ( x = 0; x < 3; x++ ) {
q2pm->s.origin[0] = base[0] + offset[ x ];
if (PMQ2_GoodPosition ()) {
q2pml.origin[0] = q2pm->s.origin[0]*0.125;
q2pml.origin[1] = q2pm->s.origin[1]*0.125;
q2pml.origin[2] = q2pm->s.origin[2]*0.125;
VectorCopy (q2pm->s.origin, q2pml.previous_origin);
return;
}
}
}
}
Con_DPrintf ("Bad InitialSnapPosition\n");
}
#endif
/*
================
PMQ2_ClampAngles
================
*/
void PMQ2_ClampAngles (void)
{
short temp;
int i;
if (q2pm->s.pm_flags & Q2PMF_TIME_TELEPORT)
{
q2pm->viewangles[YAW] = SHORT2ANGLE(q2pm->cmd.angles[YAW] + q2pm->s.delta_angles[YAW]);
q2pm->viewangles[PITCH] = 0;
q2pm->viewangles[ROLL] = 0;
}
else
{
// circularly clamp the angles with deltas
for (i=0 ; i<3 ; i++)
{
temp = q2pm->cmd.angles[i] + q2pm->s.delta_angles[i];
q2pm->viewangles[i] = SHORT2ANGLE(temp);
}
// don't let the player look up or down more than 90 degrees
if (q2pm->viewangles[PITCH] > 89 && q2pm->viewangles[PITCH] < 180)
q2pm->viewangles[PITCH] = 89;
else if (q2pm->viewangles[PITCH] < 271 && q2pm->viewangles[PITCH] >= 180)
q2pm->viewangles[PITCH] = 271;
}
AngleVectors (q2pm->viewangles, q2pml.forward, q2pml.right, q2pml.up);
}
/*
================
Pmove
Can be called by either the server or the client
================
*/
void VARGS Q2_Pmove (q2pmove_t *pmove)
{
q2pm = pmove;
// clear results
q2pm->numtouch = 0;
memset (q2pm->viewangles, 0, sizeof(vec3_t));
q2pm->viewheight = 0;
q2pm->groundentity = 0;
q2pm->watertype = 0;
q2pm->waterlevel = 0;
// clear all pmove local vars
memset (&q2pml, 0, sizeof(q2pml));
// convert origin and velocity to float values
q2pml.origin[0] = q2pm->s.origin[0]*0.125;
q2pml.origin[1] = q2pm->s.origin[1]*0.125;
q2pml.origin[2] = q2pm->s.origin[2]*0.125;
q2pml.velocity[0] = q2pm->s.velocity[0]*0.125;
q2pml.velocity[1] = q2pm->s.velocity[1]*0.125;
q2pml.velocity[2] = q2pm->s.velocity[2]*0.125;
// save old org in case we get stuck
VectorCopy (q2pm->s.origin, q2pml.previous_origin);
q2pml.frametime = q2pm->cmd.msec * 0.001;
PMQ2_ClampAngles ();
if (q2pm->s.pm_type == Q2PM_SPECTATOR)
{
PMQ2_FlyMove (false);
PMQ2_SnapPosition ();
return;
}
if (q2pm->s.pm_type >= Q2PM_DEAD)
{
q2pm->cmd.forwardmove = 0;
q2pm->cmd.sidemove = 0;
q2pm->cmd.upmove = 0;
}
if (q2pm->s.pm_type == Q2PM_FREEZE)
return; // no movement at all
// set mins, maxs, and viewheight
PMQ2_CheckDuck ();
if (q2pm->snapinitial)
PMQ2_InitialSnapPosition ();
// set groundentity, watertype, and waterlevel
PMQ2_CatagorizePosition ();
if (q2pm->s.pm_type == Q2PM_DEAD)
PMQ2_DeadMove ();
PMQ2_CheckSpecialMovement ();
// drop timing counter
if (q2pm->s.pm_time)
{
int msec;
msec = q2pm->cmd.msec >> 3;
if (!msec)
msec = 1;
if ( msec >= q2pm->s.pm_time)
{
q2pm->s.pm_flags &= ~(Q2PMF_TIME_WATERJUMP | Q2PMF_TIME_LAND | Q2PMF_TIME_TELEPORT);
q2pm->s.pm_time = 0;
}
else
q2pm->s.pm_time -= msec;
}
if (q2pm->s.pm_flags & Q2PMF_TIME_TELEPORT)
{ // teleport pause stays exactly in place
}
else if (q2pm->s.pm_flags & Q2PMF_TIME_WATERJUMP)
{ // waterjump has no control, but falls
q2pml.velocity[2] -= q2pm->s.gravity * q2pml.frametime;
if (q2pml.velocity[2] < 0)
{ // cancel as soon as we are falling down again
q2pm->s.pm_flags &= ~(Q2PMF_TIME_WATERJUMP | Q2PMF_TIME_LAND | Q2PMF_TIME_TELEPORT);
q2pm->s.pm_time = 0;
}
PMQ2_StepSlideMove ();
}
else
{
PMQ2_CheckJump ();
PMQ2_Friction ();
if (q2pm->waterlevel >= 2)
PMQ2_WaterMove ();
else {
vec3_t angles;
VectorCopy(q2pm->viewangles, angles);
if (angles[PITCH] > 180)
angles[PITCH] = angles[PITCH] - 360;
angles[PITCH] /= 3;
AngleVectors (angles, q2pml.forward, q2pml.right, q2pml.up);
PMQ2_AirMove ();
}
}
// set groundentity, watertype, and waterlevel for final spot
PMQ2_CatagorizePosition ();
PMQ2_SnapPosition ();
}
#endif