Replace Q3TA player movement with Quake Live's pmove system, decompiled from qagamex64.so via Ghidra MCP. Struct changes: - playerState_t: +12 bytes (field_08c, field_094, field_09c, lastJumpTime, jumped, crouchSlideTimer, forwardmove/rightmove/upmove) - usercmd_t: +4 bytes (field_15, field_16 between weapon and forwardmove) - pmove_t: +16 bytes (field_0dc/0e0/0e4/0f8, removed framecount) - pml_t: +12 bytes (isJumppad, wallContact, isStepJump) - pm_flags: remapped to QL values (21 flags, several moved/new) New physics systems: - Dual VQ3/PQL physics (PMF_PROMODE) - Double jump, crouch slide, chain/ramp/step jump - Wall contact movement, grapple hook physics - 34 server-tunable pmove_* cvars with cgame prediction sync Network protocol updated for new struct fields and usercmd bytes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
397 lines
12 KiB
C
397 lines
12 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake III Arena source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Foobar; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
//
|
|
// bg_slidemove.c -- part of bg_pmove functionality
|
|
//
|
|
// Reconstructed from Quake Live x86_64 binary.
|
|
// PM_SlideMove at 0x00155ed0, PM_StepSlideMove at 0x00156850.
|
|
|
|
#include "q_shared.h"
|
|
#include "bg_public.h"
|
|
#include "bg_local.h"
|
|
|
|
extern float _DAT_003cfe90; // velocity damping factor for step-up
|
|
extern int DAT_003cfe30; // edge grab enabled
|
|
extern int pmove_stepJumpFlag; // step jump enabled
|
|
|
|
/*
|
|
==================
|
|
PM_SlideMove
|
|
|
|
Returns qtrue if the velocity was clipped in some way
|
|
==================
|
|
*/
|
|
#define MAX_CLIP_PLANES 5
|
|
qboolean PM_SlideMove( qboolean gravity ) {
|
|
int bumpcount;
|
|
int numplanes;
|
|
vec3_t planes[MAX_CLIP_PLANES];
|
|
vec3_t clipVelocity;
|
|
vec3_t endClipVelocity;
|
|
vec3_t endVelocity;
|
|
vec3_t end;
|
|
vec3_t dir;
|
|
trace_t trace;
|
|
float primal_vel_x, primal_vel_y, primal_vel_z;
|
|
float time_left;
|
|
float into;
|
|
float d;
|
|
int i, j, k;
|
|
|
|
primal_vel_x = pm->ps->velocity[0];
|
|
primal_vel_y = pm->ps->velocity[1];
|
|
primal_vel_z = pm->ps->velocity[2];
|
|
|
|
if ( !gravity ) {
|
|
endVelocity[0] = 0.0f;
|
|
endVelocity[1] = 0.0f;
|
|
endVelocity[2] = 0.0f;
|
|
} else {
|
|
endVelocity[0] = primal_vel_x;
|
|
endVelocity[1] = pm->ps->velocity[1];
|
|
primal_vel_z = pm->ps->velocity[2] - (float)pm->ps->gravity * pml.frametime;
|
|
pm->ps->velocity[2] = (pm->ps->velocity[2] + primal_vel_z) * 0.5f;
|
|
endVelocity[2] = primal_vel_z;
|
|
if ( pml.groundPlane ) {
|
|
// slide along the ground plane
|
|
PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal,
|
|
pm->ps->velocity, OVERCLIP );
|
|
}
|
|
}
|
|
|
|
time_left = pml.frametime;
|
|
|
|
// never turn against the ground plane
|
|
if ( pml.groundPlane ) {
|
|
numplanes = 2;
|
|
VectorCopy( pml.groundTrace.plane.normal, planes[0] );
|
|
VectorNormalize2( pm->ps->velocity, planes[1] );
|
|
} else {
|
|
numplanes = 1;
|
|
VectorNormalize2( pm->ps->velocity, planes[0] );
|
|
}
|
|
|
|
for ( bumpcount = 0; bumpcount < 4; bumpcount++ ) {
|
|
|
|
// calculate position we are trying to move to
|
|
VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end );
|
|
|
|
// see if we can make it there
|
|
pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end,
|
|
pm->ps->clientNum, pm->tracemask );
|
|
|
|
if ( trace.allsolid ) {
|
|
// entity is completely trapped in another solid
|
|
pm->ps->velocity[2] = 0.0f;
|
|
return qtrue;
|
|
}
|
|
|
|
if ( trace.fraction > 0.0f ) {
|
|
// actually covered some distance
|
|
VectorCopy( trace.endpos, pm->ps->origin );
|
|
}
|
|
|
|
if ( trace.fraction == 1.0f ) {
|
|
break; // moved the entire distance
|
|
}
|
|
|
|
// save entity for contact
|
|
PM_AddTouchEnt( trace.entityNum );
|
|
|
|
time_left -= time_left * trace.fraction;
|
|
|
|
if ( numplanes >= MAX_CLIP_PLANES ) {
|
|
// this shouldn't really happen
|
|
VectorClear( pm->ps->velocity );
|
|
return qtrue;
|
|
}
|
|
|
|
//
|
|
// if this is the same plane we hit before, nudge velocity
|
|
// out along it, which fixes some epsilon issues with
|
|
// non-axial planes
|
|
//
|
|
for ( i = 0; i < numplanes; i++ ) {
|
|
if ( (double)DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) {
|
|
VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity );
|
|
break;
|
|
}
|
|
}
|
|
if ( i < numplanes ) {
|
|
continue;
|
|
}
|
|
VectorCopy( trace.plane.normal, planes[numplanes] );
|
|
numplanes++;
|
|
|
|
//
|
|
// modify velocity so it parallels all of the clip planes
|
|
//
|
|
|
|
// find a plane that it enters
|
|
for ( i = 0; i < numplanes; i++ ) {
|
|
into = DotProduct( pm->ps->velocity, planes[i] );
|
|
if ( (double)into >= 0.1 ) {
|
|
continue; // move doesn't interact with the plane
|
|
}
|
|
|
|
// see how hard we are hitting things
|
|
if ( -into > pml.impactSpeed ) {
|
|
pml.impactSpeed = -into;
|
|
}
|
|
|
|
// slide along the plane
|
|
PM_ClipVelocity( pm->ps->velocity, planes[i], clipVelocity, OVERCLIP );
|
|
|
|
// slide along the plane
|
|
PM_ClipVelocity( endVelocity, planes[i], endClipVelocity, OVERCLIP );
|
|
|
|
// see if there is a second plane that the new move enters
|
|
for ( j = 0; j < numplanes; j++ ) {
|
|
if ( j == i ) {
|
|
continue;
|
|
}
|
|
if ( (double)DotProduct( clipVelocity, planes[j] ) >= 0.1 ) {
|
|
continue; // move doesn't interact with the plane
|
|
}
|
|
|
|
// try clipping the move to the plane
|
|
PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP );
|
|
PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP );
|
|
|
|
// see if it goes back into the first clip plane
|
|
if ( DotProduct( clipVelocity, planes[i] ) >= 0.0f ) {
|
|
continue;
|
|
}
|
|
|
|
// slide the original velocity along the crease
|
|
CrossProduct( planes[i], planes[j], dir );
|
|
VectorNormalize( dir );
|
|
d = DotProduct( dir, pm->ps->velocity );
|
|
VectorScale( dir, d, clipVelocity );
|
|
|
|
CrossProduct( planes[i], planes[j], dir );
|
|
VectorNormalize( dir );
|
|
d = DotProduct( dir, endVelocity );
|
|
VectorScale( dir, d, endClipVelocity );
|
|
|
|
// see if there is a third plane the new move enters
|
|
for ( k = 0; k < numplanes; k++ ) {
|
|
if ( k == i || k == j ) {
|
|
continue;
|
|
}
|
|
if ( (double)DotProduct( clipVelocity, planes[k] ) >= 0.1 ) {
|
|
continue; // move doesn't interact with the plane
|
|
}
|
|
|
|
// stop dead at a triple plane interaction
|
|
VectorClear( pm->ps->velocity );
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
// if we have fixed all interactions, try another move
|
|
VectorCopy( clipVelocity, pm->ps->velocity );
|
|
VectorCopy( endClipVelocity, endVelocity );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( gravity ) {
|
|
VectorCopy( endVelocity, pm->ps->velocity );
|
|
}
|
|
|
|
// don't change velocity if in a timer (FIXME: is this correct?)
|
|
if ( pm->ps->pm_time ) {
|
|
pm->ps->velocity[0] = primal_vel_x;
|
|
pm->ps->velocity[1] = primal_vel_y;
|
|
pm->ps->velocity[2] = primal_vel_z;
|
|
}
|
|
|
|
return ( bumpcount != 0 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
PM_StepSlideMove
|
|
|
|
QL version: significantly expanded from Q3 with air-step support,
|
|
step-jump logic, edge grab, and velocity damping on step-ups.
|
|
==================
|
|
*/
|
|
void PM_StepSlideMove( qboolean gravity ) {
|
|
vec3_t start_o;
|
|
float start_vx, start_vy, start_vz;
|
|
vec3_t up, down;
|
|
vec3_t projected;
|
|
vec3_t stepStart, stepEnd;
|
|
vec3_t edgeMins, edgeMaxs, edgeEnd;
|
|
trace_t trace;
|
|
float stepSize;
|
|
float delta;
|
|
float dotNV;
|
|
|
|
VectorCopy( pm->ps->origin, start_o );
|
|
start_vx = pm->ps->velocity[0];
|
|
start_vy = pm->ps->velocity[1];
|
|
start_vz = pm->ps->velocity[2];
|
|
|
|
if ( PM_SlideMove( gravity ) == 0 ) {
|
|
return; // we got exactly where we wanted to go first try
|
|
}
|
|
|
|
// project where we would have gone without obstruction
|
|
projected[0] = start_vx * pml.frametime + start_o[0];
|
|
projected[1] = start_vy * pml.frametime + start_o[1];
|
|
projected[2] = pml.frametime * start_vz + start_o[2];
|
|
|
|
if ( pm_airSteps == 0 ) {
|
|
// trace forward to projected position, then down to check for ground
|
|
pm->trace( &trace, start_o, pm->mins, pm->maxs, projected,
|
|
pm->ps->clientNum, pm->tracemask );
|
|
VectorCopy( trace.endpos, projected );
|
|
down[0] = trace.endpos[0];
|
|
down[1] = trace.endpos[1];
|
|
down[2] = trace.endpos[2] - (float)pm_stepHeight;
|
|
pm->trace( &trace, projected, pm->mins, pm->maxs, down,
|
|
pm->ps->clientNum, pm->tracemask );
|
|
// never step up when you still have up velocity
|
|
if ( start_vz > 0.0f ) {
|
|
if ( trace.fraction == 1.0f ) {
|
|
return;
|
|
}
|
|
if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// try stepping up
|
|
up[0] = start_o[0];
|
|
up[1] = start_o[1];
|
|
up[2] = start_o[2] + (float)pm_stepHeight;
|
|
|
|
// test the player position if they were a stepheight higher
|
|
pm->trace( &trace, start_o, pm->mins, pm->maxs, up,
|
|
pm->ps->clientNum, pm->tracemask );
|
|
if ( trace.allsolid ) {
|
|
if ( pm->debugLevel ) {
|
|
Com_Printf( "%i:bend can't step\n", c_pmove );
|
|
}
|
|
return; // can't step up
|
|
}
|
|
|
|
stepSize = trace.endpos[2] - start_o[2];
|
|
// try slidemove from this position
|
|
VectorCopy( trace.endpos, pm->ps->origin );
|
|
pm->ps->velocity[0] = start_vx;
|
|
pm->ps->velocity[1] = start_vy;
|
|
pm->ps->velocity[2] = start_vz;
|
|
|
|
PM_SlideMove( gravity );
|
|
|
|
// push down the final amount
|
|
VectorCopy( pm->ps->origin, down );
|
|
down[2] -= stepSize;
|
|
pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, down,
|
|
pm->ps->clientNum, pm->tracemask );
|
|
if ( !trace.allsolid ) {
|
|
VectorCopy( trace.endpos, pm->ps->origin );
|
|
}
|
|
if ( trace.fraction < 1.0f ) {
|
|
dotNV = DotProduct( trace.plane.normal, pm->ps->velocity );
|
|
if ( dotNV >= 0.0f && fabs( dotNV ) >= 0.001f ) {
|
|
// velocity is moving away from the surface, skip clipping
|
|
goto skipClip;
|
|
}
|
|
PM_ClipVelocity( pm->ps->velocity, trace.plane.normal,
|
|
pm->ps->velocity, OVERCLIP );
|
|
}
|
|
|
|
skipClip:
|
|
// trace from start to final position to check if we actually stepped
|
|
pm->trace( &trace, start_o, pm->mins, pm->maxs, pm->ps->origin,
|
|
pm->ps->clientNum, pm->tracemask );
|
|
if ( trace.fraction < 1.0f ) {
|
|
// didn't make it all the way -- use the step move result
|
|
delta = pm->ps->origin[2] - start_o[2];
|
|
|
|
if ( delta > 2.0f ) {
|
|
pm->field_0dc = delta;
|
|
pm->field_0e0 = pm->cmd.serverTime;
|
|
}
|
|
|
|
// apply velocity damping when stepping up while airborne
|
|
if ( !pml.groundPlane && delta > 0.0f && start_vz > 0.0f ) {
|
|
float dampFactor = 1.0f - _DAT_003cfe90;
|
|
pm->ps->velocity[0] *= dampFactor;
|
|
pm->ps->velocity[1] *= dampFactor;
|
|
}
|
|
|
|
// step jump / edge grab logic
|
|
if ( pmove_stepJumpFlag && pm->ps->pm_type == PM_NORMAL
|
|
&& delta > 0.0f && (int)pm->waterlevel < 2 ) {
|
|
if ( PM_CanJump() || ( DAT_003cfe30 && PM_CanEdgeGrab() ) ) {
|
|
// check if there is walkable ground at the projected position
|
|
stepStart[0] = projected[0];
|
|
stepStart[1] = projected[1];
|
|
stepStart[2] = projected[2] + (float)pm_stepHeight;
|
|
stepEnd[0] = projected[0];
|
|
stepEnd[1] = projected[1];
|
|
stepEnd[2] = projected[2] - (float)pm_stepHeight;
|
|
pm->trace( &trace, stepStart, pm->mins, pm->maxs, stepEnd,
|
|
pm->ps->clientNum, pm->tracemask );
|
|
if ( !trace.startsolid && !trace.allsolid
|
|
&& trace.plane.normal[2] >= MIN_WALK_NORMAL ) {
|
|
if ( PM_CanJump() ) {
|
|
// normal step jump
|
|
pml.isStepJump = 1;
|
|
PM_Jump();
|
|
pml.isStepJump = 0;
|
|
} else if ( DAT_003cfe30 && PM_CanEdgeGrab() ) {
|
|
// edge grab: check for open air below with shrunk bbox
|
|
edgeMins[0] = pm->mins[0] + 1.0f;
|
|
edgeMins[1] = pm->mins[1] + 1.0f;
|
|
edgeMins[2] = pm->mins[2];
|
|
edgeMaxs[0] = pm->maxs[0] - 1.0f;
|
|
edgeMaxs[1] = pm->maxs[1] - 1.0f;
|
|
edgeMaxs[2] = pm->maxs[2];
|
|
edgeEnd[0] = pm->ps->origin[0];
|
|
edgeEnd[1] = pm->ps->origin[1];
|
|
edgeEnd[2] = pm->ps->origin[2] - 64.0f;
|
|
pm->trace( &trace, pm->ps->origin, edgeMins, edgeMaxs, edgeEnd,
|
|
pm->ps->clientNum, pm->tracemask );
|
|
if ( trace.fraction == 1.0f ) {
|
|
// over an edge -- do a crouch step jump
|
|
pml.isJumppad = 1;
|
|
PM_Jump();
|
|
pml.isJumppad = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pm->debugLevel ) {
|
|
Com_Printf( "%i:stepped %f\n", c_pmove, delta );
|
|
}
|
|
}
|
|
}
|