From: TimePath Date: Mon, 8 Dec 2014 03:33:26 +0000 (+1100) Subject: Extract movement prediction into its own module and prepare for merge with server X-Git-Tag: xonotic-v0.8.1~38^2~119 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=2c6717ccae14cb530ee54a5e617725224bf9867e;p=xonotic%2Fxonotic-data.pk3dir.git Extract movement prediction into its own module and prepare for merge with server --- diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index 86a8b4339..316cbf5ac 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -122,6 +122,7 @@ command/cl_cmd.qc ../common/nades.qc ../common/buffs.qc +../common/physics.qc ../warpzonelib/anglestransform.qc ../warpzonelib/mathlib.qc diff --git a/qcsrc/common/physics.qc b/qcsrc/common/physics.qc new file mode 100644 index 000000000..feb799b57 --- /dev/null +++ b/qcsrc/common/physics.qc @@ -0,0 +1,779 @@ +// TODO: water prediction + +// TODO: move to a common header +#define VLEN2(v) dotproduct(v, v) + +// Client/server mappings +#ifdef CSQC + + #define PHYS_INPUT_ANGLES(s) input_angles + #define PHYS_INPUT_BUTTONS(s) input_buttons + + #define PHYS_INPUT_TIMELENGTH input_timelength + + #define PHYS_INPUT_MOVEVALUES(s) input_movevalues + + #define GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE + #define GAMEPLAYFIX_NOGRAVITYONGROUND moveflags & MOVEFLAG_NOGRAVITYONGROUND + #define GAMEPLAYFIX_Q2AIRACCELERATE moveflags & MOVEFLAG_Q2AIRACCELERATE + + #define IS_DUCKED(s) (s.pmove_flags & PMF_DUCKED) + #define SET_DUCKED(s) s.pmove_flags |= PMF_DUCKED + #define UNSET_DUCKED(s) s.pmove_flags &= ~PMF_DUCKED + + #define IS_JUMP_HELD(s) (s.pmove_flags & PMF_JUMP_HELD) + #define SET_JUMP_HELD(s) s.pmove_flags |= PMF_JUMP_HELD + #define UNSET_JUMP_HELD(s) s.pmove_flags &= ~PMF_JUMP_HELD + + #define IS_ONGROUND(s) (s.pmove_flags & PMF_ONGROUND) + #define SET_ONGROUND(s) s.pmove_flags |= PMF_ONGROUND + #define UNSET_ONGROUND(s) s.pmove_flags &= ~PMF_ONGROUND + + #define PHYS_ACCELERATE getstatf(STAT_MOVEVARS_ACCELERATE) + #define PHYS_AIRACCEL_QW getstatf(STAT_MOVEVARS_AIRACCEL_QW) + #define PHYS_AIRACCEL_QW_STRETCHFACTOR getstatf(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR) + #define PHYS_AIRACCEL_SIDEWAYS_FRICTION getstatf(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION) + #define PHYS_AIRACCELERATE getstatf(STAT_MOVEVARS_AIRACCELERATE) + #define PHYS_AIRCONTROL getstatf(STAT_MOVEVARS_AIRCONTROL) + #define PHYS_AIRCONTROL_PENALTY getstatf(STAT_MOVEVARS_AIRCONTROL_PENALTY) + #define PHYS_AIRCONTROL_POWER getstatf(STAT_MOVEVARS_AIRCONTROL_POWER) + #define PHYS_AIRSPEEDLIMIT_NONQW getstatf(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW) + #define PHYS_AIRSTOPACCELERATE getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) + #define PHYS_AIRSTRAFEACCEL_QW getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW) + #define PHYS_AIRSTRAFEACCELERATE getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE) + #define PHYS_EDGEFRICTION getstatf(STAT_MOVEVARS_EDGEFRICTION) + #define PHYS_ENTGRAVITY(s) getstatf(STAT_MOVEVARS_ENTGRAVITY) + #define PHYS_FRICTION getstatf(STAT_MOVEVARS_FRICTION) + #define PHYS_GRAVITY getstatf(STAT_MOVEVARS_GRAVITY) + #define PHYS_JUMPVELOCITY getstatf(STAT_MOVEVARS_JUMPVELOCITY) + #define PHYS_MAXAIRSPEED getstatf(STAT_MOVEVARS_MAXAIRSPEED) + #define PHYS_MAXAIRSTRAFESPEED getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED) + #define PHYS_MAXSPEED getstatf(STAT_MOVEVARS_MAXSPEED) + #define PHYS_STEPHEIGHT getstatf(STAT_MOVEVARS_STEPHEIGHT) + #define PHYS_STOPSPEED getstatf(STAT_MOVEVARS_STOPSPEED) + #define PHYS_WARSOWBUNNY_ACCEL getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) + #define PHYS_WARSOWBUNNY_BACKTOSIDERATIO getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO) + #define PHYS_WARSOWBUNNY_AIRFORWARDACCEL getstatf(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL) + #define PHYS_WARSOWBUNNY_TOPSPEED getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED) + #define PHYS_WARSOWBUNNY_TURNACCEL getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) + +#elif defined(SVQC) + + #define PHYS_INPUT_ANGLES(s) s.v_angle + // FIXME + #define PHYS_INPUT_BUTTONS(s) 0 + + #define PHYS_INPUT_TIMELENGTH frametime + + #define PHYS_INPUT_MOVEVALUES(s) s.movement + + #define GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE autocvar_sv_gameplayfix_gravityunaffectedbyticrate + #define GAMEPLAYFIX_NOGRAVITYONGROUND cvar("sv_gameplayfix_nogravityonground") + #define GAMEPLAYFIX_Q2AIRACCELERATE autocvar_sv_gameplayfix_q2airaccelerate + + #define IS_DUCKED(s) s.crouch + #define SET_DUCKED(s) s.crouch = TRUE + #define UNSET_DUCKED(s) s.crouch = FALSE + + #define IS_JUMP_HELD(s) (s.flags & FL_JUMPRELEASED == 0) + #define SET_JUMP_HELD(s) s.flags &= ~FL_JUMPRELEASED + #define UNSET_JUMP_HELD(s) s.flags |= FL_JUMPRELEASED + + #define IS_ONGROUND(s) (s.flags & FL_ONGROUND) + #define SET_ONGROUND(s) s.flags |= FL_ONGROUND + #define UNSET_ONGROUND(s) s.flags &= ~FL_ONGROUND + + #define PHYS_ACCELERATE autocvar_sv_accelerate + #define PHYS_AIRACCEL_QW autocvar_sv_airaccel_qw + #define PHYS_AIRACCEL_QW_STRETCHFACTOR autocvar_sv_airaccel_qw_stretchfactor + #define PHYS_AIRACCEL_SIDEWAYS_FRICTION autocvar_sv_airaccel_sideways_friction + #define PHYS_AIRACCELERATE autocvar_sv_airaccelerate + #define PHYS_AIRCONTROL autocvar_sv_aircontrol + #define PHYS_AIRCONTROL_PENALTY autocvar_sv_aircontrol_penalty + #define PHYS_AIRCONTROL_POWER autocvar_sv_aircontrol_power + #define PHYS_AIRSPEEDLIMIT_NONQW autocvar_sv_airspeedlimit_nonqw + #define PHYS_AIRSTOPACCELERATE autocvar_sv_airstopaccelerate + #define PHYS_AIRSTRAFEACCEL_QW autocvar_sv_airstrafeaccel_qw + #define PHYS_AIRSTRAFEACCELERATE autocvar_sv_airstrafeaccelerate + #define PHYS_EDGEFRICTION 1 + #define PHYS_ENTGRAVITY(s) s.gravity + #define PHYS_FRICTION autocvar_sv_friction + #define PHYS_GRAVITY autocvar_sv_gravity + #define PHYS_JUMPVELOCITY autocvar_sv_jumpvelocity + #define PHYS_MAXAIRSPEED autocvar_sv_maxairspeed + #define PHYS_MAXAIRSTRAFESPEED autocvar_sv_maxairstrafespeed + #define PHYS_MAXSPEED autocvar_sv_maxspeed + #define PHYS_STEPHEIGHT autocvar_sv_stepheight + #define PHYS_STOPSPEED autocvar_sv_stopspeed + #define PHYS_WARSOWBUNNY_ACCEL autocvar_sv_warsowbunny_accel + #define PHYS_WARSOWBUNNY_BACKTOSIDERATIO autocvar_sv_warsowbunny_backtosideratio + #define PHYS_WARSOWBUNNY_AIRFORWARDACCEL autocvar_sv_warsowbunny_airforwardaccel + #define PHYS_WARSOWBUNNY_TOPSPEED autocvar_sv_warsowbunny_topspeed + #define PHYS_WARSOWBUNNY_TURNACCEL autocvar_sv_warsowbunny_turnaccel + +#endif + +float IsMoveInDirection(vector mv, float angle) // key mix factor +{ + if(mv_x == 0 && mv_y == 0) + return 0; // avoid division by zero + angle -= RAD2DEG * atan2(mv_y, mv_x); + angle = remainder(angle, 360) / 45; + if(angle > 1) + return 0; + if(angle < -1) + return 0; + return 1 - fabs(angle); +} + +float GeomLerp(float a, float lerp, float b) +{ + if(a == 0) + { + if(lerp < 1) + return 0; + else + return b; + } + if(b == 0) + { + if(lerp > 0) + return 0; + else + return a; + } + return a * pow(fabs(b / a), lerp); +} + +float pmove_waterjumptime; // weird engine flag we shouldn't really use but have to for now + +const float unstick_count = 27; +vector unstick_offsets[unstick_count] = +{ +// 1 no nudge (just return the original if this test passes) + '0.000 0.000 0.000', +// 6 simple nudges + ' 0.000 0.000 0.125', '0.000 0.000 -0.125', + '-0.125 0.000 0.000', '0.125 0.000 0.000', + ' 0.000 -0.125 0.000', '0.000 0.125 0.000', +// 4 diagonal flat nudges + '-0.125 -0.125 0.000', '0.125 -0.125 0.000', + '-0.125 0.125 0.000', '0.125 0.125 0.000', +// 8 diagonal upward nudges + '-0.125 0.000 0.125', '0.125 0.000 0.125', + ' 0.000 -0.125 0.125', '0.000 0.125 0.125', + '-0.125 -0.125 0.125', '0.125 -0.125 0.125', + '-0.125 0.125 0.125', '0.125 0.125 0.125', +// 8 diagonal downward nudges + '-0.125 0.000 -0.125', '0.125 0.000 -0.125', + ' 0.000 -0.125 -0.125', '0.000 0.125 -0.125', + '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125', + '-0.125 0.125 -0.125', '0.125 0.125 -0.125', +}; + +void CSQC_ClientMovement_Unstick(entity s) +{ + float i; + vector neworigin; + for (i = 0; i < unstick_count; i++) + { + neworigin = unstick_offsets[i] + s.origin; + tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, s); + if (!trace_startsolid) + { + s.origin = neworigin; + return;// true; + } + } +} + +void CSQC_ClientMovement_UpdateStatus(entity s) +{ + float f; + vector origin1, origin2; + + // make sure player is not stuck + CSQC_ClientMovement_Unstick(s); + + // set crouched + if (PHYS_INPUT_BUTTONS(s) & 16) + { + // wants to crouch, this always works.. + if (!IS_DUCKED(s)) + SET_DUCKED(s); + } + else + { + // wants to stand, if currently crouching we need to check for a + // low ceiling first + if (IS_DUCKED(s)) + { + tracebox(s.origin, PL_MIN, PL_MAX, s.origin, MOVE_NORMAL, s); + if (!trace_startsolid) + UNSET_DUCKED(s); + } + } + if (IS_DUCKED(s)) + { + s.mins = PL_CROUCH_MIN; + s.maxs = PL_CROUCH_MAX; + } + else + { + s.mins = PL_MIN; + s.maxs = PL_MAX; + } + + // set onground + origin1 = s.origin; + origin1_z += 1; + origin2 = s.origin; + origin2_z -= 1; // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :) + + tracebox(origin1, s.mins, s.maxs, origin2, MOVE_NORMAL, s); + if(trace_fraction < 1 && trace_plane_normal_z > 0.7) + { + SET_ONGROUND(s); + + // this code actually "predicts" an impact; so let's clip velocity first + f = dotproduct(s.velocity, trace_plane_normal); + if(f < 0) // only if moving downwards actually + s.velocity -= f * trace_plane_normal; + } + else + UNSET_ONGROUND(s); + + // set watertype/waterlevel + origin1 = s.origin; + origin1_z += s.mins_z + 1; + s.waterlevel = WATERLEVEL_NONE; + // TODO: convert +// s.watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK; +// if (s.watertype) +// { +// s.waterlevel = WATERLEVEL_WETFEET; +// origin1[2] = s.origin[2] + (s.mins[2] + s.maxs[2]) * 0.5f; +// if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) +// { +// s.waterlevel = WATERLEVEL_SWIMMING; +// origin1[2] = s.origin[2] + 22; +// if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) +// s.waterlevel = WATERLEVEL_SUBMERGED; +// } +// } +// +// // water jump prediction +// if (IS_ONGROUND(s) || s.velocity_z <= 0 || pmove_waterjumptime <= 0) +// pmove_waterjumptime = 0; +} + +void CSQC_ClientMovement_Move(entity s) +{ + float bump; + float t; + float f; + vector neworigin; + vector currentorigin2; + vector neworigin2; + vector primalvelocity; + float old_trace1_fraction; + vector old_trace1_endpos; + vector old_trace1_plane_normal; + float old_trace2_fraction; + vector old_trace2_plane_normal; + CSQC_ClientMovement_UpdateStatus(s); + primalvelocity = s.velocity; + for (bump = 0, t = PHYS_INPUT_TIMELENGTH; bump < 8 && VLEN2(s.velocity) > 0; bump++) + { + neworigin = s.origin + t * s.velocity; + tracebox(s.origin, s.mins, s.maxs, neworigin, MOVE_NORMAL, s); + old_trace1_fraction = trace_fraction; + old_trace1_endpos = trace_endpos; + old_trace1_plane_normal = trace_plane_normal; + if (trace_fraction < 1 && trace_plane_normal_z == 0) + { + // may be a step or wall, try stepping up + // first move forward at a higher level + currentorigin2 = s.origin; + currentorigin2_z += PHYS_STEPHEIGHT; + neworigin2 = neworigin; + neworigin2_z = s.origin_z + PHYS_STEPHEIGHT; + tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s); + if (!trace_startsolid) + { + // then move down from there + currentorigin2 = trace_endpos; + neworigin2 = trace_endpos; + neworigin2_z = s.origin_z; + old_trace2_fraction = trace_fraction; + old_trace2_plane_normal = trace_plane_normal; + tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s); + //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]); + // accept the new trace if it made some progress + if (fabs(trace_endpos_x - old_trace1_endpos_x) >= 0.03125 || fabs(trace_endpos_y - old_trace1_endpos_y) >= 0.03125) + { + trace_fraction = old_trace2_fraction; + trace_endpos = trace_endpos; + trace_plane_normal = old_trace2_plane_normal; + } + else + { + trace_fraction = old_trace1_fraction; + trace_endpos = old_trace1_endpos; + trace_plane_normal = old_trace1_plane_normal; + } + } + } + + // check if it moved at all + if (trace_fraction >= 0.001) + s.origin = trace_endpos; + + // check if it moved all the way + if (trace_fraction == 1) + break; + + // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate + // I'm pretty sure I commented it out solely because it seemed redundant + // this got commented out in a change that supposedly makes the code match QW better + // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block + if (trace_plane_normal_z > 0.7) + SET_ONGROUND(s); + + t -= t * trace_fraction; + + f = dotproduct(s.velocity, trace_plane_normal); + s.velocity -= f * trace_plane_normal; + } + if (pmove_waterjumptime > 0) + s.velocity = primalvelocity; +} + +void CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(entity s, vector wishdir, float wishspeed) +{ + float zspeed, xyspeed, dot, k; + +#if 0 + // this doesn't play well with analog input + if(s.movement_x == 0 || s.movement_y != 0) + return; // can't control movement if not moving forward or backward + k = 32; +#else + k = 32 * (2 * IsMoveInDirection(PHYS_INPUT_MOVEVALUES(s), 0) - 1); + if(k <= 0) + return; +#endif + + k *= bound(0, wishspeed / PHYS_MAXAIRSPEED, 1); + + zspeed = s.velocity_z; + s.velocity_z = 0; + xyspeed = vlen(s.velocity); s.velocity = normalize(s.velocity); + + dot = s.velocity * wishdir; + + if(dot > 0) // we can't change direction while slowing down + { + k *= pow(dot, PHYS_AIRCONTROL_POWER)*PHYS_INPUT_TIMELENGTH; + xyspeed = max(0, xyspeed - PHYS_AIRCONTROL_PENALTY * sqrt(max(0, 1 - dot*dot)) * k/32); + k *= PHYS_AIRCONTROL; + s.velocity = normalize(s.velocity * xyspeed + wishdir * k); + } + + s.velocity = s.velocity * xyspeed; + s.velocity_z = zspeed; +} + +float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor) +{ + return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw); +} + +void CSQC_ClientMovement_Physics_PM_Accelerate(entity s, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit) +{ + float vel_straight; + float vel_z; + vector vel_perpend; + float step; + vector vel_xy; + float vel_xy_current; + float vel_xy_backward, vel_xy_forward; + float speedclamp; + + if(stretchfactor > 0) + speedclamp = stretchfactor; + else if(accelqw < 0) + speedclamp = 1; + else + speedclamp = -1; // no clamping + + if(accelqw < 0) + accelqw = -accelqw; + + if(GAMEPLAYFIX_Q2AIRACCELERATE) + wishspeed0 = wishspeed; // don't need to emulate this Q1 bug + + vel_straight = dotproduct(s.velocity, wishdir); + vel_z = s.velocity_z; + vel_xy = s.velocity; + vel_xy_z -= vel_z; + vel_perpend = vel_xy - vel_straight * wishdir; + + step = accel * PHYS_INPUT_TIMELENGTH * wishspeed0; + + vel_xy_current = vlen(vel_xy); + if(speedlimit > 0) + accelqw = CSQC_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed)); + vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw); + vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw); + if(vel_xy_backward < 0) + vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards + + vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw); + + if(sidefric < 0 && VLEN2(vel_perpend)) + // negative: only apply so much sideways friction to stay below the speed you could get by "braking" + { + float f, fmin; + f = max(0, 1 + PHYS_INPUT_TIMELENGTH * wishspeed * sidefric); + fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / VLEN2(vel_perpend); + // assume: fmin > 1 + // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend + // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend + // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy + // obviously, this cannot be + if(fmin <= 0) + vel_perpend *= f; + else + { + fmin = sqrt(fmin); + vel_perpend *= max(fmin, f); + } + } + else + vel_perpend *= max(0, 1 - PHYS_INPUT_TIMELENGTH * wishspeed * sidefric); + + s.velocity = vel_perpend + vel_straight * wishdir; + + if(speedclamp >= 0) + { + float vel_xy_preclamp; + vel_xy_preclamp = vlen(s.velocity); + if(vel_xy_preclamp > 0) // prevent division by zero + { + vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp; + if(vel_xy_current < vel_xy_preclamp) + s.velocity *= (vel_xy_current / vel_xy_preclamp); + } + } + + s.velocity_z += vel_z; +} + +void CSQC_ClientMovement_Physics_PM_AirAccelerate(entity s, vector wishdir, float wishspeed) +{ + vector curvel, wishvel, acceldir, curdir; + float addspeed, accelspeed, curspeed, f; + float dot; + + if(wishspeed == 0) + return; + + curvel = s.velocity; + curvel_z = 0; + curspeed = vlen(curvel); + + if(wishspeed > curspeed * 1.01) + { + wishspeed = min(wishspeed, curspeed + PHYS_WARSOWBUNNY_AIRFORWARDACCEL * PHYS_MAXSPEED * PHYS_INPUT_TIMELENGTH); + } + else + { + f = max(0, (PHYS_WARSOWBUNNY_TOPSPEED - curspeed) / (PHYS_WARSOWBUNNY_TOPSPEED - PHYS_MAXSPEED)); + wishspeed = max(curspeed, PHYS_WARSOWBUNNY_ACCEL) + PHYS_WARSOWBUNNY_ACCEL * f * PHYS_WARSOWBUNNY_ACCEL * PHYS_INPUT_TIMELENGTH; + } + wishvel = wishdir * wishspeed; + acceldir = wishvel - curvel; + addspeed = vlen(acceldir); + acceldir = normalize(acceldir); + + accelspeed = min(addspeed, PHYS_WARSOWBUNNY_TURNACCEL * PHYS_WARSOWBUNNY_ACCEL * PHYS_INPUT_TIMELENGTH); + + if(PHYS_WARSOWBUNNY_BACKTOSIDERATIO < 1) + { + curdir = normalize(curvel); + dot = acceldir * curdir; + if(dot < 0) + acceldir = acceldir - (1 - PHYS_WARSOWBUNNY_BACKTOSIDERATIO) * dot * curdir; + } + + s.velocity += accelspeed * acceldir; +} + +void CSQC_ClientMovement_Physics_Walk(entity s) +{ + float friction; + float wishspeed; + float addspeed; + float accelspeed; + float f; + float g; + vector wishvel; + vector wishdir; + vector yawangles; + + // jump if on ground with jump button pressed but only if it has been + // released at least once since the last jump + if (PHYS_INPUT_BUTTONS(s) & 2) + { + if (IS_ONGROUND(s) && (!IS_JUMP_HELD(s) || !cvar("cl_movement_track_canjump"))) + { + s.velocity_z += PHYS_JUMPVELOCITY; + UNSET_ONGROUND(s); + SET_JUMP_HELD(s); // canjump = false + } + } + else + UNSET_JUMP_HELD(s); // canjump = true + + // calculate movement vector + yawangles = '0 0 0'; + yawangles_y = PHYS_INPUT_ANGLES(s)_y; + makevectors(yawangles); + wishvel = PHYS_INPUT_MOVEVALUES(s)_x * v_forward + PHYS_INPUT_MOVEVALUES(s)_y * v_right; + + // split wishvel into wishspeed and wishdir + wishspeed = vlen(wishvel); + if (wishspeed) + wishdir = wishvel / wishspeed; + else + wishdir = '0 0 0'; + // check if onground + if (IS_ONGROUND(s)) + { + wishspeed = min(wishspeed, PHYS_MAXSPEED); + if (IS_DUCKED(s)) + wishspeed *= 0.5; + + // apply edge friction + f = sqrt(s.velocity_x * s.velocity_x + s.velocity_y * s.velocity_y); + if (f > 0) + { + friction = PHYS_FRICTION; + if (PHYS_EDGEFRICTION != 1) + { + vector neworigin2; + vector neworigin3; + // note: QW uses the full player box for the trace, and yet still + // uses s.origin_z + s.mins_z, which is clearly an bug, but + // this mimics it for compatibility + neworigin2 = s.origin; + neworigin2_x += s.velocity_x*(16/f); + neworigin2_y += s.velocity_y*(16/f); + neworigin2_z += s.mins_z; + neworigin3 = neworigin2; + neworigin3_z -= 34; + traceline(neworigin2, neworigin3, MOVE_NORMAL, s); + if (trace_fraction == 1 && !trace_startsolid) + friction *= PHYS_EDGEFRICTION; + } + // apply ground friction + f = 1 - PHYS_INPUT_TIMELENGTH * friction * ((f < PHYS_STOPSPEED) ? (PHYS_STOPSPEED / f) : 1); + f = max(f, 0); + s.velocity *= f; + } + addspeed = wishspeed - dotproduct(s.velocity, wishdir); + if (addspeed > 0) + { + accelspeed = min(PHYS_ACCELERATE * PHYS_INPUT_TIMELENGTH * wishspeed, addspeed); + s.velocity += accelspeed * wishdir; + } + g = PHYS_GRAVITY * PHYS_ENTGRAVITY(s) * PHYS_INPUT_TIMELENGTH; + if(!(GAMEPLAYFIX_NOGRAVITYONGROUND)) + { + if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE) + s.velocity_z -= g * 0.5; + else + s.velocity_z -= g; + } + if (VLEN2(s.velocity)) + CSQC_ClientMovement_Move(s); + if(!(GAMEPLAYFIX_NOGRAVITYONGROUND) || !IS_ONGROUND(s)) + { + if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE) + s.velocity_z -= g * 0.5; + } + } + else + { + if (pmove_waterjumptime <= 0) + { + // apply air speed limit + float accel, wishspeed0, wishspeed2, accelqw, strafity; + float accelerating; + + accelqw = PHYS_AIRACCEL_QW; + wishspeed0 = wishspeed; + wishspeed = min(wishspeed, PHYS_MAXAIRSPEED); + if (IS_DUCKED(s)) + wishspeed *= 0.5; + accel = PHYS_AIRACCELERATE; + + accelerating = (dotproduct(s.velocity, wishdir) > 0); + wishspeed2 = wishspeed; + + // CPM: air control + if(PHYS_AIRSTOPACCELERATE != 0) + { + vector curdir; + curdir_x = s.velocity_x; + curdir_y = s.velocity_y; + curdir_z = 0; + curdir = normalize(curdir); + accel = accel + (PHYS_AIRSTOPACCELERATE - accel) * max(0, -dotproduct(curdir, wishdir)); + } + strafity = IsMoveInDirection(PHYS_INPUT_MOVEVALUES(s), -90) + IsMoveInDirection(PHYS_INPUT_MOVEVALUES(s), +90); // if one is nonzero, other is always zero + if(PHYS_MAXAIRSTRAFESPEED) + wishspeed = min(wishspeed, GeomLerp(PHYS_MAXAIRSPEED, strafity, PHYS_MAXAIRSTRAFESPEED)); + if(PHYS_AIRSTRAFEACCELERATE) + accel = GeomLerp(PHYS_AIRACCELERATE, strafity, PHYS_AIRSTRAFEACCELERATE); + if(PHYS_AIRSTRAFEACCEL_QW) + accelqw = + (((strafity > 0.5 ? PHYS_AIRSTRAFEACCEL_QW : PHYS_AIRACCEL_QW) >= 0) ? +1 : -1) + * + (1 - GeomLerp(1 - fabs(PHYS_AIRACCEL_QW), strafity, 1 - fabs(PHYS_AIRSTRAFEACCEL_QW))); + // !CPM + + if(PHYS_WARSOWBUNNY_TURNACCEL && accelerating && PHYS_INPUT_MOVEVALUES(s)_y == 0 && PHYS_INPUT_MOVEVALUES(s)_x != 0) + CSQC_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2); + else + CSQC_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, PHYS_AIRACCEL_QW_STRETCHFACTOR, PHYS_AIRACCEL_SIDEWAYS_FRICTION / PHYS_MAXAIRSPEED, PHYS_AIRSPEEDLIMIT_NONQW); + + if(PHYS_AIRCONTROL) + CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2); + } + g = PHYS_GRAVITY * PHYS_ENTGRAVITY(s) * PHYS_INPUT_TIMELENGTH; + if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE) + s.velocity_z -= g * 0.5; + else + s.velocity_z -= g; + CSQC_ClientMovement_Move(s); + if(!(GAMEPLAYFIX_NOGRAVITYONGROUND) || !IS_ONGROUND(s)) + { + if(GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE) + s.velocity_z -= g * 0.5; + } + } +} + +// TODO: merge this with main physics frame +void CSQC_ClientMovement_Physics_Swim(entity s) +{ + // swimming + self.flags &= ~FL_ONGROUND; + + makevectors(PHYS_INPUT_ANGLES(s)); + //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z; + vector wishvel = v_forward * PHYS_INPUT_MOVEVALUES(s)_x + v_right * PHYS_INPUT_MOVEVALUES(s)_y + '0 0 1' * PHYS_INPUT_MOVEVALUES(s)_z; + if (wishvel == '0 0 0') + wishvel = '0 0 -60'; // drift towards bottom + + vector wishdir = normalize(wishvel); + float wishspeed = vlen(wishvel); + if (wishspeed > PHYS_MAXSPEED) + wishspeed = PHYS_MAXSPEED; + wishspeed = wishspeed * 0.7; + + // water friction + self.velocity = self.velocity * (1 - PHYS_INPUT_TIMELENGTH * PHYS_FRICTION); + + // water acceleration + CSQC_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed, PHYS_ACCELERATE, 1, 0, 0, 0); +} + +void CSQC_ClientMovement_PlayerMove(entity s) +{ + //Con_Printf(" %f", frametime); + if (!(PHYS_INPUT_BUTTONS(s) & 2)) // !jump + UNSET_JUMP_HELD(s); // canjump = true + pmove_waterjumptime -= PHYS_INPUT_TIMELENGTH; + CSQC_ClientMovement_UpdateStatus(s); + if (s.waterlevel >= WATERLEVEL_SWIMMING) + CSQC_ClientMovement_Physics_Swim(s); + else + CSQC_ClientMovement_Physics_Walk(s); +} + +void CSQC_ClientMovement_PlayerMove_Frame(entity s) +{ + // if a move is more than 50ms, do it as two moves (matching qwsv) + //Con_Printf("%i ", s.cmd.msec); + if(PHYS_INPUT_TIMELENGTH > 0.0005) + { + if (PHYS_INPUT_TIMELENGTH > 0.05) + { + PHYS_INPUT_TIMELENGTH /= 2; + CSQC_ClientMovement_PlayerMove(s); + } + CSQC_ClientMovement_PlayerMove(s); + } + else + { + // we REALLY need this handling to happen, even if the move is not executed + if (!(PHYS_INPUT_BUTTONS(s) & 2)) // !jump + UNSET_JUMP_HELD(s); // canjump = true + } +} + +#undef VLEN2 + +#undef PHYS_INPUT_ANGLES +#undef PHYS_INPUT_BUTTONS + +#undef PHYS_INPUT_TIMELENGTH + +#undef PHYS_INPUT_MOVEVALUES + +#undef GAMEPLAYFIX_GRAVITYUNAFFECTEDBYTICRATE +#undef GAMEPLAYFIX_NOGRAVITYONGROUND +#undef GAMEPLAYFIX_Q2AIRACCELERATE + +#undef IS_DUCKED +#undef SET_DUCKED +#undef UNSET_DUCKED + +#undef IS_JUMP_HELD +#undef SET_JUMP_HELD +#undef UNSET_JUMP_HELD + +#undef IS_ONGROUND +#undef SET_ONGROUND +#undef UNSET_ONGROUND + +#undef PHYS_ACCELERATE +#undef PHYS_AIRACCEL_QW +#undef PHYS_AIRACCEL_QW_STRETCHFACTOR +#undef PHYS_AIRACCEL_SIDEWAYS_FRICTION +#undef PHYS_AIRACCELERATE +#undef PHYS_AIRCONTROL +#undef PHYS_AIRCONTROL_PENALTY +#undef PHYS_AIRCONTROL_POWER +#undef PHYS_AIRSPEEDLIMIT_NONQW +#undef PHYS_AIRSTOPACCELERATE +#undef PHYS_AIRSTRAFEACCEL_QW +#undef PHYS_AIRSTRAFEACCELERATE +#undef PHYS_EDGEFRICTION +#undef PHYS_ENTGRAVITY +#undef PHYS_FRICTION +#undef PHYS_GRAVITY +#undef PHYS_JUMPVELOCITY +#undef PHYS_MAXAIRSPEED +#undef PHYS_MAXAIRSTRAFESPEED +#undef PHYS_MAXSPEED +#undef PHYS_STEPHEIGHT +#undef PHYS_STOPSPEED +#undef PHYS_WARSOWBUNNY_ACCEL +#undef PHYS_WARSOWBUNNY_BACKTOSIDERATIO +#undef PHYS_WARSOWBUNNY_AIRFORWARDACCEL +#undef PHYS_WARSOWBUNNY_TOPSPEED +#undef PHYS_WARSOWBUNNY_TURNACCEL \ No newline at end of file diff --git a/qcsrc/csqcmodellib/cl_player.qc b/qcsrc/csqcmodellib/cl_player.qc index 4b848e9b7..1310c43ad 100644 --- a/qcsrc/csqcmodellib/cl_player.qc +++ b/qcsrc/csqcmodellib/cl_player.qc @@ -120,641 +120,7 @@ void CSQCPlayer_SavePrediction() csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED; } -// TODO: water prediction -float pmove_waterjumptime; // weird engine flag we shouldn't really use but have to for now -// TODO: move to a common header -#define vlen2(v) dotproduct(v, v) - -const float unstick_count = 27; -vector unstick_offsets[unstick_count] = -{ -// 1 no nudge (just return the original if this test passes) - '0.000 0.000 0.000', -// 6 simple nudges - ' 0.000 0.000 0.125', '0.000 0.000 -0.125', - '-0.125 0.000 0.000', '0.125 0.000 0.000', - ' 0.000 -0.125 0.000', '0.000 0.125 0.000', -// 4 diagonal flat nudges - '-0.125 -0.125 0.000', '0.125 -0.125 0.000', - '-0.125 0.125 0.000', '0.125 0.125 0.000', -// 8 diagonal upward nudges - '-0.125 0.000 0.125', '0.125 0.000 0.125', - ' 0.000 -0.125 0.125', '0.000 0.125 0.125', - '-0.125 -0.125 0.125', '0.125 -0.125 0.125', - '-0.125 0.125 0.125', '0.125 0.125 0.125', -// 8 diagonal downward nudges - '-0.125 0.000 -0.125', '0.125 0.000 -0.125', - ' 0.000 -0.125 -0.125', '0.000 0.125 -0.125', - '-0.125 -0.125 -0.125', '0.125 -0.125 -0.125', - '-0.125 0.125 -0.125', '0.125 0.125 -0.125', -}; - -float CSQC_ClientMovement_Unstick(entity s) -{ - float i; - vector neworigin; - for (i = 0; i < unstick_count; i++) - { - neworigin = unstick_offsets[i] + s.origin; - tracebox(neworigin, PL_CROUCH_MIN, PL_CROUCH_MAX, neworigin, MOVE_NORMAL, s); - if (!trace_startsolid) - { - s.origin = neworigin; - return true; - } - } - // if all offsets failed, give up - return false; -} - -void CSQC_ClientMovement_UpdateStatus(entity s) -{ - float f; - vector origin1, origin2; - - // make sure player is not stuck - CSQC_ClientMovement_Unstick(s); - - // set crouched - if (input_buttons & 16) - { - // wants to crouch, this always works.. - if (!s.pmove_flags & PMF_DUCKED) - s.pmove_flags |= PMF_DUCKED; - } - else - { - // wants to stand, if currently crouching we need to check for a - // low ceiling first - if (s.pmove_flags & PMF_DUCKED) - { - tracebox(s.origin, PL_MIN, PL_MAX, s.origin, MOVE_NORMAL, s); - if (!trace_startsolid) - s.pmove_flags &= ~PMF_DUCKED; - } - } - if (s.pmove_flags & PMF_DUCKED) - { - s.mins = PL_CROUCH_MIN; - s.maxs = PL_CROUCH_MAX; - } - else - { - s.mins = PL_MIN; - s.maxs = PL_MAX; - } - - // set onground - origin1 = s.origin; - origin1_z += 1; - origin2 = s.origin; - origin2_z -= 1; // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :) - - tracebox(origin1, s.mins, s.maxs, origin2, MOVE_NORMAL, s); - if(trace_fraction < 1 && trace_plane_normal_z > 0.7) - { - s.pmove_flags |= PMF_ONGROUND; - - // this code actually "predicts" an impact; so let's clip velocity first - f = dotproduct(s.velocity, trace_plane_normal); - if(f < 0) // only if moving downwards actually - s.velocity -= f * trace_plane_normal; - } - else - s.pmove_flags &= ~PMF_ONGROUND; // onground = false; - - // set watertype/waterlevel - origin1 = s.origin; - origin1_z += s.mins_z + 1; - s.waterlevel = WATERLEVEL_NONE; - // TODO: convert -// s.watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK; -// if (s.watertype) -// { -// s.waterlevel = WATERLEVEL_WETFEET; -// origin1[2] = s.origin[2] + (s.mins[2] + s.maxs[2]) * 0.5f; -// if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) -// { -// s.waterlevel = WATERLEVEL_SWIMMING; -// origin1[2] = s.origin[2] + 22; -// if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) -// s.waterlevel = WATERLEVEL_SUBMERGED; -// } -// } -// -// // water jump prediction -// if ((s.pmove_flags & PMF_ONGROUND) || s.velocity_z <= 0 || pmove_waterjumptime <= 0) -// pmove_waterjumptime = 0; -} - -void CSQC_ClientMovement_Move(entity s) -{ - float bump; - float t; - float f; - vector neworigin; - vector currentorigin2; - vector neworigin2; - vector primalvelocity; - float old_trace1_fraction; - vector old_trace1_endpos; - vector old_trace1_plane_normal; - float old_trace2_fraction; - vector old_trace2_plane_normal; - CSQC_ClientMovement_UpdateStatus(s); - primalvelocity = s.velocity; - for (bump = 0, t = input_timelength; bump < 8 && vlen2(s.velocity) > 0; bump++) - { - neworigin = s.origin + t * s.velocity; - tracebox(s.origin, s.mins, s.maxs, neworigin, MOVE_NORMAL, s); - old_trace1_fraction = trace_fraction; - old_trace1_endpos = trace_endpos; - old_trace1_plane_normal = trace_plane_normal; - if (trace_fraction < 1 && trace_plane_normal_z == 0) - { - // may be a step or wall, try stepping up - // first move forward at a higher level - currentorigin2 = s.origin; - currentorigin2_z += getstatf(STAT_MOVEVARS_STEPHEIGHT); - neworigin2 = neworigin; - neworigin2_z = s.origin_z + getstatf(STAT_MOVEVARS_STEPHEIGHT); - tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s); - if (!trace_startsolid) - { - // then move down from there - currentorigin2 = trace_endpos; - neworigin2 = trace_endpos; - neworigin2_z = s.origin_z; - old_trace2_fraction = trace_fraction; - old_trace2_plane_normal = trace_plane_normal; - tracebox(currentorigin2, s.mins, s.maxs, neworigin2, MOVE_NORMAL, s); - //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]); - // accept the new trace if it made some progress - if (fabs(trace_endpos_x - old_trace1_endpos_x) >= 0.03125 || fabs(trace_endpos_y - old_trace1_endpos_y) >= 0.03125) - { - trace_fraction = old_trace2_fraction; - trace_endpos = trace_endpos; - trace_plane_normal = old_trace2_plane_normal; - } - else - { - trace_fraction = old_trace1_fraction; - trace_endpos = old_trace1_endpos; - trace_plane_normal = old_trace1_plane_normal; - } - } - } - - // check if it moved at all - if (trace_fraction >= 0.001) - s.origin = trace_endpos; - - // check if it moved all the way - if (trace_fraction == 1) - break; - - // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate - // I'm pretty sure I commented it out solely because it seemed redundant - // this got commented out in a change that supposedly makes the code match QW better - // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block - if (trace_plane_normal_z > 0.7) - s.pmove_flags |= PMF_ONGROUND; - - t -= t * trace_fraction; - - f = dotproduct(s.velocity, trace_plane_normal); - s.velocity -= f * trace_plane_normal; - } - if (pmove_waterjumptime > 0) - s.velocity = primalvelocity; -} - -float IsMoveInDirection(vector mv, float angle) // key mix factor -{ - if(mv_x == 0 && mv_y == 0) - return 0; // avoid division by zero - angle -= RAD2DEG * atan2(mv_y, mv_x); - angle = remainder(angle, 360) / 45; - if(angle > 1) - return 0; - if(angle < -1) - return 0; - return 1 - fabs(angle); -} - -// TODO: remove this and use above function -float CSQC_IsMoveInDirection(float forward, float side, float angle) -{ - // TODO: move to a common header - #define RAD2DEG(a) ((a) * (180.0f / M_PI)) - #define ANGLEMOD(a) ((a) - 360.0 * floor((a) / 360.0)) - if(forward == 0 && side == 0) - return 0; // avoid division by zero - angle -= RAD2DEG(atan2(side, forward)); - angle = (ANGLEMOD(angle + 180) - 180) / 45; - if(angle > 1) - return 0; - if(angle < -1) - return 0; - return 1 - fabs(angle); - #undef RAD2DEG - #undef ANGLEMOD -} - -float GeomLerp(float a, float lerp, float b) -{ - if(a == 0) - { - if(lerp < 1) - return 0; - else - return b; - } - if(b == 0) - { - if(lerp > 0) - return 0; - else - return a; - } - return a * pow(fabs(b / a), lerp); -} - -void CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(entity s, vector wishdir, float wishspeed) -{ - float zspeed, xyspeed, dot, k; - -#if 0 - // this doesn't play well with analog input - if(s.movement_x == 0 || s.movement_y != 0) - return; // can't control movement if not moving forward or backward - k = 32; -#else - k = 32 * (2 * IsMoveInDirection(input_movevalues, 0) - 1); - if(k <= 0) - return; -#endif - - k *= bound(0, wishspeed / getstatf(STAT_MOVEVARS_MAXAIRSPEED), 1); - - zspeed = s.velocity_z; - s.velocity_z = 0; - xyspeed = vlen(s.velocity); s.velocity = normalize(s.velocity); - - dot = s.velocity * wishdir; - - if(dot > 0) // we can't change direction while slowing down - { - k *= pow(dot, getstatf(STAT_MOVEVARS_AIRCONTROL_POWER))*input_timelength; - xyspeed = max(0, xyspeed - getstatf(STAT_MOVEVARS_AIRCONTROL_PENALTY) * sqrt(max(0, 1 - dot*dot)) * k/32); - k *= getstatf(STAT_MOVEVARS_AIRCONTROL); - s.velocity = normalize(s.velocity * xyspeed + wishdir * k); - } - - s.velocity = s.velocity * xyspeed; - s.velocity_z = zspeed; -} - -float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor) -{ - return copysign(bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1), accelqw); -} - -void CSQC_ClientMovement_Physics_PM_Accelerate(entity s, vector wishdir, float wishspeed, float wishspeed0, float accel, float accelqw, float stretchfactor, float sidefric, float speedlimit) -{ - float vel_straight; - float vel_z; - vector vel_perpend; - float step; - vector vel_xy; - float vel_xy_current; - float vel_xy_backward, vel_xy_forward; - float speedclamp; - - if(stretchfactor > 0) - speedclamp = stretchfactor; - else if(accelqw < 0) - speedclamp = 1; - else - speedclamp = -1; // no clamping - - if(accelqw < 0) - accelqw = -accelqw; - - if(moveflags & MOVEFLAG_Q2AIRACCELERATE) - wishspeed0 = wishspeed; // don't need to emulate this Q1 bug - - vel_straight = dotproduct(s.velocity, wishdir); - vel_z = s.velocity_z; - vel_xy = s.velocity; - vel_xy_z -= vel_z; - vel_perpend = vel_xy - vel_straight * wishdir; - - step = accel * input_timelength * wishspeed0; - - vel_xy_current = vlen(vel_xy); - if(speedlimit > 0) - accelqw = CSQC_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed)); - vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw); - vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw); - if(vel_xy_backward < 0) - vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards - - vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw); - - if(sidefric < 0 && vlen2(vel_perpend)) - // negative: only apply so much sideways friction to stay below the speed you could get by "braking" - { - float f, fmin; - f = max(0, 1 + input_timelength * wishspeed * sidefric); - fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / vlen2(vel_perpend); - // assume: fmin > 1 - // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend - // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend - // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy - // obviously, this cannot be - if(fmin <= 0) - vel_perpend *= f; - else - { - fmin = sqrt(fmin); - vel_perpend *= max(fmin, f); - } - } - else - vel_perpend *= max(0, 1 - input_timelength * wishspeed * sidefric); - - s.velocity = vel_perpend + vel_straight * wishdir; - - if(speedclamp >= 0) - { - float vel_xy_preclamp; - vel_xy_preclamp = vlen(s.velocity); - if(vel_xy_preclamp > 0) // prevent division by zero - { - vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp; - if(vel_xy_current < vel_xy_preclamp) - s.velocity *= (vel_xy_current / vel_xy_preclamp); - } - } - - s.velocity_z += vel_z; -} - -void CSQC_ClientMovement_Physics_PM_AirAccelerate(entity s, vector wishdir, float wishspeed) -{ - vector curvel, wishvel, acceldir, curdir; - float addspeed, accelspeed, curspeed, f; - float dot; - - if(wishspeed == 0) - return; - - curvel = s.velocity; - curvel_z = 0; - curspeed = vlen(curvel); - - if(wishspeed > curspeed * 1.01) - { - wishspeed = min(wishspeed, curspeed + getstatf(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL) * getstatf(STAT_MOVEVARS_MAXSPEED) * input_timelength); - } - else - { - f = max(0, (getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED) - curspeed) / (getstatf(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED) - getstatf(STAT_MOVEVARS_MAXSPEED))); - wishspeed = max(curspeed, getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL)) + getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * f * getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * input_timelength; - } - wishvel = wishdir * wishspeed; - acceldir = wishvel - curvel; - addspeed = vlen(acceldir); - acceldir = normalize(acceldir); - - accelspeed = min(addspeed, getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) * getstatf(STAT_MOVEVARS_WARSOWBUNNY_ACCEL) * input_timelength); - - if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO) < 1) - { - curdir = normalize(curvel); - dot = acceldir * curdir; - if(dot < 0) - acceldir = acceldir - (1 - getstatf(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO)) * dot * curdir; - } - - s.velocity += accelspeed * acceldir; -} - -void CSQC_ClientMovement_Physics_Walk(entity s) -{ - float friction; - float wishspeed; - float addspeed; - float accelspeed; - float f; - float gravity; - vector wishvel; - vector wishdir; - vector yawangles; - - // jump if on ground with jump button pressed but only if it has been - // released at least once since the last jump - if (input_buttons & 2) - { - if ((s.pmove_flags & PMF_ONGROUND) && ((s.pmove_flags & PMF_JUMP_HELD) == 0 || !cvar("cl_movement_track_canjump"))) - { - s.velocity_z += getstatf(STAT_MOVEVARS_JUMPVELOCITY); - s.pmove_flags &= ~PMF_ONGROUND; - s.pmove_flags |= PMF_JUMP_HELD; // canjump = false - } - } - else - s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true - - // calculate movement vector - yawangles = '0 0 0'; - yawangles_y = input_angles_y; - makevectors(yawangles); - wishvel = input_movevalues_x * v_forward + input_movevalues_y * v_right; - - // split wishvel into wishspeed and wishdir - wishspeed = vlen(wishvel); - if (wishspeed) - wishdir = wishvel / wishspeed; - else - wishdir = '0 0 0'; - // check if onground - if ((s.pmove_flags & PMF_ONGROUND)) - { - wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXSPEED)); - if (s.pmove_flags & PMF_DUCKED) - wishspeed *= 0.5; - - // apply edge friction - f = sqrt(s.velocity_x * s.velocity_x + s.velocity_y * s.velocity_y); - if (f > 0) - { - friction = getstatf(STAT_MOVEVARS_FRICTION); - if (getstatf(STAT_MOVEVARS_EDGEFRICTION) != 1) - { - vector neworigin2; - vector neworigin3; - // note: QW uses the full player box for the trace, and yet still - // uses s.origin_z + s.mins_z, which is clearly an bug, but - // this mimics it for compatibility - neworigin2 = s.origin; - neworigin2_x += s.velocity_x*(16/f); - neworigin2_y += s.velocity_y*(16/f); - neworigin2_z += s.mins_z; - neworigin3 = neworigin2; - neworigin3_z -= 34; - traceline(neworigin2, neworigin3, MOVE_NORMAL, s); - if (trace_fraction == 1 && !trace_startsolid) - friction *= getstatf(STAT_MOVEVARS_EDGEFRICTION); - } - // apply ground friction - f = 1 - input_timelength * friction * ((f < getstatf(STAT_MOVEVARS_STOPSPEED)) ? (getstatf(STAT_MOVEVARS_STOPSPEED) / f) : 1); - f = max(f, 0); - s.velocity *= f; - } - addspeed = wishspeed - dotproduct(s.velocity, wishdir); - if (addspeed > 0) - { - accelspeed = min(getstatf(STAT_MOVEVARS_ACCELERATE) * input_timelength * wishspeed, addspeed); - s.velocity += accelspeed * wishdir; - } - gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength; - if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND)) - { - if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) - s.velocity_z -= gravity * 0.5; - else - s.velocity_z -= gravity; - } - if (vlen2(s.velocity)) - CSQC_ClientMovement_Move(s); - if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND)) - { - if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) - s.velocity_z -= gravity * 0.5; - } - } - else - { - if (pmove_waterjumptime <= 0) - { - // apply air speed limit - float accel, wishspeed0, wishspeed2, accelqw, strafity; - float accelerating; - - accelqw = getstatf(STAT_MOVEVARS_AIRACCEL_QW); - wishspeed0 = wishspeed; - wishspeed = min(wishspeed, getstatf(STAT_MOVEVARS_MAXAIRSPEED)); - if (s.pmove_flags & PMF_DUCKED) - wishspeed *= 0.5; - accel = getstatf(STAT_MOVEVARS_AIRACCELERATE); - - accelerating = (dotproduct(s.velocity, wishdir) > 0); - wishspeed2 = wishspeed; - - // CPM: air control - if(getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) != 0) - { - vector curdir; - curdir_x = s.velocity_x; - curdir_y = s.velocity_y; - curdir_z = 0; - curdir = normalize(curdir); - accel = accel + (getstatf(STAT_MOVEVARS_AIRSTOPACCELERATE) - accel) * max(0, -dotproduct(curdir, wishdir)); - } - strafity = CSQC_IsMoveInDirection(input_movevalues_x, input_movevalues_y, -90) + CSQC_IsMoveInDirection(input_movevalues_x, input_movevalues_y, +90); // if one is nonzero, other is always zero - if(getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED)) - wishspeed = min(wishspeed, GeomLerp(getstatf(STAT_MOVEVARS_MAXAIRSPEED), strafity, getstatf(STAT_MOVEVARS_MAXAIRSTRAFESPEED))); - if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE)) - accel = GeomLerp(getstatf(STAT_MOVEVARS_AIRACCELERATE), strafity, getstatf(STAT_MOVEVARS_AIRSTRAFEACCELERATE)); - if(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW)) - accelqw = - (((strafity > 0.5 ? getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW) : getstatf(STAT_MOVEVARS_AIRACCEL_QW)) >= 0) ? +1 : -1) - * - (1 - GeomLerp(1 - fabs(getstatf(STAT_MOVEVARS_AIRACCEL_QW)), strafity, 1 - fabs(getstatf(STAT_MOVEVARS_AIRSTRAFEACCEL_QW)))); - // !CPM - - if(getstatf(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL) && accelerating && input_movevalues_y == 0 && input_movevalues_x != 0) - CSQC_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2); - else - CSQC_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, getstatf(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR), getstatf(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION) / getstatf(STAT_MOVEVARS_MAXAIRSPEED), getstatf(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW)); - - if(getstatf(STAT_MOVEVARS_AIRCONTROL)) - CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2); - } - gravity = getstatf(STAT_MOVEVARS_GRAVITY) * getstatf(STAT_MOVEVARS_ENTGRAVITY) * input_timelength; - if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) - s.velocity_z -= gravity * 0.5; - else - s.velocity_z -= gravity; - CSQC_ClientMovement_Move(s); - if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !(s.pmove_flags & PMF_ONGROUND)) - { - if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) - s.velocity_z -= gravity * 0.5; - } - } -} - -// TODO: merge this with main physics frame -void CSQC_ClientMovement_Physics_Swim(entity s) -{ - // swimming - self.flags &= ~FL_ONGROUND; - - makevectors(input_angles); - //wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z; - vector wishvel = v_forward * input_movevalues_x + v_right * input_movevalues_y + '0 0 1' * input_movevalues_z; - if (wishvel == '0 0 0') - wishvel = '0 0 -60'; // drift towards bottom - - vector wishdir = normalize(wishvel); - float wishspeed = vlen(wishvel); - if (wishspeed > getstatf(STAT_MOVEVARS_MAXSPEED)) - wishspeed = getstatf(STAT_MOVEVARS_MAXSPEED); - wishspeed = wishspeed * 0.7; - - // water friction - self.velocity = self.velocity * (1 - input_timelength * getstatf(STAT_MOVEVARS_FRICTION)); - - // water acceleration - CSQC_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed, getstatf(STAT_MOVEVARS_ACCELERATE), 1, 0, 0, 0); -} - -void CSQC_ClientMovement_PlayerMove(entity s) -{ - //Con_Printf(" %f", frametime); - if (!(input_buttons & 2)) // !jump - s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true - pmove_waterjumptime -= input_timelength; - CSQC_ClientMovement_UpdateStatus(s); - if (s.waterlevel >= WATERLEVEL_SWIMMING) - CSQC_ClientMovement_Physics_Swim(s); - else - CSQC_ClientMovement_Physics_Walk(s); -} - -void CSQC_ClientMovement_PlayerMove_Frame(entity s) -{ - // if a move is more than 50ms, do it as two moves (matching qwsv) - //Con_Printf("%i ", s.cmd.msec); - if(input_timelength > 0.0005) - { - if (input_timelength > 0.05) - { - input_timelength /= 2; - CSQC_ClientMovement_PlayerMove(s); - } - CSQC_ClientMovement_PlayerMove(s); - } - else - { - // we REALLY need this handling to happen, even if the move is not executed - if (!(input_buttons & 2)) // !jump - s.pmove_flags &= ~PMF_JUMP_HELD; // canjump = true - } -} +void CSQC_ClientMovement_PlayerMove_Frame(entity s); void CSQCPlayer_Physics(void) { @@ -764,7 +130,6 @@ void CSQCPlayer_Physics(void) case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break; } } -#undef vlen2 void CSQCPlayer_PredictTo(float endframe, float apply_error) { diff --git a/qcsrc/server/cl_physics.qc b/qcsrc/server/cl_physics.qc index 14747fa99..fbff30684 100644 --- a/qcsrc/server/cl_physics.qc +++ b/qcsrc/server/cl_physics.qc @@ -387,37 +387,9 @@ void RaceCarPhysics() self.angles_z = smoothangles_z; } -float IsMoveInDirection(vector mv, float angle) // key mix factor -{ - if(mv_x == 0 && mv_y == 0) - return 0; // avoid division by zero - angle -= RAD2DEG * atan2(mv_y, mv_x); - angle = remainder(angle, 360) / 45; - if(angle > 1) - return 0; - if(angle < -1) - return 0; - return 1 - fabs(angle); -} +float IsMoveInDirection(vector mv, float angle); -float GeomLerp(float a, float lerp, float b) -{ - if(a == 0) - { - if(lerp < 1) - return 0; - else - return b; - } - if(b == 0) - { - if(lerp > 0) - return 0; - else - return a; - } - return a * pow(fabs(b / a), lerp); -} +float GeomLerp(float a, float lerp, float b); void CPM_PM_Aircontrol(vector wishdir, float wishspeed) { diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 288b24775..d84b37b94 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -213,7 +213,7 @@ target_music.qc ../common/nades.qc ../common/buffs.qc - +../common/physics.qc accuracy.qc ../csqcmodellib/sv_model.qc