djcev.com

//

Git Repos / fte_dogmode / qc / pmove.qc

Last update to this file was on 2024-04-12 at 18:56.

Show pmove.qc

//==============================================================================
// PMOVE
//==============================================================================

// TODO CEV: crouching
// TODO CEV: crouch sliding (useful for sliding up stairs)
// TODO CEV: crouch-wall-thing (wallrunning? wall sliding?)
// TODO CEV: improved player unstick function
// TODO CEV: varied sounds (different jump sounds)

//======================================================================
// globals
//======================================================================

// the following are all managed by FTEQW -- CEV
float input_buttons; // buttons pressed by the client
// float input_impulse;
float input_timelength; // frame / tic time
vector input_angles; // +x = DOWN
vector input_movevalues; // movement requested by client

//======================================================================
// fields
//======================================================================

// note: timers are expensive, require synch between server and client -- CEV
// .entity groundentity; // already defined in entvars_t
.vector groundnormal;
.float pmove_flags; // custom movement flags -- CEV
.float doublejump_timer; // time in which a player can doublejump
.void() customphysics;

//======================================================================
// pmove constants (could be reworked as cvars, would add overhead) -- CEV
//======================================================================

// acceleration & friction
const float PM_AIRACCEL = 6.0f; // 10 in Q1; now 7.5 or 8.0
const float PM_AIRACCELQ3 = 0.8f; // 1.0 in Q3 ?; now 0.75 or 0.8
const float PM_AIRACCELFWD = 0.8f; // 1 feels close to Q3 / CPM
const float PM_AIRACCELBACK = 5.0f; // Air stop speed in Q3? 5.0f?
const float PM_AIRACCELBASE = 32.0f; // PM_MAXSPEED / 10.0
const float PM_AIRACCELTURN = 250.0f; // affects +fwd turning radius; 150.0f
const float PM_BOOSTACCEL = 10.0f; // ground boost accel; 10; 10 + 1.25
const float PM_BOOSTFRICTION = 1.0f; // ground boost friction; 4 is Q1
const float PM_GROUNDACCEL = 10.0f; // 10 is Q1, 15 is CPM
const float PM_GROUNDFRICTION = 6.0f; // 4 for Q1, 6 for Q3, 8 for (old?) CPM
const vector PM_GROUNDDIST_V = '0 0 1'; // distance for ground check
const float PM_WATERACCEL = 10.0f; // water acceleration
const float PM_WATERFRICTION = 4.0f; // friction in water

// horizontal speeds (mostly)
const float PM_BOOSTWISHSPEED = 400.0f; // 320, 400
const float PM_CROUCHSPEED = 120.0f; // ???
const float PM_MAXSPEED = 320.0f; // 320 always
const float PM_MAXAIRSPEED = 30.0f; // 30 for Q1 air control
const float PM_RUNSPEED = 320.0f; // run speed override; 400.0 default
const float PM_STOPSPEED = 100.0f;
const float PM_TELEEXITSPEED = 400.0f; // exit teleporters at this speed
const float PM_WALKSPEED = 200.0f; // walk speed override
const float PM_WATERMAXSPEED = 224.0f; // 320 * 0.7
const float PM_WATERSINKSPEED = 60.0f;

// vertical speeds (mostly)
const float PM_GRAVITY = 800.0f; // superseded by world_gravity global
const float PM_JUMPSPEED = 270.0f; // standard jump Z velocity; 90 * 3
const float PM_DOUBLEJUMPSPEED = 270.0f;// 270 * 1.5 in CPM
const float PM_SHORTJUMPSPEED = 180.0f; // 180 = 90 * 2
const float PM_SHORTJUMPTHRESH = 340.0f;// shortjump when horiz speed under this
const float PM_STAIRJUMPSPEED = 360.0f; // 360 = 90 * 4
const float PM_TELEJUMPSPEED = 360.0f; // same as STAIRJUMPSPEED
const float PM_WALLJUMPFORCE = 90.0f; // push away from wall
const float PM_WALLJUMPGROUND = 28.0f; // distance from ground to allow WJ (36)
const float PM_WALLJUMPLIMIT = -225.f; // no walljump if Z vel below this
const float PM_WALLJUMPSPEED = 270.0f; // same as JUMPSPEED
const float PM_WALLJUMPDOUBLE = 360.0f; // same as STAIRJUMPSPEED

// timing
const float PM_DOUBLEJUMP_WINDOW = 0.4f;// 2 jumps in this time is a double
const float PM_TELEJUMP_WINDOW = 0.4f; // duration to perform a telejump
const float PM_WALLJUMP_WINDOW = 0.3f; // dj timer < this to walljump
const float PM_WALLCLIP_WINDOW = 0.25f; //

// misc
const float PM_MAX_CLIP_PLANES = 5;
const float PM_OVERCLIP = 1.0f; // Quake3's OVERCLIP is 1.001f
const float PM_ONESIDEDCLIP = -0.1f; // -0.001, -0.01, -0.1
const float PM_STEPHEIGHT = 18.0f; // 18 for Q1, 22 for later games?
const vector PM_TELEDROP = '0 0 -64'; // drop teleporter exit to floor if
// floor is within this distance
// water level
const float WATERLEVEL_NONE = 0;
const float WATERLEVEL_FEET = 1;
const float WATERLEVEL_WAIST = 2;
const float WATERLEVEL_EYES = 3;

// Player Sizes
const vector PM_STAND_MIN = VEC_HULL_MIN;
const vector PM_STAND_MAX = VEC_HULL_MAX;
const vector PM_CROUCH_MIN = '-16 -16 -18';
const vector PM_CROUCH_MAX = '16 16 18';

// pmove_flags is used by the engine; FTE defines two constants:
// PMF_JUMP_HELD = 1, PMF_LADDER = 2. So those two constants should
// to be first (and in that order) in our enum below. -- CEV
enumflags
{
PMF_JUMP_HELD, // player is holding the jump key
PMF_ONLADDER, // entity is on a ladder
PMF_ONGROUND, // entity is on ground
PMF_STARTGROUND, // entity started the move on ground
PMF_CROUCH_HELD, // player is holding the crouch key
PMF_CROUCHED, // entity is crouching
PMF_DOUBLEJUMPED, // entity has doublejumped
PMF_WALLJUMPED, // entity has walljumped
PMF_WATERJUMPED, // entity has waterjumped
PMF_PRE_MOVE, // hint that origin hasn't updated yet
PMF_SLIDE_GRAVITY, // slidemove hint to apply gravity
PMF_SLIDE_SKIM, // slidemove hint to skim/wallclip
PMF_SLIDE_STEP, // slidemove hint to traverse steps
PMF_SLIDE_STICKY, // slidemove hint to stick to ground
PMF_AIRSTEPPED // we've airstepped
};

//======================================================================
// forward declarations
//======================================================================

float(float maxspeed, float a, float c, float t) PM_MaxCircleGroundSpeed;

static void(entity ent) PM_DoTouch;
float() PM_Nudge;
void() PM_DanceMove;
void(float dogravity, float sticky, float dostep, float doskim) PM_DanceMoveQ3;
void() PM_CategorizePosition;
void(float friction, float move_time) PM_Friction;
void(vector wishdir, float wishspeed, float accel, float move_time)
PM_Accelerate;
void(vector wishvel, float wishspeed, float accel, float move_time)
PM_AirAccelerate;
void(vector wishdir, float wishspeed, float move_time) PM_AirControl;
void(vector wishdir) PM_Jump;
void() PM_WallJump;
void(vector wishvel, float move_time) PM_WalkAccelerate;
void(vector wishvel, float move_time) PM_SwimAccelerate;
void(vector wishvel, float scale, float move_time) PM_NoClipAccelerate;
void(float move_time) PM_ManageTimers;
void(entity target) PM_Move;

