1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23 // cg_scoreboard -- draw the scoreboard on top of the game screen
24 #include "cg_local.h"
25
26
27 #define SCOREBOARD_X (0)
28
29 #define SB_HEADER 86
30 #define SB_TOP (SB_HEADER+32)
31
32 // Where the status bar starts, so we don't overwrite it
33 #define SB_STATUSBAR 420
34
35 #define SB_NORMAL_HEIGHT 40
36 #define SB_INTER_HEIGHT 16 // interleaved height
37
38 #define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT)
39 #define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1)
40
41 // Used when interleaved
42
43
44
45 #define SB_LEFT_BOTICON_X (SCOREBOARD_X+0)
46 #define SB_LEFT_HEAD_X (SCOREBOARD_X+32)
47 #define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64)
48 #define SB_RIGHT_HEAD_X (SCOREBOARD_X+96)
49 // Normal
50 #define SB_BOTICON_X (SCOREBOARD_X+32)
51 #define SB_HEAD_X (SCOREBOARD_X+64)
52
53 #define SB_SCORELINE_X 112
54
55 #define SB_RATING_WIDTH (6 * BIGCHAR_WIDTH) // width 6
56 #define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6
57 #define SB_RATING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH) // width 6
58 #define SB_PING_X (SB_SCORELINE_X + 12 * BIGCHAR_WIDTH + 8) // width 5
59 #define SB_TIME_X (SB_SCORELINE_X + 17 * BIGCHAR_WIDTH + 8) // width 5
60 #define SB_NAME_X (SB_SCORELINE_X + 22 * BIGCHAR_WIDTH) // width 15
61
62 // The new and improved score board
63 //
64 // In cases where the number of clients is high, the score board heads are interleaved
65 // here's the layout
66
67 //
68 // 0 32 80 112 144 240 320 400 <-- pixel position
69 // bot head bot head score ping time name
70 //
71 // wins/losses are drawn on bot icon now
72
73 static qboolean localClient; // true if local client has been displayed
74
75
76 /*
77 =================
78 CG_DrawScoreboard
79 =================
80 */
CG_DrawClientScore(int y,score_t * score,float * color,float fade,qboolean largeFormat)81 static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) {
82 char string[1024];
83 vec3_t headAngles;
84 clientInfo_t *ci;
85 int iconx, headx;
86
87 if ( score->client < 0 || score->client >= cgs.maxclients ) {
88 Com_Printf( "Bad score->client: %i\n", score->client );
89 return;
90 }
91
92 ci = &cgs.clientinfo[score->client];
93
94 iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2);
95 headx = SB_HEAD_X + (SB_RATING_WIDTH / 2);
96
97 // draw the handicap or bot skill marker (unless player has flag)
98 if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) {
99 if( largeFormat ) {
100 CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse );
101 }
102 else {
103 CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse );
104 }
105 } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) {
106 if( largeFormat ) {
107 CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse );
108 }
109 else {
110 CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse );
111 }
112 } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) {
113 if( largeFormat ) {
114 CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse );
115 }
116 else {
117 CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse );
118 }
119 } else {
120 if ( ci->botSkill > 0 && ci->botSkill <= 5 ) {
121 if ( cg_drawIcons.integer ) {
122 if( largeFormat ) {
123 CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] );
124 }
125 else {
126 CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] );
127 }
128 }
129 } else if ( ci->handicap < 100 ) {
130 Com_sprintf( string, sizeof( string ), "%i", ci->handicap );
131 if ( cgs.gametype == GT_TOURNAMENT )
132 CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color );
133 else
134 CG_DrawSmallStringColor( iconx, y, string, color );
135 }
136
137 // draw the wins / losses
138 if ( cgs.gametype == GT_TOURNAMENT ) {
139 Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses );
140 if( ci->handicap < 100 && !ci->botSkill ) {
141 CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color );
142 }
143 else {
144 CG_DrawSmallStringColor( iconx, y, string, color );
145 }
146 }
147 }
148
149 // draw the face
150 VectorClear( headAngles );
151 headAngles[YAW] = 180;
152 if( largeFormat ) {
153 CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE,
154 score->client, headAngles );
155 }
156 else {
157 CG_DrawHead( headx, y, 16, 16, score->client, headAngles );
158 }
159
160 #ifdef MISSIONPACK
161 // draw the team task
162 if ( ci->teamTask != TEAMTASK_NONE ) {
163 if ( ci->teamTask == TEAMTASK_OFFENSE ) {
164 CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader );
165 }
166 else if ( ci->teamTask == TEAMTASK_DEFENSE ) {
167 CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader );
168 }
169 }
170 #endif
171 // draw the score line
172 if ( score->ping == -1 ) {
173 Com_sprintf(string, sizeof(string),
174 " connecting %s", ci->name);
175 } else if ( ci->team == TEAM_SPECTATOR ) {
176 Com_sprintf(string, sizeof(string),
177 " SPECT %3i %4i %s", score->ping, score->time, ci->name);
178 } else {
179 Com_sprintf(string, sizeof(string),
180 "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name);
181 }
182
183 // highlight your position
184 if ( score->client == cg.snap->ps.clientNum ) {
185 float hcolor[4];
186 int rank;
187
188 localClient = qtrue;
189
190 if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR
191 || cgs.gametype >= GT_TEAM ) {
192 rank = -1;
193 } else {
194 rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG;
195 }
196 if ( rank == 0 ) {
197 hcolor[0] = 0;
198 hcolor[1] = 0;
199 hcolor[2] = 0.7f;
200 } else if ( rank == 1 ) {
201 hcolor[0] = 0.7f;
202 hcolor[1] = 0;
203 hcolor[2] = 0;
204 } else if ( rank == 2 ) {
205 hcolor[0] = 0.7f;
206 hcolor[1] = 0.7f;
207 hcolor[2] = 0;
208 } else {
209 hcolor[0] = 0.7f;
210 hcolor[1] = 0.7f;
211 hcolor[2] = 0.7f;
212 }
213
214 hcolor[3] = fade * 0.7;
215 CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y,
216 640 - SB_SCORELINE_X - BIGCHAR_WIDTH, BIGCHAR_HEIGHT+1, hcolor );
217 }
218
219 CG_DrawBigString( SB_SCORELINE_X + (SB_RATING_WIDTH / 2), y, string, fade );
220
221 // add the "ready" marker for intermission exiting
222 if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) {
223 CG_DrawBigStringColor( iconx, y, "READY", color );
224 }
225 }
226
227 /*
228 =================
229 CG_TeamScoreboard
230 =================
231 */
CG_TeamScoreboard(int y,team_t team,float fade,int maxClients,int lineHeight)232 static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) {
233 int i;
234 score_t *score;
235 float color[4];
236 int count;
237 clientInfo_t *ci;
238
239 color[0] = color[1] = color[2] = 1.0;
240 color[3] = fade;
241
242 count = 0;
243 for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) {
244 score = &cg.scores[i];
245 ci = &cgs.clientinfo[ score->client ];
246
247 if ( team != ci->team ) {
248 continue;
249 }
250
251 CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT );
252
253 count++;
254 }
255
256 return count;
257 }
258
259 /*
260 =================
261 CG_DrawScoreboard
262
263 Draw the normal in-game scoreboard
264 =================
265 */
CG_DrawOldScoreboard(void)266 qboolean CG_DrawOldScoreboard( void ) {
267 int x, y, w, i, n1, n2;
268 float fade;
269 float *fadeColor;
270 char *s;
271 int maxClients;
272 int lineHeight;
273 int topBorderSize, bottomBorderSize;
274
275 // don't draw amuthing if the menu or console is up
276 if ( cg_paused.integer ) {
277 cg.deferredPlayerLoading = 0;
278 return qfalse;
279 }
280
281 if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) {
282 cg.deferredPlayerLoading = 0;
283 return qfalse;
284 }
285
286 // don't draw scoreboard during death while warmup up
287 if ( cg.warmup && !cg.showScores ) {
288 return qfalse;
289 }
290
291 if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD ||
292 cg.predictedPlayerState.pm_type == PM_INTERMISSION ) {
293 fade = 1.0;
294 fadeColor = colorWhite;
295 } else {
296 fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME );
297
298 if ( !fadeColor ) {
299 // next time scoreboard comes up, don't print killer
300 cg.deferredPlayerLoading = 0;
301 cg.killerName[0] = 0;
302 return qfalse;
303 }
304 fade = *fadeColor;
305 }
306
307
308 // fragged by ... line
309 if ( cg.killerName[0] ) {
310 s = va("Fragged by %s", cg.killerName );
311 w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
312 x = ( SCREEN_WIDTH - w ) / 2;
313 y = 40;
314 CG_DrawBigString( x, y, s, fade );
315 }
316
317 // current rank
318 if ( cgs.gametype < GT_TEAM) {
319 if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) {
320 s = va("%s place with %i",
321 CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),
322 cg.snap->ps.persistant[PERS_SCORE] );
323 w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
324 x = ( SCREEN_WIDTH - w ) / 2;
325 y = 60;
326 CG_DrawBigString( x, y, s, fade );
327 }
328 } else {
329 if ( cg.teamScores[0] == cg.teamScores[1] ) {
330 s = va("Teams are tied at %i", cg.teamScores[0] );
331 } else if ( cg.teamScores[0] >= cg.teamScores[1] ) {
332 s = va("Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] );
333 } else {
334 s = va("Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] );
335 }
336
337 w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
338 x = ( SCREEN_WIDTH - w ) / 2;
339 y = 60;
340 CG_DrawBigString( x, y, s, fade );
341 }
342
343 // scoreboard
344 y = SB_HEADER;
345
346 CG_DrawPic( SB_SCORE_X + (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardScore );
347 CG_DrawPic( SB_PING_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardPing );
348 CG_DrawPic( SB_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime );
349 CG_DrawPic( SB_NAME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardName );
350
351 y = SB_TOP;
352
353 // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores
354 if ( cg.numScores > SB_MAXCLIENTS_NORMAL ) {
355 maxClients = SB_MAXCLIENTS_INTER;
356 lineHeight = SB_INTER_HEIGHT;
357 topBorderSize = 8;
358 bottomBorderSize = 16;
359 } else {
360 maxClients = SB_MAXCLIENTS_NORMAL;
361 lineHeight = SB_NORMAL_HEIGHT;
362 topBorderSize = 16;
363 bottomBorderSize = 16;
364 }
365
366 localClient = qfalse;
367
368 if ( cgs.gametype >= GT_TEAM ) {
369 //
370 // teamplay scoreboard
371 //
372 y += lineHeight/2;
373
374 if ( cg.teamScores[0] >= cg.teamScores[1] ) {
375 n1 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight );
376 CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED );
377 y += (n1 * lineHeight) + BIGCHAR_HEIGHT;
378 maxClients -= n1;
379 n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight );
380 CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE );
381 y += (n2 * lineHeight) + BIGCHAR_HEIGHT;
382 maxClients -= n2;
383 } else {
384 n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight );
385 CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE );
386 y += (n1 * lineHeight) + BIGCHAR_HEIGHT;
387 maxClients -= n1;
388 n2 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight );
389 CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED );
390 y += (n2 * lineHeight) + BIGCHAR_HEIGHT;
391 maxClients -= n2;
392 }
393 n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight );
394 y += (n1 * lineHeight) + BIGCHAR_HEIGHT;
395
396 } else {
397 //
398 // free for all scoreboard
399 //
400 n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight );
401 y += (n1 * lineHeight) + BIGCHAR_HEIGHT;
402 n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight );
403 y += (n2 * lineHeight) + BIGCHAR_HEIGHT;
404 }
405
406 if (!localClient) {
407 // draw local client at the bottom
408 for ( i = 0 ; i < cg.numScores ; i++ ) {
409 if ( cg.scores[i].client == cg.snap->ps.clientNum ) {
410 CG_DrawClientScore( y, &cg.scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT );
411 break;
412 }
413 }
414 }
415
416 // load any models that have been deferred
417 if ( ++cg.deferredPlayerLoading > 10 ) {
418 CG_LoadDeferredPlayers();
419 }
420
421 return qtrue;
422 }
423
424 //================================================================================
425
426 /*
427 ================
428 CG_CenterGiantLine
429 ================
430 */
CG_CenterGiantLine(float y,const char * string)431 static void CG_CenterGiantLine( float y, const char *string ) {
432 float x;
433 vec4_t color;
434
435 color[0] = 1;
436 color[1] = 1;
437 color[2] = 1;
438 color[3] = 1;
439
440 x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) );
441
442 CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 );
443 }
444
445 /*
446 =================
447 CG_DrawTourneyScoreboard
448
449 Draw the oversize scoreboard for tournements
450 =================
451 */
CG_DrawOldTourneyScoreboard(void)452 void CG_DrawOldTourneyScoreboard( void ) {
453 const char *s;
454 vec4_t color;
455 int min, tens, ones;
456 clientInfo_t *ci;
457 int y;
458 int i;
459
460 // request more scores regularly
461 if ( cg.scoresRequestTime + 2000 < cg.time ) {
462 cg.scoresRequestTime = cg.time;
463 trap_SendClientCommand( "score" );
464 }
465
466 color[0] = 1;
467 color[1] = 1;
468 color[2] = 1;
469 color[3] = 1;
470
471 // draw the dialog background
472 color[0] = color[1] = color[2] = 0;
473 color[3] = 1;
474 CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color );
475
476 // print the mesage of the day
477 s = CG_ConfigString( CS_MOTD );
478 if ( !s[0] ) {
479 s = "Scoreboard";
480 }
481
482 // print optional title
483 CG_CenterGiantLine( 8, s );
484
485 // print server time
486 ones = cg.time / 1000;
487 min = ones / 60;
488 ones %= 60;
489 tens = ones / 10;
490 ones %= 10;
491 s = va("%i:%i%i", min, tens, ones );
492
493 CG_CenterGiantLine( 64, s );
494
495
496 // print the two scores
497
498 y = 160;
499 if ( cgs.gametype >= GT_TEAM ) {
500 //
501 // teamplay scoreboard
502 //
503 CG_DrawStringExt( 8, y, "Red Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 );
504 s = va("%i", cg.teamScores[0] );
505 CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 );
506
507 y += 64;
508
509 CG_DrawStringExt( 8, y, "Blue Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 );
510 s = va("%i", cg.teamScores[1] );
511 CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 );
512 } else {
513 //
514 // free for all scoreboard
515 //
516 for ( i = 0 ; i < MAX_CLIENTS ; i++ ) {
517 ci = &cgs.clientinfo[i];
518 if ( !ci->infoValid ) {
519 continue;
520 }
521 if ( ci->team != TEAM_FREE ) {
522 continue;
523 }
524
525 CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 );
526 s = va("%i", ci->score );
527 CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 );
528 y += 64;
529 }
530 }
531
532
533 }
534
535