D:\cygwin\wf_fusion_src_r6\CVSROOT\qcommon\pmove.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 "qcommon.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;


    int         groundsurfFlags;
    cplane_t    groundplane;
    int         groundcontents;

    vec3_t      previous_origin;
    qboolean    ladder;
} pml_t;

pmove_t     *pm;
pml_t       pml;


// movement parameters
float   pm_stopspeed = 100;
float   pm_maxspeed = 320;
float   pm_duckspeed = 100;

float   pm_accelerate = 10;
float   pm_airaccelerate = 0;
float   pm_wateraccelerate = 6;

float   pm_friction = 6;
float   pm_waterfriction = 1;


/*

  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)
==================
*/
#define STOP_EPSILON    0.1

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_SlideMove

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
int PM_SlideMove (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;
    int         blocked = 0;
    
    numbumps = 4;
    
    VectorCopy (pml.velocity, primal_velocity);
    numplanes = 0;
    
    time_left = pml.frametime;

    for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
    {
        VectorMA ( pml.origin, time_left, pml.velocity, end );

        trace = pm->trace (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 3;
        }

        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++;
        }

        if (!trace.plane.normal[2])
        {
            blocked |= 2;       // step
        }

        time_left -= time_left * trace.fraction;

        // slide along this plane
        if (numplanes >= MAX_CLIP_PLANES)
        {   // this shouldn't really happen
            VectorClear (pml.velocity);
            break;
        }

        VectorCopy (trace.plane.normal, planes[numplanes]);
        numplanes++;

//
// modify original_velocity so it parallels all of the clip planes
//
        for (i=0 ; i<numplanes ; i++)
        {
            PM_ClipVelocity (pml.velocity, planes[i], pml.velocity, 1.01);
            for (j=0 ; j<numplanes ; j++)
                if (j != i)
                {
                    if (DotProduct (pml.velocity, planes[j]) < 0)
                        break;  // not ok
                }
            if (j == numplanes)
                break;
        }
        
        if (i != numplanes)
        {   // go along this plane
        }
        else
        {   // go along the crease
            if (numplanes != 2)
            {
                VectorClear (pml.velocity);
                break;
            }
            CrossProduct (planes[0], planes[1], dir);
            VectorNormalize (dir);
            d = DotProduct (dir, pml.velocity);
            VectorScale (dir, d, pml.velocity);
        }

        //
        // if velocity is against the original velocity, stop dead
        // to avoid tiny occilations in sloping corners
        //
        if (DotProduct (pml.velocity, primal_velocity) <= 0)
        {
            VectorClear (pml.velocity);
            break;
        }
    }

    if (pm->s.pm_time)
    {
        VectorCopy (primal_velocity, pml.velocity);
    }

    return blocked;
}

/*
==================
PM_StepSlideMove

Each intersection will try to step over the obstruction instead of
sliding along it.
==================
*/
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;
    int         blocked;

    VectorCopy (pml.origin, start_o);
    VectorCopy (pml.velocity, start_v);

    blocked = PM_SlideMove ();

    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_SlideMove ();

    // 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;
    }

    if ((blocked & 2) || trace.plane.normal[2] == 1)
        pm->step = qtrue;

    //!! 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 = vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2];
    if (speed < 1)
    {
        vel[0] = 0;
        vel[1] = 0;
        return;
    }

    speed = sqrt(speed);
    drop = 0;

// apply ground friction
    if ((pm->groundentity && !(pml.groundsurfFlags & SURF_SLICK) ) && (pm->waterlevel < 2) || (pml.ladder) )
    {
        friction = pm_friction;
        control = speed < pm_stopspeed ? pm_stopspeed : speed;
        drop += control*friction*pml.frametime;
    }

// apply water friction
    if ((pm->waterlevel >= 2) && !pml.ladder)
        drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime;

