}
}
+// look for anything interesting like player IP addresses or ping reports
+qboolean CL_ExaminePrintString(const char *text)
+{
+ const char *t;
+ if (!strcmp(text, "Client ping times:\n"))
+ {
+ cl.parsingtextmode = CL_PARSETEXTMODE_PING;
+ cl.parsingtextplayerindex = 0;
+ return !sb_showscores;
+ }
+ if (!strncmp(text, "host: ", 9))
+ {
+ cl.parsingtextmode = CL_PARSETEXTMODE_STATUS;
+ cl.parsingtextplayerindex = 0;
+ return true;
+ }
+ if (cl.parsingtextmode == CL_PARSETEXTMODE_PING)
+ {
+ // if anything goes wrong, we'll assume this is not a ping report
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
+ t = text;
+ while (*t == ' ')
+ t++;
+ if (*t >= '0' && *t <= '9')
+ {
+ int ping = atoi(t);
+ while (*t >= '0' && *t <= '9')
+ t++;
+ if (*t == ' ')
+ {
+ int charindex = 0;
+ t++;
+ for (charindex = 0;cl.scores[cl.parsingtextplayerindex].name[charindex] == t[charindex];charindex++)
+ ;
+ if (cl.scores[cl.parsingtextplayerindex].name[charindex] == 0 && t[charindex] == '\n')
+ {
+ cl.scores[cl.parsingtextplayerindex].qw_ping = bound(0, ping, 9999);
+ for (cl.parsingtextplayerindex++;cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0];cl.parsingtextplayerindex++)
+ ;
+ if (cl.parsingtextplayerindex < cl.maxclients)
+ {
+ // we parsed a valid ping entry, so expect another to follow
+ cl.parsingtextmode = CL_PARSETEXTMODE_PING;
+ }
+ return !sb_showscores;
+ }
+ }
+ }
+ }
+ if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS)
+ {
+ if (!strncmp(text, "players: ", 9))
+ {
+ cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID;
+ cl.parsingtextplayerindex = 0;
+ return true;
+ }
+ else if (!strstr(text, ": "))
+ {
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE; // status report ended
+ return true;
+ }
+ }
+ if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERID)
+ {
+ // if anything goes wrong, we'll assume this is not a status report
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
+ if (text[0] == '#' && text[1] >= '0' && text[1] <= '9')
+ {
+ t = text + 1;
+ cl.parsingtextplayerindex = atoi(t);
+ while (*t >= '0' && *t <= '9')
+ t++;
+ if (*t == ' ')
+ {
+ cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERIP;
+ return true;
+ }
+ }
+ }
+ if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERIP)
+ {
+ // if anything goes wrong, we'll assume this is not a status report
+ cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
+ if (text[0] == ' ')
+ {
+ t = text;
+ while (*t == ' ')
+ t++;
+ // botclient is perfectly valid, but we don't care about bots
+ if (strcmp(t, "botclient\n"))
+ {
+ lhnetaddress_t address;
+ LHNETADDRESS_FromString(&address, t, 0);
+ // TODO: log the IP address and player name?
+ }
+ cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID;
+ return true;
+ }
+ }
+ return true;
+}
+
#define SHOWNET(x) if(cl_shownet.integer==2)Con_Printf("%3i:%s\n", msg_readcount-1, x);
//[515]: csqc
case qw_svc_print:
i = MSG_ReadByte();
- if (i == 3) // chat
- CSQC_AddPrintText(va("\1%s", MSG_ReadString())); //[515]: csqc
- else
- CSQC_AddPrintText(MSG_ReadString());
+ temp = MSG_ReadString();
+ if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports
+ {
+ if (i == 3) // chat
+ CSQC_AddPrintText(va("\1%s", temp)); //[515]: csqc
+ else
+ CSQC_AddPrintText(temp);
+ }
break;
case qw_svc_centerprint:
break;
case svc_print:
- CSQC_AddPrintText(MSG_ReadString()); //[515]: csqc
+ temp = MSG_ReadString();
+ if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports
+ CSQC_AddPrintText(temp); //[515]: csqc
break;
case svc_centerprint:
}
particle_t;
+typedef enum cl_parsingtextmode_e
+{
+ CL_PARSETEXTMODE_NONE,
+ CL_PARSETEXTMODE_PING,
+ CL_PARSETEXTMODE_STATUS,
+ CL_PARSETEXTMODE_STATUS_PLAYERID,
+ CL_PARSETEXTMODE_STATUS_PLAYERIP
+}
+cl_parsingtextmode_t;
+
//
// the client_state_t structure is wiped completely at every
// server signon
// [cl.maxclients]
scoreboard_t *scores;
+ // keep track of svc_print parsing state (analyzes ping reports and status reports)
+ cl_parsingtextmode_t parsingtextmode;
+ int parsingtextplayerindex;
+
// entity database stuff
// latest received entity frame numbers
#define LATESTFRAMENUMS 3
#include "cl_screen.h"
+extern qboolean sb_showscores;
+
#define NUMCROSSHAIRS 32
extern cachepic_t *r_crosshairs[NUMCROSSHAIRS+1];
if (cmd_source == src_command)
{
- if (!sv.active)
+ // if running a client, try to send over network so the client's status report parser will see the report
+ if (cls.state == ca_connected)
{
Cmd_ForwardToServer ();
return;
else
print = SV_ClientPrintf;
+ if (!sv.active)
+ return;
+
for (players = 0, j = 0;j < svs.maxclients;j++)
if (svs.clients[j].active)
players++;
}
else
hours = 0;
- print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", j+1, client->name, (int)client->edict->fields.server->frags, hours, minutes, seconds);
+ print ("#%-3u %-16.16s %3i %2i:%02i:%02i\n", j+1, client->name, (int)client->edict->fields.server->frags, hours, minutes, seconds);
print (" %s\n", client->netconnection ? client->netconnection->address : "botclient");
}
}
==================
*/
+void Host_Pings_f (void); // called by Host_Ping_f
void Host_Ping_f (void)
{
int i;
if (cmd_source == src_command)
{
- if (!sv.active)
+ // if running a client, try to send over network so the client's ping report parser will see the report
+ if (cls.state == ca_connected)
{
Cmd_ForwardToServer ();
return;
else
print = SV_ClientPrintf;
+ if (!sv.active)
+ return;
+
print("Client ping times:\n");
for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
{
continue;
print("%4i %s\n", (int)floor(client->ping*1000+0.5), client->name);
}
+
+ // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report)
+ Host_Pings_f();
}
/*
MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
MSG_WriteString(&cls.netcon->message, "pings");
}
+ else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5 || cls.protocol == PROTOCOL_DARKPLACES6 || cls.protocol == PROTOCOL_DARKPLACES7)
+ {
+ // these servers usually lack the pings command and so a less efficient "ping" command must be sent, which on modern DP servers will also reply with a pingplreport command after the ping listing
+ MSG_WriteByte(&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, "ping");
+ }
else
{
+ // newer server definitely has pings command, so use it for more efficiency
MSG_WriteByte(&cls.netcon->message, clc_stringcmd);
MSG_WriteString(&cls.netcon->message, "pings");
}