//------------------------------------------------------------------------------

//----------------------------------------------------------------------
// PM_MaxCircleGroundSpeed
//
// circle jump calculations (in python, probably wrong on my [CEV's] part):
// 320 * math.sqrt((A * (2 - A * (1 / T))) / (c * (2 - c * (1 / T))))
// where A = accel (10.0), c = friction (4.0), T = framerate (q3 125, q1 77?)
// this equation is from the article "Circle-Jump Theory" by injx, found here:
// https://www.artifexquake.com/?page_id=184
//
// Max ground speed for accel 10, friction 6, T 125, w 320 (vq3) is ~409
// Max ground speed for accel 15, friction 6, T 125, w 320 (CPM) is ~497
// accel 10.000, friction 6, T 125, wishspeed 380 = 486
// accel 10.000, friction 6, T 125, wishspeed 400 = 512
//----------------------------------------------------------------------
float(float maxspeed, float a, float f, float t) PM_MaxCircleGroundSpeed =
{
return maxspeed * sqrt ((a * (2.0 - a * (1.0 / t))) /
(f * (2.0 - f * (1.0 / t))));
};

//----------------------------------------------------------------------
// PM_DoTouch
//----------------------------------------------------------------------
static void(entity ent) PM_DoTouch =
{
if (ent && ent.touch != __NULL__)
{
local entity stemp = self;
local entity otemp = other;

other = self;
self = ent;
self.touch ();
self = stemp;
other = otemp;
}
};

//----------------------------------------------------------------------
// PM_Nudge
// from the GPL2 CSQCTest code that comes with FTEQW; is called often
// to nudge player origin due to float/network precision errors -- CEV
//----------------------------------------------------------------------
float() PM_Nudge =
{
dprint ("PM_Nudge: starting function...\n");

vector test, org = self.origin;

// check current position.
tracebox (org, self.mins, self.maxs, org, FALSE, self);
if (!trace_startsolid)
return TRUE;

// truncate to network accuracy
org_x = floor (org_x * 8 + (1.0 / 16)) * 0.125;
org_y = floor (org_y * 8 + (1.0 / 16)) * 0.125;
org_z = floor (org_z * 8 + (1.0 / 16)) * 0.125;

test = org;

static float offsets[] = {0, -1./8, 1./8, -2./8, 2./8};
for (float z = 0; z < offsets.length; z++)
{
test.z = org.z + offsets[z];
for (float y = 0; y < offsets.length; y++)
{
test.y = org.y + offsets[y];
for (float x = 0; x < offsets.length; x++)
{
test.x = org.x + offsets[x];
tracebox (test, self.mins, self.maxs, test,
FALSE, self);
if (!trace_startsolid)
{
// okay, that'll do
self.origin = test;
return TRUE;
}
}
}
}
self.origin = org;
return FALSE;
};

