Git Repos / fte_dogmode / qc / pmove.qc
Last update to this file was on 2024-11-20 at 23:54.
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
// 10 - 6.5265 = 3.4375
// 3.4375 / 2.0 = 1.71875
// 10 - 1.71875 = 8.28125
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; 0.85
const float PM_AIRACCELXFWD = 1.0f; // 1 feels close to Q3 / CPM; 0.85f
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_SLICKACCEL = 10.0f; // accel while on ice / slick floor
const float PM_SLIDEACCELY = 70.0f; //
const float PM_SLIDEACCELXY = 1.0f; // crouchslide accel; 0.85f
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 = 80.0f; // ???
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_STOPSPEED = 100.0f; // used in friction calculations
const float PM_WALKSPEED = 160.0f; // walk speed
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_FLMIN = 0.125f; // 1.0 / 8.0; minimum float precision
const float PM_MAXVELOCITY = 4095.0f; // 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_INTERACT = 0.0f; //
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
// 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 19 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_ONMOVINGENT, // entity is standing on a networked ent
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 | PMF_ONMOVINGENT;
// 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)
// PM_TRUNCATEVECTORTOEIGTH(vec)
// PM_CHECKVELOCITY(vel)
float() PM_Nudge;
// PM_SETONGROUND_NOGROUND()
// PM_SETONGROUND_TRACEGROUND()
// PM_SETONGROUND()
// PM_DANCEMOVE_ADDTOUCH(ent)
// PM_DANCEMOVE_DOTOUCH(ent)
// PM_DANCEMOVE_AXIALNUDGE(plane)
// PM_DANCEMOVE_CLIP_INNER(v, pl_a, pl_b, pl_c, k)
// PM_DANCEMOVE_CLIP(v, pl_i, pl_j, pl_k, k)
// PM_DANCEMOVE_GROUNDIMPACT()
// PM_DANCEMOVE(end, start_vel, i, j, k)
void() PM_CrouchStart;
void() PM_CrouchStop;
void() PM_CrouchSlideStart;
void() PM_CrouchSlideStop;
void() PM_Jump;
void() PM_WallClimb;
void() PM_WallJump;
void() PM_WallJumpCheck;
// PM_FRICTION(speed1, temp, friction, move_time)
// PM_ACCELERATE(dir, wish, speed1, newspeed, accel, move_time)
// PM_AIRCONTROL(dir, wish, cur, new, zspeed, dot, move_time)
// PM_MOVE_MOVETYPES_CATEGORIZEPOSITION()
// PM_MOVE_MOVETYPES_SETNOCLIPFLAGS()
// PM_MOVE_MOVETYPES_NOCLIPACCELERATE(scale, move_time)
// PM_MOVE_MOVETYPES_SWIMPREMOVE_SSQC()
// PM_MOVE_MOVETYPES_SWIMPREMOVE(start, end, move_time)
// PM_MOVE_MOVETYPES_SWIMACCELERATE(move_time)
// PM_MOVE_MOVETYPES_WALKPREMOVE()
// PM_MOVE_MOVETYPES_WALKACCELERATE(move_time)
// PM_MOVE_MOVETYPES_MANAGETIMER(adjust_time)
// PM_MOVE_PRE()
// PM_MOVE_MOVETYPES()
// PM_MOVE_POST()
#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_TRUNCATEVECTORTOEIGTH - truncate a vector to 0.125 precision -- CEV
//----------------------------------------------------------------------
#define PM_TRUNCATEVECTORTOEIGTH(vec) \
{ \
vec.x = (floor(vec.x * 8 + (1.0 / 16))) * PM_FLMIN; \
vec.y = (floor(vec.y * 8 + (1.0 / 16))) * PM_FLMIN; \
vec.z = (floor(vec.z * 8 + (1.0 / 16))) * PM_FLMIN; \
}
//----------------------------------------------------------------------
// PM_CHECKVELOCITY - bound self.velocity to server maximums -- CEV
//----------------------------------------------------------------------
#define PM_CHECKVELOCITY(vel) \
{ \
vel.x = bound (-PM_MAXVELOCITY, vel.x, PM_MAXVELOCITY); \
vel.y = bound (-PM_MAXVELOCITY, vel.y, PM_MAXVELOCITY); \
vel.z = bound (-PM_MAXVELOCITY, vel.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
PM_TRUNCATEVECTORTOEIGTH (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;
};
//----------------------------------------------------------------------
#define PM_SETONGROUND_NOGROUND() \
{ \
/* not on ground, clear pmove flags & fields -- CEV */ \
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 (); \
} \
}
//----------------------------------------------------------------------
#define PM_SETONGROUND_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); \
}
//----------------------------------------------------------------------
// PM_SETONGROUND -- manage ground flags and fields -- CEV
//----------------------------------------------------------------------
#define PM_SETONGROUND() \
{ \
/* 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_MAXSPEED) \
{ \
PM_CrouchSlideStart (); \
} } } } } \
/* set groundentity and groundnormal -- CEV */ \
self.groundentity = trace_ent; \
self.groundnormal = trace_plane_normal; \
/* now manage flags -- CEV */ \
if (!(self.pm_flags & PMF_SLIDE_STEP)) \
{ \
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 */ \
PM_SETONGROUND_NOGROUND () \
} \
/* flag when standing on a vertically moving ent -- CEV */ \
if (trace_networkentity || trace_ent.velocity.z != 0) \
self.pm_flags |= PMF_ONMOVINGENT; \
else if (self.pm_flags & PMF_ONMOVINGENT) \
self.pm_flags &= ~PMF_ONMOVINGENT; \
} \
else \
{ \
PM_SETONGROUND_NOGROUND () \
} \
}
//----------------------------------------------------------------------
#define PM_DANCEMOVE_ADDTOUCH(ent) \
{ \
if (ent) \
{ \
if (ent.touch) \
{ \
if (ent != touched_ent3) { \
if (ent != touched_ent2) { \
if (ent != touched_ent1) \
{ \
touched_ent3 = touched_ent2; \
touched_ent2 = touched_ent1; \
touched_ent1 = ent; \
} } } \
} \
} \
}
//----------------------------------------------------------------------
#define PM_DANCEMOVE_DOTOUCH(ent) \
{ \
if (ent) \
{ \
if (ent.touch != __NULL__) \
{ \
if (ent.touch != sub_null) \
{ \
stemp = self; \
otemp = other; \
other = self; \
self = ent; \
self.touch (); \
self = stemp; \
other = otemp; \
} \
} \
} \
}
//----------------------------------------------------------------------
#define PM_DANCEMOVE_AXIALNUDGE(plane) \
{ \
if (plane) \
{ \
if (trace_plane_normal * plane > 0.99f) \
{ \
self.velocity += trace_plane_normal; \
} \
} \
}
//----------------------------------------------------------------------
// PM_DANCEMOVE_CLIP_INNER
// This macro is meant to be called from PM_DANCEMOVE_CLIP -- CEV
//----------------------------------------------------------------------
#define PM_DANCEMOVE_CLIP_INNER(v, pl_a, pl_b, pl_c, k) \
{ \
/* test if plane_b exists, is not identical to plane_a, and
* that velocity interacts with it -- CEV */ \
if (pl_b) { \
if (pl_b != pl_a) \
{ \
k = self.velocity * pl_b; \
if (k < PM_PLANE_INTERACT) \
{ \
/* clip to plane_b (the second plane) -- CEV */ \
self.velocity -= pl_b * k; \
/* test if move goes back into the first plane */ \
if (self.velocity * pl_a < 0) \
{ \
/* slide along the crease -- CEV */ \
/* ("><" is crossproduct) -- CEV */ \
v = pl_a >< pl_b; \
v = normalize (v); \
self.velocity = v * (v * self.velocity); \
/* check if we interact with a 3rd plane */ \
if (pl_c) { \
if (self.velocity * pl_c < 0) \
{ \
/* stop when 3 planes interact */ \
self.velocity = '0 0 0'; \
break; \
} } \
} \
} \
} } \
}
//----------------------------------------------------------------------
// PM_DANCEMOVE_CLIP
// This macro is meant to be called from PM_DANCEMOVE -- CEV
//----------------------------------------------------------------------
#define PM_DANCEMOVE_CLIP(v, pl_i, pl_j, pl_k, k) \
{ \
/* test if plane_i exists & if velocity interacts with it -- CEV */ \
if (pl_i) \
{ \
k = self.velocity * pl_i; \
if (k < PM_PLANE_INTERACT) \
{ \
/* clip to the plane -- CEV */ \
self.velocity -= pl_i * k; \
/* check for 2nd and 3rd plane interactions -- CEV */ \
PM_DANCEMOVE_CLIP_INNER (v, pl_i, pl_j, pl_k, k) \
PM_DANCEMOVE_CLIP_INNER (v, pl_i, pl_k, pl_j, k) \
} \
} \
}
//----------------------------------------------------------------------
// PM_DANCEMOVE_GROUNDIMPACT
// This macro is meant to be called from PM_DANCEMOVE -- CEV
//----------------------------------------------------------------------
#ifdef SSQC
#define PM_DANCEMOVE_GROUNDIMPACT() \
{ \
/* 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
#ifdef CSQC
#define PM_DANCEMOVE_GROUNDIMPACT() \
{ \
}
#endif
//----------------------------------------------------------------------
// 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
//----------------------------------------------------------------------
#define PM_DANCEMOVE(end, start_vel, i, j, k) \
{ \
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_ClearFlags2; \
/* declare and initialize variables -- CEV */ \
local vector plane1, plane2; \
local float time_left = input_timelength; \
local entity stemp, otemp, touched_ent1, touched_ent2, touched_ent3; \
plane1 = plane2 = '0 0 0'; \
i = j = k = 0; \
start_vel = self.velocity; \
stemp = otemp = touched_ent1 = touched_ent2 = touched_ent3 = __NULL__; \
/* 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 */ \
dprint ("PM_DANCEMOVE: player entity stuck!"); \
dprint (sprintf(" timestamp %g\n", time)); \
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; \
/* save trace ent for later then reduce time_left -- CEV */ \
PM_DANCEMOVE_ADDTOUCH (trace_ent) \
time_left -= time_left * trace_fraction; \
/* integrated StepSlideMove from Nuclide / CSQCTest
* only attempt to step if requested and there's time left
* and we hit something like a vertical plane -- CEV */ \
if (self.pm_flags & PMF_SLIDE_STEP) { \
if (time_left > 0) { \
if (trace_plane_normal.z <= PM_PLANE_GROUND) \
{ \
/* 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; \
local float first_net = trace_networkentity; \
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 || trace_startsolid) \
goto PM_DanceMove_RejectStep2; \
if (trace_endpos.z - self.origin.z <= 0) \
{ \
/* dprint (sprintf("PM_DANCEMOVE: A %v\n", \
trace_endpos - self.origin)); */ \
goto PM_DanceMove_RejectStep2; \
} \
local vector roof_plane = '0 0 0'; \
if (trace_fraction < 1.0f) \
{ \
PM_DANCEMOVE_ADDTOUCH (trace_ent) \
roof_plane = trace_plane_normal; \
} \
/* 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_RejectStep2; \
/* 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_FLMIN) \
if (fabs(self.origin.y - trace_endpos.y) < PM_FLMIN) \
/* dprint (sprintf("PM_DANCEMOVE: B\n")); */ \
goto PM_DanceMove_RejectStep2; \
} \
local vector fwd_plane = '0 0 0'; \
/* local vector save_pos = trace_endpos; */ \
j -= j * trace_fraction; \
if (trace_fraction < 1.0f) \
{ \
PM_DANCEMOVE_ADDTOUCH (trace_ent) \
fwd_plane = trace_plane_normal; \
} \
/* third: move down; +1 to k is important -- CEV */ \
end = trace_endpos; \
end.z -= k + 1; \
tracebox (trace_endpos, self.mins, self.maxs, end, \
PM_MOVEFLAGS, self); \
/*
if (trace_endpos == save_pos) \
{ \
dprint (sprintf("PM_DANCEMOVE: D\n")); \
goto PM_DanceMove_RejectStep2; \
} \
*/ \
/* The down move will sometimes hit nothing (frac 1).
* In this case trace_plane_normal is unreliable (again
* we hit nothing) so do some extra checks to determine
* if we keep the step move -- CEV */ \
if (trace_fraction == 1.0f) \
{ \
/*
dprint (sprintf("PM_DANCEMOVE: norm %v, " \
"diff %v, time %g\n", \
trace_plane_normal, \
trace_endpos - self.origin, \
j)); \
*/ \
/* Check the plane just in case -- CEV */ \
if (trace_plane_normal.z != 0) \
{ \
dprint ("PM_DANCEMOVE: C\n"); \
goto PM_DanceMove_RejectStep2; \
} \
/* If we haven't moved and first_plane is not
* vertical then reject the attempt -- CEV */ \
if (trace_endpos == self.origin) \
{ \
if (first_plane.z != 0.0f) \
goto PM_DanceMove_RejectStep2; \
} \
else \
{ \
/* TODO CEV we still need to reject the
* step here, don't fully understand
* the reason why -- CEV */ \
/*
dprint (sprintf("PM_DANCEMOVE: Z %v\n",\
trace_endpos - save_pos)); \
*/ \
goto PM_DanceMove_RejectStep2; \
} \
} \
else if (trace_plane_normal.z <= PM_PLANE_GROUND) \
{ \
/* this is the expected and regular
* not-good-ground step rejection -- CEV */ \
goto PM_DanceMove_RejectStep2; \
} \
if (trace_allsolid == FALSE) \
{ \
/* accept the stepped move. update time_left,
* origin, touched ents, and stored planes,
* then update flags -- CEV */ \
time_left = j; \
self.origin = trace_endpos; \
if (trace_ent.touch) \
PM_DANCEMOVE_ADDTOUCH (trace_ent) \
plane2 = plane1 = '0 0 0'; \
if (roof_plane) { \
if (roof_plane != trace_plane_normal) \
{ \
plane1 = roof_plane; \
} } \
if (fwd_plane) { \
if (fwd_plane != trace_plane_normal) \
{ \
plane2 = plane1; \
plane1 = fwd_plane; \
} } \
self.pm_flags |= PMF_STEPPED; \
if (!(self.pm_flags & PMF_ONGROUND)) { \
if (start_vel.z <= 0) \
{ \
/* for stairjumps. -- CEV */ \
self.pm_flags |= PMF_AIRSTEPPED; \
} } \
} \
else \
{ \
/* discard the step attempt -- CEV */ \
PM_DanceMove_RejectStep2: \
trace_plane_normal = first_plane; \
/* TODO CEV rewind touched ents? */ \
trace_ent = first_ent; \
trace_fraction = first_frac; \
trace_networkentity = first_net; \
} \
} } } \
PM_DANCEMOVE_AXIALNUDGE (self.velocity) \
PM_DANCEMOVE_AXIALNUDGE (plane1) \
PM_DANCEMOVE_AXIALNUDGE (plane2) \
/* we've found a ground plane so call PM_SETONGROUND -- CEV */ \
if (trace_plane_normal.z > PM_PLANE_GROUND) \
{ \
PM_DANCEMOVE_GROUNDIMPACT () \
PM_SETONGROUND () \
} \
/* check stored planes and clip velocity as needed -- CEV */ \
PM_DANCEMOVE_CLIP (end, trace_plane_normal, plane1, plane2, k) \
PM_DANCEMOVE_CLIP (end, plane1, trace_plane_normal, plane2, k) \
PM_DANCEMOVE_CLIP (end, plane2, plane1, trace_plane_normal, k) \
/* store current plane and plane1 for the next pass -- CEV */ \
if (trace_plane_normal != self.groundnormal) \
{ \
plane2 = plane1; \
plane1 = trace_plane_normal; \
} \
/* 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; \
} \
/* call the touch functions of any entities we've collided with
* (as needed) -- CEV */ \
PM_DANCEMOVE_DOTOUCH (touched_ent3) \
PM_DANCEMOVE_DOTOUCH (touched_ent2) \
PM_DANCEMOVE_DOTOUCH (touched_ent1) \
/* 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; \
/* take 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)) \
{ \
/*
dprint (sprintf("PM_DANCEMOVE: glue %f\n", self.origin.z));
*/ \
k = self.velocity * self.groundnormal; \
self.velocity -= self.groundnormal * k; \
} } } } } } \
/*
if (i > 0)
dprint (sprintf("PM_DANCEMOVE: move complete, "
"i %g, time_left %g, velocity.z %g\n",
i, time_left, self.velocity.z));
*/ \
PM_DanceMove_ClearFlags2: \
/* 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;
setsize (self, PM_CROUCH_MIN, PM_CROUCH_MAX);
#ifdef CSQC
// transition to crouch view offset (if not already in progress) -- CEV
if (self.entnum == player_localentnum) {
if (self.view_ofs != PM_CROUCH_VIEWOFS)
{
view_crouch_old = PM_STAND_VIEWOFS.z;
view_crouch_finished = time + PLAYER_CROUCH_SMOOTH;
} }
#endif
self.view_ofs = PM_CROUCH_VIEWOFS;
};
//----------------------------------------------------------------------
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);
#ifdef CSQC
// transition to stand view offset (like above) -- CEV
if (self.entnum == player_localentnum) {
if (self.view_ofs != PM_STAND_VIEWOFS)
{
view_crouch_old = PM_CROUCH_VIEWOFS_z;
view_crouch_finished = time + PLAYER_CROUCH_SMOOTH;
} }
#endif
self.view_ofs = PM_STAND_VIEWOFS;
} }
};
//----------------------------------------------------------------------
// 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_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 PM_WALLJUMPGROUND 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_MAXSPEED) {
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;
}
};
//----------------------------------------------------------------------
#define PM_FRICTION(speed1, temp, friction, move_time) \
{ \
speed1 = vlen (self.velocity); \
if (speed1 < PM_FLMIN) \
{ \
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; \
} \
}
//----------------------------------------------------------------------
#define PM_ACCELERATE(dir, wish, speed1, newspeed, accel, move_time) \
{ \
speed1 = wish - (self.velocity * dir); \
if (speed1 > 0) \
{ \
newspeed = accel * move_time * wish; \
newspeed = min (newspeed, speed1); \
self.velocity += newspeed * dir; \
} \
}
//----------------------------------------------------------------------
#define PM_AIRCONTROL(dir, wish, cur, new, zspeed, dot, move_time) \
{ \
zspeed = self.velocity.z; \
self.velocity.z = 0; \
cur = vlen (self.velocity); \
self.velocity = normalize (self.velocity); \
dot = self.velocity * dir; \
if (dot > 0) \
{ \
new = PM_AIRACCELBASE; \
new *= bound (0, wish / PM_MAXAIRSPEED, 1); \
new *= PM_AIRACCELTURN * dot * dot * move_time; \
self.velocity = self.velocity * cur + dir * new; \
self.velocity = normalize (self.velocity); \
} \
self.velocity *= cur; \
self.velocity.z = zspeed; \
}
//----------------------------------------------------------------------
// PM_MOVE_MOVETYPES_CATEGORIZEPOSITION
// Based on similarly-named function in the GPL2 purecsqc pmove.qc
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_CATEGORIZEPOSITION() \
{ \
/* 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.velocity.z > 180) \
{ \
if (self.pm_flags & PMF_ONRAMP) \
{ \
PM_SETONGROUND_TRACEGROUND () \
PM_SETONGROUND () \
} \
else if (self.pm_flags & PMF_ONSLOPE) \
{ \
PM_SETONGROUND_TRACEGROUND () \
PM_SETONGROUND () \
} \
else \
{ \
PM_SETONGROUND_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_TRACEGROUND () \
PM_SETONGROUND () \
} \
/* 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; \
} } \
}
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_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_MOVE_MOVETYPES_NOCLIPACCELERATE -- for flying / noclip -- CEV
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_NOCLIPACCELERATE(move_time) \
{ \
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); \
wishdir = normalize (wishvel); \
PM_FRICTION (speed1, temp, PM_GROUNDFRICTION, move_time) \
PM_ACCELERATE (wishdir, wishspeed, speed1, temp, PM_GROUNDACCEL, \
move_time) \
}
#ifdef SSQC
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_SWIMPREMOVE_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
#ifdef CSQC
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_SWIMPREMOVE_SSQC() \
{ \
}
#endif
//----------------------------------------------------------------------
// PM_MOVE_MOVETYPES_SWIMPREMOVE -- water jump, water wading sounds -- CEV
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_SWIMPREMOVE(start, end, move_time) \
{ \
/* CheckWaterJump */ \
if (self.conlevel == WATERLEVEL_WAIST) \
{ \
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; \
} \
} \
} \
PM_MOVE_MOVETYPES_SWIMPREMOVE_SSQC () \
if (self.pm_flags & PMF_STEPPED) \
{ \
self.pm_flags &= ~PMF_STEPPED; \
self.pm_flags &= ~PMF_AIRSTEPPED; \
} \
}
//----------------------------------------------------------------------
// PM_MOVE_MOVETYPES_SWIMACCELERATE
// Largely copied from id Software's WaterMove -- CEV
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_SWIMACCELERATE(move_time) \
{ \
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 */ \
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); \
if (!(self.pm_flags & PMF_WATERJUMPED)) \
{ \
PM_FRICTION (speed1, temp, PM_WATERFRICTION, move_time) \
} \
PM_ACCELERATE (wishdir, wishspeed, speed1, temp, PM_WATERACCEL, \
move_time) \
}
//----------------------------------------------------------------------
// PM_MOVE_MOVETYPES_WALKPREMOVE -- crouching, jumping, ladders -- CEV
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_WALKPREMOVE() \
{ \
if (self.pm_flags & PMF_ONLADDER) \
{ \
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_NOGROUND () \
self.pm_timer = 0; \
} \
else \
{ \
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; \
} \
} \
/* 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 */ \
/* 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; \
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)); \
wishyaw = -wishyaw; \
if (temp > 60.0f) \
if (temp < 120.0f) \
wishyaw = -90.0f; \
} \
} \
}
//----------------------------------------------------------------------
// PM_MOVE_MOVETYPES_WALKACCELERATE -- accel & friction for MOVETYPE_WALK -- CEV
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_WALKACCELERATE(move_time) \
{ \
accel = friction = speed1 = temp = 0; \
/* 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 */ \
wishnew = min (wishspeed, PM_MAXSPEED); \
if (wishspeed) \
accel = PM_SLICKACCEL; \
} \
else if (self.pm_flags & PMF_CROUCHSLIDE) \
{ \
/* movement while crouchsliding -- CEV */ \
wishnew = min (wishspeed, PM_CROUCHSPEED); \
/* water level adds extra friction -- CEV */ \
friction = PM_SLIDEFRICTION + self.conlevel; \
if (wishspeed) { \
if (fabs(wishyaw) == 90) \
{ \
/* Q1 air accel -- CEV */ \
/* same behavior as when in the air -- CEV */ \
accel = PM_SLIDEACCELY; \
wishnew = 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 */ \
friction = PM_GROUNDFRICTION * 0.1f; \
else \
friction = PM_GROUNDFRICTION; \
if (wishspeed) \
{ \
if (self.pm_timer > 0) \
{ \
/* go directly to PM_MAXWISHSPEED
* for stairjumps -- CEV */ \
wishnew = PM_MAXWISHSPEED; \
} \
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; \
wishnew = 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) \
{ \
/* Q3 accel -- CEV */ \
accel = PM_SURFACCELXY; \
wishnew = min (wishspeed, PM_MAXSPEED); \
} \
else \
{ \
/* Q1 air accel -- CEV */ \
accel = PM_SURFACCELY; \
wishnew = min (wishspeed, PM_MAXAIRSPEED); \
} \
} \
} \
else if (wishspeed) \
{ \
/* movement in the air -- CEV */ \
if (fabs(wishyaw) == 90) \
{ \
/* Q1 air accel -- CEV */ \
if (wishyaw < 0) { \
if (self.speed < PM_MAXSPEED) \
{ \
/* if player is holding +fwd / +back and
* they're below MAXSPEED then nudge them
* towards wishdir -- CEV */ \
wishnew = min (wishspeed, PM_MAXSPEED); \
accel = PM_AIRACCELXY; \
PM_ACCELERATE (wishdir, wishnew, speed1, \
temp, accel, move_time) \
} } \
accel = PM_AIRACCELY; \
wishnew = min (wishspeed, PM_MAXAIRSPEED); \
} \
else \
{ \
wishnew = min (wishspeed, PM_MAXSPEED); \
/* apply a penalty to wishspeed if player is holding
* jump (simulating PM_CmdScale behavior in Quake 3)
* -- CEV */ \
if (self.pm_flags & PMF_JUMP_HELD) { \
/*
if (!(self.pm_flags & PMF_DOUBLEJUMPED)) \
{ \
*/ \
/* scale in Q3 is 2.519685 when not holding
* jump, 1.781686 when holding jump. 1.78 is
* roughly 70% of 2.51. -- CEV */ \
wishnew *= (0.7f + (0.3f * \
(1 - (self.pm_timer / \
PM_DOUBLEJUMP_WINDOW)))); \
/* } */ } \
if (wishyaw == 0) \
{ \
/* CPM / Painkiller 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; \
} \
} \
} \
if (friction > 0) \
{ \
/* standard Quake friction -- CEV */ \
PM_FRICTION (speed1, temp, friction, move_time) \
} \
if (accel) \
{ \
/* standard Quake ground accel function -- CEV */ \
PM_ACCELERATE (wishdir, wishnew, speed1, temp, accel, \
move_time) \
} \
if (wishspeed) { \
if (wishyaw == 0 || wishyaw == 180) \
{ \
PM_AIRCONTROL (wishdir, wishnew, speed1, temp, friction, \
accel, move_time) \
} } \
}
//----------------------------------------------------------------------
// PM_MOVE_MOVETYPES_MANAGETIMER
// self.pm_timer is pos when jumping, neg when crouchsliding -- CEV
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES_MANAGETIMER(adjust_time) \
{ \
if (self.pm_timer) \
{ \
if (self.pm_flags & PMF_CROUCHSLIDE) \
{ \
self.pm_timer += adjust_time; \
if (self.pm_timer >= 0) \
self.pm_timer = 0; \
} \
else \
{ \
self.pm_timer -= adjust_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_PRE -- prepare to move the player -- CEV
//----------------------------------------------------------------------
#define PM_MOVE_PRE() \
{ \
/* 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 (); \
} \
/* truncate velocity to network precision -- CEV */ \
if (self.velocity != '0 0 0') \
{ \
PM_TRUNCATEVECTORTOEIGTH (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; \
} \
}
//----------------------------------------------------------------------
// PM_MOVE_MOVETYPES
//----------------------------------------------------------------------
#define PM_MOVE_MOVETYPES() \
{ \
local vector wishdir, wishvel; \
local float speed1, temp, wishspeed, wishyaw, wishyawdiff; \
wishdir = wishvel = '0 0 0'; \
speed1 = temp = wishspeed = wishyaw = wishyawdiff = 0; \
self.speed = vlen ([self.velocity.x, self.velocity.y, 0]); \
if (self.movetype == MOVETYPE_NOCLIP) \
{ \
PM_MOVE_MOVETYPES_SETNOCLIPFLAGS () \
makevectors (input_angles); \
wishvel = v_forward * input_movevalues.x; \
wishvel += v_right * input_movevalues.y; \
wishvel += v_up * input_movevalues.z; \
PM_MOVE_MOVETYPES_NOCLIPACCELERATE (input_timelength) \
/* we're noclipping so update origin directly -- CEV */ \
self.origin += self.velocity * input_timelength; \
} \
else \
{ \
/* the following vars will be used in macros below */ \
local vector vec1, vec2; \
local float accel, friction, half_time, wishnew; \
vec1 = vec2 = '0 0 0'; \
accel = friction = wishnew = 0; \
half_time = input_timelength * 0.5f; \
if (self.movetype == MOVETYPE_WALK) \
{ \
/* work out the properties of the player's position */ \
PM_MOVE_MOVETYPES_CATEGORIZEPOSITION () \
if (self.conlevel < WATERLEVEL_WAIST) \
{ \
/* intended movement for walking/air accel */ \
/* 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; \
wishdir = normalize (wishvel); \
wishspeed = vlen (wishvel); \
/* walking & air acceleration -- CEV */ \
PM_MOVE_MOVETYPES_WALKPREMOVE () \
PM_MOVE_MOVETYPES_WALKACCELERATE (half_time) \
} \
else \
{ \
/* work out intended movement for swimming */ \
makevectors (input_angles); \
wishvel = v_forward * input_movevalues.x + \
v_right * input_movevalues.y + \
v_up * input_movevalues.z; \
wishdir = normalize (wishvel); \
wishspeed = vlen (wishvel); \
/* swim acceleration -- CEV */ \
PM_MOVE_MOVETYPES_SWIMPREMOVE (vec1, vec2, \
half_time) \
PM_MOVE_MOVETYPES_SWIMACCELERATE (half_time) \
} \
/* handle countdown timers -- CEV */ \
PM_MOVE_MOVETYPES_MANAGETIMER (half_time) \
} \
else if (self.movetype == MOVETYPE_FLY) \
{ \
PM_MOVE_MOVETYPES_SETNOCLIPFLAGS () \
makevectors (input_angles); \
wishvel = v_forward * input_movevalues.x; \
wishvel += v_right * input_movevalues.y; \
wishvel += v_up * input_movevalues.z; \
PM_MOVE_MOVETYPES_NOCLIPACCELERATE (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; \
} } \
/* bound velocity to fixed server maximums -- CEV */ \
if (self.velocity != '0 0 0') \
{ \
PM_CHECKVELOCITY (self.velocity) \
} \
/* Do the move. Bounce, Rock, Skate, Roll -- CEV */ \
PM_DANCEMOVE (vec1, vec2, accel, friction, temp) \
/* now the back half of player acceleration -- CEV */ \
if (self.movetype == MOVETYPE_WALK) \
{ \
if (self.conlevel < WATERLEVEL_WAIST) \
{ \
PM_MOVE_MOVETYPES_WALKACCELERATE (half_time) \
} \
else \
{ \
PM_MOVE_MOVETYPES_SWIMACCELERATE (half_time) \
} \
PM_MOVE_MOVETYPES_MANAGETIMER (half_time) \
} \
else if (self.movetype == MOVETYPE_FLY) \
{ \
PM_MOVE_MOVETYPES_NOCLIPACCELERATE (half_time) \
} \
/* clear ladder flag -- CEV */ \
if (self.pm_flags & PMF_ONLADDER) \
self.pm_flags &= ~PMF_ONLADDER; \
touchtriggers (); \
base_entity_positioncontents (self); \
} \
}
//----------------------------------------------------------------------
// PM_MOVE_POST -- handle any after-move operations -- CEV
//
// If we have velocity then restrict it to server maximums and truncate
// it to network precision -- CEV
// Then udpate speed, origin, and oldorigin -- CEV
//----------------------------------------------------------------------
#define PM_MOVE_POST() \
{ \
if (self.velocity != '0 0 0') \
{ \
PM_CHECKVELOCITY (self.velocity) \
PM_TRUNCATEVECTORTOEIGTH (self.velocity) \
} \
self.speed = vlen ([self.velocity.x, self.velocity.y, 0]); \
setorigin (self, self.origin); \
self.oldorigin = self.origin; \
}
#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.