diff --git a/engine/client/cl_cam.c b/engine/client/cl_cam.c index 3d729a587..22cd3f054 100644 --- a/engine/client/cl_cam.c +++ b/engine/client/cl_cam.c @@ -315,6 +315,7 @@ static qboolean InitFlyby(playerview_t *pv, vec3_t selforigin, vec3_t playerorig } pv->cam_locked = true; + pv->viewentity = pv->cam_spec_track+1; VectorCopy(vec, pv->cam_desired_position); return true; } @@ -783,6 +784,7 @@ void Cam_TrackPlayer(int seat, char *cmdname, char *plrarg) pv->cam_auto = CAM_TRACK; Cam_Lock(pv, slot); pv->cam_locked = true; + pv->viewentity = slot+1; } void Cam_Track_f(void) diff --git a/engine/client/cl_ents.c b/engine/client/cl_ents.c index 48b0a9624..213a3197d 100644 --- a/engine/client/cl_ents.c +++ b/engine/client/cl_ents.c @@ -59,7 +59,7 @@ static struct predicted_player static void CL_LerpNetFrameState(int fsanim, framestate_t *fs, lerpents_t *le); qboolean CL_PredictPlayer(lerpents_t *le, entity_state_t *state, int sequence); void CL_PlayerFrameUpdated(player_state_t *plstate, entity_state_t *state, int sequence); -void CL_AckedInputFrame(int seq, qboolean worldstateokay); +void CL_AckedInputFrame(int inseq, int outseq, qboolean worldstateokay); extern int cl_playerindex, cl_h_playerindex, cl_rocketindex, cl_grenadeindex, cl_gib1index, cl_gib2index, cl_gib3index; @@ -334,7 +334,7 @@ void CLQW_ParseDelta (entity_state_t *from, entity_state_t *to, int bits, qboole // bitcounts[i]++; #ifdef PROTOCOLEXTENSIONS - if (bits & U_EVENMORE && (cls.fteprotocolextensions & (PEXT_SCALE|PEXT_TRANS|PEXT_FATNESS|PEXT_HEXEN2|PEXT_COLOURMOD|PEXT_DPFLAGS|PEXT_MODELDBL|PEXT_ENTITYDBL|PEXT_ENTITYDBL2))) + if ((bits & U_EVENMORE) && (cls.fteprotocolextensions & (PEXT_SCALE|PEXT_TRANS|PEXT_FATNESS|PEXT_HEXEN2|PEXT_COLOURMOD|PEXT_DPFLAGS|PEXT_MODELDBL|PEXT_ENTITYDBL|PEXT_ENTITYDBL2))) morebits = MSG_ReadByte (); if (morebits & U_YETMORE) morebits |= MSG_ReadByte()<<8; @@ -708,6 +708,9 @@ void CLFTE_ParseBaseline(entity_state_t *es, qboolean numberisimportant) CLFTE_ReadDelta(entnum, es, &nullentitystate, &nullentitystate); } + +void CL_PredictEntityMovement(entity_state_t *estate, float age); + /* Note: strictly speaking, you don't need multiple frames, just two and flip between them. FTE retains the full 64 frames because its interpolation will go multiple packets back in time to cover packet loss. @@ -738,9 +741,7 @@ void CLFTE_ParseEntities(void) else if (cls.protocol == CP_NETQUAKE) { int i; - if (cls.demoplayback) - cls.netchan.incoming_unreliable++; //demo playback has no sequence info... - cls.netchan.incoming_sequence = cls.netchan.incoming_unreliable; + cls.netchan.incoming_sequence++; cl.last_servermessage = realtime; if (cls.fteprotocolextensions2 & PEXT2_PREDINFO) inputframe = MSG_ReadLong(); @@ -754,8 +755,6 @@ void CLFTE_ParseEntities(void) else cl.ackframes[cl.numackframes++] = cls.netchan.incoming_sequence; - cl.inframes[cls.netchan.incoming_sequence&UPDATE_MASK].receivedtime = realtime; - { extern vec3_t demoangles; int fr = cls.netchan.incoming_sequence&UPDATE_MASK; @@ -778,6 +777,8 @@ void CLFTE_ParseEntities(void) newp = &cl.inframes[newpacket].packet_entities; oldp = &cl.inframes[oldpacket].packet_entities; cl.inframes[newpacket].invalid = true; + cl.inframes[newpacket].receivedtime = realtime; + cl.inframes[newpacket].frameid = cls.netchan.incoming_sequence; if (!cl.validsequence || cls.netchan.incoming_sequence-cl.validsequence >= UPDATE_BACKUP-1 || oldp == newp) @@ -881,18 +882,12 @@ void CLFTE_ParseEntities(void) } } - for (oldindex = 0; oldindex < newp->num_entities; oldindex++) + if (cl.do_lerp_players) { - e = newp->entities + oldindex; - if (e->number > cl.allocated_client_slots) - break; - - /*update the prediction info if needed*/ -// if (e->u.q1.pmovetype) + //predict in-place based upon calculated latencies and stuff, stuff can then be interpolated properly + for (oldindex = 0; oldindex < newp->num_entities; oldindex++) { - inframe_t *fram; - fram = &cl.inframes[cls.netchan.incoming_sequence & UPDATE_MASK]; - CL_PlayerFrameUpdated(&fram->playerstate[e->number-1], e, cls.netchan.incoming_sequence); + CL_PredictEntityMovement(newp->entities + oldindex, (cl.inframes[cl.parsecount&UPDATE_MASK].packet_entities.servertime - cl.currentpacktime) + (realtime - cl.gametimemark)); } } @@ -900,7 +895,7 @@ void CLFTE_ParseEntities(void) { cl.oldvalidsequence = cl.validsequence; cl.validsequence = cls.netchan.incoming_sequence; - CL_AckedInputFrame(inputframe, true); + CL_AckedInputFrame(cls.netchan.incoming_sequence, inputframe, true); cl.inframes[newpacket].invalid = false; } else @@ -908,7 +903,7 @@ void CLFTE_ParseEntities(void) newp->num_entities = 0; cl.validsequence = 0; - CL_AckedInputFrame(inputframe, false); + CL_AckedInputFrame(cls.netchan.incoming_sequence, inputframe, false); } } @@ -932,6 +927,8 @@ void CLQW_ParsePacketEntities (qboolean delta) newpacket = cls.netchan.incoming_sequence&UPDATE_MASK; newp = &cl.inframes[newpacket].packet_entities; cl.inframes[newpacket].invalid = false; + cl.inframes[newpacket].frameid = cls.netchan.incoming_sequence; + cl.inframes[newpacket].receivedtime = realtime; if (cls.protocol == CP_QUAKEWORLD && cls.demoplayback == DPB_MVD) { @@ -998,7 +995,7 @@ void CLQW_ParsePacketEntities (qboolean delta) //FIXME cl.oldvalidsequence = cl.validsequence; cl.validsequence = cls.netchan.incoming_sequence; - CL_AckedInputFrame(cls.netchan.incoming_sequence, true); + CL_AckedInputFrame(cls.netchan.incoming_sequence, cls.netchan.incoming_sequence, true); oldindex = 0; newindex = 0; @@ -1318,9 +1315,10 @@ void CLDP_ParseDarkPlaces5Entities(void) //the things I do.. :o( //client->server sequence ack if (cls.protocol_nq >= CPNQ_DP7) - CL_AckedInputFrame(MSG_ReadLong(), true); /*client input sequence which has been acked*/ + CL_AckedInputFrame(cls.netchan.incoming_sequence, MSG_ReadLong(), true); /*client input sequence which has been acked*/ cl.inframes[(cls.netchan.incoming_sequence)&UPDATE_MASK].receivedtime = realtime; + cl.inframes[(cls.netchan.incoming_sequence)&UPDATE_MASK].frameid = cls.netchan.incoming_sequence; pack = &cl.inframes[(cls.netchan.incoming_sequence)&UPDATE_MASK].packet_entities; pack->servertime = cl.gametime; oldpack = *pack; @@ -2316,6 +2314,14 @@ void CLQ1_AddVisibleBBoxes(void) VectorAdd(state->origin, min, min); VectorAdd(state->origin, max, max); CLQ1_AddOrientedCube(s, min, max, NULL, 0.1, 0, 0, 1); + + max[0] = max[1] = 8*(state->solid & 31); + min[0] = min[1] = -max[0]; + min[2] = -8*((state->solid>>5) & 31); + max[2] = 8*((state->solid>>10) & 63) - 32; + VectorAdd(state->u.q1.predorg, min, min); + VectorAdd(state->u.q1.predorg, max, max); + CLQ1_AddOrientedCube(s, min, max, NULL, 0, 0, 0.1, 1); } } } @@ -2739,6 +2745,8 @@ static void CL_TransitionPacketEntities(int newsequence, packet_entities_t *newp entity_state_t *snew, *sold; int i; int oldpnum, newpnum; + float *snew__origin; + float *sold__origin; vec3_t move; @@ -2791,7 +2799,59 @@ static void CL_TransitionPacketEntities(int newsequence, packet_entities_t *newp le->sequence = newsequence; le->entstate = snew; - VectorSubtract(snew->origin, sold->origin, move); + if (snew->u.q1.pmovetype) + { + if (!cl.do_lerp_players) + { + entity_state_t *from; + float age; + packet_entities_t *latest; + if (sold == snew) + { + /*keep trails correct*/ + le->isnew = true; + VectorCopy(le->origin, le->lastorigin); + } + CL_UpdateNetFrameLerpState(sold == snew, snew->frame, le); + + + from = sold; //eww + age = servertime - oldpack->servertime; + latest = &cl.inframes[cl.validsequence & UPDATE_MASK].packet_entities; + for (i = 0; i < latest->num_entities; i++) + { + if (latest->entities[i].number == snew->number) + { + from = &latest->entities[i]; + //use realtime instead. + //also, use the sent timings instead of received as those are assumed to be more reliable + age = (realtime - cl.outframes[cl.ackedmovesequence & UPDATE_MASK].senttime) - cls.latency; + break; + } + } + if (age > 1) + age = 1; + + if (cl_predict_players.ival) + { + CL_PredictEntityMovement(from, age); + VectorCopy(from->u.q1.predorg, le->origin); + } + else + VectorCopy(from->origin, le->origin); + VectorCopy(from->angles, le->angles); + continue; + } + + snew__origin = snew->u.q1.predorg; + sold__origin = sold->u.q1.predorg; + } + else + { + snew__origin = snew->origin; + sold__origin = sold->origin; + } + VectorSubtract(snew__origin, sold__origin, move); if (DotProduct(move, move) > 200*200 || snew->modelindex != sold->modelindex) { sold = snew; //teleported? @@ -2799,24 +2859,15 @@ static void CL_TransitionPacketEntities(int newsequence, packet_entities_t *newp } VectorCopy(le->origin, le->lastorigin); - if (snew->u.q1.pmovetype && CL_PredictPlayer(le, snew, newsequence)) - { - if (sold == snew) - { - /*keep trails correct*/ - le->isnew = true; - VectorCopy(le->origin, le->lastorigin); - } - } - else if (sold == snew) + if (sold == snew) { //new this frame (or we noticed something changed significantly) - VectorCopy(snew->origin, le->origin); + VectorCopy(snew__origin, le->origin); VectorCopy(snew->angles, le->angles); - VectorCopy(snew->origin, le->oldorigin); + VectorCopy(snew__origin, le->oldorigin); VectorCopy(snew->angles, le->oldangle); - VectorCopy(snew->origin, le->neworigin); + VectorCopy(snew__origin, le->neworigin); VectorCopy(snew->angles, le->newangle); le->orglerpdeltatime = 0.1; @@ -2831,7 +2882,7 @@ static void CL_TransitionPacketEntities(int newsequence, packet_entities_t *newp { float lfrac; //ignore the old packet entirely, except for maybe its time. - if (!VectorEquals(le->neworigin, snew->origin) || !VectorEquals(le->newangle, snew->angles)) + if (!VectorEquals(le->neworigin, snew__origin) || !VectorEquals(le->newangle, snew->angles)) { le->orglerpdeltatime = bound(0, oldpack->servertime - le->orglerpstarttime, 0.11); //clamp to 10 tics per second le->orglerpstarttime = oldpack->servertime; @@ -2839,7 +2890,7 @@ static void CL_TransitionPacketEntities(int newsequence, packet_entities_t *newp VectorCopy(le->neworigin, le->oldorigin); VectorCopy(le->newangle, le->oldangle); - VectorCopy(snew->origin, le->neworigin); + VectorCopy(snew__origin, le->neworigin); VectorCopy(snew->angles, le->newangle); } @@ -2863,7 +2914,7 @@ static void CL_TransitionPacketEntities(int newsequence, packet_entities_t *newp //lerp based purely on the packet times, for (i = 0; i < 3; i++) { - le->origin[i] = sold->origin[i] + frac*(move[i]); + le->origin[i] = sold__origin[i] + frac*(move[i]); a1 = sold->angles[i]; a2 = snew->angles[i]; @@ -2895,7 +2946,7 @@ static qboolean CL_ChooseInterpolationFrames(int *newf, int *oldf, float servert //we should be picking the packet just after the server time, and the one just before for (i = cls.netchan.incoming_sequence; i >= cls.netchan.incoming_sequence-UPDATE_MASK; i--) { - if (cl.inframes[i&UPDATE_MASK].receivedtime < 0 || /*cl.inframes[i&UPDATE_MASK].latency < 0 ||*/ cl.inframes[i&UPDATE_MASK].invalid) + if (cl.inframes[i&UPDATE_MASK].frameid != i || cl.inframes[i&UPDATE_MASK].invalid) continue; //packetloss/choke, it's really only a problem for the oldframe, but... if (cl.inframes[i&UPDATE_MASK].packet_entities.servertime >= servertime) @@ -2930,7 +2981,7 @@ static qboolean CL_ChooseInterpolationFrames(int *newf, int *oldf, float servert /*just grab the most recent frame that is valid*/ for (i = cls.netchan.incoming_sequence; i >= cls.netchan.incoming_sequence-UPDATE_MASK; i--) { - if (cl.inframes[i&UPDATE_MASK].receivedtime < 0 || cl.inframes[i&UPDATE_MASK].latency < 0 || cl.inframes[i&UPDATE_MASK].invalid) + if (cl.inframes[i&UPDATE_MASK].frameid != i || cl.inframes[i&UPDATE_MASK].invalid) continue; //packetloss/choke, it's really only a problem for the oldframe, but... *oldf = *newf = i; return true; @@ -3122,6 +3173,9 @@ void CL_LinkPacketEntities (void) ent->light_known = 0; ent->forcedshader = NULL; + if (state->number >= cl.maxlerpents) + continue; + le = &cl.lerpents[state->number]; memset(&ent->framestate, 0, sizeof(ent->framestate)); @@ -4379,6 +4433,24 @@ void CL_LinkPlayers (void) CLQ1_AddShadow(ent); CLQ1_AddPowerupShell(ent, false, state->effects); + if ((r_showbboxes.ival & 3) == 3) + { + vec3_t min, max; + extern vec3_t player_mins, player_maxs; + shader_t *s = R_RegisterShader("bboxshader", SUF_NONE, NULL); + if (s) + { + VectorAdd(state->origin, player_mins, min); + VectorAdd(state->origin, player_maxs, max); + CLQ1_AddOrientedCube(s, min, max, NULL, 0.1, 0, 0, 1); + + VectorAdd(ent->origin, player_mins, min); + VectorAdd(ent->origin, player_maxs, max); + CLQ1_AddOrientedCube(s, min, max, NULL, 0, 0, 0.1, 1); + } + } + + if (r_torch.ival) { dlight_t *dl; diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index a23e7e2c0..596b33849 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -944,7 +944,13 @@ void CLNQ_SendMove (usercmd_t *cmd, int pnum, sizebuf_t *buf) MSG_WriteByte (buf, clc_move); if (cls.protocol_nq >= CPNQ_DP7 || (cls.fteprotocolextensions2 & PEXT2_PREDINFO)) - MSG_WriteLong(buf, cl.movesequence); + { + extern cvar_t cl_nopred; + if (cl_nopred.ival) + MSG_WriteLong(buf, 0); + else + MSG_WriteLong(buf, cl.movesequence); + } MSG_WriteFloat (buf, cl.gametime); // so server can get ping times @@ -1004,28 +1010,38 @@ void Name_Callback(struct cvar_s *var, char *oldvalue) void CLNQ_SendCmd(sizebuf_t *buf) { int i; + int seat; usercmd_t *cmd; i = cl.movesequence & UPDATE_MASK; cl.outframes[i].senttime = realtime; - cmd = &cl.outframes[i].cmd[0]; - *cmd = independantphysics[0]; - cmd->lightlevel = 0; -#ifdef CSQC_DAT - CSQC_Input_Frame(0, cmd); -#endif - memset(&independantphysics[0], 0, sizeof(independantphysics[0])); + cl.outframes[i].latency = -1; + cl.outframes[i].server_message_num = cl.validsequence; + cl.outframes[i].cmd_sequence = cl.movesequence; + for (seat = 0; seat < cl.splitclients; seat++) + { + cmd = &cl.outframes[i].cmd[seat]; + *cmd = independantphysics[seat]; + cmd->lightlevel = 0; +#ifdef CSQC_DAT + CSQC_Input_Frame(seat, cmd); +#endif + memset(&independantphysics[seat], 0, sizeof(independantphysics[seat])); + } //inputs are only sent once we receive an entity. if (cls.signon == 4) { - // send the unreliable message - if (independantphysics[0].impulse && !cls.netchan.message.cursize) - CLNQ_SendMove (cmd, 0, &cls.netchan.message); - else - CLNQ_SendMove (cmd, 0, buf); + for (seat = 0; seat < cl.splitclients; seat++) + { + // send the unreliable message +// if (independantphysics[seat].impulse && !cls.netchan.message.cursize) +// CLNQ_SendMove (&cl.outframes[i].cmd[seat], seat, &cls.netchan.message); +// else + CLNQ_SendMove (&cl.outframes[i].cmd[seat], seat, buf); + } } for (i = 0; i < cl.numackframes; i++) @@ -1050,7 +1066,7 @@ float CL_FilterTime (double time, float wantfps, qboolean ignoreserver) //now re return -1; /*ignore the server if we're playing demos, sending to the server only as replies, or if its meant to be disabled (netfps depending on where its called from)*/ - if (cls.demoplayback != DPB_NONE || cls.protocol != CP_QUAKEWORLD || ignoreserver) + if (cls.demoplayback != DPB_NONE || (cls.protocol != CP_QUAKEWORLD && cls.protocol != CP_NETQUAKE) || ignoreserver) { if (!wantfps) return -1; @@ -1370,6 +1386,7 @@ qboolean CLQ2_SendCmd (sizebuf_t *buf) cmd->lightlevel = lightlev; cl.outframes[i].senttime = realtime; + cl.outframes[i].latency = -1; memset(&independantphysics[0], 0, sizeof(independantphysics[0])); if (cmd->buttons) @@ -1400,8 +1417,13 @@ qboolean CLQW_SendCmd (sizebuf_t *buf) int st = buf->cursize; cl.movesequence = cls.netchan.outgoing_sequence; //make sure its correct even over map changes. - curframe = cls.netchan.outgoing_sequence & UPDATE_MASK; - seq_hash = cls.netchan.outgoing_sequence; + curframe = cl.movesequence & UPDATE_MASK; + seq_hash = cl.movesequence; + + cl.outframes[curframe].server_message_num = cl.validsequence; + cl.outframes[curframe].cmd_sequence = cl.movesequence; + cl.outframes[curframe].senttime = realtime; + cl.outframes[curframe].latency = -1; // send this and the previous cmds in the message, so // if the last packet was dropped, it can be recovered @@ -1422,7 +1444,6 @@ qboolean CLQW_SendCmd (sizebuf_t *buf) #endif memset(&independantphysics[plnum], 0, sizeof(independantphysics[plnum])); } - cl.outframes[curframe].senttime = realtime; if ((cls.fteprotocolextensions2 & PEXT2_PRYDONCURSOR) && (*cl_prydoncursor.string && cl_prydoncursor.ival >= 0) && cls.state == ca_active) { @@ -1490,7 +1511,7 @@ qboolean CLQW_SendCmd (sizebuf_t *buf) cl.inframes[cls.netchan.outgoing_sequence&UPDATE_MASK].delta_sequence = -1; if (cl.sendprespawn) - buf->cursize = st; //tastyspleen.net is alergic. + buf->cursize = st; //don't send movement commands while we're still supposedly downloading. mvdsv does not like that. return dontdrop; } @@ -1638,7 +1659,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) } if (cl_netfps.value > 0 || !fullsend) { - int spare; + float spare; spare = CL_FilterTime(msecstouse, wantfps, false); if (!spare && (msecstouse < 200 #ifdef IRCCONNECT @@ -1726,7 +1747,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) // if we're not doing clc_moves and etc, don't continue unless we wrote something previous // or we have something on the reliable buffer (or we're loopback and don't care about flooding) if (!fullsend && cls.netchan.remote_address.type != NA_LOOPBACK && buf.cursize < 1 && cls.netchan.message.cursize < 1) - return; + return; if (fullsend) { @@ -1766,6 +1787,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) if (cls.demorecording) CL_WriteDemoCmd(cmd); + Con_DPrintf("generated sequence %i\n", cl.movesequence); cl.movesequence++; #ifdef IRCCONNECT @@ -1778,7 +1800,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) else { // don't count this message when calculating PL - cl.inframes[i].latency = -3; + cl.outframes[i].latency = -3; // drop this message cls.netchan.outgoing_sequence++; dropcount++; @@ -1808,7 +1830,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) else { // don't count this message when calculating PL - cl.inframes[i].latency = -3; + cl.outframes[i].latency = -3; // drop this message cls.netchan.outgoing_sequence++; dropcount++; diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 125002701..c77580cbe 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -867,7 +867,8 @@ void CL_CheckForResend (void) MSG_WriteLong(&sb, strtoul(password.string, NULL, 0)); /*password*/ /*FTE servers will detect this string and treat it as a qw challenge instead (if it allows qw clients), so protocol choice is deterministic*/ - MSG_WriteString(&sb, "getchallenge"); + if (contype & 1) + MSG_WriteString(&sb, "getchallenge"); *(int*)sb.data = LongSwap(NETFLAG_CTL | sb.cursize); NET_SendPacket (NS_CLIENT, sb.cursize, sb.data, &adr); @@ -881,6 +882,7 @@ void CL_BeginServerConnect(int port) { if (!port) port = cl_defaultport.value; + cls.protocol = CP_UNKNOWN; SCR_SetLoadingStage(LS_CONNECTION); connect_time = 0; connect_defaultport = port; @@ -932,7 +934,7 @@ void CL_Join_f (void) { if (cls.state) { //Hmm. This server sucks. - if (cls.z_ext & Z_EXT_JOIN_OBSERVE) + if ((cls.z_ext & Z_EXT_JOIN_OBSERVE) || cls.protocol != CP_QUAKEWORLD) Cmd_ForwardToServer(); else Cbuf_AddText("\nspectator 0;reconnect\n", RESTRICT_LOCAL); @@ -959,10 +961,10 @@ void CL_Observe_f (void) if (Cmd_Argc() != 2) { if (cls.state) - { //Hmm. This server sucks. - if (cls.z_ext & Z_EXT_JOIN_OBSERVE) + { + if ((cls.z_ext & Z_EXT_JOIN_OBSERVE) || cls.protocol != CP_QUAKEWORLD) Cmd_ForwardToServer(); - else + else //Hmm. This server sucks. Cbuf_AddText("\nspectator 1;reconnect\n", RESTRICT_LOCAL); return; } @@ -1349,7 +1351,7 @@ void CL_Disconnect (void) Alias_WipeStuffedAliases(); //now start up the csqc/menu module again. - CSQC_UnconnectedInit(); +// CSQC_UnconnectedInit(); } #undef serverrunning @@ -3963,7 +3965,7 @@ double Host_Frame (double time) #endif ) { - realtime += spare/1000; //don't use it all! +// realtime += spare/1000; //don't use it all! spare = CL_FilterTime((realtime - oldrealtime)*1000, maxfps, maxfpsignoreserver); if (!spare) return (cl_yieldcpu.ival || vid.isminimized)? (1.0 / maxfps - (realtime - oldrealtime)) : 0; @@ -3972,7 +3974,7 @@ double Host_Frame (double time) if (spare > cl_sparemsec.ival) spare = cl_sparemsec.ival; - realtime -= spare/1000; //don't use it all! +// realtime -= spare/1000; //don't use it all! } else spare = 0; diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 701f7744c..4042d3cf6 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -315,7 +315,7 @@ int packet_latency[NET_TIMINGS]; int CL_CalcNet (void) { int i; - inframe_t *frame; + outframe_t *frame; int lost = 0; int percent; int sent; @@ -327,7 +327,7 @@ int CL_CalcNet (void) ; i <= cl.movesequence ; i++) { - frame = &cl.inframes[i&UPDATE_MASK]; + frame = &cl.outframes[i&UPDATE_MASK]; if (i > cl.lastackedmovesequence) { // no response yet @@ -351,8 +351,8 @@ int CL_CalcNet (void) packet_latency[i&NET_TIMINGSMASK] = 9997; // c2spps sent--; } - else if (frame->invalid) - packet_latency[i&NET_TIMINGSMASK] = 9998; // invalid delta +// else if (frame->invalid) +// packet_latency[i&NET_TIMINGSMASK] = 9998; // invalid delta else packet_latency[i&NET_TIMINGSMASK] = frame->latency * 60; } @@ -365,40 +365,48 @@ int CL_CalcNet (void) return percent; } -void CL_AckedInputFrame(int seq, qboolean worldstateokay) +void CL_AckedInputFrame(int inseq, int outseq, qboolean worldstateokay) { unsigned int i; unsigned int newmod; - inframe_t *frame; + outframe_t *frame; - newmod = seq & UPDATE_MASK; - frame = &cl.inframes[newmod]; -// calculate latency - frame->latency = realtime - cl.outframes[newmod].senttime; - if (frame->latency < 0 || frame->latency > 1.0) + newmod = outseq & UPDATE_MASK; + + //calc the latency for this frame, but only if its not a dupe ack. we want the youngest, not the oldest, so we can calculate network latency rather than simply packet frequency + if (outseq != cl.lastackedmovesequence) { -// Con_Printf ("Odd latency: %5.2f\n", latency); - } - else - { - // drift the average latency towards the observed latency - if (frame->latency < cls.latency) - cls.latency = frame->latency; + frame = &cl.outframes[newmod]; + // calculate latency + frame->latency = realtime - frame->senttime; + if (frame->latency < 0 || frame->latency > 1.0) + { + // Con_Printf ("Odd latency: %5.2f\n", latency); + } else - cls.latency += 0.001; // drift up, so correction are needed - } + { + // drift the average latency towards the observed latency + if (frame->latency < cls.latency) + cls.latency = frame->latency; + else + cls.latency += 0.001; // drift up, so correction are needed + } - //and mark any missing ones as dropped - if (seq != cl.lastackedmovesequence) - { + if (cl.inframes[inseq&UPDATE_MASK].invalid) + frame->latency = -4; + + //and mark any missing ones as dropped for (i = (cl.lastackedmovesequence+1) & UPDATE_MASK; i != newmod; i=(i+1)&UPDATE_MASK) { - cl.inframes[i].latency = -1; +//nq has no concept of choking. outbound packets that are accepted during a single frame will be erroneoulsy considered dropped. nq never had a netgraph based upon outgoing timings. +// Con_Printf("Dropped moveframe %i\n", i); + cl.outframes[i].latency = -1; } } + cl.inframes[inseq&UPDATE_MASK].ackframe = outseq; if (worldstateokay) - cl.ackedmovesequence = seq; - cl.lastackedmovesequence = seq; + cl.ackedmovesequence = outseq; + cl.lastackedmovesequence = outseq; } //============================================================================= @@ -4002,7 +4010,7 @@ void CL_ParseClientdata (void) parsecounttime = cl.outframes[i].senttime; if (cls.protocol == CP_QUAKEWORLD) - CL_AckedInputFrame(cl.parsecount, false); + CL_AckedInputFrame(cls.netchan.incoming_sequence, cl.parsecount, false); } /* @@ -4439,9 +4447,9 @@ void CL_MuzzleFlash (int destsplit) dl->radius = 200 + (rand()&31); dl->minlight = 32; dl->die = cl.time + 0.1334; - dl->color[0] = 0.2; - dl->color[1] = 0.1; - dl->color[2] = 0.05; + dl->color[0] = 1.0; + dl->color[1] = 0.4; + dl->color[2] = 0.2; dl->channelfade[0] = 1.5; dl->channelfade[1] = 0.75; @@ -5762,7 +5770,7 @@ void CLQW_ParseServerMessage (void) case svc_chokecount: // some preceding packets were choked i = MSG_ReadByte (); for (j=0 ; jmsec / 2; //special care to avoid forgetting an msec here and there CL_PredictUsercmd (pnum, entnum, from, &temp, &split); + split.msec = u->msec - split.msec; CL_PredictUsercmd (pnum, entnum, &temp, to, &split); return; } @@ -443,11 +446,11 @@ void CL_CatagorizePosition (playerview_t *pv) } //Smooth out stair step ups. //Called before CL_EmitEntities so that the player's lightning model origin is updated properly -void CL_CalcCrouch (playerview_t *pv, float stepchange) +void CL_CalcCrouch (playerview_t *pv) { qboolean teleported; vec3_t delta; - float orgz = DotProduct(pv->simorg, pv->gravitydir); //compensate for running on walls. + float orgz = -DotProduct(pv->simorg, pv->gravitydir); //compensate for running on walls. VectorSubtract(pv->simorg, pv->oldorigin, delta); @@ -494,6 +497,8 @@ void CL_CalcCrouch (playerview_t *pv, float stepchange) // in air or moving down pv->oldz = orgz; pv->crouch += host_frametime * 150; + if (orgz - pv->oldz <= 0) + pv->crouch -= orgz - pv->oldz; //if the view moved down, remove that amount from our crouching to avoid unneeded bobbing if (pv->crouch > 0) pv->crouch = 0; pv->crouchspeed = 100; @@ -514,112 +519,6 @@ float LerpAngles360(float to, float from, float frac) return to + frac*delta; } -//shamelessly ripped from zquake -extern cvar_t cl_nolerp; -static void CL_LerpMove (int pnum, float msgtime) -{ - static int lastsequence = 0; - static vec3_t lerp_angles[3]; - static vec3_t lerp_origin[3]; - static float lerp_times[3]; - static qboolean nolerp[2]; - static float demo_latency = 0.01; - float frac; - float simtime; - int i; - int from, to; - - if (!CL_MayLerp() || cls.demoplayback == DPB_MVD || cls.demoplayback == DPB_EZTV) - return; - -#ifdef NQPROT - if (cls.demoplayback == DPB_NETQUAKE) - return; -#endif - - if (cls.netchan.outgoing_sequence < lastsequence) - { - // reset - lastsequence = -1; - lerp_times[0] = -1; - demo_latency = 0.01; - } - - if (cls.netchan.outgoing_sequence > lastsequence) - { - lastsequence = cls.netchan.outgoing_sequence; - // move along - lerp_times[2] = lerp_times[1]; - lerp_times[1] = lerp_times[0]; - lerp_times[0] = msgtime; - - VectorCopy (lerp_origin[1], lerp_origin[2]); - VectorCopy (lerp_origin[0], lerp_origin[1]); - VectorCopy (cl.playerview[pnum].simorg, lerp_origin[0]); - - VectorCopy (lerp_angles[1], lerp_angles[2]); - VectorCopy (lerp_angles[0], lerp_angles[1]); - VectorCopy (cl.playerview[pnum].simangles, lerp_angles[0]); - - nolerp[1] = nolerp[0]; - nolerp[0] = false; - for (i = 0; i < 3; i++) - if (fabs(lerp_origin[0][i] - lerp_origin[1][i]) > 40) - break; - if (i < 3) - nolerp[0] = true; // a teleport or something - } - - simtime = realtime - demo_latency; - - // adjust latency - if (simtime > lerp_times[0]) - { - // Com_DPrintf ("HIGH clamp\n"); - demo_latency = realtime - lerp_times[0]; - } - else if (simtime < lerp_times[2]) - { - // Com_DPrintf (" low clamp\n"); - demo_latency = realtime - lerp_times[2]; - } - else - { - // drift towards ideal latency - float ideal_latency = (lerp_times[0] - lerp_times[2]) * 0.6; - if (demo_latency > ideal_latency) - demo_latency = max(demo_latency - host_frametime * 0.1, ideal_latency); - } - - // decide where to lerp from - if (simtime > lerp_times[1]) - { - from = 1; - to = 0; - } - else - { - from = 2; - to = 1; - } - - if (nolerp[to]) - return; - - frac = (simtime - lerp_times[from]) / (lerp_times[to] - lerp_times[from]); - frac = bound (0, frac, 1); - - for (i=0 ; i<3 ; i++) - { - cl.playerview[pnum].simorg[i] = lerp_origin[from][i] + - frac * (lerp_origin[to][i] - lerp_origin[from][i]); - cl.playerview[pnum].simangles[i] = LerpAngles360(lerp_angles[from][i], lerp_angles[to][i], frac); - } - -// LerpVector (lerp_origin[from], lerp_origin[to], frac, cl.simorg); -// LerpAngles (lerp_angles[from], lerp_angles[to], frac, cl.simangles); -} - short LerpAngles16(short to, short from, float frac) { int delta; @@ -648,7 +547,7 @@ void CL_CalcClientTime(void) f = bound(0, f, 1); cl.servertime = cl.gametime*f + cl.oldgametime*(1-f); } - else if (0) + else if (!cl_predict_smooth.ival || (cl_predict_smooth.ival == 2 && !cls.demoplayback)) { float f; f = cl.gametime - cl.oldgametime; @@ -750,6 +649,9 @@ void CL_CalcClientTime(void) } else { + if (cl_pushlatency.value > 0) + Cvar_Set (&cl_pushlatency, "0"); + cl.time = realtime - cls.latency - cl_pushlatency.value*0.001; if (cl.time > realtime) cl.time = realtime; @@ -789,11 +691,11 @@ static void CL_DecodeStateSize(unsigned short solid, int modelindex, vec3_t mins /*called on packet reception*/ #include "pr_common.h" -void CL_PlayerFrameUpdated(player_state_t *plstate, entity_state_t *state, int sequence) +static void CL_EntStateToPlayerState(player_state_t *plstate, entity_state_t *state) { - /*update the prediction info*/ vec3_t a; - int pmtype, i; + int pmtype; + memset(plstate, 0, sizeof(*plstate)); switch(state->u.q1.pmovetype) { case MOVETYPE_NOCLIP: @@ -823,72 +725,62 @@ void CL_PlayerFrameUpdated(player_state_t *plstate, entity_state_t *state, int s plstate->pm_type = pmtype; VectorCopy(state->origin, plstate->origin); - plstate->command.angles[0] = state->angles[0] * -3 *65536/360.0; - plstate->command.angles[1] = state->angles[1] * 65536/360.0; - plstate->command.angles[2] = state->angles[2] * 65536/360.0; VectorScale(state->u.q1.velocity, 1/8.0, plstate->velocity); - plstate->messagenum = sequence; a[0] = ((-192-state->u.q1.gravitydir[0])/256.0f) * 360; a[1] = (state->u.q1.gravitydir[1]/256.0f) * 360; a[2] = 0; AngleVectors(a, plstate->gravitydir, NULL, NULL); - cl.players[state->number-1].stats[STAT_WEAPONFRAME] = state->u.q1.weaponframe; - cl.players[state->number-1].statsf[STAT_WEAPONFRAME] = state->u.q1.weaponframe; - for (i = 0; i < cl.splitclients; i++) - { - if (cl.playerview[i].playernum == state->number-1) - { - cl.playerview[i].stats[STAT_WEAPONFRAME] = state->u.q1.weaponframe; - cl.playerview[i].statsf[STAT_WEAPONFRAME] = state->u.q1.weaponframe; - cl.playerview[i].pmovetype = pmtype; - } - } - CL_DecodeStateSize(state->solid, state->modelindex, plstate->szmins, plstate->szmaxs); } - -/*called once every rendered frame*/ -qboolean CL_PredictPlayer(lerpents_t *le, entity_state_t *state, int sequence) +static void CL_EntStateToPlayerCommand(usercmd_t *cmd, entity_state_t *state, float age) { - int msec, oldphysent; + int msec; + float extra; + memset(cmd, 0, sizeof(*cmd)); + + extra = /*-cls.latency + */ 0.02; //network latency + extra += age; //if the state is not exactly current +// extra += realtime - cl.inframes[cl.validsequence&UPDATE_MASK].receivedtime; +// extra += (cl.inframes[cl.validsequence&UPDATE_MASK].receivedtime - cl.inframes[cl.oldvalidsequence&UPDATE_MASK].receivedtime)*4; + msec = 1000*extra; + Con_DPrintf("%i: age = %i, stale=%i\n", state->number, msec, state->u.q1.msec); + msec += state->u.q1.msec; //this is the age on the server + cmd->msec = bound(0, msec, 250); + + cmd->forwardmove = state->u.q1.movement[0]; + cmd->sidemove = state->u.q1.movement[1]; + cmd->upmove = state->u.q1.movement[2]; + + cmd->angles[0] = state->angles[0] * -3 *65536/360.0; + cmd->angles[1] = state->angles[1] * 65536/360.0; + cmd->angles[2] = state->angles[2] * 65536/360.0; +} + +void CL_PredictEntityMovement(entity_state_t *estate, float age) +{ + player_state_t startstate, resultstate; usercmd_t cmd; - player_state_t start, exact; - int pnum; + int oldphysent; + //build the entitystate state into a player state for prediction to use - if (state->number-1 > cl.allocated_client_slots || cl.intermission) - return false; - - /*local players just interpolate for now. the prediction code will move it to the right place afterwards*/ - for (pnum = 0; pnum < cl.splitclients; pnum++) + if (!estate->u.q1.pmovetype) + VectorCopy(estate->origin, estate->u.q1.predorg); + else { - if (state->number-1 == cl.playerview[pnum].playernum) - return false; + CL_EntStateToPlayerState(&startstate, estate); + CL_EntStateToPlayerCommand(&cmd, estate, age); + +// cmd.forwardmove = 5000; +// cmd.msec = sin(realtime*6) * 128 + 128; + oldphysent = pmove.numphysent; + pmove.onground = true; + CL_PredictUsercmd(0, estate->number, &startstate, &resultstate, &cmd); //uses player 0's maxspeed/grav... + pmove.numphysent = oldphysent; + + VectorCopy(resultstate.origin, estate->u.q1.predorg); } - - memset(&cmd, 0, sizeof(cmd)); - memset(&start, 0, sizeof(start)); - - CL_PlayerFrameUpdated(&start, state, sequence); - - msec = 500*(realtime - cls.latency + 0.02 - cl.inframes[sequence & UPDATE_MASK].receivedtime); - cmd.msec = bound(0, msec, 255); - cmd.forwardmove = state->u.q1.movement[0]; - cmd.sidemove = state->u.q1.movement[1]; - cmd.upmove = state->u.q1.movement[2]; - - oldphysent = pmove.numphysent; - CL_PredictUsercmd (0, state->number, &start, &exact, &cmd); //uses player 0's maxspeed/grav... - pmove.numphysent = oldphysent; - - /*need to update the entity's angles and origin so the linkentities function puts it in the correct predicted place*/ - le->angles[0] = state->angles[0]; - le->angles[1] = state->angles[1]; - le->angles[2] = state->angles[2]; - VectorCopy (exact.origin, le->origin); - - return true; } /* @@ -898,17 +790,23 @@ CL_PredictMove */ void CL_PredictMovePNum (int seat) { + //when this is called, the entity states have been interpolated. + //interpolation state should be updated to match prediction state, so entities move correctly in mirrors/portals. + + //this entire function is pure convolouted bollocks. playerview_t *pv = &cl.playerview[seat]; - inframe_t indstate; - outframe_t indcmd; int i; float f; - inframe_t *from, *to = NULL; - outframe_t *cmdfrom, *cmdto; + int fromframe, toframe; + outframe_t *backdate; + player_state_t *fromstate, *tostate, framebuf[2]; //need two framebufs so we can interpolate between two states. + usercmd_t *cmdfrom, *cmdto; + double fromtime, totime; int oldphysent; - vec3_t lrp, lrpv; double simtime; extern cvar_t cl_netfps; + lerpents_t *le; + qboolean nopred; //these are to make svc_viewentity work better float *vel; @@ -923,23 +821,31 @@ void CL_PredictMovePNum (int seat) } else { + qboolean extrap = cl_predict_extrapolate.ival; +// float fps = 1/host_frametime; +// fps = bound(6.7, fps, cls.maxfps); netfps = bound(6.7, netfps, cls.maxfps); - if (netfps < 30) +// if (netfps > fps) +// netfps = fps; + if (!*cl_predict_extrapolate.string) + extrap = netfps < 30; + if (!extrap) + { + //interpolate. The input rate is completely smoothed out, at the cost of some latency. + //You can still get juddering if the video rate doesn't match the monitor refresh rate (and isn't so high that it doesn't matter). + //note that the code below will back-date input frames if the server acks too fast. + simtime = realtime - (1/netfps); + } + else { //extrapolate if we've a low net rate. This should reduce apparent lag, but will be jerky if the net rate is not an (inverse) multiple of the monitor rate. //this is in addition to any monitor desync. simtime = realtime; } - else - { - //interpolate. The input rate is completely smoothed out, at the cost of some latency. - //You can still get juddering if the video rate doesn't match the monitor refresh rate (and isn't so high that it doesn't matter). - //note that the code below will back-date input frames if the server acks too fast. - netfps = bound(6.7, netfps, cls.maxfps); - simtime = realtime - (1/netfps); - } } + simtime += bound(-0.5, cl_predict_timenudge.value, 0.5); + pv->nolocalplayer = !!(cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) || (cls.protocol != CP_QUAKEWORLD); #ifdef Q2CLIENT @@ -953,9 +859,6 @@ void CL_PredictMovePNum (int seat) } #endif - if (cl_pushlatency.value > 0) - Cvar_Set (&cl_pushlatency, "0"); - if (cl.paused && !(cls.demoplayback!=DPB_MVD && cls.demoplayback!=DPB_EZTV) && (!cl.spectator || !pv->cam_auto)) return; @@ -966,6 +869,7 @@ void CL_PredictMovePNum (int seat) if (cl.intermission==1 && cls.protocol == CP_QUAKEWORLD) { + //quakeworld locks view position once you hit intermission. VectorCopy (pv->intermissionangles, pv->simangles); return; } @@ -985,255 +889,264 @@ void CL_PredictMovePNum (int seat) VectorCopy (pv->viewangles, pv->simangles); } + nopred = cl_nopred.ival; + //don't wrap - if (cl.movesequence - cl.ackedmovesequence >= UPDATE_BACKUP-1) + if (!cl.ackedmovesequence) + nopred = true; + else if (cl.movesequence - cl.ackedmovesequence >= UPDATE_BACKUP-1) return; - // this is the last frame received from the server - from = &cl.inframes[cl.validsequence & UPDATE_MASK]; - cmdfrom = &cl.outframes[cl.ackedmovesequence & UPDATE_MASK]; - vel = from->playerstate[pv->playernum].velocity; - org = from->playerstate[pv->playernum].origin; - -#ifdef PEXT_SETVIEW - //if the view is attached to an arbitary entity... - if (pv->viewentity && (pv->viewentity != pv->playernum+1 || cl.ackedmovesequence == cl.movesequence)) + //these things also force-disable prediction + if ( (cls.demoplayback==DPB_MVD || cls.demoplayback == DPB_EZTV) || + cl.paused || pv->pmovetype == PM_NONE || pv->pmovetype == PM_FREEZE) { - if (pv->viewentity >= 0 && pv->viewentity <= cl.allocated_client_slots && from->playerstate[pv->viewentity-1].messagenum == cl.validsequence) - { - } - else if (pv->viewentity < cl.maxlerpents) - { - pv->nolocalplayer = true; -// Con_Printf("Using lerped pos\n"); - org = cl.lerpents[pv->viewentity].origin; - vel = vec3_origin; - goto fixedorg; - } - } -#endif - if (!from->playerstate[pv->playernum].messagenum) - { - //no player states?? put the view on an ent - if (pv->playernum < cl.maxlerpents) - { - pv->nolocalplayer = true; -// Con_Printf("Using lerped pos\n"); - org = cl.lerpents[pv->playernum+1].origin; - vel = vec3_origin; - goto fixedorg; - } + nopred = true; } + // figure out the first frame to lerp from. + // we generate one new input frame every 1/72th of a second, with a refresh rate of 60hz that's blatently obvious + // if we live in the present, we'll only have half a frame. in order to avoid extrapolation (which can give a swimmy feel), we live in the past by one frame time period + // if we're running somewhere with a low latency, we can get a reply from the server before our next input frame is even generated, so we need to go backwards beyond the current state - if (((cl_nopred.value && cls.demoplayback!=DPB_MVD && cls.demoplayback != DPB_EZTV) || cl.paused || pv->pmovetype == PM_NONE)) + if (nopred) { - if (cl.do_lerp_players) + //match interpolation info + fromframe = ((char*)cl.previouspackentities - (char*)&cl.inframes[0].packet_entities) / sizeof(inframe_t); + fromtime = cl.inframes[fromframe & UPDATE_MASK].packet_entities.servertime; + toframe = ((char*)cl.currentpackentities - (char*)&cl.inframes[0].packet_entities) / sizeof(inframe_t); + totime = cl.inframes[toframe & UPDATE_MASK].packet_entities.servertime; + simtime = cl.currentpacktime; + } + else + { + fromframe = 0; + toframe = 0; + totime = fromtime = 0; + + //try to find the inbound frame that sandwiches the realtime that we're trying to simulate. + //if we're predicting, this will be some time in the future, and thus we'll be forced to pick the most recent frame. + //if we're interpolating, we'll need to grab the frame before that. + //we're only interested in inbound frames, not outbound, but its outbound frames that contain the prediction timing, so we need to look that up + //(note that in qw, inframe[i].ack==i holds true, but this code tries to be generic for unsyncronised protocols) + //(note that in nq, using outbound times means we'll skip over dupe states without noticing, and input packets with dupes should also be handled gracefully) + Con_DPrintf("in:%i:%i out:%i:%i ack:%i\n", cls.netchan.incoming_sequence, cl.validsequence, cls.netchan.outgoing_sequence,cl.movesequence, cl.ackedmovesequence); + for (i = cl.validsequence; i >= cls.netchan.incoming_sequence - UPDATE_MASK; i--) { - lerpents_t *le = NULL; - if (pv->nolocalplayer) + int out; + //skip frames which were not received, or are otherwise invalid. yay packetloss + if (cl.inframes[i & UPDATE_MASK].frameid != i || cl.inframes[i & UPDATE_MASK].invalid) { - if (pv->viewentity < cl.maxlerpents) - le = &cl.lerpents[pv->viewentity]; + Con_DPrintf("stale incoming command %i\n", i); + continue; } - else + + //each inbound frame tracks the outgoing frame that was last applied to it, and its outgoing frames that contain our timing info + out = cl.inframes[i&UPDATE_MASK].ackframe; + backdate = &cl.outframes[out & UPDATE_MASK]; + if (backdate->cmd_sequence != out) { - if (pv->viewentity >= 1 && pv->viewentity <= MAX_CLIENTS) - le = &cl.lerpplayers[pv->viewentity-1]; + Con_DPrintf("stale outgoing command %i (%i:%i:%i)\n", i, out, backdate->cmd_sequence, backdate->server_message_num); + continue; } - if (le) - org = le->origin; - vel = vec3_origin; + //okay, looks valid + + //if this is the first one we found, make sure both from+to are set properly + if (!fromframe) + { + fromframe = i; + fromtime = backdate->senttime; + } + toframe = fromframe; + totime = fromtime; + fromframe = i; + fromtime = backdate->senttime; + if (fromtime < simtime) + break; //okay, we found the first frame that is older, no need to continue looking } - -fixedorg: - VectorCopy (vel, pv->simvel); - VectorCopy (org, pv->simorg); - -// to = &cl.inframes[cl.ackedinputsequence & UPDATE_MASK]; - - - - CL_CatagorizePosition(pv); - goto out; } + Con_DPrintf("sim%f, %i(%i-%i): old%f, cur%f\n", simtime, cl.ackedmovesequence, fromframe, toframe, fromtime, totime); + + fromstate = &cl.inframes[fromframe & UPDATE_MASK].playerstate[pv->playernum]; + tostate = &cl.inframes[toframe & UPDATE_MASK].playerstate[pv->playernum]; + le = &cl.lerpplayers[pv->playernum]; + + //if our network protocol doesn't have a concept of separate players, make sure our player states are updated from those entities + //fixme: use entity states instead of player states to avoid the extra work here + if (pv->nolocalplayer || nopred) + { + packet_entities_t *pe; + pe = &cl.inframes[fromframe & UPDATE_MASK].packet_entities; + for (i = 0; i < pe->num_entities; i++) + { + if (pe->entities[i].number == pv->viewentity) + { + CL_EntStateToPlayerState(fromstate, &pe->entities[i]); + break; + } + } + pe = &cl.inframes[toframe & UPDATE_MASK].packet_entities; + for (i = 0; i < pe->num_entities; i++) + { + if (pe->entities[i].number == pv->viewentity) + { + CL_EntStateToPlayerState(tostate, &pe->entities[i]); + + if (cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) + { + cl.players[pv->playernum].stats[STAT_WEAPONFRAME] = pe->entities[i].u.q1.weaponframe; + cl.players[pv->playernum].statsf[STAT_WEAPONFRAME] = pe->entities[i].u.q1.weaponframe; + + pv->stats[STAT_WEAPONFRAME] = pe->entities[i].u.q1.weaponframe; + pv->statsf[STAT_WEAPONFRAME] = pe->entities[i].u.q1.weaponframe; + pv->pmovetype = tostate->pm_type; + } + break; + } + } + if (pv->nolocalplayer && pv->viewentity < cl.maxlerpents) + le = &cl.lerpents[pv->viewentity]; + } + + vel = fromstate->velocity; + org = fromstate->origin; + // predict forward until cl.time <= to->senttime oldphysent = pmove.numphysent; CL_SetSolidPlayers(); - pmove.skipent = pv->playernum+1; + pmove.skipent = pv->viewentity; -// Con_Printf("%i<%i %i\n", cl.ackedmovesequence, cl.movesequence, cl.validsequence); + cmdfrom = cmdto = &cl.outframes[cl.ackedmovesequence & UPDATE_MASK].cmd[seat]; - to = &cl.inframes[cl.validsequence & UPDATE_MASK]; - cmdto = &cl.outframes[cl.ackedmovesequence & UPDATE_MASK]; - - if (pv->viewentity && pv->viewentity != pv->playernum+1 && CL_MayLerp()) + if (!nopred) { - float f; - if (cl.do_lerp_players) + for (i=1 ; icam_spec_track]; - org = le->origin; - vel = vec3_origin; - VectorCopy(le->angles, pv->simangles); - goto fixedorg; - } - - to = &cl.inframes[cl.validsequence & UPDATE_MASK]; - from = &cl.inframes[cl.oldvalidsequence & UPDATE_MASK]; - - //figure out the lerp factor - if (cl.gametime == cl.servertime) - f = 0; - else - { - f = (cl.gametime-cl.servertime)/(cl.gametime-cl.oldgametime); - //f = (cl.time-cl.lerpents[state->number].lerptime)/cl.lerpents[state->number].lerprate; - } - if (f<0) - f=0; - if (f>1) - f=1; -// f = 1-f; - - - // calculate origin - for (i=0 ; i<3 ; i++) - { - lrp[i] = to->playerstate[pv->cam_spec_track].origin[i] + - f * (from->playerstate[pv->cam_spec_track].origin[i] - to->playerstate[pv->cam_spec_track].origin[i]); - - lrpv[i] = to->playerstate[pv->cam_spec_track].velocity[i] + - f * (from->playerstate[pv->cam_spec_track].velocity[i] - to->playerstate[pv->cam_spec_track].velocity[i]); - - pv->simangles[i] = LerpAngles16(to->playerstate[pv->cam_spec_track].command.angles[i], from->playerstate[pv->cam_spec_track].command.angles[i], f)*360.0f/65535; - } - - org = lrp; - vel = lrpv; - - pv->pmovetype = PM_NONE; - goto fixedorg; - } - else - { - if (cls.demoplayback==DPB_MVD || cls.demoplayback==DPB_EZTV) - { - from = &cl.inframes[(cl.ackedmovesequence-1) & UPDATE_MASK]; - to = &cl.inframes[(cl.ackedmovesequence) & UPDATE_MASK]; - cmdto = &cl.outframes[(cl.ackedmovesequence) & UPDATE_MASK]; - to->playerstate->pm_type = PM_SPECTATOR; - simtime = cmdto->senttime; - - VectorCopy (pv->simvel, from->playerstate[pv->playernum].velocity); - VectorCopy (pv->simorg, from->playerstate[pv->playernum].origin); - - CL_PredictUsercmd (seat, 0, &from->playerstate[pv->playernum], &to->playerstate[pv->playernum], &cmdto->cmd[seat]); - } - else - { - for (i=1 ; i= simtime) { - to = &cl.inframes[(cl.validsequence+i) & UPDATE_MASK]; - cmdto = &cl.outframes[(cl.ackedmovesequence+i) & UPDATE_MASK]; + if (i == 1) + { + //we must always predict a frame, just to ensure that the playerstate's jump status etc is valid for the next frame, even if we're not going to use it for interpolation. + //this assumes that we always have at least one video frame to each network frame, of course. + //note that q2 updates its values via networking rather than propagation. + Con_DPrintf(" propagate %i: %f-%f\n", cl.ackedmovesequence+i, fromtime, totime); + CL_PredictUsercmd (seat, pv->viewentity, tostate, &cl.inframes[(toframe+i) & UPDATE_MASK].playerstate[pv->playernum], &of->cmd[seat]); + } + break; + } + if (of->cmd_sequence != cl.ackedmovesequence+i) + { + Con_DPrintf("trying to predict a frame which is no longer valid\n"); + break; + } + fromtime = totime; + fromstate = tostate; + fromframe = toframe; //qw debug + cmdfrom = cmdto; - CL_PredictUsercmd (seat, pv->playernum+1, &from->playerstate[pv->playernum], &to->playerstate[pv->playernum], &cmdto->cmd[seat]); + cmdto = &of->cmd[seat]; + totime = of->senttime; + toframe = cl.ackedmovesequence+i;//qw debug - if (cmdto->senttime >= simtime) - break; - from = to; + if (i == 1)//I've no idea how else to propogate event state from one frame to the next + tostate = &cl.inframes[(fromframe+i) & UPDATE_MASK].playerstate[pv->playernum]; + else + tostate = &framebuf[i&1]; + + Con_DPrintf(" pred %i: %f-%f\n", cl.ackedmovesequence+i, fromtime, totime); + CL_PredictUsercmd (seat, pv->viewentity, fromstate, tostate, cmdto); + } + + if (simtime > totime) + { + //extrapolate X extra seconds + float msec; + usercmd_t indcmd; + + msec = ((simtime - totime) * 1000); + if (msec >= 1) + { cmdfrom = cmdto; + fromstate = tostate; + fromtime = totime; + fromframe = toframe; + + tostate = &framebuf[i++&1]; + if (independantphysics[seat].msec && !cls.demoplayback) + indcmd = independantphysics[seat]; + else + indcmd = *cmdto; + cmdto = &indcmd; + totime = simtime; + toframe+=1; + + cmdto->msec = bound(0, msec, 250); + + Con_DPrintf(" extrap %i: %f-%f\n", toframe, fromtime, simtime); + CL_PredictUsercmd (seat, pv->viewentity, fromstate, tostate, cmdto); } } - - if (simtime > cmdto->senttime) - { - float msec; - cmdfrom = cmdto; - from = to; - to = &indstate; - cmdto = &indcmd; - if (independantphysics[seat].msec && !cls.demoplayback) - cmdto->cmd[seat] = independantphysics[seat]; - else - cmdto->cmd[seat] = cmdfrom->cmd[seat]; - cmdto->senttime = simtime; - msec = ((cmdto->senttime - cmdfrom->senttime) * 1000) + 0.5; - cmdto->cmd[seat].msec = bound(0, msec, 250); - - CL_PredictUsercmd (seat, pv->playernum+1, &from->playerstate[pv->playernum] - , &to->playerstate[pv->playernum], &cmdto->cmd[seat]); - } pv->onground = pmove.onground; - pv->pmovetype = to->playerstate[pv->playernum].pm_type; - stepheight = to->playerstate[pv->playernum].origin[2] - from->playerstate[pv->playernum].origin[2]; - } - - //backdate it if our simulation time is in the past. this will happen on localhost, but not on 300-ping servers. - for (i = 0; cmdfrom->senttime > simtime && i < 16; i++) - { - to = from; - cmdto = cmdfrom; - from = &cl.inframes[(cl.validsequence-i) & UPDATE_MASK]; - cmdfrom = &cl.outframes[(cl.ackedmovesequence-i) & UPDATE_MASK]; + pv->pmovetype = tostate->pm_type; } pmove.numphysent = oldphysent; -// Con_Printf("%f %f %f\n", cmdfrom->senttime, simtime, cmdto->senttime); - if (cmdto->senttime == cmdfrom->senttime) + if (totime == fromtime) { - VectorCopy (to->playerstate[pv->playernum].velocity, pv->simvel); - VectorCopy (to->playerstate[pv->playernum].origin, pv->simorg); + VectorCopy (tostate->velocity, pv->simvel); + VectorCopy (tostate->origin, pv->simorg); +Con_DPrintf("%f %f %f\n", fromtime, simtime, totime); } else { - int pnum = pv->playernum; + vec3_t move; // now interpolate some fraction of the final frame - f = (simtime - cmdfrom->senttime) / (cmdto->senttime - cmdfrom->senttime); + f = (simtime - fromtime) / (totime - fromtime); if (f < 0) f = 0; if (f > 1) f = 1; - - for (i=0 ; i<3 ; i++) - if ( fabs(from->playerstate[pnum].origin[i] - to->playerstate[pnum].origin[i]) > 128) - { // teleported, so don't lerp - VectorCopy (to->playerstate[pnum].velocity, pv->simvel); - VectorCopy (to->playerstate[pnum].origin, pv->simorg); - goto out; - } - - for (i=0 ; i<3 ; i++) +Con_DPrintf("%i:%f %f %i:%f (%f)\n", fromframe, fromtime, simtime, toframe, totime, f); + VectorSubtract(tostate->origin, fromstate->origin, move); + if (DotProduct(move, move) > 128*128) { - pv->simorg[i] = (1-f)*from->playerstate[pnum].origin[i] + f*to->playerstate[pnum].origin[i]; - pv->simvel[i] = (1-f)*from->playerstate[pnum].velocity[i] + f*to->playerstate[pnum].velocity[i]; + // teleported, so don't lerp + VectorCopy (tostate->velocity, pv->simvel); + VectorCopy (tostate->origin, pv->simorg); + } + else + { + for (i=0 ; i<3 ; i++) + { + pv->simorg[i] = (1-f)*fromstate->origin[i] + f*tostate->origin[i]; + pv->simvel[i] = (1-f)*fromstate->velocity[i] + f*tostate->velocity[i]; -/* if (cl.spectator && Cam_TrackNum(vnum) >= 0) - pv->simangles[i] = LerpAngles16(from->playerstate[pnum].command.angles[i], to->playerstate[pnum].command.angles[i], f) * (360.0/65535); - else if (cls.demoplayback == DPB_QUAKEWORLD) - pv->simangles[i] = LerpAngles16(cmdfrom->cmd[vnum].angles[i], cmdto->cmd[vnum].angles[i], f) * (360.0/65535); -*/ } +/* if (cl.spectator && Cam_TrackNum(vnum) >= 0) + pv->simangles[i] = LerpAngles16(from->playerstate[pnum].command.angles[i], to->playerstate[pnum].command.angles[i], f) * (360.0/65535); + else if (cls.demoplayback == DPB_QUAKEWORLD) + pv->simangles[i] = LerpAngles16(cmdfrom->cmd[vnum].angles[i], cmdto->cmd[vnum].angles[i], f) * (360.0/65535); +*/ } + } CL_CatagorizePosition(pv); } - if (pv->nolocalplayer && cl.maxlerpents > pv->playernum+1) + if (le) { //keep the entity tracking the prediction position, so mirrors don't go all weird - VectorCopy(to->playerstate[pv->playernum].origin, cl.lerpents[pv->playernum+1].origin); - VectorScale(pv->simangles, 1, cl.lerpents[pv->playernum+1].angles); - cl.lerpents[pv->playernum+1].angles[0] *= -0.333; + VectorCopy(tostate->origin, le->origin); + VectorScale(pv->simangles, 1, le->angles); + le->angles[0] *= -0.333; } - if (cls.demoplayback) - CL_LerpMove (seat, cmdto->senttime); +// if (cls.demoplayback) +// CL_LerpMove (seat, totime); -out: - CL_CalcCrouch (pv, stepheight); + CL_CalcCrouch (pv); pv->waterlevel = pmove.waterlevel; VectorCopy(pmove.gravitydir, pv->gravitydir); } @@ -1267,4 +1180,7 @@ void CL_InitPrediction (void) extern char cl_predictiongroup[]; Cvar_Register (&cl_pushlatency, cl_predictiongroup); Cvar_Register (&cl_nopred, cl_predictiongroup); + Cvar_Register (&cl_predict_extrapolate, cl_predictiongroup); + Cvar_Register (&cl_predict_timenudge, cl_predictiongroup); + Cvar_Register (&cl_predict_smooth, cl_predictiongroup); } diff --git a/engine/client/client.h b/engine/client/client.h index 351194432..d85a4eec3 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -186,11 +186,12 @@ typedef struct player_info_s typedef struct { double senttime; // time cmd was sent off + float latency; // the time the packet was acked. -1=choked, -2=dropped, -3=never sent, -4=reply came back invalid // generated on client side usercmd_t cmd[MAX_SPLITS]; // cmd that generated the frame - int cmd_sequence; - int server_message_num; + int cmd_sequence; //the outgoing move sequence. if not equal to expected, that index was stale and is no longer valid + int server_message_num; //the inbound frame that was valid when this command was generated int server_time; int client_time; @@ -200,9 +201,10 @@ typedef struct { //this is the sequence we requested for this frame. int delta_sequence; // sequence number to delta from, -1 = full update - float latency; // received from server + int frameid; //the sequence number of the frame, so we can easily detect which frames are valid without poking all in advance, etc + int ackframe; //the outgoing sequence this frame acked (for prediction backlerping). double receivedtime; // time message was received, or -1 player_state_t playerstate[MAX_CLIENTS+MAX_SPLITS]; // message received that reflects performing // the usercmd diff --git a/engine/client/clq2_ents.c b/engine/client/clq2_ents.c index a3e63ad68..12f1bc2ac 100644 --- a/engine/client/clq2_ents.c +++ b/engine/client/clq2_ents.c @@ -1015,7 +1015,7 @@ void CLQ2_ParseFrame (void) i = MSG_ReadByte (); for (j=0 ; jstring, desc); + else + Con_Footerf(false, "%s %s", cmd, var->string); + } else - Con_Footerf(false, ""); + { + if (desc) + Con_Footerf(false, "%s: %s", cmd, desc); + else + Con_Footerf(false, ""); + } } else { diff --git a/engine/client/m_multi.c b/engine/client/m_multi.c index bf250320c..8f0d43ab6 100644 --- a/engine/client/m_multi.c +++ b/engine/client/m_multi.c @@ -908,8 +908,14 @@ void M_Menu_Network_f (void) NULL }; static const char *splitvalues[] = {"0", "1", "2", "3", NULL}; + static const char *smoothingopts[] = { + "Lower Latency", + "Smoother", + "Smooth Demos Only", + }; + static const char *smoothingvalues[] = {"0", "1", "2", NULL}; extern cvar_t cl_download_csprogs, cl_download_redirection, requiredownloads, cl_solid_players; - extern cvar_t cl_splitscreen, cl_predict_players; + extern cvar_t cl_splitscreen, cl_predict_players, cl_predict_smooth, cl_predict_extrapolate; menu_t *menu; int y; menubulk_t bulk[] = @@ -924,6 +930,8 @@ void M_Menu_Network_f (void) MB_CHECKBOXCVARTIP("Redirect Download", cl_download_redirection, 0, "Whether the client will ignore download redirection from servers"), MB_CHECKBOXCVARTIP("Download CSQC", cl_download_csprogs, 0, "Whether to allow the client to download CSQC (client-side QuakeC) progs from servers"), MB_SPACING(4), + MB_COMBOCVAR("Network Smoothing", cl_predict_smooth, smoothingopts, smoothingvalues, "Smoother gameplay comes at the cost of higher latency. Which do you favour?"), + MB_CHECKBOXCVARTIP("Extrapolate Prediction", cl_predict_extrapolate, 0, "Extrapolate local player movement beyond the frames already sent to the server"), MB_CHECKBOXCVARTIP("Predict Other Players", cl_predict_players, 0, "Toggle player prediction"), MB_CHECKBOXCVARTIP("Solid Players", cl_solid_players, 0, "When running/clipping into other players, ON make it appear they are solid, OFF will make it appear like running into a marshmellon."), MB_COMBOCVAR("Split-screen", cl_splitscreen, splitopts, splitvalues, "Enables split screen with a number of clients. This feature requires server support."), diff --git a/engine/client/m_options.c b/engine/client/m_options.c index ed25b786a..54055afa2 100644 --- a/engine/client/m_options.c +++ b/engine/client/m_options.c @@ -233,17 +233,74 @@ void M_Menu_Audio_Speakers_f (void) menu->selecteditem = NULL; } +struct audiomenuinfo +{ + char **outdevnames; + char **outdevdescs; + char **capdevnames; + char **capdevdescs; +}; +void M_Menu_Audio_Remove(menu_t *menu) +{ + int i; + struct audiomenuinfo *info = menu->data; + for (i = 0; info->outdevnames[i]; i++) + Z_Free(info->outdevnames[i]); + for (i = 0; info->outdevdescs[i]; i++) + Z_Free(info->outdevdescs[i]); + for (i = 0; info->capdevnames[i]; i++) + Z_Free(info->capdevnames[i]); + for (i = 0; info->capdevdescs[i]; i++) + Z_Free(info->capdevdescs[i]); +} +struct audiomenuinfo *M_Menu_Audio_Setup(menu_t *menu) +{ +#ifdef VOICECHAT + extern cvar_t snd_voip_capturedevice_opts; +#endif + extern cvar_t snd_device_opts; + int pairs, i; + struct audiomenuinfo *info = menu->data; + menu->remove = M_Menu_Audio_Remove; + + Cmd_TokenizeString(snd_device_opts.string?snd_device_opts.string:"", false, false); + pairs = Cmd_Argc()/2; + info->outdevnames = BZ_Malloc((pairs+1)*sizeof(char*)); + info->outdevdescs = BZ_Malloc((pairs+1)*sizeof(char*)); + for (i = 0; i < pairs; i++) + { + info->outdevnames[i] = Z_StrDup(Cmd_Argv(i*2+0)); + info->outdevdescs[i] = Z_StrDup(Cmd_Argv(i*2+1)); + } + info->outdevnames[i] = NULL; + info->outdevdescs[i] = NULL; +#ifdef VOICECHAT + Cmd_TokenizeString(snd_voip_capturedevice_opts.string?snd_voip_capturedevice_opts.string:"", false, false); + pairs = Cmd_Argc()/2; + info->capdevnames = BZ_Malloc((pairs+1)*sizeof(char*)); + info->capdevdescs = BZ_Malloc((pairs+1)*sizeof(char*)); + for (i = 0; i < pairs; i++) + { + info->capdevnames[i] = Z_StrDup(Cmd_Argv(i*2+0)); + info->capdevdescs[i] = Z_StrDup(Cmd_Argv(i*2+1)); + } + info->capdevnames[i] = NULL; + info->capdevdescs[i] = NULL; +#endif + return info; +} + menucombo_t *MC_AddCvarCombo(menu_t *menu, int x, int y, const char *caption, cvar_t *cvar, const char **ops, const char **values); void M_Menu_Audio_f (void) { int y; - menu_t *menu = M_Options_Title(&y, 0); + menu_t *menu = M_Options_Title(&y, sizeof(struct audiomenuinfo)); + struct audiomenuinfo *info = M_Menu_Audio_Setup(menu); extern cvar_t nosound, snd_leftisright, snd_device, snd_khz, snd_speakers, ambient_level, bgmvolume, snd_playersoundvolume, ambient_fade, cl_staticsounds, snd_inactive, _snd_mixahead; // extern cvar_t snd_noextraupdate, snd_eax, precache; #ifdef VOICECHAT - extern cvar_t cl_voip_play, cl_voip_send, cl_voip_test, cl_voip_micamp, cl_voip_vad_threshhold, cl_voip_ducking, cl_voip_noisefilter, cl_voip_codec; + extern cvar_t snd_voip_capturedevice, snd_voip_play, snd_voip_send, snd_voip_test, snd_voip_micamp, snd_voip_vad_threshhold, snd_voip_ducking, snd_voip_noisefilter, snd_voip_codec; #endif - extern char **soundoutdevicecodes, **soundoutdevicenames; static const char *soundqualityoptions[] = { "11025 Hz", @@ -313,7 +370,7 @@ void M_Menu_Audio_f (void) MB_SPACING(8), MB_CONSOLECMD("Restart Sound", "snd_restart\n", "Restart audio systems and apply set options."), MB_SPACING(4), - MB_COMBOCVAR("Output Device", snd_device, soundoutdevicenames, soundoutdevicecodes, NULL), + MB_COMBOCVAR("Output Device", snd_device, info->outdevdescs, info->outdevnames, NULL), MB_SLIDER("Volume", volume, 0, 1, 0.1, NULL), MB_COMBOCVAR("Speaker Setup", snd_speakers, speakeroptions, speakervalues, NULL), MB_COMBOCVAR("Frequency", snd_khz, soundqualityoptions, soundqualityvalues, NULL), @@ -338,15 +395,15 @@ void M_Menu_Audio_f (void) #ifdef VOICECHAT MB_REDTEXT("Voice Options", false), MB_TEXT("\x80\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x82", false), - MB_SLIDER("Voice Volume", cl_voip_play, 0, 2, 0.1, NULL), -// MB_COMBOCVAR("Microphone Device", snd_micdevice, inputdeviceoptions, inputdevicevalue, NULL), - MB_CHECKBOXCVAR("Microphone Test", cl_voip_test, 0), - MB_SLIDER("Microphone Volume", cl_voip_micamp, 0, 2, 0.1, NULL), - MB_COMBOCVAR("Activation Mode", cl_voip_send, voipsendoptions, voipsendvalue, NULL), - MB_SLIDER("Act. Threshhold", cl_voip_vad_threshhold, 0, 30, 1, NULL), - MB_CHECKBOXCVAR("Audio Ducking", cl_voip_ducking, 0), - MB_CHECKBOXCVAR("Noise Cancelation", cl_voip_noisefilter, 0), - MB_COMBOCVAR("Codec", cl_voip_codec, voipcodecoptions, voipcodecvalue, NULL), + MB_COMBOCVAR("Microphone Device", snd_voip_capturedevice, info->capdevdescs, info->capdevnames, NULL), + MB_SLIDER("Voice Volume", snd_voip_play, 0, 2, 0.1, NULL), + MB_CHECKBOXCVAR("Microphone Test", snd_voip_test, 0), + MB_SLIDER("Microphone Volume", snd_voip_micamp, 0, 2, 0.1, NULL), + MB_COMBOCVAR("Activation Mode", snd_voip_send, voipsendoptions, voipsendvalue, NULL), + MB_SLIDER("Act. Threshhold", snd_voip_vad_threshhold, 0, 30, 1, NULL), + MB_CHECKBOXCVAR("Audio Ducking", snd_voip_ducking, 0), + MB_CHECKBOXCVAR("Noise Cancelation", snd_voip_noisefilter, 0), + MB_COMBOCVAR("Codec", snd_voip_codec, voipcodecoptions, voipcodecvalue, NULL), #endif //MB_CONSOLECMD("Speaker Test", "menu_speakers\n", "Test speaker setup output."), diff --git a/engine/client/merged.h b/engine/client/merged.h index 10eb7dcf4..0906788df 100644 --- a/engine/client/merged.h +++ b/engine/client/merged.h @@ -223,9 +223,9 @@ typedef struct typedef struct texnums_s { texid_t base; texid_t bump; + texid_t specular; texid_t upperoverlay; texid_t loweroverlay; - texid_t specular; texid_t fullbright; } texnums_t; typedef enum uploadfmt diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index 87c5e7ef3..15401bca6 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -71,6 +71,7 @@ int csqc_playerseat; //can be negative. static playerview_t *csqc_playerview; static qboolean csqc_isdarkplaces; static qboolean csqc_singlecheats; /*single player or cheats active, allowing custom addons*/ +static qboolean csqc_mayread; //csqc is allowed to ReadByte(); static char csqc_printbuffer[8192]; @@ -194,6 +195,8 @@ static csqcglobals_t csqcg; playerview_t csqc_nullview; +void VARGS CSQC_Abort (char *format, ...); //an error occured. + //fixme: we should be using entity numbers, not view numbers. static void CSQC_ChangeLocalPlayer(int seat) { @@ -1935,6 +1938,12 @@ static void QCBUILTIN PF_cs_ModelnameForIndex(pubprogfuncs_t *prinst, struct glo static void QCBUILTIN PF_ReadByte(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadByte is not valid at this time"); + G_FLOAT(OFS_RETURN) = -1; + return; + } if (csqc_fakereadbyte != -1) { G_FLOAT(OFS_RETURN) = csqc_fakereadbyte; @@ -1948,45 +1957,93 @@ static void QCBUILTIN PF_ReadByte(pubprogfuncs_t *prinst, struct globalvars_s *p static void QCBUILTIN PF_ReadChar(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadChar is not valid at this time"); + G_FLOAT(OFS_RETURN) = -1; + return; + } G_FLOAT(OFS_RETURN) = MSG_ReadChar(); } static void QCBUILTIN PF_ReadShort(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadShort is not valid at this time"); + G_FLOAT(OFS_RETURN) = -1; + return; + } G_FLOAT(OFS_RETURN) = MSG_ReadShort(); } static void QCBUILTIN PF_ReadEntityNum(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { unsigned int val; + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadEntityNum is not valid at this time"); + G_FLOAT(OFS_RETURN) = -1; + return; + } val = MSGCL_ReadEntity(); G_FLOAT(OFS_RETURN) = val; } static void QCBUILTIN PF_ReadLong(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadLong is not valid at this time"); + G_FLOAT(OFS_RETURN) = -1; + return; + } G_FLOAT(OFS_RETURN) = MSG_ReadLong(); } static void QCBUILTIN PF_ReadCoord(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadCoord is not valid at this time"); + G_FLOAT(OFS_RETURN) = -1; + return; + } G_FLOAT(OFS_RETURN) = MSG_ReadCoord(); } static void QCBUILTIN PF_ReadFloat(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadFloat is not valid at this time"); + G_FLOAT(OFS_RETURN) = -1; + return; + } G_FLOAT(OFS_RETURN) = MSG_ReadFloat(); } static void QCBUILTIN PF_ReadString(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { - char *read = MSG_ReadString(); - + char *read; + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadString is not valid at this time"); + G_INT(OFS_RETURN) = 0; + return; + } + read = MSG_ReadString(); RETURN_TSTRING(read); } static void QCBUILTIN PF_ReadAngle(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals) { + if (!csqc_mayread) + { + CSQC_Abort("PF_ReadAngle is not valid at this time"); + G_FLOAT(OFS_RETURN) = -1; + return; + } G_FLOAT(OFS_RETURN) = MSG_ReadAngle(); } @@ -2248,16 +2305,6 @@ static void QCBUILTIN PF_cs_getinputstate (pubprogfuncs_t *prinst, struct global G_FLOAT(OFS_RETURN) = false; return; } - if (f > cl.movesequence) - { - G_FLOAT(OFS_RETURN) = false; - return; - } - if (f < cl.movesequence - UPDATE_MASK || f < 0) - { - G_FLOAT(OFS_RETURN) = false; - return; - } /*outgoing_sequence says how many packets have actually been sent, but there's an extra pending packet which has not been sent yet - be warned though, its data will change in the coming frames*/ if (f == cl.movesequence) @@ -2265,9 +2312,17 @@ static void QCBUILTIN PF_cs_getinputstate (pubprogfuncs_t *prinst, struct global cmd = &independantphysics[seat]; for (f=0 ; f<3 ; f++) cmd->angles[f] = ((int)(csqc_playerview->viewangles[f]*65536.0/360)&65535); + //FIXME: msec probably isn't right } else + { + if (cl.outframes[f&UPDATE_MASK].cmd_sequence != f) + { + G_FLOAT(OFS_RETURN) = false; + return; + } cmd = &cl.outframes[f&UPDATE_MASK].cmd[seat]; + } cs_set_input_state(cmd); @@ -5165,6 +5220,7 @@ qboolean CSQC_Init (qboolean anycsqc, qboolean csdatenabled, unsigned int checks csprogs_checksum = checksum; csqc_usinglistener = false; + csqc_mayread = false; csqc_singlecheats = cls.demoplayback; if (atoi(Info_ValueForKey(cl.serverinfo, "*cheats"))) @@ -5857,7 +5913,9 @@ qboolean CSQC_ParseTempEntity(unsigned char firstbyte) csqc_fakereadbyte = firstbyte; pr_globals = PR_globals(csqcprogs, PR_CURRENT); + csqc_mayread = true; PR_ExecuteProgram (csqcprogs, csqcg.parse_tempentity); + csqc_mayread = false; csqc_fakereadbyte = -1; return !!G_FLOAT(OFS_RETURN); } @@ -5877,6 +5935,7 @@ qboolean CSQC_ParseGamePacket(void) return false; } + csqc_mayread = true; PR_ExecuteProgram (csqcprogs, parsefnc); if (msg_readcount != start + len) @@ -5892,8 +5951,10 @@ qboolean CSQC_ParseGamePacket(void) Host_EndGame("CSQC not running or is unable to parse events.\n"); return false; } + csqc_mayread = true; PR_ExecuteProgram (csqcprogs, parsefnc); } + csqc_mayread = false; return true; } @@ -6193,7 +6254,9 @@ void CSQC_ParseEntities(void) } *csqcg.self = EDICT_TO_PROG(csqcprogs, (void*)ent); + csqc_mayread = true; PR_ExecuteProgram(csqcprogs, csqcg.ent_update); + csqc_mayread = false; if (cl.csqcdebug) { diff --git a/engine/client/r_surf.c b/engine/client/r_surf.c index 835563f63..96d4b2aa6 100644 --- a/engine/client/r_surf.c +++ b/engine/client/r_surf.c @@ -2033,6 +2033,9 @@ void Surf_SetupFrame(void) case Q1CONTENTS_SOLID: r_viewcontents |= FTECONTENTS_SOLID; break; + case Q1CONTENTS_LADDER: + r_viewcontents |= FTECONTENTS_LADDER; + break; } } } diff --git a/engine/client/renderer.c b/engine/client/renderer.c index 78c08cf05..5e85bb782 100644 --- a/engine/client/renderer.c +++ b/engine/client/renderer.c @@ -45,7 +45,7 @@ void GL_Texturemode2d_Callback (struct cvar_s *var, char *oldvalue); void GL_Texture_Anisotropic_Filtering_Callback (struct cvar_s *var, char *oldvalue); #endif -cvar_t _vid_wait_override = CVARAF ("vid_wait", "1", +cvar_t _vid_wait_override = CVARAF ("vid_wait", "0", "_vid_wait_override", CVAR_ARCHIVE); cvar_t _windowed_mouse = CVARF ("_windowed_mouse","1", diff --git a/engine/client/sbar.c b/engine/client/sbar.c index f636f2fd4..2c5ec0964 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -2285,9 +2285,9 @@ static void Sbar_Voice(int y) { #ifdef VOICECHAT int loudness; - if (!cl_voip_showmeter.ival) + if (!snd_voip_showmeter.ival) return; - loudness = S_Voip_Loudness(cl_voip_showmeter.ival==2); + loudness = S_Voip_Loudness(snd_voip_showmeter.ival==2); if (loudness >= 0) { int w; diff --git a/engine/client/snd_directx.c b/engine/client/snd_directx.c index 5f4b5e4d1..ab4587a07 100644 --- a/engine/client/snd_directx.c +++ b/engine/client/snd_directx.c @@ -42,10 +42,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define iDirectSoundEnumerate(a,b,c) pDirectSoundEnumerate(a,b) HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter); +HRESULT (WINAPI *pDirectSoundEnumerate)(LPDSENUMCALLBACKA lpCallback, LPVOID lpContext); #if defined(VOICECHAT) HRESULT (WINAPI *pDirectSoundCaptureCreate)(GUID FAR *lpGUID, LPDIRECTSOUNDCAPTURE FAR *lplpDS, IUnknown FAR *pUnkOuter); +HRESULT (WINAPI *pDirectSoundCaptureEnumerate)(LPDSENUMCALLBACK lpDSEnumCallback, LPVOID lpContext); #endif -HRESULT (WINAPI *pDirectSoundEnumerate)(LPDSENUMCALLBACKA lpCallback, LPVOID lpContext ); // 64K is > 1 second at 16-bit, 22050 Hz #define WAV_BUFFERS 64 @@ -511,25 +512,21 @@ static void DSOUND_Submit(soundcardinfo_t *sc, int start, int end) static qboolean DSOUND_InitOutputLibrary(void) { if (!hInstDS) - { hInstDS = LoadLibrary("dsound.dll"); - - if (hInstDS == NULL) - { - Con_SafePrintf ("Couldn't load dsound.dll\n"); - return false; - } - - pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate"); - - if (!pDirectSoundCreate) - { - Con_SafePrintf ("Couldn't get DS proc addr\n"); - return false; - } - - pDirectSoundEnumerate = (void *)GetProcAddress(hInstDS,"DirectSoundEnumerateA"); + if (!hInstDS) + { + Con_SafePrintf ("Couldn't load dsound.dll\n"); + return false; } + if (!pDirectSoundCreate) + pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate"); + if (!pDirectSoundCreate) + { + Con_SafePrintf ("Couldn't get DS proc addr\n"); + return false; + } + if (!pDirectSoundEnumerate) + pDirectSoundEnumerate = (void *)GetProcAddress(hInstDS,"DirectSoundEnumerateA"); return true; } /* @@ -1027,16 +1024,51 @@ typedef struct LPDIRECTSOUNDCAPTUREBUFFER DSCaptureBuffer; long lastreadpos; } dsndcapture_t; -const long bufferbytes = 1024*1024; +static const long bufferbytes = 1024*1024; -const long inputwidth = 2; +static const long inputwidth = 2; -void *DSOUND_Capture_Init (int rate) +static BOOL CALLBACK dsound_capture_enumerate_ds(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + char guidbuf[128]; + wchar_t mssuck[128]; + void (QDECL *callback) (const char *drivername, const char *devicecode, const char *readablename) = lpContext; + + if (lpGuid == NULL) //we don't care about the (dupe) default device + return TRUE; + + StringFromGUID2(lpGuid, mssuck, sizeof(mssuck)/sizeof(mssuck[0])); + wcstombs(guidbuf, mssuck, sizeof(guidbuf)); + callback(SDRVNAME, guidbuf, lpcstrDescription); + return TRUE; +} + +static qboolean QDECL DSOUND_Capture_Enumerate (void (QDECL *callback) (const char *drivername, const char *devicecode, const char *readablename)) +{ + if (!pDirectSoundCaptureEnumerate) + { + /*make sure its loaded*/ + if (!hInstDS) + hInstDS = LoadLibrary("dsound.dll"); + if (hInstDS) + pDirectSoundCaptureEnumerate = (void *)GetProcAddress(hInstDS,"DirectSoundCaptureEnumerateA"); + + if (!pDirectSoundCaptureEnumerate) + return false; + } + + if (!FAILED(pDirectSoundCaptureEnumerate(dsound_capture_enumerate_ds, callback))) + return true; + return false; +} + +static void *QDECL DSOUND_Capture_Init (int rate, char *device) { dsndcapture_t *result; DSCBUFFERDESC bufdesc; WAVEFORMATEX wfxFormat; + GUID *dsguid, guid; wfxFormat.wFormatTag = WAVE_FORMAT_PCM; wfxFormat.nChannels = 1; @@ -1052,6 +1084,19 @@ void *DSOUND_Capture_Init (int rate) bufdesc.dwReserved = 0; bufdesc.lpwfxFormat = &wfxFormat; + if (device && *device) + { + wchar_t mssuck[128]; + mbstowcs(mssuck, device, sizeof(mssuck)/sizeof(mssuck[0])-1); + CLSIDFromString(mssuck, &guid); + dsguid = &guid; + } + else + { + memset(&guid, 0, sizeof(GUID)); + dsguid = NULL; + } + /*probably already inited*/ if (!hInstDS) { @@ -1073,12 +1118,10 @@ void *DSOUND_Capture_Init (int rate) Con_SafePrintf ("Couldn't get DS proc addr\n"); return NULL; } - -// pDirectSoundCaptureEnumerate = (void *)GetProcAddress(hInstDS,"DirectSoundCaptureEnumerateA"); } result = Z_Malloc(sizeof(*result)); - if (!FAILED(pDirectSoundCaptureCreate(NULL, &result->DSCapture, NULL))) + if (!FAILED(pDirectSoundCaptureCreate(dsguid, &result->DSCapture, NULL))) { if (!FAILED(IDirectSoundCapture_CreateCaptureBuffer(result->DSCapture, &bufdesc, &result->DSCaptureBuffer, NULL))) { @@ -1091,7 +1134,7 @@ void *DSOUND_Capture_Init (int rate) return NULL; } -void DSOUND_Capture_Start(void *ctx) +static void QDECL DSOUND_Capture_Start(void *ctx) { DWORD capturePos; dsndcapture_t *c = ctx; @@ -1101,13 +1144,13 @@ void DSOUND_Capture_Start(void *ctx) IDirectSoundCaptureBuffer_GetCurrentPosition(c->DSCaptureBuffer, &capturePos, &c->lastreadpos); } -void DSOUND_Capture_Stop(void *ctx) +static void QDECL DSOUND_Capture_Stop(void *ctx) { dsndcapture_t *c = ctx; IDirectSoundCaptureBuffer_Stop(c->DSCaptureBuffer); } -void DSOUND_Capture_Shutdown(void *ctx) +static void QDECL DSOUND_Capture_Shutdown(void *ctx) { dsndcapture_t *c = ctx; if (c->DSCaptureBuffer) @@ -1123,7 +1166,7 @@ void DSOUND_Capture_Shutdown(void *ctx) } /*minsamples is a hint*/ -unsigned int DSOUND_Capture_Update(void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes) +static unsigned int QDECL DSOUND_Capture_Update(void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes) { dsndcapture_t *c = ctx; HRESULT hr; @@ -1176,6 +1219,9 @@ unsigned int DSOUND_Capture_Update(void *ctx, unsigned char *buffer, unsigned in } snd_capture_driver_t DSOUND_Capture = { + 1, + SDRVNAME, + DSOUND_Capture_Enumerate, DSOUND_Capture_Init, DSOUND_Capture_Start, DSOUND_Capture_Update, diff --git a/engine/client/snd_dma.c b/engine/client/snd_dma.c index b664b7695..97e428d38 100644 --- a/engine/client/snd_dma.c +++ b/engine/client/snd_dma.c @@ -112,24 +112,28 @@ cvar_t snd_linearresample_stream = CVARAF( "s_linearresample_stream", "0", cvar_t snd_mixerthread = CVARAD( "s_mixerthread", "1", "snd_mixerthread", "When enabled sound mixing will be run on a separate thread. Currently supported only by directsound. Other drivers may unconditionally thread audio. Set to 0 only if you have issues."); -cvar_t snd_device = CVARAF( "s_device", "", +cvar_t snd_device = CVARAF( "s_device", "", "snd_device", CVAR_ARCHIVE); +cvar_t snd_device_opts = CVARFD( "_s_device_opts", "", CVAR_NOSET, "The possible audio output devices, in \"value\" \"description\" pairs."); #ifdef VOICECHAT static void S_Voip_Play_Callback(cvar_t *var, char *oldval); -cvar_t cl_voip_send = CVARD("cl_voip_send", "0", "Sends voice-over-ip data to the server whenever it is set"); -cvar_t cl_voip_test = CVARD("cl_voip_test", "0", "If 1, enables you to hear your own voice directly, bypassing the server and thus without networking latency, but is fine for checking audio levels. Note that sv_voip_echo can be set if you want to include latency and packetloss considerations, but setting that cvar requires server admin access and is thus much harder to use."); -cvar_t cl_voip_vad_threshhold = CVARD("cl_voip_vad_threshhold", "15", "This is the threshhold for voice-activation-detection when sending voip data"); -cvar_t cl_voip_vad_delay = CVARD("cl_voip_vad_delay", "0.3", "Keeps sending voice data for this many seconds after voice activation would normally stop"); -cvar_t cl_voip_capturingvol = CVARAFD("cl_voip_capturingvol", "0.5", NULL, CVAR_ARCHIVE, "Volume multiplier applied while capturing, to avoid your audio from being heard by others. Does not affect game volume when other speak (minimum of cl_voip_capturingvol and cl_voip_ducking is used)."); -cvar_t cl_voip_showmeter = CVARAFD("cl_voip_showmeter", "1", NULL, CVAR_ARCHIVE, "Shows your speech volume above the standard hud. 0=hide, 1=show when transmitting, 2=ignore voice-activation disable"); +cvar_t snd_voip_capturedevice = CVARF("cl_voip_capturedevice", "", CVAR_ARCHIVE); +cvar_t snd_voip_capturedevice_opts = CVARFD("_cl_voip_capturedevice_opts", "", CVAR_NOSET, "The possible audio capture devices, in \"value\" \"description\" pairs."); +int voipbutton; //+voip, no longer part of cl_voip_send to avoid it getting saved +cvar_t snd_voip_send = CVARFD("cl_voip_send", "0", CVAR_ARCHIVE, "Sends voice-over-ip data to the server whenever it is set.\n0: only send voice if +voip is pressed.\n1: voice activation.\n2: constantly send.\n+4: Do not send to game, only to rtp sessions."); +cvar_t snd_voip_test = CVARD("cl_voip_test", "0", "If 1, enables you to hear your own voice directly, bypassing the server and thus without networking latency, but is fine for checking audio levels. Note that sv_voip_echo can be set if you want to include latency and packetloss considerations, but setting that cvar requires server admin access and is thus much harder to use."); +cvar_t snd_voip_vad_threshhold = CVARD("cl_voip_vad_threshhold", "15", "This is the threshhold for voice-activation-detection when sending voip data"); +cvar_t snd_voip_vad_delay = CVARD("cl_voip_vad_delay", "0.3", "Keeps sending voice data for this many seconds after voice activation would normally stop"); +cvar_t snd_voip_capturingvol = CVARAFD("cl_voip_capturingvol", "0.5", NULL, CVAR_ARCHIVE, "Volume multiplier applied while capturing, to avoid your audio from being heard by others. Does not affect game volume when other speak (minimum of cl_voip_capturingvol and cl_voip_ducking is used)."); +cvar_t snd_voip_showmeter = CVARAFD("cl_voip_showmeter", "1", NULL, CVAR_ARCHIVE, "Shows your speech volume above the standard hud. 0=hide, 1=show when transmitting, 2=ignore voice-activation disable"); -cvar_t cl_voip_play = CVARAFDC("cl_voip_play", "1", NULL, CVAR_ARCHIVE, "Enables voip playback. Value is a volume scaler.", S_Voip_Play_Callback); -cvar_t cl_voip_ducking = CVARAFD("cl_voip_ducking", "0.5", NULL, CVAR_ARCHIVE, "Scales game audio by this much when someone is talking to you. Does not affect your speaker volume when you speak (minimum of cl_voip_capturingvol and cl_voip_ducking is used)."); -cvar_t cl_voip_micamp = CVARAFDC("cl_voip_micamp", "2", NULL, CVAR_ARCHIVE, "Amplifies your microphone when using voip.", 0); -cvar_t cl_voip_codec = CVARAFDC("cl_voip_codec", "0", NULL, CVAR_ARCHIVE, "0: speex. 1: raw. 2: opus.", 0); -cvar_t cl_voip_noisefilter = CVARAFDC("cl_voip_noisefilter", "1", NULL, CVAR_ARCHIVE, "Enable the use of the noise cancelation filter.", 0); -cvar_t cl_voip_autogain = CVARAFDC("cl_voip_autogain", "0", NULL, CVAR_ARCHIVE, "Attempts to normalize your voice levels to a standard level. Useful for lazy people, but interferes with voice activation levels.", 0); +cvar_t snd_voip_play = CVARAFDC("cl_voip_play", "1", NULL, CVAR_ARCHIVE, "Enables voip playback. Value is a volume scaler.", S_Voip_Play_Callback); +cvar_t snd_voip_ducking = CVARAFD("cl_voip_ducking", "0.5", NULL, CVAR_ARCHIVE, "Scales game audio by this much when someone is talking to you. Does not affect your speaker volume when you speak (minimum of cl_voip_capturingvol and cl_voip_ducking is used)."); +cvar_t snd_voip_micamp = CVARAFDC("cl_voip_micamp", "2", NULL, CVAR_ARCHIVE, "Amplifies your microphone when using voip.", 0); +cvar_t snd_voip_codec = CVARAFDC("cl_voip_codec", "0", NULL, CVAR_ARCHIVE, "0: speex. 1: raw. 2: opus.", 0); +cvar_t snd_voip_noisefilter = CVARAFDC("cl_voip_noisefilter", "1", NULL, CVAR_ARCHIVE, "Enable the use of the noise cancelation filter.", 0); +cvar_t snd_voip_autogain = CVARAFDC("cl_voip_autogain", "0", NULL, CVAR_ARCHIVE, "Attempts to normalize your voice levels to a standard level. Useful for lazy people, but interferes with voice activation levels.", 0); #endif extern vfsfile_t *rawwritefile; @@ -408,6 +412,13 @@ static dllfunction_t qspeexdspfuncs[] = snd_capture_driver_t DSOUND_Capture; snd_capture_driver_t OSS_Capture; +snd_capture_driver_t *capturedrivers[] = +{ + &DSOUND_Capture, + &OSS_Capture, + NULL +}; + static qboolean S_SpeexDSP_Init(void) { #ifndef SPEEX_STATIC @@ -615,7 +626,7 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un { if (decodesamps + s_voip.decframesize[sender] > sizeof(decodebuf)/sizeof(decodebuf[0])) { - S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); decodesamps = 0; } switch(codec) @@ -640,7 +651,7 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un { if (decodesamps + s_voip.decframesize[sender] >= sizeof(decodebuf)/sizeof(decodebuf[0])) { - S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); decodesamps = 0; } switch(codec) @@ -671,7 +682,7 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un seq++; if (decodesamps + s_voip.decframesize[sender] >= sizeof(decodebuf)/sizeof(decodebuf[0])) { - S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); decodesamps = 0; } } @@ -688,7 +699,7 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un len = bytes; if (decodesamps > 0) { - S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); decodesamps = 0; } r = qopus_decode(s_voip.decoder[sender], start, len, decodebuf + decodesamps, sizeof(decodebuf)/sizeof(decodebuf[0]) - decodesamps, false); @@ -712,7 +723,7 @@ void S_Voip_Decode(unsigned int sender, unsigned int codec, unsigned int gen, un Con_DPrintf("%i dropped audio frames\n", drops); if (decodesamps > 0) - S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, cl_voip_play.value); + S_RawAudio(sender, (qbyte*)decodebuf, s_voip.decsamplerate[sender], decodesamps, 1, 2, snd_voip_play.value); } #ifdef SUPPORT_ICE @@ -763,7 +774,7 @@ void S_Voip_Parse(void) gen &= 0x0f; seq = MSG_ReadByte(); bytes = MSG_ReadShort(); - if (bytes > sizeof(data) || cl_voip_play.value <= 0) + if (bytes > sizeof(data) || snd_voip_play.value <= 0) { MSG_ReadSkip(bytes); return; @@ -773,7 +784,7 @@ void S_Voip_Parse(void) sender %= MAX_CLIENTS; //if testing, don't get confused if the server is echoing voice too! - if (cl_voip_test.ival) + if (snd_voip_test.ival) if (sender == cl.playerview[0].playernum) return; @@ -800,6 +811,53 @@ static float S_Voip_Preprocess(short *start, unsigned int samples, float micamp) } return level; } +static void S_Voip_TryInitCaptureContext(char *driver, char *device, int rate) +{ + int i; + /*Add new drivers in order of priority*/ + for (i = 0; capturedrivers[i]; i++) + { + if (capturedrivers[i]->Init) + { + s_voip.cdriver = capturedrivers[i]; + break; + } + } + + /*no way to capture audio, give up early*/ + if (!s_voip.cdriver || !s_voip.cdriver->Init) + Con_Printf("No microphone interfaces supported\n"); + else + { + s_voip.cdriverctx = s_voip.cdriver->Init(s_voip.encsamplerate, NULL); + if (!s_voip.cdriverctx) + Con_Printf("No microphone detected\n"); + } +} + +static void S_Voip_InitCaptureContext(int rate) +{ + char *s; + + s_voip.cdriver = NULL; + s_voip.cdriverctx = NULL; + + for (s = snd_voip_capturedevice.string; ; ) + { + char *sep; + s = COM_Parse(s); + if (!*com_token) + break; + + sep = strchr(com_token, ':'); + if (sep) + *sep++ = 0; + S_Voip_TryInitCaptureContext(com_token, sep, rate); + } + if (!sndcardinfo) + S_Voip_TryInitCaptureContext(NULL, NULL, rate); +} + void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) { unsigned char outbuf[8192]; @@ -811,21 +869,21 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) unsigned int samps; float level; int len; - float micamp = cl_voip_micamp.value; + float micamp = snd_voip_micamp.value; qboolean voipsendenable = true; - int voipcodec = cl_voip_codec.ival; + int voipcodec = snd_voip_codec.ival; qboolean rtpstream = NET_RTP_Active(); if (buf) { /*if you're sending sound, you should be prepared to accept others yelling at you to shut up*/ - if (cl_voip_play.value <= 0) + if (snd_voip_play.value <= 0) voipsendenable = false; if (!(cls.fteprotocolextensions2 & PEXT2_VOICECHAT)) voipsendenable = false; } else - voipsendenable = cl_voip_test.ival; + voipsendenable = snd_voip_test.ival; if (rtpstream) { voipsendenable = true; @@ -835,7 +893,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } - voicevolumemod = s_voip.lastspoke_any > realtime?cl_voip_ducking.value:1; + voicevolumemod = s_voip.lastspoke_any > realtime?snd_voip_ducking.value:1; if (!voipsendenable || (voipcodec != s_voip.enccodec && s_voip.cdriver)) { @@ -871,7 +929,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) return; } - voipsendenable = cl_voip_send.ival>0; + voipsendenable = voipbutton || (snd_voip_send.ival>0); if (!s_voip.cdriver) { @@ -880,16 +938,6 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) if (!voipsendenable) return; - /*Add new drivers in order of priority*/ - if (!s_voip.cdriver || !s_voip.cdriver->Init) - s_voip.cdriver = &DSOUND_Capture; - if (!s_voip.cdriver || !s_voip.cdriver->Init) - s_voip.cdriver = &OSS_Capture; - - /*no way to capture audio, give up*/ - if (!s_voip.cdriver || !s_voip.cdriver->Init) - return; - /*see if we can init our encoding codec...*/ switch(voipcodec) { @@ -970,10 +1018,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } s_voip.enccodec = voipcodec; - s_voip.cdriverctx = s_voip.cdriver->Init(s_voip.encsamplerate); - - if (!s_voip.cdriverctx) - Con_Printf("No microphone detected\n"); + S_Voip_InitCaptureContext(s_voip.encsamplerate); //sets cdriver+cdriverctx } /*couldn't init a driver?*/ @@ -1022,7 +1067,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } if (s_voip.wantsend) - voicevolumemod = min(voicevolumemod, cl_voip_capturingvol.value); + voicevolumemod = min(voicevolumemod, snd_voip_capturingvol.value); s_voip.capturepos += s_voip.cdriver->Update(s_voip.cdriverctx, (unsigned char*)s_voip.capturebuf + s_voip.capturepos, s_voip.encframesize*2, sizeof(s_voip.capturebuf) - s_voip.capturepos); @@ -1042,9 +1087,9 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) { start = (short*)(s_voip.capturebuf + encpos); - if (cl_voip_noisefilter.ival || cl_voip_autogain.ival) + if (snd_voip_noisefilter.ival || snd_voip_autogain.ival) { - if (!s_voip.speexdsp.preproc || cl_voip_noisefilter.modified || cl_voip_noisefilter.modified || s_voip.speexdsp.curframesize != s_voip.encframesize || s_voip.speexdsp.cursamplerate != s_voip.encsamplerate) + if (!s_voip.speexdsp.preproc || snd_voip_noisefilter.modified || snd_voip_noisefilter.modified || s_voip.speexdsp.curframesize != s_voip.encframesize || s_voip.speexdsp.cursamplerate != s_voip.encsamplerate) { if (s_voip.speexdsp.preproc) qspeex_preprocess_state_destroy(s_voip.speexdsp.preproc); @@ -1053,9 +1098,9 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) { int i; s_voip.speexdsp.preproc = qspeex_preprocess_state_init(s_voip.encframesize, s_voip.encsamplerate); - i = cl_voip_noisefilter.ival; + i = snd_voip_noisefilter.ival; qspeex_preprocess_ctl(s_voip.speexdsp.preproc, SPEEX_PREPROCESS_SET_DENOISE, &i); - i = cl_voip_autogain.ival; + i = snd_voip_autogain.ival; qspeex_preprocess_ctl(s_voip.speexdsp.preproc, SPEEX_PREPROCESS_SET_AGC, &i); s_voip.speexdsp.curframesize = s_voip.encframesize; @@ -1172,7 +1217,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) s_voip.enctimestamp += samps; nl = (3000*level) / (32767.0f*32767*samps); s_voip.voiplevel = (s_voip.voiplevel*7 + nl)/8; - if (s_voip.voiplevel < cl_voip_vad_threshhold.ival && !(cl_voip_send.ival & 6)) + if (s_voip.voiplevel < snd_voip_vad_threshhold.ival && !voipbutton && !(snd_voip_send.ival & 6)) { /*try and dump it, it was too quiet, and they're not pressing +voip*/ if (s_voip.keeps > samps) @@ -1188,7 +1233,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } } else - s_voip.keeps = s_voip.encsamplerate * cl_voip_vad_delay.value; + s_voip.keeps = s_voip.encsamplerate * snd_voip_vad_delay.value; if (outpos) { if (s_voip.dumps > s_voip.encsamplerate/4) @@ -1199,7 +1244,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) if (outpos && (!buf || buf->maxsize - buf->cursize >= outpos+4)) { - if (buf && (cl_voip_send.ival != 4)) + if (buf && (snd_voip_send.ival != 4)) { MSG_WriteByte(buf, clc); MSG_WriteByte(buf, (s_voip.enccodec<<4) | (s_voip.generation & 0x0f)); /*gonna leave that nibble clear here... in this version, the client will ignore packets with those bits set. can use them for codec or something*/ @@ -1223,7 +1268,7 @@ void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf) } #endif - if (cl_voip_test.ival) + if (snd_voip_test.ival) S_Voip_Decode(cl.playerview[0].playernum, s_voip.enccodec, s_voip.generation & 0x0f, initseq, outpos, outbuf); //update our own lastspoke, so queries shows that we're speaking when we're speaking in a generic way, even if we can't hear ourselves. @@ -1244,11 +1289,11 @@ void S_Voip_Ignore(unsigned int slot, qboolean ignore) } static void S_Voip_Enable_f(void) { - Cvar_SetValue(&cl_voip_send, cl_voip_send.ival | 2); + voipbutton = true; } static void S_Voip_Disable_f(void) { - Cvar_SetValue(&cl_voip_send, cl_voip_send.ival & ~2); + voipbutton = false; } static void S_Voip_f(void) { @@ -1272,7 +1317,7 @@ static void S_Voip_Play_Callback(cvar_t *var, char *oldval) } void S_Voip_MapChange(void) { - Cvar_ForceCallback(&cl_voip_play); + Cvar_ForceCallback(&snd_voip_play); } int S_Voip_Loudness(qboolean ignorevad) { @@ -1289,6 +1334,24 @@ qboolean S_Voip_Speaking(unsigned int plno) return s_voip.lastspoke[plno] > realtime; } +void QDECL S_Voip_EnumeratedCaptureDevice(const char *driver, const char *devicecode, const char *readabledevice) +{ + const char *fullintname; + char opts[8192]; + char nbuf[1024]; + char dbuf[1024]; + + if (devicecode && ( strchr(devicecode, ' ') || + strchr(devicecode, '\"'))) + fullintname = va("\"%s:%s\"", driver, devicecode); //it'll all get escaped anyway. but yeah, needs to be a single token or our multi-device stuff won't work properly. yes, this is a bit of a hack. + else if (devicecode) + fullintname = va("%s:%s", driver, devicecode); + else + fullintname = driver; + + Q_snprintfz(opts, sizeof(opts), "%s%s%s %s", snd_voip_capturedevice_opts.string, *snd_voip_capturedevice_opts.string?" ":"", COM_QuotedString(fullintname, nbuf, sizeof(nbuf)), COM_QuotedString(readabledevice, dbuf, sizeof(dbuf))); + Cvar_ForceSet(&snd_voip_capturedevice_opts, opts); +} void S_Voip_Init(void) { int i; @@ -1296,21 +1359,34 @@ void S_Voip_Init(void) s_voip.deccodec[i] = VOIP_INVALID; s_voip.enccodec = VOIP_INVALID; - Cvar_Register(&cl_voip_send, "Voice Chat"); - Cvar_Register(&cl_voip_vad_threshhold, "Voice Chat"); - Cvar_Register(&cl_voip_vad_delay, "Voice Chat"); - Cvar_Register(&cl_voip_capturingvol, "Voice Chat"); - Cvar_Register(&cl_voip_showmeter, "Voice Chat"); - Cvar_Register(&cl_voip_play, "Voice Chat"); - Cvar_Register(&cl_voip_test, "Voice Chat"); - Cvar_Register(&cl_voip_ducking, "Voice Chat"); - Cvar_Register(&cl_voip_micamp, "Voice Chat"); - Cvar_Register(&cl_voip_codec, "Voice Chat"); - Cvar_Register(&cl_voip_noisefilter, "Voice Chat"); - Cvar_Register(&cl_voip_autogain, "Voice Chat"); + Cvar_Register(&snd_voip_capturedevice, "Voice Chat"); + Cvar_Register(&snd_voip_capturedevice_opts, "Voice Chat"); + Cvar_Register(&snd_voip_send, "Voice Chat"); + Cvar_Register(&snd_voip_vad_threshhold, "Voice Chat"); + Cvar_Register(&snd_voip_vad_delay, "Voice Chat"); + Cvar_Register(&snd_voip_capturingvol, "Voice Chat"); + Cvar_Register(&snd_voip_showmeter, "Voice Chat"); + Cvar_Register(&snd_voip_play, "Voice Chat"); + Cvar_Register(&snd_voip_test, "Voice Chat"); + Cvar_Register(&snd_voip_ducking, "Voice Chat"); + Cvar_Register(&snd_voip_micamp, "Voice Chat"); + Cvar_Register(&snd_voip_codec, "Voice Chat"); + Cvar_Register(&snd_voip_noisefilter, "Voice Chat"); + Cvar_Register(&snd_voip_autogain, "Voice Chat"); Cmd_AddCommand("+voip", S_Voip_Enable_f); Cmd_AddCommand("-voip", S_Voip_Disable_f); Cmd_AddCommand("voip", S_Voip_f); + + + Cvar_ForceSet(&snd_voip_capturedevice_opts, ""); + S_Voip_EnumeratedCaptureDevice("", NULL, "Default"); + for (i = 0; capturedrivers[i]; i++) + { + if (!capturedrivers[i]->Init) + continue; + if (!capturedrivers[i]->Enumerate || !capturedrivers[i]->Enumerate(S_Voip_EnumeratedCaptureDevice)) + S_Voip_EnumeratedCaptureDevice(capturedrivers[i]->drivername, NULL, va("Default %s", capturedrivers[i]->drivername)); + } } #else void S_Voip_Parse(void) @@ -1493,7 +1569,7 @@ static soundcardinfo_t *SNDDMA_Init(char *driver, char *device) { //if the sample speeds of multiple soundcards do not match, it'll fail. if (snd_speed != sc->sn.speed) { - Con_Printf("S_Startup: Ignoring soundcard %s due to mismatched sample speeds.\nTry running Quake with -singlesound to use just the primary soundcard\n", sc->name); + Con_Printf("S_Startup: Ignoring soundcard %s due to mismatched sample speeds.\n", sc->name); S_ShutdownCard(sc); continue; } @@ -1545,33 +1621,29 @@ static soundcardinfo_t *SNDDMA_Init(char *driver, char *device) return NULL; } -int numsoundoutdevices; -char **soundoutdevicecodes; -char **soundoutdevicenames; void QDECL S_EnumeratedOutDevice(const char *driver, const char *devicecode, const char *readabledevice) { const char *fullintname; + char opts[8192]; + char nbuf[1024]; + char dbuf[1024]; - if (devicecode && strchr(devicecode, ' ')) - fullintname = va("\"%s:%s\"", driver, devicecode); + if (devicecode && ( strchr(devicecode, ' ') || + strchr(devicecode, '\"'))) + fullintname = va("\"%s:%s\"", driver, devicecode); //it'll all get escaped anyway. but yeah, needs to be a single token or our multi-device stuff won't work properly. yes, this is a bit of a hack. else if (devicecode) fullintname = va("%s:%s", driver, devicecode); else fullintname = driver; - soundoutdevicecodes = realloc(soundoutdevicecodes, (numsoundoutdevices+2) * sizeof(char*)); - soundoutdevicecodes[numsoundoutdevices] = strdup(fullintname); - soundoutdevicecodes[numsoundoutdevices+1] = NULL; - soundoutdevicenames = realloc(soundoutdevicenames, (numsoundoutdevices+2) * sizeof(char*)); - soundoutdevicenames[numsoundoutdevices] = strdup(readabledevice); - soundoutdevicenames[numsoundoutdevices+1] = NULL; - numsoundoutdevices++; + Q_snprintfz(opts, sizeof(opts), "%s%s%s %s", snd_device_opts.string, *snd_device_opts.string?" ":"", COM_QuotedString(fullintname, nbuf, sizeof(nbuf)), COM_QuotedString(readabledevice, dbuf, sizeof(dbuf))); + Cvar_ForceSet(&snd_device_opts, opts); } void S_EnumerateDevices(void) { int i; sounddriver_t *sd; - numsoundoutdevices = 0; + Cvar_ForceSet(&snd_device_opts, ""); S_EnumeratedOutDevice("", NULL, "Default"); for (i = 0; outputdrivers[i]; i++) @@ -1772,8 +1844,6 @@ void S_Init (void) { int p; - S_EnumerateDevices(); - Con_DPrintf("\nSound Initialization\n"); Cmd_AddCommand("play", S_Play); @@ -1805,10 +1875,6 @@ void S_Init (void) Cvar_Register(&snd_samplebits, "Sound controls"); Cvar_Register(&snd_playbackrate, "Sound controls"); -#ifdef VOICECHAT - S_Voip_Init(); -#endif - Cvar_Register(&snd_inactive, "Sound controls"); #ifdef MULTITHREAD @@ -1816,10 +1882,16 @@ void S_Init (void) #endif Cvar_Register(&snd_playersoundvolume, "Sound controls"); Cvar_Register(&snd_device, "Sound controls"); + Cvar_Register(&snd_device_opts, "Sound controls"); Cvar_Register(&snd_linearresample, "Sound controls"); Cvar_Register(&snd_linearresample_stream, "Sound controls"); +#ifdef VOICECHAT + S_Voip_Init(); +#endif + S_EnumerateDevices(); + #ifdef MULTITHREAD mixermutex = Sys_CreateMutex(); #endif @@ -1895,20 +1967,6 @@ void S_Shutdown(qboolean final) Z_Free(known_sfx); known_sfx = NULL; num_sfx = 0; - - if (final) - { - while (numsoundoutdevices) - { - numsoundoutdevices--; - free(soundoutdevicenames[numsoundoutdevices]); - free(soundoutdevicecodes[numsoundoutdevices]); - } - free(soundoutdevicenames); - soundoutdevicenames = NULL; - free(soundoutdevicecodes); - soundoutdevicecodes = NULL; - } } diff --git a/engine/client/snd_linux.c b/engine/client/snd_linux.c index df2b06037..1564779b8 100644 --- a/engine/client/snd_linux.c +++ b/engine/client/snd_linux.c @@ -356,11 +356,21 @@ int (*pOSS_InitCard) (soundcardinfo_t *sc, int cardnum) = &OSS_InitCard; #ifdef VOICECHAT //this does apparently work after all. #include -void *OSS_Capture_Init(int rate) + +static qboolean QDECL OSS_Capture_Enumerate (void (QDECL *callback) (const char *drivername, const char *devicecode, const char *readablename)) +{ + //open /dev/dsp or /dev/mixer or env("OSS_MIXERDEV") or something + //SNDCTL_SYSINFO to get sysinfo.numcards + //for i=0; iincoming_unreliable - 1; if (drop > 0) { - Con_DPrintf("Dropped %i datagrams (%i - %i)\n", drop, chan->incoming_unreliable+1, sequence-1); + if (showdrop.ival) + Con_Printf("Dropped %i datagrams (%i - %i)\n", drop, chan->incoming_unreliable+1, sequence-1); chan->drop_count += drop; } chan->incoming_unreliable = sequence; diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index 01376c06a..93f13d71f 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -5044,7 +5044,7 @@ static struct icestate_s *icelist; #if !defined(SERVERONLY) && defined(VOICECHAT) -extern cvar_t cl_voip_send; +extern cvar_t snd_voip_send; struct rtpheader_s { unsigned char v2_p1_x1_cc4; @@ -5502,7 +5502,7 @@ qboolean QDECL ICE_Set(struct icestate_s *con, char *prop, char *value) } #if !defined(SERVERONLY) && defined(VOICECHAT) - cl_voip_send.ival = (cl_voip_send.ival & ~4) | (NET_RTP_Active()?4:0); + snd_voip_send.ival = (snd_voip_send.ival & ~4) | (NET_RTP_Active()?4:0); #endif } else if (!strcmp(prop, "controlled")) diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 573e827c8..247fd20ea 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -911,6 +911,8 @@ typedef struct entity_state_s unsigned char gravitydir[2]; //pitch/yaw, no roll unsigned short traileffectnum; + + vec3_t predorg; } q1; } u; unsigned short modelindex2; //q2/vweps @@ -955,21 +957,22 @@ typedef struct int num_entities; int max_entities; entity_state_t *entities; - qboolean fixangles[MAX_SPLITS]; + qboolean fixangles[MAX_SPLITS]; //these should not be in here vec3_t fixedangles[MAX_SPLITS]; } packet_entities_t; typedef struct usercmd_s { //the first members of this structure MUST match the q2 version - qbyte msec; + qbyte msec_compat; qbyte buttons_compat; short angles[3]; short forwardmove, sidemove, upmove; qbyte impulse; - qbyte lightlevel; + qbyte lightlevel; //freestyle + int msec; int buttons; int weapon; int servertime; @@ -1434,7 +1437,7 @@ typedef struct q1usercmd_s //TENEBRAE_GFX_DLIGHTS #define PFLAGS_NOSHADOW 1 #define PFLAGS_CORONA 2 -#define PFLAGS_FULLDYNAMIC 128 +#define PFLAGS_FULLDYNAMIC 128 //NOTE: this is a dp-ism. for tenebrae compat, this should be effects&16 and not pflags&128, as effects&16 already means something else #define RENDER_STEP 1 #define RENDER_GLOWTRAIL 2 diff --git a/engine/gl/gl_heightmap.c b/engine/gl/gl_heightmap.c index 0dbe8ae3c..671213a92 100644 --- a/engine/gl/gl_heightmap.c +++ b/engine/gl/gl_heightmap.c @@ -3403,6 +3403,7 @@ void Mod_Terrain_Create_f(void) "{\n" "classname \"worldspawn\"\n" "message \"%s\"\n" + "_sky sky1\n" "_segmentsize 1024\n" "_minxsegment -2048\n" "_minysegment -2048\n" diff --git a/engine/gl/gl_rmain.c b/engine/gl/gl_rmain.c index f21efa079..425658f55 100644 --- a/engine/gl/gl_rmain.c +++ b/engine/gl/gl_rmain.c @@ -826,6 +826,7 @@ void GLR_DrawPortal(batch_t *batch, batch_t **blist, int portaltype) switch(portaltype) { case 1: /*fbo explicit mirror (fucked depth, working clip plane)*/ + //fixme: pvs is surely wrong? r_refdef.flipcull ^= true; R_MirrorMatrix(&plane); break; diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 16258f3a9..35dd3e49d 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -4226,6 +4226,8 @@ static void QCBUILTIN PF_pointcontents (pubprogfuncs_t *prinst, struct globalvar G_FLOAT(OFS_RETURN) = Q1CONTENTS_SLIME; else if (cont & FTECONTENTS_WATER) G_FLOAT(OFS_RETURN) = Q1CONTENTS_WATER; + else if (cont & FTECONTENTS_LADDER) + G_FLOAT(OFS_RETURN) = Q1CONTENTS_LADDER; else G_FLOAT(OFS_RETURN) = Q1CONTENTS_EMPTY; } @@ -9701,7 +9703,7 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs {"findflags", PF_FindFlags, 0, 0, 0, 449, "entity(entity start, .float fld, float match)"},//DP_QC_FINDFLAGS {"findchainflags", PF_sv_findchainflags,0, 0, 0, 450, "entity(.float fld, float match)"},//DP_QC_FINDCHAINFLAGS {"gettagindex", PF_gettagindex, 0, 0, 0, 451, "float(entity ent, string tagname)"},// (DP_MD3_TAGSINFO) - {"gettaginfo", PF_gettaginfo, 0, 0, 0, 452, "vector(entity ent, float tagindex)"},// (DP_MD3_TAGSINFO) + {"gettaginfo", PF_gettaginfo, 0, 0, 0, 452, D("vector(entity ent, float tagindex)", "Obtains the current worldspace position+orientation of the bone or tag from the given entity. The return value is the world coord, v_forward, v_right, v_up are also set according to the bone/tag's orientation.")},// (DP_MD3_TAGSINFO) {"dropclient", PF_dropclient, 0, 0, 0, 453, "void(entity player)"},//DP_SV_BOTCLIENT {"spawnclient", PF_spawnclient, 0, 0, 0, 454, "entity()"},//DP_SV_BOTCLIENT {"clienttype", PF_clienttype, 0, 0, 0, 455, "float(entity client)"},//botclient @@ -10259,7 +10261,7 @@ void PR_DumpPlatform_f(void) {"ltime", ".float", QW|NQ}, {"entnum", ".float", CS, "The entity number as its known on the server."}, {"drawmask", ".float", CS, "Acts as a filter in the addentities call."}, - {"predraw", ".__variant()", CS, "Called by addentities after the filter and before the entity is actually drawn. Do your interpolation and animation in here. Return true to inhibit addition of the entity (this is defined as variant for compat with legacy code, your code should use .float instead)."}, + {"predraw", ".float()", CS, "Called by addentities after the filter and before the entity is actually drawn. Do your interpolation and animation in here. Return true to inhibit addition of the entity, and false for the entity to be added to the scene."}, {"lastruntime", ".float", QW}, {"movetype", ".float", QW|NQ|CS}, {"solid", ".float", QW|NQ|CS}, @@ -10446,7 +10448,7 @@ void PR_DumpPlatform_f(void) {"SOLID_SLIDEBOX", "const float", QW|NQ|CS, NULL, SOLID_SLIDEBOX}, {"SOLID_BSP", "const float", QW|NQ|CS, NULL, SOLID_BSP}, {"SOLID_CORPSE", "const float", QW|NQ|CS, NULL, SOLID_CORPSE}, - {"SOLID_LADDER", "const float", QW|NQ|CS, NULL, SOLID_LADDER}, + {"SOLID_LADDER", "const float", QW|NQ|CS, "Obsolete and may be removed at some point. Use skin=CONTENT_LADDER and solid_bsp or solid_trigger instead.", SOLID_LADDER}, {"SOLID_PHYSICS_BOX", "const float", QW|NQ|CS, NULL, SOLID_PHYSICS_BOX}, {"SOLID_PHYSICS_SPHERE", "const float", QW|NQ|CS, NULL, SOLID_PHYSICS_SPHERE}, {"SOLID_PHYSICS_CAPSULE", "const float", QW|NQ|CS, NULL, SOLID_PHYSICS_CAPSULE}, @@ -10468,6 +10470,7 @@ void PR_DumpPlatform_f(void) {"CONTENT_SLIME", "const float", QW|NQ|CS, NULL, Q1CONTENTS_SLIME}, {"CONTENT_LAVA", "const float", QW|NQ|CS, NULL, Q1CONTENTS_LAVA}, {"CONTENT_SKY", "const float", QW|NQ|CS, NULL, Q1CONTENTS_SKY}, + {"CONTENT_LADDER", "const float", QW|NQ|CS, NULL, Q1CONTENTS_LADDER}, {"CHAN_AUTO", "const float", QW|NQ|CS, NULL, CHAN_AUTO}, {"CHAN_WEAPON", "const float", QW|NQ|CS, NULL, CHAN_WEAPON}, diff --git a/engine/server/server.h b/engine/server/server.h index aff8287c3..741e68f9c 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -288,7 +288,7 @@ typedef struct // reply double senttime; //time we sent this frame to the client, for ping calcs int sequence; //the outgoing sequence - without mask, meaning we know if its current or stale - float ping_time; //how long it took for the client to ack it, may be negativ + float ping_time; //how long it took for the client to ack it, may be negative float move_msecs; // int packetsizein; //amount of data received for this frame int packetsizeout; //amount of data that was sent in the frame @@ -1144,8 +1144,8 @@ void ClientReliableWrite_Coord(client_t *cl, float f); void ClientReliableWrite_Long(client_t *cl, int c); void ClientReliableWrite_Short(client_t *cl, int c); void ClientReliableWrite_Entity(client_t *cl, int c); -void ClientReliableWrite_String(client_t *cl, char *s); -void ClientReliableWrite_SZ(client_t *cl, void *data, int len); +void ClientReliableWrite_String(client_t *cl, const char *s); +void ClientReliableWrite_SZ(client_t *cl, const void *data, int len); #ifdef SVRANKING diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index 83dbd09cc..de8eb2e9e 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -1671,8 +1671,16 @@ void SV_Heartbeat_f (void) svs.last_heartbeat = -9999; } +#define FOREACHCLIENT(i,cl) \ +for (i = sv.mvdrecording?-1:0; i < sv.allocated_client_slots; i++) \ +if (cl = (i==-1?&demo.recorder:&svs.clients[i])) \ +if ((i == -1) || cl->state > cs_zombie) + void SV_SendServerInfoChange(char *key, const char *value) { + int i; + client_t *cl; + if (!sv.state) return; @@ -1685,9 +1693,24 @@ void SV_SendServerInfoChange(char *key, const char *value) return; //FIXME!!! #endif - MSG_WriteByte (&sv.reliable_datagram, svc_serverinfo); - MSG_WriteString (&sv.reliable_datagram, key); - MSG_WriteString (&sv.reliable_datagram, value); + FOREACHCLIENT(i, cl) + { + if (ISQWCLIENT(cl)) + { + ClientReliableWrite_Begin(cl, svc_serverinfo, strlen(key) + strlen(value)+3); + ClientReliableWrite_String(cl, key); + ClientReliableWrite_String(cl, value); + } + else if (ISNQCLIENT(cl) && (cl->fteprotocolextensions2 & PEXT2_PREDINFO)) + { + ClientReliableWrite_Begin(cl, svc_stufftext, 1+6+strlen(key)+2+strlen(value)+3); + ClientReliableWrite_SZ(cl, "//svi ", 6); + ClientReliableWrite_SZ(cl, key, strlen(key)); + ClientReliableWrite_SZ(cl, " \"", 2); + ClientReliableWrite_SZ(cl, value, strlen(value)); + ClientReliableWrite_String(cl, "\"\n"); + } + } } /* diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index 2d197e640..cc1b718ae 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -3489,7 +3489,7 @@ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qboolean ignore MSG_WriteByte (msg, STAT_TIME); MSG_WriteLong (msg, (int)(sv.world.physicstime * 1000)); - client->nextservertimeupdate = sv.world.physicstime;//+10; + client->nextservertimeupdate = sv.world.physicstime; } else if (client->zquake_extensions & Z_EXT_SERVERTIME && sv.world.physicstime - client->nextservertimeupdate > 0) { //the zquake ext causes the server to send out peridoic timings, allowing for moderatly accurate game time. diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index ef9889d0e..34e7ccbaa 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -930,10 +930,7 @@ void SV_FullClientUpdate (client_t *client, client_t *to) SV_FullClientUpdate(client, &svs.clients[i]); } if (sv.mvdrecording) - { SV_FullClientUpdate(client, &demo.recorder); - SV_MVD_WriteReliables(); - } return; } @@ -3215,7 +3212,7 @@ qboolean SVNQ_ConnectionlessPacket(void) flags = MSG_ReadByte(); passwd = MSG_ReadLong(); - if (!strncmp(MSG_ReadString(), "getchallenge", 12)) + if (!strncmp(MSG_ReadString(), "getchallenge", 12) && (sv_listen_qw.ival || sv_listen_dp.ival)) { /*dual-stack client, supporting either DP or QW protocols*/ SVC_GetChallenge (); diff --git a/engine/server/sv_mvd.c b/engine/server/sv_mvd.c index 180bbb8c2..3aad1d496 100644 --- a/engine/server/sv_mvd.c +++ b/engine/server/sv_mvd.c @@ -1010,7 +1010,7 @@ void SV_WriteMVDMessage (sizebuf_t *msg, int type, int to, float time) DestFlush(false); } -//if you use ClientRelaible to write to demo.recorder's message buffer (for code reuse) call this function to ensure its flushed. +//if you use ClientReliable to write to demo.recorder's message buffer (for code reuse) call this function to ensure its flushed. void SV_MVD_WriteReliables(void) { int i; diff --git a/engine/server/sv_nchan.c b/engine/server/sv_nchan.c index f0e84e4ab..b6a5fc91a 100644 --- a/engine/server/sv_nchan.c +++ b/engine/server/sv_nchan.c @@ -183,7 +183,7 @@ void ClientReliableWrite_Entity(client_t *cl, int c) MSG_WriteEntity(&cl->netchan.message, c); } -void ClientReliableWrite_String(client_t *cl, char *s) +void ClientReliableWrite_String(client_t *cl, const char *s) { if (cl->num_backbuf) { diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 726ee082b..009c4baec 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -2090,11 +2090,11 @@ void World_Physics_Frame(world_t *w) } else { - int newt; - int delt; + float newt; + float delt; newt = sv.time*1000; delt = newt - svs.clients[i-1].msecs; - if (delt > 1000/77 || delt < -10) + if (delt > 1000/77.0 || delt < -10) { float ft = host_frametime; host_client = &svs.clients[i-1]; diff --git a/engine/server/sv_send.c b/engine/server/sv_send.c index c0aefe37e..e95b690da 100644 --- a/engine/server/sv_send.c +++ b/engine/server/sv_send.c @@ -2026,6 +2026,8 @@ void SV_FlushBroadcasts (void) } } + SV_MVD_WriteReliables(); + SZ_Clear (&sv.reliable_datagram); SZ_Clear (&sv.datagram); #ifdef NQPROT diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 6e6a3bcb6..8f4876200 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -58,7 +58,7 @@ cvar_t cmd_allowaccess = SCVAR("cmd_allowaccess", "0"); //set to 1 to allow cmd cvar_t cmd_gamecodelevel = SCVAR("cmd_gamecodelevel", STRINGIFY(RESTRICT_LOCAL)); //execution level which gamecode is told about (for unrecognised commands) cvar_t sv_pure = CVARFD("sv_pure", "", CVAR_SERVERINFO, "The most evil cvar in the world, many clients will ignore this.\n0=standard quake rules.\n1=clients should prefer files within packages present on the server.\n2=clients should use *only* files within packages present on the server.\nDue to quake 1.01/1.06 differences, a setting of 2 only works in total conversions."); -cvar_t sv_nqplayerphysics = CVARAD("sv_nqplayerphysics", "0", "sv_nomsec", "Disregard player prediction and run NQ-style player physics instead. This can be used for compatibility with mods that expect exact behaviour."); +cvar_t sv_nqplayerphysics = CVARAD("sv_nqplayerphysics", "0", "sv_nomsec", "Disable player prediction and run NQ-style player physics instead. This can be used for compatibility with mods that expect exact behaviour."); cvar_t sv_edgefriction = CVARAF("sv_edgefriction", "2", "edgefriction", 0); @@ -585,19 +585,6 @@ void SVNQ_New_f (void) MSG_WriteString (&host_client->netchan.message, sv.strings.sound_precache[i]); MSG_WriteByte (&host_client->netchan.message, 0); -// send music - MSG_WriteByte (&host_client->netchan.message, svc_cdtrack); - if (progstype == PROG_H2) - { - MSG_WriteByte (&host_client->netchan.message, sv.h2cdtrack); - MSG_WriteByte (&host_client->netchan.message, sv.h2cdtrack); - } - else - { - MSG_WriteByte (&host_client->netchan.message, ((edict_t*)sv.world.edicts)->v->sounds); - MSG_WriteByte (&host_client->netchan.message, ((edict_t*)sv.world.edicts)->v->sounds); - } - // set view MSG_WriteByte (&host_client->netchan.message, svc_setview); MSG_WriteEntity (&host_client->netchan.message, NUM_FOR_EDICT(svprogfuncs, host_client->edict)); @@ -605,13 +592,10 @@ void SVNQ_New_f (void) MSG_WriteByte (&host_client->netchan.message, svc_signonnum); MSG_WriteByte (&host_client->netchan.message, 1); - MSG_WriteByte (&host_client->netchan.message, svc_setpause); - MSG_WriteByte (&host_client->netchan.message, sv.paused); - // host_client->sendsignon = true; // host_client->spawned = false; // need prespawn, spawn, etc - host_client->prespawn_stage = PRESPAWN_MAPCHECK; + host_client->prespawn_stage = PRESPAWN_SERVERINFO; host_client->prespawn_idx = 0; } @@ -917,18 +901,25 @@ void SV_SendClientPrespawnInfo(client_t *client) { if (client->prespawn_idx == 0) { - ClientReliableWrite_Begin(client, svc_stufftext, 20 + strlen(svs.info)); - ClientReliableWrite_String (client, va("fullserverinfo \"%s\"\n", svs.info) ); + if (!ISNQCLIENT(client) || (client->fteprotocolextensions2 & PEXT2_PREDINFO)) + { //nq does not normally get serverinfo sent to it. + ClientReliableWrite_Begin(client, svc_stufftext, 20 + strlen(svs.info)); + ClientReliableWrite_String (client, va("fullserverinfo \"%s\"\n", svs.info) ); + } } else if (client->prespawn_idx == 1) { - ClientReliableWrite_Begin(client, svc_cdtrack, 2); + int track = 0; + if (progstype == PROG_H2) - ClientReliableWrite_Byte (client, sv.h2cdtrack); + track = sv.h2cdtrack; //hexen2 has a special hack else if (svprogfuncs) - ClientReliableWrite_Byte (client, ((edict_t*)sv.world.edicts)->v->sounds); - else - ClientReliableWrite_Byte (client, 0); + track = ((edict_t*)sv.world.edicts)->v->sounds; + + ClientReliableWrite_Begin(client, svc_cdtrack, 2); + ClientReliableWrite_Byte (client, track); + if (ISNQCLIENT(client)) + ClientReliableWrite_Byte (client, track); } else if (client->prespawn_idx == 2) { @@ -946,6 +937,11 @@ void SV_SendClientPrespawnInfo(client_t *client) ClientReliableWrite_SZ(client, buffer, strlen(buffer)); ClientReliableWrite_String(client, "\n"); } + else if (client->prespawn_idx == 4) + { + ClientReliableWrite_Begin(client, svc_setpause, 2); + ClientReliableWrite_Byte (client, sv.paused); + } else { client->prespawn_stage++; @@ -958,144 +954,154 @@ void SV_SendClientPrespawnInfo(client_t *client) if (client->prespawn_stage == PRESPAWN_SOUNDLIST) { - int maxclientsupportedsounds = 256; -#ifdef PEXT_SOUNDDBL - if (client->fteprotocolextensions & PEXT_SOUNDDBL) - maxclientsupportedsounds = MAX_SOUNDS; -#endif - started = false; - - //allows stalling for the soundlist command, for compat. - if (client->prespawn_idx & 0x80000000) - return; - - while (client->netchan.message.cursize < (client->netchan.message.maxsize/2)) + if (!ISQWCLIENT(client)) + client->prespawn_stage++; + else { - if (!started) + int maxclientsupportedsounds = 256; + #ifdef PEXT_SOUNDDBL + if (client->fteprotocolextensions & PEXT_SOUNDDBL) + maxclientsupportedsounds = MAX_SOUNDS; + #endif + started = false; + + //allows stalling for the soundlist command, for compat. + if (client->prespawn_idx & 0x80000000) + return; + + while (client->netchan.message.cursize < (client->netchan.message.maxsize/2)) { - started = true; -#ifdef PEXT_SOUNDDBL - if (client->prespawn_idx > 255) + if (!started) { - MSG_WriteByte (&client->netchan.message, svcfte_soundlistshort); - MSG_WriteShort (&client->netchan.message, client->prespawn_idx); + started = true; + #ifdef PEXT_SOUNDDBL + if (client->prespawn_idx > 255) + { + MSG_WriteByte (&client->netchan.message, svcfte_soundlistshort); + MSG_WriteShort (&client->netchan.message, client->prespawn_idx); + } + else + #endif + { + MSG_WriteByte (&client->netchan.message, svc_soundlist); + MSG_WriteByte (&client->netchan.message, client->prespawn_idx); + } + } + client->prespawn_idx++; + + if (client->prespawn_idx >= maxclientsupportedsounds || !*sv.strings.sound_precache[client->prespawn_idx]) + { + //write final-end-of-list + MSG_WriteByte (&client->netchan.message, 0); + MSG_WriteByte (&client->netchan.message, 0); + started = 0; + client->prespawn_stage++; + client->prespawn_idx = 0; + break; } else -#endif - { - MSG_WriteByte (&client->netchan.message, svc_soundlist); - MSG_WriteByte (&client->netchan.message, client->prespawn_idx); - } + MSG_WriteString (&client->netchan.message, sv.strings.sound_precache[client->prespawn_idx]); } - client->prespawn_idx++; - - if (client->prespawn_idx >= maxclientsupportedsounds || !*sv.strings.sound_precache[client->prespawn_idx]) + if (started) { - //write final-end-of-list + //write end-of-packet MSG_WriteByte (&client->netchan.message, 0); - MSG_WriteByte (&client->netchan.message, 0); - started = 0; - client->prespawn_stage++; - client->prespawn_idx = 0; - break; - } - else - MSG_WriteString (&client->netchan.message, sv.strings.sound_precache[client->prespawn_idx]); - } - if (started) - { - //write end-of-packet - MSG_WriteByte (&client->netchan.message, 0); - MSG_WriteByte (&client->netchan.message, (client->prespawn_idx&0xff)?client->prespawn_idx:0xff); + MSG_WriteByte (&client->netchan.message, (client->prespawn_idx&0xff)?client->prespawn_idx:0xff); - if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) - client->prespawn_idx |= 0x80000000; + if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) + client->prespawn_idx |= 0x80000000; + } } } if (client->prespawn_stage == PRESPAWN_MODELLIST) { - started = false; - - //allows stalling for the soundlist command, for compat. - if (client->prespawn_idx & 0x80000000) - return; - - while (client->netchan.message.cursize < (client->netchan.message.maxsize/2)) + if (!ISQWCLIENT(client)) + client->prespawn_stage++; + else { - if (!started) + started = false; + + //allows stalling for the soundlist command, for compat. + if (client->prespawn_idx & 0x80000000) + return; + + while (client->netchan.message.cursize < (client->netchan.message.maxsize/2)) { - started = true; -#ifdef PEXT_SOUNDDBL - if (client->prespawn_idx > 255) + if (!started) { - MSG_WriteByte (&client->netchan.message, svcfte_modellistshort); - MSG_WriteShort (&client->netchan.message, client->prespawn_idx); + started = true; + #ifdef PEXT_SOUNDDBL + if (client->prespawn_idx > 255) + { + MSG_WriteByte (&client->netchan.message, svcfte_modellistshort); + MSG_WriteShort (&client->netchan.message, client->prespawn_idx); + } + else + #endif + { + MSG_WriteByte (&client->netchan.message, svc_modellist); + MSG_WriteByte (&client->netchan.message, client->prespawn_idx); + } + } + client->prespawn_idx++; + + if (client->prespawn_idx >= client->maxmodels || !sv.strings.model_precache[client->prespawn_idx]) + { + //write final-end-of-list + MSG_WriteByte (&client->netchan.message, 0); + MSG_WriteByte (&client->netchan.message, 0); + started = 0; + + if (client->zquake_extensions & Z_EXT_VWEP) + { + char mname[MAX_QPATH]; + char vweaplist[1024] = "//vwep"; + + for (i = 0; sv.strings.vw_model_precache[i]; i++) + { + //grab the model name... without a progs/ prefix if it has one + if (!strncmp(sv.strings.vw_model_precache[i], "progs/", 6)) + Q_strncpy(mname, sv.strings.vw_model_precache[i]+6, sizeof(mname)); + else + Q_strncpy(mname, sv.strings.vw_model_precache[i], sizeof(mname)); + + //strip .mdl extensions, for compat with ezquake + if (!strcmp(COM_FileExtension(mname), "mdl")) + COM_StripExtension(mname, mname, sizeof(mname)); + + //add it to the vweap command, taking care of any remaining spaces in names. + if (strchr(mname, ' ') || !*mname) + Q_strncatz(vweaplist, va(" \"%s\"", mname), sizeof(vweaplist)); + else + Q_strncatz(vweaplist, va(" %s", mname), sizeof(vweaplist)); + } + + if (strlen(vweaplist) <= sizeof(vweaplist)-2) + { + Q_strncatz(vweaplist, "\n", sizeof(vweaplist)); + + ClientReliableWrite_Begin(client, svc_stufftext, 2+strlen(vweaplist)); + ClientReliableWrite_String(client, vweaplist); + } + } + + client->prespawn_stage++; + client->prespawn_idx = 0; + break; } else -#endif - { - MSG_WriteByte (&client->netchan.message, svc_modellist); - MSG_WriteByte (&client->netchan.message, client->prespawn_idx); - } + MSG_WriteString (&client->netchan.message, sv.strings.model_precache[client->prespawn_idx]); } - client->prespawn_idx++; - - if (client->prespawn_idx >= client->maxmodels || !sv.strings.model_precache[client->prespawn_idx]) + if (started) { - //write final-end-of-list + //write end-of-packet MSG_WriteByte (&client->netchan.message, 0); - MSG_WriteByte (&client->netchan.message, 0); - started = 0; + MSG_WriteByte (&client->netchan.message, (client->prespawn_idx&0xff)?client->prespawn_idx:0xff); - if (client->zquake_extensions & Z_EXT_VWEP) - { - char mname[MAX_QPATH]; - char vweaplist[1024] = "//vwep"; - - for (i = 0; sv.strings.vw_model_precache[i]; i++) - { - //grab the model name... without a progs/ prefix if it has one - if (!strncmp(sv.strings.vw_model_precache[i], "progs/", 6)) - Q_strncpy(mname, sv.strings.vw_model_precache[i]+6, sizeof(mname)); - else - Q_strncpy(mname, sv.strings.vw_model_precache[i], sizeof(mname)); - - //strip .mdl extensions, for compat with ezquake - if (!strcmp(COM_FileExtension(mname), "mdl")) - COM_StripExtension(mname, mname, sizeof(mname)); - - //add it to the vweap command, taking care of any remaining spaces in names. - if (strchr(mname, ' ') || !*mname) - Q_strncatz(vweaplist, va(" \"%s\"", mname), sizeof(vweaplist)); - else - Q_strncatz(vweaplist, va(" %s", mname), sizeof(vweaplist)); - } - - if (strlen(vweaplist) <= sizeof(vweaplist)-2) - { - Q_strncatz(vweaplist, "\n", sizeof(vweaplist)); - - ClientReliableWrite_Begin(client, svc_stufftext, 2+strlen(vweaplist)); - ClientReliableWrite_String(client, vweaplist); - } - } - - client->prespawn_stage++; - client->prespawn_idx = 0; - break; + if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) + client->prespawn_idx |= 0x80000000; } - else - MSG_WriteString (&client->netchan.message, sv.strings.model_precache[client->prespawn_idx]); - } - if (started) - { - //write end-of-packet - MSG_WriteByte (&client->netchan.message, 0); - MSG_WriteByte (&client->netchan.message, (client->prespawn_idx&0xff)?client->prespawn_idx:0xff); - - if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) - client->prespawn_idx |= 0x80000000; } } @@ -4421,6 +4427,7 @@ void Cmd_FPSList_f(void) double minf, maxf; double ftime; int frames; + char *protoname; for (c = 0; c < sv.allocated_client_slots; c++) @@ -4431,10 +4438,23 @@ void Cmd_FPSList_f(void) SV_CalcNetRates(cl, &ftime, &frames, &minf, &maxf); + switch(cl->protocol) + { + case SCP_QUAKEWORLD: protoname = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"fteqw":(cl->fteprotocolextensions||cl->fteprotocolextensions2?"qw":"qwid"); break; + case SCP_QUAKE2: protoname = "q2"; break; + case SCP_QUAKE3: protoname = "q3"; break; + case SCP_NETQUAKE: protoname = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":"nqid"; break; + case SCP_PROQUAKE: protoname = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":"nq"; break; + case SCP_FITZ666: protoname = (cl->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)?"ftenq":"fitz"; break; + case SCP_DARKPLACES6: protoname = "dpp6"; break; + case SCP_DARKPLACES7: protoname = "dpp7"; break; + default: protoname = "?"; break; + } + if (frames) - SV_ClientPrintf(host_client, PRINT_HIGH, "%s: %gfps (min%g max %g), c2s: %ibps, s2c: %ibps\n", cl->name, ftime/frames, minf, maxf, (int)cl->inrate, (int)cl->outrate); + SV_ClientPrintf(host_client, PRINT_HIGH, "%s: %gfps (%g - %g), c2s: %ibps, s2c: %ibps, ping %ims(-%i), pl %i%% %s\n", cl->name, ftime/frames, minf, maxf, (int)cl->inrate, (int)cl->outrate, SV_CalcPing(cl, false), (int)(1000*cl->delay), cl->lossage, protoname); else - SV_ClientPrintf(host_client, PRINT_HIGH, "%s: unknown framerate, c2s: %ibps, s2c: %ibps\n", cl->name, (int)cl->inrate, (int)cl->outrate); + SV_ClientPrintf(host_client, PRINT_HIGH, "%s: unknown framerate, c2s: %ibps, s2c: %ibps, ping %ims(-%i), pl %i%% %s\n", cl->name, (int)cl->inrate, (int)cl->outrate, SV_CalcPing(cl, false), (int)(1000*cl->delay), cl->lossage, protoname); } } @@ -5312,7 +5332,7 @@ void AddLinksToPmove ( edict_t *player, areanode_t *node ) case Q1CONTENTS_SKY: pe->forcecontentsmask = FTECONTENTS_SKY; break; - case -16: + case Q1CONTENTS_LADDER: pe->forcecontentsmask = FTECONTENTS_LADDER; break; default: @@ -6208,17 +6228,13 @@ void SV_ExecuteClientMessage (client_t *cl) cl->delay = 0; else { - if (frame->ping_time*1000 > sv_minping.value+1) + float diff = frame->ping_time*1000 - sv_minping.value; + if (fabs(diff) > 1) { - cl->delay -= 0.001; - if (cl->delay < 0) - cl->delay = 0; - } - if (frame->ping_time*1000 < sv_minping.value) - { - cl->delay += 0.001; - if (cl->delay > 1) - cl->delay = 1; + //FIXME: we should use actual arrival times instead, so we don't get so much noise and seesawing. + diff = bound(-25, diff, 25); //don't swing wildly + cl->delay -= 0.001*(diff/25); //scale towards the ideal value + cl->delay = bound(0, cl->delay, 1); //but make sure things don't go crazy } } } @@ -6794,7 +6810,7 @@ void SVNQ_ReadClientMove (usercmd_t *move) host_client->edict->xv->button7 = ((bits >> 6) & 1); host_client->edict->xv->button8 = ((bits >> 7) & 1); - if (host_client->last_sequence) + if (host_client->last_sequence && !sv_nqplayerphysics.ival) { host_frametime = timesincelast; diff --git a/engine/server/svhl_game.c b/engine/server/svhl_game.c index ec7b5e164..1231cca35 100644 --- a/engine/server/svhl_game.c +++ b/engine/server/svhl_game.c @@ -1575,7 +1575,7 @@ extern vec3_t player_maxs; case Q1CONTENTS_SKY: pe->forcecontentsmask = FTECONTENTS_SKY; break; - case -16: + case Q1CONTENTS_LADDER: pe->forcecontentsmask = FTECONTENTS_LADDER; break; default: