X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=cl_input.c;h=d9a061658a912dcaf0b75a6298391d5f7ffd9d50;hp=8bc8ef1644c6ef44336ff1eb6a6f9047de0fd8b3;hb=544356c7bb6288760533142b8ab402f4d8d966ef;hpb=dd8a6d49ec6e856d160365982a119f65acbb6913 diff --git a/cl_input.c b/cl_input.c index 8bc8ef16..d9a06165 100644 --- a/cl_input.c +++ b/cl_input.c @@ -288,6 +288,8 @@ cvar_t m_filter = {CVAR_SAVE, "m_filter","0", "smoothes mouse movement, less res cvar_t cl_netinputpacketspersecond = {CVAR_SAVE, "cl_netinputpacketspersecond","50", "how many input packets to send to server each second"}; +cvar_t cl_nodelta = {0, "cl_nodelta", "0", "disables delta compression of non-player entities in QW network protocol"}; + /* ================ @@ -302,9 +304,9 @@ void CL_AdjustAngles (void) float up, down; if (in_speed.state & 1) - speed = host_realframetime * cl_anglespeedkey.value; + speed = cl.realframetime * cl_anglespeedkey.value; else - speed = host_realframetime; + speed = cl.realframetime; if (!(in_strafe.state & 1)) { @@ -352,19 +354,18 @@ Send the intended movement message to the server */ void CL_Move (void) { - vec3_t temp; float mx, my; static float old_mouse_x = 0, old_mouse_y = 0; // clamp before the move to prevent starting with bad angles CL_AdjustAngles (); - // get basic movement from keyboard - // PRYDON_CLIENTCURSOR needs to survive basemove resets - VectorCopy (cl.cmd.cursor_screen, temp); - memset (&cl.cmd, 0, sizeof(cl.cmd)); - VectorCopy (temp, cl.cmd.cursor_screen); + // reset some of the command fields + cl.cmd.forwardmove = 0; + cl.cmd.sidemove = 0; + cl.cmd.upmove = 0; + // get basic movement from keyboard if (in_strafe.state & 1) { cl.cmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_right); @@ -494,8 +495,8 @@ void CL_UpdatePrydonCursor(void) cl.cmd.cursor_screen[1] = bound(-1, cl.cmd.cursor_screen[1], 1); cl.cmd.cursor_screen[2] = 1; - scale[0] = -r_refdef.frustum_x; - scale[1] = -r_refdef.frustum_y; + scale[0] = -r_view.frustum_x; + scale[1] = -r_view.frustum_y; scale[2] = 1; // trace distance @@ -504,19 +505,46 @@ void CL_UpdatePrydonCursor(void) // calculate current view matrix V_CalcRefdef(); VectorClear(temp); - Matrix4x4_Transform(&r_refdef.viewentitymatrix, temp, cl.cmd.cursor_start); + Matrix4x4_Transform(&r_view.matrix, temp, cl.cmd.cursor_start); VectorSet(temp, cl.cmd.cursor_screen[2] * scale[2], cl.cmd.cursor_screen[0] * scale[0], cl.cmd.cursor_screen[1] * scale[1]); - Matrix4x4_Transform(&r_refdef.viewentitymatrix, temp, cl.cmd.cursor_end); + Matrix4x4_Transform(&r_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, false); - // makes sparks where cursor is - //CL_SparkShower(cl.cmd.cursor_impact, cl.cmd.cursor_normal, 5, 0); + 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, false); +} + +void CL_ClientMovement_InputQW(qw_usercmd_t *cmd) +{ + int i; + int n; + // remove stale queue items + n = cl.movement_numqueue; + cl.movement_numqueue = 0; + for (i = 0;i < n;i++) + if (cl.movement_queue[i].sequence > cls.netcon->qw.incoming_sequence) + cl.movement_queue[cl.movement_numqueue++] = cl.movement_queue[i]; + // add to input queue if there is room + if (cl.movement_numqueue < (int)(sizeof(cl.movement_queue)/sizeof(cl.movement_queue[0]))) + { + // add to input queue + cl.movement_queue[cl.movement_numqueue].sequence = cls.netcon->qw.outgoing_sequence; + cl.movement_queue[cl.movement_numqueue].time = realtime; + cl.movement_queue[cl.movement_numqueue].frametime = cmd->msec / 1000.0; + VectorCopy(cmd->angles, cl.movement_queue[cl.movement_numqueue].viewangles); + cl.movement_queue[cl.movement_numqueue].move[0] = cmd->forwardmove; + cl.movement_queue[cl.movement_numqueue].move[1] = cmd->sidemove; + cl.movement_queue[cl.movement_numqueue].move[2] = cmd->upmove; + cl.movement_queue[cl.movement_numqueue].jump = (cmd->buttons & 2) != 0; + cl.movement_queue[cl.movement_numqueue].crouch = false; + cl.movement_numqueue++; + } + cl.movement_replay = true; } void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch) { int i; int n; + double lasttime = cl.movement_numqueue >= 0 ? cl.movement_queue[cl.movement_numqueue - 1].time : 0; // remove stale queue items n = cl.movement_numqueue; cl.movement_numqueue = 0; @@ -528,9 +556,8 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch) } else { - double simulatedtime = cl.mtime[0] + cl_movement_latency.value / 1000.0; for (i = 0;i < n;i++) - if (cl.movement_queue[i].time >= cl.mtime[0] && cl.movement_queue[i].time <= simulatedtime) + if (cl.movement_queue[i].time >= cl.mtime[0] - cl_movement_latency.value / 1000.0 && cl.movement_queue[i].time <= cl.mtime[0]) cl.movement_queue[cl.movement_numqueue++] = cl.movement_queue[i]; } // add to input queue if there is room @@ -538,8 +565,8 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch) { // add to input queue cl.movement_queue[cl.movement_numqueue].sequence = cl.movesequence; - cl.movement_queue[cl.movement_numqueue].time = cl.mtime[0] + cl_movement_latency.value / 1000.0; - cl.movement_queue[cl.movement_numqueue].frametime = cl.mtime[0] - cl.mtime[1]; + cl.movement_queue[cl.movement_numqueue].time = cl.mtime[0]; + cl.movement_queue[cl.movement_numqueue].frametime = bound(0, cl.mtime[0] - lasttime, 0.1); VectorCopy(cl.viewangles, cl.movement_queue[cl.movement_numqueue].viewangles); cl.movement_queue[cl.movement_numqueue].move[0] = cl.cmd.forwardmove; cl.movement_queue[cl.movement_numqueue].move[1] = cl.cmd.sidemove; @@ -551,245 +578,561 @@ void CL_ClientMovement_Input(qboolean buttonjump, qboolean buttoncrouch) cl.movement_replay = true; } -void CL_ClientMovement_Replay(void) +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; + // whether jump button has been released since last jump + qboolean canjump; + // 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; + + // movement parameters for physics code + float movevars_gravity; + float movevars_stopspeed; + float movevars_maxspeed; + float movevars_spectatormaxspeed; + float movevars_accelerate; + float movevars_airaccelerate; + float movevars_wateraccelerate; + float movevars_friction; + float movevars_waterfriction; + float movevars_entgravity; + float movevars_jumpvelocity; + float movevars_edgefriction; + float movevars_maxairspeed; + float movevars_stepheight; + + // 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; - int contents; - int crouch; - int onground; - double edgefriction; - double frametime; double t; - vec_t wishspeed; - vec_t addspeed; - vec_t accelspeed; vec_t f; - vec_t *playermins; - vec_t *playermaxs; - vec3_t currentorigin; - vec3_t currentvelocity; - vec3_t forward; - vec3_t right; - vec3_t up; - vec3_t wishvel; - vec3_t wishdir; vec3_t neworigin; vec3_t currentorigin2; vec3_t neworigin2; - vec3_t yawangles; + 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->q.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++) + { + VectorMA(s->origin, t, s->velocity, neworigin); + trace = CL_TraceBox(s->origin, s->mins, s->maxs, neworigin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + 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] + s->movevars_stepheight); + VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + s->movevars_stepheight); + trace2 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + 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_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, true); + //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); + } + } + } - if (!cl.movement_replay) - return; - cl.movement_replay = false; + // check if it moved at all + if (trace.fraction >= 0.001) + VectorCopy(trace.endpos, s->origin); - // fetch current starting values - VectorCopy(cl_entities[cl.playerentity].state_current.origin, currentorigin); - VectorCopy(cl.mvelocity[0], currentvelocity); - // FIXME: try minor nudges in various directions if startsolid to find a - // safe place to start the walk (due to network compression in some - // protocols this starts in solid) - //currentorigin[2] += (1.0 / 32.0); // slight nudge to get out of the floor - crouch = false; // this will be updated on first move + // check if it moved all the way + if (trace.fraction == 1) + break; - // check if onground - VectorSet(currentorigin2, currentorigin[0], currentorigin[1], currentorigin[2] + 1); - VectorSet(neworigin2, currentorigin[0], currentorigin[1], currentorigin[2] - 1); - trace = CL_TraceBox(currentorigin2, cl_playercrouchmins, cl_playercrouchmaxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true); - onground = trace.fraction < 1 && trace.plane.normal[2] > 0.7; - //Con_Printf("%f: ", cl.mtime[0]); + //if (trace.plane.normal[2] > 0.7) + // s->onground = true; + + t -= t * trace.fraction; - // replay the input queue to predict current location - // note: this relies on the fact there's always one queue item at the end + 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; - for (i = 0;cl.movement && i < cl.movement_numqueue;i++) + // water jump only in certain situations + // this mimics quakeworld code + if (s->q.jump && s->waterlevel == 2 && s->velocity[2] >= -180) { - client_movementqueue_t *q = cl.movement_queue + bound(0, i, cl.movement_numqueue - 1); - frametime = q->frametime; - //Con_Printf(" %f", frametime); - //if (frametime > 0) + vec3_t forward; + vec3_t yawangles; + vec3_t spot; + VectorSet(yawangles, 0, s->q.viewangles[1], 0); + AngleVectors(yawangles, forward, NULL, NULL); + VectorMA(s->origin, 24, forward, spot); + spot[2] += 8; + if (CL_TraceBox(spot, vec3_origin, vec3_origin, spot, true, NULL, 0, false).startsolid) { - if (q->crouch) - { - // wants to crouch, this always works... - if (!crouch) - crouch = true; - } - else - { - // wants to stand, if currently crouching we need to check for a - // low ceiling first - if (crouch) - { - trace = CL_TraceBox(currentorigin, cl_playerstandmins, cl_playerstandmaxs, currentorigin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true); - if (!trace.startsolid) - crouch = false; - } - } - if (crouch) - { - playermins = cl_playercrouchmins; - playermaxs = cl_playercrouchmaxs; - } - else - { - playermins = cl_playerstandmins; - playermaxs = cl_playerstandmaxs; - } - // change velocity according to q->viewangles and q->move - contents = CL_PointSuperContents(currentorigin); - if (contents & SUPERCONTENTS_LIQUIDSMASK) + spot[2] += 24; + if (!CL_TraceBox(spot, vec3_origin, vec3_origin, spot, true, NULL, 0, false).startsolid) { - // swim - AngleVectors(q->viewangles, forward, right, up); - VectorSet(up, 0, 0, 1); - VectorMAMAM(q->move[0], forward, q->move[1], right, q->move[2], up, wishvel); - wishspeed = VectorLength(wishvel); - if (wishspeed) - VectorScale(wishvel, 1 / wishspeed, wishdir); - else - VectorSet( wishdir, 0.0, 0.0, 0.0 ); - wishspeed = min(wishspeed, cl_movement_maxspeed.value); - if (crouch) - wishspeed *= 0.5; - wishspeed *= 0.6; - VectorScale(currentvelocity, (1 - frametime * cl_movement_friction.value), currentvelocity); - f = wishspeed - DotProduct(currentvelocity, wishdir); - if (f > 0) - { - f = min(f, cl_movement_accelerate.value * frametime * wishspeed); - VectorMA(currentvelocity, f, wishdir, currentvelocity); - } - if (q->jump) - { - if (contents & SUPERCONTENTS_LAVA) - currentvelocity[2] = 50; - else if (contents & SUPERCONTENTS_SLIME) - currentvelocity[2] = 80; - else - { - if (gamemode == GAME_NEXUIZ) - currentvelocity[2] = 200; - else - currentvelocity[2] = 100; - } - } + VectorScale(forward, 50, s->velocity); + s->velocity[2] = 310; + s->waterjumptime = 2; + s->onground = false; + s->canjump = false; } + } + } + + if (!VectorLength2(s->q.move)) + { + // drift towards bottom + VectorSet(wishvel, 0, 0, -60); + } + else + { + // swim + vec3_t forward; + vec3_t right; + vec3_t up; + // calculate movement vector + AngleVectors(s->q.viewangles, forward, right, up); + VectorSet(up, 0, 0, 1); + VectorMAMAM(s->q.move[0], forward, s->q.move[1], right, s->q.move[2], 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, 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 { - // walk - if (onground && q->jump) - { - currentvelocity[2] += cl_movement_jumpvelocity.value; - onground = false; - } - VectorSet(yawangles, 0, q->viewangles[1], 0); - AngleVectors(yawangles, forward, right, up); - VectorMAM(q->move[0], forward, q->move[1], right, wishvel); - wishspeed = VectorLength(wishvel); - if (wishspeed) - VectorScale(wishvel, 1 / wishspeed, wishdir); - else - VectorSet( wishdir, 0.0, 0.0, 0.0 ); - wishspeed = min(wishspeed, cl_movement_maxspeed.value); - if (crouch) - wishspeed *= 0.5; - // check if onground - if (onground) - { - // apply ground friction - f = sqrt(currentvelocity[0] * currentvelocity[0] + currentvelocity[1] * currentvelocity[1]); - edgefriction = 1; - if (f > 0) - { - VectorSet(currentorigin2, currentorigin[0] + currentvelocity[0]*(16/f), currentorigin[1] + currentvelocity[1]*(16/f), currentorigin[2] + playermins[2]); - VectorSet(neworigin2, currentorigin2[0], currentorigin2[1], currentorigin2[2] - 34); - trace = CL_TraceBox(currentorigin2, vec3_origin, vec3_origin, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true); - if (trace.fraction == 1) - edgefriction = cl_movement_edgefriction.value; - } - // apply friction - f = 1 - frametime * edgefriction * ((f < cl_movement_stopspeed.value) ? (cl_movement_stopspeed.value / f) : 1) * cl_movement_friction.value; - f = max(f, 0); - VectorScale(currentvelocity, f, currentvelocity); - } - else - { - // apply air speed limit - wishspeed = min(wishspeed, cl_movement_maxairspeed.value); - } if (gamemode == GAME_NEXUIZ) - addspeed = wishspeed; + s->velocity[2] = 200; else - addspeed = wishspeed - DotProduct(currentvelocity, wishdir); - if (addspeed > 0) - { - accelspeed = min(cl_movement_accelerate.value * frametime * wishspeed, addspeed); - VectorMA(currentvelocity, accelspeed, wishdir, currentvelocity); - } - currentvelocity[2] -= cl_gravity.value * frametime; + s->velocity[2] = 100; } } - //if (i < cl.movement_numqueue - 1 || (cl_movement.integer & 4)) + } + + 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->canjump && s->onground) + { + 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) { - if (crouch) - { - playermins = cl_playercrouchmins; - playermaxs = cl_playercrouchmaxs; - } + 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) + { + // apply air speed limit + wishspeed = min(wishspeed, s->movevars_maxairspeed); + // Nexuiz has no upper bound on air acceleration, but little control + if (gamemode == GAME_NEXUIZ) + addspeed = wishspeed; + else + addspeed = wishspeed - DotProduct(s->velocity, wishdir); + if (addspeed > 0) { - playermins = cl_playerstandmins; - playermaxs = cl_playerstandmaxs; + accelspeed = min(s->movevars_accelerate * s->q.frametime * wishspeed, addspeed); + VectorMA(s->velocity, accelspeed, wishdir, s->velocity); } - onground = false; - for (bump = 0, t = frametime;bump < 8 && VectorLength2(currentvelocity) > 0;bump++) + } + 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; + + if (!cl.movement_replay) + return; + cl.movement_replay = false; + + // 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 = true; + + // 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; + } + 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_accelerate.value; + s.movevars_wateraccelerate = cl_movement_accelerate.value; + s.movevars_friction = cl_movement_friction.value; + s.movevars_waterfriction = cl_movement_friction.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; + } + + if (cl.movement) + { + //Con_Printf("%f: ", cl.mtime[0]); + + // 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) { - VectorMA(currentorigin, t, currentvelocity, neworigin); - trace = CL_TraceBox(currentorigin, playermins, playermaxs, neworigin, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true); - 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, currentorigin[0], currentorigin[1], currentorigin[2] + cl_movement_stepheight.value); - VectorSet(neworigin2, neworigin[0], neworigin[1], currentorigin[2] + cl_movement_stepheight.value); - trace2 = CL_TraceBox(currentorigin2, playermins, playermaxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true); - // then move down from there - VectorCopy(trace2.endpos, currentorigin2); - VectorSet(neworigin2, trace2.endpos[0], trace2.endpos[1], currentorigin[2]); - trace3 = CL_TraceBox(currentorigin2, playermins, playermaxs, neworigin2, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, true); - //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); - } - } - if (trace.fraction == 1) - { - VectorCopy(trace.endpos, currentorigin); - break; - } - if (trace.plane.normal[2] > 0.7) - onground = true; - t *= 1 - trace.fraction; - if (trace.fraction >= 0.001) - VectorCopy(trace.endpos, currentorigin); - f = DotProduct(currentvelocity, trace.plane.normal); - VectorMA(currentvelocity, -f, trace.plane.normal, currentvelocity); + 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]; + } // store replay location + CL_ClientMovement_UpdateStatus(&s); + cl.onground = s.onground; + cl.movement_time[1] = cl.movement_time[0]; + cl.movement_time[0] = cl.movement_queue[cl.movement_numqueue-1].time; VectorCopy(cl.movement_origin, cl.movement_oldorigin); - VectorCopy(currentorigin, cl.movement_origin); - VectorCopy(currentvelocity, cl.movement_velocity); - //VectorCopy(currentorigin, cl_entities[cl.playerentity].state_current.origin); - //VectorSet(cl_entities[cl.playerentity].state_current.angles, 0, cl.viewangles[1], 0); + 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); +} + +void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, qw_usercmd_t *from, qw_usercmd_t *to) +{ + int bits; + + bits = 0; + if (to->angles[0] != from->angles[0]) + bits |= QW_CM_ANGLE1; + if (to->angles[1] != from->angles[1]) + bits |= QW_CM_ANGLE2; + if (to->angles[2] != from->angles[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->angles[0]); + if (bits & QW_CM_ANGLE2) + MSG_WriteAngle16i(buf, to->angles[1]); + if (bits & QW_CM_ANGLE3) + MSG_WriteAngle16i(buf, to->angles[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); } /* @@ -824,16 +1167,15 @@ void CL_SendMove(void) accumtotal++; #endif - if (cl_movement.integer && cls.signon == SIGNONS) + cl.movement = cl_movement.integer && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission; + if (cl_movement.integer && cls.signon == SIGNONS && cls.protocol != PROTOCOL_QUAKEWORLD) { if (!cl.movement_needupdate) return; cl.movement_needupdate = false; - cl.movement = cl.stats[STAT_HEALTH] > 0 && !cl.intermission; } else { - cl.movement = false; if (realtime < lastsendtime + 1.0 / bound(10, cl_netinputpacketspersecond.value, 100)) return; // don't let it fall behind if CL_SendMove hasn't been called recently @@ -914,7 +1256,71 @@ void CL_SendMove(void) // PROTOCOL_DARKPLACES5 clc_move = 19 bytes total // PROTOCOL_DARKPLACES6 clc_move = 52 bytes total // PROTOCOL_DARKPLACES7 clc_move = 56 bytes total - if (cl.protocol == PROTOCOL_QUAKE || cl.protocol == PROTOCOL_QUAKEDP || cl.protocol == PROTOCOL_NEHAHRAMOVIE) + // PROTOCOL_QUAKEWORLD clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll) + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + int checksumindex; + double msectime; + static double oldmsectime; + qw_usercmd_t *cmd, *oldcmd; + qw_usercmd_t nullcmd; + + //Con_Printf("code qw_clc_move\n"); + + i = cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK; + cmd = &cl.qw_moves[i]; + memset(&nullcmd, 0, sizeof(nullcmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->buttons = bits; + cmd->impulse = impulse; + cmd->forwardmove = (short)bound(-32768, forwardmove, 32767); + cmd->sidemove = (short)bound(-32768, sidemove, 32767); + cmd->upmove = (short)bound(-32768, upmove, 32767); + VectorCopy(cl.viewangles, cmd->angles); + msectime = realtime * 1000; + cmd->msec = (unsigned char)bound(0, msectime - oldmsectime, 255); + // ridiculous value rejection (matches qw) + if (cmd->msec > 250) + cmd->msec = 100; + oldmsectime = msectime; + + CL_ClientMovement_InputQW(cmd); + + MSG_WriteByte(&buf, qw_clc_move); + // save the position for a checksum byte + checksumindex = buf.cursize; + MSG_WriteByte(&buf, 0); + // packet loss percentage + // FIXME: netgraph stuff + MSG_WriteByte(&buf, 0); + // write most recent 3 moves + i = (cls.netcon->qw.outgoing_sequence-2) & QW_UPDATE_MASK; + cmd = &cl.qw_moves[i]; + QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, cmd); + oldcmd = cmd; + i = (cls.netcon->qw.outgoing_sequence-1) & QW_UPDATE_MASK; + cmd = &cl.qw_moves[i]; + QW_MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd); + oldcmd = cmd; + i = cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK; + cmd = &cl.qw_moves[i]; + QW_MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd); + // calculate the checksum + buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->qw.outgoing_sequence); + // if delta compression history overflows, request no delta + if (cls.netcon->qw.outgoing_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1) + cl.qw_validsequence = 0; + // request delta compression if appropriate + if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording) + { + cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = cl.qw_validsequence; + MSG_WriteByte(&buf, qw_clc_delta); + MSG_WriteByte(&buf, cl.qw_validsequence & 255); + } + else + cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = -1; + } + else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE) { // 5 bytes MSG_WriteByte (&buf, clc_move); @@ -929,8 +1335,10 @@ void CL_SendMove(void) // 2 bytes MSG_WriteByte (&buf, bits); MSG_WriteByte (&buf, impulse); + + CL_ClientMovement_Input((bits & 2) != 0, false); } - else if (cl.protocol == PROTOCOL_DARKPLACES2 || cl.protocol == PROTOCOL_DARKPLACES3) + else if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) { // 5 bytes MSG_WriteByte (&buf, clc_move); @@ -945,8 +1353,10 @@ void CL_SendMove(void) // 2 bytes MSG_WriteByte (&buf, bits); MSG_WriteByte (&buf, impulse); + + CL_ClientMovement_Input((bits & 2) != 0, false); } - else if (cl.protocol == PROTOCOL_DARKPLACES1 || cl.protocol == PROTOCOL_DARKPLACES4 || cl.protocol == PROTOCOL_DARKPLACES5) + else if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5) { // 5 bytes MSG_WriteByte (&buf, clc_move); @@ -961,12 +1371,14 @@ void CL_SendMove(void) // 2 bytes MSG_WriteByte (&buf, bits); MSG_WriteByte (&buf, impulse); + + CL_ClientMovement_Input((bits & 2) != 0, false); } else { // 5 bytes MSG_WriteByte (&buf, clc_move); - if (cl.protocol != PROTOCOL_DARKPLACES6) + if (cls.protocol != PROTOCOL_DARKPLACES6) { if (cl_movement.integer) { @@ -989,8 +1401,8 @@ void CL_SendMove(void) MSG_WriteByte (&buf, impulse); // PRYDON_CLIENTCURSOR // 30 bytes - MSG_WriteShort (&buf, cl.cmd.cursor_screen[0] * 32767.0f); - MSG_WriteShort (&buf, cl.cmd.cursor_screen[1] * 32767.0f); + MSG_WriteShort (&buf, (short)(cl.cmd.cursor_screen[0] * 32767.0f)); + MSG_WriteShort (&buf, (short)(cl.cmd.cursor_screen[1] * 32767.0f)); MSG_WriteFloat (&buf, cl.cmd.cursor_start[0]); MSG_WriteFloat (&buf, cl.cmd.cursor_start[1]); MSG_WriteFloat (&buf, cl.cmd.cursor_start[2]); @@ -998,23 +1410,26 @@ void CL_SendMove(void) MSG_WriteFloat (&buf, cl.cmd.cursor_impact[1]); MSG_WriteFloat (&buf, cl.cmd.cursor_impact[2]); MSG_WriteShort (&buf, cl.cmd.cursor_entitynumber); - } - // FIXME: bits & 16 is +button5, Nexuiz specific - CL_ClientMovement_Input((bits & 2) != 0, (bits & 16) != 0); + // FIXME: bits & 16 is +button5, Nexuiz specific + CL_ClientMovement_Input((bits & 2) != 0, (bits & 16) != 0); + } } - // ack the last few frame numbers - // (redundent to improve handling of client->server packet loss) - // for LATESTFRAMENUMS == 3 case this is 15 bytes - for (i = 0;i < LATESTFRAMENUMS;i++) + if (cls.protocol != PROTOCOL_QUAKEWORLD) { - if (cl.latestframenums[i] > 0) + // ack the last few frame numbers + // (redundent to improve handling of client->server packet loss) + // for LATESTFRAMENUMS == 3 case this is 15 bytes + for (i = 0;i < LATESTFRAMENUMS;i++) { - if (developer_networkentities.integer >= 1) - Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]); - MSG_WriteByte(&buf, clc_ackframe); - MSG_WriteLong(&buf, cl.latestframenums[i]); + if (cl.latestframenums[i] > 0) + { + if (developer_networkentities.integer >= 1) + Con_Printf("send clc_ackframe %i\n", cl.latestframenums[i]); + MSG_WriteByte(&buf, clc_ackframe); + MSG_WriteLong(&buf, cl.latestframenums[i]); + } } } @@ -1023,7 +1438,7 @@ void CL_SendMove(void) } // send the reliable message (forwarded commands) if there is one - NetConn_SendUnreliableMessage(cls.netcon, &buf); + NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol); if (cls.netcon->message.overflowed) { @@ -1126,5 +1541,7 @@ void CL_InitInput (void) Cvar_RegisterVariable(&m_filter); Cvar_RegisterVariable(&cl_netinputpacketspersecond); + + Cvar_RegisterVariable(&cl_nodelta); }