// scale the velocity
    newspeed = speed - drop;
    if (newspeed <= 0)
    {
        newspeed = 0;
        VectorClear ( vel );
    }
    else
    {
        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 (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)
{
    //
    // 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
        clamp ( wishvel[0], -25, 25 );
        clamp ( wishvel[1], -25, 25 );
    }
}

/*
===================
PM_LadderMove

===================
*/
void PM_LadderMove (void)
{
    int     i;
    vec3_t  wishvel;
    float   wishspeed;
    vec3_t  wishdir;
    float   fmove, smove;
    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;

        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
        clamp ( wishvel[0], -25, 25 );
        clamp ( wishvel[1], -25, 25 );

    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)
    {
        wishspeed = maxspeed/wishspeed;
        VectorScale (wishvel, wishspeed, wishvel);
        wishspeed = maxspeed;
    }
    
    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 ();
}

/*
===================
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)
    {
        wishspeed = pm_maxspeed/wishspeed;
        VectorScale (wishvel, 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)
    {
        wishspeed = maxspeed/wishspeed;
        VectorScale (wishvel, 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
        if (pml.velocity[2] > 0)
            pml.velocity[2] = 0; //!!! this is before the accel
        PM_Accelerate (wishdir, wishspeed, pm_accelerate);

        // fix for negative trigger_gravity fields
        if (pm->s.gravity > 0) {
            if (pml.velocity[2] > 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_CategorizePosition
=============
*/
void PM_CategorizePosition (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) //!!ZOID changed from 100 to 180 (ramp accel)
    {
        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.groundsurfFlags = trace.surfFlags;
        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;

#if 0   // Tonik -- disable this buggy code
                // 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;
                }
#endif
            }
        }

        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;
        }
    }