//----------------------------------------------------------------------
// PM_DanceMove
//
// Updates origin according to velocity, moves the player through the world.
// Same as Q3 SlideMove and Q1 FlyMove. This version handles steps, applies
// gravity, and restores velocity based on a timer check (those latter two
// are features of Q3's SlideMove). I've inlined ClipVelocity to hopefully
// improve performance.
//
// Based on code from the Nuclide SDK (presumably by Eukara), specifically
// the function PMoveCustom_Move found in the file pmove_custom.qc. That
// in turn appears to be based on a similar function in the CSQCTest code
// that comes with FTEQW. -- CEV
//----------------------------------------------------------------------
void() PM_DanceMove =
{
vector end, prev_plane, prev_plane2, start_vel;
float f, grav, i, stepsize, time_left;
entity touched_ent;

// silence some FTEQCC warnings -- CEV
end = prev_plane = prev_plane2 = '0 0 0';
f = grav = i = stepsize = 0;
time_left = input_timelength;

if (self.pmove_flags & PMF_SLIDE_GRAVITY)
{
if (self.gravity)
grav = self.gravity;
else
grav = 1.0;
// world_gravity is set in world () (on the server)
// or PlayerUpdate (client) -- CEV
grav *= world_gravity * time_left;
// Half now, half later. Apparently affects framerate
// dependence. -- CEV
self.velocity_z -= grav * 0.5;
}

start_vel = self.velocity;

// Borrowing FTEQW's pm_airstep cvar here. If false, don't step up
// while in the air (changes stairs) -- CEV
if (!(world_airstep) && !(self.pmove_flags & PMF_ONGROUND))
self.pmove_flags &= ~PMF_SLIDE_STEP;

// no stepping. useful for testing -- CEV
if (world_nostep)
self.pmove_flags &= ~PMF_SLIDE_STEP;

// we need to bounce off surfaces (in order to slide along them),
// so we need at 2 attempts -- comment from the Nuclide SDK
// I've changed this to a larger number of attempts (from 3 to 5)
// to accommodate more complex plane interactions -- CEV
for (i = 5; time_left > 0 && i; i--)
{
// set our destination & test the move -- CEV
end = self.origin + (self.velocity * time_left);
tracebox (self.origin, self.mins, self.maxs, end, FALSE, self);

if (trace_allsolid || trace_startsolid)
{
if (trace_ent.classgroup & CG_PROJECTILE)
{
// self is in a projectile; call its touch
// function (maybe it'll explode)
/*
#ifdef SSQC
dprint (sprintf("PM_DanceMove: in a solid %s\n",
trace_ent.classname));
#endif
PM_DoTouch (trace_ent);
*/
continue;
}

// self is in something else; attempt to nudge out
if (PM_Nudge())
continue;

// nah, we're stuck. don't build up falling damage
// but allow sideways acceleration -- CEV
#if defined(CSQC)
dprint ("PM_DanceMove: client player entity stuck ");
#elif defined(SSQC)
dprint ("PM_DanceMove: server player entity stuck ");
#endif
dprint (sprintf("in a %s!\n", trace_ent.classname));
self.velocity_z = 0;
break;
}

// accept the move -- CEV
self.origin = trace_endpos;

if (trace_fraction >= 1.0f)
// no obstructions, made the whole move -- CEV
break;

// zero out stepsize, then store the plane normal and touched
// ent for later, then reduce time_left -- CEV
stepsize = 0;
touched_ent = trace_ent;
time_left -= time_left * trace_fraction;

// integrated StepSlideMove from Nuclide / CSQCTest
// only attempt to step if there's time left, stepping was
// requested, and we hit a vertical plane -- CEV
if (self.pmove_flags & PMF_SLIDE_STEP && time_left > 0 &&
trace_plane_normal_z == 0)
{
// store the entity and plane normal from our
// first move attempt, setup a vec to track
// velocity changes -- CEV
local entity first_ent = trace_ent;
local vector first_plane = trace_plane_normal;
local vector new_vel = self.velocity;
f = time_left;

// first: move up
trace_endpos_z += PM_STEPHEIGHT;
tracebox (self.origin, self.mins, self.maxs,
trace_endpos, FALSE, self);

if (trace_fraction < 1.0f)
{
// do a simple clipvel to the roof plane
// right now -- CEV
f -= f * trace_fraction;
new_vel -= trace_plane_normal *
(new_vel * trace_plane_normal);

if (trace_ent && trace_ent.touch)
touched_ent = trace_ent;
}

// second: move forward
stepsize = trace_endpos_z - self.origin_z;
end = trace_endpos + (new_vel * f);
end_z = trace_endpos_z;
tracebox (trace_endpos, self.mins, self.maxs, end,
FALSE, self);

#ifdef SSQC
if (trace_allsolid || trace_startsolid)
// caution
dprint ("PM_DanceMove: fwd move solid\n");
#endif

if (trace_fraction >= 1.0f)
{
// forward move didn't hit anything
// third: move down
end = trace_endpos;
end_z -= stepsize;
// + (self.pmove_flags &
// PMF_SLIDE_STICKY ? 16 : 1);
tracebox (trace_endpos, self.mins, self.maxs,
end, FALSE, self);

if (trace_allsolid || trace_startsolid)
{
// reject the move
#ifdef SSQC
dprint ("PM_DanceMove: down solid\n");
#endif
goto PM_DanceMove_RejectStep;
}
else if (trace_fraction < 1.0f &&
trace_plane_normal_z > 0.7f)
{
// Update time_left, stepsize, origin,
// velocity & touched_ent (if need);
// the later inline ClipVel will
// handle the plane -- CEV
time_left = f - (f * trace_fraction);
stepsize = trace_endpos_z -
self.origin_z;
self.origin = trace_endpos;

if (new_vel != self.velocity)
self.velocity = new_vel;

if (trace_ent && trace_ent.touch)
touched_ent = trace_ent;

// don't attempt to step again
// self.pmove_flags &= ~PMF_SLIDE_STEP;
}
else
{
// reject the move
goto PM_DanceMove_RejectStep;
}
}
else
{
// forward move hit something
if (trace_plane_normal != first_plane &&
trace_plane_normal_z == 0)
{
// hit something different than the
// first move; note that we're not
// restoring the first plane normal
if (trace_ent && trace_ent.touch)
touched_ent = trace_ent;
time_left -= time_left * trace_fraction;
stepsize = 0;
}
else
{
// revert to move before step attempt
PM_DanceMove_RejectStep:
touched_ent = trace_ent = first_ent;
trace_plane_normal = first_plane;
stepsize = 0;
}
}
}

// do the ground check before we might stop the loop -- CEV
if (trace_plane_normal_z > 0.7)
{
if (touched_ent.solid == SOLID_BSP)
{
self.groundentity = touched_ent;
self.groundnormal = trace_plane_normal;
self.flags |= FL_ONGROUND;
self.pmove_flags |= PMF_ONGROUND;
#ifdef SSQC
// before clipping velocity - CEV
self.jump_flag = self.velocity_z;
#endif
}
}
else
{
self.groundentity = __NULL__;
self.groundnormal = __NULL__;
self.flags &= ~FL_ONGROUND;
self.pmove_flags &= ~PMF_ONGROUND;
}

// clip velocity to the plane -- CEV
f = self.velocity * trace_plane_normal;

if (f < 0)
f *= PM_OVERCLIP;
else
if (trace_plane_normal_z > 0.7)
if (self.pmove_flags & PMF_SLIDE_STICKY)
f /= PM_OVERCLIP;
else
f *= PM_ONESIDEDCLIP;
else
f /= PM_OVERCLIP;

self.velocity -= trace_plane_normal * f;

// if velocity interacts with prev_plane then clip to it.
// I'm duplicating a little code here (PM_ClipVelocity),
// hopefully the audience will forgive me. -- CEV
if (prev_plane && prev_plane != trace_plane_normal &&
self.velocity * prev_plane < 0)
{
f = self.velocity * prev_plane;

if (f < 0)
f *= PM_OVERCLIP;
else
if (prev_plane_z > 0.7)
if (self.pmove_flags & PMF_SLIDE_STICKY)
f /= PM_OVERCLIP;
else
f *= PM_ONESIDEDCLIP;
else
f /= PM_OVERCLIP;

self.velocity -= prev_plane * f;

// Slide along the crease of previous plane and
// current plane if the two still interact -- CEV
if (self.velocity * trace_plane_normal < 0)
{
end = crossproduct (prev_plane,
trace_plane_normal);
end = normalize (end);
self.velocity = end * (end * self.velocity);
}

// An optimization from Quake 3 -- CEV
if (prev_plane2 && prev_plane2 != trace_plane_normal &&
prev_plane2 != prev_plane &&
self.velocity * prev_plane2 < 0)
{
// Stop if we interact with three planes -- CEV
#ifdef SSQC
dprint ("PM_DanceMove: triple plane stop\n");
#endif
self.velocity = '0 0 0';
PM_DoTouch (touched_ent);

break;
}
}

// an optimization from Quake 1; is this necessary? -- CEV
if (self.velocity * start_vel <= 0)
{
// we've turned against original velocity so zero
// out vel, touch any ents, and stop the loop
self.velocity = '0 0 0';
PM_DoTouch (touched_ent);
break;
}

// store current plane and prev_plane for later -- CEV
prev_plane2 = prev_plane;
prev_plane = trace_plane_normal;

// touch the saved entity last in case doing so overwrites
// trace_plane_normal -- CEV
PM_DoTouch (touched_ent);
}

// wallclip / wall skim timer check
// if doskim is true and velocity is non-zero and we lost speed
// during the move then restore velocity -- CEV
if ((self.pmove_flags & PMF_SLIDE_SKIM) && self.velocity)
{
local float speedloss = vlen([start_vel_x, start_vel_y, 0]) -
vlen([self.velocity_x, self.velocity_y, 0]);

if (speedloss > 0)
{
if (self.pmove_flags & PMF_DOUBLEJUMPED)
{
#ifdef SSQC
dprint (sprintf("PM_DanceMove: "
"double skim Z vel %g, loss %g\n",
self.velocity_z, speedloss));
#endif

if (speedloss <= PM_RUNSPEED)
{
self.velocity = start_vel;
self.velocity_z = min (start_vel_z,
self.velocity_z);
}
}
else if (self.pmove_flags & PMF_SLIDE_STICKY)
{
self.velocity = [start_vel_x, start_vel_y,
min (start_vel_z, self.velocity_z)];
}
else
{
self.velocity = start_vel;
}
}
}

// final gravity check here -- CEV
if (self.pmove_flags & PMF_SLIDE_GRAVITY)
self.velocity_z -= grav * 0.5;

// if stepsize is nonzero and we changed from inair to onground then
// we've airstepped. flag for later (see player_footsteps) -- CEV
#ifdef SSQC
if (stepsize > 0 && !(self.pmove_flags & PMF_STARTGROUND) &&
self.pmove_flags & PMF_ONGROUND)
{
// more debugging -- CEV
// dprint (sprintf("PM_DanceMove: airstep: %g\n", stepsize));
self.step_time = time;
self.pmove_flags |= PMF_AIRSTEPPED;
}
#endif

// clear slide hint flags -- CEV
self.pmove_flags = self.pmove_flags - (self.pmove_flags &
(PMF_SLIDE_GRAVITY | PMF_SLIDE_SKIM | PMF_SLIDE_STEP |
PMF_SLIDE_STICKY));

// a final call to setorigin to update links -- CEV
setorigin (self, self.origin);
};

