+ // calculate current view matrix
+ Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, cl.cmd.cursor_start);
+ // calculate direction vector of cursor in viewspace by using frustum slopes
+ VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, (v_flipped.integer ? -1 : 1) * cl.cmd.cursor_screen[0] * -r_refdef.view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_refdef.view.frustum_y * 1000000);
+ Matrix4x4_Transform(&r_refdef.view.matrix, temp, cl.cmd.cursor_end);
+ // trace from view origin to the cursor
+ cl.cmd.cursor_fraction = CL_SelectTraceLine(cl.cmd.cursor_start, cl.cmd.cursor_end, cl.cmd.cursor_impact, cl.cmd.cursor_normal, &cl.cmd.cursor_entitynumber, (chase_active.integer || cl.intermission) ? &cl.entities[cl.playerentity].render : NULL);
+}
+
+typedef enum waterlevel_e
+{
+ WATERLEVEL_NONE,
+ WATERLEVEL_WETFEET,
+ WATERLEVEL_SWIMMING,
+ WATERLEVEL_SUBMERGED
+}
+waterlevel_t;
+
+typedef struct cl_clientmovement_state_s
+{
+ // position
+ vec3_t origin;
+ vec3_t velocity;
+ // current bounding box (different if crouched vs standing)
+ vec3_t mins;
+ vec3_t maxs;
+ // currently on the ground
+ qboolean onground;
+ // currently crouching
+ qboolean crouched;
+ // what kind of water (SUPERCONTENTS_LAVA for instance)
+ int watertype;
+ // how deep
+ waterlevel_t waterlevel;
+ // weird hacks when jumping out of water
+ // (this is in seconds and counts down to 0)
+ float waterjumptime;
+
+ // user command
+ usercmd_t cmd;
+}
+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_Move(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false).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->cmd.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_Move(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+ 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] - 1); // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :)
+ trace = CL_Move(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+ 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_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 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_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK)
+ {
+ s->waterlevel = WATERLEVEL_SWIMMING;
+ origin1[2] = s->origin[2] + 22;
+ if (CL_Move(origin1, vec3_origin, vec3_origin, origin1, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, 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;
+ double t;
+ vec_t f;
+ vec3_t neworigin;
+ vec3_t currentorigin2;
+ vec3_t neworigin2;
+ vec3_t primalvelocity;
+ trace_t trace;
+ trace_t trace2;
+ trace_t trace3;
+ CL_ClientMovement_UpdateStatus(s);
+ VectorCopy(s->velocity, primalvelocity);
+ for (bump = 0, t = s->cmd.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++)
+ {
+ VectorMA(s->origin, t, s->velocity, neworigin);
+ trace = CL_Move(s->origin, s->mins, s->maxs, neworigin, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+ if (trace.fraction < 1 && trace.plane.normal[2] == 0)
+ {
+ // may be a step or wall, try stepping up
+ // first move forward at a higher level
+ VectorSet(currentorigin2, s->origin[0], s->origin[1], s->origin[2] + cl.movevars_stepheight);
+ VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + cl.movevars_stepheight);
+ trace2 = CL_Move(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+ if (!trace2.startsolid)
+ {
+ // then move down from there
+ VectorCopy(trace2.endpos, currentorigin2);
+ VectorSet(neworigin2, trace2.endpos[0], trace2.endpos[1], s->origin[2]);
+ trace3 = CL_Move(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+ //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(trace3.endpos[0] - trace.endpos[0]) >= 0.03125 || fabs(trace3.endpos[1] - trace.endpos[1]) >= 0.03125)
+ {
+ trace = trace2;
+ VectorCopy(trace3.endpos, trace.endpos);
+ }
+ }
+ }
+
+ // check if it moved at all
+ if (trace.fraction >= 0.001)
+ VectorCopy(trace.endpos, s->origin);
+
+ // check if it moved all the way
+ if (trace.fraction == 1)
+ break;
+
+ //if (trace.plane.normal[2] > 0.7)
+ // s->onground = true;
+
+ t -= t * trace.fraction;
+
+ f = DotProduct(s->velocity, trace.plane.normal);
+ VectorMA(s->velocity, -f, trace.plane.normal, s->velocity);
+ }
+ if (s->waterjumptime > 0)
+ VectorCopy(primalvelocity, s->velocity);
+}
+
+
+void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s)
+{
+ vec_t wishspeed;
+ vec_t f;
+ vec3_t wishvel;
+ vec3_t wishdir;
+
+ // water jump only in certain situations
+ // this mimics quakeworld code
+ if (s->cmd.jump && s->waterlevel == 2 && s->velocity[2] >= -180)
+ {
+ vec3_t forward;
+ vec3_t yawangles;
+ vec3_t spot;
+ VectorSet(yawangles, 0, s->cmd.viewangles[1], 0);
+ AngleVectors(yawangles, forward, NULL, NULL);
+ VectorMA(s->origin, 24, forward, spot);
+ spot[2] += 8;
+ if (CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
+ {
+ spot[2] += 24;
+ if (!CL_Move(spot, vec3_origin, vec3_origin, spot, MOVE_NOMONSTERS, NULL, 0, true, false, NULL, false).startsolid)
+ {
+ VectorScale(forward, 50, s->velocity);
+ s->velocity[2] = 310;
+ s->waterjumptime = 2;
+ s->onground = false;
+ s->cmd.canjump = false;
+ }
+ }
+ }
+
+ if (!(s->cmd.forwardmove*s->cmd.forwardmove + s->cmd.sidemove*s->cmd.sidemove + s->cmd.upmove*s->cmd.upmove))
+ {
+ // drift towards bottom
+ VectorSet(wishvel, 0, 0, -60);
+ }
+ else
+ {
+ // swim
+ vec3_t forward;
+ vec3_t right;
+ vec3_t up;
+ // calculate movement vector
+ AngleVectors(s->cmd.viewangles, forward, right, up);
+ VectorSet(up, 0, 0, 1);
+ VectorMAMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, s->cmd.upmove, up, wishvel);
+ }
+
+ // split wishvel into wishspeed and wishdir
+ wishspeed = VectorLength(wishvel);
+ if (wishspeed)
+ VectorScale(wishvel, 1 / wishspeed, wishdir);
+ else
+ VectorSet( wishdir, 0.0, 0.0, 0.0 );
+ wishspeed = min(wishspeed, cl.movevars_maxspeed) * 0.7;
+
+ if (s->crouched)
+ wishspeed *= 0.5;
+
+ if (s->waterjumptime <= 0)
+ {
+ // water friction
+ f = 1 - s->cmd.frametime * cl.movevars_waterfriction * (cls.protocol == PROTOCOL_QUAKEWORLD ? s->waterlevel : 1);
+ f = bound(0, f, 1);
+ VectorScale(s->velocity, f, s->velocity);
+
+ // water acceleration
+ f = wishspeed - DotProduct(s->velocity, wishdir);
+ if (f > 0)
+ {
+ f = min(cl.movevars_wateraccelerate * s->cmd.frametime * wishspeed, f);
+ VectorMA(s->velocity, f, wishdir, s->velocity);
+ }
+
+ // holding jump button swims upward slowly
+ if (s->cmd.jump)
+ {
+ if (s->watertype & SUPERCONTENTS_LAVA)
+ s->velocity[2] = 50;
+ else if (s->watertype & SUPERCONTENTS_SLIME)
+ s->velocity[2] = 80;
+ else
+ {
+ if (gamemode == GAME_NEXUIZ)
+ s->velocity[2] = 200;
+ else
+ s->velocity[2] = 100;
+ }
+ }
+ }
+
+ CL_ClientMovement_Move(s);
+}
+
+void CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed)
+{
+ vec_t zspeed, speed, dot, k;
+
+ if(s->cmd.forwardmove == 0 || s->cmd.sidemove != 0)
+ return;
+
+ zspeed = s->velocity[2];
+ s->velocity[2] = 0;
+ speed = VectorNormalizeLength(s->velocity);
+
+ dot = DotProduct(s->velocity, wishdir);
+ k = 32;
+ k *= cl.movevars_aircontrol*dot*dot*s->cmd.frametime;
+
+ if(dot > 0) { // we can't change direction while slowing down
+ VectorMAM(speed, s->velocity, k, wishdir, s->velocity);
+ VectorNormalize(s->velocity);
+ }
+
+ VectorScale(s->velocity, speed, s->velocity);
+ s->velocity[2] = zspeed;
+}
+
+void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t accel, vec_t accelqw, vec_t sidefric)
+{
+ vec_t vel_straight, vel_z;
+ vec3_t vel_perpend;
+ vec_t addspeed;
+
+ vel_straight = DotProduct(s->velocity, wishdir);
+ vel_z = s->velocity[2];
+ VectorMA(s->velocity, -vel_straight, wishdir, vel_perpend); vel_perpend[2] -= vel_z;
+
+ addspeed = wishspeed - vel_straight;
+ if(addspeed > 0)
+ vel_straight = vel_straight + min(addspeed, accel * s->cmd.frametime * wishspeed) * accelqw;
+ if(wishspeed > 0)
+ vel_straight = vel_straight + min(wishspeed, accel * s->cmd.frametime * wishspeed) * (1 - accelqw);
+
+ VectorScale(vel_perpend, 1 - s->cmd.frametime * wishspeed * sidefric, vel_perpend);
+
+ VectorMA(vel_perpend, vel_straight, wishdir, s->velocity);
+ s->velocity[2] += vel_z;
+}
+
+void CL_ClientMovement_Physics_PM_AirAccelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed)
+{
+ vec3_t curvel, wishvel, acceldir, curdir;
+ float addspeed, accelspeed, curspeed;
+ float dot;
+
+ float airforwardaccel = cl.movevars_warsowbunny_airforwardaccel;
+ float bunnyaccel = cl.movevars_warsowbunny_accel;
+ float bunnytopspeed = cl.movevars_warsowbunny_topspeed;
+ float turnaccel = cl.movevars_warsowbunny_turnaccel;
+ float backtosideratio = cl.movevars_warsowbunny_backtosideratio;
+
+ if( !wishspeed )
+ return;
+
+ VectorCopy( s->velocity, curvel );
+ curvel[2] = 0;
+ curspeed = VectorLength( curvel );
+
+ if( wishspeed > curspeed * 1.01f )
+ {
+ float accelspeed = curspeed + airforwardaccel * cl.movevars_maxairspeed * s->cmd.frametime;
+ if( accelspeed < wishspeed )
+ wishspeed = accelspeed;
+ }
+ else
+ {
+ float f = ( bunnytopspeed - curspeed ) / ( bunnytopspeed - cl.movevars_maxairspeed );
+ if( f < 0 )
+ f = 0;
+ wishspeed = max( curspeed, cl.movevars_maxairspeed ) + bunnyaccel * f * cl.movevars_maxairspeed * s->cmd.frametime;
+ }
+ VectorScale( wishdir, wishspeed, wishvel );
+ VectorSubtract( wishvel, curvel, acceldir );
+ addspeed = VectorNormalizeLength( acceldir );
+
+ accelspeed = turnaccel * cl.movevars_maxairspeed /* wishspeed */ * s->cmd.frametime;
+ if( accelspeed > addspeed )
+ accelspeed = addspeed;
+
+ if( backtosideratio < 1.0f )
+ {
+ VectorNormalize2( curvel, curdir );
+ dot = DotProduct( acceldir, curdir );
+ if( dot < 0 )
+ VectorMA( acceldir, -( 1.0f - backtosideratio ) * dot, curdir, acceldir );
+ }
+
+ VectorMA( s->velocity, accelspeed, acceldir, s->velocity );
+}
+
+void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s)
+{
+ vec_t friction;
+ vec_t wishspeed;
+ vec_t addspeed;
+ vec_t accelspeed;
+ vec_t f;
+ vec3_t forward;
+ vec3_t right;
+ vec3_t up;
+ vec3_t wishvel;
+ vec3_t wishdir;
+ vec3_t yawangles;
+ trace_t trace;
+
+ // jump if on ground with jump button pressed but only if it has been
+ // released at least once since the last jump
+ if (s->cmd.jump)
+ {
+ if (s->onground && (s->cmd.canjump || !cl_movement_track_canjump.integer)) // FIXME remove this cvar again when canjump logic actually works, or maybe keep it for mods that allow "pogo-ing"
+ {
+ s->velocity[2] += cl.movevars_jumpvelocity;
+ s->onground = false;
+ s->cmd.canjump = false;
+ }
+ }
+ else
+ s->cmd.canjump = true;
+
+ // calculate movement vector
+ VectorSet(yawangles, 0, s->cmd.viewangles[1], 0);
+ AngleVectors(yawangles, forward, right, up);
+ VectorMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, wishvel);
+
+ // split wishvel into wishspeed and wishdir
+ wishspeed = VectorLength(wishvel);
+ if (wishspeed)
+ VectorScale(wishvel, 1 / wishspeed, wishdir);
+ else
+ VectorSet( wishdir, 0.0, 0.0, 0.0 );
+ // check if onground
+ if (s->onground)
+ {
+ wishspeed = min(wishspeed, cl.movevars_maxspeed);
+ if (s->crouched)
+ wishspeed *= 0.5;
+
+ // apply edge friction
+ f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]);
+ if (f > 0)
+ {
+ friction = cl.movevars_friction;
+ if (cl.movevars_edgefriction != 1)
+ {
+ vec3_t neworigin2;
+ vec3_t neworigin3;
+ // note: QW uses the full player box for the trace, and yet still
+ // uses s->origin[2] + s->mins[2], which is clearly an bug, but
+ // this mimics it for compatibility
+ VectorSet(neworigin2, s->origin[0] + s->velocity[0]*(16/f), s->origin[1] + s->velocity[1]*(16/f), s->origin[2] + s->mins[2]);
+ VectorSet(neworigin3, neworigin2[0], neworigin2[1], neworigin2[2] - 34);
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ trace = CL_Move(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+ else
+ trace = CL_Move(neworigin2, vec3_origin, vec3_origin, neworigin3, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true, true, NULL, false);
+ if (trace.fraction == 1 && !trace.startsolid)
+ friction *= cl.movevars_edgefriction;
+ }
+ // apply ground friction
+ f = 1 - s->cmd.frametime * friction * ((f < cl.movevars_stopspeed) ? (cl.movevars_stopspeed / f) : 1);
+ f = max(f, 0);
+ VectorScale(s->velocity, f, s->velocity);
+ }
+ addspeed = wishspeed - DotProduct(s->velocity, wishdir);
+ if (addspeed > 0)
+ {
+ accelspeed = min(cl.movevars_accelerate * s->cmd.frametime * wishspeed, addspeed);
+ VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
+ }
+ s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ s->velocity[2] = 0;
+ if (VectorLength2(s->velocity))
+ CL_ClientMovement_Move(s);
+ }
+ else
+ {
+ if (s->waterjumptime <= 0)
+ {
+ // apply air speed limit
+ vec_t accel, wishspeed2, accelqw;
+ qboolean accelerating;
+
+ accelqw = cl.movevars_airaccel_qw;
+ wishspeed = min(wishspeed, cl.movevars_maxairspeed);
+ if (s->crouched)
+ wishspeed *= 0.5;
+ accel = cl.movevars_airaccelerate;
+
+ accelerating = (DotProduct(s->velocity, wishdir) > 0);
+ wishspeed2 = wishspeed;
+
+ // CPM: air control
+ if(cl.movevars_airstopaccelerate != 0)
+ if(DotProduct(s->velocity, wishdir) < 0)
+ accel = cl.movevars_airstopaccelerate;
+ if(s->cmd.forwardmove == 0 && s->cmd.sidemove != 0)
+ {
+ if(cl.movevars_maxairstrafespeed)
+ {
+ if(wishspeed > cl.movevars_maxairstrafespeed)
+ wishspeed = cl.movevars_maxairstrafespeed;
+ if(cl.movevars_maxairstrafespeed < cl.movevars_maxairspeed)
+ accelqw = 1;
+ // otherwise, CPMA-style air acceleration misbehaves a lot
+ // if partially non-QW acceleration is used (as in, strafing
+ // would get faster than moving forward straight)
+ }
+ if(cl.movevars_airstrafeaccelerate)
+ {
+ accel = cl.movevars_airstrafeaccelerate;
+ if(cl.movevars_airstrafeaccelerate > cl.movevars_airaccelerate)
+ accelqw = 1;
+ // otherwise, CPMA-style air acceleration misbehaves a lot
+ // if partially non-QW acceleration is used (as in, strafing
+ // would get faster than moving forward straight)
+ }
+ }
+ // !CPM
+
+ if(cl.movevars_warsowbunny_turnaccel && accelerating && s->cmd.sidemove == 0 && s->cmd.forwardmove != 0)
+ CL_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2);
+ else
+ CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, accel, accelqw, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed);
+
+ if(cl.movevars_aircontrol)
+ CL_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2);
+ }
+ s->velocity[2] -= cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+ CL_ClientMovement_Move(s);
+ }
+}
+
+void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s)
+{
+ //Con_Printf(" %f", frametime);
+ if (!s->cmd.jump)
+ s->cmd.canjump = true;
+ s->waterjumptime -= s->cmd.frametime;
+ CL_ClientMovement_UpdateStatus(s);
+ if (s->waterlevel >= WATERLEVEL_SWIMMING)
+ CL_ClientMovement_Physics_Swim(s);
+ else
+ CL_ClientMovement_Physics_Walk(s);
+}
+
+extern cvar_t slowmo;
+void CL_UpdateMoveVars(void)
+{
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ {
+ }
+ else if (cl.stats[STAT_MOVEVARS_TICRATE])
+ {
+ cl.movevars_timescale = cl.statsf[STAT_MOVEVARS_TIMESCALE];
+ cl.movevars_gravity = cl.statsf[STAT_MOVEVARS_GRAVITY];
+ cl.movevars_stopspeed = cl.statsf[STAT_MOVEVARS_STOPSPEED] ;
+ cl.movevars_maxspeed = cl.statsf[STAT_MOVEVARS_MAXSPEED];
+ cl.movevars_spectatormaxspeed = cl.statsf[STAT_MOVEVARS_SPECTATORMAXSPEED];
+ cl.movevars_accelerate = cl.statsf[STAT_MOVEVARS_ACCELERATE];
+ cl.movevars_airaccelerate = cl.statsf[STAT_MOVEVARS_AIRACCELERATE];
+ cl.movevars_wateraccelerate = cl.statsf[STAT_MOVEVARS_WATERACCELERATE];
+ cl.movevars_entgravity = cl.statsf[STAT_MOVEVARS_ENTGRAVITY];
+ cl.movevars_jumpvelocity = cl.statsf[STAT_MOVEVARS_JUMPVELOCITY];
+ cl.movevars_edgefriction = cl.statsf[STAT_MOVEVARS_EDGEFRICTION];
+ cl.movevars_maxairspeed = cl.statsf[STAT_MOVEVARS_MAXAIRSPEED];
+ cl.movevars_stepheight = cl.statsf[STAT_MOVEVARS_STEPHEIGHT];
+ cl.movevars_airaccel_qw = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW];
+ cl.movevars_airaccel_sideways_friction = cl.statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION];
+ cl.movevars_friction = cl.statsf[STAT_MOVEVARS_FRICTION];
+ cl.movevars_wallfriction = cl.statsf[STAT_MOVEVARS_WALLFRICTION];
+ cl.movevars_waterfriction = cl.statsf[STAT_MOVEVARS_WATERFRICTION];
+ cl.movevars_airstopaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTOPACCELERATE];
+ cl.movevars_airstrafeaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE];
+ cl.movevars_maxairstrafespeed = cl.statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED];
+ cl.movevars_aircontrol = cl.statsf[STAT_MOVEVARS_AIRCONTROL];
+ cl.movevars_warsowbunny_airforwardaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL];
+ cl.movevars_warsowbunny_accel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL];
+ cl.movevars_warsowbunny_topspeed = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED];
+ cl.movevars_warsowbunny_turnaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL];
+ cl.movevars_warsowbunny_backtosideratio = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO];
+ }
+ else
+ {
+ cl.movevars_timescale = slowmo.value;
+ cl.movevars_gravity = sv_gravity.value;
+ cl.movevars_stopspeed = cl_movement_stopspeed.value;
+ cl.movevars_maxspeed = cl_movement_maxspeed.value;
+ cl.movevars_spectatormaxspeed = cl_movement_maxspeed.value;
+ cl.movevars_accelerate = cl_movement_accelerate.value;
+ cl.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value;
+ cl.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value;
+ cl.movevars_friction = cl_movement_friction.value;
+ cl.movevars_wallfriction = cl_movement_wallfriction.value;
+ cl.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value;
+ cl.movevars_entgravity = 1;
+ cl.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
+ cl.movevars_edgefriction = cl_movement_edgefriction.value;
+ cl.movevars_maxairspeed = cl_movement_maxairspeed.value;
+ cl.movevars_stepheight = cl_movement_stepheight.value;
+ cl.movevars_airaccel_qw = cl_movement_airaccel_qw.value;
+ cl.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value;
+ cl.movevars_airstopaccelerate = 0;
+ cl.movevars_airstrafeaccelerate = 0;
+ cl.movevars_maxairstrafespeed = 0;
+ cl.movevars_aircontrol = 0;
+ cl.movevars_warsowbunny_airforwardaccel = 0;
+ cl.movevars_warsowbunny_accel = 0;
+ cl.movevars_warsowbunny_topspeed = 0;
+ cl.movevars_warsowbunny_turnaccel = 0;
+ cl.movevars_warsowbunny_backtosideratio = 0;
+ }
+}
+
+void CL_ClientMovement_Replay(void)
+{
+ int i;
+ double totalmovemsec;
+ cl_clientmovement_state_t s;
+
+ if (cl.movement_predicted && !cl.movement_replay)
+ return;
+
+ // set up starting state for the series of moves
+ memset(&s, 0, sizeof(s));
+ VectorCopy(cl.entities[cl.playerentity].state_current.origin, s.origin);
+ VectorCopy(cl.mvelocity[0], s.velocity);
+ s.crouched = true; // will be updated on first move
+ //Con_Printf("movement replay starting org %f %f %f vel %f %f %f\n", s.origin[0], s.origin[1], s.origin[2], s.velocity[0], s.velocity[1], s.velocity[2]);
+
+ totalmovemsec = 0;
+ for (i = 0;i < CL_MAX_USERCMDS;i++)
+ if (cl.movecmd[i].sequence > cls.servermovesequence)
+ totalmovemsec += cl.movecmd[i].msec;
+ cl.movement_predicted = totalmovemsec >= cl_movement_minping.value && cls.servermovesequence && (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission);
+ //Con_Printf("%i = %.0f >= %.0f && %i && (%i && %i && %i == %i && %i > 0 && %i\n", cl.movement_predicted, totalmovemsec, cl_movement_minping.value, cls.servermovesequence, cl_movement.integer, !cls.demoplayback, cls.signon, SIGNONS, cl.stats[STAT_HEALTH], !cl.intermission);
+ if (cl.movement_predicted)
+ {
+ //Con_Printf("%ims\n", cl.movecmd[0].msec);
+
+ // replay the input queue to predict current location
+ // note: this relies on the fact there's always one queue item at the end
+
+ // find how many are still valid
+ for (i = 0;i < CL_MAX_USERCMDS;i++)
+ if (cl.movecmd[i].sequence <= cls.servermovesequence)
+ break;
+ // now walk them in oldest to newest order
+ for (i--;i >= 0;i--)
+ {
+ s.cmd = cl.movecmd[i];
+ if (i < CL_MAX_USERCMDS - 1)
+ s.cmd.canjump = cl.movecmd[i+1].canjump;
+ // if a move is more than 50ms, do it as two moves (matching qwsv)
+ //Con_Printf("%i ", s.cmd.msec);
+ if (s.cmd.frametime > 0.05)
+ {
+ s.cmd.frametime /= 2;
+ CL_ClientMovement_PlayerMove(&s);
+ }
+ CL_ClientMovement_PlayerMove(&s);
+ cl.movecmd[i].canjump = s.cmd.canjump;
+ }
+ //Con_Printf("\n");
+ }
+ else
+ {
+ // get the first movement queue entry to know whether to crouch and such
+ s.cmd = cl.movecmd[0];
+ }
+ CL_ClientMovement_UpdateStatus(&s);
+
+ if (cls.demoplayback) // for bob, speedometer
+ VectorCopy(cl.mvelocity[0], cl.movement_velocity);
+ else
+ {
+ cl.movement_replay = false;
+ // update the interpolation target position and velocity
+ VectorCopy(s.origin, cl.movement_origin);
+ VectorCopy(s.velocity, cl.movement_velocity);
+ }
+
+ // update the onground flag if appropriate
+ if (cl.movement_predicted)
+ {
+ // when predicted we simply set the flag according to the UpdateStatus
+ cl.onground = s.onground;
+ }
+ else
+ {
+ // when not predicted, cl.onground is cleared by cl_parse.c each time
+ // an update packet is received, but can be forced on here to hide
+ // server inconsistencies in the onground flag
+ // (which mostly occur when stepping up stairs at very high framerates
+ // where after the step up the move continues forward and not
+ // downward so the ground is not detected)
+ //
+ // such onground inconsistencies can cause jittery gun bobbing and
+ // stair smoothing, so we set onground if UpdateStatus says so
+ if (s.onground)
+ cl.onground = true;
+ }
+
+ // react to onground state changes (for gun bob)
+ if (cl.onground)
+ {
+ if (!cl.oldonground)
+ cl.hitgroundtime = cl.movecmd[0].time;
+ cl.lastongroundtime = cl.movecmd[0].time;
+ }
+ cl.oldonground = cl.onground;
+}
+
+void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to)
+{
+ int bits;
+
+ bits = 0;
+ if (to->viewangles[0] != from->viewangles[0])
+ bits |= QW_CM_ANGLE1;
+ if (to->viewangles[1] != from->viewangles[1])
+ bits |= QW_CM_ANGLE2;
+ if (to->viewangles[2] != from->viewangles[2])
+ bits |= QW_CM_ANGLE3;
+ if (to->forwardmove != from->forwardmove)
+ bits |= QW_CM_FORWARD;
+ if (to->sidemove != from->sidemove)
+ bits |= QW_CM_SIDE;
+ if (to->upmove != from->upmove)
+ bits |= QW_CM_UP;
+ if (to->buttons != from->buttons)
+ bits |= QW_CM_BUTTONS;
+ if (to->impulse != from->impulse)
+ bits |= QW_CM_IMPULSE;
+
+ MSG_WriteByte(buf, bits);
+ if (bits & QW_CM_ANGLE1)
+ MSG_WriteAngle16i(buf, to->viewangles[0]);
+ if (bits & QW_CM_ANGLE2)
+ MSG_WriteAngle16i(buf, to->viewangles[1]);
+ if (bits & QW_CM_ANGLE3)
+ MSG_WriteAngle16i(buf, to->viewangles[2]);
+ if (bits & QW_CM_FORWARD)
+ MSG_WriteShort(buf, (short) to->forwardmove);
+ if (bits & QW_CM_SIDE)
+ MSG_WriteShort(buf, (short) to->sidemove);
+ if (bits & QW_CM_UP)
+ MSG_WriteShort(buf, (short) to->upmove);
+ if (bits & QW_CM_BUTTONS)
+ MSG_WriteByte(buf, to->buttons);
+ if (bits & QW_CM_IMPULSE)
+ MSG_WriteByte(buf, to->impulse);
+ MSG_WriteByte(buf, to->msec);