//  laddertime
    pm->ladderlevel     = 0;
    pm->laddertype      = 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_LADDER)
    {
        pm->laddertype = cont;
        pm->ladderlevel = 1;
        point[2] = pml.origin[2] + pm->mins[2] + sample1;
        cont = pm->pointcontents (point);
        if (cont & MASK_LADDER)
        {
            pm->laddertype = 2;
            point[2] = pml.origin[2] + pm->mins[2] + sample2;
            cont = pm->pointcontents (point);
            if (cont & MASK_LADDER)
                pm->ladderlevel = 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;
        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 = qfalse;

    // 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.surfFlags & SURF_LADDER))
        pml.ladder = qtrue;

    // 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 (qboolean doclip)
{
    float       speed, drop, friction, control, newspeed;
    float       currentspeed, addspeed, accelspeed;
    int         i;
    vec3_t      wishvel;
    float       fmove, smove;
    vec3_t      wishdir;
    float       wishspeed;
    vec3_t      end;
    trace_t     trace;

    pm->viewheight = 26;

    // friction

    speed = VectorLength (pml.velocity);
    if (speed < 1)
    {
        VectorClear (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)
    {
        wishspeed = pm_maxspeed/wishspeed;
        VectorScale (wishvel, 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;
    vec3_t  end;

    pm->mins[0] = -15;
    pm->mins[1] = -15;

    pm->maxs[0] = 15;
    pm->maxs[1] = 15;

    pm->mins[2] = -24;

    if (pm->s.pm_type == PM_GIB || pm->s.pm_type == PM_DEAD)
    {
        pm->maxs[2] = -8;
        pm->viewheight = 8;
        return;
    }

    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
            VectorCopy (pml.origin, end);
            end[2] += 32 - pm->maxs[2];
            trace = pm->trace (pml.origin, pm->mins, pm->maxs, end);
            if (trace.fraction == 1) {
                pm->maxs[2] = 32;
                pm->s.pm_flags &= ~PMF_DUCKED;
            }
        }
    }

    if (pm->s.pm_flags & PMF_DUCKED)
    {
        pm->maxs[2] = 14;
        pm->viewheight = 12;
    }
    else
    {
        pm->maxs[2] = 32;
        pm->viewheight = 26;
    }
}


/*
==============
PM_DeadMove
==============
*/
void PM_DeadMove (void)
{
    float   forward;

    if (!pm->groundentity)
        return;

    // extra friction
    forward = VectorLength (pml.velocity) - 20;

    if (forward <= 0)
    {
        VectorClear (pml.velocity);
    }
    else
    {
        VectorNormalize (pml.velocity);
        VectorScale (pml.velocity, forward, pml.velocity);
    }
}


qboolean    PM_GoodPosition (void)
{
    trace_t trace;
    vec3_t  origin, end;
    int     i;

    if (pm->s.pm_type == PM_SPECTATOR)
        return qtrue;

    for (i=0 ; i<3 ; i++)
        origin[i] = end[i] = pm->s.origin[i]*0.125;
    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)
{
    int     sign[3];
    int     i, j, bits;
    int     base[3];
    // try all single bits first
    static const int jitterbits[8] = {0,4,1,2,3,5,6,7};

    // snap velocity to eigths
    for (i=0 ; i<3 ; i++)
        pm->s.velocity[i] = (int)(pml.velocity[i]*8);

    for (i=0 ; i<3 ; i++)
    {
        if (pml.origin[i] >= 0)
            sign[i] = 1;
        else 
            sign[i] = -1;
        pm->s.origin[i] = (int)(pml.origin[i]*8);
        if (pm->s.origin[i]*0.125 == pml.origin[i])
            sign[i] = 0;
    }
    VectorCopy (pm->s.origin, base);

    // try all combinations
    for (j=0 ; j<8 ; j++)
    {
        bits = jitterbits[j];
        VectorCopy (base, pm->s.origin);
        for (i=0 ; i<3 ; i++)
            if (bits & (1<<i) )
                pm->s.origin[i] += sign[i];

        if (PM_GoodPosition ())
            return;
    }

    // go back to the last position
    VectorCopy (pml.previous_origin, pm->s.origin);
    VectorClear (pm->s.velocity);
}


/*
================
PM_InitialSnapPosition

================
*/
void PM_InitialSnapPosition(void)
{
    int        x, y, z;
    int        base[3];
    static const 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 ()) {
                    pml.origin[0] = pm->s.origin[0]*0.125;
                    pml.origin[1] = pm->s.origin[1]*0.125;
                    pml.origin[2] = pm->s.origin[2]*0.125;
                    VectorCopy (pm->s.origin, pml.previous_origin);
                    return;
                }
            }
        }
    }

    Com_DPrintf ("Bad InitialSnapPosition\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;
    pm->step = qfalse;

    // clear all pmove local vars
    memset (&pml, 0, sizeof(pml));

    // convert origin and velocity to float values
    pml.origin[0] = pm->s.origin[0]*0.125;
    pml.origin[1] = pm->s.origin[1]*0.125;
    pml.origin[2] = pm->s.origin[2]*0.125;

    pml.velocity[0] = pm->s.velocity[0]*0.125;
    pml.velocity[1] = pm->s.velocity[1]*0.125;
    pml.velocity[2] = pm->s.velocity[2]*0.125;

    // save old org in case we get stuck
    VectorCopy (pm->s.origin, pml.previous_origin);

    pml.frametime = pm->cmd.msec * 0.001;

    PM_ClampAngles ();

    if (pm->s.pm_type == PM_SPECTATOR)
    {
        PM_FlyMove (qfalse);
        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.cantmove)
    {
        if (pm->s.cantmove & WF_NO_X_MOVE)
            pm->cmd.forwardmove = 0;
        if (pm->s.cantmove & WF_NO_Y_MOVE)
            pm->cmd.sidemove = 0;
        if (pm->s.cantmove & WF_NO_Z_MOVE)
            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_CategorizePosition ();

    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 if (pm->ladderlevel >= 1)
        {
                PM_LadderMove ();
        }
        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_CategorizePosition ();

    PM_SnapPosition ();
}