//----------------------------------------------------------------------
// PM_DanceMoveQ3
//
// An alternate SlideMove based more closely on Q3's SlideMove. This
// solves all interactions correctly by using an array of planes and a
// lot of loops. Unfortunately it's slow and greatly affects the way
// movement feels - heavier, slower - and sometimes seems to affect
// framerate. Included here (in a working form) so I can experiment
// with it. -- CEV
//----------------------------------------------------------------------
void(float dogravity, float sticky, float dostep, float doskim) PM_DanceMoveQ3 =
{
vector plane[PM_MAX_CLIP_PLANES], end, start_v, new_v;
float backoff, grav, h, i, j, k, np, stepsize, time_left;
entity touched_ent;

// silence some FTEQCC warnings -- CEV
backoff = grav = h = i = j = k = np = stepsize = time_left = 0;

if (dogravity)
{
if (self.gravity)
grav = self.gravity;
else
grav = 1.0;
// world_gravity is set in world () (on the server)
// or PlayerUpdate (client) -- CEV
grav = grav * world_gravity * input_timelength;
// Half now, half later. Apparently affects framerate
// dependence. -- CEV
self.velocity_z -= grav * 0.5;
}

new_v = start_v = self.velocity;

if (!(world_airstep) && !(self.pmove_flags & PMF_ONGROUND))
dostep = FALSE;

if (world_nostep)
dostep = FALSE;

for (h = 5, time_left = input_timelength; time_left > 0 && h; h--)
{
// set our destination & test the move -- CEV
end = self.origin + (self.velocity * time_left);
tracebox (self.origin, self.mins, self.maxs, end, FALSE, self);

if (trace_allsolid || trace_startsolid)
{
// entity is trapped in a solid; attempt to nudge out
if (PM_Nudge())
continue;

// nah, we're stuck. don't build up falling damage
// but allow sideways acceleration -- CEV
self.velocity_z = 0;
break;
}

// accept the move -- CEV
self.origin = trace_endpos;

if (trace_fraction >= 1.0f)
// no obstructions, made the whole move -- CEV
break;

if (np >= PM_MAX_CLIP_PLANES)
{
dprint (sprintf("PM_DanceMoveQ3: no more plane storage,"
" numbumps: %g\n", fabs(h - 5)));
self.velocity = '0 0 0';
break;
}

// duplicate plane check from Quake3 -- CEV
/*
for (i = 0; i < np; i++)
{
if (trace_plane_normal * plane[i] > 0.99)
{
#ifdef SSQC
dprint ("PM_DanceMoveQ3: non-axial plane\n");
#endif
self.velocity += trace_plane_normal;
break;
}
}

if (i < np)
continue;
*/

// zero out stepsize, then store the plane normal and touched
// ent for later, then reduce time_left -- CEV
stepsize = 0;
touched_ent = trace_ent;
plane[np] = trace_plane_normal;
np++;
time_left -= time_left * trace_fraction;

// integrated StepSlideMove from Nuclide / CSQCTest -- CEV
if (dostep && time_left && trace_plane_normal_z == 0)
{
i = time_left;
new_v = self.velocity;

// first: move up
trace_endpos_z += PM_STEPHEIGHT;
tracebox (self.origin, self.mins, self.maxs,
trace_endpos, FALSE, self);

if (trace_fraction < 1.0f)
{
// do a simple clipvel to the
// roof plane right now -- CEV
i -= i * trace_fraction;
new_v -= trace_plane_normal *
(new_v * trace_plane_normal);
}

// second: move forward
stepsize = trace_endpos_z - self.origin_z;
end = trace_endpos + (new_v * i);
end_z = trace_endpos_z;
tracebox (trace_endpos, self.mins, self.maxs, end,
FALSE, self);

// save trace_ent if it has a touch function
if (trace_ent && trace_ent.touch != __NULL__)
touched_ent = trace_ent;

if (trace_allsolid || trace_startsolid)
{
// reject the move immediately -- CEV
#ifdef SSQC
dprint ("PM_DanceMoveQ3: fwd move solid\n");
#endif
stepsize = 0;
self.velocity = '0 0 0';
break;
}
else if (trace_fraction >= 1.0f)
{
// forward move didn't hit anything
// third: move down
end = trace_endpos;
end_z -= stepsize + 1;
tracebox (trace_endpos, self.mins, self.maxs,
end, FALSE, self);

if (trace_allsolid || trace_startsolid)
{
// again reject the move -- CEV
#ifdef SSQC
dprint ("PM_DanceMoveQ3: down move "
"solid\n");
#endif
stepsize = 0;
self.velocity = '0 0 0';
break;
}
else if (trace_fraction < 1.0f &&
trace_plane_normal_z > 0.7f)
{
// we'll let our proper ClipVel loop
// below take care of this -- CEV
plane[np - 1] = trace_plane_normal;
time_left = i - (i * trace_fraction);
stepsize = trace_endpos_z -
self.origin_z;
self.origin = trace_endpos;

if (new_v != self.velocity)
// hit a roof on the way
self.velocity = new_v;

touched_ent = trace_ent;

// don't attempt to step again
dostep = FALSE;
}
else
{
// down move didn't hit and/or it hit
// a plane too steep to be ground.
// reset & continue on -- CEV
trace_plane_normal = plane[np - 1];
stepsize = 0;
}
}
else
{
// forward move hit something. reset
// and move on. -- CEV
trace_plane_normal = plane[np - 1];
stepsize = 0;
}
}

// need to do the ground check & touch the saved ent before
// we might stop the loop early -- CEV
if (trace_plane_normal_z > 0.7)
{
if (touched_ent.solid == SOLID_BSP)
{
self.groundentity = touched_ent;
self.groundnormal = trace_plane_normal;
self.flags |= FL_ONGROUND;
self.pmove_flags |= PMF_ONGROUND;
}
}
else
{
self.groundentity = __NULL__;
self.groundnormal = __NULL__;
self.flags &= ~FL_ONGROUND;
self.pmove_flags &= ~PMF_ONGROUND;
}

// touch the saved entity -- CEV
PM_DoTouch (touched_ent);

// Quake 3's plane interaction and velocity clipping scheme.
// Solves more interactions than Q1's FlyMove and Nuclide's
// Pmove_Custom. Probably the slowest of the three mentioned.
// -- CEV
for (i = 0; i < np; i++)
{
if (plane[i] != self.groundnormal)
if (self.velocity * plane[i] >= 0.1)
continue;

// Inline ClipVelocity / PM_Rebound
backoff = self.velocity * plane[i];

if (backoff < 0)
backoff *= PM_OVERCLIP;
else
if (sticky)
backoff /= PM_OVERCLIP;
else
backoff *= PM_ONESIDEDCLIP;

new_v = self.velocity - (plane[i] * backoff);

// new_v -= plane[i] * (new_v * plane[i]);

for (j = 0; j < np; j++)
{
if (j == i)
continue;

if (plane[j] != self.groundnormal)
if (new_v * plane[j] >= 0.1)
continue;

// duplicating code here to avoid a function
// call -- CEV
backoff = new_v * plane[j];

if (backoff < 0)
backoff *= PM_OVERCLIP;
else
if (sticky)
backoff /= PM_OVERCLIP;
else
backoff *= PM_ONESIDEDCLIP;

new_v -= plane[j] * backoff;

// new_v -= plane[j] * (new_v * plane[j]);

// see if we still interact with the first
// clip plane (an optimization from Q3)
if (new_v * plane[i] >= 0)
continue;

if (sticky)
continue;

// slide along the crease
end = crossproduct (plane[i], plane[j]);
end = normalize (end);
new_v = end * (end * new_v);

// see if the move enters a third plane
for (k = 0; k < np; k++)
{
if (k == i || k == j)
continue;

if (new_v * plane[k] >= 0.1)
// no interaction
continue;

#ifdef SSQC
dprint ("PM_DanceMoveQ3: 3x stop\n");
#endif
self.velocity = '0 0 0';

// break out of all the loops now
// by skipping directly to the end
goto dancemove_end;
}
}

// try another move
self.velocity = new_v;
break;
}

// an optimization from Quake 1; is this necessary? -- CEV
if (self.velocity * start_v <= 0)
{
// we've turned against original velocity so
// clear vel and stop the loop
self.velocity = '0 0 0';
break;
}
}

dancemove_end:

// wallclip / wall skim timer check
// if doskim is true and velocity is non-zero and we lost speed
// during the move then restore velocity -- CEV
if (doskim && self.velocity && (vlen(start_v) > vlen(self.velocity)))
{
if (sticky)
self.velocity = [start_v_x, start_v_y,
min (start_v_z, self.velocity_z)];
else
self.velocity = start_v;
}

// final gravity check here -- CEV
if (dogravity)
self.velocity_z -= grav * 0.5;

// a final call to setorigin to update links -- CEV
setorigin (self, self.origin);
};

