1
0
Fork 0
forked from fte/fteqw

added mic input device option.

reworked prediction code, now more generic.
added cl_lerp_smooth, cl_predict_extrapolate, cl_predict_timenudge cvars to allow tweaking player prediction/smoothness in a few different ways. cl_lerp_smooth's default changed to not smooth out live games in order to avoid unnecessary lag (was effectively set to 1, and would be 0 in vanilla).

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4471 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2013-09-06 22:57:44 +00:00
parent 61ff88d970
commit 763cef2441
36 changed files with 1174 additions and 829 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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++;

View file

@ -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;

View file

@ -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 ; j<i ; j++)
cl.inframes[(cls.netchan.incoming_acknowledged-1-j)&UPDATE_MASK].latency = -2;
cl.outframes[(cls.netchan.incoming_acknowledged-1-j)&UPDATE_MASK].latency = -2;
break;
case svc_modellist:
@ -6296,6 +6304,13 @@ void CLNQ_ParseServerMessage (void)
{
cl_dp_serverextension_download = true;
}
else if (!strncmp(s, "//svi ", 6))
{
Cmd_TokenizeString(s+2, false, false);
Con_DPrintf("SERVERINFO: %s=%s\n", Cmd_Argv(1), Cmd_Argv(2));
Info_SetValueForStarKey (cl.serverinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_SERVERINFO_STRING);
CL_CheckServerInfo();
}
else if (!strncmp(s, "\ncl_downloadbegin ", 17))
CLDP_ParseDownloadBegin(s);
else if (!strncmp(s, "\ncl_downloadfinished ", 17))
@ -6447,6 +6462,7 @@ void CLNQ_ParseServerMessage (void)
cl.inframes[fr&UPDATE_MASK].packet_entities.fixangles[destsplit] = false;
}
cl.inframes[cls.netchan.incoming_sequence&UPDATE_MASK].receivedtime = realtime;
cl.inframes[cls.netchan.incoming_sequence&UPDATE_MASK].frameid = cls.netchan.incoming_sequence;
if (CPNQ_IS_DP)
{

View file

@ -20,6 +20,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "quakedef.h"
#include "winquake.h"
cvar_t cl_predict_extrapolate = CVARD("cl_predict_extrapolate", "", "If 1, enables prediction based upon partial input frames which can change over time resulting in a swimmy feel but does not need to interpolate. If 0, prediction will stay in the past and thus use only completed frames. Interpolation will then be used to smooth movement.\nThis cvar only applies when video and input frames are independant (ie: cl_netfps is set).");
cvar_t cl_predict_timenudge = CVARD("cl_predict_timenudge", "0", "A debug feature. You should normally leave this as 0. Nudges local player prediction into the future if positive (resulting in extrapolation), or into the past if negative (resulting in laggy interpolation). Value is in seconds, so small decimals are required. This cvar applies even if input frames are tied to video frames.");
cvar_t cl_predict_smooth = CVARD("cl_lerp_smooth", "2", "If 2, will act as 1 when playing demos and otherwise act as if set to 0.\nIf 1, interpolation will run in the past, resulting in really smooth movement at the cost of latency (even on bunchy german ISDNs).\nIf 0, interpolation will be based upon packet arrival times and may judder due to packet loss.");
cvar_t cl_nopred = SCVAR("cl_nopred","0");
cvar_t cl_pushlatency = SCVAR("pushlatency","-999");
@ -368,9 +371,9 @@ void CL_PredictUsercmd (int pnum, int entnum, player_state_t *from, player_state
usercmd_t split;
split = *u;
split.msec /= 2;
split.msec = u->msec / 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 ; i<UPDATE_BACKUP-1 && cl.ackedmovesequence+i < cl.movesequence; i++)
{
lerpents_t *le = &cl.lerpplayers[pv->cam_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<UPDATE_BACKUP-1 && cl.ackedmovesequence+i <
cl.movesequence; i++)
outframe_t *of = &cl.outframes[(cl.ackedmovesequence+i) & UPDATE_MASK];
if (totime >= 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);
}

View file

@ -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

View file

@ -1015,7 +1015,7 @@ void CLQ2_ParseFrame (void)
i = MSG_ReadByte ();
for (j=0 ; j<i ; j++)
cl.inframes[ (cls.netchan.incoming_acknowledged-1-j)&UPDATE_MASK ].latency = -2;
cl.outframes[ (cls.netchan.incoming_acknowledged-1-j)&UPDATE_MASK ].latency = -2;
if (cl_shownet.value == 3)
Con_Printf (" frame:%i delta:%i\n", cl.q2frame.serverframe, cl.q2frame.deltaframe);

View file

@ -346,10 +346,21 @@ void CompleteCommand (qboolean force)
}
if (cmd)
{
if (desc)
Con_Footerf(false, "%s: %s", cmd, desc);
cvar_t *var = Cvar_FindVar(cmd);
if (var)
{
if (desc)
Con_Footerf(false, "%s %s\n%s", cmd, var->string, 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
{

View file

@ -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."),

View file

@ -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."),

View file

@ -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

View file

@ -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)
{

View file

@ -2033,6 +2033,9 @@ void Surf_SetupFrame(void)
case Q1CONTENTS_SOLID:
r_viewcontents |= FTECONTENTS_SOLID;
break;
case Q1CONTENTS_LADDER:
r_viewcontents |= FTECONTENTS_LADDER;
break;
}
}
}

View file

@ -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",

View file

@ -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;

View file

@ -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,

View file

@ -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;
}
}

