From 7199e8ddded777709df090973cab53c040592b72 Mon Sep 17 00:00:00 2001
From: Ben Millwood <thebenmachine@gmail.com>
Date: Sat, 3 Oct 2009 13:15:37 +0000
Subject: * Featured server system   - The new master server sends some server
 records with an additional     flag for the client to mark them as featured -
 they will then appear     in a separate list.

---
 src/client/cl_main.c | 164 +++++++++++++++++++++++++++++++++++++++++++++------
 src/client/cl_ui.c   |  11 ++++
 src/client/client.h  |  11 ++++
 src/ui/ui_local.h    |   1 +
 src/ui/ui_main.c     |  15 ++++-
 5 files changed, 181 insertions(+), 21 deletions(-)

(limited to 'src')

diff --git a/src/client/cl_main.c b/src/client/cl_main.c
index 16ddcffc..47cf0196 100644
--- a/src/client/cl_main.c
+++ b/src/client/cl_main.c
@@ -2240,6 +2240,7 @@ void CL_InitServerInfo( serverInfo_t *server, netadr_t *address ) {
 	server->clients = 0;
 	server->hostName[0] = '\0';
 	server->mapName[0] = '\0';
+	server->label = NULL;
 	server->maxClients = 0;
 	server->maxPing = 0;
 	server->minPing = 0;
@@ -2249,6 +2250,102 @@ void CL_InitServerInfo( serverInfo_t *server, netadr_t *address ) {
 	server->netType = 0;
 }
 
+/*
+===================
+CL_GSRSequenceInformation
+
+Parses this packet's index and the number of packets from a master server's
+response. Updates the packet count and returns the index. Advances the data
+pointer as appropriate (but only when parsing was successful)
+
+The sequencing information isn't terribly useful at present (we can skip
+duplicate packets, but we don't bother to make sure we've got all of them).
+===================
+*/
+int CL_GSRSequenceInformation( byte **data )
+{
+	char *p = (char *)*data, *e;
+	int ind, num;
+	// '\0'-delimited fields: this packet's index, total number of packets
+	if( *p++ != '\0' )
+		return -1;
+
+	ind = strtol( p, (char **)&e, 10 );
+	if( *e++ != '\0' )
+		return -1;
+
+	num = strtol( e, (char **)&p, 10 );
+	if( *p++ != '\0' )
+		return -1;
+
+	if( num <= 0 || ind <= 0 || ind > num )
+		return -1; // nonsensical response
+
+	if( cls.numMasterPackets > 0 && num != cls.numMasterPackets )
+	{
+		// Assume we sent two getservers and somehow they changed in
+		// between - only use the results that arrive later
+		Com_DPrintf( "Master changed its mind about packet count!\n" );
+		cls.receivedMasterPackets = 0;
+		cls.numglobalservers = 0;
+		cls.numGlobalServerAddresses = 0;
+	}
+	cls.numMasterPackets = num;
+
+	// successfully parsed
+	*data = (byte *)p;
+	return ind;
+}
+
+/*
+===================
+CL_GSRFeaturedLabel
+
+Parses from the data an arbitrary text string labelling the servers in the
+following getserversresponse packet.
+Either this matches an existing label, or it is copied into a new one.
+The relevant buffer, or NULL, is returned, and *data is advanced as appropriate
+===================
+*/
+char *CL_GSRFeaturedLabel( byte **data )
+{
+	char label[ MAX_FEATLABEL_CHARS ] = { 0 }, *l = label;
+	int  i;
+
+	// copy until '\0' which indicates field break
+	// or slash which indicates beginning of server list
+	while( **data && **data != '\\' && **data != '/' )
+	{
+		if( l < &label[ sizeof( label ) - 1 ] )
+			*l = **data;
+		else if( l == &label[ sizeof( label ) - 1 ] )
+			Com_DPrintf( S_COLOR_YELLOW "Warning: "
+				"CL_GSRFeaturedLabel: overflow\n" );
+		l++, (*data)++;
+	}
+
+	if( !label[ 0 ] )
+		return NULL;
+
+	// find the label in the stored array
+	for( i = 0; i < cls.numFeaturedServerLabels; i++ )
+	{
+		l = cls.featuredServerLabels[ i ];
+		if( strcmp( label, l ) == 0 )
+			return l;
+	}
+	if( i == MAX_FEATURED_LABELS )
+	{
+		Com_DPrintf( S_COLOR_YELLOW "Warning: CL_GSRFeaturedLabel: "
+			"ran out of label space, dropping %s\n", label );
+		return NULL;
+	}
+	if( i == 0 )
+		l = cls.featuredServerLabels[ 0 ];
+	Q_strncpyz( l, label, sizeof( *cls.featuredServerLabels ) );
+	return l;
+}
+
 #define MAX_SERVERSPERPACKET	256
 
 /*
@@ -2262,13 +2359,18 @@ void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extend
 	int				numservers;
 	byte*			buffptr;
 	byte*			buffend;
+	char			*label = NULL;
 	
-	Com_Printf("CL_ServersResponsePacket\n");
+	Com_DPrintf("CL_ServersResponsePacket%s\n",
+		(extended) ? " (extended)" : "");
 
 	if (cls.numglobalservers == -1) {
 		// state to detect lack of servers or lack of response
 		cls.numglobalservers = 0;
 		cls.numGlobalServerAddresses = 0;
+		cls.numMasterPackets = 0;
+		cls.receivedMasterPackets = 0;
+		cls.numFeaturedServerLabels = 0;
 	}
 
 	// parse through server response string
@@ -2276,14 +2378,47 @@ void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extend
 	buffptr    = msg->data;
 	buffend    = buffptr + msg->cursize;
 
+	// skip header
+	buffptr += 4;
+
 	// advance to initial token
-	do
+	// I considered using strchr for this but I don't feel like relying
+	// on its behaviour with '\0'
+	while( *buffptr && *buffptr != '\\' && *buffptr != '/' )
 	{
-		if(*buffptr == '\\' || (extended && *buffptr == '/'))
-			break;
-		
 		buffptr++;
-	} while (buffptr < buffend);
+
+		if( buffptr >= buffend )
+			break;
+	}
+
+	if( *buffptr == '\0' )
+	{
+		int ind = CL_GSRSequenceInformation( &buffptr );
+		if( ind >= 0 )
+		{
+			// this denotes the start of new-syntax stuff
+			// have we already received this packet?
+			if( cls.receivedMasterPackets & ( 1 << ( ind - 1 ) ) )
+			{
+				Com_DPrintf( "CL_ServersResponsePacket: "
+					"received packet %d again, ignoring\n",
+					ind );
+				return;
+			}
+			// TODO: detect dropped packets and make another
+			// request
+			Com_DPrintf( "CL_ServersResponsePacket: packet "
+				"%d of %d\n", ind, cls.numMasterPackets );
+			cls.receivedMasterPackets |= ( 1 << ( ind - 1 ) );
+
+			label = CL_GSRFeaturedLabel( &buffptr );
+			Com_DPrintf( "CL_GSRFeaturedLabel: %s\n", label );
+		}
+		// now skip to the server list
+		for(; buffptr < buffend && *buffptr != '\\' && *buffptr != '/';
+			buffptr++ );
+	}
 
 	while (buffptr + 1 < buffend)
 	{
@@ -2339,6 +2474,7 @@ void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extend
 		serverInfo_t *server = &cls.globalServers[count];
 
 		CL_InitServerInfo( server, &addresses[i] );
+		server->label = label;
 		// advance to next slot
 		count++;
 	}
@@ -3725,7 +3861,6 @@ void CL_GlobalServers_f( void ) {
 	netadr_t	to;
 	int			count, i, masterNum;
 	char		command[1024], *masteraddress;
-	char		*cmdname;
 	
 	if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > 4)
 	{
@@ -3760,17 +3895,10 @@ void CL_GlobalServers_f( void ) {
 	cls.numglobalservers = -1;
 	cls.pingUpdateSource = AS_GLOBAL;
 
-	// Use the extended query for IPv6 masters
-	if (to.type == NA_IP6 || to.type == NA_MULTICAST6)
-	{
-		cmdname = "getserversExt " GAMENAME_FOR_MASTER;
-
-		// TODO: test if we only have an IPv6 connection. If it's the case,
-		//       request IPv6 servers only by appending " ipv6" to the command
-	}
-	else
-		cmdname = "getservers";
-	Com_sprintf( command, sizeof(command), "%s %s", cmdname, Cmd_Argv(2) );
+	// TODO: test if we only have an IPv6 connection. If it's the case,
+	//       request IPv6 servers only by appending " ipv6" to the command
+	Com_sprintf( command, sizeof(command), "getserversExt "
+		GAMENAME_FOR_MASTER " %s", Cmd_Argv(2) );
 
 	for (i=3; i < count; i++)
 	{
diff --git a/src/client/cl_ui.c b/src/client/cl_ui.c
index 49719824..34ae04ea 100644
--- a/src/client/cl_ui.c
+++ b/src/client/cl_ui.c
@@ -285,6 +285,8 @@ static void LAN_GetServerInfo( int source, int n, char *buf, int buflen ) {
 		buf[0] = '\0';
 		Info_SetValueForKey( info, "hostname", server->hostName);
 		Info_SetValueForKey( info, "mapname", server->mapName);
+		if( server->label )
+			Info_SetValueForKey( info, "label", server->label);
 		Info_SetValueForKey( info, "clients", va("%i",server->clients));
 		Info_SetValueForKey( info, "sv_maxclients", va("%i",server->maxClients));
 		Info_SetValueForKey( info, "ping", va("%i",server->ping));
@@ -375,6 +377,15 @@ static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int
 		return 0;
 	}
 
+	// featured servers on top
+	// this is not so that they are more noticeable but that codewise it
+	// makes it much simpler to have them contiguous in the list
+	// so changing this also requires changing the feederID counting and
+	// similar code that depends on them coming first
+	res = Q_stricmpn( server1->label, server2->label, MAX_FEATLABEL_CHARS );
+	if( res )
+		return -res;
+
 	res = 0;
 	switch( sortKey ) {
 		case SORT_HOST:
diff --git a/src/client/client.h b/src/client/client.h
index f19c6230..5a1106b4 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -288,6 +288,7 @@ typedef struct {
 	char	  	hostName[MAX_HOSTNAME_LENGTH];
 	char	  	mapName[MAX_NAME_LENGTH];
 	char	  	game[MAX_NAME_LENGTH];
+	char		*label; // for featured servers, NULL otherwise
 	int			netType;
 	int			gameType;
 	int		  	clients;
@@ -298,6 +299,9 @@ typedef struct {
 	qboolean	visible;
 } serverInfo_t;
 
+#define MAX_FEATURED_LABELS  8
+#define MAX_FEATLABEL_CHARS  1024
+
 typedef struct {
 	connstate_t	state;				// connection status
 
@@ -318,6 +322,10 @@ typedef struct {
 	int			realtime;			// ignores pause
 	int			realFrametime;		// ignoring pause, so console always works
 
+	// master server sequence information
+	int			numMasterPackets;
+	unsigned int		receivedMasterPackets; // bitfield
+
 	int			numlocalservers;
 	serverInfo_t	localServers[MAX_OTHER_SERVERS];
 
@@ -330,6 +338,9 @@ typedef struct {
 	int			numfavoriteservers;
 	serverInfo_t	favoriteServers[MAX_OTHER_SERVERS];
 
+	int  numFeaturedServerLabels;
+	char featuredServerLabels[ MAX_FEATURED_LABELS ][ MAX_FEATLABEL_CHARS ];
+
 	int pingUpdateSource;		// source currently pinging or updating
 
 	// update server info
diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h
index 48eea7db..938f1e23 100644
--- a/src/ui/ui_local.h
+++ b/src/ui/ui_local.h
@@ -102,6 +102,7 @@ typedef struct serverStatus_s
   int    currentServer;
   int    displayServers[MAX_DISPLAY_SERVERS];
   int    numDisplayServers;
+  int    numFeaturedServers;
   int    numPlayersOnServers;
   int    nextDisplayRefresh;
   int    nextSortTime;
diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c
index 441ce79a..43aaa0af 100644
--- a/src/ui/ui_main.c
+++ b/src/ui/ui_main.c
@@ -907,6 +907,8 @@ static void UI_BuildServerDisplayList( qboolean force )
       if( ping > 0 )
       {
         trap_LAN_MarkServerVisible( ui_netSource.integer, i, qfalse );
+        if( Info_ValueForKey( info, "label" )[0] )
+          uiInfo.serverStatus.numFeaturedServers++;
         numinvisible++;
       }
     }
@@ -1043,6 +1045,7 @@ static void UI_StartServerRefresh( qboolean full )
   uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000;
   // clear number of displayed servers
   uiInfo.serverStatus.numDisplayServers = 0;
+  uiInfo.serverStatus.numFeaturedServers = 0;
   uiInfo.serverStatus.numPlayersOnServers = 0;
   // mark all servers as visible so we store ping updates for them
   trap_LAN_MarkServerVisible( ui_netSource.integer, -1, qtrue );
@@ -3342,7 +3345,10 @@ static int UI_FeederCount( float feederID )
   else if( feederID == FEEDER_MAPS )
     return uiInfo.mapCount;
   else if( feederID == FEEDER_SERVERS )
-    return uiInfo.serverStatus.numDisplayServers;
+    return uiInfo.serverStatus.numDisplayServers -
+           uiInfo.serverStatus.numFeaturedServers;
+  else if( feederID == FEEDER_FEATURED )
+    return uiInfo.serverStatus.numFeaturedServers;
   else if( feederID == FEEDER_SERVERSTATUS )
     return uiInfo.serverStatusInfo.numLines;
   else if( feederID == FEEDER_FINDPLAYER )
@@ -3434,12 +3440,15 @@ static const char *UI_FeederItemText( float feederID, int index, int column, qha
     int actual;
     return UI_SelectedMap( index, &actual );
   }
-  else if( feederID == FEEDER_SERVERS )
+  else if( feederID == FEEDER_SERVERS || feederID == FEEDER_FEATURED )
   {
-    if( index >= 0 && index < uiInfo.serverStatus.numDisplayServers )
+    if( index >= 0 && index < UI_FeederCount( feederID ) )
     {
       int ping;
 
+      if( feederID == FEEDER_SERVERS )
+        index += UI_FeederCount( FEEDER_FEATURED );
+
       if( lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000 )
       {
         trap_LAN_GetServerInfo( ui_netSource.integer, uiInfo.serverStatus.displayServers[index],
-- 
cgit