//----------------------------------------------------------------------
// PM_CategorizePosition
// Based on similarly-named function in the GPL2 purecsqc pmove.qc
//----------------------------------------------------------------------
void() PM_CategorizePosition =
{
vector point;
float contents;

if (self.movetype == MOVETYPE_NOCLIP || self.movetype == MOVETYPE_FLY)
{
// noclip is never on ground
self.groundentity = __NULL__;
self.groundnormal = __NULL__;
self.pmove_flags &= ~PMF_DOUBLEJUMPED;
self.pmove_flags &= ~PMF_WALLJUMPED;
self.pmove_flags &= ~PMF_ONGROUND;
self.flags &= ~FL_ONGROUND;
}
else
{
// TODO CEV there must be a way to optimize out this
// tracebox ground check some of the time

// do a trace to check for ground
tracebox (self.origin, self.mins, self.maxs,
self.origin - PM_GROUNDDIST_V, FALSE, self);
// only onground if we hit it & it faces upwards
if (trace_fraction < 1.0f && trace_plane_normal_z > 0.7f)
{
// on ground
self.groundentity = trace_ent;
self.groundnormal = trace_plane_normal;
self.flags |= FL_ONGROUND;
self.pmove_flags |= PMF_ONGROUND;
self.pmove_flags &= ~PMF_WALLJUMPED;
}
else
{
// not on ground
self.groundentity = __NULL__;
self.groundnormal = __NULL__;
self.flags &= ~FL_ONGROUND;
self.pmove_flags &= ~PMF_ONGROUND;
}
}

// check water levels
point = self.origin;
point_z = self.origin_z + self.mins_z + 1;
contents = pointcontents (point);
if (contents < CONTENT_SOLID)
{
self.watertype = contents;
point_z = self.origin_z + (self.mins_z + self.maxs_z) * 0.5;
if (pointcontents(point) < CONTENT_SOLID)
{
point_z = self.origin_z + self.maxs_z;
if (pointcontents(point) < CONTENT_SOLID)
self.waterlevel = WATERLEVEL_EYES;
else
self.waterlevel = WATERLEVEL_WAIST;
}
else
{
self.waterlevel = WATERLEVEL_FEET;
}
}
else
{
self.watertype = CONTENT_EMPTY;
self.waterlevel = WATERLEVEL_NONE;
}

// can't be waterjumping if we're on ground
if (self.pmove_flags & PMF_WATERJUMPED &&
(self.waterlevel == WATERLEVEL_NONE ||
self.pmove_flags & PMF_ONGROUND))
{
#ifdef SSQC
dprint ("PM_CategorizePosition: clearing FL_WATERJUMP\n");
#endif
self.pmove_flags &= ~PMF_WATERJUMPED;
self.flags &= ~FL_WATERJUMP;
}
};

//----------------------------------------------------------------------
// PM_Friction
// Ground friction
//----------------------------------------------------------------------
void(float friction, float move_time) PM_Friction =
{
float control, newspeed, speed;

speed = vlen (self.velocity);

if (speed < 1)
{
self.velocity = '0 0 0';
return;
}

// calculate what their new speed should be
control = speed < PM_STOPSPEED ? PM_STOPSPEED : speed;
newspeed = speed - control * friction * move_time;

// and slow them
if (newspeed < 0)
newspeed = 0;

self.velocity = self.velocity * (newspeed / speed);
};

//----------------------------------------------------------------------
// PM_Accelerate
// Ground acceleration & Quake 3 style air acceleration -- CEV
//----------------------------------------------------------------------
void(vector wishdir, float wishspeed, float accel, float move_time)
PM_Accelerate =
{
float newspeed, speed;

speed = wishspeed - (self.velocity * wishdir);

if (speed <= 0)
return;

newspeed = accel * move_time * wishspeed;
if (newspeed > speed)
newspeed = speed;

self.velocity += newspeed * wishdir;
};

//----------------------------------------------------------------------
// PM_AirAccelerate
// Quake 1 style air acceleration -- CEV
//----------------------------------------------------------------------
void(vector wishvel, float wishspeed, float accel, float move_time)
PM_AirAccelerate =
{
float newspeed, speed, wishspd;

// need to ignore Z velocity -- CEV
wishvel = [wishvel_x, wishvel_y, 0];
wishspd = vlen (wishvel);
wishvel = normalize (wishvel);
if (wishspd > PM_MAXAIRSPEED)
wishspd = PM_MAXAIRSPEED;
speed = self.velocity * wishvel;
speed = wishspd - speed;

if (speed <= 0)
return;

newspeed = accel * move_time * wishspeed;
if (newspeed > speed)
newspeed = speed;
self.velocity += newspeed * wishvel;
};

//----------------------------------------------------------------------
// PM_AirControl
// CPM / Painkiller-like Air Control. Based on Xonotic sources. -- CEV
//----------------------------------------------------------------------
void(vector wishdir, float wishspeed, float move_time) PM_AirControl =
{
local float dot, speed, turn, zspeed;

zspeed = self.velocity_z;
self.velocity_z = 0;

speed = vlen (self.velocity);
self.velocity = normalize (self.velocity);

dot = self.velocity * wishdir;

if (dot > 0)
{
turn = PM_AIRACCELBASE * bound (0, wishspeed / PM_MAXSPEED, 1);
turn *= PM_AIRACCELTURN * dot * dot * move_time;
self.velocity = normalize (self.velocity * speed +
wishdir * turn);
}

self.velocity *= speed;
self.velocity_z = zspeed;
};

//----------------------------------------------------------------------
// PM_Jump
//----------------------------------------------------------------------
void(vector wishdir) PM_Jump =
{
// are we already waterjumping, or is jump being held?
if (self.pmove_flags & PMF_WATERJUMPED ||
self.pmove_flags & PMF_JUMP_HELD)
return;

#ifdef SSQC
local string msg = "";
#endif

// make sure we get at least jumpspeed upwards from
// the ground plane by clamping it first.
if (self.groundnormal && (self.velocity * self.groundnormal < 0))
{
// #ifdef SSQC
// dprint("PM_Jump: clamping to ground\n");
// #endif
self.velocity -= self.groundnormal *
(self.velocity * self.groundnormal);
}

// make sure we get at least the indicated jump speed
// this changes the behavior of downward ramps -- CEV
if (self.velocity_z < 0)
self.velocity_z = 0;

if (self.doublejump_timer > 0)
{
// it may be useful in the future (for a tricks mode, etc)
// to distinguish between different jump types -- CEV
if (self.teleport_time > time - (PM_TELEJUMP_WINDOW + 0.2))
{
// a teleport jump: allow a larger (+0.2) window to
// account for time to travel thru teleporter -- CEV
#ifdef SSQC
msg = sprintf ("PM_Jump: telejump %g, ",
self.velocity_z);
#endif
// non-additive jump, though it shouldn't matter -- CEV
self.velocity_z = PM_TELEJUMPSPEED;
}
else if (self.groundnormal && self.groundnormal_z == 1)
{
#ifdef SSQC
msg = sprintf ("PM_Jump: stairjump %g, ",
self.velocity_z);
#endif
// don't do additive stairjumps on flat ground -- CEV
self.velocity_z = PM_STAIRJUMPSPEED;
}
else
{
#ifdef SSQC
msg = sprintf ("PM_Jump: doublejump %g, ",
self.velocity_z);
#endif
// the groundnormal might be weird - some kind of
// ramp - so we want an additive doublejump here -- CEV
self.velocity_z += PM_DOUBLEJUMPSPEED;
}

// set the doublejump flag -- CEV
self.pmove_flags |= PMF_DOUBLEJUMPED;
}
else if (input_movevalues_y && input_movevalues_x == 0 &&
vlen (self.velocity) <= PM_SHORTJUMPTHRESH)
{
// short jump with added sideways velocity - a dodge
self.velocity_z = 0;
self.velocity = wishdir * PM_BOOSTWISHSPEED;
// self.velocity = normalize (self.velocity);
// self.velocity *= 400;
self.velocity_z = PM_SHORTJUMPSPEED;

#ifdef SSQC
msg = sprintf ("PM_Jump: short jump %v ", self.velocity);
#endif
}
else
{
// normal jump
#ifdef SSQC
if (self.velocity_z > 0)
msg = sprintf ("PM_Jump: jump %g, ", self.velocity_z);
#endif

// do an additive jump on non-flat ground -- CEV
if (self.groundnormal_z == 1)
// self.velocity_z = min (PM_STAIRJUMPSPEED,
// self.velocity_z += PM_JUMPSPEED);
self.velocity_z = PM_JUMPSPEED;
else
self.velocity_z += PM_JUMPSPEED;
}

// report new Z velocity
#ifdef SSQC
if (msg != "")
dprint (sprintf("%s %g\n", msg, self.velocity_z));
#endif

// manage flags -- CEV
self.groundentity = __NULL__;
self.groundnormal = __NULL__;
self.pmove_flags &= ~PMF_ONGROUND;
self.flags &= ~FL_ONGROUND;
self.pmove_flags |= PMF_JUMP_HELD;
self.button2 = 0;
#ifdef SSQC
// player jumping sound; copied into pmove from client.qc -- CEV
sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
#endif

// timers for all jumps -- CEV
self.doublejump_timer = PM_DOUBLEJUMP_WINDOW;
};