View file

@ -356,11 +356,21 @@ int (*pOSS_InitCard) (soundcardinfo_t *sc, int cardnum) = &OSS_InitCard;
#ifdef VOICECHAT //this does apparently work after all.
#include <stdint.h>
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; i<sysinfo.numcards
//SNDCTL_CARDINFO
return false;
}
void *OSS_Capture_Init(int rate, char *snddev)
{
int tmp;
intptr_t fd;
char *snddev = "/dev/dsp";
if (!snddev || !*snddev)
snddev = "/dev/dsp";
fd = open(snddev, O_RDONLY | O_NONBLOCK); //try the primary device
if (fd == -1)
return NULL;
@ -426,6 +436,9 @@ unsigned int OSS_Capture_Update(void *ctx, unsigned char *buffer, unsigned int m
snd_capture_driver_t OSS_Capture =
{
1,
"OSS",
OSS_Capture_Enumerate,
OSS_Capture_Init,
OSS_Capture_Start,
OSS_Capture_Update,

View file

@ -151,7 +151,7 @@ void S_ResetFailedLoad(void);
void S_Voip_Parse(void);
#endif
#ifdef VOICECHAT
extern cvar_t cl_voip_showmeter;
extern cvar_t snd_voip_showmeter;
void S_Voip_Transmit(unsigned char clc, sizebuf_t *buf);
void S_Voip_MapChange(void);
int S_Voip_Loudness(qboolean ignorevad); //-1 for not capturing, otherwise between 0 and 100
@ -312,11 +312,14 @@ extern soundcardinfo_t *sndcardinfo;
typedef struct
{
void *(*Init) (int samplerate); /*create a new context*/
void (*Start) (void *ctx); /*begin grabbing new data, old data is potentially flushed*/
unsigned int (*Update) (void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes); /*grab the data into a different buffer*/
void (*Stop) (void *ctx); /*stop grabbing new data, old data may remain*/
void (*Shutdown) (void *ctx); /*destroy everything*/
int apiver;
char *drivername;
qboolean (QDECL *Enumerate) (void (QDECL *callback) (const char *drivername, const char *devicecode, const char *readablename));
void *(QDECL *Init) (int samplerate, char *device); /*create a new context*/
void (QDECL *Start) (void *ctx); /*begin grabbing new data, old data is potentially flushed*/
unsigned int (QDECL *Update) (void *ctx, unsigned char *buffer, unsigned int minbytes, unsigned int maxbytes); /*grab the data into a different buffer*/
void (QDECL *Stop) (void *ctx); /*stop grabbing new data, old data may remain*/
void (QDECL *Shutdown) (void *ctx); /*destroy everything*/
} snd_capture_driver_t;
#endif

View file

@ -203,6 +203,10 @@ unsigned int Net_PextMask(int maskset, qboolean fornq)
if (MAX_CLIENTS != QWMAX_CLIENTS)
mask |= PEXT2_MAXPLAYERS;
//kinda depenant
if (mask & PEXT2_PREDINFO)
mask |= PEXT2_REPLACEMENTDELTAS;
if (fornq)
{
//only ones that are tested
@ -444,7 +448,8 @@ nqprot_t NQNetChan_Process(netchan_t *chan)
drop = sequence - chan->incoming_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;

View file

@ -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"))

View file

@ -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

View file

@ -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"

View file

@ -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;

View file

@ -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},

View file

@ -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

View file

@ -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");
}
}
}
/*

View file

@ -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.

View file

@ -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 ();

View file

@ -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;

View file

@ -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)
{

View file

@ -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];

View file

@ -2026,6 +2026,8 @@ void SV_FlushBroadcasts (void)
}
}
SV_MVD_WriteReliables();
SZ_Clear (&sv.reliable_datagram);
SZ_Clear (&sv.datagram);
#ifdef NQPROT

View file

@ -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;

View file

@ -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: