This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/pmove.c

1587 lines
34 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 "engine.h"
#include "pmove.h"
#include "mathlib.h"
#define PM_SPEED 160.f
#define STEPSIZE 18
#define OVERCLIP 1.001f
#define MAX_CLIP_PLANES 5
#define JUMP_VELOCITY 270
#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes
#define DEFAULT_VIEWHEIGHT 26
#define CROUCH_VIEWHEIGHT 12
#define DEAD_VIEWHEIGHT -16
// 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;
vec3_t previous_origin;
vec3_t previous_velocity;
int previous_waterlevel;
float impact_speed;
trace_t groundtrace;
bool groundplane;
float frametime;
// states
bool walking;
bool onladder;
} pml_t;
pmove_t *pm;
pml_t pml;
// movement parameters
float pm_maxspeed = 300;
float pm_duckspeed = 100;
float pm_waterspeed = 400;
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 = 0.0f;
float pm_wateraccelerate = 10.0f;
float pm_flyaccelerate = 8.0f;
float pm_friction = 6.0f;
float pm_waterfriction = 1.0f;
float pm_flightfriction = 3.0f;
/*
walking up a step should kill some velocity
*/
void PM_SnapVector( float *v )
{
int i;
float f;
f = *v;
__asm fld f;
__asm fistp i;
*v = i;
v++;
f = *v;
__asm fld f;
__asm fistp i;
*v = i;
v++;
f = *v;
__asm fld f;
__asm fistp i;
*v = i;
}
/*
===============
PM_AddTouchEnt
===============
*/
void PM_AddTouchEnt( edict_t *entity )
{
int i;
if( pm->numtouch == MAXTOUCH )
return;
// see if it is already added
for ( i = 0; i < pm->numtouch; i++ )
{
if( pm->touchents[ i ] == entity )
return;
}
// add it
pm->touchents[pm->numtouch] = entity;
pm->numtouch++;
}
/*
==================
PM_ClipVelocity
Slide off of the impacting object
returns the blocked flags (1 = floor, 2 = step / wall)
==================
*/
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_SlideMove
Returns true if the velocity was clipped in some way
==================
*/
bool PM_SlideMove( bool gravity )
{
int bumpcount, numbumps = 4;
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;
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.5f;
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
trace = pm->trace( pm->ps.origin, pm->mins, pm->maxs, end );
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 true;
}
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.ent );
time_left -= time_left * trace.fraction;
if( numplanes >= MAX_CLIP_PLANES )
{
// this shouldn't really happen
VectorClear( pm->ps.velocity );
return true;
}
// 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.1f ) continue; // move doesn't interact with the plane
// see how hard we are hitting things
if( -into > pml.impact_speed )
{
pml.impact_speed = -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.1f )
{
// move doesn't interact with the plane
continue;
}
// 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.1f )
{
// move doesn't interact with the plane
continue;
}
// stop dead at a tripple plane interaction
VectorClear( pm->ps.velocity );
return true;
}
}
// 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( bool gravity )
{
vec3_t start_o, start_v;
vec3_t down_o, down_v;
trace_t trace;
vec3_t up, down;
float stepSize;
VectorCopy( pm->ps.origin, start_o );
VectorCopy( pm->ps.velocity, start_v );
if( !PM_SlideMove( gravity ))
{
// we got exactly where we wanted to go first try
return;
}
VectorCopy( start_o, down );
down[2] -= STEPSIZE;
trace = pm->trace( start_o, pm->mins, pm->maxs, down );
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
trace = pm->trace( start_o, pm->mins, pm->maxs, up );
if( trace.allsolid ) 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;
trace = pm->trace( pm->ps.origin, pm->mins, pm->maxs, down );
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 );
}
// add some code for footsteps sound here
}
/*
==================
PM_Friction
Handles both ground friction and water friction
==================
*/
void PM_Friction( void )
{
vec3_t vec;
float *vel;
float speed, newspeed, control;
float drop = 0;
vel = pm->ps.velocity;
VectorCopy( vel, vec );
if( pml.walking ) vec[2] = 0; // ignore slope movement
speed = VectorLength( vec );
if( speed < 1 )
{
vel[0] = vel[1] = 0; // allow sinking underwater
// FIXME: still have z friction underwater?
return;
}
// apply ground friction
if( pm->waterlevel <= 1 )
{
if( pml.walking && !(pml.groundtrace.flags & SURF_SLICK) || (pml.onladder))
{
control = speed < pm_stopspeed ? pm_stopspeed : speed;
drop += control * pm_friction * pml.frametime;
}
}
// apply water friction
if( pm->waterlevel && !pml.onladder ) drop += speed * pm_waterfriction * pm->waterlevel * pml.frametime;
// scale the velocity
newspeed = speed - drop;
if (newspeed < 0) newspeed = 0;
newspeed /= speed;
VectorScale( vel, newspeed, vel );
}
/*
==============
PM_Accelerate
Handles user intended acceleration
==============
*/
void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel )
{
int i;
float addspeed, accelspeed, currentspeed;
currentspeed = DotProduct (pm->ps.velocity, wishdir);
addspeed = wishspeed - currentspeed;
if( addspeed <= 0 ) return;
accelspeed = accel * pml.frametime * wishspeed;
if( accelspeed > addspeed ) accelspeed = addspeed;
for( i = 0; i < 3; i++ )
pm->ps.velocity[i] += accelspeed * wishdir[i];
}
/*
=============
PM_AddCurrents
=============
*/
void PM_AddCurrents( vec3_t wishvel )
{
vec3_t v;
float s;
// account for onladders
if (pml.onladder && fabs(pm->ps.velocity[2]) <= 200)
{
if ((pm->ps.viewangles[PITCH] <= -15) && (pm->cmd.forwardmove > 0))
wishvel[2] = 200;
else if ((pm->ps.viewangles[PITCH] >= 15) && (pm->cmd.forwardmove > 0))
wishvel[2] = -200;
else if (pm->cmd.upmove > 0)
wishvel[2] = 200;
else if (pm->cmd.upmove < 0)
wishvel[2] = -200;
else wishvel[2] = 0;
// limit horizontal speed when on a onladder
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 (pm->watertype & MASK_CURRENT)
{
VectorClear (v);
if (pm->watertype & CONTENTS_CURRENT_0)
v[0] += 1;
if (pm->watertype & CONTENTS_CURRENT_90)
v[1] += 1;
if (pm->watertype & CONTENTS_CURRENT_180)
v[0] -= 1;
if (pm->watertype & CONTENTS_CURRENT_270)
v[1] -= 1;
if (pm->watertype & CONTENTS_CURRENT_UP)
v[2] += 1;
if (pm->watertype & CONTENTS_CURRENT_DOWN)
v[2] -= 1;
s = pm_waterspeed;
if ((pm->waterlevel == 1) && (pm->ps.groundentity))
s /= 2;
VectorMA (wishvel, s, v, wishvel);
}
//
// add conveyor belt velocities
//
if (pm->ps.groundentity)
{
VectorClear (v);
if (pml.groundtrace.contents & CONTENTS_CURRENT_0)
v[0] += 1;
if (pml.groundtrace.contents & CONTENTS_CURRENT_90)
v[1] += 1;
if (pml.groundtrace.contents & CONTENTS_CURRENT_180)
v[0] -= 1;
if (pml.groundtrace.contents & CONTENTS_CURRENT_270)
v[1] -= 1;
if (pml.groundtrace.contents & CONTENTS_CURRENT_UP)
v[2] += 1;
if (pml.groundtrace.contents & CONTENTS_CURRENT_DOWN)
v[2] -= 1;
VectorMA (wishvel, 100 /* pm->ps.groundentity->speed */, v, wishvel);
}
}
/*
============
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;
max = abs( cmd->forwardmove );
if( abs( cmd->sidemove ) > max )
{
max = abs( cmd->sidemove );
}
if( abs( cmd->upmove ) > max )
{
max = abs( cmd->upmove );
}
if( !max ) return 0.0f;
total = sqrt( cmd->forwardmove * cmd->forwardmove + cmd->sidemove * cmd->sidemove + cmd->upmove * cmd->upmove );
return (float)PM_SPEED * max / ( 127.0 * total );
}
/*
=============
PM_CheckJump
=============
*/
static bool PM_CheckJump( void )
{
if( pm->cmd.upmove < 10 )
{
// not holding jump
pm->ps.pm_flags &= ~PMF_JUMP_HELD;
return false;
}
// 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 false;
}
pml.groundplane = false; // jumping away
pml.walking = false;
pm->ps.groundentity = NULL;
pm->ps.pm_flags |= PMF_JUMP_HELD;
pm->ps.velocity[2] = JUMP_VELOCITY;
return true;
}
static void PM_CheckOnLadder( void )
{
vec3_t spot;
vec3_t flatforward;
trace_t trace;
if (pm->ps.pm_time) return;
pml.onladder = false;
// check for onladder
flatforward[0] = pml.forward[0];
flatforward[1] = pml.forward[1];
flatforward[2] = 0;
VectorNormalize (flatforward);
VectorMA (pm->ps.origin, 1, flatforward, spot);
trace = pm->trace (pm->ps.origin, pm->mins, pm->maxs, spot);
if((trace.fraction < 1) && (trace.contents & CONTENTS_LADDER))
{
pml.onladder = true;
pml.walking = false;
}
}
/*
=============
PM_CheckWaterJump
=============
*/
static bool PM_CheckWaterJump( void )
{
vec3_t spot;
int cont;
vec3_t flatforward;
if( pm->ps.pm_time ) return false;
flatforward[0] = pml.forward[0];
flatforward[1] = pml.forward[1];
flatforward[2] = 0;
VectorNormalize( flatforward );
// check for water jump
if( pm->waterlevel != 2 ) return false;
VectorMA( pm->ps.origin, 30, flatforward, spot );
spot[2] += 4;
cont = pm->pointcontents(spot );
if(!(cont & CONTENTS_SOLID)) return false;
spot[2] += 16;
cont = pm->pointcontents( spot );
if( cont ) return false;
// jump out of water
VectorScale( pml.forward, 50, pm->ps.velocity );
pm->ps.velocity[2] = 350;
pm->ps.pm_flags |= PMF_TIME_WATERJUMP;
pm->ps.pm_time = 255;
return true;
}
/*
===================
PM_WaterJumpMove
Flying out of the water
===================
*/
static void PM_WaterJumpMove( void )
{
// waterjump has no control, but falls
PM_StepSlideMove( true );
pm->ps.velocity[2] -= pm->ps.gravity * pml.frametime;
if( pm->ps.velocity[2] < 0 )
{
// cancel as soon as we are falling down again
pm->ps.pm_flags &= ~PMF_ALL_TIMES;
pm->ps.pm_time = 0;
}
}
/*
===================
PM_WaterMove
===================
*/
static void PM_WaterMove( void )
{
int i;
vec3_t wishvel;
float wishspeed;
vec3_t wishdir;
float scale;
float vel;
if( PM_CheckWaterJump())
{
PM_WaterJumpMove();
return;
}
PM_Friction();
scale = PM_CmdScale( &pm->cmd );
// user intentions
if( !scale )
{
wishvel[0] = 0;
wishvel[1] = 0;
wishvel[2] = -60; // sink towards bottom
}
else
{
for(i = 0; i < 3; i++)
wishvel[i] = scale * pml.forward[i] * pm->cmd.forwardmove + scale * pml.right[i] * pm->cmd.sidemove;
wishvel[2] += scale * pm->cmd.upmove;
}
PM_AddCurrents( wishvel );
VectorCopy( wishvel, wishdir );
wishspeed = VectorNormalize( wishdir );
if( wishspeed > PM_SPEED * pm_swimscale )
{
wishspeed = PM_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( false );
}
/*
===================
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.sidemove;
}
wishvel[2] += scale * pm->cmd.upmove;
}
VectorCopy( wishvel, wishdir );
wishspeed = VectorNormalize(wishdir);
PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate );
PM_StepSlideMove( false );
}
/*
===================
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.sidemove;
cmd = pm->cmd;
scale = PM_CmdScale( &cmd );
// 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;
PM_AddCurrents( wishvel );
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.onladder )
{
PM_Accelerate( wishdir, wishspeed, pm_accelerate );
if( !wishvel[2] )
{
if( pm->ps.velocity[2] > 0 )
{
pm->ps.velocity[2] -= pm->ps.gravity * pml.frametime;
if( pm->ps.velocity[2] < 0 ) pm->ps.velocity[2] = 0;
}
else
{
pm->ps.velocity[2] += pm->ps.gravity * pml.frametime;
if( pm->ps.velocity[2] > 0 ) pm->ps.velocity[2] = 0;
}
}
}
else if( pml.groundplane )
{
PM_ClipVelocity (pm->ps.velocity, pml.groundtrace.plane.normal, pm->ps.velocity, OVERCLIP );
}
PM_StepSlideMove ( true );
}
/*
===================
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.sidemove;
cmd = pm->cmd;
scale = PM_CmdScale( &cmd );
// 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
PM_AddCurrents( wishvel );
VectorCopy( wishvel, wishdir );
wishspeed = VectorNormalize( wishdir );
wishspeed *= scale;
// clamp the speed lower if ducking
if( pm->ps.pm_flags & PMF_DUCKED )
{
if( wishspeed > pm_duckspeed )
{
wishspeed = pm_duckspeed;
}
}
// 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_SPEED * waterScale )
{
wishspeed = PM_SPEED * waterScale;
}
}
// when a player gets hit, they temporarily lose
// full control, which allows them to be moved a bit
if( pml.groundtrace.flags & SURF_SLICK )
{
accelerate = pm_airaccelerate;
}
else
{
accelerate = pm_accelerate;
}
PM_Accelerate( wishdir, wishspeed, accelerate );
//Msg("velocity = %1.1f %1.1f %1.1f\n", pm->ps.velocity[0], pm->ps.velocity[1], pm->ps.velocity[2]);
//Msg("velocity1 = %1.1f\n", VectorLength(pm->ps.velocity));
if( pml.groundtrace.flags & SURF_SLICK )
{
pm->ps.velocity[2] -= pm->ps.gravity * pml.frametime;
}
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( false );
//Msg("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;
// friction
speed = VectorLength( pm->ps.velocity );
if( speed < 1 )
{
VectorCopy( vec3_origin, pm->ps.velocity );
}
else
{
drop = 0;
friction = pm_friction * 1.5f; // 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.sidemove;
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_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;
// 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;
// start footstep cycle over
pm->ps.bobcycle = 0;
}
/*
=============
PM_CorrectAllSolid
=============
*/
static int PM_CorrectAllSolid( trace_t *trace )
{
int i, j, k;
vec3_t point;
// 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;
*trace = pm->trace( point, pm->mins, pm->maxs, point );
if( !trace->allsolid )
{
point[0] = pm->ps.origin[0];
point[1] = pm->ps.origin[1];
point[2] = pm->ps.origin[2] - 0.25;
pml.groundtrace = pm->trace( pm->ps.origin, pm->mins, pm->maxs, point );
trace = &pml.groundtrace;
return true;
}
}
}
}
pm->ps.groundentity = NULL;
pml.groundplane = false;
pml.walking = false;
return false;
}
/*
=============
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.groundentity )
{
// 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;
trace = pm->trace( pm->ps.origin, pm->mins, pm->maxs, point );
if ( trace.fraction == 1.0 )
{
if( pm->cmd.forwardmove >= 0 )
{
// anim jump forward
}
else
{
// anim jump backward
}
}
}
pm->ps.groundentity = NULL;
pml.groundplane = false;
pml.walking = false;
}
/*
=============
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;
trace = pm->trace( pm->ps.origin, pm->mins, pm->maxs, point );
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 = false;
pml.walking = false;
return;
}
// check if getting thrown off the ground
if( pm->ps.velocity[2] > 0 && DotProduct( pm->ps.velocity, trace.plane.normal ) > 10 )
{
// go into jump animation
if( pm->cmd.forwardmove >= 0 )
{
// anim jump forward
}
else
{
// anim jump backward
}
pm->ps.groundentity = NULL;
pml.groundplane = false;
pml.walking = false;
return;
}
// slopes that are too steep will not be considered onground
if ( trace.plane.normal[2] < MIN_WALK_NORMAL )
{
// FIXME: if they can't slide down the slope, let them
// walk (sharp crevices)
pm->ps.groundentity = NULL;
pml.groundplane = true;
pml.walking = false;
return;
}
pml.groundplane = true;
pml.walking = true;
// 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.groundentity )
{
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.groundentity = trace.ent;
PM_AddTouchEnt( trace.ent );
}
/*
=============
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] - 0.25;
cont = pm->pointcontents( point );
if( cont & MASK_WATER )
{
sample2 = pm->ps.viewheight - pm->mins[2];
sample1 = sample2 / 2;
pm->watertype = cont;
pm->waterlevel = 1;
point[2] = pm->ps.origin[2] + pm->mins[2] + sample1;
cont = pm->pointcontents( point );
if( cont & MASK_WATER )
{
pm->waterlevel = 2;
point[2] = pm->ps.origin[2] + pm->mins[2] + sample2;
cont = pm->pointcontents (point );
if( cont & MASK_WATER ) pm->waterlevel = 3;
}
}
}
/*
==============
PM_CheckDuck
Sets mins, maxs, and pm->ps.viewheight
==============
*/
static void PM_CheckDuck( void )
{
trace_t trace;
pm->mins[0] = -16;
pm->mins[1] = -16;
pm->maxs[0] = 16;
pm->maxs[1] = 16;
if( pm->ps.pm_type == PM_GIB )
{
pm->mins[2] = 0;
pm->maxs[2] = 16;
pm->ps.viewheight = 8;
return;
}
pm->mins[2] = -24;
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;
trace = pm->trace( pm->ps.origin, pm->mins, pm->maxs, pm->ps.origin );
if( !trace.allsolid ) pm->ps.pm_flags &= ~PMF_DUCKED;
}
}
if( pm->ps.pm_flags & PMF_DUCKED )
{
pm->maxs[2] = 4;
pm->ps.viewheight = -2;
}
else
{
pm->maxs[2] = 32;
pm->ps.viewheight = 22;
}
}
/*
================
PM_DropTimers
================
*/
static void PM_DropTimers( void )
{
if( pm->ps.pm_time )
{
int msec;
msec = pm->cmd.msec>>3;
if(!msec) msec = 1;
if( msec >= pm->ps.pm_time)
{
pm->ps.pm_flags &= ~PMF_ALL_TIMES;
pm->ps.pm_time = 0;
}
else pm->ps.pm_time -= msec;
}
}
/*
================
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( player_state_t *ps, const usercmd_t *cmd )
{
short temp;
int i;
if( ps->pm_type == PM_INTERMISSION )
{
return; // no view changes at all
}
if( ps->pm_type == PM_DEAD )
{
return; // no view changes at all
}
if( pm->ps.pm_flags & PMF_TIME_TELEPORT)
{
ps->viewangles[YAW] = SHORT2ANGLE( pm->cmd.angles[YAW] + pm->ps.delta_angles[YAW] );
ps->viewangles[PITCH] = 0;
ps->viewangles[ROLL] = 0;
}
// circularly clamp the angles with deltas
for( i = 0; i < 3; i++ )
{
temp = pm->cmd.angles[i] + pm->ps.delta_angles[i];
ps->viewangles[i] = SHORT2ANGLE(temp);
}
// don't let the player look up or down more than 90 degrees
if( ps->viewangles[PITCH] > 89 && ps->viewangles[PITCH] < 180 )
ps->viewangles[PITCH] = 89;
else if( ps->viewangles[PITCH] < 271 && ps->viewangles[PITCH] >= 180 )
ps->viewangles[PITCH] = 271;
}
/*
================
Pmove
Can be called by either the server or the client
================
*/
void Pmove( pmove_t *pmove )
{
pm = pmove;
// clear results
pm->numtouch = 0;
pm->watertype = 0;
pm->waterlevel = 0;
// clear all pmove local vars
memset (&pml, 0, sizeof(pml));
// 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 = pm->cmd.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;
}
if( pm->ps.pm_type >= PM_DEAD )
{
pm->cmd.forwardmove = 0;
pm->cmd.sidemove = 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 )
{
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_CheckOnLadder();
PM_DropTimers();
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();
}
// set groundentity, watertype, and waterlevel
PM_GroundTrace();
PM_SetWaterLevel();
PM_SnapVector( pm->ps.velocity );
if( pmove->ps.pm_flags & PMF_JUMP_HELD )
{
pmove->cmd.upmove = 20;
}
//PM_CheckStuck();
}