VectorMA( s->velocity, accelspeed, acceldir, s->velocity );
}
+static void CL_ClientMovement_Physics_CheckJump(cl_clientmovement_state_t *s)
+{
+ // 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))
+ {
+ s->velocity[2] += cl.movevars_jumpvelocity;
+ s->onground = false;
+ s->cmd.canjump = false;
+ }
+ }
+ else
+ s->cmd.canjump = true;
+}
+
static 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;
+ vec_t speed;
vec_t gravity;
vec3_t forward;
vec3_t right;
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))
- {
- s->velocity[2] += cl.movevars_jumpvelocity;
- s->onground = false;
- s->cmd.canjump = false;
- }
- }
- else
- s->cmd.canjump = true;
+ CL_ClientMovement_Physics_CheckJump(s);
// calculate movement vector
VectorSet(yawangles, 0, s->cmd.viewangles[1], 0);
wishspeed *= 0.5;
// apply edge friction
- f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]);
- if (f > 0)
+ speed = VectorLength2(s->velocity);
+ if (speed > 0)
{
friction = cl.movevars_friction;
if (cl.movevars_edgefriction != 1)
// 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(neworigin2, s->origin[0] + s->velocity[0]*(16/speed), s->origin[1] + s->velocity[1]*(16/speed), s->origin[2] + s->mins[2]);
VectorSet(neworigin3, neworigin2[0], neworigin2[1], neworigin2[2] - 34);
if (cls.protocol == PROTOCOL_QUAKEWORLD)
trace = CL_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, 0, 0, collision_extendmovelength.value, true, true, NULL, true);
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);
+ speed = 1 - s->cmd.frametime * friction * ((speed < cl.movevars_stopspeed) ? (cl.movevars_stopspeed / speed) : 1);
+ speed = max(speed, 0);
+ VectorScale(s->velocity, speed, s->velocity);
}
addspeed = wishspeed - DotProduct(s->velocity, wishdir);
if (addspeed > 0)
else
{
cl.moveflags = 0;
- cl.movevars_ticrate = (cls.demoplayback ? 1.0f : host_timescale.value) / bound(1.0f, cl_netfps.value, 1000.0f);
+ cl.movevars_ticrate = (cls.demoplayback ? 1.0f : host_timescale.value) / bound(10.0f, cl_netfps.value, 1000.0f);
cl.movevars_timescale = (cls.demoplayback ? 1.0f : host_timescale.value);
cl.movevars_gravity = sv_gravity.value;
cl.movevars_stopspeed = cl_movement_stopspeed.value;
usercmd_t *cmd;
sizebuf_t buf;
unsigned char data[1024];
- double packettime;
- int msecdelta;
+ float packettime;
qbool quemove;
qbool important;
// set viewangles
VectorCopy(cl.viewangles, cl.cmd.viewangles);
- msecdelta = (int)(floor(cl.cmd.time * 1000) - floor(cl.movecmd[1].time * 1000));
- cl.cmd.msec = (unsigned char)bound(0, msecdelta, 255);
+ // bones_was_here: previously cl.cmd.frametime was floored to nearest millisec
+ // this meant the smoothest async movement required integer millisec
+ // client and server frame times (eg 125fps)
+ cl.cmd.frametime = bound(0.0, cl.cmd.time - cl.movecmd[1].time, 0.255);
// ridiculous value rejection (matches qw)
- if (cl.cmd.msec > 250)
- cl.cmd.msec = 100;
- cl.cmd.frametime = cl.cmd.msec * (1.0 / 1000.0);
+ if (cl.cmd.frametime > 0.25)
+ cl.cmd.frametime = 0.1;
+ cl.cmd.msec = (unsigned char)floor(cl.cmd.frametime * 1000);
switch(cls.protocol)
{
cl.movecmd[0] = cl.cmd;
// don't predict more than 200fps
- if (host.realtime >= cl.lastpackettime + 0.005)
+ if (cl.timesincepacket >= 0.005)
cl.movement_replay = true; // redo the prediction
// now decide whether to actually send this move
// (otherwise it is only for prediction)
+ // do not send 0ms packets because they mess up physics
+ if(cl.cmd.msec == 0 && cl.time > cl.oldtime && (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS))
+ return;
+
// don't send too often or else network connections can get clogged by a
// high renderer framerate
- packettime = 1.0 / bound(1, cl_netfps.value, 1000);
+ packettime = 1.0f / bound(10.0f, cl_netfps.value, 1000.0f);
if (cl.movevars_timescale && cl.movevars_ticrate)
{
+ // try to ensure at least 1 packet per server frame
+ // and apply soft limit of 2, hard limit < 4 (packettime reduced further below)
float maxtic = cl.movevars_ticrate / cl.movevars_timescale;
- packettime = min(packettime, maxtic);
+ packettime = bound(maxtic * 0.5f, packettime, maxtic);
}
+ // bones_was_here: reduce packettime to (largest multiple of realframetime) <= packettime
+ // prevents packet rates lower than cl_netfps or server frame rate
+ // eg: cl_netfps 60 and cl_maxfps 250 would otherwise send only 50 netfps
+ // with this line that config sends 62.5 netfps
+ // (this causes it to emit packets at a steady beat)
+ packettime = floor(packettime / (float)cl.realframetime) * (float)cl.realframetime;
- // do not send 0ms packets because they mess up physics
- if(cl.cmd.msec == 0 && cl.time > cl.oldtime && (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS))
- return;
// always send if buttons changed or an impulse is pending
// even if it violates the rate limit!
important = (cl.cmd.impulse || (cl_netimmediatebuttons.integer && cl.cmd.buttons != cl.movecmd[1].buttons));
- // don't send too often (cl_netfps)
- if (!important && host.realtime < cl.lastpackettime + packettime)
+
+ // don't send too often (cl_netfps), allowing a small margin for float error
+ // bones_was_here: accumulate realframetime to prevent low packet rates
+ // previously with cl_maxfps == cl_netfps it did not send every frame as
+ // host.realtime - cl.lastpackettime was often well below (or above) packettime
+ if (!important && cl.timesincepacket < packettime * 0.99999f)
+ {
+ cl.timesincepacket += cl.realframetime;
return;
+ }
+
// don't choke the connection with packets (obey rate limit)
// it is important that this check be last, because it adds a new
// frame to the shownetgraph output and any cancelation after this
// we also still send if it is important
if (!NetConn_CanSend(cls.netcon) && !important)
return;
- // try to round off the lastpackettime to a multiple of the packet interval
- // (this causes it to emit packets at a steady beat)
- if (packettime > 0)
- cl.lastpackettime = floor(host.realtime / packettime) * packettime;
- else
- cl.lastpackettime = host.realtime;
+
+ // reset the packet timing accumulator
+ cl.timesincepacket = cl.realframetime;
buf.maxsize = sizeof(data);
buf.cursize = 0;
{
Con_Print("CL_SendMove: lost server connection\n");
CL_Disconnect();
- SV_LockThreadMutex();
- SV_Shutdown();
- SV_UnlockThreadMutex();
}
}