/* 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(); }