1 /*
2 ** hu_scores.cpp
3 ** Routines for drawing the scoreboards.
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2008 Randy Heit
7 ** Copyright 2007-2008 Christopher Westley
8 ** All rights reserved.
9 **
10 ** Redistribution and use in source and binary forms, with or without
11 ** modification, are permitted provided that the following conditions
12 ** are met:
13 **
14 ** 1. Redistributions of source code must retain the above copyright
15 **    notice, this list of conditions and the following disclaimer.
16 ** 2. Redistributions in binary form must reproduce the above copyright
17 **    notice, this list of conditions and the following disclaimer in the
18 **    documentation and/or other materials provided with the distribution.
19 ** 3. The name of the author may not be used to endorse or promote products
20 **    derived from this software without specific prior written permission.
21 **
22 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 **---------------------------------------------------------------------------
33 **
34 */
35 
36 // HEADER FILES ------------------------------------------------------------
37 
38 #include "c_console.h"
39 #include "st_stuff.h"
40 #include "teaminfo.h"
41 #include "templates.h"
42 #include "v_video.h"
43 #include "doomstat.h"
44 #include "g_level.h"
45 #include "d_netinf.h"
46 #include "v_font.h"
47 #include "v_palette.h"
48 #include "d_player.h"
49 #include "hu_stuff.h"
50 #include "gstrings.h"
51 #include "d_net.h"
52 #include "c_dispatch.h"
53 
54 // MACROS ------------------------------------------------------------------
55 
56 // TYPES -------------------------------------------------------------------
57 
58 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
59 
60 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
61 
62 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
63 
64 static void HU_DoDrawScores (player_t *, player_t *[MAXPLAYERS]);
65 static void HU_DrawTimeRemaining (int y);
66 static void HU_DrawPlayer(player_t *, bool, int, int, int, int, int, int, int, int, int);
67 
68 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
69 
EXTERN_CVAR(Float,timelimit)70 EXTERN_CVAR (Float, timelimit)
71 
72 // PUBLIC DATA DEFINITIONS -------------------------------------------------
73 
74 CVAR (Bool,	sb_cooperative_enable,				true,		CVAR_ARCHIVE)
75 CVAR (Int,	sb_cooperative_headingcolor,		CR_RED,		CVAR_ARCHIVE)
76 CVAR (Int,	sb_cooperative_yourplayercolor,		CR_GREEN,	CVAR_ARCHIVE)
77 CVAR (Int,	sb_cooperative_otherplayercolor,	CR_GREY,	CVAR_ARCHIVE)
78 
79 CVAR (Bool,	sb_deathmatch_enable,				true,		CVAR_ARCHIVE)
80 CVAR (Int,	sb_deathmatch_headingcolor,			CR_RED,		CVAR_ARCHIVE)
81 CVAR (Int,	sb_deathmatch_yourplayercolor,		CR_GREEN,	CVAR_ARCHIVE)
82 CVAR (Int,	sb_deathmatch_otherplayercolor,		CR_GREY,	CVAR_ARCHIVE)
83 
84 CVAR (Bool,	sb_teamdeathmatch_enable,			true,		CVAR_ARCHIVE)
85 CVAR (Int,	sb_teamdeathmatch_headingcolor,		CR_RED,		CVAR_ARCHIVE)
86 
87 int STACK_ARGS comparepoints (const void *arg1, const void *arg2)
88 {
89 	// Compare first be frags/kills, then by name.
90 	player_t *p1 = *(player_t **)arg1;
91 	player_t *p2 = *(player_t **)arg2;
92 	int diff;
93 
94 	diff = deathmatch ? p2->fragcount - p1->fragcount : p2->killcount - p1->killcount;
95 	if (diff == 0)
96 	{
97 		diff = stricmp(p1->userinfo.GetName(), p2->userinfo.GetName());
98 	}
99 	return diff;
100 }
101 
compareteams(const void * arg1,const void * arg2)102 int STACK_ARGS compareteams (const void *arg1, const void *arg2)
103 {
104 	// Compare first by teams, then by frags, then by name.
105 	player_t *p1 = *(player_t **)arg1;
106 	player_t *p2 = *(player_t **)arg2;
107 	int diff;
108 
109 	diff = p1->userinfo.GetTeam() - p2->userinfo.GetTeam();
110 	if (diff == 0)
111 	{
112 		diff = p2->fragcount - p1->fragcount;
113 		if (diff == 0)
114 		{
115 			diff = stricmp (p1->userinfo.GetName(), p2->userinfo.GetName());
116 		}
117 	}
118 	return diff;
119 }
120 
121 bool SB_ForceActive = false;
122 
123 // PRIVATE DATA DEFINITIONS ------------------------------------------------
124 
125 // CODE --------------------------------------------------------------------
126 
127 //==========================================================================
128 //
129 // HU_DrawScores
130 //
131 //==========================================================================
132 
HU_DrawScores(player_t * player)133 void HU_DrawScores (player_t *player)
134 {
135 	if (deathmatch)
136 	{
137 		if (teamplay)
138 		{
139 			if (!sb_teamdeathmatch_enable)
140 				return;
141 		}
142 		else
143 		{
144 			if (!sb_deathmatch_enable)
145 				return;
146 		}
147 	}
148 	else
149 	{
150 		if (!sb_cooperative_enable || !multiplayer)
151 			return;
152 	}
153 
154 	int i, j;
155 	player_t *sortedplayers[MAXPLAYERS];
156 
157 	if (player->camera && player->camera->player)
158 		player = player->camera->player;
159 
160 	sortedplayers[MAXPLAYERS-1] = player;
161 	for (i = 0, j = 0; j < MAXPLAYERS - 1; i++, j++)
162 	{
163 		if (&players[i] == player)
164 			i++;
165 		sortedplayers[j] = &players[i];
166 	}
167 
168 	if (teamplay && deathmatch)
169 		qsort (sortedplayers, MAXPLAYERS, sizeof(player_t *), compareteams);
170 	else
171 		qsort (sortedplayers, MAXPLAYERS, sizeof(player_t *), comparepoints);
172 
173 	HU_DoDrawScores (player, sortedplayers);
174 
175 	V_SetBorderNeedRefresh();
176 }
177 
178 //==========================================================================
179 //
180 // HU_GetPlayerWidths
181 //
182 // Returns the widest player name and class icon.
183 //
184 //==========================================================================
185 
HU_GetPlayerWidths(int & maxnamewidth,int & maxscorewidth,int & maxiconheight)186 void HU_GetPlayerWidths(int &maxnamewidth, int &maxscorewidth, int &maxiconheight)
187 {
188 	maxnamewidth = SmallFont->StringWidth("Name");
189 	maxscorewidth = 0;
190 	maxiconheight = 0;
191 
192 	for (int i = 0; i < MAXPLAYERS; i++)
193 	{
194 		if (playeringame[i])
195 		{
196 			int width = SmallFont->StringWidth(players[i].userinfo.GetName());
197 			if (width > maxnamewidth)
198 			{
199 				maxnamewidth = width;
200 			}
201 			if (players[i].mo->ScoreIcon.isValid())
202 			{
203 				FTexture *pic = TexMan[players[i].mo->ScoreIcon];
204 				width = pic->GetScaledWidth() - pic->GetScaledLeftOffset() + 2;
205 				if (width > maxscorewidth)
206 				{
207 					maxscorewidth = width;
208 				}
209 				// The icon's top offset does not count toward its height, because
210 				// zdoom.pk3's standard Hexen class icons are designed that way.
211 				int height = pic->GetScaledHeight() - pic->GetScaledTopOffset();
212 				if (height > maxiconheight)
213 				{
214 					maxiconheight = height;
215 				}
216 			}
217 		}
218 	}
219 }
220 
221 //==========================================================================
222 //
223 // HU_DoDrawScores
224 //
225 //==========================================================================
226 
HU_DoDrawScores(player_t * player,player_t * sortedplayers[MAXPLAYERS])227 static void HU_DoDrawScores (player_t *player, player_t *sortedplayers[MAXPLAYERS])
228 {
229 	int color;
230 	int height, lineheight;
231 	unsigned int i;
232 	int maxnamewidth, maxscorewidth, maxiconheight;
233 	int numTeams = 0;
234 	int x, y, ypadding, bottom;
235 	int col2, col3, col4, col5;
236 
237 	if (deathmatch)
238 	{
239 		if (teamplay)
240 			color = sb_teamdeathmatch_headingcolor;
241 		else
242 			color = sb_deathmatch_headingcolor;
243 	}
244 	else
245 	{
246 		color = sb_cooperative_headingcolor;
247 	}
248 
249 	HU_GetPlayerWidths(maxnamewidth, maxscorewidth, maxiconheight);
250 	height = SmallFont->GetHeight() * CleanYfac;
251 	lineheight = MAX(height, maxiconheight * CleanYfac);
252 	ypadding = (lineheight - height + 1) / 2;
253 
254 	bottom = ST_Y;
255 	y = MAX(48*CleanYfac, (bottom - MAXPLAYERS * (height + CleanYfac + 1)) / 2);
256 
257 	HU_DrawTimeRemaining (bottom - height);
258 
259 	if (teamplay && deathmatch)
260 	{
261 		y -= (BigFont->GetHeight() + 8) * CleanYfac;
262 
263 		for (i = 0; i < Teams.Size (); i++)
264 		{
265 			Teams[i].m_iPlayerCount = 0;
266 			Teams[i].m_iScore = 0;
267 		}
268 
269 		for (i = 0; i < MAXPLAYERS; ++i)
270 		{
271 			if (playeringame[sortedplayers[i]-players] && TeamLibrary.IsValidTeam (sortedplayers[i]->userinfo.GetTeam()))
272 			{
273 				if (Teams[sortedplayers[i]->userinfo.GetTeam()].m_iPlayerCount++ == 0)
274 				{
275 					numTeams++;
276 				}
277 
278 				Teams[sortedplayers[i]->userinfo.GetTeam()].m_iScore += sortedplayers[i]->fragcount;
279 			}
280 		}
281 
282 		int scorexwidth = SCREENWIDTH / MAX(8, numTeams);
283 		int numscores = 0;
284 		int scorex;
285 
286 		for (i = 0; i < Teams.Size(); ++i)
287 		{
288 			if (Teams[i].m_iPlayerCount)
289 			{
290 				numscores++;
291 			}
292 		}
293 
294 		scorex = (SCREENWIDTH - scorexwidth * (numscores - 1)) / 2;
295 
296 		for (i = 0; i < Teams.Size(); ++i)
297 		{
298 			if (Teams[i].m_iPlayerCount)
299 			{
300 				char score[80];
301 				mysnprintf (score, countof(score), "%d", Teams[i].m_iScore);
302 
303 				screen->DrawText (BigFont, Teams[i].GetTextColor(),
304 					scorex - BigFont->StringWidth(score)*CleanXfac/2, y, score,
305 					DTA_CleanNoMove, true, TAG_DONE);
306 
307 				scorex += scorexwidth;
308 			}
309 		}
310 
311 		y += (BigFont->GetHeight() + 8) * CleanYfac;
312 	}
313 
314 	const char *text_color = GStrings("SCORE_COLOR"),
315 		*text_frags = GStrings(deathmatch ? "SCORE_FRAGS" : "SCORE_KILLS"),
316 		*text_name = GStrings("SCORE_NAME"),
317 		*text_delay = GStrings("SCORE_DELAY");
318 
319 	col2 = (SmallFont->StringWidth(text_color) + 8) * CleanXfac;
320 	col3 = col2 + (SmallFont->StringWidth(text_frags) + 8) * CleanXfac;
321 	col4 = col3 + maxscorewidth * CleanXfac;
322 	col5 = col4 + (maxnamewidth + 8) * CleanXfac;
323 	x = (SCREENWIDTH >> 1) - (((SmallFont->StringWidth(text_delay) * CleanXfac) + col5) >> 1);
324 
325 	screen->DrawText (SmallFont, color, x, y, text_color,
326 		DTA_CleanNoMove, true, TAG_DONE);
327 
328 	screen->DrawText (SmallFont, color, x + col2, y, text_frags,
329 		DTA_CleanNoMove, true, TAG_DONE);
330 
331 	screen->DrawText (SmallFont, color, x + col4, y, text_name,
332 		DTA_CleanNoMove, true, TAG_DONE);
333 
334 	screen->DrawText(SmallFont, color, x + col5, y, text_delay,
335 		DTA_CleanNoMove, true, TAG_DONE);
336 
337 	y += height + 6 * CleanYfac;
338 	bottom -= height;
339 
340 	for (i = 0; i < MAXPLAYERS && y <= bottom; i++)
341 	{
342 		if (playeringame[sortedplayers[i] - players])
343 		{
344 			HU_DrawPlayer(sortedplayers[i], player == sortedplayers[i], x, col2, col3, col4, col5, maxnamewidth, y, ypadding, lineheight);
345 			y += lineheight + CleanYfac;
346 		}
347 	}
348 }
349 
350 //==========================================================================
351 //
352 // HU_DrawTimeRemaining
353 //
354 //==========================================================================
355 
HU_DrawTimeRemaining(int y)356 static void HU_DrawTimeRemaining (int y)
357 {
358 	if (deathmatch && timelimit && gamestate == GS_LEVEL)
359 	{
360 		char str[80];
361 		int timeleft = (int)(timelimit * TICRATE * 60) - level.maptime;
362 		int hours, minutes, seconds;
363 
364 		if (timeleft < 0)
365 			timeleft = 0;
366 
367 		hours = timeleft / (TICRATE * 3600);
368 		timeleft -= hours * TICRATE * 3600;
369 		minutes = timeleft / (TICRATE * 60);
370 		timeleft -= minutes * TICRATE * 60;
371 		seconds = timeleft / TICRATE;
372 
373 		if (hours)
374 			mysnprintf (str, countof(str), "Level ends in %d:%02d:%02d", hours, minutes, seconds);
375 		else
376 			mysnprintf (str, countof(str), "Level ends in %d:%02d", minutes, seconds);
377 
378 		screen->DrawText (SmallFont, CR_GREY, SCREENWIDTH/2 - SmallFont->StringWidth (str)/2*CleanXfac,
379 			y, str, DTA_CleanNoMove, true, TAG_DONE);
380 	}
381 }
382 
383 //==========================================================================
384 //
385 // HU_DrawPlayer
386 //
387 //==========================================================================
388 
HU_DrawPlayer(player_t * player,bool highlight,int col1,int col2,int col3,int col4,int col5,int maxnamewidth,int y,int ypadding,int height)389 static void HU_DrawPlayer (player_t *player, bool highlight, int col1, int col2, int col3, int col4, int col5, int maxnamewidth, int y, int ypadding, int height)
390 {
391 	int color;
392 	char str[80];
393 
394 	if (highlight)
395 	{
396 		// The teamplay mode uses colors to show teams, so we need some
397 		// other way to do highlighting. And it may as well be used for
398 		// all modes for the sake of consistancy.
399 		screen->Dim(MAKERGB(200,245,255), 0.125f, col1 - 12*CleanXfac, y - 1, col5 + (maxnamewidth + 24)*CleanXfac, height + 2);
400 	}
401 
402 	col2 += col1;
403 	col3 += col1;
404 	col4 += col1;
405 	col5 += col1;
406 
407 	color = HU_GetRowColor(player, highlight);
408 	HU_DrawColorBar(col1, y, height, (int)(player - players));
409 	mysnprintf (str, countof(str), "%d", deathmatch ? player->fragcount : player->killcount);
410 
411 	screen->DrawText (SmallFont, color, col2, y + ypadding, player->playerstate == PST_DEAD && !deathmatch ? "DEAD" : str,
412 		DTA_CleanNoMove, true, TAG_DONE);
413 
414 	if (player->mo->ScoreIcon.isValid())
415 	{
416 		FTexture *pic = TexMan[player->mo->ScoreIcon];
417 		screen->DrawTexture (pic, col3, y,
418 			DTA_CleanNoMove, true,
419 			TAG_DONE);
420 	}
421 
422 	screen->DrawText (SmallFont, color, col4, y + ypadding, player->userinfo.GetName(),
423 		DTA_CleanNoMove, true, TAG_DONE);
424 
425 	int avgdelay = 0;
426 	for (int i = 0; i < BACKUPTICS; i++)
427 	{
428 		avgdelay += netdelay[nodeforplayer[(int)(player - players)]][i];
429 	}
430 	avgdelay /= BACKUPTICS;
431 
432 	mysnprintf(str, countof(str), "%d", (avgdelay * ticdup) * (1000 / TICRATE));
433 
434 	screen->DrawText(SmallFont, color, col5, y + ypadding, str,
435 		DTA_CleanNoMove, true, TAG_DONE);
436 
437 	if (teamplay && Teams[player->userinfo.GetTeam()].GetLogo().IsNotEmpty ())
438 	{
439 		FTexture *pic = TexMan[Teams[player->userinfo.GetTeam()].GetLogo().GetChars ()];
440 		screen->DrawTexture (pic, col1 - (pic->GetScaledWidth() + 2) * CleanXfac, y,
441 			DTA_CleanNoMove, true, TAG_DONE);
442 	}
443 }
444 
445 //==========================================================================
446 //
447 // HU_DrawColorBar
448 //
449 //==========================================================================
450 
HU_DrawColorBar(int x,int y,int height,int playernum)451 void HU_DrawColorBar(int x, int y, int height, int playernum)
452 {
453 	float h, s, v, r, g, b;
454 
455 	D_GetPlayerColor (playernum, &h, &s, &v, NULL);
456 	HSVtoRGB (&r, &g, &b, h, s, v);
457 
458 	screen->Clear (x, y, x + 24*CleanXfac, y + height, -1,
459 		MAKEARGB(255,clamp(int(r*255.f),0,255),
460 					 clamp(int(g*255.f),0,255),
461 					 clamp(int(b*255.f),0,255)));
462 }
463 
464 //==========================================================================
465 //
466 // HU_GetRowColor
467 //
468 //==========================================================================
469 
HU_GetRowColor(player_t * player,bool highlight)470 int HU_GetRowColor(player_t *player, bool highlight)
471 {
472 	if (teamplay && deathmatch)
473 	{
474 		if (TeamLibrary.IsValidTeam (player->userinfo.GetTeam()))
475 			return Teams[player->userinfo.GetTeam()].GetTextColor();
476 		else
477 			return CR_GREY;
478 	}
479 	else
480 	{
481 		if (!highlight)
482 		{
483 			if (demoplayback && player == &players[consoleplayer])
484 			{
485 				return CR_GOLD;
486 			}
487 			else
488 			{
489 				return deathmatch ? sb_deathmatch_otherplayercolor : sb_cooperative_otherplayercolor;
490 			}
491 		}
492 		else
493 		{
494 			return deathmatch ? sb_deathmatch_yourplayercolor : sb_cooperative_yourplayercolor;
495 		}
496 	}
497 }
498 
CCMD(togglescoreboard)499 CCMD (togglescoreboard)
500 {
501 	SB_ForceActive = !SB_ForceActive;
502 }
503