Hud scoreboard

Ok, so Quake 3 Arena has a nice detailed scoreboard. But what if you just need your score and your place on the scoreboard? It’s a bit irritating to break from the action to check them, so that’s why I had this idea. This tutorial will let you add a mini scoreboard right in your hud.

First, we need to know where we want it, I thought it would be a good idea to place it under that little score display in the lower right corner of the screen. However, this display is a little low so we’ll place this display higher. So, let’s find that piece of code. Set cgame as your active configuration and open up cg_draw.c. We want it in the lower right corner, that code starts somewhere at line 796.

Ok! Let’s start coding.

1. Adding the new function(s)

First, let’s place the code, that places the mini scoreboard on the screen, above of CG_DrawScores:

/*
=================
CG_DrawMiniScore

Draw the small scoreboard display
=================
*/
void CG_DrawMiniScore( int y, score_t *score, qboolean smallfont ) {
	char	string[1024];
	float	hcolor[4];
	float	color[4];
	clientInfo_t	*ci;
	int		x;
	int		w;
	int		sw;
	int		sh;
	const char *s;

	ci = &cgs.clientinfo[score->client];

	// draw the score line
	// Use S_COLOR_WHITE so colored names don't change score color.
	Com_sprintf(string, sizeof(string),
		"%s "S_COLOR_WHITE" %3i", ci->name, score->score);

	// colorize line
	if ( ci->team == TEAM_BLUE ) {
		hcolor[0] = 0;
		hcolor[1] = 0;
		hcolor[2] = 1;
	} else if ( ci->team == TEAM_RED ) {
		hcolor[0] = 1;
		hcolor[1] = 0;
		hcolor[2] = 0;
	} else {
		hcolor[0] = 0.7;
		hcolor[1] = 0.7;
		hcolor[2] = 0.7;
	}

	hcolor[3] = 0.33;

	if(smallfont)
	{
		sw = TINYCHAR_WIDTH;
		sh = TINYCHAR_HEIGHT;
	}
	else
	{
		sw = SMALLCHAR_WIDTH;
		sh = SMALLCHAR_HEIGHT;
	}

	x = 640;
	s = va( "%s", string );
	w = CG_DrawStrlen( s ) * sw + 8;
	x -= w;

	CG_FillRect( x+1, y, w, sh+1, hcolor );

	if(smallfont)
	{
		color[0] = color[1] = color[2] = color[3] = 1.0;
		CG_DrawStringExt( x, y, string, color, qtrue, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 );
	}
	else
	{
		CG_DrawSmallString( x, y, string, 1.0 );
	}
}

int CG_MakeMiniScoreboard( int y)
{
	int		i;
	score_t	*score;
	int		count;
	int players;
	clientInfo_t	*ci;
	qboolean smallfont;
	int lineHeight;

	players = 0;
	count = 0;

	for ( i = 0 ; i < cg.numScores ; i++ )
	{
		score = &cg.scores[i];
		ci = &cgs.clientinfo[ score->client ];

		if ( score->client < 0 || score->client >= cgs.maxclients ) {
			Com_Printf( "Bad score->client: %i\n", score->client );
			return 0;
		}

		// Don't show connecting people or spectators
		if ( score->ping == -1 || ci->team == TEAM_SPECTATOR ) {
			continue;
		}

		players++;
	}

	if (!players)
		return 0;

	if(players > 7) {
		lineHeight = (TINYCHAR_HEIGHT - 4)*2+1;
		smallfont = qtrue;
	}
	else {
		lineHeight = (SMALLCHAR_HEIGHT - 8)*2+1;
		smallfont = qfalse;
	}	

	// draw (at most) the top 11 players
	for ( i = 0 ; i < cg.numScores && count < 11 ; i++ )
	{
		score = &cg.scores[i];
		ci = &cgs.clientinfo[ score->client ];
		if ( score->ping == -1 || ci->team == TEAM_SPECTATOR ) {
			continue;
		}

		CG_DrawMiniScore( y + lineHeight * count, score, smallfont);
		count++;
	}

	return count;
}

/*
=================
CG_DrawScores

Draw the small two score display
=================
*/
static float CG_DrawScores( float y )
{

Now, let’s break it down:

CG_DrawMiniScore : Places it on the screen. If teamplay is on it make the lines team colored, else just white.

CG_MakeMiniScoreboard : Set’s everything up. As you can see from the comments, it doesn’t show connecting and spectating people. It calls CG_DrawMiniScore to write every client’s score down.

2. Calling the function

So! Now we have the code to place it on the screen, now we need to call it somewhere, but first, place the display higher so the scoreboard fits under.

static float CG_DrawScores( float y )
{
	const char	*s;
	int			s1, s2, score;
	int			x, w;
	int			v;
	vec4_t		color;
	float		y1;
	gitem_t		*item;

	s1 = cgs.scores1;
	s2 = cgs.scores2;

	y -=  BIGCHAR_HEIGHT + 8;
	y -=  BIGCHAR_HEIGHT * 14;

	y1 = y;

Now for the call: Go to the bottom of CG_DrawScores and place this code:

            if ( cgs.fraglimit ) {
                  s = va( "%2i", cgs.fraglimit );
                  w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8;
                  x -= w;
                  CG_DrawBigString( x + 4, y, s, 1.0F);
            }
      }

      if ( cg.scoresRequestTime + 5000 < cg.time )
      {
            // the scores are more than five seconds out of date,
			// so request new ones
			cg.scoresRequestTime = cg.time;
			trap_SendClientCommand( "score" );
	  }
	  CG_MakeMiniScoreboard( y + ((BIGCHAR_HEIGHT - 8)*2)+8 );

      return y1 - 8;
}

The first part of this piece of code, updates the scores (every 5 sec.). Then it does the call. If you want the scores to be updated faster, I think 2 seconds is the minimum.

3. Compile…

Now compile and run! Remember, it’s client side, so you can use it on any server that has pure server off.

I made the mini scoreboard for my own mod (P.U.F.F), just wanted to mention that.