//----------------------------------------------------------------------
// PM_WallJump
//----------------------------------------------------------------------
void() PM_WallJump =
{
// are we in water, already waterjumping, already walljumping,
// or is jump being held?
if (self.waterlevel > WATERLEVEL_NONE ||
self.pmove_flags & PMF_WATERJUMPED ||
self.pmove_flags & PMF_JUMP_HELD ||
self.pmove_flags & PMF_WALLJUMPED)
{
/*
#ifdef SSQC
dprint ("PM_WallJump: returning in first block\n");
#endif
*/
return;
}

// mandatory interval between jump and walljump
// if (self.doublejump_timer <= PM_WALLJUMP_WINDOW)
// return;

if (self.velocity_z < PM_WALLJUMPLIMIT)
{
// falling too fast to walljump
// TODO CEV it would be a good idea to include a sound here.
/*
#ifdef SSQC
dprint ("PM_WallJump: falling too fast, returning\n");
#endif
*/
return;
}

// start at STEPHEIGHT + 1 to try and clear any steps -- CEV
local float f = 0;
local vector start = self.origin;
// start_z += PM_STEPHEIGHT + 1;
start_z = start_z + 12;
local vector end = start;

for (float i = 0; i < 4; i++)
{
// I've borrowed a little logic from Quake Champions Classic
// here, shoutout to RhapsodyInGeek, check out their good work
// https://github.com/RhapsodyInGeek/Quake-Champions-Classic
// v_forward and v_right should still correspond to the
// makevectors call from PM_Move -- CEV
if (i < 2)
if (input_movevalues_x == 0)
continue;
else
end = v_forward * 10;
if (i > 1)
if (input_movevalues_y == 0)
continue;
else
end = v_right * 10;
if (i == 1 || i == 3)
end = end - end * 2;

end = start + end;

// this ends up firing *a lot* when hopping through a map
// normally, unfortunately. traceline may be better here -- CEV
tracebox (start, PM_CROUCH_MIN, PM_CROUCH_MAX, end, TRUE, self);

// in order: we hit something, that something is vaguely
// vertical, we hit it at a speed greater than 0, and
// that something is within 20 (units?) -- CEV
if (trace_fraction < 1.0f &&
trace_plane_normal_z <= 0.7f &&
trace_plane_normal_z >= 0 &&
vlen(self.origin - trace_endpos) <= 20)
{
// If self.velocity * trace_plane_normal is less than
// zero that means the user is directing their movement
// *into* the wall instead of away from it. Could be
// useful later for vaulting or climbing. -- CEV
f = self.velocity * trace_plane_normal;
if (f >= 0)
{
// moving away from the wall
self.pmove_flags |= PMF_WALLJUMPED;
break;
}
#ifdef SSQC
else
{
dprint (sprintf("PM_WallJump: wallclimb "
"normalspeed %f\n", f));
}
#endif
}
}

if (self.pmove_flags & PMF_WALLJUMPED)
{
// bounce off the wall plane -- CEV
self.velocity_x += trace_plane_normal_x * PM_WALLJUMPFORCE;
self.velocity_y += trace_plane_normal_y * PM_WALLJUMPFORCE;

if (self.doublejump_timer > 0)
{
#ifdef SSQC
dprint ("PM_WallJump: wall double ");
#endif
self.velocity_z = PM_WALLJUMPDOUBLE;
}
else
{
#ifdef SSQC
dprint ("PM_WallJump: walljump ");
#endif
self.velocity_z = PM_WALLJUMPSPEED;
}

// manage flags & fields; PMF_WALLJUMPED is already set -- CEV
self.pmove_flags |= PMF_JUMP_HELD;
self.doublejump_timer = PM_DOUBLEJUMP_WINDOW;

// server-side stuff
#ifdef SSQC
dprint (sprintf("dist %g, normalspeed %g\n",
vlen(self.origin - trace_endpos), f));
self.button2 = 0;
sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);
#endif
}
};

