QL stair momentum: conditional velocity clip + air steps + step jump

THE key QL stair mechanic: skip velocity clip in PM_StepSlideMove when
velocity is moving away from the step surface (dot product >= 0). Q3
always clips, zeroing vertical momentum on every step. QL preserves
upward velocity through steps, enabling smooth bunny-hop stair traversal.

Also includes:
- Remove Q3 velocity[2]>0 gate (pm_airSteps): allow step-ups during jumps
- pml.isStepJump flag for step jump context in PM_Jump
- PM_Jump: additive velocity for step jumps (+=270, min 270, max 700)
- 100ms jump cooldown via lastJumpTime

Has known glitches — saved for reference before fixing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
serge_shubin 2026-03-21 06:08:10 +08:00
parent 3327e9680c
commit 6de5824395
3 changed files with 39 additions and 11 deletions

View file

@ -51,6 +51,10 @@ typedef struct {
vec3_t previous_origin; vec3_t previous_origin;
vec3_t previous_velocity; vec3_t previous_velocity;
int previous_waterlevel; int previous_waterlevel;
// QL step jump context flag — set before calling PM_Jump
// from PM_StepSlideMove to get additive velocity instead of flat set
qboolean isStepJump;
} pml_t; } pml_t;
extern pmove_t *pm; extern pmove_t *pm;

View file

@ -383,6 +383,14 @@ qboolean PM_CanJump( void ) {
return qfalse; return qfalse;
} }
// QL: 100ms minimum delay between jumps.
// Prevents same-frame double-fires. Step jumps launch the player
// high enough (~400ms airtime) that the next stair collision
// naturally exceeds this threshold.
if ( pm->cmd.serverTime - pm->ps->lastJumpTime < 100 ) {
return qfalse;
}
return qtrue; return qtrue;
} }
@ -392,7 +400,23 @@ void PM_Jump( void ) {
pm->ps->pm_flags |= PMF_JUMP_HELD; pm->ps->pm_flags |= PMF_JUMP_HELD;
pm->ps->groundEntityNum = ENTITYNUM_NONE; pm->ps->groundEntityNum = ENTITYNUM_NONE;
pm->ps->velocity[2] = JUMP_VELOCITY;
if ( pml.isStepJump ) {
// QL step jump: additive velocity, preserving existing upward motion.
// This launches the player high enough to skip several stairs,
// so the next collision is >100ms away and the cooldown works.
pm->ps->velocity[2] += JUMP_VELOCITY;
if ( pm->ps->velocity[2] < JUMP_VELOCITY ) {
pm->ps->velocity[2] = JUMP_VELOCITY;
}
if ( pm->ps->velocity[2] > 700 ) {
pm->ps->velocity[2] = 700;
}
} else {
// Normal ground jump: flat velocity set (Q3 behavior)
pm->ps->velocity[2] = JUMP_VELOCITY;
}
pm->ps->lastJumpTime = pm->cmd.serverTime; pm->ps->lastJumpTime = pm->cmd.serverTime;
PM_AddEvent( EV_JUMP ); PM_AddEvent( EV_JUMP );

View file

@ -246,15 +246,9 @@ void PM_StepSlideMove( qboolean gravity ) {
return; // we got exactly where we wanted to go first try return; // we got exactly where we wanted to go first try
} }
VectorCopy(start_o, down); // QL pm_airSteps: allow step-ups with upward velocity.
down[2] -= STEPSIZE; // Q3 blocked step-ups during jumps unless ground was directly below.
pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); // This prevented smooth stair traversal while bunny-hopping.
VectorSet(up, 0, 0, 1);
// never step up when you still have up velocity
if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 ||
DotProduct(trace.plane.normal, up) < 0.7)) {
return;
}
VectorCopy (pm->ps->origin, down_o); VectorCopy (pm->ps->origin, down_o);
VectorCopy (pm->ps->velocity, down_v); VectorCopy (pm->ps->velocity, down_v);
@ -285,8 +279,14 @@ void PM_StepSlideMove( qboolean gravity ) {
if ( !trace.allsolid ) { if ( !trace.allsolid ) {
VectorCopy (trace.endpos, pm->ps->origin); VectorCopy (trace.endpos, pm->ps->origin);
} }
// QL: only clip velocity to step surface when moving INTO it.
// Skip clip when velocity is already moving away (preserves
// upward momentum through stair steps during bunny-hopping).
if ( trace.fraction < 1.0 ) { if ( trace.fraction < 1.0 ) {
PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); float vdotn = DotProduct( pm->ps->velocity, trace.plane.normal );
if ( vdotn < 0 || fabs( vdotn ) < 0.001f ) {
PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP );
}
} }
#if 0 #if 0