Git Repos / fte_dogmode / qc / pmove.qc
Last update to this file was on 2024-09-17 at 12:58.
Show pmove.qc
//==============================================================================
// PMOVE
//==============================================================================
// TODO CEV: improved player unstick function
// TODO CEV: varied sounds (different jump sounds)
// TODO CEV: fix telejump check (teleporter time isn't networked to CSQC)
//======================================================================
// globals
//======================================================================
#if defined(CSQC) || defined (SSQC)
// 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
#endif
//======================================================================
// fields
//======================================================================
#if defined(CSQC) || defined(SSQC)
// note: timers are expensive, require synch between server and client -- CEV
// .entity groundentity; // already defined in entvars_t
.vector groundnormal; // ground plane normal; NOT networked
.float pm_flags; // custom movement flags -- CEV
.float pm_timer; // crouchslide & jump timer -- CEV
.void() customphysics;
#endif
//======================================================================
// pmove constants (could be reworked as cvars, would add overhead) -- CEV
//======================================================================
#if defined(CSQC) || defined(SSQC)
// acceleration & friction
const float PM_AIRACCELY = 70.0f; // (320 / 30) * 6.5625; 106.666 in Q1
const float PM_AIRACCELXY = 1.0f; // for Q3 strafejumping; 1.0 in Q3
const float PM_AIRACCELXFWD = 1.0f; // 1 feels close to Q3 / CPM
const float PM_AIRACCELXBACK = 2.5f; // Air stop speed in Q3? 5.0f?
const float PM_AIRACCELBASE = 32.0f; //
const float PM_AIRACCELTURN = 250.0f; // affects +fwd turning radius; 150.0f
const float PM_GROUNDACCEL = 10.0f; // 10 is Q1 & VQ3, 15 is CPM
const float PM_GROUNDFRICTION = 6.0f; // 4 for Q1, 6 for Q3, 8 for (old?) CPM
const float PM_SLIDEACCELY = 70.0f; //
const float PM_SLIDEACCELXY = 1.0f; // crouchslide accel
const float PM_SLIDEFRICTION = 0.25f; // crouchslide friction; 1.0?
const float PM_SURFACCELY = 106.666f; // (320 / 30) * 10.0
const float PM_SURFACCELXY = 1.0f; //
const float PM_WATERACCEL = 10.0f; // water acceleration
const float PM_WATERFRICTION = 4.0f; // friction in water
// horizontal speeds (mostly)
const float PM_CROUCHSPEED = 120.0f; // ???
const float PM_CROUCHAIRSPEED = 11.25f; // PM_CROUCHSPEED / 10.666
const float PM_MAXSPEED = 320.0f; // 320 always
const float PM_MAXAIRSPEED = 30.0f; // 30 for Q1 (PM_MAXSPEED / 10.666)
const float PM_MAXWISHSPEED = 400.0f; // 320, 400
const float PM_RUNSPEED = 320.0f; // run speed; same as MAXSPEED
const float PM_RUNAIRSPEED = 30.0f; // PM_RUNSPEED / 10.666
const float PM_STOPSPEED = 100.0f; // used in friction calculations
const float PM_WALKSPEED = 200.0f; // walk speed
const float PM_WALKAIRSPEED = 18.75f; // PM_WALKSPEED / 10.666
const float PM_WATERMAXSPEED = 224.0f; // id1 224; 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_CLIMBSPEEDMIN = 45.0f; // min ladder & climbing speed
const float PM_CLIMBSPEEDMAX = 160.0f; // max climbing speed; Rubicon2 160
const float PM_CLIMBACCEL = 45.0f; // affected by CLIMBSCALE
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_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
const float PM_WALLJUMPLIMIT = -180.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 (in seconds)
const float PM_CROUCHSLIDE_TIME = 1.0f; // crouchslide total duration
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_WALLCLIP_WINDOW = 0.15f; // 0.4 - 0.25
// button mapping (should be moved elsewhere, somewhere more global)
const float PM_BTN_ATTACK = INPUT_BUTTON0;
const float PM_BTN_DOWN = INPUT_BUTTON8;// crouch key
const float PM_BTN_JUMP = INPUT_BUTTON2;// jump key
const float PM_BTN_WALK = INPUT_BUTTON7;// walk key
// misc
const float PM_CLIMBSCALE = 0.75; // scale XY vel by this while climbing
const float PM_EPSILONF = 0.125f; // 1.0 / 8.0; minimum float precision
const float PM_MAX_CLIP_PLANES = 3; // counting ground
const float PM_MAXVELOCITY = 4095; // 32768 / 8.0; vel networked as short
const float PM_OVERCLIP = 1.0f; // Q3 OVERCLIP is 1.001f, Q1 1.0f
const float PM_STEPHEIGHT = 18.0f; // 18 for Q1, 22 for later games?
const float PM_PLANE_FLAT = 1.0f; // flat horizontal
const float PM_PLANE_GROUND = 0.7f; // above this is ONGROUND
const float PM_PLANE_VERTICAL = 0.0f; // straight up vertical
const vector PM_TELEDROP = '0 0 -64'; // drop teleporter exit to floor if
#if defined(SSQC) // floor is within this distance
const float PM_MOVEFLAGS = MOVE_NORMAL;
#elif defined(CSQC)
const float PM_MOVEFLAGS = MOVE_NORMAL; // is MOVE_LAGGED supported in CSQC?
#endif
// 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 -24';
const vector PM_CROUCH_MAX = '16 16 16'; // Q3 crouch MAX_z is 16 -- CEV
// Player View Offset
// Q1 Ranger's view offset is lower than Q3; is Q1 Ranger canonically shorter?
const vector PM_STAND_VIEWOFS = '0 0 22'; // Q3 default 0 0 26 -- CEV
const vector PM_CROUCH_VIEWOFS = '0 0 8'; // Q3 crouch 0 0 12 -- CEV
// PM_SetOnGround flags; positive values are assumed to be SSQC entnums
const float PMG_BLANK = -1; // ignored
const float PMG_MIDMOVE = -2; // called during DanceMove
const float PMG_TRACEGROUND = -3; // run a trace looking for ground
const float PMG_NOGROUND = -4; // skip checks and clear ground flags
// pmove_flags is used by the engine; FTE defines two constants:
// PMF_JUMP_HELD = 1, PMF_LADDER = 2. So those two constants should
// be first (and in that order) in our enum below. -- CEV
// this is currently 18 flags; max flags for a float is 23 -- 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_ONRAMP, // entity is on a ramp
PMF_ONSLOPE, // entity is on a steep slope
PMF_ONSLICK, // entity is on a slick (icy) surface
PMF_CROUCH_HELD, // player is holding the crouch key
PMF_CROUCHED, // entity is crouching
PMF_CROUCHSLIDE, // entity is crouch sliding
PMF_WALK_HELD, // player is holding the walk key
PMF_STEPPED, // entity has stepped up
PMF_AIRSTEPPED, // entity has airstepped
PMF_DOUBLEJUMPED, // entity has doublejumped
PMF_WALLJUMPED, // entity has walljumped
PMF_WATERJUMPED, // entity has waterjumped
PMF_CLIMB, // entity is climbing
PMF_PUSHED, // entity moved by a trigger_push
PMF_SLIDE_STEP // slidemove hint to traverse steps
};
// remove these flags when landing on ground -- CEV
const float PM_ONGROUNDFLAGS = PMF_ONSLOPE | PMF_CLIMB | PMF_PUSHED |
PMF_WALLJUMPED;
// remove these flags when landing on a steep slope -- CEV
const float PM_ONSLOPEFLAGS = PMF_ONSLICK | PMF_ONRAMP | PMF_ONGROUND;
// remove these flags when not on ground -- CEV
const float PM_NOGROUNDFLAGS = PMF_ONGROUND | PMF_ONRAMP | PMF_ONSLICK |
PMF_ONSLOPE;
// don't transmit these flags from server to client -- CEV
const float PM_SLIDEFLAGS = PMF_SLIDE_STEP;
#endif
//======================================================================
// forward declarations
//======================================================================
#if 0
vector(vector vel, vector normal, float overbounce) PM_ClipVelocity;
float(float maxspeed, float a, float c, float t) PM_MaxCircleGroundSpeed;
#endif
#if defined(CSQC) || defined (SSQC)
static void(entity ent) PM_DoTouch;
vector(vector v) PM_TruncateVectorToEighth;
void() PM_CheckVelocity;
float() PM_Nudge;
void(float pmg_flag) PM_SetOnGround;
void() PM_SetNoClipFlags;
void() PM_CategorizePosition;
void() PM_DanceMove;
void() PM_CrouchStart;
void() PM_CrouchStop;
void() PM_CrouchSlideStart;
void() PM_CrouchSlideStop;
void() PM_Jump;
void() PM_WallClimb;
void() PM_WallJump;
void() PM_WallJumpCheck;
void(vector wishvel, float scale, float move_time) PM_NoClipAccelerate;
void(vector wishvel, float move_time) PM_SwimPreMove;
void(vector wishvel, float move_time) PM_SwimAccelerate;
void(vector wishvel, float move_time) PM_WalkPreMove;
void(vector wishvel, float move_time) PM_WalkAccelerate;
void(float move_time) PM_ManageTimer;
void(entity e) PM_Move;
#endif
//------------------------------------------------------------------------------
#if 0
//----------------------------------------------------------------------
// PM_ClipVelocity -- slide off the impacting surface -- CEV
//----------------------------------------------------------------------
vector(vector vel, vector normal, float overbounce) PM_ClipVelocity =
{
// the same implementation used in Quake 3 -- CEV
local float backoff = vel * normal;
if (backoff < 0)
backoff *= overbounce;
else
backoff /= overbounce;
vel -= normal * backoff;
return vel;
};
//----------------------------------------------------------------------
// 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://github.com/5xp/strafe-theory
//
// 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))));
};
#endif
#if defined(CSQC) || defined (SSQC)
//----------------------------------------------------------------------
// PM_DoTouch
//----------------------------------------------------------------------
static void(entity ent) PM_DoTouch =
{
if (ent.touch != __NULL__)
{
if (ent.touch != sub_null)
{
local entity stemp = self;
local entity otemp = other;
other = self;
self = ent;
self.touch ();
self = stemp;
other = otemp;
}
}
};
//----------------------------------------------------------------------
// PM_TruncateVector - truncate a vector to 0.125 precision -- CEV
//----------------------------------------------------------------------
vector(vector v) PM_TruncateVectorToEighth =
{
v_x = (floor(v_x * 8 + (1.0 / 16))) * PM_EPSILONF;
v_y = (floor(v_y * 8 + (1.0 / 16))) * PM_EPSILONF;
v_z = (floor(v_z * 8 + (1.0 / 16))) * PM_EPSILONF;
return v;
};
//----------------------------------------------------------------------
// PM_CheckVelocity - bound self.velocity to server maximums -- CEV
//----------------------------------------------------------------------
void() PM_CheckVelocity =
{
self.velocity_x = bound (-PM_MAXVELOCITY, self.velocity_x,
PM_MAXVELOCITY);
self.velocity_y = bound (-PM_MAXVELOCITY, self.velocity_y,
PM_MAXVELOCITY);
self.velocity_z = bound (-PM_MAXVELOCITY, self.velocity_z,
PM_MAXVELOCITY);
};
//----------------------------------------------------------------------
// 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 =
{
vector test, org;
test = org = self.origin;
// check current position.
tracebox (org, self.mins, self.maxs, org, PM_MOVEFLAGS, self);
if (!trace_startsolid)
return TRUE;
// truncate to network accuracy
org = PM_TruncateVectorToEighth (org);
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,
PM_MOVEFLAGS, self);
if (!trace_startsolid)
{
// okay, that'll do
self.origin = test;
return TRUE;
}
}
}
}
self.origin = org;
return FALSE;
};
//----------------------------------------------------------------------
// PM_SetOnGround -- manage ground flags and fields
// arguments in order are: ground entity, ground plane, ground trace
// end position, ground entity num (or PMG_* flag if negative) -- CEV
//----------------------------------------------------------------------
void(float pmg_flag) PM_SetOnGround =
{
// flagged to skip checks and clear ONGROUND -- CEV
if (pmg_flag == PMG_NOGROUND)
goto PM_SetOnGround_NoGround;
if (pmg_flag == PMG_TRACEGROUND)
{
// do a trace to check for ground -- CEV
tracebox (self.origin, self.mins, self.maxs,
self.origin - '0 0 0.25', PM_MOVEFLAGS, self);
}
// onground if we hit something & it faces upwards
if (trace_fraction < 1.0f)
{
if (trace_plane_normal_z > PM_PLANE_GROUND)
{
// on ground -- CEV
// start crouchsliding if: we just landed, we're
// holding crouch, we aren't already sliding, we're
// not in water, and we're going faster than run
// speed -- CEV
if (!(self.pm_flags & PMF_ONGROUND)) {
if (self.pm_flags & PMF_CROUCH_HELD) {
if (!(self.pm_flags & PMF_CROUCHSLIDE)) {
if (self.conlevel < WATERLEVEL_WAIST) {
if (self.speed > PM_RUNSPEED)
{
PM_CrouchSlideStart ();
} } } } }
// set groundentity and groundnormal -- CEV
self.groundentity = trace_ent;
self.groundnormal = trace_plane_normal;
// now manage flags -- CEV
if (pmg_flag != PMG_MIDMOVE)
{
self.flags |= FL_ONGROUND;
self.pm_flags |= PMF_ONGROUND;
}
if (self.groundnormal_z < PM_PLANE_FLAT)
self.pm_flags |= PMF_ONRAMP;
else if (self.pm_flags & PMF_ONRAMP)
self.pm_flags &= ~PMF_ONRAMP;
if (trace_surfaceflagsf & Q3SURFACEFLAG_SLICK)
// on a slick/icy surface -- CEV
self.pm_flags |= PMF_ONSLICK;
self.pm_flags = self.pm_flags -
(self.pm_flags & PM_ONGROUNDFLAGS);
}
else if (trace_plane_normal_z > PM_PLANE_VERTICAL)
{
// on a steep slope
// set groundentity and groundnormal -- CEV
self.groundentity = __NULL__;
self.groundnormal = '0 0 0';
// now do flags -- CEV
self.flags &= ~FL_ONGROUND;
self.pm_flags |= PMF_ONSLOPE;
self.pm_flags = self.pm_flags -
(self.pm_flags & PM_ONSLOPEFLAGS);
self.pm_timer = 0;
// don't crouchslide on a slope -- CEV
if (self.pm_flags & PMF_CROUCHSLIDE)
PM_CrouchSlideStop ();
}
else
{
// we probably shouldn't end up here -- CEV
goto PM_SetOnGround_NoGround;
}
#ifdef CSQC
// don't smooth out steps when on a network ent -- CEV
if (trace_networkentity > 0 || trace_ent.velocity != '0 0 0')
view_step_disable = TRUE;
else if (view_step_disable)
view_step_disable = FALSE;
#endif
}
else
{
// not on ground, clear pmove flags & fields -- CEV
PM_SetOnGround_NoGround:
self.groundentity = __NULL__;
self.groundnormal = '0 0 0';
self.flags &= ~FL_ONGROUND;
self.pm_flags = self.pm_flags -
(self.pm_flags & PM_NOGROUNDFLAGS);
// don't crouchslide in air -- CEV
if (self.pm_flags & PMF_CROUCHSLIDE)
{
if (self.pm_timer < 0)
self.pm_timer = 0;
PM_CrouchSlideStop ();
}
#ifdef CSQC
if (view_step_disable)
view_step_disable = FALSE;
#endif
}
};
//----------------------------------------------------------------------
void() PM_SetNoClipFlags =
{
// noclip is never on ground
if (self.groundentity != __NULL__)
self.groundentity = __NULL__;
if (self.groundnormal != '0 0 0')
self.groundnormal = '0 0 0';
if (self.pm_flags & PMF_DOUBLEJUMPED)
self.pm_flags &= ~PMF_DOUBLEJUMPED;
if (self.pm_flags & PMF_WALLJUMPED)
self.pm_flags &= ~PMF_WALLJUMPED;
if (self.pm_flags & PMF_ONGROUND)
self.pm_flags &= ~PMF_ONGROUND;
if (self.flags & FL_ONGROUND)
self.flags &= ~FL_ONGROUND;
if (self.pm_flags & PMF_PUSHED)
self.pm_flags &= ~PMF_PUSHED;
if (self.pm_flags & PMF_CLIMB)
self.pm_flags &= ~PMF_CLIMB;
self.pm_timer = 0;
};
//----------------------------------------------------------------------
// PM_CategorizePosition
// Based on similarly-named function in the GPL2 purecsqc pmove.qc
//----------------------------------------------------------------------
void() PM_CategorizePosition =
{
if (self.velocity_z > 180)
{
// if Z velocity is greater than 180 and we're not on a
// ramp or a steep slope then assume we're not onground
// (an optimization from Warsow / Warfork) -- CEV
if (self.pm_flags & PMF_ONRAMP)
PM_SetOnGround (PMG_TRACEGROUND);
else if (self.pm_flags & PMF_ONSLOPE)
PM_SetOnGround (PMG_TRACEGROUND);
else
PM_SetOnGround (PMG_NOGROUND);
}
else
{
// look for ground, manage flags & fields -- CEV
// setting onground here before the acceleration functions
// turns out to be faster (in my testing) than relying on
// DanceMove to correctly track ground state -- CEV
PM_SetOnGround (PMG_TRACEGROUND);
}
// set waterlevel and watertype -- CEV
base_entity_positioncontents (self);
// don't crouchslide in water -- CEV
if (self.pm_flags & PMF_CROUCHSLIDE) {
if (self.pm_flags & PMF_ONGROUND) {
if (self.conlevel >= WATERLEVEL_WAIST)
{
if (self.pm_timer < 0)
self.pm_timer = 0;
PM_CrouchSlideStop ();
} } }
// can't be waterjumping if we're on ground
if (self.pm_flags & PMF_WATERJUMPED) {
if (self.conlevel == WATERLEVEL_NONE || self.pm_flags & PMF_ONGROUND)
{
self.pm_flags &= ~PMF_WATERJUMPED;
self.flags &= ~FL_WATERJUMP;
} }
};
//----------------------------------------------------------------------
// 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).
//
// 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 =
{
local float grav = 0;
if (self.conlevel < WATERLEVEL_WAIST) {
if (!(self.pm_flags & PMF_ONLADDER)) {
if (!(self.pm_flags & PMF_ONGROUND) || self.pm_flags & PMF_ONRAMP)
{
if (self.gravity)
grav = self.gravity;
else
grav = 1.0;
// world_gravity is set in worldspawn() -- CEV
grav *= world_gravity * input_timelength;
// Half now, half later. Apparently affects framerate
// dependence. -- CEV
self.velocity_z -= grav * 0.5f;
} } }
if (self.velocity == '0 0 0')
// we aren't moving so skip to clearing flags -- CEV
goto PM_DanceMove_ClearFlags;
// declare and initialize variables -- CEV
local vector end, plane1, plane2, start_vel;
local float i, j, k, time_left;
local entity touched_ent;
local vector *jp = __NULL__;
end = plane1 = plane2 = '0 0 0';
i = j = k = 0;
time_left = input_timelength;
start_vel = self.velocity;
// attempt at most 4 moves, stopping early if time_left runs out,
// clipping velocity as we go -- CEV
for (i = 0; time_left > 0 && i < 4; i++)
{
// set our destination & test the move -- CEV
end = self.origin + (self.velocity * time_left);
tracebox (self.origin, self.mins, self.maxs, end,
PM_MOVEFLAGS, self);
if (trace_allsolid)
{
// self is in something else; attempt to nudge out
if (PM_Nudge())
continue;
else
setorigin (self, self.oldorigin);
// 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
self.velocity_z = 0;
dprint (sprintf(" timestamp %g\n", time));
break;
}
// accept the move -- CEV
self.origin = trace_endpos;
if (trace_fraction >= 1.0f)
// no obstructions, made the whole move -- CEV
break;
// save the trace ent for later then reduce time_left -- CEV
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.pm_flags & PMF_SLIDE_STEP) {
if (time_left > 0) {
if (trace_plane_normal_z == PM_PLANE_VERTICAL)
{
// store the entity and plane normal from above in
// case we need to reject the step attempt -- CEV
local vector first_plane = trace_plane_normal;
local entity first_ent = trace_ent;
local float first_frac = trace_fraction;
#ifdef CSQC
local float first_net = trace_networkentity;
#endif
j = time_left;
// first: move up
trace_endpos_z += PM_STEPHEIGHT;
tracebox (self.origin, self.mins, self.maxs,
trace_endpos, PM_MOVEFLAGS, self);
if (trace_allsolid)
goto PM_DanceMove_RejectStep;
local vector roof_plane = trace_plane_normal;
if (trace_fraction < 1.0f)
if (trace_ent.touch)
touched_ent = trace_ent;
// second: move forward
k = trace_endpos_z - self.origin_z;
end = trace_endpos + (self.velocity * j);
end_z = trace_endpos_z;
tracebox (trace_endpos, self.mins, self.maxs, end,
PM_MOVEFLAGS, self);
if (trace_allsolid)
goto PM_DanceMove_RejectStep;
// if we hit the same plane we're trying to step over
// and we made no progress in the move then reject
// the step attempt -- CEV
if (trace_plane_normal == first_plane) {
if (fabs(self.origin_x - trace_endpos_x) < PM_EPSILONF)
if (fabs(self.origin_y - trace_endpos_y) < PM_EPSILONF)
goto PM_DanceMove_RejectStep;
}
if (trace_fraction < 1.0f)
if (trace_ent.touch)
touched_ent = trace_ent;
local vector fwd_plane = trace_plane_normal;
j -= j * trace_fraction;
// third: move down
end = trace_endpos;
end_z -= k + 1;
tracebox (trace_endpos, self.mins, self.maxs, end,
PM_MOVEFLAGS, self);
// When airborne and directing momentum into the
// intersection of a step and a wall the down move
// will sometimes hit nothing (trace_fraction 1
// plane normal '0 0 0'). We *do not* want to reject
// the step when this happens. -- CEV
if (trace_fraction == 1.0f)
{
if (trace_plane_normal_z != 0)
goto PM_DanceMove_RejectStep;
}
else if (trace_plane_normal_z <= PM_PLANE_GROUND)
{
// this is the expected and regular
// not-good-ground step rejection -- CEV
goto PM_DanceMove_RejectStep;
}
if (trace_allsolid == FALSE)
{
// accept the stepped move. update time_left,
// origin, touched_ent, store any unique
// planes, then update flags -- CEV
time_left = j;
self.origin = trace_endpos;
if (trace_ent.touch)
touched_ent = trace_ent;
if (roof_plane != trace_plane_normal) {
if (roof_plane != plane1) {
if (roof_plane != plane2)
{
plane2 = plane1;
plane1 = roof_plane;
} } }
if (fwd_plane != trace_plane_normal) {
if (fwd_plane != plane1) {
if (fwd_plane != plane2)
{
plane2 = plane1;
plane1 = fwd_plane;
} } }
self.pm_flags |= PMF_STEPPED;
if (!(self.pm_flags & PMF_ONGROUND))
// this is nuts. for stairjumps. -- CEV
if (start_vel_z <= 0)
self.pm_flags |= PMF_AIRSTEPPED;
}
else
{
// discard the step attempt -- CEV
PM_DanceMove_RejectStep:
trace_plane_normal = first_plane;
touched_ent = trace_ent = first_ent;
trace_fraction = first_frac;
#ifdef CSQC
trace_networkentity = first_net;
#endif
}
} } }
// we've found a ground plane so call PM_SetOnGround -- CEV
if (trace_plane_normal_z > PM_PLANE_GROUND)
{
#ifdef SSQC
// impact info (see player_postthink) -- CEV
if (!(self.pm_flags & PMF_ONGROUND)) {
if (self.velocity_z < self.jump_flag)
{
self.jump_flag = self.velocity_z;
} }
#endif
PM_SetOnGround (PMG_MIDMOVE);
}
// clip to the plane if velocity interacts with it -- CEV
k = self.velocity * trace_plane_normal;
if (k < 0)
self.velocity -= trace_plane_normal * k;
// loop over stored planes clipping velocity as needed;
// this block may look familiar, it's similar to the
// strategy used in Quake3's SlideMove -- CEV
for (j = 0; j < PM_MAX_CLIP_PLANES; j++)
{
// no arrays were harmed in this loop -- CEV
if (j == 0) { jp = &self.groundnormal; }
else if (j == 1) { jp = &plane1; }
else if (j == 2) { jp = &plane2; }
// test if plane interacts & is not current plane
if (*jp == trace_plane_normal) { continue; }
k = self.velocity * *jp;
if (k >= 0) { continue; }
// clip to the plane -- CEV
self.velocity -= *jp * k;
// test if move goes back into the first plane -- CEV
if (self.velocity * trace_plane_normal >= 0)
continue;
// slide along crease ("><" is crossproduct) -- CEV
end = trace_plane_normal >< *jp;
end = normalize (end);
self.velocity = end * (end * self.velocity);
// see if we enter a third plane -- CEV
for (k = 0; k < PM_MAX_CLIP_PLANES; k++)
{
if (k == j) { continue; }
if (k == 0) { jp = &self.groundnormal; }
else if (k == 1) { jp = &plane1; }
else if (k == 2) { jp = &plane2; }
if (*jp == trace_plane_normal) { continue; }
if (self.velocity * *jp >= 0) { continue; }
// stop when 3 planes interact -- CEV
self.velocity = '0 0 0';
break;
}
if (self.velocity == '0 0 0') { break; }
}
// store current plane and plane1 for the next pass -- CEV
if (trace_plane_normal != self.groundnormal)
{
plane2 = plane1;
plane1 = trace_plane_normal;
}
// touch last in case doing so changes trace_plane_normal -- CEV
PM_DoTouch (touched_ent);
// stop if we've turned against original velocity -- CEV
if (self.velocity * start_vel <= 0)
self.velocity = '0 0 0';
// stop the loop if velocity is now zero -- CEV
if (self.velocity == '0 0 0')
break;
}
// if we're not onground and the timer is greater than
// PM_WALLCLIP_WINDOW (within 250ms of pressing jump)
// then restore velocity (to skim past walls) -- CEV
if (!(self.pm_flags & PMF_ONGROUND)) {
if (self.pm_timer > PM_WALLCLIP_WINDOW)
{
j = start_vel_z;
k = self.velocity_z;
self.velocity = start_vel;
// need the min of vel_z so headbump doublejumps work -- CEV
if (fabs(j) > fabs(k))
self.velocity_z = k;
else
self.velocity_z = j;
} }
// final gravity check here -- CEV
if (grav)
self.velocity_z -= grav * 0.5f;
// clip to the ground plane under certain complex conditions
// (this is done to recreate / simulate Q3/CPM stair behavior) -- CEV
if (self.pm_timer > 0) {
if (self.velocity_z) {
if (self.groundnormal_z > PM_PLANE_GROUND) {
if (!(self.pm_flags & PMF_CROUCH_HELD)) {
if (!(self.pm_flags & PMF_WALK_HELD)) {
if (!(self.pm_flags & PMF_DOUBLEJUMPED))
{
#if 0
dprint (sprintf("PM_DanceMove: glue %f\n", self.origin_z));
#endif
k = self.velocity * self.groundnormal;
self.velocity -= self.groundnormal * k;
} } } } } }
#if 0
if (i > 0)
dprint (sprintf("PM_DanceMove: move complete, "
"i %g, time_left %g, velocity_z %g\n",
i, time_left, self.velocity_z));
#endif
PM_DanceMove_ClearFlags:
// clear slide hint flags -- CEV
self.pm_flags = self.pm_flags - (self.pm_flags & PM_SLIDEFLAGS);
};
//----------------------------------------------------------------------
void() PM_CrouchStart =
{
// crouch
self.pm_flags |= PMF_CROUCHED;
// self.pm_flags |= PMF_CROUCH_HELD;
setsize (self, PM_CROUCH_MIN, PM_CROUCH_MAX);
self.view_ofs = PM_CROUCH_VIEWOFS;
#ifdef CSQC
// smooth crouching -- CEV
if (self.entnum == player_localentnum)
{
view_offset_old = PM_STAND_VIEWOFS_z;
view_offset_finished = time + PLAYER_CROUCH_SMOOTH;
}
#endif
};
//----------------------------------------------------------------------
void() PM_CrouchStop =
{
// uncrouch if we're clear to stand
tracebox (self.origin, PM_STAND_MIN, PM_STAND_MAX, self.origin,
PM_MOVEFLAGS, self);
if (trace_startsolid == FALSE) {
if (trace_allsolid == FALSE)
{
self.pm_flags &= ~PMF_CROUCHED;
setsize (self, PM_STAND_MIN, PM_STAND_MAX);
self.view_ofs = PM_STAND_VIEWOFS;
#ifdef CSQC
// smooth crouching -- CEV
if (self.entnum == player_localentnum)
{
view_offset_old = PM_CROUCH_VIEWOFS_z;
view_offset_finished = time + PLAYER_CROUCH_SMOOTH;
}
#endif
} }
};
//----------------------------------------------------------------------
// PM_CrouchSlideStart
// Crouchslide behavior is based on Rapha's Quake Champions movement
// tutorial found here: https://www.youtube.com/watch?v=95spyl1LRTc&t=1039s
// because I've never played QC (or Q4). Please note that it doesn't
// work exactly as described in that video. -- CEV
//----------------------------------------------------------------------
void() PM_CrouchSlideStart =
{
if (self.pm_timer >= 0)
{
#ifdef SSQC
#if 0
sound (self, CHAN_AUTO, "player/slidestart.ogg",
0.4, ATTN_FEET);
#endif
sound (self, CHAN_SLIDE, "player/slide.ogg",
0.2, ATTN_FEET);
#endif
self.pm_timer = -PM_CROUCHSLIDE_TIME;
}
#ifdef SSQC
dprint (sprintf("PM_CrouchSlideStart: lesgo %g\n", self.velocity_z));
#endif
self.pm_flags |= PMF_CROUCHSLIDE;
};
//----------------------------------------------------------------------
// PM_CrouchSlideStop
//----------------------------------------------------------------------
void() PM_CrouchSlideStop =
{
self.pm_flags &= ~PMF_CROUCHSLIDE;
#ifdef SSQC
sound (self, CHAN_SLIDE, "misc/null.wav", 0.4, ATTN_FEET);
#endif
};
//----------------------------------------------------------------------
// PM_Jump
//----------------------------------------------------------------------
void() PM_Jump =
{
// are we already waterjumping, or is jump being held?
if (self.pm_flags & PMF_WATERJUMPED)
return;
if (self.pm_flags & PMF_JUMP_HELD)
return;
#ifdef SSQC
local string wav = "";
local float vol = 0;
#endif
// make sure we get at least jumpspeed upwards from the ground
// plane by clipping velocity to it. necessary for rampjumps. -- CEV
if (self.groundnormal_z > PM_PLANE_GROUND)
if (self.velocity * self.groundnormal < 0)
self.velocity -= self.groundnormal *
(self.velocity * self.groundnormal);
// this changes the behavior of downward ramps -- CEV
if (self.velocity_z < 0)
self.velocity_z = 0;
if (self.pm_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
// non-additive jump, though it shouldn't matter -- CEV
self.velocity_z = PM_TELEJUMPSPEED;
}
else if (self.pm_flags & PMF_ONRAMP)
{
// the groundnormal is weird - a ramp - so we
// want an additive doublejump here -- CEV
self.velocity_z += PM_DOUBLEJUMPSPEED;
}
else
{
// flat ground, don't do an additive jump -- CEV
self.velocity_z = PM_STAIRJUMPSPEED;
}
#ifdef SSQC
vol = 0.9;
wav = "player/plyrjmp8.wav";
#endif
// set the doublejump flag -- CEV
self.pm_flags |= PMF_DOUBLEJUMPED;
}
else
{
// normal jump
#ifdef SSQC
local float r2 = rint (random() * 3);
vol = 0.1;
wav = sprintf ("player/jump0%g.ogg", r2 + 1);
#endif
if (self.pm_flags & PMF_ONRAMP)
{
// do an additive jump on non-flat ground -- CEV
self.velocity_z += PM_JUMPSPEED;
}
else
{
// ignore the jump input if the player has stepped
// up within the last frame (for CPM stairjumps)
// -- CEV
if (self.pm_flags & PMF_AIRSTEPPED)
self.velocity_z = PM_JUMPSPEED;
else if (self.pm_flags & PMF_CROUCH_HELD)
self.velocity_z = PM_JUMPSPEED;
else if (self.pm_flags & PMF_WALK_HELD)
self.velocity_z = PM_JUMPSPEED;
else if (!(self.pm_flags & PMF_STEPPED))
self.velocity_z = PM_JUMPSPEED;
#if 0
else
dprint (sprintf("PM_Jump: eating jump; %f\n",
self.origin_z));
#endif
}
}
// manage flags -- CEV
PM_SetOnGround (PMG_NOGROUND);
self.pm_flags |= PMF_JUMP_HELD;
if (self.pm_flags & PMF_CROUCHSLIDE)
PM_CrouchSlideStop ();
#ifdef SSQC
// player jumping sound; moved into pmove from client.qc -- CEV
if (wav != "")
sound (self, CHAN_BODY, wav, vol, ATTN_NORM);
player_footstep ();
#endif
// timers for all jumps -- CEV
self.pm_timer = PM_DOUBLEJUMP_WINDOW;
};
//----------------------------------------------------------------------
// PM_WallClimb -- climbing; checks performed in PM_WallJumpCheck -- CEV
//----------------------------------------------------------------------
void() PM_WallClimb =
{
// accelerate upwards toward a vertical ledge -- CEV
local float grav;
local float zspeed = self.velocity_z;
// counteract gravity with additional Z velocity -- CEV
if (self.gravity)
grav = self.gravity;
else
grav = 1.0f;
grav *= world_gravity * input_timelength;
zspeed += grav + PM_CLIMBACCEL;
self.velocity *= PM_CLIMBSCALE;
self.velocity_z = bound (PM_CLIMBSPEEDMIN, zspeed, PM_CLIMBSPEEDMAX);
// reset pm_timer so we don't stick to the floor -- CEV
self.pm_flags |= PMF_CLIMB;
self.pm_flags |= PMF_JUMP_HELD;
self.pm_timer = 0;
#if 0
dprint (sprintf("PM_WallClimb: vel_z %g\n", self.velocity_z));
#endif
};
//----------------------------------------------------------------------
// PM_WallJump -- Perform an actual walljump -- CEV
//----------------------------------------------------------------------
void() PM_WallJump =
{
#ifdef SSQC
local float vol = 0;
local string wav = "";
#endif
// bounce off the wall plane if Z velocity is positive -- CEV
// if we bounce off the wall plane when Z vel is negative it
// introduces a scenario where a player can fall off a ledge
// and then (for just a frame or two) jump off the wall
// behind them to gain additional XY speed. You can still
// walljump from that floor/wall you fell from now but you
// won't gain horizontal speed. (found this while attempting
// to circlejump over the center gap in CPM22) -- CEV
if (self.velocity_z > 0)
self.velocity += trace_plane_normal * PM_WALLJUMPFORCE;
if (self.pm_timer > 0)
{
#ifdef SSQC
vol = 0.9;
wav = "player/plyrjmp8.wav";
#endif
self.velocity_z = PM_WALLJUMPDOUBLE;
}
else
{
#ifdef SSQC
local float r = rint (random() * 3);
vol = 0.2;
wav = sprintf ("player/jump0%g.ogg", r + 1);
#endif
self.velocity_z = PM_WALLJUMPSPEED;
}
// manage flags & fields -- CEV
self.pm_flags |= PMF_JUMP_HELD;
self.pm_flags |= PMF_WALLJUMPED;
self.pm_timer = 0;
// server-side stuff
#ifdef SSQC
player_footstep ();
if (wav != "")
sound (self, CHAN_BODY, wav, vol, ATTN_NORM);
#endif
};
//----------------------------------------------------------------------
// PM_WallJumpCheck -- Wall jumping, mantling, and climbing -- CEV
//----------------------------------------------------------------------
void() PM_WallJumpCheck =
{
// are we in water, waterjumping, or falling too fast? -- CEV
if (self.conlevel > WATERLEVEL_NONE)
return;
if (self.pm_flags & PMF_WATERJUMPED)
return;
if (self.velocity_z < PM_WALLJUMPLIMIT)
return;
// are we within 32 units of the floor? -- CEV
tracebox (self.origin, self.mins, self.maxs, self.origin - '0 0 64',
PM_MOVEFLAGS, self);
local float grounddist = self.origin_z - trace_endpos_z;
if (grounddist <= PM_WALLJUMPGROUND)
if (trace_plane_normal_z > PM_PLANE_GROUND)
return;
local float i, checks;
local vector start, end;
checks = (self.pm_flags & PMF_WALLJUMPED ? 1 : 4);
start = end = self.origin;
for (i = 0; i < checks; i++)
{
// I've borrowed a little logic from Quake Champions Classic
// here, shoutout to RhapsodyInGeek, check out their good work
// https://github.com/Quake-Champions-Classic/mod
// v_forward and v_right should still be those set by the
// makevectors call from PM_Move -- CEV
if (i < 2)
if (input_movevalues_x == 0)
continue;
else
// check further when +back -- CEV
if (i == 0)
end = v_forward * 10;
else if (i == 1)
end = v_forward * 25;
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 (specifically when releasing jump button after
// a jump has registered). traceline would be better here
// except it complicates wall climbing. -- CEV
tracebox (start, PM_CROUCH_MIN, PM_CROUCH_MAX, end,
MOVE_NOMONSTERS | PM_MOVEFLAGS, self);
#if 0
dprint (sprintf("PM_WallJumpCheck: frac %g, norm_z %g, dist "
"%g\n", trace_fraction, trace_plane_normal_z,
vlen(self.origin - trace_endpos)));
#endif
// in order: we hit something and it's vaguely vertical -- CEV
if (trace_fraction < 1.0f) {
if (trace_plane_normal_z <= PM_PLANE_GROUND) {
if (trace_plane_normal_z >= PM_PLANE_VERTICAL)
{
if (!(self.pm_flags & PMF_JUMP_HELD)) {
if (!(self.pm_flags & PMF_WALLJUMPED)) {
if (self.velocity * trace_plane_normal >= 0)
{
// moving away from the wall, not holding
// jump, and haven't walljumped -- CEV
PM_WallJump ();
break;
} } }
if (i == 0) {
if (self.pm_flags & PMF_JUMP_HELD) {
if (self.speed <= PM_RUNSPEED) {
if (fabs(self.velocity_z) < 90.0f) {
if (grounddist > 42)
{
// holding jump and moving *into* the wall
// at a rate below MAXSPEED with a low Z
// velocity and at least 43 units away from
// the ground; attempt to climb -- CEV
local vector tpn_ang;
tpn_ang = vectoangles (trace_plane_normal);
// must be facing the impacted surface -- CEV
if (angledif(input_angles_y, tpn_ang_y) <= 150)
continue;
// we're looking for empty space, for a trace
// to not hit anything, so use tracelines
// below -- CEV
// horizontal forward at least 64u off floor
start_z += 22;
traceline (start, start + v_forward * 30,
MOVE_NOMONSTERS | PM_MOVEFLAGS, self);
if (trace_fraction >= 1.0f)
{
// Ranger's fingies can't grip a
// vertical surface -- CEV
traceline (trace_endpos,
trace_endpos - '0 0 64',
MOVE_NOMONSTERS | PM_MOVEFLAGS,
self);
if (trace_plane_normal_z >
PM_PLANE_GROUND)
{
PM_WallClimb ();
break;
}
}
// horizontal forward at least 96u off floor
start_z += 32;
traceline (start, start + v_forward * 30,
MOVE_NOMONSTERS | PM_MOVEFLAGS, self);
if (trace_fraction >= 1.0f)
{
traceline (trace_endpos,
trace_endpos - '0 0 64',
MOVE_NOMONSTERS | PM_MOVEFLAGS,
self);
if (trace_plane_normal_z >
PM_PLANE_GROUND)
{
PM_WallClimb ();
break;
}
}
start = self.origin;
} } } } }
} } }
// player can't hold on to thin air -- CEV
self.pm_flags &= ~PMF_CLIMB;
}
};
//----------------------------------------------------------------------
// PM_NoClipAccelerate -- for flying / noclip; not for swimming -- CEV
//----------------------------------------------------------------------
void(vector wishvel, float scale, float move_time) PM_NoClipAccelerate =
{
local vector wishdir;
local float newspeed, speed1, temp, wishspeed;
if (input_buttons & PM_BTN_JUMP)
// smartjump
wishvel_z = max (PM_MAXSPEED, wishvel_z);
else if (input_buttons & PM_BTN_DOWN)
// smartcrouch
wishvel_z = min (-PM_MAXSPEED, wishvel_z);
wishspeed = vlen (wishvel) * scale;
wishdir = normalize (wishvel);
// inline PM_Friction -- CEV
speed1 = vlen (self.velocity);
if (speed1 < PM_EPSILONF)
{
self.velocity = '0 0 0';
}
else
{
// calculate what their new speed should be & slow them
temp = speed1 < PM_STOPSPEED ? PM_STOPSPEED : speed1;
newspeed = speed1 - temp * PM_GROUNDFRICTION * move_time;
if (newspeed <= 0)
self.velocity = '0 0 0';
else
self.velocity *= newspeed / speed1;
}
// inline PM_Accelerate -- CEV
speed1 = wishspeed - (self.velocity * wishdir);
if (speed1 > 0)
{
newspeed = PM_GROUNDACCEL * move_time * wishspeed;
newspeed = min (newspeed, speed1);
self.velocity += newspeed * wishdir;
}
};
//----------------------------------------------------------------------
// PM_SwimPreMove -- Check for water jump, water wading sounds -- CEV
//----------------------------------------------------------------------
void(vector wishvel, float move_time) PM_SwimPreMove =
{
// CheckWaterJump
if (self.conlevel == 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.pm_flags |= PMF_WATERJUMPED;
self.flags &= ~FL_JUMPRELEASED;
self.pm_flags &= ~PMF_JUMP_HELD;
// Z velocity was 225
self.velocity_z = PM_JUMPSPEED;
}
}
}
#ifdef SSQC
// functionality copied into pmove from client.qc -- CEV
if (self.conlevel >= WATERLEVEL_WAIST) {
if (self.conlevel >= WATERLEVEL_EYES) {
if (self.swim_time < time)
{
// play swimming sound
self.swim_time = time + 1;
if (random() < 0.5)
sound (self, CHAN_BODY, "misc/water1.wav",
0.6, ATTN_NORM);
else
sound (self, CHAN_BODY, "misc/water2.wav",
0.6, ATTN_NORM);
} } }
#endif
if (self.pm_flags & PMF_STEPPED)
{
self.pm_flags &= ~PMF_STEPPED;
self.pm_flags &= ~PMF_AIRSTEPPED;
}
};
//----------------------------------------------------------------------
// PM_SwimAccelerate -- Largely copied from id Software's WaterMove -- CEV
//----------------------------------------------------------------------
void(vector wishvel, float move_time) PM_SwimAccelerate =
{
local vector wishdir;
local float newspeed, speed1, temp, wishspeed;
if (!(self.pm_flags & PMF_WATERJUMPED))
{
if (input_buttons & PM_BTN_JUMP)
// smartjump
wishvel_z = max (PM_MAXSPEED, wishvel_z);
else if (input_buttons & PM_BTN_DOWN)
// smartcrouch
// self.pm_flags |= PMF_CROUCH_HELD;
wishvel_z = min (-PM_MAXSPEED, wishvel_z);
else if (input_movevalues == '0 0 0')
// drift towards bottom -- CEV
wishvel_z -= PM_WATERSINKSPEED;
}
wishspeed = vlen (wishvel);
wishspeed = min (wishspeed, PM_WATERMAXSPEED);
wishdir = normalize (wishvel);
// inline PM_Friction -- CEV
if (!(self.pm_flags & PMF_WATERJUMPED))
{
speed1 = vlen (self.velocity);
if (speed1 < PM_EPSILONF)
{
self.velocity = '0 0 0';
}
else
{
// calculate what their new speed should be & slow them
temp = speed1 < PM_STOPSPEED ? PM_STOPSPEED : speed1;
newspeed = speed1 - temp * PM_WATERFRICTION * move_time;
if (newspeed <= 0)
self.velocity = '0 0 0';
else
self.velocity *= newspeed / speed1;
}
}
// inline PM_Accelerate -- CEV
speed1 = wishspeed - (self.velocity * wishdir);
if (speed1 > 0)
{
newspeed = PM_WATERACCEL * move_time * wishspeed;
newspeed = min (newspeed, speed1);
self.velocity += newspeed * wishdir;
}
};
//----------------------------------------------------------------------
// PM_WalkPreMove -- Check for crouching, jumping, ladders -- CEV
//----------------------------------------------------------------------
void(vector wishvel, float move_time) PM_WalkPreMove =
{
if (self.pm_flags & PMF_ONLADDER)
{
// ladder physics
local float zspeed = fabs(self.velocity_z) + PM_CLIMBACCEL;
self.velocity *= PM_CLIMBSCALE;
self.speed = vlen ([self.velocity_x, self.velocity_y, 0]);
if (input_buttons & PM_BTN_JUMP || input_movevalues_z > 0)
{
// PlayerClimb -- johnfitz
self.velocity_z = bound (PM_CLIMBSPEEDMIN, zspeed,
PM_CLIMBSPEEDMAX);
}
else if (input_buttons & PM_BTN_DOWN || input_movevalues_z < 0)
{
// PlayerClimbDown -- CEV
self.velocity_z = bound (-PM_CLIMBSPEEDMAX, -zspeed,
-PM_CLIMBSPEEDMIN);
}
else
{
self.flags |= FL_JUMPRELEASED;
self.pm_flags &= ~PMF_JUMP_HELD;
self.velocity_z = 0;
}
if (self.pm_flags & PMF_ONGROUND)
PM_SetOnGround (PMG_NOGROUND);
self.pm_timer = 0;
return;
}
if (input_buttons & PM_BTN_JUMP)
{
// +jump was pressed
if (self.pm_flags & PMF_ONGROUND)
PM_Jump ();
else if (world_walljump)
PM_WallJumpCheck ();
}
else
{
self.flags |= FL_JUMPRELEASED;
self.pm_flags &= ~PMF_JUMP_HELD;
}
if (input_buttons & PM_BTN_DOWN && !(self.pm_flags & PMF_CROUCHED))
{
PM_CrouchStart ();
}
else if (!(input_buttons & PM_BTN_DOWN) && self.pm_flags & PMF_CROUCHED)
{
PM_CrouchStop ();
}
if (self.pm_flags & PMF_STEPPED)
{
self.pm_flags &= ~PMF_STEPPED;
self.pm_flags &= ~PMF_AIRSTEPPED;
}
};
//----------------------------------------------------------------------
// PM_WalkAccelerate -- accel & friction for MOVETYPE_WALK -- CEV
//----------------------------------------------------------------------
void(vector wishvel, float move_time) PM_WalkAccelerate =
{
local float accel, friction, speed1, temp, wishyaw, wishspeed;
local vector wishdir = normalize (wishvel);
accel = friction = wishyaw = 0;
wishspeed = vlen (wishvel);
// determine the user's requested movement direction / angle.
// inspired by IsMoveInDirection from Nexuiz / Xonotic; this
// is slow and could be improved -- CEV
if (wishspeed)
{
// work out the requested movement direction -- CEV
#if 1
// vectoyaw ignores the Z component of its input -- CEV
wishyaw -= vectoyaw (input_movevalues);
wishyaw = fabs (wishyaw - 360.0f * rint(wishyaw / 360.0f));
// we don't care about 180 (backward) wishyaw; we'll test
// the dotproduct of wishvel and self.velocity later to
// determine if we're slowing down or speeding up -- CEV
if (wishyaw == 180)
wishyaw = 0;
#else
if (input_movevalues_x)
if (input_movevalues_y)
wishyaw = 45.0f;
else
wishyaw = 0;
else
wishyaw = 90.0f;
#endif
if (wishyaw == 0)
{
// test if the player is looking perpendicular to the
// direction they're moving in (+/- 30 degrees); if
// that's the case then alter wishyaw so the checks
// below will do Q1 air accel -- CEV
temp = input_angles_y - vectoyaw(self.velocity);
temp = fabs (temp - 360.0f * rint(temp / 360.0f));
if (temp > 60.0f)
if (temp < 120.0f)
wishyaw = 90.0f;
}
}
// priority and sequence of these checks is important -- CEV
if (self.pm_flags & PMF_ONGROUND)
{
if (self.pm_flags & PMF_ONSLICK)
{
// ice physics; friction remains 0 -- CEV
wishspeed = min (wishspeed, PM_MAXSPEED);
if (wishspeed) {
if (wishyaw == 90)
{
// Q1 air accel -- CEV
accel = PM_AIRACCELY;
wishspeed = min (wishspeed, PM_RUNAIRSPEED);
}
else if (wishyaw == 0)
{
// CPM / Painkiller style control -- CEV
if (wishvel * self.velocity < 0)
accel = PM_AIRACCELXBACK;
else
accel = PM_AIRACCELXFWD;
}
else
{
// regular ground accel -- CEV
accel = PM_AIRACCELXY;
} }
}
else if (self.pm_flags & PMF_CROUCHSLIDE)
{
// movement while crouchsliding -- CEV
wishspeed = min (wishspeed, PM_CROUCHSPEED);
// water level adds extra friction -- CEV
friction = PM_SLIDEFRICTION + self.conlevel;
if (wishspeed) {
if (wishyaw == 90)
{
// Q1 air accel -- CEV
// same behavior as when in the air -- CEV
accel = PM_SLIDEACCELY;
wishspeed = min (wishspeed, PM_MAXAIRSPEED);
}
else if (wishyaw == 0)
{
// CPM / Painkiller style control -- CEV
if (wishvel * self.velocity < 0)
accel = PM_AIRACCELXBACK;
else
accel = PM_AIRACCELXFWD;
}
else
{
// regular ground accel -- CEV
accel = PM_SLIDEACCELXY;
} }
}
else
{
// movement onground -- CEV
if (self.pm_flags & PMF_STEPPED)
// changing the friction constant is sketchy;
// it might help with navigating stairs -- CEV
friction = PM_GROUNDFRICTION * 0.1f;
else
friction = PM_GROUNDFRICTION;
if (wishspeed)
{
if (self.pm_flags & PMF_CROUCHED)
temp = PM_CROUCHSPEED;
else if (self.pm_flags & PMF_WALK_HELD)
temp = PM_WALKSPEED;
else if (self.pm_timer > 0)
temp = PM_MAXWISHSPEED;
else
temp = PM_RUNSPEED;
if (self.pm_timer > 0)
// go directly to PM_MAXWISHSPEED
// for stairjumps -- CEV
wishspeed = temp;
else
wishspeed = min (wishspeed, temp);
if (self.pm_flags & PMF_STEPPED)
accel = PM_GROUNDACCEL * 0.1f;
else
accel = PM_GROUNDACCEL;
}
}
}
else if (self.pm_flags & PMF_ONSLOPE)
{
// movement while surfing -- CEV
if (wishspeed)
{
if (wishyaw == 45 || wishyaw == 135)
{
// regular ground accel / strafejumping -- CEV
accel = PM_SURFACCELXY;
wishspeed = min (wishspeed, PM_MAXSPEED);
}
else
{
// Q1 style air accel -- CEV
accel = PM_SURFACCELY;
wishspeed = min (wishspeed, PM_MAXAIRSPEED);
}
}
}
else if (wishspeed)
{
// movement in the air -- CEV
if (wishyaw == 90)
{
// Q1 air accel -- CEV
accel = PM_AIRACCELY;
if (self.pm_flags & PMF_CROUCHED)
// temp = PM_CROUCHAIRSPEED;
temp = PM_RUNAIRSPEED;
else if (self.pm_flags & PMF_WALK_HELD)
temp = PM_WALKAIRSPEED;
else
temp = PM_RUNAIRSPEED;
wishspeed = min (wishspeed, temp);
}
else
{
if (self.pm_flags & PMF_CROUCHED)
temp = PM_CROUCHSPEED;
else if (self.pm_flags & PMF_WALK_HELD)
temp = PM_WALKSPEED;
else
temp = PM_MAXSPEED;
wishspeed = min (wishspeed, temp);
if (wishyaw == 0)
{
// CPM / Painkiller style air control -- CEV
if (wishvel * self.velocity < 0)
accel = PM_AIRACCELXBACK;
else
accel = PM_AIRACCELXFWD;
}
else
{
// X and Y in the air: strafejumping -- CEV
accel = PM_AIRACCELXY;
}
}
}
// inline PM_Friction -- CEV
if (friction > 0)
{
speed1 = vlen ([self.velocity_x, self.velocity_y, 0]);
if (speed1 < PM_EPSILONF)
{
self.velocity = '0 0 0';
}
else
{
// calculate what their new speed should be & slow them
if (self.pm_timer > 0)
temp = speed1;
else if (speed1 < PM_STOPSPEED)
temp = PM_STOPSPEED;
else
temp = speed1;
temp = speed1 - temp * friction * move_time;
if (temp <= 0)
self.velocity = '0 0 0';
else
self.velocity *= temp / speed1;
}
}
// inline PM_Accelerate -- CEV
if (accel > 0)
{
speed1 = wishspeed - (self.velocity * wishdir);
if (speed1 > 0)
{
// borrow / re-use the accel var -- CEV
accel = accel * move_time * wishspeed;
accel = min (accel, speed1);
self.velocity += accel * wishdir;
}
}
// inline PM_AirControl -- CEV
if (wishyaw == 0 || wishyaw == 180)
{
// borrow the friction var to hold Z speed -- CEV
friction = self.velocity_z;
self.velocity_z = 0;
speed1 = vlen (self.velocity);
self.velocity = normalize (self.velocity);
// borrow the accel var to hold dotproduct -- CEV
accel = self.velocity * wishdir;
if (accel > 0)
{
temp = PM_AIRACCELBASE;
temp *= bound (0, wishspeed / PM_MAXAIRSPEED, 1);
temp *= PM_AIRACCELTURN * accel * accel * move_time;
self.velocity = self.velocity * speed1 + wishdir * temp;
self.velocity = normalize (self.velocity);
}
self.velocity *= speed1;
self.velocity_z = friction;
}
#if 0
dprint (sprintf("PM_WalkAccelerate: velocity %v, speed %g, "
"wvel %v, wspeed %g\n",
self.velocity, vlen ([self.velocity_x, self.velocity_y, 0]),
wishvel, wish));
#endif
}
//----------------------------------------------------------------------
// PM_ManageTimer -- pos when jumping, neg when crouchsliding -- CEV
//----------------------------------------------------------------------
void(float move_time) PM_ManageTimer =
{
if (self.pm_timer)
{
if (self.pm_flags & PMF_CROUCHSLIDE)
{
self.pm_timer += move_time;
if (self.pm_timer >= 0)
self.pm_timer = 0;
}
else
{
self.pm_timer -= move_time;
if (self.pm_timer <= 0)
self.pm_timer = 0;
}
}
if (self.pm_flags & PMF_CROUCHSLIDE)
{
if (self.pm_timer >= 0)
PM_CrouchSlideStop ();
}
if (self.pm_flags & PMF_DOUBLEJUMPED)
{
if (self.pm_timer <= 0)
self.pm_flags &= ~PMF_DOUBLEJUMPED;
}
};
//----------------------------------------------------------------------
// PM_Move -- PMOVE entrypoint -- CEV
//----------------------------------------------------------------------
void(entity e) PM_Move =
{
// 'self' in this context is not reliable; it should be set to
// the entity passed to this function -- CEV
local entity oself = self;
self = e;
#ifdef CSQC
if (self.entnum == player_localentnum)
view_pl = self;
#endif
// 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 == FALSE)
PM_Nudge ();
if (self.velocity != '0 0 0')
self.velocity = PM_TruncateVectorToEighth (self.velocity);
// clear JUMP_HELD if it's set and the user isn't pressing jump
if (!(input_buttons & PM_BTN_JUMP))
self.pm_flags &= ~PMF_JUMP_HELD;
// crouch key, crouchsliding -- CEV
if (input_buttons & PM_BTN_DOWN)
{
self.pm_flags |= PMF_CROUCH_HELD;
}
else
{
self.pm_flags &= ~PMF_CROUCH_HELD;
if (self.pm_flags & PMF_CROUCHSLIDE)
PM_CrouchSlideStop ();
}
// walk key, walking -- CEV
if (input_buttons & PM_BTN_WALK)
self.pm_flags |= PMF_WALK_HELD;
else
self.pm_flags &= ~PMF_WALK_HELD;
local vector wishvel;
local float half_time = input_timelength * 0.5;
self.speed = vlen ([self.velocity_x, self.velocity_y, 0]);
if (self.movetype == MOVETYPE_WALK)
{
// figure out the properties of the player's position -- CEV
PM_CategorizePosition ();
// split acceleration in two to improve responsiveness -- CEV
if (self.conlevel >= WATERLEVEL_WAIST)
{
makevectors (input_angles);
wishvel = v_forward * input_movevalues_x +
v_right * input_movevalues_y +
v_up * input_movevalues_z;
// swim acceleration
PM_SwimPreMove (wishvel, half_time);
PM_SwimAccelerate (wishvel, half_time);
}
else
{
// only yaw matters here -- CEV
makevectors (input_angles_y * '0 1 0');
wishvel = v_forward * input_movevalues_x +
v_right * input_movevalues_y;
wishvel_z = 0;
PM_WalkPreMove (wishvel, half_time);
PM_WalkAccelerate (wishvel, half_time);
}
// timers (part 1) -- CEV
PM_ManageTimer (half_time);
// step if nostep is false and we're not on a ladder...
if (world_nostep == FALSE) {
if (!(self.pm_flags & PMF_ONLADDER))
{
// ... and either airstep is true or we're onground
if (world_airstep == TRUE)
self.pm_flags |= PMF_SLIDE_STEP;
else if (self.pm_flags & PMF_ONGROUND)
self.pm_flags |= PMF_SLIDE_STEP;
} }
if (self.velocity != '0 0 0')
PM_CheckVelocity ();
// Do the move. Bounce, Rock, Skate, Roll -- CEV
PM_DanceMove ();
// second pass at acceleration; note that a touch function
// might have changed the v_ vectors during the move -- CEV
if (self.conlevel >= WATERLEVEL_WAIST)
PM_SwimAccelerate (wishvel, half_time);
else
PM_WalkAccelerate (wishvel, half_time);
// timers (part 2) -- CEV
PM_ManageTimer (half_time);
// clear ladder flag -- CEV
if (self.pm_flags & PMF_ONLADDER)
self.pm_flags &= ~PMF_ONLADDER;
touchtriggers ();
base_entity_positioncontents (self);
}
else if (self.movetype == MOVETYPE_FLY)
{
// split acceleration in two to improve responsiveness -- CEV
PM_SetNoClipFlags ();
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, half_time);
// step if nostep is false...
if (world_nostep == FALSE)
{
// ... and either airstep is true or we're onground
if (world_airstep == TRUE)
self.pm_flags |= PMF_SLIDE_STEP;
else if (self.pm_flags & PMF_ONGROUND)
self.pm_flags |= PMF_SLIDE_STEP;
}
if (self.velocity != '0 0 0')
PM_CheckVelocity ();
PM_DanceMove ();
// note that the v_ vectors may have changed in the move -- CEV
PM_NoClipAccelerate (wishvel, 1.0f, half_time);
touchtriggers ();
base_entity_positioncontents (self);
}
else if (self.movetype == MOVETYPE_NOCLIP)
{
// noclip is a debugging feature, no need to
// worry about acceleration consistency -- CEV
PM_SetNoClipFlags ();
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;
}
if (self.velocity != '0 0 0')
{
PM_CheckVelocity ();
self.velocity = PM_TruncateVectorToEighth (self.velocity);
}
self.speed = vlen ([self.velocity_x, self.velocity_y, 0]);
setorigin (self, self.origin);
self.oldorigin = self.origin;
// restore self just in case -- CEV
self = oself;
};
#endif
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.