// Copyright (C) 2001-2002 Raven Software. // // cg_scoreboard -- draw the scoreboard on top of the game screen #include "cg_local.h" #define SB_HEADER 86 #define SB_TOP (SB_HEADER+32) // Where the status bar starts, so we don't overwrite it #define SB_STATUSBAR 420 #define SB_MAXCLIENTS_NORMAL 12 #define SB_MAXCLIENTS_MORE 26 #define SB_MAXCLIENTS_ALOT 32 // Used when interleaved #define SB_HEADER_WIDTH (580) #define SB_HEADER_HEIGHT 30 #define SB_HEADER_X ((640-SB_HEADER_WIDTH)/2) #define SB_HEADER_Y 86 #define SB_SCORELINE_X (SB_HEADER_X+30) #define SB_SCORELINE_Y 120 #define SB_SCORELINE_WIDTH (SB_HEADER_WIDTH-60) #define SB_NAME_X (SB_SCORELINE_X) #define SB_SCORE_X (SB_SCORELINE_X+287) #define SB_KILLS_X (SB_SCORELINE_X+335) #define SB_DEATHS_X (SB_SCORELINE_X+384) #define SB_PING_X (SB_SCORELINE_X+440) #define SB_TIME_X (SB_SCORELINE_X+489) int sb_lineHeight; int sb_maxClients; float sb_nameFontScale; float sb_numberFontScale; float sb_readyFontScale; /* ================= CG_DrawClientScore ================= */ static void CG_DrawClientScore( float x, float y, float w, score_t* score, float* color ) { clientInfo_t* ci; vec4_t dataColor; vec4_t nameColor; const char* s; float f; nameColor[3] = dataColor[3] = 1.0f; // Validate the score if ( score->client < 0 || score->client >= cgs.maxclients ) { Com_Printf( "Bad score->client: %i\n", score->client ); return; } // Convienience ci = &cgs.clientinfo[score->client]; CG_DrawPic ( x - 5, y, w, sb_lineHeight, cgs.media.scoreboardLine ); // highlight your position if ( score->client == cg.snap->ps.clientNum ) { vec4_t hcolor; hcolor[0] = 1.0f; hcolor[1] = 1.0f; hcolor[2] = 1.0f; hcolor[3] = .10f; CG_FillRect( x - 5, y, w, sb_lineHeight, hcolor ); VectorSet ( nameColor, 1.0f, 1.0f, 1.0f ); VectorSet ( dataColor, 0.5f, 0.5f, 0.5f ); } else if ( (cg.snap->ps.pm_type == PM_DEAD) && score->client == cg.snap->ps.persistant[PERS_ATTACKER] ) { vec4_t hcolor; hcolor[0] = 1.0f; hcolor[1] = 1.0f; hcolor[2] = 1.0f; hcolor[3] = .10f; CG_FillRect( x - 5, y, w, sb_lineHeight, hcolor ); VectorCopy ( color, nameColor ); VectorSet ( dataColor, 0.5f, 0.5f, 0.5f ); } else { VectorCopy ( color, nameColor ); VectorSet ( dataColor, 0.3f, 0.3f, 0.3f ); if ( ci->ghost ) { VectorScale ( nameColor, 0.6f, nameColor ); } } CG_DrawText( x, y, cgs.media.hudFont, sb_nameFontScale, nameColor, ci->name, 24, DT_OUTLINE ); s = va("%i", score->score ); f = trap_R_GetTextWidth ( s, cgs.media.hudFont, sb_nameFontScale, 0 ); CG_DrawText( x + w - 10 - f, y, cgs.media.hudFont, sb_nameFontScale, nameColor, va("%i", score->score), 0, DT_OUTLINE ); // Draw skull if dead and not in intermission if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { vec3_t deadColor; deadColor[0] = 0.60f; deadColor[1] = 0.60f; deadColor[2] = 0.60f; deadColor[3] = 1.0f; CG_DrawText( x + w - 70, y + 3, cgs.media.hudFont, sb_readyFontScale, deadColor, "READY", 0, DT_OUTLINE ); } } else if ( ci->ghost ) { CG_DrawPic ( x + w - 70, y + 1, sb_numberFontScale * 0.8f, sb_numberFontScale * 0.8f, cgs.media.deadShader ); } // Draw any gametype items the guy is carrying else { float xx = x + w - 70; int i; for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) { centity_t* cent; cent = CG_GetEntity ( score->client); // No have item, no draw it if ( !(ci->gametypeitems & (1<kills, score->deaths); f = trap_R_GetTextWidth ( s, cgs.media.hudFont, sb_readyFontScale, 0 ); CG_DrawText( x + w - 10 - f, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, s, 0, 0 ); CG_DrawText( x, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, va("id: %i", score->client ), 0, 0 ); CG_DrawText( x + 40, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, va("ping: %i", score->ping ), 0, 0 ); CG_DrawText( x + 95, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, va("time: %i", score->time ), 0, 0 ); if ( score->teamkillDamage ) { CG_DrawText( x + 150, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, va("tk: %i%%", score->teamkillDamage ), 0, 0 ); } } /* ================= CG_TeamScoreboard ================= */ static int CG_TeamScoreboard( float x, float y, float w, team_t team ) { int i; int skipped; float color[4]; int count; clientInfo_t *ci; const char* s; int players; qboolean drawnClient; // Do we make sure the current client is drawn? drawnClient = qtrue; if ( cg.scores [ cg.snap->ps.clientNum ].team == team ) { drawnClient = qfalse; } // Determine the color for this team switch ( team ) { case TEAM_RED: VectorCopy4 ( g_color_table[ColorIndex(COLOR_RED)], color ); break; case TEAM_BLUE: VectorCopy4 ( g_color_table[ColorIndex(COLOR_BLUE)], color ); break; case TEAM_FREE: VectorCopy4 ( g_color_table[ColorIndex(COLOR_GREEN)], color ); break; case TEAM_SPECTATOR: default: VectorCopy4 ( colorWhite, color ); break; } // Draw as many clients as we can for this team for ( skipped = -1, count = 0, i = 0 ; i < cg.numScores && count < sb_maxClients ; i++ ) { score_t* score; score = &cg.scores[i]; ci = &cgs.clientinfo[ score->client ]; if ( team != score->team ) { continue; } if ( count == sb_maxClients - 1 && !drawnClient ) { if ( score->client != cg.snap->ps.clientNum ) { skipped = i; continue; } drawnClient = qtrue; } CG_DrawClientScore( x, y + SB_HEADER_HEIGHT + sb_lineHeight * count, w, score, color ); count++; } if ( skipped != -1 && count < sb_maxClients ) { CG_DrawClientScore( x, y + SB_HEADER_HEIGHT + sb_lineHeight * count, w, &cg.scores[skipped], color ); count++; } s = ""; switch ( team ) { case TEAM_RED: s = "RED TEAM"; players = ui_info_redcount.integer; break; case TEAM_BLUE: s = "BLUE TEAM"; players = ui_info_bluecount.integer; break; case TEAM_FREE: s = "PLAYERS"; players = ui_info_freecount.integer; break; default: case TEAM_SPECTATOR: s = "SPECTATORS"; players = ui_info_speccount.integer; break; } // Use the same team color here, but alpha it a bit. color[3] = 0.6f; // Draw the header information for this team CG_DrawPic ( x - 5, y, w, 25, cgs.media.scoreboardHeader ); CG_FillRect( x - 5, y, w, 25, color ); CG_DrawText ( x, y, cgs.media.hudFont, 0.40f, colorWhite, va("%s", s), 0, 0 ); CG_DrawText ( x, y + 13, cgs.media.hudFont, 0.30f, colorWhite, va("players: %d", players), 0, 0 ); // Draw the totals if this is the red or blue team if ( (team == TEAM_RED || team == TEAM_BLUE)) { const char* s; float f; s = va("%d",(cg.teamScores[team-TEAM_RED])); f = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.43f, 0 ); CG_DrawText ( x + w - 10 - f, y, cgs.media.hudFont, 0.43f, colorWhite, s, 0, DT_OUTLINE ); } if ( count ) { CG_DrawPic ( x - 5, y + SB_HEADER_HEIGHT + sb_lineHeight * count, w, sb_lineHeight, cgs.media.scoreboardFooter ); } y = count * sb_lineHeight + y + 10; if ( y > cg.scoreBoardBottom ) { cg.scoreBoardBottom = y; } return y; } /* ================= CG_DrawNormalScoreboard Draws a scoreboard that has no teams ================= */ qboolean CG_DrawNormalScoreboard ( float y ) { cg.scoreBoardBottom = 0; // DRaw the game timer and the game type CG_DrawText ( 385, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, "Game Time:", 0, DT_OUTLINE ); CG_DrawTimer ( 455, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, DT_OUTLINE, cg.time - cgs.levelStartTime ); CG_DrawText ( 150, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, cgs.gametypeData->displayName, 0, DT_OUTLINE ); if ( ui_info_speccount.integer ) { CG_FillRect ( 0, 470, 640, 10, colorBlack ); CG_DrawText ( 5, 470, cgs.media.hudFont, 0.30f, colorWhite, va("SPECTATORS: %s", cg.scoreBoardSpectators), 0, 0 ); } if ( ui_info_freecount.integer > 10 ) { sb_maxClients = 16; sb_lineHeight = 20; sb_nameFontScale = 0.35f; sb_readyFontScale = 0.30f; sb_numberFontScale = trap_R_GetTextHeight ( "W", cgs.media.hudFont, sb_nameFontScale, 0 ); } else { sb_maxClients = 10; sb_lineHeight = 30; sb_nameFontScale = 0.43f; sb_readyFontScale = 0.30f; sb_numberFontScale = trap_R_GetTextHeight ( "W", cgs.media.hudFont, sb_nameFontScale, 0 ) + 4; } CG_TeamScoreboard ( 150, y, 350, TEAM_FREE ); return qtrue; } /* ================= CG_DrawTeamScoreboard Draw the normal in-game scoreboard ================= */ qboolean CG_DrawTeamScoreboard( float y ) { qboolean redFirst = qfalse; cg.scoreBoardBottom = 0; // DRaw the game timer and the game type CG_DrawText ( 470, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, "Game Time:", 0, DT_OUTLINE ); CG_DrawTimer ( 540, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, DT_OUTLINE, cg.time - cgs.levelStartTime ); CG_DrawText ( 60, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, cgs.gametypeData->displayName, 0, DT_OUTLINE ); if ( ui_info_speccount.integer ) { CG_FillRect ( 0, 470, 640, 10, colorBlack ); CG_DrawText ( 5, 470, cgs.media.hudFont, 0.30f, colorWhite, va("SPECTATORS: %s", cg.scoreBoardSpectators), 0, 0 ); } if ( ui_info_redcount.integer > 10 || ui_info_bluecount.integer > 10 ) { sb_maxClients = 16; sb_lineHeight = 20; sb_nameFontScale = 0.35f; sb_readyFontScale = 0.30f; sb_numberFontScale = trap_R_GetTextHeight ( "W", cgs.media.hudFont, sb_nameFontScale, 0 ); } else { sb_maxClients = 10; sb_lineHeight = 30; sb_nameFontScale = 0.43f; sb_readyFontScale = 0.30f; sb_numberFontScale = trap_R_GetTextHeight ( "W", cgs.media.hudFont, sb_nameFontScale, 0 ) + 4; } // If there are more scores than the scoreboard can show then show the // players team first rather than the winning team if ( cgs.clientinfo[cg.clientNum].team == TEAM_SPECTATOR ) { if ( cg.teamScores[0] >= cg.teamScores[1] ) { redFirst = qtrue; } } else if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) { redFirst = qtrue; } if ( redFirst ) { CG_TeamScoreboard( 50, y, 265, TEAM_RED ); CG_TeamScoreboard( 330, y, 265, TEAM_BLUE ); } else { CG_TeamScoreboard( 330, y, 265, TEAM_RED ); CG_TeamScoreboard( 50, y, 265, TEAM_BLUE ); } return qtrue; } /* ================= CG_DrawScoreboard Draws either a team or a normal scoreboard ================= */ qboolean CG_DrawScoreboard ( void ) { float y; float w; // don't draw amuthing if the menu or console is up if ( cg_paused.integer ) { cg.deferredPlayerLoading = 0; return qfalse; } // don't draw scoreboard during death while warmup up if ( cg.warmup && !cg.showScores ) { return qfalse; } if ( !cg.showScores && cg.predictedPlayerState.pm_type != PM_DEAD && cg.predictedPlayerState.pm_type != PM_INTERMISSION ) { return qfalse; } // scoreboard y = 45; if ( cgs.gametypeData->teams ) { if ( ui_info_redcount.integer < 10 && ui_info_bluecount.integer < 10 ) { y += 50; } } else if ( ui_info_freecount.integer < 10 ) { y += 50; } // Draw any gameover text if ( cgs.gameover[0] ) { w = trap_R_GetTextWidth ( cgs.gameover, cgs.media.hudFont, 0.48f, 0 ); CG_DrawText ( 320 - w / 2, y - 30, cgs.media.hudFont, 0.48f, colorWhite, cgs.gameover, 0, DT_OUTLINE ); } else if ( cgs.gametypeMessageTime > cg.time ) { w = trap_R_GetTextWidth ( cgs.gametypeMessage, cgs.media.hudFont, 0.48f, 0 ); CG_DrawText ( 320 - w / 2, y - 30, cgs.media.hudFont, 0.48f, colorWhite, cgs.gametypeMessage, 0, DT_OUTLINE ); } // Should we draw who killed you? else if ( cg.snap->ps.pm_type == PM_DEAD && cg.snap->ps.persistant[PERS_ATTACKER] < MAX_CLIENTS && cg.snap->ps.persistant[PERS_ATTACKER] != cg.snap->ps.clientNum ) { const char* s; s = va("Killed by %s", cgs.clientinfo[cg.snap->ps.persistant[PERS_ATTACKER]].name ); w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.48f, 0 ); CG_DrawText ( 320 - w / 2, y - 30, cgs.media.hudFont, 0.48f, colorWhite, s, 0, DT_OUTLINE ); } else if ( cgs.gametypeData->teams ) { const char* s; if ( cg.teamScores[0] == cg.teamScores[1] ) { s = va ( "Game Tied at %d", cg.teamScores[0] ); } else if ( cg.teamScores[0] > cg.teamScores[1] ) { s = va ( "Red leads Blue by %d", cg.teamScores[0] - cg.teamScores[1] ); } else { s = va ( "Blue leads Red by %d", cg.teamScores[1] - cg.teamScores[0] ); } w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.48f, 0 ); CG_DrawText ( 320 - w / 2, y - 30, cgs.media.hudFont, 0.48f, colorWhite, s, 0, DT_OUTLINE ); } // load any models that have been deferred cg.deferredPlayerLoading++; if ( cgs.gametypeData->teams ) { return CG_DrawTeamScoreboard ( y ); } return CG_DrawNormalScoreboard ( y ); }