+ float movevars_airaccel_qw;
+ float movevars_airaccel_sideways_friction;
+
+ // user command
+ client_movementqueue_t q;
+}
+cl_clientmovement_state_t;
+
+#define NUMOFFSETS 27
+static vec3_t offsets[NUMOFFSETS] =
+{
+// 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},
+};
+
+qboolean CL_ClientMovement_Unstick(cl_clientmovement_state_t *s)
+{
+ int i;
+ vec3_t neworigin;
+ for (i = 0;i < NUMOFFSETS;i++)
+ {
+ VectorAdd(offsets[i], s->origin, neworigin);
+ if (!CL_TraceBox(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true).startsolid)
+ {
+ VectorCopy(neworigin, s->origin);
+ return true;
+ }
+ }
+ // if all offsets failed, give up
+ return false;
+}
+
+void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s)
+{
+ vec3_t origin1, origin2;
+ trace_t trace;
+
+ // make sure player is not stuck
+ CL_ClientMovement_Unstick(s);
+
+ // set crouched
+ if (s->q.crouch)
+ {
+ // wants to crouch, this always works..
+ if (!s->crouched)
+ s->crouched = true;
+ }
+ else
+ {
+ // wants to stand, if currently crouching we need to check for a
+ // low ceiling first
+ if (s->crouched)
+ {
+ trace = CL_TraceBox(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+ if (!trace.startsolid)
+ s->crouched = false;
+ }
+ }
+ if (s->crouched)
+ {
+ VectorCopy(cl.playercrouchmins, s->mins);
+ VectorCopy(cl.playercrouchmaxs, s->maxs);
+ }
+ else
+ {
+ VectorCopy(cl.playerstandmins, s->mins);
+ VectorCopy(cl.playerstandmaxs, s->maxs);
+ }
+
+ // set onground
+ VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + 1);
+ VectorSet(origin2, s->origin[0], s->origin[1], s->origin[2] - 2);
+ trace = CL_TraceBox(origin1, s->mins, s->maxs, origin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+ s->onground = trace.fraction < 1 && trace.plane.normal[2] > 0.7;
+
+ // set watertype/waterlevel
+ VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + s->mins[2] + 1);
+ s->waterlevel = WATERLEVEL_NONE;
+ s->watertype = CL_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, 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_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+ {
+ s->waterlevel = WATERLEVEL_SWIMMING;
+ origin1[2] = s->origin[2] + 22;
+ if (CL_TraceBox(origin1, vec3_origin, vec3_origin, origin1, true, NULL, 0, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+ s->waterlevel = WATERLEVEL_SUBMERGED;
+ }
+ }
+
+ // water jump prediction
+ if (s->onground || s->velocity[2] <= 0 || s->waterjumptime <= 0)
+ s->waterjumptime = 0;
+}
+
+void CL_ClientMovement_Move(cl_clientmovement_state_t *s)
+{
+ int bump;