Home | History | Annotate | Download | only in client
      1 /*
      2 Copyright (C) 1996-1997 Id Software, Inc.
      3 
      4 This program is free software; you can redistribute it and/or
      5 modify it under the terms of the GNU General Public License
      6 as published by the Free Software Foundation; either version 2
      7 of the License, or (at your option) any later version.
      8 
      9 This program is distributed in the hope that it will be useful,
     10 but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     12 
     13 See the GNU General Public License for more details.
     14 
     15 You should have received a copy of the GNU General Public License
     16 along with this program; if not, write to the Free Software
     17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 */
     20 
     21 #include "quakedef.h"
     22 
     23 
     24 movevars_t		movevars;
     25 
     26 playermove_t	pmove;
     27 
     28 int			onground;
     29 int			waterlevel;
     30 int			watertype;
     31 
     32 float		frametime;
     33 
     34 vec3_t		forward, right, up;
     35 
     36 vec3_t	player_mins = {-16, -16, -24};
     37 vec3_t	player_maxs = {16, 16, 32};
     38 
     39 // #define	PM_GRAVITY			800
     40 // #define	PM_STOPSPEED		100
     41 // #define	PM_MAXSPEED			320
     42 // #define	PM_SPECTATORMAXSPEED	500
     43 // #define	PM_ACCELERATE		10
     44 // #define	PM_AIRACCELERATE	0.7
     45 // #define	PM_WATERACCELERATE	10
     46 // #define	PM_FRICTION			6
     47 // #define	PM_WATERFRICTION	1
     48 
     49 void PM_InitBoxHull (void);
     50 
     51 void Pmove_Init (void)
     52 {
     53 	PM_InitBoxHull ();
     54 }
     55 
     56 #define	STEPSIZE	18
     57 
     58 
     59 #define	BUTTON_JUMP	2
     60 
     61 
     62 /*
     63 ==================
     64 PM_ClipVelocity
     65 
     66 Slide off of the impacting object
     67 returns the blocked flags (1 = floor, 2 = step / wall)
     68 ==================
     69 */
     70 #define	STOP_EPSILON	0.1
     71 
     72 int PM_ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
     73 {
     74 	float	backoff;
     75 	float	change;
     76 	int		i, blocked;
     77 
     78 	blocked = 0;
     79 	if (normal[2] > 0)
     80 		blocked |= 1;		// floor
     81 	if (!normal[2])
     82 		blocked |= 2;		// step
     83 
     84 	backoff = DotProduct (in, normal) * overbounce;
     85 
     86 	for (i=0 ; i<3 ; i++)
     87 	{
     88 		change = normal[i]*backoff;
     89 		out[i] = in[i] - change;
     90 		if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
     91 			out[i] = 0;
     92 	}
     93 
     94 	return blocked;
     95 }
     96 
     97 
     98 /*
     99 ============
    100 PM_FlyMove
    101 
    102 The basic solid body movement clip that slides along multiple planes
    103 ============
    104 */
    105 #define	MAX_CLIP_PLANES	5
    106 
    107 int PM_FlyMove (void)
    108 {
    109 	int			bumpcount, numbumps;
    110 	vec3_t		dir;
    111 	float		d;
    112 	int			numplanes;
    113 	vec3_t		planes[MAX_CLIP_PLANES];
    114 	vec3_t		primal_velocity, original_velocity;
    115 	int			i, j;
    116 	pmtrace_t		trace;
    117 	vec3_t		end;
    118 	float		time_left;
    119 	int			blocked;
    120 
    121 	numbumps = 4;
    122 
    123 	blocked = 0;
    124 	VectorCopy (pmove.velocity, original_velocity);
    125 	VectorCopy (pmove.velocity, primal_velocity);
    126 	numplanes = 0;
    127 
    128 	time_left = frametime;
    129 
    130 	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
    131 	{
    132 		for (i=0 ; i<3 ; i++)
    133 			end[i] = pmove.origin[i] + time_left * pmove.velocity[i];
    134 
    135 		trace = PM_PlayerMove (pmove.origin, end);
    136 
    137 		if (trace.startsolid || trace.allsolid)
    138 		{	// entity is trapped in another solid
    139 			VectorCopy (vec3_origin, pmove.velocity);
    140 			return 3;
    141 		}
    142 
    143 		if (trace.fraction > 0)
    144 		{	// actually covered some distance
    145 			VectorCopy (trace.endpos, pmove.origin);
    146 			numplanes = 0;
    147 		}
    148 
    149 		if (trace.fraction == 1)
    150 			 break;		// moved the entire distance
    151 
    152 		// save entity for contact
    153 		pmove.touchindex[pmove.numtouch] = trace.ent;
    154 		pmove.numtouch++;
    155 
    156 		if (trace.plane.normal[2] > 0.7)
    157 		{
    158 			blocked |= 1;		// floor
    159 		}
    160 		if (!trace.plane.normal[2])
    161 		{
    162 			blocked |= 2;		// step
    163 		}
    164 
    165 		time_left -= time_left * trace.fraction;
    166 
    167 	// cliped to another plane
    168 		if (numplanes >= MAX_CLIP_PLANES)
    169 		{	// this shouldn't really happen
    170 			VectorCopy (vec3_origin, pmove.velocity);
    171 			break;
    172 		}
    173 
    174 		VectorCopy (trace.plane.normal, planes[numplanes]);
    175 		numplanes++;
    176 
    177 //
    178 // modify original_velocity so it parallels all of the clip planes
    179 //
    180 		for (i=0 ; i<numplanes ; i++)
    181 		{
    182 			PM_ClipVelocity (original_velocity, planes[i], pmove.velocity, 1);
    183 			for (j=0 ; j<numplanes ; j++)
    184 				if (j != i)
    185 				{
    186 					if (DotProduct (pmove.velocity, planes[j]) < 0)
    187 						break;	// not ok
    188 				}
    189 			if (j == numplanes)
    190 				break;
    191 		}
    192 
    193 		if (i != numplanes)
    194 		{	// go along this plane
    195 		}
    196 		else
    197 		{	// go along the crease
    198 			if (numplanes != 2)
    199 			{
    200 //				Con_Printf ("clip velocity, numplanes == %i\n",numplanes);
    201 				VectorCopy (vec3_origin, pmove.velocity);
    202 				break;
    203 			}
    204 			CrossProduct (planes[0], planes[1], dir);
    205 			d = DotProduct (dir, pmove.velocity);
    206 			VectorScale (dir, d, pmove.velocity);
    207 		}
    208 
    209 //
    210 // if original velocity is against the original velocity, stop dead
    211 // to avoid tiny occilations in sloping corners
    212 //
    213 		if (DotProduct (pmove.velocity, primal_velocity) <= 0)
    214 		{
    215 			VectorCopy (vec3_origin, pmove.velocity);
    216 			break;
    217 		}
    218 	}
    219 
    220 	if (pmove.waterjumptime)
    221 	{
    222 		VectorCopy (primal_velocity, pmove.velocity);
    223 	}
    224 	return blocked;
    225 }
    226 
    227 /*
    228 =============
    229 PM_GroundMove
    230 
    231 Player is on ground, with no upwards velocity
    232 =============
    233 */
    234 void PM_GroundMove (void)
    235 {
    236 	vec3_t	start, dest;
    237 	pmtrace_t	trace;
    238 	vec3_t	original, originalvel, down, up, downvel;
    239 	float	downdist, updist;
    240 
    241 	pmove.velocity[2] = 0;
    242 	if (!pmove.velocity[0] && !pmove.velocity[1] && !pmove.velocity[2])
    243 		return;
    244 
    245 	// first try just moving to the destination
    246 	dest[0] = pmove.origin[0] + pmove.velocity[0]*frametime;
    247 	dest[1] = pmove.origin[1] + pmove.velocity[1]*frametime;
    248 	dest[2] = pmove.origin[2];
    249 
    250 	// first try moving directly to the next spot
    251 	VectorCopy (dest, start);
    252 	trace = PM_PlayerMove (pmove.origin, dest);
    253 	if (trace.fraction == 1)
    254 	{
    255 		VectorCopy (trace.endpos, pmove.origin);
    256 		return;
    257 	}
    258 
    259 	// try sliding forward both on ground and up 16 pixels
    260 	// take the move that goes farthest
    261 	VectorCopy (pmove.origin, original);
    262 	VectorCopy (pmove.velocity, originalvel);
    263 
    264 	// slide move
    265 	PM_FlyMove ();
    266 
    267 	VectorCopy (pmove.origin, down);
    268 	VectorCopy (pmove.velocity, downvel);
    269 
    270 	VectorCopy (original, pmove.origin);
    271 	VectorCopy (originalvel, pmove.velocity);
    272 
    273 // move up a stair height
    274 	VectorCopy (pmove.origin, dest);
    275 	dest[2] += STEPSIZE;
    276 	trace = PM_PlayerMove (pmove.origin, dest);
    277 	if (!trace.startsolid && !trace.allsolid)
    278 	{
    279 		VectorCopy (trace.endpos, pmove.origin);
    280 	}
    281 
    282 // slide move
    283 	PM_FlyMove ();
    284 
    285 // press down the stepheight
    286 	VectorCopy (pmove.origin, dest);
    287 	dest[2] -= STEPSIZE;
    288 	trace = PM_PlayerMove (pmove.origin, dest);
    289 	if ( trace.plane.normal[2] < 0.7)
    290 		goto usedown;
    291 	if (!trace.startsolid && !trace.allsolid)
    292 	{
    293 		VectorCopy (trace.endpos, pmove.origin);
    294 	}
    295 	VectorCopy (pmove.origin, up);
    296 
    297 	// decide which one went farther
    298 	downdist = (down[0] - original[0])*(down[0] - original[0])
    299 		+ (down[1] - original[1])*(down[1] - original[1]);
    300 	updist = (up[0] - original[0])*(up[0] - original[0])
    301 		+ (up[1] - original[1])*(up[1] - original[1]);
    302 
    303 	if (downdist > updist)
    304 	{
    305 usedown:
    306 		VectorCopy (down, pmove.origin);
    307 		VectorCopy (downvel, pmove.velocity);
    308 	} else // copy z value from slide move
    309 		pmove.velocity[2] = downvel[2];
    310 
    311 // if at a dead stop, retry the move with nudges to get around lips
    312 
    313 }
    314 
    315 
    316 
    317 /*
    318 ==================
    319 PM_Friction
    320 
    321 Handles both ground friction and water friction
    322 ==================
    323 */
    324 void PM_Friction (void)
    325 {
    326 	float	*vel;
    327 	float	speed, newspeed, control;
    328 	float	friction;
    329 	float	drop;
    330 	vec3_t	start, stop;
    331 	pmtrace_t		trace;
    332 
    333 	if (pmove.waterjumptime)
    334 		return;
    335 
    336 	vel = pmove.velocity;
    337 
    338 	speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]);
    339 	if (speed < 1)
    340 	{
    341 		vel[0] = 0;
    342 		vel[1] = 0;
    343 		return;
    344 	}
    345 
    346 	friction = movevars.friction;
    347 
    348 // if the leading edge is over a dropoff, increase friction
    349 	if (onground != -1) {
    350 		start[0] = stop[0] = pmove.origin[0] + vel[0]/speed*16;
    351 		start[1] = stop[1] = pmove.origin[1] + vel[1]/speed*16;
    352 		start[2] = pmove.origin[2] + player_mins[2];
    353 		stop[2] = start[2] - 34;
    354 
    355 		trace = PM_PlayerMove (start, stop);
    356 
    357 		if (trace.fraction == 1) {
    358 			friction *= 2;
    359 		}
    360 	}
    361 
    362 	drop = 0;
    363 
    364 	if (waterlevel >= 2) // apply water friction
    365 		drop += speed*movevars.waterfriction*waterlevel*frametime;
    366 	else if (onground != -1) // apply ground friction
    367 	{
    368 		control = speed < movevars.stopspeed ? movevars.stopspeed : speed;
    369 		drop += control*friction*frametime;
    370 	}
    371 
    372 
    373 // scale the velocity
    374 	newspeed = speed - drop;
    375 	if (newspeed < 0)
    376 		newspeed = 0;
    377 	newspeed /= speed;
    378 
    379 	vel[0] = vel[0] * newspeed;
    380 	vel[1] = vel[1] * newspeed;
    381 	vel[2] = vel[2] * newspeed;
    382 }
    383 
    384 
    385 /*
    386 ==============
    387 PM_Accelerate
    388 ==============
    389 */
    390 void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel)
    391 {
    392 	int			i;
    393 	float		addspeed, accelspeed, currentspeed;
    394 
    395 	if (pmove.dead)
    396 		return;
    397 	if (pmove.waterjumptime)
    398 		return;
    399 
    400 	currentspeed = DotProduct (pmove.velocity, wishdir);
    401 	addspeed = wishspeed - currentspeed;
    402 	if (addspeed <= 0)
    403 		return;
    404 	accelspeed = accel*frametime*wishspeed;
    405 	if (accelspeed > addspeed)
    406 		accelspeed = addspeed;
    407 
    408 	for (i=0 ; i<3 ; i++)
    409 		pmove.velocity[i] += accelspeed*wishdir[i];
    410 }
    411 
    412 void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel)
    413 {
    414 	int			i;
    415 	float		addspeed, accelspeed, currentspeed, wishspd = wishspeed;
    416 
    417 	if (pmove.dead)
    418 		return;
    419 	if (pmove.waterjumptime)
    420 		return;
    421 
    422 	if (wishspd > 30)
    423 		wishspd = 30;
    424 	currentspeed = DotProduct (pmove.velocity, wishdir);
    425 	addspeed = wishspd - currentspeed;
    426 	if (addspeed <= 0)
    427 		return;
    428 	accelspeed = accel * wishspeed * frametime;
    429 	if (accelspeed > addspeed)
    430 		accelspeed = addspeed;
    431 
    432 	for (i=0 ; i<3 ; i++)
    433 		pmove.velocity[i] += accelspeed*wishdir[i];
    434 }
    435 
    436 
    437 
    438 /*
    439 ===================
    440 PM_WaterMove
    441 
    442 ===================
    443 */
    444 void PM_WaterMove (void)
    445 {
    446 	int		i;
    447 	vec3_t	wishvel;
    448 	float	wishspeed;
    449 	vec3_t	wishdir;
    450 	vec3_t	start, dest;
    451 	pmtrace_t	trace;
    452 
    453 //
    454 // user intentions
    455 //
    456 	for (i=0 ; i<3 ; i++)
    457 		wishvel[i] = forward[i]*pmove.cmd.forwardmove + right[i]*pmove.cmd.sidemove;
    458 
    459 	if (!pmove.cmd.forwardmove && !pmove.cmd.sidemove && !pmove.cmd.upmove)
    460 		wishvel[2] -= 60;		// drift towards bottom
    461 	else
    462 		wishvel[2] += pmove.cmd.upmove;
    463 
    464 	VectorCopy (wishvel, wishdir);
    465 	wishspeed = VectorNormalize(wishdir);
    466 
    467 	if (wishspeed > movevars.maxspeed)
    468 	{
    469 		VectorScale (wishvel, movevars.maxspeed/wishspeed, wishvel);
    470 		wishspeed = movevars.maxspeed;
    471 	}
    472 	wishspeed *= 0.7;
    473 
    474 //
    475 // water acceleration
    476 //
    477 //	if (pmove.waterjumptime)
    478 //		Con_Printf ("wm->%f, %f, %f\n", pmove.velocity[0], pmove.velocity[1], pmove.velocity[2]);
    479 	PM_Accelerate (wishdir, wishspeed, movevars.wateraccelerate);
    480 
    481 // assume it is a stair or a slope, so press down from stepheight above
    482 	VectorMA (pmove.origin, frametime, pmove.velocity, dest);
    483 	VectorCopy (dest, start);
    484 	start[2] += STEPSIZE + 1;
    485 	trace = PM_PlayerMove (start, dest);
    486 	if (!trace.startsolid && !trace.allsolid)	// FIXME: check steep slope?
    487 	{	// walked up the step
    488 		VectorCopy (trace.endpos, pmove.origin);
    489 		return;
    490 	}
    491 
    492 	PM_FlyMove ();
    493 //	if (pmove.waterjumptime)
    494 //		Con_Printf ("<-wm%f, %f, %f\n", pmove.velocity[0], pmove.velocity[1], pmove.velocity[2]);
    495 }
    496 
    497 
    498 /*
    499 ===================
    500 PM_AirMove
    501 
    502 ===================
    503 */
    504 void PM_AirMove (void)
    505 {
    506 	int			i;
    507 	vec3_t		wishvel;
    508 	float		fmove, smove;
    509 	vec3_t		wishdir;
    510 	float		wishspeed;
    511 
    512 	fmove = pmove.cmd.forwardmove;
    513 	smove = pmove.cmd.sidemove;
    514 
    515 	forward[2] = 0;
    516 	right[2] = 0;
    517 	VectorNormalize (forward);
    518 	VectorNormalize (right);
    519 
    520 	for (i=0 ; i<2 ; i++)
    521 		wishvel[i] = forward[i]*fmove + right[i]*smove;
    522 	wishvel[2] = 0;
    523 
    524 	VectorCopy (wishvel, wishdir);
    525 	wishspeed = VectorNormalize(wishdir);
    526 
    527 //
    528 // clamp to server defined max speed
    529 //
    530 	if (wishspeed > movevars.maxspeed)
    531 	{
    532 		VectorScale (wishvel, movevars.maxspeed/wishspeed, wishvel);
    533 		wishspeed = movevars.maxspeed;
    534 	}
    535 
    536 //	if (pmove.waterjumptime)
    537 //		Con_Printf ("am->%f, %f, %f\n", pmove.velocity[0], pmove.velocity[1], pmove.velocity[2]);
    538 
    539 	if ( onground != -1)
    540 	{
    541 		pmove.velocity[2] = 0;
    542 		PM_Accelerate (wishdir, wishspeed, movevars.accelerate);
    543 		pmove.velocity[2] -= movevars.entgravity * movevars.gravity * frametime;
    544 		PM_GroundMove ();
    545 	}
    546 	else
    547 	{	// not on ground, so little effect on velocity
    548 		PM_AirAccelerate (wishdir, wishspeed, movevars.accelerate);
    549 
    550 		// add gravity
    551 		pmove.velocity[2] -= movevars.entgravity * movevars.gravity * frametime;
    552 
    553 		PM_FlyMove ();
    554 
    555 	}
    556 
    557 //Con_Printf("airmove:vec: %4.2f %4.2f %4.2f\n",
    558 //			pmove.velocity[0],
    559 //			pmove.velocity[1],
    560 //			pmove.velocity[2]);
    561 //
    562 
    563 //	if (pmove.waterjumptime)
    564 //		Con_Printf ("<-am%f, %f, %f\n", pmove.velocity[0], pmove.velocity[1], pmove.velocity[2]);
    565 }
    566 
    567 
    568 
    569 /*
    570 =============
    571 PM_CatagorizePosition
    572 =============
    573 */
    574 void PM_CatagorizePosition (void)
    575 {
    576 	vec3_t		point;
    577 	int			cont;
    578 	pmtrace_t		tr;
    579 
    580 // if the player hull point one unit down is solid, the player
    581 // is on ground
    582 
    583 // see if standing on something solid
    584 	point[0] = pmove.origin[0];
    585 	point[1] = pmove.origin[1];
    586 	point[2] = pmove.origin[2] - 1;
    587 	if (pmove.velocity[2] > 180)
    588 	{
    589 		onground = -1;
    590 	}
    591 	else
    592 	{
    593 		tr = PM_PlayerMove (pmove.origin, point);
    594 		if ( tr.plane.normal[2] < 0.7)
    595 			onground = -1;	// too steep
    596 		else
    597 			onground = tr.ent;
    598 		if (onground != -1)
    599 		{
    600 			pmove.waterjumptime = 0;
    601 			if (!tr.startsolid && !tr.allsolid)
    602 				VectorCopy (tr.endpos, pmove.origin);
    603 		}
    604 
    605 		// standing on an entity other than the world
    606 		if (tr.ent > 0)
    607 		{
    608 			pmove.touchindex[pmove.numtouch] = tr.ent;
    609 			pmove.numtouch++;
    610 		}
    611 	}
    612 
    613 //
    614 // get waterlevel
    615 //
    616 	waterlevel = 0;
    617 	watertype = CONTENTS_EMPTY;
    618 
    619 	point[2] = pmove.origin[2] + player_mins[2] + 1;
    620 	cont = PM_PointContents (point);
    621 
    622 	if (cont <= CONTENTS_WATER)
    623 	{
    624 		watertype = cont;
    625 		waterlevel = 1;
    626 		point[2] = pmove.origin[2] + (player_mins[2] + player_maxs[2])*0.5;
    627 		cont = PM_PointContents (point);
    628 		if (cont <= CONTENTS_WATER)
    629 		{
    630 			waterlevel = 2;
    631 			point[2] = pmove.origin[2] + 22;
    632 			cont = PM_PointContents (point);
    633 			if (cont <= CONTENTS_WATER)
    634 				waterlevel = 3;
    635 		}
    636 	}
    637 }
    638 
    639 
    640 /*
    641 =============
    642 JumpButton
    643 =============
    644 */
    645 void JumpButton (void)
    646 {
    647 	if (pmove.dead)
    648 	{
    649 		pmove.oldbuttons |= BUTTON_JUMP;	// don't jump again until released
    650 		return;
    651 	}
    652 
    653 	if (pmove.waterjumptime)
    654 	{
    655 		pmove.waterjumptime -= frametime;
    656 		if (pmove.waterjumptime < 0)
    657 			pmove.waterjumptime = 0;
    658 		return;
    659 	}
    660 
    661 	if (waterlevel >= 2)
    662 	{	// swimming, not jumping
    663 		onground = -1;
    664 
    665 		if (watertype == CONTENTS_WATER)
    666 			pmove.velocity[2] = 100;
    667 		else if (watertype == CONTENTS_SLIME)
    668 			pmove.velocity[2] = 80;
    669 		else
    670 			pmove.velocity[2] = 50;
    671 		return;
    672 	}
    673 
    674 	if (onground == -1)
    675 		return;		// in air, so no effect
    676 
    677 	if ( pmove.oldbuttons & BUTTON_JUMP )
    678 		return;		// don't pogo stick
    679 
    680 	onground = -1;
    681 	pmove.velocity[2] += 270;
    682 
    683 	pmove.oldbuttons |= BUTTON_JUMP;	// don't jump again until released
    684 }
    685 
    686 /*
    687 =============
    688 CheckWaterJump
    689 =============
    690 */
    691 void CheckWaterJump (void)
    692 {
    693 	vec3_t	spot;
    694 	int		cont;
    695 	vec3_t	flatforward;
    696 
    697 	if (pmove.waterjumptime)
    698 		return;
    699 
    700 	// ZOID, don't hop out if we just jumped in
    701 	if (pmove.velocity[2] < -180)
    702 		return; // only hop out if we are moving up
    703 
    704 	// see if near an edge
    705 	flatforward[0] = forward[0];
    706 	flatforward[1] = forward[1];
    707 	flatforward[2] = 0;
    708 	VectorNormalize (flatforward);
    709 
    710 	VectorMA (pmove.origin, 24, flatforward, spot);
    711 	spot[2] += 8;
    712 	cont = PM_PointContents (spot);
    713 	if (cont != CONTENTS_SOLID)
    714 		return;
    715 	spot[2] += 24;
    716 	cont = PM_PointContents (spot);
    717 	if (cont != CONTENTS_EMPTY)
    718 		return;
    719 	// jump out of water
    720 	VectorScale (flatforward, 50, pmove.velocity);
    721 	pmove.velocity[2] = 310;
    722 	pmove.waterjumptime = 2;	// safety net
    723 	pmove.oldbuttons |= BUTTON_JUMP;	// don't jump again until released
    724 }
    725 
    726 /*
    727 =================
    728 NudgePosition
    729 
    730 If pmove.origin is in a solid position,
    731 try nudging slightly on all axis to
    732 allow for the cut precision of the net coordinates
    733 =================
    734 */
    735 void NudgePosition (void)
    736 {
    737 	vec3_t	base;
    738 	int		x, y, z;
    739 	int		i;
    740 	static int		sign[3] = {0, -1, 1};
    741 
    742 	VectorCopy (pmove.origin, base);
    743 
    744 	for (i=0 ; i<3 ; i++)
    745 		pmove.origin[i] = ((int)(pmove.origin[i]*8)) * 0.125;
    746 //	pmove.origin[2] += 0.124;
    747 
    748 //	if (pmove.dead)
    749 //		return;		// might be a squished point, so don'y bother
    750 //	if (PM_TestPlayerPosition (pmove.origin) )
    751 //		return;
    752 
    753 	for (z=0 ; z<=2 ; z++)
    754 	{
    755 		for (x=0 ; x<=2 ; x++)
    756 		{
    757 			for (y=0 ; y<=2 ; y++)
    758 			{
    759 				pmove.origin[0] = base[0] + (sign[x] * 1.0/8);
    760 				pmove.origin[1] = base[1] + (sign[y] * 1.0/8);
    761 				pmove.origin[2] = base[2] + (sign[z] * 1.0/8);
    762 				if (PM_TestPlayerPosition (pmove.origin))
    763 					return;
    764 			}
    765 		}
    766 	}
    767 	VectorCopy (base, pmove.origin);
    768 //	Con_DPrintf ("NudgePosition: stuck\n");
    769 }
    770 
    771 /*
    772 ===============
    773 SpectatorMove
    774 ===============
    775 */
    776 void SpectatorMove (void)
    777 {
    778 	float	speed, drop, friction, control, newspeed, accel;
    779 	float	currentspeed, addspeed, accelspeed;
    780 	int			i;
    781 	vec3_t		wishvel;
    782 	float		fmove, smove;
    783 	vec3_t		wishdir;
    784 	float		wishspeed;
    785 #ifndef SERVERONLY
    786 	extern float	server_version;	// version of server we connected to
    787 #endif
    788 
    789 	// friction
    790 
    791 	speed = Length (pmove.velocity);
    792 	if (speed < 1)
    793 	{
    794 		VectorCopy (vec3_origin, pmove.velocity)
    795 	}
    796 	else
    797 	{
    798 		drop = 0;
    799 
    800 		friction = movevars.friction*1.5;	// extra friction
    801 		control = speed < movevars.stopspeed ? movevars.stopspeed : speed;
    802 		drop += control*friction*frametime;
    803 
    804 		// scale the velocity
    805 		newspeed = speed - drop;
    806 		if (newspeed < 0)
    807 			newspeed = 0;
    808 		newspeed /= speed;
    809 
    810 		VectorScale (pmove.velocity, newspeed, pmove.velocity);
    811 	}
    812 
    813 	// accelerate
    814 	fmove = pmove.cmd.forwardmove;
    815 	smove = pmove.cmd.sidemove;
    816 
    817 	VectorNormalize (forward);
    818 	VectorNormalize (right);
    819 
    820 	for (i=0 ; i<3 ; i++)
    821 		wishvel[i] = forward[i]*fmove + right[i]*smove;
    822 	wishvel[2] += pmove.cmd.upmove;
    823 
    824 	VectorCopy (wishvel, wishdir);
    825 	wishspeed = VectorNormalize(wishdir);
    826 
    827 	//
    828 	// clamp to server defined max speed
    829 	//
    830 	if (wishspeed > movevars.spectatormaxspeed)
    831 	{
    832 		VectorScale (wishvel, movevars.spectatormaxspeed/wishspeed, wishvel);
    833 		wishspeed = movevars.spectatormaxspeed;
    834 	}
    835 
    836 	currentspeed = DotProduct(pmove.velocity, wishdir);
    837 	addspeed = wishspeed - currentspeed;
    838 	if (addspeed <= 0)
    839 		return;
    840 	accelspeed = movevars.accelerate*frametime*wishspeed;
    841 	if (accelspeed > addspeed)
    842 		accelspeed = addspeed;
    843 
    844 	for (i=0 ; i<3 ; i++)
    845 		pmove.velocity[i] += accelspeed*wishdir[i];
    846 
    847 
    848 	// move
    849 	VectorMA (pmove.origin, frametime, pmove.velocity, pmove.origin);
    850 }
    851 
    852 /*
    853 =============
    854 PlayerMove
    855 
    856 Returns with origin, angles, and velocity modified in place.
    857 
    858 Numtouch and touchindex[] will be set if any of the physents
    859 were contacted during the move.
    860 =============
    861 */
    862 void PlayerMove (void)
    863 {
    864 	frametime = pmove.cmd.msec * 0.001;
    865 	pmove.numtouch = 0;
    866 
    867 	AngleVectors (pmove.angles, forward, right, up);
    868 
    869 	if (pmove.spectator)
    870 	{
    871 		SpectatorMove ();
    872 		return;
    873 	}
    874 
    875 	NudgePosition ();
    876 
    877 	// take angles directly from command
    878 	VectorCopy (pmove.cmd.angles, pmove.angles);
    879 
    880 	// set onground, watertype, and waterlevel
    881 	PM_CatagorizePosition ();
    882 
    883 	if (waterlevel == 2)
    884 		CheckWaterJump ();
    885 
    886 	if (pmove.velocity[2] < 0)
    887 		pmove.waterjumptime = 0;
    888 
    889 	if (pmove.cmd.buttons & BUTTON_JUMP)
    890 		JumpButton ();
    891 	else
    892 		pmove.oldbuttons &= ~BUTTON_JUMP;
    893 
    894 	PM_Friction ();
    895 
    896 	if (waterlevel >= 2)
    897 		PM_WaterMove ();
    898 	else
    899 		PM_AirMove ();
    900 
    901 	// set onground, watertype, and waterlevel for final spot
    902 	PM_CatagorizePosition ();
    903 }
    904 
    905