cvar_t cl_anglespeedkey = {CVAR_SAVE, "cl_anglespeedkey","1.5","how much +speed multiplies keyboard turning speed"};
cvar_t cl_movement = {CVAR_SAVE, "cl_movement", "0", "enables clientside prediction of your player movement"};
+cvar_t cl_movement_replay = {0, "cl_movement_replay", "1", "use engine prediction"};
cvar_t cl_movement_nettimeout = {CVAR_SAVE, "cl_movement_nettimeout", "0.3", "stops predicting moves when server is lagging badly (avoids major performance problems), timeout in seconds"};
cvar_t cl_movement_minping = {CVAR_SAVE, "cl_movement_minping", "0", "whether to use prediction when ping is lower than this value in milliseconds"};
cvar_t cl_movement_track_canjump = {CVAR_SAVE, "cl_movement_track_canjump", "1", "track if the player released the jump key between two jumps to decide if he is able to jump or not; when off, this causes some \"sliding\" slightly above the floor when the jump key is held too long; if the mod allows repeated jumping by holding space all the time, this has to be set to zero too"};
cvar_t m_accelerate_maxspeed = {CVAR_SAVE, "m_accelerate_maxspeed","10000", "above this speed, full acceleration is done"};
cvar_t m_accelerate_filter = {CVAR_SAVE, "m_accelerate_filter","0.1", "mouse acceleration factor filtering"};
-cvar_t cl_netfps = {CVAR_SAVE, "cl_netfps","20", "how many input packets to send to server each second"};
+cvar_t cl_netfps = {CVAR_SAVE, "cl_netfps","72", "how many input packets to send to server each second"};
cvar_t cl_netrepeatinput = {CVAR_SAVE, "cl_netrepeatinput", "1", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server), only affects DP7 and later servers (Quake uses 0, QuakeWorld uses 2, and just for comparison Quake3 uses 1)"};
cvar_t cl_netimmediatebuttons = {CVAR_SAVE, "cl_netimmediatebuttons", "1", "sends extra packets whenever your buttons change or an impulse is used (basically: whenever you click fire or change weapon)"};
}
// if not in menu, apply mouse move to viewangles/movement
- if (!key_consoleactive && key_dest == key_game && !cl.csqc_wantsmousemove)
+ if (!key_consoleactive && key_dest == key_game && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0)
{
float modulatedsensitivity = sensitivity.value * cl.sensitivityscale;
- if (cl_prydoncursor.integer)
- {
- // mouse interacting with the scene, mostly stationary view
- V_StopPitchDrift();
- cl.cmd.cursor_screen[0] += in_mouse_x * modulatedsensitivity / vid.width;
- cl.cmd.cursor_screen[1] += in_mouse_y * modulatedsensitivity / vid.height;
- }
- else if (in_strafe.state & 1)
+ if (in_strafe.state & 1)
{
// strafing mode, all looking is movement
V_StopPitchDrift();
}
}
else // don't pitch drift when csqc is controlling the mouse
+ {
+ // mouse interacting with the scene, mostly stationary view
V_StopPitchDrift();
+ // update prydon cursor
+ cl.cmd.cursor_screen[0] = in_windowmouse_x * 2.0 / vid.width - 1.0;
+ cl.cmd.cursor_screen[1] = in_windowmouse_y * 2.0 / vid.height - 1.0;
+ }
if(v_flipped.integer)
{
{
vec3_t temp;
- if (!cl_prydoncursor.integer)
+ if (cl_prydoncursor.integer <= 0)
VectorClear(cl.cmd.cursor_screen);
/*
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);
+ if (cl_prydoncursor_notrace.integer)
+ {
+ cl.cmd.cursor_fraction = 1.0f;
+ VectorCopy(cl.cmd.cursor_end, cl.cmd.cursor_impact);
+ VectorClear(cl.cmd.cursor_normal);
+ cl.cmd.cursor_entitynumber = 0;
+ }
+ else
+ 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
s->velocity[2] = 80;
else
{
- if (gamemode == GAME_NEXUIZ)
+ if (gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
s->velocity[2] = 200;
else
s->velocity[2] = 100;
CL_ClientMovement_Move(s);
}
+static vec_t CL_IsMoveInDirection(vec_t forward, vec_t side, vec_t angle)
+{
+ 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);
+}
+
+static vec_t CL_GeomLerp(vec_t a, vec_t lerp, vec_t 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 CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed)
{
vec_t zspeed, speed, dot, k;
+#if 0
+ // this doesn't play well with analog input
if(s->cmd.forwardmove == 0 || s->cmd.sidemove != 0)
return;
-
+ k = 32;
+#else
+ k = 32 * (2 * CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, 0) - 1);
+ if(k <= 0)
+ return;
+#endif
+
+ k *= bound(0, wishspeed / cl.movevars_maxairspeed, 1);
+
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
+ k *= pow(dot, cl.movevars_aircontrol_power)*s->cmd.frametime;
+ speed = max(0, speed - cl.movevars_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32);
+ k *= cl.movevars_aircontrol;
VectorMAM(speed, s->velocity, k, wishdir, s->velocity);
VectorNormalize(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 wishspeed0, vec_t accel, vec_t accelqw, vec_t sidefric)
+float CL_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor)
+{
+ return
+ (accelqw < 0 ? -1 : +1)
+ *
+ bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1);
+}
+
+void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t wishspeed0, vec_t accel, vec_t accelqw, vec_t sidefric, vec_t speedlimit)
{
vec_t vel_straight;
vec_t vel_z;
step = accel * s->cmd.frametime * wishspeed0;
vel_xy_current = VectorLength(vel_xy);
+ if(speedlimit > 0)
+ accelqw = CL_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)
// negative: only apply so much sideways friction to stay below the speed you could get by "braking"
{
vec_t f, fmin;
- f = 1 - s->cmd.frametime * wishspeed * sidefric;
+ f = max(0, 1 + s->cmd.frametime * wishspeed * sidefric);
fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / VectorLength2(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)
VectorScale(vel_perpend, f, vel_perpend);
else
- VectorScale(vel_perpend, min(1.0f, max(fmin, f)), vel_perpend);
+ {
+ fmin = sqrt(fmin);
+ VectorScale(vel_perpend, max(fmin, f), vel_perpend);
+ }
}
else
- VectorScale(vel_perpend, 1 - s->cmd.frametime * wishspeed * sidefric, vel_perpend);
+ VectorScale(vel_perpend, max(0, 1 - s->cmd.frametime * wishspeed * sidefric), vel_perpend);
VectorMA(vel_perpend, vel_straight, wishdir, s->velocity);
vec_t addspeed;
vec_t accelspeed;
vec_t f;
+ vec_t gravity;
vec3_t forward;
vec3_t right;
vec3_t up;
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(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND)
+ gravity = 0;
+ else
+ gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+ if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+ s->velocity[2] -= gravity * 0.5f;
+ else
+ s->velocity[2] -= gravity;
if (cls.protocol == PROTOCOL_QUAKEWORLD)
s->velocity[2] = 0;
if (VectorLength2(s->velocity))
CL_ClientMovement_Move(s);
+ if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+ s->velocity[2] -= gravity * 0.5f;
}
else
{
if (s->waterjumptime <= 0)
{
// apply air speed limit
- vec_t accel, wishspeed0, wishspeed2, accelqw;
+ vec_t accel, wishspeed0, wishspeed2, accelqw, strafity;
qboolean accelerating;
accelqw = cl.movevars_airaccel_qw;
// 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)
- }
+ vec3_t curdir;
+ curdir[0] = s->velocity[0];
+ curdir[1] = s->velocity[1];
+ curdir[2] = 0;
+ VectorNormalize(curdir);
+ accel = accel + (cl.movevars_airstopaccelerate - accel) * max(0, -DotProduct(curdir, wishdir));
}
+ strafity = CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, -90) + CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, +90); // if one is nonzero, other is always zero
+ if(cl.movevars_maxairstrafespeed)
+ wishspeed = min(wishspeed, CL_GeomLerp(cl.movevars_maxairspeed, strafity, cl.movevars_maxairstrafespeed));
+ if(cl.movevars_airstrafeaccelerate)
+ accel = CL_GeomLerp(cl.movevars_airaccelerate, strafity, cl.movevars_airstrafeaccelerate);
+ if(cl.movevars_airstrafeaccel_qw)
+ accelqw =
+ (((strafity > 0.5 ? cl.movevars_airstrafeaccel_qw : cl.movevars_airaccel_qw) >= 0) ? +1 : -1)
+ *
+ (1 - CL_GeomLerp(1 - fabs(cl.movevars_airaccel_qw), strafity, 1 - fabs(cl.movevars_airstrafeaccel_qw)));
// !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, wishspeed0, accel, accelqw, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed);
+ CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed, cl.movevars_airspeedlimit_nonqw);
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;
+ gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime;
+ if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+ s->velocity[2] -= gravity * 0.5f;
+ else
+ s->velocity[2] -= gravity;
CL_ClientMovement_Move(s);
+ if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE)
+ s->velocity[2] -= gravity * 0.5f;
}
}
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_airstrafeaccel_qw = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW];
cl.movevars_aircontrol = cl.statsf[STAT_MOVEVARS_AIRCONTROL];
+ cl.movevars_aircontrol_power = cl.statsf[STAT_MOVEVARS_AIRCONTROL_POWER];
+ cl.movevars_aircontrol_penalty = cl.statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY];
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];
+ cl.movevars_airspeedlimit_nonqw = cl.statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW];
}
else
{
cl.movevars_airstopaccelerate = 0;
cl.movevars_airstrafeaccelerate = 0;
cl.movevars_maxairstrafespeed = 0;
+ cl.movevars_airstrafeaccel_qw = 0;
cl.movevars_aircontrol = 0;
+ cl.movevars_aircontrol_power = 2;
+ cl.movevars_aircontrol_penalty = 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;
+ cl.movevars_airspeedlimit_nonqw = 0;
}
if(!(cl.moveflags & MOVEFLAG_VALID))
if(gamemode == GAME_NEXUIZ)
cl.moveflags = MOVEFLAG_Q2AIRACCELERATE;
}
+
+ if(cl.movevars_aircontrol_power <= 0)
+ cl.movevars_aircontrol_power = 2; // CPMA default
}
void CL_ClientMovement_Replay(void)
if (cl.movement_predicted && !cl.movement_replay)
return;
+ if (!cl_movement_replay.integer)
+ 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);
if (in_button8.state & 3) bits |= 128;
if (in_use.state & 3) bits |= 256;
if (key_dest != key_game || key_consoleactive) bits |= 512;
- if (cl_prydoncursor.integer) bits |= 1024;
+ if (cl_prydoncursor.integer > 0) bits |= 1024;
if (in_button9.state & 3) bits |= 2048;
if (in_button10.state & 3) bits |= 4096;
if (in_button11.state & 3) bits |= 8192;
if (in_button16.state & 3) bits |= 262144;
// button bits 19-31 unused currently
// rotate/zoom view serverside if PRYDON_CLIENTCURSOR cursor is at edge of screen
- if (cl.cmd.cursor_screen[0] <= -1) bits |= 8;
- if (cl.cmd.cursor_screen[0] >= 1) bits |= 16;
- if (cl.cmd.cursor_screen[1] <= -1) bits |= 32;
- if (cl.cmd.cursor_screen[1] >= 1) bits |= 64;
+ if(cl_prydoncursor.integer > 0)
+ {
+ if (cl.cmd.cursor_screen[0] <= -1) bits |= 8;
+ if (cl.cmd.cursor_screen[0] >= 1) bits |= 16;
+ if (cl.cmd.cursor_screen[1] <= -1) bits |= 32;
+ if (cl.cmd.cursor_screen[1] >= 1) bits |= 64;
+ }
// set buttons and impulse
cl.cmd.buttons = bits;
break;
case PROTOCOL_DARKPLACES6:
case PROTOCOL_DARKPLACES7:
- // FIXME: cl.cmd.buttons & 16 is +button5, Nexuiz specific
+ // FIXME: cl.cmd.buttons & 16 is +button5, Nexuiz/Xonotic specific
cl.cmd.crouch = (cl.cmd.buttons & 16) != 0;
break;
case PROTOCOL_UNKNOWN:
// 5 bytes
MSG_WriteByte (&buf, clc_move);
MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time
- // 3 bytes
- for (i = 0;i < 3;i++)
- MSG_WriteAngle8i (&buf, cl.cmd.viewangles[i]);
+ // 3 bytes (6 bytes in proquake)
+ if (cls.proquake_servermod == 1) // MOD_PROQUAKE
+ {
+ for (i = 0;i < 3;i++)
+ MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]);
+ }
+ else
+ {
+ for (i = 0;i < 3;i++)
+ MSG_WriteAngle8i (&buf, cl.cmd.viewangles[i]);
+ }
// 6 bytes
MSG_WriteCoord16i (&buf, cl.cmd.forwardmove);
MSG_WriteCoord16i (&buf, cl.cmd.sidemove);
Cvar_RegisterVariable(&cl_movecliptokeyboard);
Cvar_RegisterVariable(&cl_movement);
+ Cvar_RegisterVariable(&cl_movement_replay);
Cvar_RegisterVariable(&cl_movement_nettimeout);
Cvar_RegisterVariable(&cl_movement_minping);
Cvar_RegisterVariable(&cl_movement_track_canjump);