csqcplayer_status = CSQCPLAYERSTATUS_PREDICTED;
}
+// TODO: replace cvar("cl_movement_"...) with getstatf(STAT_MOVEVARS_...)
+// TODO: cls.protocol == PROTOCOL_QUAKEWORLD ?
+// 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)
+void AngleVectors (vector angles, vector forward, vector right, vector up)
+{
+ float angle, sr, sp, sy, cr, cp, cy;
+
+ angle = angles_y * (M_PI*2 / 360);
+ sy = sin(angle);
+ cy = cos(angle);
+ angle = angles_x * (M_PI*2 / 360);
+ sp = sin(angle);
+ cp = cos(angle);
+ if (forward)
+ {
+ forward_x = cp*cy;
+ forward_y = cp*sy;
+ forward_z = -sp;
+ }
+ if (right || up)
+ {
+ if (angles_z)
+ {
+ angle = angles_z * (M_PI*2 / 360);
+ sr = sin(angle);
+ cr = cos(angle);
+ if (right)
+ {
+ right_x = -1*(sr*sp*cy+cr*-sy);
+ right_y = -1*(sr*sp*sy+cr*cy);
+ right_z = -1*(sr*cp);
+ }
+ if (up)
+ {
+ up_x = (cr*sp*cy+-sr*-sy);
+ up_y = (cr*sp*sy+-sr*cy);
+ up_z = cr*cp;
+ }
+ }
+ else
+ {
+ if (right)
+ {
+ right_x = sy;
+ right_y = -cy;
+ right_z = 0;
+ }
+ if (up)
+ {
+ up_x = (sp*cy);
+ up_y = (sp*sy);
+ up_z = cp;
+ }
+ }
+ }
+}
+
+// TODO: move these elsewhere
+vector cl_playerstandmins = '-16 -16 -24';
+vector cl_playerstandmaxs = '16 16 45';
+vector cl_playercrouchmins = '-16 -16 -24';
+vector cl_playercrouchmaxs = '16 16 25';
+
+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, cl_playercrouchmins, cl_playercrouchmaxs, 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, cl_playerstandmins, cl_playerstandmaxs, s.origin, MOVE_NORMAL, s);
+ if (!trace_startsolid)
+ s.pmove_flags &= ~PMF_DUCKED;
+ }
+ }
+ if (s.pmove_flags & PMF_DUCKED)
+ {
+ s.mins = cl_playercrouchmins;
+ s.maxs = cl_playercrouchmaxs;
+ }
+ else
+ {
+ s.mins = cl_playerstandmins;
+ s.maxs = cl_playerstandmaxs;
+ }
+
+ // 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 += cvar("cl_movement_stepheight");
+ neworigin2 = neworigin;
+ neworigin2_z = s.origin_z + cvar("cl_movement_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
+ // <LordHavoc> 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 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 CSQC_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, speed, dot, k;
+
+ k = 32 * (2 * CSQC_IsMoveInDirection(input_movevalues_x, input_movevalues_y, 0) - 1);
+ if(k <= 0)
+ return;
+
+ k *= bound(0, wishspeed / cvar("cl_movement_maxairspeed"), 1);
+
+ zspeed = s.velocity_z;
+ s.velocity_z = 0;
+ speed = vlen(s.velocity);
+ if (speed) s.velocity /= speed;
+
+ dot = dotproduct(s.velocity, wishdir);
+
+ if(dot > 0) { // we can't change direction while slowing down
+ k *= pow(dot, cvar("cl_movement_aircontrol_power"))*input_timelength;
+ speed = max(0, speed - cvar("cl_movement_aircontrol_penalty") * sqrt(max(0, 1 - dot*dot)) * k/32);
+ k *= cvar("cl_movement_aircontrol");
+ s.velocity = speed * s.velocity + k * wishdir;
+ s.velocity = normalize(s.velocity);
+ }
+
+ s.velocity *= speed;
+ s.velocity_z = zspeed;
+}
+
+float CSQC_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
+{
+ return
+ (accelqw < 0 ? -1 : +1)
+ *
+ bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1);
+}
+
+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_Walk(entity s)
+{
+ float friction;
+ float wishspeed;
+ float addspeed;
+ float accelspeed;
+ float f;
+ float gravity;
+ vector forward = '0 0 0';
+ vector right = '0 0 0';
+ vector up = '0 0 0';
+ 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 += cvar("cl_movement_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;
+ AngleVectors(yawangles, forward, right, up);
+ wishvel = input_movevalues_x * forward + input_movevalues_y * 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, cvar("cl_movement_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 = cvar("cl_movement_friction");
+ if (cvar("cl_movement_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;
+// if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ tracebox(neworigin2, s.mins, s.maxs, neworigin3, MOVE_NORMAL, s);
+// else
+// traceline(neworigin2, neworigin3, MOVE_NORMAL, s);
+ if (trace_fraction == 1 && !trace_startsolid)
+ friction *= cvar("cl_movement_edgefriction");
+ }
+ // apply ground friction
+ f = 1 - input_timelength * friction * ((f < cvar("cl_movement_stopspeed")) ? (cvar("cl_movement_stopspeed") / f) : 1);
+ f = max(f, 0);
+ s.velocity *= f;
+ }
+ addspeed = wishspeed - dotproduct(s.velocity, wishdir);
+ if (addspeed > 0)
+ {
+ accelspeed = min(cvar("cl_movement_accelerate") * input_timelength * wishspeed, addspeed);
+ s.velocity += accelspeed * wishdir;
+ }
+ gravity = cvar("cl_movement_gravity") * cvar("cl_movement_entgravity") * input_timelength;
+ if(!(moveflags & MOVEFLAG_NOGRAVITYONGROUND))
+ {
+ if(moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+ s.velocity_z -= gravity * 0.5;
+ else
+ s.velocity_z -= gravity;
+ }
+// if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ s.velocity_z = 0;
+ 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 = cvar("cl_movement_airaccel_qw");
+ wishspeed0 = wishspeed;
+ wishspeed = min(wishspeed, cvar("cl_movement_maxairspeed"));
+ if (s.pmove_flags & PMF_DUCKED)
+ wishspeed *= 0.5;
+ accel = cvar("cl_movement_airaccelerate");
+
+ accelerating = (dotproduct(s.velocity, wishdir) > 0);
+ wishspeed2 = wishspeed;
+
+ // CPM: air control
+ if(cvar("cl_movement_airstopaccelerate") != 0)
+ {
+ vector curdir;
+ curdir_x = s.velocity_x;
+ curdir_y = s.velocity_y;
+ curdir_z = 0;
+ curdir = normalize(curdir);
+ accel = accel + (cvar("cl_movement_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(cvar("cl_movement_maxairstrafespeed"))
+ wishspeed = min(wishspeed, CSQC_GeomLerp(cvar("cl_movement_maxairspeed"), strafity, cvar("cl_movement_maxairstrafespeed")));
+ if(cvar("cl_movement_airstrafeaccelerate"))
+ accel = CSQC_GeomLerp(cvar("cl_movement_airaccelerate"), strafity, cvar("cl_movement_airstrafeaccelerate"));
+ if(cvar("cl_movement_airstrafeaccel_qw"))
+ accelqw =
+ (((strafity > 0.5 ? cvar("cl_movement_airstrafeaccel_qw") : cvar("cl_movement_airaccel_qw")) >= 0) ? +1 : -1)
+ *
+ (1 - CSQC_GeomLerp(1 - fabs(cvar("cl_movement_airaccel_qw")), strafity, 1 - fabs(cvar("cl_movement_airstrafeaccel_qw"))));
+ // !CPM
+
+// if(cvar("cl_movement_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, cvar("cl_movement_airaccel_qw_stretchfactor"), cvar("cl_movement_airaccel_sideways_friction") / cvar("cl_movement_maxairspeed"), cvar("cl_movement_airspeedlimit_nonqw"));
+
+ if(cvar("cl_movement_aircontrol"))
+ CSQC_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
+ }
+ gravity = cvar("cl_movement_gravity") * cvar("cl_movement_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;
+ }
+ }
+}
+
+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);
+ // TODO
+// if (s.waterlevel >= WATERLEVEL_SWIMMING)
+// CL_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 CSQCPlayer_Physics(void)
+{
+ switch(cvar("cl_movement")) {
+ case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break;
+ case 1: runstandardplayerphysics(self); break;
+ default: break;
+ }
+}
+#undef vlen2
+
void CSQCPlayer_PredictTo(float endframe, float apply_error)
{
CSQCPlayer_Unpredict();
{
if (!getinputstate(csqcplayer_moveframe))
break;
- runstandardplayerphysics(self);
+ CSQCPlayer_Physics();
CSQCPlayer_SetMinsMaxs();
csqcplayer_moveframe++;
}