diff --git a/engine/client/cl_cam.c b/engine/client/cl_cam.c index f5465ede2..e6e489458 100644 --- a/engine/client/cl_cam.c +++ b/engine/client/cl_cam.c @@ -547,7 +547,7 @@ void Cam_Unlock(playerview_t *pv) { if (pv->cam_state) { - CL_SendClientCommand(true, "ptrack"); + CL_SendSeatClientCommand(true, pv-cl.playerview, "ptrack"); pv->cam_state = CAM_FREECAM; pv->viewentity = (cls.demoplayback)?0:(pv->playernum+1); //free floating SCR_CenterPrint(pv-cl.playerview, NULL, true); @@ -564,7 +564,7 @@ void Cam_Lock(playerview_t *pv, int playernum) { pv->cam_lastviewtime = -1000; //allow the wallcam to re-snap as soon as it can - CL_SendClientCommand(true, "ptrack %i", playernum); + CL_SendSeatClientCommand(true, pv-cl.playerview, "ptrack %i", playernum); if (pv->cam_spec_track != playernum) { //flashgrens suck diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index 775160a2d..b99dc78bd 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -1563,10 +1563,11 @@ typedef struct clcmdbuf_s { struct clcmdbuf_s *next; int len; qboolean reliable; + unsigned int seat; char command[4]; //this is dynamically allocated, so this is variably sized. } clcmdbuf_t; -clcmdbuf_t *clientcmdlist; -void VARGS CL_SendClientCommand(qboolean reliable, char *format, ...) +static clcmdbuf_t *clientcmdlist; +void VARGS CL_SendSeatClientCommand(qboolean reliable, unsigned int seat, char *format, ...) { qboolean oldallow; va_list argptr; @@ -1594,6 +1595,7 @@ void VARGS CL_SendClientCommand(qboolean reliable, char *format, ...) strcpy(buf->command, string); buf->len = strlen(buf->command); buf->reliable = reliable; + buf->seat = seat; //add to end of the list so that the first of the list is the first to be sent. if (!clientcmdlist) @@ -1607,6 +1609,17 @@ void VARGS CL_SendClientCommand(qboolean reliable, char *format, ...) CL_AllowIndependantSendCmd(oldallow); } +void VARGS CL_SendClientCommand(qboolean reliable, char *format, ...) +{ + va_list argptr; + char string[2048]; + + va_start (argptr, format); + Q_vsnprintfz (string,sizeof(string), format,argptr); + va_end (argptr); + + CL_SendSeatClientCommand(reliable, 0, "%s", string); +} //sometimes a server will quickly restart twice. //connected clients will then receive TWO 'new' commands - both with the same servercount value. @@ -2090,13 +2103,6 @@ static void CL_SendUserinfoUpdate(void) qboolean final = true; char enckey[2048]; char encval[2048]; - //handle splitscreen - char pl[64]; - - if (seat) - Q_snprintfz(pl, sizeof(pl), "%i ", seat); - else - *pl = 0; #ifdef Q3CLIENT if (cls.protocol == CP_QUAKE3) @@ -2121,7 +2127,6 @@ static void CL_SendUserinfoUpdate(void) InfoBuf_ToString(info, userinfo, sizeof(userinfo), NULL, NULL, NULL, NULL, NULL); MSG_WriteByte (&cls.netchan.message, clcq2_userinfo); - SZ_Write(&cls.netchan.message, pl, strlen(pl)); MSG_WriteString (&cls.netchan.message, userinfo); } return; @@ -2145,24 +2150,27 @@ static void CL_SendUserinfoUpdate(void) if (final && !bloboffset && *encval != '\xff' && *encval != '\xff') { //vanilla-compatible info. - s = va("%ssetinfo \"%s\" \"%s\"", pl, enckey, encval); + s = va("setinfo \"%s\" \"%s\"", enckey, encval); } else if (cls.fteprotocolextensions2 & PEXT2_INFOBLOBS) { //only flood servers that actually support it. if (final) - s = va("%ssetinfo \"%s\" \"%s\" %u", pl, enckey, encval, (unsigned int)bloboffset); + s = va("setinfo \"%s\" \"%s\" %u", enckey, encval, (unsigned int)bloboffset); else - s = va("%ssetinfo \"%s\" \"%s\" %u+", pl, enckey, encval, (unsigned int)bloboffset); + s = va("setinfo \"%s\" \"%s\" %u+", enckey, encval, (unsigned int)bloboffset); } else { //server doesn't support it, just ignore the key InfoSync_Remove(&cls.userinfosync, 0); return; } - if (cls.protocol == CP_QUAKE2) - MSG_WriteByte (&cls.netchan.message, clcq2_stringcmd); + if (seat && (cls.fteprotocolextensions&PEXT_SPLITSCREEN)) + { + MSG_WriteByte (&cls.netchan.message, (cls.protocol == CP_QUAKE2)?clcq2_stringcmd_seat:clcfte_stringcmd_seat); + MSG_WriteByte (&cls.netchan.message, seat); + } else - MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + MSG_WriteByte (&cls.netchan.message, (cls.protocol == CP_QUAKE2)?clcq2_stringcmd:clc_stringcmd); MSG_WriteString (&cls.netchan.message, s); } @@ -2462,14 +2470,26 @@ void CL_SendCmd (double frametime, qboolean mainloop) break; if (!strncmp(clientcmdlist->command, "spawn", 5) && cls.userinfosync.numkeys && cl.haveserverinfo) break; //HACK: don't send the spawn until all pending userinfos have been flushed. - MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + if (clientcmdlist->seat && (cls.fteprotocolextensions&PEXT_SPLITSCREEN)) + { + MSG_WriteByte (&cls.netchan.message, clcfte_stringcmd_seat); + MSG_WriteByte (&cls.netchan.message, clientcmdlist->seat); + } + else + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); MSG_WriteString (&cls.netchan.message, clientcmdlist->command); } else { if (buf.cursize + 2+strlen(clientcmdlist->command)+100 <= buf.maxsize) { - MSG_WriteByte (&buf, clc_stringcmd); + if (clientcmdlist->seat && (cls.fteprotocolextensions&PEXT_SPLITSCREEN)) + { + MSG_WriteByte (&cls.netchan.message, clcfte_stringcmd_seat); + MSG_WriteByte (&cls.netchan.message, clientcmdlist->seat); + } + else + MSG_WriteByte (&buf, clc_stringcmd); MSG_WriteString (&buf, clientcmdlist->command); } } @@ -2653,7 +2673,7 @@ void CL_SendCvar_f (void) val = ""; else val = var->string; - CL_SendClientCommand(true, "sentcvar %s \"%s\"", name, val); + CL_SendSeatClientCommand(true, CL_TargettedSplit(false), "sentcvar %s \"%s\"", name, val); } /* diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index aad18c280..65ab2d01a 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -7118,7 +7118,7 @@ void CLQW_ParseServerMessage (void) for (; i < cl.splitclients; i++) { //svcfte_choosesplitclient has a modulo that is also broken, but at least there's no parse errors this way MSG_ReadByte(); -// CL_SendClientCommand(true, va("%i drop", i+1)); +// CL_SendSeatClientCommand(true, i, drop"); } cl.splitclients = MAX_SPLITS; } diff --git a/engine/client/client.h b/engine/client/client.h index f4d06fcd5..9be812704 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -1275,6 +1275,7 @@ qboolean CL_AllowIndependantSendCmd(qboolean allow); //returns previous state. void CL_FlushClientCommands(void); void VARGS CL_SendClientCommand(qboolean reliable, char *format, ...) LIKEPRINTF(2); +void VARGS CL_SendSeatClientCommand(qboolean reliable, unsigned int seat, char *format, ...) LIKEPRINTF(3); float CL_FilterTime (double time, float wantfps, float limit, qboolean ignoreserver); int CL_RemoveClientCommands(char *command); diff --git a/engine/client/sbar.c b/engine/client/sbar.c index 8cb26fa49..4eb36c9d3 100644 --- a/engine/client/sbar.c +++ b/engine/client/sbar.c @@ -811,7 +811,7 @@ static void Sbar_Hexen2InvLeft_f(void) #endif if (cls.protocol == CP_QUAKE2) { - CL_SendClientCommand(true, "invprev"); + CL_SendSeatClientCommand(true, seat, "invprev"); } else { @@ -838,7 +838,7 @@ static void Sbar_Hexen2InvRight_f(void) #endif if (cls.protocol == CP_QUAKE2) { - CL_SendClientCommand(true, "invnext"); + CL_SendSeatClientCommand(true, seat, "invnext"); } else { @@ -866,7 +866,7 @@ static void Sbar_Hexen2InvUse_f(void) if (cls.protocol == CP_QUAKE2) { - CL_SendClientCommand(true, "invuse"); + CL_SendSeatClientCommand(true, seat, "invuse"); } else { diff --git a/engine/client/zqtp.c b/engine/client/zqtp.c index 7b4ab8c70..97568f5a1 100644 --- a/engine/client/zqtp.c +++ b/engine/client/zqtp.c @@ -3883,10 +3883,10 @@ void CL_Say (qboolean team, char *extra) //the server is expected to use Cmd_Args and to strip first+last chars if the first is a quote. this is annoying and clumsy for mods to parse. #ifdef HAVE_LEGACY if (!dpcompat_console.ival) - CL_SendClientCommand(true, "%s%s \"%s%s\"", split?va("%i ", split+1):"", team ? "say_team" : "say", extra?extra:"", sendtext); + CL_SendSeatClientCommand(true, split, "%s \"%s%s\"", team ? "say_team" : "say", extra?extra:"", sendtext); else #endif - CL_SendClientCommand(true, "%s%s %s%s", split?va("%i ", split+1):"", team ? "say_team" : "say", extra?extra:"", sendtext); + CL_SendSeatClientCommand(true, split, "%s %s%s", team ? "say_team" : "say", extra?extra:"", sendtext); } } diff --git a/engine/common/cmd.c b/engine/common/cmd.c index ce79c492e..240338111 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -2638,20 +2638,10 @@ void Cmd_ForwardToServer (void) #endif sp = CL_TargettedSplit(false); - if (sp) - { - if (Cmd_Argc() > 1) - CL_SendClientCommand(true, "%i %s %s", sp+1, Cmd_Argv(0), Cmd_Args()); - else - CL_SendClientCommand(true, "%i %s", sp+1, Cmd_Argv(0)); - } + if (Cmd_Argc() > 1) + CL_SendSeatClientCommand(true, sp, "%s %s", Cmd_Argv(0), Cmd_Args()); else - { - if (Cmd_Argc() > 1) - CL_SendClientCommand(true, "%s %s", Cmd_Argv(0), Cmd_Args()); - else - CL_SendClientCommand(true, "%s", Cmd_Argv(0)); - } + CL_SendSeatClientCommand(true, sp, "%s", Cmd_Argv(0)); } // don't forward the first argument @@ -2713,10 +2703,7 @@ static void Cmd_ForwardToServer_f (void) if (Cmd_Argc() > 1) { int split = CL_TargettedSplit(false); - if (split) - CL_SendClientCommand(true, "%i %s", split+1, Cmd_Args()); - else - CL_SendClientCommand(true, "%s", Cmd_Args()); + CL_SendSeatClientCommand(true, split, "%s", Cmd_Args()); } } #else diff --git a/engine/common/cvar.c b/engine/common/cvar.c index f2fca68ee..825452747 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -1497,10 +1497,7 @@ qboolean Cvar_Command (int level) if (Cmd_FromGamecode() && cls.protocol == CP_QUAKEWORLD) { //don't bother even changing the cvar locally, just update the server's version. //fixme: quake2/quake3 latching. - if (seat) - CL_SendClientCommand(true, "%i setinfo %s %s", seat+1, v->name, COM_QuotedString(str, buffer, sizeof(buffer), false)); - else - CL_SendClientCommand(true, "setinfo %s %s", v->name, COM_QuotedString(str, buffer, sizeof(buffer), false)); + CL_SendSeatClientCommand(true, seat, "setinfo %s %s", v->name, COM_QuotedString(str, buffer, sizeof(buffer), false)); } else CL_SetInfo(seat, v->name, str); diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 02b447c85..c8dee7b3d 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -465,6 +465,7 @@ enum clcq2_ops_e clcr1q2_multimoves = 6, // for crappy clients that can't lerp //fte-extended + clcq2_stringcmd_seat = 30, clcq2_voicechat = 31 }; @@ -502,6 +503,7 @@ enum { #define clcfte_voicechat 83 #define clcfte_brushedit 84 #define clcfte_move 85 //part of PEXT2_VRINPUTS. replaces clc_move+clcfte_prydoncursor+clcdp_ackframe +#define clcfte_stringcmd_seat 86 #define VRM_LOSS (1u<<0) //for server packetloss reports #define VRM_DELAY (1u<<1) //for server to compute lag properly. diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 11a896a25..318b48822 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -1424,10 +1424,10 @@ static void SVC_Log (void) seq = seq+1; //they will get the next sequence from the one they already have } else - seq = 0; + seq = svs.logsequence-1; if (!fraglog_public.ival) - { //frag logs are not public (for DoS protection perhaps?. + { //frag logs are not public (for DoS protection perhaps?) data[0] = A2A_NACK; NET_SendPacket (svs.sockets, 1, data, &net_from); return; @@ -2372,6 +2372,8 @@ client_t *SV_AddSplit(client_t *controller, char *info, int id) Q_strncpyz(cl->guid, "", sizeof(cl->guid)); cl->name = cl->namebuf; cl->team = cl->teambuf; + cl->userinfo.ChangeCB = svs.info.ChangeCB; + cl->userinfo.ChangeCTX = &cl->userinfo; if (!cl->userid || !loadgame) cl->userid = ++nextuserid; diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index d283b28e8..b65422f0f 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -6459,31 +6459,6 @@ void SV_ExecuteUserCommand (const char *s, qboolean fromQC) Cmd_ExecLevel=1; - if (!fromQC && host_client->controlled) //now see if it's meant to be from a slave client - { //'cmd 2 say hi' should - char *a=Cmd_Argv(0), *e; - int pnum = strtoul(a, &e, 10); - - //commands might be in the form of eg '2on2' so make sure that its fully numeric. - //KTX uses eg 'cmd 231', so don't take it if it can't be a seat, but there may be race conditions so we don't want error messages when it might have been a seat. - if (!*e && pnum >= 1 && pnum <= MAX_SPLITS) - { - client_t *sp; - for (sp = host_client; sp; sp = sp->controlled) - { - if (!--pnum) - { - host_client = sp; - break; - } - } - - sv_player = host_client->edict; - s = Cmd_Args(); - Cmd_ShiftArgs(1, false); - } - } - #ifdef Q2SERVER if (ISQ2CLIENT(host_client)) u = ucmdsq2; @@ -8330,6 +8305,13 @@ void SV_ExecuteClientMessage (client_t *cl) break; #endif + case clcfte_stringcmd_seat: + c = MSG_ReadByte(); + host_client = cl; + while (c --> 0 && host_client->controlled) + host_client = host_client->controlled; + sv_player = host_client->edict; + //fall through case clc_stringcmd: s = MSG_ReadString (); SV_ExecuteUserCommand (s, false); @@ -8576,6 +8558,13 @@ void SVQ2_ExecuteClientMessage (client_t *cl) ge->ClientUserinfoChanged (cl->q2edict, s); //tell the gamecode break; + case clcq2_stringcmd_seat: + c = MSG_ReadByte(); + host_client = cl; + while (c --> 0 && host_client->controlled) + host_client = host_client->controlled; + sv_player = host_client->edict; + //fall through case clcq2_stringcmd: s = MSG_ReadString (); SV_ExecuteUserCommand (s, false);