//----------------------------------------------------------------------
// PM_WalkAccelerate
// Handle accel & friction for MOVETYPE_WALK entities (players). -- CEV
//----------------------------------------------------------------------
void(vector wishvel, float move_time) PM_WalkAccelerate =
{
float accel, friction, newspeed, speed, temp, wishspeed;

accel = friction = temp = 0;

wishspeed = vlen (wishvel);
vector wishdir = normalize (wishvel);

if (self.pmove_flags & PMF_PRE_MOVE)
{
if (self.pmove_flags & PMF_ONLADDER)
{
// ladder physics
// these currently stutter in high ping (or low netfps)
// situations because we rely on touch()ing the ladder
// trigger to set PMF_ONLADDER and the client doesn't
// know about the trigger brush yet -- TODO CEV
self.velocity *= 0.75;

if (input_buttons & 2 || self.button2 > 0 ||
input_movevalues_z > 0)
{
// PlayerClimb -- johnfitz
self.velocity_z = 160;
}
else if (input_movevalues_z < 0)
{
// PlayerClimbDown -- CEV
self.velocity_z = -160;
}
else
{
self.flags |= FL_JUMPRELEASED;
self.pmove_flags &= ~PMF_JUMP_HELD;
self.velocity_z = 0;
}

self.pmove_flags &= ~PMF_ONGROUND;
}
else
{
if (input_buttons & 2 || self.button2 > 0 ||
input_movevalues_z > 0)
{
// +jump was pressed
if (self.pmove_flags & PMF_ONGROUND)
// normal jump
PM_Jump (wishdir);
else if (world_walljump)
// walljump
PM_WallJump ();
}
else
{
self.flags |= FL_JUMPRELEASED;
}
}

if (self.pmove_flags & PMF_ONGROUND)
self.pmove_flags |= PMF_STARTGROUND;
else
self.pmove_flags &= ~PMF_STARTGROUND;
}

// Test STARTGROUND (the ONGROUND status post jump) to avoid
// a ground friction frame when chaining jumps together.
// Probably has unintended consequences. -- CEV
if (self.pmove_flags & PMF_STARTGROUND)
{
// we're on ground so pick accel and friction values -- CEV
accel = PM_GROUNDACCEL;
friction = PM_GROUNDFRICTION;

if (self.doublejump_timer > 0)
wishspeed = min (wishspeed, PM_BOOSTWISHSPEED);
else
wishspeed = min (wishspeed, PM_MAXSPEED);
}
else if (self.pmove_flags & PMF_ONGROUND)
{
// we're in the second acceleration pass and we've just
// landed. For this one-half acceleration frame set
// friction to zero and accel to AIRACCELQ3. This
// moment is particularly noticeable on stairs. -- CEV
accel = PM_AIRACCELQ3;
friction = 0;

// rewrite user input to direct movement along current
// velocity in the half-frame after we've just landed.
// will only rewrite when neither +fwd or +back is
// pressed. the neverending quest to improve stairs. -- CEV
if (input_movevalues_y && input_movevalues_x == 0)
{
input_movevalues_x = fabs (input_movevalues_y);

// we've altered input movevalues so rework wishvel
// start by ensuring v_ globals are correct
makevectors ([0, input_angles_y, 0]);
wishvel = v_forward * input_movevalues_x;
wishvel += v_right * input_movevalues_y;
// is wishvel now against self.velocity?
if (wishvel * self.velocity < 0)
{
// then movevalues_x should be negative
input_movevalues_x = -input_movevalues_x;
wishvel = v_forward * input_movevalues_x;
wishvel += v_right * input_movevalues_y;
}
wishspeed = vlen (wishvel);
wishdir = normalize (wishvel);
}

if (self.doublejump_timer > 0)
wishspeed = min (wishspeed, PM_BOOSTWISHSPEED);
else
wishspeed = min (wishspeed, PM_MAXSPEED);
}
else if (input_movevalues_x == 0)
{
// Q1 air accel when requesting sideways movement in the air
// flag a check below with this -1 -- CEV
if (wishspeed > PM_MAXSPEED)
wishspeed = PM_MAXSPEED;

accel = -1;
}
else
{
if (wishspeed > PM_MAXSPEED)
wishspeed = PM_MAXSPEED;

if (input_movevalues_y == 0)
{
// Painkiller style air control
if (wishvel * self.velocity < 0)
// against original velocity; we're slowing down
accel = PM_AIRACCELBACK;
else
// we're with original velocity - gaining speed
accel = PM_AIRACCELFWD;

// borrow the friction var to flag a conditional below
friction = -1;
}
else
{
// we're in the air and the player is requesting both
// forward/back and sideways movement, so pick the Q3
// air accel value -- CEV
accel = PM_AIRACCELQ3;
}
}

// I've duplicated a fair amount of code below, inlining the various
// acceleration functions to hopefully improve performance -- CEV

// apply friction (ground or otherwise)
if (friction > 0)
{
// inline PM_Friction
speed = vlen (self.velocity);

if (speed < 1)
{
self.velocity = '0 0 0';
}
else
{
// calculate what their new speed should be
temp = speed < PM_STOPSPEED ? PM_STOPSPEED : speed;
newspeed = speed - temp * friction * move_time;

// and slow them
if (newspeed < 0)
newspeed = 0;

self.velocity *= newspeed / speed;
}
}

if (accel == -1)
{
// Quake 1 sideways air acceleration; inline PM_AirAccelerate
wishvel_z = 0;
temp = vlen (wishvel);
wishvel = normalize (wishvel);
if (temp > PM_MAXAIRSPEED)
temp = PM_MAXAIRSPEED;
speed = self.velocity * wishvel;
speed = temp - speed;

if (speed > 0)
{
newspeed = PM_AIRACCEL * move_time * wishspeed;
if (newspeed > speed)
newspeed = speed;
self.velocity += newspeed * wishvel;
}
}
else if (accel > 0)
{
// ground & Quake 3 style acceleration; inline PM_Accelerate
speed = wishspeed - (self.velocity * wishdir);

if (speed > 0)
{
newspeed = accel * move_time * wishspeed;
if (newspeed > speed)
newspeed = speed;

self.velocity += newspeed * wishdir;
}
}

// CPM & PK-style air control, needs to happen after PM_Accelerate
// based on code from Nexuiz & Xonotic
if (friction == -1)
{
// inline PM_AirControl
temp = self.velocity_z;
self.velocity_z = 0;

speed = vlen (self.velocity);
self.velocity = normalize (self.velocity);

// reuse the friction float for dotproduct -- CEV
friction = self.velocity * wishdir;

// reuse the accel var for turning -- CEV
accel = PM_AIRACCELBASE * bound (0, wishspeed / PM_MAXSPEED, 1);
accel *= PM_AIRACCELTURN * friction * friction * move_time;

if (friction > 0)
{
self.velocity = normalize (self.velocity * speed +
wishdir * accel);
}

self.velocity *= speed;
self.velocity_z = temp;
}

if (!(self.pmove_flags & PMF_PRE_MOVE))
self.pmove_flags &= ~PMF_STARTGROUND;
};

//----------------------------------------------------------------------
// PM_SwimAccelerate
// Largely copied from id Software's WaterMove. Works like NoClipAccelerate.
//----------------------------------------------------------------------
void(vector wishvel, float move_time) PM_SwimAccelerate =
{
float wishspeed;

if (self.pmove_flags & PMF_PRE_MOVE)
{
// CheckWaterJump
if (self.waterlevel == WATERLEVEL_WAIST)
{
local vector start, end;

start = self.origin;
start_z = start_z + 8;
// use wishvel instead of v_forward -- CEV
end = [wishvel_x, wishvel_y, 0];
end = normalize (end);
traceline (start, (start + end * 24), TRUE, self);

if (trace_fraction < 1)
{
// solid at the waist
start_z = start_z + self.maxs_z - 8;
self.movedir = trace_plane_normal * -50;
traceline (start, (start + end * 24),
TRUE, self);

if (trace_fraction == 1)
{
// open at eye level
self.flags |= FL_WATERJUMP;
self.pmove_flags |= PMF_WATERJUMPED;
self.flags &= ~FL_JUMPRELEASED;
self.pmove_flags &= ~PMF_JUMP_HELD;
// Z velocity was 225
self.velocity_z = PM_JUMPSPEED;
// we don't care about the 'safety net'
// in this movement set (unlike vanilla)
// so don't set teleport_time -- CEV
// self.teleport_time = time + 2;
}
}
}

#ifdef SSQC
// functionality copied into pmove from client.qc -- CEV
if (self.waterlevel >= WATERLEVEL_WAIST)
{
if (self.swim_flag < time)
{
// play swimming sound
self.swim_flag = time + 1;
if (random() < 0.5)
sound (self, CHAN_BODY,
"misc/water1.wav", 1,ATTN_NORM);
else
sound (self, CHAN_BODY,
"misc/water2.wav", 1,ATTN_NORM);
}
}
#endif
}

if (input_buttons & 2 && !(self.pmove_flags & PMF_WATERJUMPED))
// smartjump
wishvel_z = max (PM_MAXSPEED, wishvel_z);
else if (input_movevalues == '0 0 0')
// drift towards bottom -- CEV
wishvel_z -= PM_WATERSINKSPEED;

wishspeed = vlen (wishvel);
if (wishspeed > PM_WATERMAXSPEED)
wishspeed = PM_WATERMAXSPEED;
wishvel = normalize (wishvel);

// water friction; reuse PM_Friction with a special constant -- CEV
if (!(self.pmove_flags & PMF_WATERJUMPED))
PM_Friction (PM_WATERFRICTION, move_time);

// accelerate; reuse PM_Accelerate with a special constant -- CEV
PM_Accelerate (wishvel, wishspeed, PM_WATERACCEL, move_time);
};

