/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2002-2021 Q3Rally Team (Per Thormann - q3rally@gmail.com) This file is part of q3rally source code. q3rally source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. q3rally source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with q3rally; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cg_scoreboard -- draw the scoreboard on top of the game screen #include "cg_local.h" // Q3Rally Code Start // #define SCOREBOARD_X (0) #define SCOREBOARD_X (32) // END #define SB_HEADER 86 #define SB_TOP (SB_HEADER+32) // Where the status bar starts, so we don't overwrite it // Q3Rally Code Start // #define SB_STATUSBAR 420 #define SB_STATUSBAR 400 // END #define SB_NORMAL_HEIGHT 40 #define SB_INTER_HEIGHT 16 // interleaved height #define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) #define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) // Used when interleaved #define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) #define SB_LEFT_HEAD_X (SCOREBOARD_X+32) #define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) #define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) // Normal #define SB_BOTICON_X (SCOREBOARD_X+32) #define SB_HEAD_X (SCOREBOARD_X+64) // Q3Rally Code Start // #define SB_SCORELINE_X 112 #define SB_SCORELINE_X 128 // END #define SB_RATING_WIDTH (6 * BIGCHAR_WIDTH) // width 6 // Q3Rally Code Start //#define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6 #define SB_RATING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH) // width 6 /* #define SB_PING_X (SB_SCORELINE_X + 12 * BIGCHAR_WIDTH + 8) // width 5 #define SB_TIME_X (SB_SCORELINE_X + 17 * BIGCHAR_WIDTH + 8) // width 5 #define SB_NAME_X (SB_SCORELINE_X + 22 * BIGCHAR_WIDTH) // width 15 */ #define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6 #define SB_PING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH + 8) // width 5 #define SB_TIME_X (SB_SCORELINE_X + 11 * BIGCHAR_WIDTH + 8) // width 5 #define SB_TOTAL_TIME_X (SB_SCORELINE_X + 16 * BIGCHAR_WIDTH + 8) // width 5 #define SB_NAME_X (SB_SCORELINE_X + 21 * BIGCHAR_WIDTH) // width 15 // END // The new and improved score board // // In cases where the number of clients is high, the score board heads are interleaved // here's the layout // // 0 32 80 112 144 240 320 400 <-- pixel position // bot head bot head score ping time name // // wins/losses are drawn on bot icon now static qboolean localClient; // true if local client has been displayed /* ================= CG_DrawScoreboard ================= */ static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { char string[1024]; clientInfo_t *ci; int iconx, headx; // Q3Rally Code Start // vec3_t headAngles; int totalTime; char *ttime, *time; // END if ( score->client < 0 || score->client >= cgs.maxclients ) { Com_Printf( "Bad score->client: %i\n", score->client ); return; } ci = &cgs.clientinfo[score->client]; // Q3Rally Code Start // iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2); // headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); iconx = SB_BOTICON_X - (SB_RATING_WIDTH / 2); headx = SB_HEAD_X - (SB_RATING_WIDTH / 2); // END // draw the handicap or bot skill marker (unless player has flag) if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); } } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse ); } } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse ); } } // STONELANCE - draw flag beside winner else if (cg_entities[score->client].finishRaceTime && cg_entities[score->client].currentPosition == 1){ // UPDATE - enable this /* if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, -1 ); } else { CG_DrawFlagModel( iconx, y, 16, 16, -1 ); } */ } // END else { if ( ci->botSkill > 0 && ci->botSkill <= 5 ) { if ( cg_drawIcons.integer ) { if( largeFormat ) { CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); } else { CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); } } } else if ( ci->handicap < 100 ) { Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); // STONELANCE - removed gametype /* if ( cgs.gametype == GT_TOURNAMENT ) CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); else */ // END CG_DrawSmallStringColor( iconx, y, string, color ); } // draw the wins / losses // STONELANCE - removed gametype /* if ( cgs.gametype == GT_TOURNAMENT ) { Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); if( ci->handicap < 100 && !ci->botSkill ) { CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); } else { CG_DrawSmallStringColor( iconx, y, string, color ); } } */ // END } // draw the face // Q3Rally Code Start /* VectorClear( headAngles ); headAngles[YAW] = 180; if( largeFormat ) { CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, score->client, headAngles ); } else { CG_DrawHead( headx, y, 16, 16, score->client, headAngles ); } */ if( largeFormat ) CG_DrawPic( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, cgs.clientinfo[score->client].modelIcon ); else CG_DrawPic( headx, y, 16, 16, cgs.clientinfo[score->client].modelIcon ); // times if (cg_entities[score->client].finishRaceTime) totalTime = cg_entities[score->client].finishRaceTime - score->time; else if (score->time) totalTime = cg.time - score->time; else totalTime = 0; if (totalTime < 0) totalTime = 0; ttime = getStringForTime(totalTime); if ( isRallyRace() && !isRallyNonDMRace() && score->score > 0){ totalTime -= TIME_BONUS_PER_FRAG * score->score; time = getStringForTimeDuration(0, totalTime); } else time = ttime; // END #ifdef MISSIONPACK // draw the team task if ( ci->teamTask != TEAMTASK_NONE ) { if ( ci->teamTask == TEAMTASK_OFFENSE ) { CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader ); } else if ( ci->teamTask == TEAMTASK_DEFENSE ) { CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader ); } } #endif // draw the score line if ( score->ping == -1 ) { Com_sprintf(string, sizeof(string), " connecting %s", ci->name); } else if ( ci->team == TEAM_SPECTATOR // Q3Rally Code Start // && !cg_entities[score->client].finishRaceTime // END ) { Com_sprintf(string, sizeof(string), // Q3Rally Code Start // " SPECT %3i %4i %s", score->ping, score->time, ci->name); " SPECT %9i %s %s %s", score->ping, time, ttime, ci->name); // END } else { Com_sprintf(string, sizeof(string), // Q3Rally Code Start // "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name); "%10i %14i %s %s %s", score->score, score->ping, time, ttime, ci->name); // END } // highlight your position if ( score->client == cg.snap->ps.clientNum ) { float hcolor[4]; int rank; localClient = qtrue; if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cgs.gametype >= GT_TEAM ) { rank = -1; } else { rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; } if ( rank == 0 ) { hcolor[0] = 0; hcolor[1] = 0; hcolor[2] = 0.7f; } else if ( rank == 1 ) { hcolor[0] = 0.7f; hcolor[1] = 0; hcolor[2] = 0; } else if ( rank == 2 ) { hcolor[0] = 0.7f; hcolor[1] = 0.7f; hcolor[2] = 0; } else { hcolor[0] = 0.7f; hcolor[1] = 0.7f; hcolor[2] = 0.7f; } hcolor[3] = fade * 0.7; // Q3Rally Code Start // CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y, // 640 - SB_SCORELINE_X - BIGCHAR_WIDTH, BIGCHAR_HEIGHT+1, hcolor ); CG_FillRect( SB_SCORE_X - (SB_RATING_WIDTH / 2), y, 560 - SB_SCORELINE_X - SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT+1, hcolor ); // END } // Q3Rally Code Start // CG_DrawBigString( SB_SCORELINE_X + (SB_RATING_WIDTH / 2), y, string, fade ); CG_DrawSmallString( SB_SCORELINE_X - (SB_RATING_WIDTH / 2), y, string, fade ); // END // add the "ready" marker for intermission exiting if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { // Q3Rally Code Start // CG_DrawBigStringColor( iconx, y, "READY", color ); CG_DrawSmallStringColor( iconx, y, "READY", color ); // END } } /* ================= CG_TeamScoreboard ================= */ static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) { int i; score_t *score; float color[4]; int count; clientInfo_t *ci; color[0] = color[1] = color[2] = 1.0; color[3] = fade; count = 0; for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) { score = &cg.scores[i]; ci = &cgs.clientinfo[ score->client ]; if ( team != ci->team ) { continue; } CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); count++; } return count; } /* ================= CG_DrawScoreboard Draw the normal in-game scoreboard ================= */ qboolean CG_DrawOldScoreboard( void ) { int x, y, w, i, n1, n2; float fade; float *fadeColor; char *s; int maxClients; int lineHeight; int topBorderSize, bottomBorderSize; // Q3Rally Code Start int team; char *teamName; // END // don't draw amuthing if the menu or console is up if ( cg_paused.integer ) { cg.deferredPlayerLoading = 0; return qfalse; } if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { 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 ) { fade = 1.0; fadeColor = colorWhite; } else { fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); if ( !fadeColor ) { // next time scoreboard comes up, don't print killer cg.deferredPlayerLoading = 0; cg.killerName[0] = 0; return qfalse; } fade = *fadeColor; } // Q3Rally Code Start if (cg.scoresRequestTime + 2000 < cg.time){ cg.scoresRequestTime = cg.time; trap_SendClientCommand( "score" ); /* UPDATE: need this? if (cgs.gametype == GT_TEAM_RACING || cgs.gametype == GT_TEAM_RACING_DM){ trap_SendClientCommand( "times" ); } */ } // END // fragged by ... line if ( cg.killerName[0] ) { s = va("Fragged by %s", cg.killerName ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; y = 40; CG_DrawBigString( x, y, s, fade ); } // current rank if ( cgs.gametype < GT_TEAM) { if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { s = va("%s place with %i", CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), cg.snap->ps.persistant[PERS_SCORE] ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; y = 60; CG_DrawBigString( x, y, s, fade ); } } else { // Q3Rally Code Start if ( TiedWinner() ) { team = GetTeamAtRank(1); s = va("Winners are tied at %i", cg.teamScores[team-TEAM_RED] ); } else { team = GetTeamAtRank(1); switch(team){ case TEAM_RED: teamName = "Red"; break; case TEAM_BLUE: teamName = "Blue"; break; case TEAM_GREEN: teamName = "Green"; break; case TEAM_YELLOW: teamName = "Yellow"; break; default: teamName = "Unknown"; break; } s = va("%s leads with %i", teamName, cg.teamScores[team-TEAM_RED] ); } /* if ( cg.teamScores[0] == cg.teamScores[1] ) { s = va("Teams are tied at %i", cg.teamScores[0] ); } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { s = va("Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] ); } else { s = va("Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] ); } */ // END w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; y = 60; CG_DrawBigString( x, y, s, fade ); } // scoreboard y = SB_HEADER; // Q3Rally Code Start CG_DrawPic( SB_SCORE_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardScore ); // END CG_DrawPic( SB_PING_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardPing ); CG_DrawPic( SB_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime ); // Q3Rally Code Start CG_DrawPic( SB_TOTAL_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime ); // END CG_DrawPic( SB_NAME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardName ); y = SB_TOP; // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores if ( cg.numScores > SB_MAXCLIENTS_NORMAL ) { maxClients = SB_MAXCLIENTS_INTER; lineHeight = SB_INTER_HEIGHT; topBorderSize = 8; bottomBorderSize = 16; } else { maxClients = SB_MAXCLIENTS_NORMAL; lineHeight = SB_NORMAL_HEIGHT; topBorderSize = 16; bottomBorderSize = 16; } localClient = qfalse; if ( cgs.gametype >= GT_TEAM ) { // // teamplay scoreboard // y += lineHeight/2; // Q3Rally Code Start for(i = 0; i < 4; i++){ team = GetTeamAtRank(i+1); if (team == -1) continue; n1 = CG_TeamScoreboard( y, team, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, team ); y += (n1 * lineHeight) + SMALLCHAR_HEIGHT; maxClients -= n1; } /* if ( cg.teamScores[0] >= cg.teamScores[1] ) { n1 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n1; n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); y += (n2 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n2; } else { n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n1; n2 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); y += (n2 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n2; } */ // END n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; } else { // // free for all scoreboard // n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); y += (n2 * lineHeight) + BIGCHAR_HEIGHT; } if (!localClient) { // draw local client at the bottom for ( i = 0 ; i < cg.numScores ; i++ ) { if ( cg.scores[i].client == cg.snap->ps.clientNum ) { CG_DrawClientScore( y, &cg.scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT ); break; } } } // load any models that have been deferred if ( ++cg.deferredPlayerLoading > 10 ) { CG_LoadDeferredPlayers(); } return qtrue; } //================================================================================ // STONELANCE - removed #if 0 /* ================ CG_CenterGiantLine ================ */ static void CG_CenterGiantLine( float y, const char *string ) { float x; vec4_t color; color[0] = 1; color[1] = 1; color[2] = 1; color[3] = 1; x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); } /* ================= CG_DrawTourneyScoreboard Draw the oversize scoreboard for tournements ================= */ void CG_DrawTourneyScoreboard( void ) { const char *s; vec4_t color; int min, tens, ones; clientInfo_t *ci; int y; int i; // request more scores regularly if ( cg.scoresRequestTime + 2000 < cg.time ) { cg.scoresRequestTime = cg.time; trap_SendClientCommand( "score" ); } // draw the dialog background color[0] = color[1] = color[2] = 0; color[3] = 1; CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); color[0] = 1; color[1] = 1; color[2] = 1; color[3] = 1; // print the mesage of the day s = CG_ConfigString( CS_MOTD ); if ( !s[0] ) { s = "Scoreboard"; } // print optional title CG_CenterGiantLine( 8, s ); // print server time ones = cg.time / 1000; min = ones / 60; ones %= 60; tens = ones / 10; ones %= 10; s = va("%i:%i%i", min, tens, ones ); CG_CenterGiantLine( 64, s ); // print the two scores y = 160; if ( cgs.gametype >= GT_TEAM ) { // // teamplay scoreboard // CG_DrawStringExt( 8, y, "Red Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); s = va("%i", cg.teamScores[0] ); CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); y += 64; CG_DrawStringExt( 8, y, "Blue Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); s = va("%i", cg.teamScores[1] ); CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); } else { // // free for all scoreboard // for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { ci = &cgs.clientinfo[i]; if ( !ci->infoValid ) { continue; } if ( ci->team != TEAM_FREE ) { continue; } CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); s = va("%i", ci->score ); CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); y += 64; } } } #endif // END