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