--- /dev/null
+// 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
+ // <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)
+ 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
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
- // <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 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)
{
case 2: CSQC_ClientMovement_PlayerMove_Frame(self); break;
}
}
-#undef vlen2
void CSQCPlayer_PredictTo(float endframe, float apply_error)
{