/* 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 "collision.h" #define STEPSIZE 18 // 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; csurface_t *groundsurface; cplane_t groundplane; int groundcontents; vec3_t previous_origin; bool ladder; } pml_t; pmove_t *pm; pml_t pml; // movement parameters float pm_stopspeed = 100; float pm_maxspeed = 300; float pm_duckspeed = 100; float pm_accelerate = 10; float pm_airaccelerate = 0; float pm_wateraccelerate = 10; float pm_friction = 6; float pm_waterfriction = 1; float pm_waterspeed = 400; /* walking up a step should kill some velocity */ /* ================== 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) * 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; } } /* ================== PM_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 PM_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; trace_t trace; vec3_t end; float time_left; numbumps = 4; VectorCopy (pml.velocity, primal_velocity); numplanes = 0; time_left = pml.frametime; for (bumpcount=0 ; bumpcounttrace (pml.origin, pm->mins, pm->maxs, end); if (trace.allsolid) { // entity is trapped in another solid pml.velocity[2] = 0; // don't build up falling damage return; } if (trace.fraction > 0) { // actually covered some distance VectorCopy (trace.endpos, pml.origin); numplanes = 0; } if (trace.fraction == 1) break; // moved the entire distance // save entity for contact if (pm->numtouch < MAXTOUCH && trace.ent) { pm->touchents[pm->numtouch] = trace.ent; pm->numtouch++; } time_left -= time_left * trace.fraction; // slide along this plane if (numplanes >= MAX_CLIP_PLANES) { // this shouldn't really happen VectorCopy (vec3_origin, pml.velocity); break; } VectorCopy (trace.plane.normal, planes[numplanes]); numplanes++; // modify original_velocity so it parallels all of the clip planes for (i=0 ; is.pm_time) { VectorCopy (primal_velocity, pml.velocity); } } /* ================== PM_StepSlideMove ================== */ void PM_StepSlideMove (void) { vec3_t start_o, start_v; vec3_t down_o, down_v; trace_t trace; float down_dist, up_dist; vec3_t up, down; VectorCopy (pml.origin, start_o); VectorCopy (pml.velocity, start_v); PM_StepSlideMove_ (); VectorCopy (pml.origin, down_o); VectorCopy (pml.velocity, down_v); VectorCopy (start_o, up); up[2] += STEPSIZE; trace = pm->trace (up, pm->mins, pm->maxs, up); if (trace.allsolid) return; // can't step up // try sliding above VectorCopy (up, pml.origin); VectorCopy (start_v, pml.velocity); PM_StepSlideMove_ (); // push down the final amount VectorCopy (pml.origin, down); down[2] -= STEPSIZE; trace = pm->trace (pml.origin, pm->mins, pm->maxs, down); if (!trace.allsolid) { VectorCopy (trace.endpos, pml.origin); } VectorCopy(pml.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]); if (down_dist > up_dist || trace.plane.normal[2] < MIN_STEP_NORMAL) { VectorCopy (down_o, pml.origin); VectorCopy (down_v, pml.velocity); return; } // Special case // if we were walking along a plane, then we need to copy the Z over pml.velocity[2] = down_v[2]; } /* ================== PM_Friction Handles both ground friction and water friction ================== */ void PM_Friction (void) { float *vel; float speed, newspeed, control; float friction; float drop; vel = pml.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 ((pm->groundentity && pml.groundsurface && !(pml.groundsurface->flags & SURF_SLICK) ) || (pml.ladder) ) { friction = pm_friction; control = speed < pm_stopspeed ? pm_stopspeed : speed; drop += control*friction*pml.frametime; } // apply water friction if (pm->waterlevel && !pml.ladder) drop += speed*pm_waterfriction*pm->waterlevel*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 ============== */ void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) { int i; float addspeed, accelspeed, currentspeed; currentspeed = DotProduct (pml.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++) pml.velocity[i] += accelspeed*wishdir[i]; } void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel) { int i; float addspeed, accelspeed, currentspeed, wishspd = wishspeed; if (wishspd > 30) wishspd = 30; currentspeed = DotProduct (pml.velocity, wishdir); addspeed = wishspd - currentspeed; if (addspeed <= 0) return; accelspeed = accel * wishspeed * pml.frametime; if (accelspeed > addspeed) accelspeed = addspeed; for (i=0 ; i<3 ; i++) pml.velocity[i] += accelspeed*wishdir[i]; } /* ============= PM_AddCurrents ============= */ void PM_AddCurrents (vec3_t wishvel) { vec3_t v; float s; // account for ladders if (pml.ladder && fabs(pml.velocity[2]) <= 200) { if ((pm->viewangles[PITCH] <= -15) && (pm->cmd.forwardmove > 0)) wishvel[2] = 200; else if ((pm->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 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 (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->groundentity)) s /= 2; VectorMA (wishvel, s, v, wishvel); } // // add conveyor belt velocities // if (pm->groundentity) { VectorClear (v); if (pml.groundcontents & CONTENTS_CURRENT_0) v[0] += 1; if (pml.groundcontents & CONTENTS_CURRENT_90) v[1] += 1; if (pml.groundcontents & CONTENTS_CURRENT_180) v[0] -= 1; if (pml.groundcontents & CONTENTS_CURRENT_270) v[1] -= 1; if (pml.groundcontents & CONTENTS_CURRENT_UP) v[2] += 1; if (pml.groundcontents & CONTENTS_CURRENT_DOWN) v[2] -= 1; VectorMA (wishvel, 100 /* pm->groundentity->speed */, v, wishvel); } } /* =================== PM_WaterMove =================== */ void PM_WaterMove (void) { int i; vec3_t wishvel; float wishspeed; vec3_t wishdir; // user intentions for (i=0 ; i<3 ; i++) wishvel[i] = pml.forward[i]*pm->cmd.forwardmove + pml.right[i]*pm->cmd.sidemove; if (!pm->cmd.forwardmove && !pm->cmd.sidemove && !pm->cmd.upmove) wishvel[2] -= 60; // drift towards bottom else wishvel[2] += pm->cmd.upmove; PM_AddCurrents (wishvel); VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); if (wishspeed > pm_maxspeed) { VectorScale (wishvel, pm_maxspeed/wishspeed, wishvel); wishspeed = pm_maxspeed; } wishspeed *= 0.5; PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); PM_StepSlideMove (); } /* =================== PM_AirMove =================== */ void PM_AirMove (void) { int i; vec3_t wishvel; float fmove, smove; vec3_t wishdir; float wishspeed; float maxspeed; fmove = pm->cmd.forwardmove; smove = pm->cmd.sidemove; 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); // clamp to server defined max speed maxspeed = (pm->s.pm_flags & PMF_DUCKED) ? pm_duckspeed : pm_maxspeed; if (wishspeed > maxspeed) { VectorScale (wishvel, maxspeed/wishspeed, wishvel); wishspeed = maxspeed; } if ( pml.ladder ) { PM_Accelerate (wishdir, wishspeed, pm_accelerate); if (!wishvel[2]) { if (pml.velocity[2] > 0) { pml.velocity[2] -= pm->s.gravity * pml.frametime; if (pml.velocity[2] < 0) pml.velocity[2] = 0; } else { pml.velocity[2] += pm->s.gravity * pml.frametime; if (pml.velocity[2] > 0) pml.velocity[2] = 0; } } PM_StepSlideMove (); } else if ( pm->groundentity ) { // walking on ground pml.velocity[2] = 0; //!!! this is before the accel PM_Accelerate (wishdir, wishspeed, pm_accelerate); if(pm->s.gravity > 0) pml.velocity[2] = 0; else pml.velocity[2] -= pm->s.gravity * pml.frametime; if (!pml.velocity[0] && !pml.velocity[1]) return; PM_StepSlideMove (); } else { // not on ground, so little effect on velocity if (pm_airaccelerate) PM_AirAccelerate (wishdir, wishspeed, pm_accelerate); else PM_Accelerate (wishdir, wishspeed, 1); // add gravity pml.velocity[2] -= pm->s.gravity * pml.frametime; PM_StepSlideMove (); } } /* ============= PM_CatagorizePosition ============= */ void PM_CatagorizePosition (void) { vec3_t point; int cont; trace_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] = pml.origin[0]; point[1] = pml.origin[1]; point[2] = pml.origin[2] - 0.25; if (pml.velocity[2] > 180) { pm->s.pm_flags &= ~PMF_ON_GROUND; pm->groundentity = NULL; } else { trace = pm->trace (pml.origin, pm->mins, pm->maxs, point); pml.groundplane = trace.plane; pml.groundsurface = trace.surface; pml.groundcontents = trace.contents; if (!trace.ent || (trace.plane.normal[2] < 0.7 && !trace.startsolid) ) { pm->groundentity = NULL; pm->s.pm_flags &= ~PMF_ON_GROUND; } else { pm->groundentity = trace.ent; // hitting solid ground will end a waterjump if (pm->s.pm_flags & PMF_TIME_WATERJUMP) { pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT); pm->s.pm_time = 0; } if (! (pm->s.pm_flags & PMF_ON_GROUND) ) { // just hit the ground pm->s.pm_flags |= PMF_ON_GROUND; // don't do landing time if we were just going down a slope if (pml.velocity[2] < -200) { pm->s.pm_flags |= PMF_TIME_LAND; // don't allow another jump for a little while if (pml.velocity[2] < -400) pm->s.pm_time = 25; else pm->s.pm_time = 18; } } } if (pm->numtouch < MAXTOUCH && trace.ent) { pm->touchents[pm->numtouch] = trace.ent; pm->numtouch++; } } // get waterlevel, accounting for ducking pm->waterlevel = 0; pm->watertype = 0; sample2 = pm->viewheight - pm->mins[2]; sample1 = sample2 / 2; point[2] = pml.origin[2] + pm->mins[2] + 1; cont = pm->pointcontents (point); if (cont & MASK_WATER) { pm->watertype = cont; pm->waterlevel = 1; point[2] = pml.origin[2] + pm->mins[2] + sample1; cont = pm->pointcontents (point); if (cont & MASK_WATER) { pm->waterlevel = 2; point[2] = pml.origin[2] + pm->mins[2] + sample2; cont = pm->pointcontents (point); if (cont & MASK_WATER) pm->waterlevel = 3; } } } /* ============= PM_CheckJump ============= */ void PM_CheckJump (void) { if (pm->s.pm_flags & PMF_TIME_LAND) { // hasn't been long enough since landing to jump again return; } if (pm->cmd.upmove < 10) { // not holding jump pm->s.pm_flags &= ~PMF_JUMP_HELD; return; } // must wait for jump to be released if (pm->s.pm_flags & PMF_JUMP_HELD) return; if (pm->s.pm_type == PM_DEAD) return; if (pm->waterlevel >= 2) { // swimming, not jumping pm->groundentity = NULL; if (pml.velocity[2] <= -300) return; if (pm->watertype == CONTENTS_WATER) pml.velocity[2] = 100; else if (pm->watertype == CONTENTS_SLIME) pml.velocity[2] = 80; else pml.velocity[2] = 50; return; } if (pm->groundentity == NULL) return; // in air, so no effect pm->s.pm_flags |= PMF_JUMP_HELD; pm->groundentity = NULL; pml.velocity[2] += 270; if (pml.velocity[2] < 270) pml.velocity[2] = 270; } /* ============= PM_CheckSpecialMovement ============= */ void PM_CheckSpecialMovement (void) { vec3_t spot; int cont; vec3_t flatforward; trace_t trace; if (pm->s.pm_time) return; pml.ladder = false; // check for ladder flatforward[0] = pml.forward[0]; flatforward[1] = pml.forward[1]; flatforward[2] = 0; VectorNormalize (flatforward); VectorMA (pml.origin, 1, flatforward, spot); trace = pm->trace (pml.origin, pm->mins, pm->maxs, spot); if ((trace.fraction < 1) && (trace.contents & CONTENTS_LADDER)) pml.ladder = true; // check for water jump if (pm->waterlevel != 2) return; VectorMA (pml.origin, 30, flatforward, spot); spot[2] += 4; cont = pm->pointcontents (spot); if (!(cont & CONTENTS_SOLID)) return; spot[2] += 16; cont = pm->pointcontents (spot); if (cont) return; // jump out of water VectorScale (flatforward, 50, pml.velocity); pml.velocity[2] = 350; pm->s.pm_flags |= PMF_TIME_WATERJUMP; pm->s.pm_time = 255; } /* =============== PM_FlyMove =============== */ void PM_FlyMove (bool 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; trace_t trace; pm->viewheight = 22; // friction speed = VectorLength (pml.velocity); if (speed < 1) { VectorCopy (vec3_origin, pml.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 (pml.velocity, newspeed, pml.velocity); } // accelerate fmove = pm->cmd.forwardmove; smove = pm->cmd.sidemove; VectorNormalize (pml.forward); VectorNormalize (pml.right); 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); // clamp to server defined max speed if (wishspeed > pm_maxspeed) { VectorScale (wishvel, pm_maxspeed/wishspeed, wishvel); wishspeed = pm_maxspeed; } currentspeed = DotProduct(pml.velocity, wishdir); addspeed = wishspeed - currentspeed; if (addspeed <= 0) return; accelspeed = pm_accelerate*pml.frametime*wishspeed; if (accelspeed > addspeed) accelspeed = addspeed; for (i = 0; i < 3; i++) pml.velocity[i] += accelspeed*wishdir[i]; if (doclip) { for (i=0 ; i<3 ; i++) end[i] = pml.origin[i] + pml.frametime * pml.velocity[i]; trace = pm->trace (pml.origin, pm->mins, pm->maxs, end); VectorCopy (trace.endpos, pml.origin); } else { // move VectorMA (pml.origin, pml.frametime, pml.velocity, pml.origin); } } /* ============== PM_CheckDuck Sets mins, maxs, and pm->viewheight ============== */ 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->s.pm_type == PM_GIB) { pm->mins[2] = 0; pm->maxs[2] = 16; pm->viewheight = 8; return; } pm->mins[2] = -24; if (pm->s.pm_type == PM_DEAD) { pm->s.pm_flags |= PMF_DUCKED; } else if (pm->cmd.upmove < 0 && (pm->s.pm_flags & PMF_ON_GROUND) ) { // duck pm->s.pm_flags |= PMF_DUCKED; } else { // stand up if possible if (pm->s.pm_flags & PMF_DUCKED) { // try to stand up pm->maxs[2] = 32; trace = pm->trace (pml.origin, pm->mins, pm->maxs, pml.origin); if (!trace.allsolid) pm->s.pm_flags &= ~PMF_DUCKED; } } if (pm->s.pm_flags & PMF_DUCKED) { pm->maxs[2] = 4; pm->viewheight = -2; } else { pm->maxs[2] = 32; pm->viewheight = 22; } } /* ============== PM_DeadMove ============== */ void PM_DeadMove (void) { float forward; if (!pm->groundentity) return; // extra friction forward = VectorLength (pml.velocity); forward -= 20; if (forward <= 0) { VectorClear (pml.velocity); } else { VectorNormalize (pml.velocity); VectorScale (pml.velocity, forward, pml.velocity); } } bool PM_GoodPosition (void) { trace_t trace; vec3_t origin, end; int i; if (pm->s.pm_type == PM_SPECTATOR) return true; for (i = 0; i < 3; i++) origin[i] = end[i] = pm->s.origin[i]; trace = pm->trace (origin, pm->mins, pm->maxs, end); return !trace.allsolid; } /* ================ PM_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 PM_SnapPosition (void) { VectorCopy( pml.origin, pm->s.origin ); VectorCopy( pml.velocity, pm->s.velocity ); if (PM_GoodPosition()) return; VectorCopy (pml.previous_origin, pm->s.origin); } /* ================ PM_InitialSnapPosition ================ */ void PM_InitialSnapPosition(void) { int x, y, z; short base[3]; static int offset[3] = { 0, -1, 1 }; VectorCopy (pm->s.origin, base); for ( z = 0; z < 3; z++ ) { pm->s.origin[2] = base[2] + offset[ z ]; for ( y = 0; y < 3; y++ ) { pm->s.origin[1] = base[1] + offset[ y ]; for ( x = 0; x < 3; x++ ) { pm->s.origin[0] = base[0] + offset[ x ]; if (PM_GoodPosition ()) { VectorCopy (pm->s.origin, pml.origin); VectorCopy (pm->s.origin, pml.previous_origin); return; } } } } MsgWarn ("PM_InitialSnapPosition: bad position\n"); } /* ================ PM_ClampAngles ================ */ void PM_ClampAngles (void) { short temp; int i; if (pm->s.pm_flags & PMF_TIME_TELEPORT) { pm->viewangles[YAW] = SHORT2ANGLE(pm->cmd.angles[YAW] + pm->s.delta_angles[YAW]); pm->viewangles[PITCH] = 0; pm->viewangles[ROLL] = 0; } else { // circularly clamp the angles with deltas for (i=0 ; i<3 ; i++) { temp = pm->cmd.angles[i] + pm->s.delta_angles[i]; pm->viewangles[i] = SHORT2ANGLE(temp); } // don't let the player look up or down more than 90 degrees if (pm->viewangles[PITCH] > 89 && pm->viewangles[PITCH] < 180) pm->viewangles[PITCH] = 89; else if (pm->viewangles[PITCH] < 271 && pm->viewangles[PITCH] >= 180) pm->viewangles[PITCH] = 271; } AngleVectors (pm->viewangles, pml.forward, pml.right, pml.up); } /* ================ Pmove Can be called by either the server or the client ================ */ void Pmove (pmove_t *pmove) { pm = pmove; // clear results pm->numtouch = 0; VectorClear (pm->viewangles); pm->viewheight = 0; pm->groundentity = 0; pm->watertype = 0; pm->waterlevel = 0; // clear all pmove local vars memset (&pml, 0, sizeof(pml)); // convert origin and velocity to float values VectorCopy(pm->s.origin, pml.origin ); VectorCopy(pm->s.velocity, pml.velocity); VectorCopy (pm->s.origin, pml.previous_origin); // save old org in case we get stuck pml.frametime = pm->cmd.msec * 0.001; PM_ClampAngles (); if (pm->s.pm_type == PM_SPECTATOR) { PM_FlyMove (false); PM_SnapPosition (); return; } if (pm->s.pm_type >= PM_DEAD) { pm->cmd.forwardmove = 0; pm->cmd.sidemove = 0; pm->cmd.upmove = 0; } if (pm->s.pm_type == PM_FREEZE) return; // no movement at all // set mins, maxs, and viewheight PM_CheckDuck (); if (pm->snapinitial) PM_InitialSnapPosition (); // set groundentity, watertype, and waterlevel PM_CatagorizePosition (); if (pm->s.pm_type == PM_DEAD) PM_DeadMove (); PM_CheckSpecialMovement (); // drop timing counter if (pm->s.pm_time) { int msec; msec = pm->cmd.msec >> 3; if (!msec) msec = 1; if ( msec >= pm->s.pm_time) { pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT); pm->s.pm_time = 0; } else pm->s.pm_time -= msec; } if (pm->s.pm_flags & PMF_TIME_TELEPORT) { // teleport pause stays exactly in place } else if (pm->s.pm_flags & PMF_TIME_WATERJUMP) { // waterjump has no control, but falls pml.velocity[2] -= pm->s.gravity * pml.frametime; if (pml.velocity[2] < 0) { // cancel as soon as we are falling down again pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT); pm->s.pm_time = 0; } PM_StepSlideMove (); } else { PM_CheckJump (); PM_Friction (); if (pm->waterlevel >= 2) PM_WaterMove (); else { vec3_t angles; VectorCopy(pm->viewangles, angles); if (angles[PITCH] > 180) angles[PITCH] = angles[PITCH] - 360; angles[PITCH] /= 3; AngleVectors (angles, pml.forward, pml.right, pml.up); PM_AirMove (); } } // set groundentity, watertype, and waterlevel for final spot PM_CatagorizePosition (); PM_SnapPosition (); }