+ // 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, s->movevars_maxspeed) * 0.7;
+
+ if (s->crouched)
+ wishspeed *= 0.5;
+
+ if (s->waterjumptime <= 0)
+ {
+ // water friction
+ f = 1 - s->q.frametime * s->movevars_waterfriction * s->waterlevel;
+ f = bound(0, f, 1);
+ VectorScale(s->velocity, f, s->velocity);
+
+ // water acceleration
+ f = wishspeed - DotProduct(s->velocity, wishdir);
+ if (f > 0)
+ {
+ f = min(s->movevars_wateraccelerate * s->q.frametime * wishspeed, f);
+ VectorMA(s->velocity, f, wishdir, s->velocity);
+ }
+
+ // holding jump button swims upward slowly
+ if (s->q.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_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->q.jump && s->onground)// && s->canjump) // FIXME: canjump doesn't work properly
+ {
+ s->velocity[2] += s->movevars_jumpvelocity;
+ s->onground = false;
+ s->canjump = false;
+ }
+
+ // calculate movement vector
+ VectorSet(yawangles, 0, s->q.viewangles[1], 0);
+ AngleVectors(yawangles, forward, right, up);
+ VectorMAM(s->q.move[0], forward, s->q.move[1], 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 );
+ wishspeed = min(wishspeed, s->movevars_maxspeed);
+ if (s->crouched)
+ wishspeed *= 0.5;
+
+ // check if onground
+ if (s->onground)
+ {
+ // apply edge friction
+ f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]);
+ friction = s->movevars_friction;
+ if (f > 0 && s->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_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+ else
+ trace = CL_TraceBox(neworigin2, vec3_origin, vec3_origin, neworigin3, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true);
+ if (trace.fraction == 1 && !trace.startsolid)
+ friction *= s->movevars_edgefriction;
+ }
+ // apply ground friction
+ f = 1 - s->q.frametime * friction * ((f < s->movevars_stopspeed) ? (s->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(s->movevars_accelerate * s->q.frametime * wishspeed, addspeed);
+ VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
+ }
+ s->velocity[2] -= cl_gravity.value * s->q.frametime;
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ s->velocity[2] = 0;
+ if (VectorLength2(s->velocity))
+ CL_ClientMovement_Move(s);
+ }
+ else
+ {
+ if (s->waterjumptime <= 0)
+ {
+ vec_t f;
+ vec_t vel_straight;
+ vec_t vel_z;
+ vec3_t vel_perpend;
+
+ // apply air speed limit
+ wishspeed = min(wishspeed, s->movevars_maxairspeed);
+
+ /*
+ addspeed = wishspeed - DotProduct(s->velocity, wishdir);
+ if (addspeed > 0)
+ {
+ accelspeed = min(s->movevars_accelerate * s->q.frametime * wishspeed, addspeed);
+ VectorMA(s->velocity, accelspeed, wishdir, s->velocity);
+ }
+ */
+
+ vel_straight = DotProduct(s->velocity, wishdir);
+ vel_z = s->velocity[2];
+ VectorMA(s->velocity, -vel_straight, wishdir, vel_perpend);
+ vel_perpend[2] -= vel_z;
+
+ f = wishspeed - vel_straight;
+ if(f > 0)
+ vel_straight += min(f, s->movevars_accelerate * s->q.frametime * wishspeed) * s->movevars_airaccel_qw;
+ if(wishspeed > 0)
+ vel_straight += min(wishspeed, s->movevars_accelerate * s->q.frametime * wishspeed) * (1 - s->movevars_airaccel_qw);
+
+ VectorM(1 - (s->q.frametime * (wishspeed / s->movevars_maxairspeed) * s->movevars_airaccel_sideways_friction), vel_perpend, vel_perpend);
+
+ VectorMA(vel_perpend, vel_straight, wishdir, s->velocity);
+ s->velocity[2] += vel_z;
+ }
+ s->velocity[2] -= cl_gravity.value * s->q.frametime;
+ CL_ClientMovement_Move(s);
+ }
+}
+
+void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s)
+{
+ //Con_Printf(" %f", frametime);
+ if (!s->q.jump)
+ s->canjump = true;
+ s->waterjumptime -= s->q.frametime;
+ CL_ClientMovement_UpdateStatus(s);
+ if (s->waterlevel >= WATERLEVEL_SWIMMING)
+ CL_ClientMovement_Physics_Swim(s);
+ else
+ CL_ClientMovement_Physics_Walk(s);
+}
+
+void CL_ClientMovement_Replay(void)
+{
+ int i;
+ cl_clientmovement_state_t s;
+
+ // 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
+ s.canjump = cl.movement_replay_canjump;
+ //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]);
+
+ // set up movement variables
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ {
+ s.movevars_gravity = cl.qw_movevars_gravity;
+ s.movevars_stopspeed = cl.qw_movevars_stopspeed;
+ s.movevars_maxspeed = cl.qw_movevars_maxspeed;
+ s.movevars_spectatormaxspeed = cl.qw_movevars_spectatormaxspeed;
+ s.movevars_accelerate = cl.qw_movevars_accelerate;
+ s.movevars_airaccelerate = cl.qw_movevars_airaccelerate;
+ s.movevars_wateraccelerate = cl.qw_movevars_wateraccelerate;
+ s.movevars_friction = cl.qw_movevars_friction;
+ s.movevars_waterfriction = cl.qw_movevars_waterfriction;
+ s.movevars_entgravity = cl.qw_movevars_entgravity;
+ s.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
+ s.movevars_edgefriction = cl_movement_edgefriction.value;
+ s.movevars_maxairspeed = cl_movement_maxairspeed.value;
+ s.movevars_stepheight = cl_movement_stepheight.value;
+ s.movevars_airaccel_qw = 1.0;
+ s.movevars_airaccel_sideways_friction = 0.0;
+ }
+ else
+ {
+ s.movevars_gravity = sv_gravity.value;
+ s.movevars_stopspeed = cl_movement_stopspeed.value;
+ s.movevars_maxspeed = cl_movement_maxspeed.value;
+ s.movevars_spectatormaxspeed = cl_movement_maxspeed.value;
+ s.movevars_accelerate = cl_movement_accelerate.value;
+ s.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value;
+ s.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value;
+ s.movevars_friction = cl_movement_friction.value;
+ s.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value;
+ s.movevars_entgravity = 1;
+ s.movevars_jumpvelocity = cl_movement_jumpvelocity.value;
+ s.movevars_edgefriction = cl_movement_edgefriction.value;
+ s.movevars_maxairspeed = cl_movement_maxairspeed.value;
+ s.movevars_stepheight = cl_movement_stepheight.value;
+ s.movevars_airaccel_qw = cl_movement_airaccel_qw.value;
+ s.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value;
+ }
+
+ cl.movement_predicted = (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission) && ((cls.protocol != PROTOCOL_DARKPLACES6 && cls.protocol != PROTOCOL_DARKPLACES7) || cl.servermovesequence);
+ if (cl.movement_predicted)
+ {
+ //Con_Printf("%f: ", cl.movecmd[0].time);
+
+ // replay the input queue to predict current location
+ // note: this relies on the fact there's always one queue item at the end
+
+ for (i = 0;i < cl.movement_numqueue;i++)
+ {
+ s.q = cl.movement_queue[i];
+ // if a move is more than 50ms, do it as two moves (matching qwsv)
+ if (s.q.frametime > 0.05)
+ {
+ s.q.frametime *= 0.5;
+ CL_ClientMovement_PlayerMove(&s);
+ }
+ CL_ClientMovement_PlayerMove(&s);
+ }
+ }
+ else
+ {
+ // get the first movement queue entry to know whether to crouch and such
+ s.q = cl.movement_queue[0];
+ }
+ CL_ClientMovement_UpdateStatus(&s);
+
+ // store replay location
+ if (cl.movecmd[0].time > cl.movecmd[1].time)
+ {
+ cl.movement_time[1] = cl.movecmd[1].time;
+ cl.movement_time[0] = cl.movecmd[0].time;
+ cl.movement_time[2] = cl.time;
+ VectorCopy(cl.movement_origin, cl.movement_oldorigin);
+ VectorCopy(s.origin, cl.movement_origin);
+ VectorCopy(s.velocity, cl.movement_velocity);
+ //VectorCopy(s.origin, cl.entities[cl.playerentity].state_current.origin);
+ //VectorSet(cl.entities[cl.playerentity].state_current.angles, 0, cl.viewangles[1], 0);
+
+ // update the onground flag if appropriate
+ // when not predicted, cl.onground is only cleared by cl_parse.c, but can
+ // be set forcefully here to hide server inconsistencies in the onground
+ // flag (such as when stepping up stairs, the onground flag tends to turn
+ // off briefly due to precision errors, particularly at high framerates),
+ // such inconsistencies can mess up the gun bobbing and stair smoothing,
+ // so they must be avoided.
+ if (cl.movement_predicted)
+ cl.onground = s.onground;
+ else 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, to->forwardmove);
+ if (bits & QW_CM_SIDE)
+ MSG_WriteShort(buf, to->sidemove);
+ if (bits & QW_CM_UP)
+ MSG_WriteShort(buf, 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);
+}
+
+/*
+==============
+CL_SendMove
+==============
+*/
+usercmd_t nullcmd; // for delta compression of qw moves
+void CL_SendMove(void)
+{
+ int i, j, packetloss, maxusercmds;
+ int bits;
+ sizebuf_t buf;
+ unsigned char data[1024];
+ static double lastsendtime = 0;
+ double msectime;
+ static double oldmsectime;