//----------------------------------------------------------------------
// PM_NoClipAccelerate
//
// Basic acceleration for flying / noclipping players. Not suitable for
// swimming. -- CEV
//----------------------------------------------------------------------
void(vector wishvel, float scale, float move_time) PM_NoClipAccelerate =
{
float wishspeed;

// smartjump
if (input_buttons & 2)
wishvel_z = max (PM_MAXSPEED, wishvel_z);

wishspeed = vlen (wishvel) * scale;
wishvel = normalize (wishvel);

PM_Friction (PM_GROUNDFRICTION, move_time);
PM_Accelerate (wishvel, wishspeed, PM_GROUNDACCEL, move_time);
};

//----------------------------------------------------------------------
void(float move_time) PM_ManageTimers =
{
if (self.doublejump_timer > 0 && move_time > 0)
self.doublejump_timer -= move_time;
else if (self.doublejump_timer < 0)
self.doublejump_timer = 0;

if (self.doublejump_timer == 0)
self.pmove_flags &= ~PMF_DOUBLEJUMPED;
};

//----------------------------------------------------------------------
// PM_Move -- PMOVE entrypoint -- CEV
//----------------------------------------------------------------------
void(entity target) PM_Move =
{
if (input_timelength <= 0)
// don't process partial frames -- CEV
return;

vector wishvel;

// Nudge player's origin if the server is sending low-precision
// (16 bit?) player coordinates. This can be fixed by setting
// sv_bigcoords to 1 (so the server sends float coords). -- CEV
if (!world_bigcoords)
PM_Nudge ();

// rework user input, copying something like QuakeSpasm's always run
// feature here. This turns the +speed button into a hold-to-walk
// button when cl_run is 0 (when FTE's always run setting is on).
// -- CEV
if (world_clrun == FALSE)
{
local float m;

// we care about x and y, not so much z -- CEV
if (input_movevalues_x)
{
m = fabs (input_movevalues_x);
if (m > 400.0f)
input_movevalues_x = PM_WALKSPEED *
(input_movevalues_x / m);
else if (m == 400.0f)
input_movevalues_x = world_clforwardspeed *
(input_movevalues_x / m);
/*
else if (m != 0)
input_movevalues_x = PM_RUNSPEED *
(input_movevalues_x / m);
*/
}

if (input_movevalues_y)
{
m = fabs (input_movevalues_y);
if (m > 400.0f)
input_movevalues_y = PM_WALKSPEED *
(input_movevalues_y / m);
else if (m == 400.0f)
input_movevalues_y = world_clsidespeed *
(input_movevalues_y / m);
/*
else if (m != 0)
input_movevalues_y = PM_RUNSPEED *
(input_movevalues_y / m);
*/
}
}

// TODO CEV this is the wrong place to clear PMF_JUMP_HELD
if (!(input_buttons & 2))
self.pmove_flags &= ~PMF_JUMP_HELD;

// figure out the properties of the player's position, then clear timers
PM_CategorizePosition ();
PM_ManageTimers (0);

switch (self.movetype)
{
case MOVETYPE_WALK:
// signal acceleration functions that we're pre-move
self.pmove_flags |= PMF_PRE_MOVE;

// split the acceleration in two to reduce
// framerate dependence. -- CEV
if (self.waterlevel >= WATERLEVEL_WAIST)
{
// figure out wishvel -- CEV
makevectors (input_angles);
wishvel = v_forward * input_movevalues_x +
v_right * input_movevalues_y +
v_up * input_movevalues_z;

// swim acceleration
PM_SwimAccelerate (wishvel,
input_timelength * 0.5f);
}
else
{
// only yaw matters here -- CEV
makevectors ([0, input_angles_y, 0]);
wishvel = v_forward * input_movevalues_x +
v_right * input_movevalues_y;
// don't need wishvel_z -- CEV
// v_up * input_movevalues_z;

PM_WalkAccelerate (wishvel,
input_timelength * 0.5f);

// WalkAccelerate calls Jump which might
// alter pmove_flags, so do our feature
// checks afterwards -- CEV

// stick to the ground if we've jumped and
// haven't yet doublejumped
if (self.doublejump_timer > 0 &&
!(self.pmove_flags & PMF_WALLJUMPED) &&
!(self.pmove_flags & PMF_DOUBLEJUMPED))
{
self.pmove_flags |= PMF_SLIDE_STICKY;
}

// apply gravity only when in the air, on
// a ramp, or in the 'sticky' state -- CEV
if (!(self.pmove_flags & PMF_ONGROUND) ||
self.pmove_flags & PMF_SLIDE_STICKY ||
self.groundnormal_z < 1.0f)
{
self.pmove_flags |= PMF_SLIDE_GRAVITY;
}

// skim / wallclipping checks -- CEV
if (self.doublejump_timer >
(PM_DOUBLEJUMP_WINDOW -
PM_WALLCLIP_WINDOW) &&
!(self.pmove_flags & PMF_WALLJUMPED) &&
self.teleport_time <= (time - 0.1))
{
self.pmove_flags |= PMF_SLIDE_SKIM;
}

if (self.pmove_flags & PMF_ONLADDER)
{
// feature set for ladders -- CEV
self.pmove_flags &= ~PMF_SLIDE_GRAVITY;
self.pmove_flags &= ~PMF_SLIDE_SKIM;
self.pmove_flags &= ~PMF_SLIDE_STICKY;
}
}

// timers (part 1) -- CEV
PM_ManageTimers (input_timelength * 0.5f);

// Do the move. Bounce, Rock, Skate, Roll -- CEV
self.pmove_flags |= PMF_SLIDE_STEP;
PM_DanceMove ();

// signal accel functions we're post-move
self.pmove_flags &= ~PMF_PRE_MOVE;

// second pass at acceleration
if (self.waterlevel >= WATERLEVEL_WAIST)
PM_SwimAccelerate (wishvel,
input_timelength * 0.5f);
else
PM_WalkAccelerate (wishvel,
input_timelength * 0.5f);

// timers (part 2) -- CEV
PM_ManageTimers (input_timelength * 0.5f);

// clear ladder flag -- CEV
if (self.pmove_flags & PMF_ONLADDER)
self.pmove_flags &= ~PMF_ONLADDER;
break;

case MOVETYPE_FLY:
// split acceleration in two to reduce framerate
// dependence. -- CEV
makevectors (input_angles);
wishvel = v_forward * input_movevalues_x;
wishvel += v_right * input_movevalues_y;
wishvel += v_up * input_movevalues_z;

PM_NoClipAccelerate (wishvel, 1.0f,
input_timelength * 0.5f);
self.pmove_flags |= PMF_SLIDE_STEP;
PM_DanceMove ();
PM_NoClipAccelerate (wishvel, 1.0f,
input_timelength * 0.5f);
break;

case MOVETYPE_NOCLIP:
// noclip is a debugging feature, no need to
// worry about acceleration consistency. -- CEV
makevectors (input_angles);
wishvel = v_forward * input_movevalues_x;
wishvel += v_right * input_movevalues_y;
wishvel += v_up * input_movevalues_z;

PM_NoClipAccelerate (wishvel, 1.0f, input_timelength);
// we're noclipping so update origin directly -- CEV
self.origin += self.velocity * input_timelength;
break;

case MOVETYPE_NONE:
break;
}

// make sure we've touched the ground entity (it might've slipped
// through the slidemove above) -- CEV
PM_DoTouch (self.groundentity);

touchtriggers ();
};

Return to the top of this page or return to the overview of this repo.

Log pmove.qc

Return to the top of this page or return to the overview of this repo.