1 // Emacs style mode select   -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id$
5 //
6 // Copyright (C) 2012 by Alex Mayfield.
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; either version 2
11 // of the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // DESCRIPTION:
19 //   HUD elements.  Holds 'variable' information for the HUD such
20 //   as player data, formatted text strings and patches.
21 //
22 //-----------------------------------------------------------------------------
23 
24 #include <algorithm>
25 #include <sstream>
26 
27 #include "c_cvars.h"
28 #include "cl_demo.h"
29 #include "m_fixed.h" // This should probably go into d_netinf.h
30 #include "d_netinf.h"
31 #include "d_player.h"
32 #include "doomstat.h"
33 #include "g_warmup.h"
34 #include "hu_drawers.h"
35 #include "hu_elements.h"
36 #include "p_ctf.h"
37 #include "v_text.h"
38 #include "v_video.h"
39 
40 size_t P_NumPlayersInGame(void);
41 int CL_GetPlayerColor(player_t*);
42 
43 extern NetDemo netdemo;
44 extern bool HasBehavior;
45 extern fixed_t FocalLengthX;
46 
47 EXTERN_CVAR (sv_fraglimit)
48 EXTERN_CVAR (sv_gametype)
49 EXTERN_CVAR (sv_maxclients)
50 EXTERN_CVAR (sv_maxplayers)
51 EXTERN_CVAR (sv_maxplayersperteam)
52 EXTERN_CVAR (sv_scorelimit)
53 EXTERN_CVAR (sv_timelimit)
54 
55 EXTERN_CVAR (hud_targetnames)
56 EXTERN_CVAR (sv_allowtargetnames)
57 
58 size_t P_NumPlayersInGame();
59 size_t P_NumPlayersOnTeam(team_t team);
60 
61 namespace hud {
62 
63 // Player sorting functions
cmpFrags(const player_t * arg1,const player_t * arg2)64 static bool STACK_ARGS cmpFrags(const player_t *arg1, const player_t *arg2) {
65 	return arg2->fragcount < arg1->fragcount;
66 }
67 
cmpKills(const player_t * arg1,const player_t * arg2)68 static bool STACK_ARGS cmpKills(const player_t *arg1, const player_t *arg2) {
69 	return arg2->killcount < arg1->killcount;
70 }
71 
cmpPoints(const player_t * arg1,const player_t * arg2)72 static bool STACK_ARGS cmpPoints (const player_t *arg1, const player_t *arg2) {
73 	return arg2->points < arg1->points;
74 }
75 
76 // Returns true if a player is ingame.
ingamePlayer(player_t * player)77 bool ingamePlayer(player_t* player) {
78 	return (player->ingame() && player->spectator == false);
79 }
80 
81 // Returns true if a player is ingame and on a specific team
inTeamPlayer(player_t * player,const byte team)82 bool inTeamPlayer(player_t* player, const byte team) {
83 	return (player->ingame() && player->userinfo.team == team && player->spectator == false);
84 }
85 
86 // Returns true if a player is a spectator
spectatingPlayer(player_t * player)87 bool spectatingPlayer(player_t* player) {
88 	return (player->ingame() && player->spectator == true);
89 }
90 
91 // Returns a sorted player list.  Calculates at most once a gametic.
sortedPlayers(void)92 std::vector<player_t *> sortedPlayers(void) {
93 	static int sp_tic = -1;
94 	static std::vector<player_t *> sortedplayers(players.size());
95 
96 	if (sp_tic == gametic) {
97 		return sortedplayers;
98 	}
99 
100 	sortedplayers.clear();
101 	for (Players::iterator it = players.begin();it != players.end();++it) {
102 		sortedplayers.push_back(&*it);
103 	}
104 
105 	if (sv_gametype == GM_COOP) {
106 		std::sort(sortedplayers.begin(), sortedplayers.end(), cmpKills);
107 	} else {
108 		std::sort(sortedplayers.begin(), sortedplayers.end(), cmpFrags);
109 		if (sv_gametype == GM_CTF) {
110 			std::sort(sortedplayers.begin(), sortedplayers.end(), cmpPoints);
111 		}
112 	}
113 
114 	sp_tic = gametic;
115 	return sortedplayers;
116 }
117 
118 // Return a text color based on a ping.
pingTextColor(unsigned short ping)119 int pingTextColor(unsigned short ping) {
120 	if (ping <= 80) {
121 		return CR_GREEN;
122 	} else if (ping <= 160) {
123 		return CR_GOLD;
124 	} else if (ping <= 240) {
125 		return CR_ORANGE;
126 	}
127 	return CR_RED;
128 }
129 
130 // Return a text color based on a team.
teamTextColor(byte team)131 int teamTextColor(byte team) {
132 	int color;
133 
134 	switch (team) {
135 	case TEAM_BLUE:
136 		color = CR_BLUE;
137 		break;
138 	case TEAM_RED:
139 		color = CR_RED;
140 		break;
141 	default:
142 		color = CR_GREY;
143 		break;
144 	}
145 
146 	return color;
147 }
148 
149 //// OLD-STYLE "VARIABLES" ////
150 // Please don't add any more of these.
151 
152 // Return a "help" string.
HelpText()153 std::string HelpText() {
154 	if (P_NumPlayersInGame() >= sv_maxplayers)
155 	{
156 		return "Game is full";
157 	}
158 
159 	if (sv_gametype == GM_TEAMDM || sv_gametype == GM_CTF)
160 	{
161 		size_t min_players = MAXPLAYERS;
162 		for (byte i = 0;i < NUMTEAMS;i++)
163 		{
164 			size_t players = P_NumPlayersOnTeam((team_t)i);
165 			if (players < min_players)
166 				min_players = players;
167 		}
168 		if (sv_maxplayersperteam && min_players >= sv_maxplayersperteam) {
169 			return "Game is full";
170 		}
171 	}
172 
173 	return "Press USE to join";
174 }
175 
176 // Return a string that contains the name of the player being spectated,
177 // or a blank string if you are looking out of your own viewpoint.
SpyPlayerName(int & color)178 std::string SpyPlayerName(int& color) {
179 	color = CR_GREY;
180 	player_t *plyr = &displayplayer();
181 
182 	if (plyr == &consoleplayer()) {
183 		return "";
184 	}
185 
186 	if (sv_gametype == GM_TEAMDM || sv_gametype == GM_CTF) {
187 		color = teamTextColor(plyr->userinfo.team);
188 	}
189 
190 	return plyr->userinfo.netname;
191 }
192 
193 // Returns a string that contains the current warmup state.
Warmup(int & color)194 std::string Warmup(int& color)
195 {
196 	color = CR_GREY;
197 	player_t *dp = &displayplayer();
198 	player_t *cp = &consoleplayer();
199 
200 	::Warmup::status_t wstatus = warmup.get_status();
201 
202 	if (wstatus == ::Warmup::WARMUP)
203 	{
204 		if (dp->spectator)
205 			return "Warmup: You are spectating";
206 		else if (dp->ready)
207 		{
208 			color = CR_GREEN;
209 			if (dp == cp)
210 				return "Warmup: You are ready";
211 			else
212 				return "Warmup: This player is ready";
213 		}
214 		else
215 		{
216 			color = CR_RED;
217 			if (dp == cp)
218 				return "Warmup: You are not ready";
219 			else
220 				return "Warmup: This player is not ready";
221 		}
222 	}
223 	else if (wstatus == ::Warmup::COUNTDOWN || wstatus == ::Warmup::FORCE_COUNTDOWN)
224 	{
225 		color = CR_GOLD;
226 		return "Match is about to start...";
227 	}
228 	return "";
229 }
230 
231 // Return a string that contains the amount of time left in the map,
232 // or a blank string if there is no timer needed.  Can also display
233 // warmup information if it exists.
Timer(int & color)234 std::string Timer(int& color) {
235 	color = CR_GREY;
236 
237 	if (!multiplayer || !(sv_timelimit > 0.0f)) {
238 		return "";
239 	}
240 
241 	int timeleft = level.timeleft;
242 
243 	if (timeleft < 0) {
244 		timeleft = 0;
245 	}
246 
247 	int hours = timeleft / (TICRATE * 3600);
248 
249 	timeleft -= hours * TICRATE * 3600;
250 	int minutes = timeleft / (TICRATE * 60);
251 
252 	timeleft -= minutes * TICRATE * 60;
253 	int seconds = timeleft / TICRATE;
254 
255 	if (minutes <= 0) {
256 		color = CR_BRICK;
257 	}
258 
259 	char str[9];
260 	if (hours) {
261 		sprintf(str, "%02d:%02d:%02d", hours, minutes, seconds);
262 	} else {
263 		sprintf(str, "%02d:%02d", minutes, seconds);
264 	}
265 
266 	return str;
267 }
268 
IntermissionTimer()269 std::string IntermissionTimer() {
270 	if (gamestate != GS_INTERMISSION) {
271 		return "";
272 	}
273 
274 	int timeleft = level.inttimeleft * TICRATE;
275 
276 	if (timeleft < 0) {
277 		timeleft = 0;
278 	}
279 
280 	int hours = timeleft / (TICRATE * 3600);
281 
282 	timeleft -= hours * TICRATE * 3600;
283 	int minutes = timeleft / (TICRATE * 60);
284 
285 	timeleft -= minutes * TICRATE * 60;
286 	int seconds = timeleft / TICRATE;
287 
288 	char str[9];
289 	if (hours) {
290 		sprintf(str, "%02d:%02d:%02d", hours, minutes, seconds);
291 	} else {
292 		sprintf(str, "%02d:%02d", minutes, seconds);
293 	}
294 
295 	return str;
296 }
297 
298 // Return a "spread" of personal frags or team points that the
299 // current player or team is ahead or behind by.
PersonalSpread(int & color)300 std::string PersonalSpread(int& color) {
301 	color = CR_BRICK;
302 	player_t *plyr = &displayplayer();
303 
304 	if (sv_gametype == GM_DM) {
305 		// Seek the highest number of frags.
306 		byte ingame = 0;
307 		size_t maxother = 0;
308 		short maxfrags = -32768;
309 		for (Players::iterator it = players.begin();it != players.end();++it) {
310 			if (!validplayer(*it)) {
311 				continue;
312 			}
313 
314 			if (!(it->ingame()) || it->spectator) {
315 				continue;
316 			}
317 
318 			if (it->fragcount == maxfrags) {
319 				maxother++;
320 			}
321 
322 			if (it->fragcount > maxfrags) {
323 				maxfrags = it->fragcount;
324 				maxother = 0;
325 			}
326 
327 			ingame += 1;
328 		}
329 
330 		// A spread needs two players to make sense.
331 		if (ingame <= 1) {
332 			return "";
333 		}
334 
335 		// Return the correct spread.
336 		if (maxfrags == plyr->fragcount && maxother > 0) {
337 			// We have the maximum number of frags but we share the
338 			// throne with someone else.  But at least we can take
339 			// a little shortcut here.
340 			color = CR_GREEN;
341 			return "+0";
342 		}
343 
344 		std::ostringstream buffer;
345 		if (maxfrags == plyr->fragcount) {
346 			// We have the maximum number of frags.  Calculate how
347 			// far above the other players we are.
348 			short nextfrags = -32768;
349 			for (Players::iterator it = players.begin();it != players.end();++it) {
350 				if (!validplayer(*it)) {
351 					continue;
352 				}
353 
354 				if (!(it->ingame()) || it->spectator) {
355 					continue;
356 				}
357 
358 				if (it->id == plyr->id) {
359 					continue;
360 				}
361 
362 				if (it->fragcount > nextfrags) {
363 					nextfrags = it->fragcount;
364 				}
365 			}
366 
367 			color = CR_GREEN;
368 			buffer << "+" << maxfrags - nextfrags;
369 			return buffer.str();
370 		}
371 
372 		// We are behind the leader.
373 		buffer << (plyr->fragcount - maxfrags);
374 		return buffer.str();
375 	} else if (sv_gametype == GM_TEAMDM || sv_gametype == GM_CTF) {
376 		// Team spreads are significantly easier.  Just compare two numbers.
377 		// FIXME: Not if we have more than two teams!
378 		std::ostringstream buffer;
379 		switch (plyr->userinfo.team) {
380 		case TEAM_BLUE:
381 			if (TEAMpoints[TEAM_BLUE] >= TEAMpoints[TEAM_RED]) {
382 				color = CR_GREEN;
383 				buffer << "+";
384 			}
385 			buffer << (TEAMpoints[TEAM_BLUE] - TEAMpoints[TEAM_RED]);
386 			break;
387 		case TEAM_RED:
388 			if (TEAMpoints[TEAM_RED] >= TEAMpoints[TEAM_BLUE]) {
389 				color = CR_GREEN;
390 				buffer << "+";
391 			}
392 			buffer << (TEAMpoints[TEAM_RED] - TEAMpoints[TEAM_BLUE]);
393 			break;
394 		default:
395 			// No valid team?  Something is wrong...
396 			return "";
397 		}
398 		return buffer.str();
399 	}
400 
401 	// We're not in an appropriate gamemode.
402 	return "";
403 }
404 
405 // Return a string that contains the current team score or personal
406 // frags of the individual player.  Optionally returns the "limit"
407 // as well.
PersonalScore(int & color)408 std::string PersonalScore(int& color) {
409 	color = CR_GREY;
410 	std::ostringstream buffer;
411 	player_t *plyr = &displayplayer();
412 
413 	if (sv_gametype == GM_DM) {
414 		buffer << plyr->fragcount;
415 		if (sv_fraglimit.asInt() > 0) {
416 			buffer << "/" << sv_fraglimit.asInt();
417 		}
418 	} else if (sv_gametype == GM_TEAMDM || sv_gametype == GM_CTF) {
419 		color = teamTextColor(plyr->userinfo.team);
420 		buffer << TEAMpoints[plyr->userinfo.team];
421 
422 		if (sv_gametype == GM_TEAMDM) {
423 			if (sv_fraglimit.asInt() > 0) {
424 				buffer << "/" << sv_fraglimit.asInt();
425 			}
426 		} else {
427 			if (sv_scorelimit.asInt() > 0) {
428 				buffer << "/" << sv_scorelimit.asInt();
429 			}
430 		}
431 	}
432 
433 	return buffer.str();
434 }
435 
436 // Return the amount of time elapsed in a netdemo.
NetdemoElapsed()437 std::string NetdemoElapsed() {
438 	if (!(netdemo.isPlaying() || netdemo.isPaused())) {
439 		return "";
440 	}
441 
442 	int timeelapsed = netdemo.calculateTimeElapsed();
443 	int hours = timeelapsed / 3600;
444 
445 	timeelapsed -= hours * 3600;
446 	int minutes = timeelapsed / 60;
447 
448 	timeelapsed -= minutes * 60;
449 	int seconds = timeelapsed;
450 
451 	char str[9];
452 	if (hours) {
453 		sprintf(str, "%02d:%02d:%02d", hours, minutes, seconds);
454 	} else {
455 		sprintf(str, "%02d:%02d", minutes, seconds);
456 	}
457 
458 	return str;
459 }
460 
461 // Return current map number/total maps in demo
NetdemoMaps()462 std::string NetdemoMaps() {
463 	std::vector<int> maptimes = netdemo.getMapChangeTimes();
464 
465 	// Single map demo
466 	if (maptimes.empty()) {
467 		return "";
468 	}
469 
470 	std::ostringstream buffer;
471 	size_t current_map = 1;
472 
473 	// See if we're in a tic past one of the map change times.
474 	for (size_t i = 0;i < maptimes.size();i++) {
475 		if (maptimes[i] <= netdemo.calculateTimeElapsed()) {
476 			current_map = (i + 1);
477 		}
478 	}
479 
480 	buffer << current_map << "/" << maptimes.size();
481 	return buffer.str();
482 }
483 
484 // Returns clients connected / max clients.
ClientsSplit()485 std::string ClientsSplit() {
486 	std::ostringstream buffer;
487 
488 	buffer << players.size() << "/" << sv_maxclients;
489 	return buffer.str();
490 }
491 
492 // Returns players connected / max players.
PlayersSplit()493 std::string PlayersSplit() {
494 	std::ostringstream buffer;
495 
496 	buffer << P_NumPlayersInGame() << "/" << sv_maxplayers;
497 	return buffer.str();
498 }
499 
500 // Returns the number of players on a team
CountTeamPlayers(byte team)501 byte CountTeamPlayers(byte team) {
502 	byte count = 0;
503 	for (size_t i = 0;i < sortedPlayers().size();i++) {
504 		player_t* player = sortedPlayers()[i];
505 		if (inTeamPlayer(player, team)) {
506 			count++;
507 		}
508 	}
509 	return count;
510 }
511 
512 // Returns the number of spectators on a team
CountSpectators()513 byte CountSpectators() {
514 	byte count = 0;
515 	for (size_t i = 0;i < sortedPlayers().size();i++) {
516 		player_t* player = sortedPlayers()[i];
517 		if (spectatingPlayer(player)) {
518 			count++;
519 		}
520 	}
521 	return count;
522 }
523 
TeamPlayers(int & color,byte team)524 std::string TeamPlayers(int& color, byte team) {
525 	color = teamTextColor(team);
526 
527 	std::ostringstream buffer;
528 	buffer << (short)CountTeamPlayers(team);
529 	return buffer.str();
530 }
531 
TeamName(int & color,byte team)532 std::string TeamName(int& color, byte team) {
533 	color = teamTextColor(team);
534 
535 	switch (team) {
536 	case TEAM_BLUE:
537 		return "BLUE TEAM";
538 	case TEAM_RED:
539 		return "RED TEAM";
540 	default:
541 		return "NO TEAM";
542 	}
543 }
544 
TeamFrags(int & color,byte team)545 std::string TeamFrags(int& color, byte team) {
546 	if (CountTeamPlayers(team) == 0) {
547 		color = CR_GREY;
548 		return "---";
549 	}
550 
551 	color = teamTextColor(team);
552 
553 	int fragcount = 0;
554 	for (size_t i = 0;i < sortedPlayers().size();i++) {
555 		player_t* player = sortedPlayers()[i];
556 		if (inTeamPlayer(player, team)) {
557 			fragcount += player->fragcount;
558 		}
559 	}
560 
561 	std::ostringstream buffer;
562 	buffer << fragcount;
563 	return buffer.str();
564 }
565 
TeamPoints(int & color,byte team)566 std::string TeamPoints(int& color, byte team) {
567 	if (team >= NUMTEAMS) {
568 		color = CR_GREY;
569 		return "---";
570 	}
571 
572 	color = teamTextColor(team);
573 
574 	std::ostringstream buffer;
575 	buffer << TEAMpoints[team];
576 	return buffer.str();
577 }
578 
TeamKD(int & color,byte team)579 std::string TeamKD(int& color, byte team) {
580 	if (CountTeamPlayers(team) == 0) {
581 		color = CR_GREY;
582 		return "---";
583 	}
584 
585 	color = teamTextColor(team);
586 
587 	int killcount = 0;
588 	unsigned int deathcount = 0;
589 	for (size_t i = 0;i < sortedPlayers().size();i++) {
590 		player_t* player = sortedPlayers()[i];
591 		if (inTeamPlayer(player, team)) {
592 			killcount += player->fragcount;
593 			deathcount += player->deathcount;
594 		}
595 	}
596 
597 	std::ostringstream buffer;
598 	buffer.precision(2);
599 	buffer << std::fixed;
600 	float kd;
601 	if (deathcount == 0)
602 		kd = (float)killcount / 1.0f;
603 	else {
604 		kd = (float)killcount / (float)deathcount;
605 	}
606 	buffer << kd;
607 	return buffer.str();
608 }
609 
TeamPing(int & color,byte team)610 std::string TeamPing(int& color, byte team) {
611 	if (CountTeamPlayers(team) == 0) {
612 		color = CR_GREY;
613 		return "---";
614 	}
615 
616 	unsigned int ping = 0;
617 	for (size_t i = 0;i < sortedPlayers().size();i++) {
618 		player_t* player = sortedPlayers()[i];
619 		if (inTeamPlayer(player, team)) {
620 			ping += player->ping;
621 		}
622 	}
623 
624 	ping = ping / CountTeamPlayers(team);
625 	color = pingTextColor(ping);
626 
627 	std::ostringstream buffer;
628 	buffer << ping;
629 	return buffer.str();
630 }
631 
632 //// NEW-STYLE "ELEMENTS" ////
633 
634 // "Elemants" are actually all-in-one drawable packages.  There are two types
635 // of elements: plain old elements and ElementArrays, which draw the same thing
636 // multiple times.  I'm still trying to figure out the interface for these though.
637 
638 // Draw a list of player colors in the game.  Lines up with player names.
EAPlayerColors(int x,int y,const unsigned short w,const unsigned short h,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit)639 void EAPlayerColors(int x, int y,
640                     const unsigned short w, const unsigned short h,
641                     const float scale,
642                     const x_align_t x_align, const y_align_t y_align,
643                     const x_align_t x_origin, const y_align_t y_origin,
644                     const short padding, const short limit) {
645 	byte drawn = 0;
646 	for (size_t i = 0;i < sortedPlayers().size();i++) {
647 		// Make sure we're not overrunning our limit.
648 		if (limit != 0 && drawn >= limit) {
649 			break;
650 		}
651 
652 		player_t* player = sortedPlayers()[i];
653 		if (ingamePlayer(player)) {
654 			int playercolor = CL_GetPlayerColor(player);
655 			int color = BestColor(GetDefaultPalette()->basecolors,
656 			                      RPART(playercolor),
657 			                      GPART(playercolor),
658 			                      BPART(playercolor),
659 			                      GetDefaultPalette()->numcolors);
660 
661 			if (!screen->is8bit()) color = playercolor;
662 
663 			hud::Clear(x, y, w, h, scale, x_align, y_align, x_origin, y_origin, color);
664 			y += h + padding;
665 			drawn += 1;
666 		}
667 	}
668 }
669 
670 // Draw a list of player colors on a team.  Lines up with player names.
EATeamPlayerColors(int x,int y,const unsigned short w,const unsigned short h,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const byte team)671 void EATeamPlayerColors(int x, int y,
672                         const unsigned short w, const unsigned short h,
673                         const float scale,
674                         const x_align_t x_align, const y_align_t y_align,
675                         const x_align_t x_origin, const y_align_t y_origin,
676                         const short padding, const short limit,
677                         const byte team) {
678 	byte drawn = 0;
679 	for (size_t i = 0;i < sortedPlayers().size();i++) {
680 		// Make sure we're not overrunning our limit.
681 		if (limit != 0 && drawn >= limit) {
682 			break;
683 		}
684 
685 		player_t* player = sortedPlayers()[i];
686 		if (inTeamPlayer(player, team)) {
687 			int playercolor = CL_GetPlayerColor(player);
688 			int color = BestColor(GetDefaultPalette()->basecolors,
689 			                      RPART(playercolor),
690 			                      GPART(playercolor),
691 			                      BPART(playercolor),
692 			                      GetDefaultPalette()->numcolors);
693 
694 			if (!screen->is8bit()) color = playercolor;
695 
696 			hud::Clear(x, y, w, h, scale, x_align, y_align, x_origin, y_origin, color);
697 			y += h + padding;
698 			drawn += 1;
699 		}
700 	}
701 }
702 
703 // Draw a list of players in the game.
EAPlayerNames(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const bool force_opaque)704 void EAPlayerNames(int x, int y, const float scale,
705                    const x_align_t x_align, const y_align_t y_align,
706                    const x_align_t x_origin, const y_align_t y_origin,
707                    const short padding, const short limit,
708                    const bool force_opaque) {
709 	byte drawn = 0;
710 	for (size_t i = 0;i < sortedPlayers().size();i++) {
711 		// Make sure we're not overrunning our limit.
712 		if (limit != 0 && drawn >= limit) {
713 			break;
714 		}
715 
716 		player_t* player = sortedPlayers()[i];
717 		if (ingamePlayer(player)) {
718 			int color = CR_GREY;
719 			if (player->id == displayplayer().id) {
720 				color = CR_GOLD;
721 			}
722 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
723 			              player->userinfo.netname.c_str(), color, force_opaque);
724 			y += 7 + padding;
725 			drawn += 1;
726 		}
727 	}
728 }
729 
730 // Draw a list of players on a team.
EATeamPlayerNames(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const byte team,const bool force_opaque)731 void EATeamPlayerNames(int x, int y, const float scale,
732                        const x_align_t x_align, const y_align_t y_align,
733                        const x_align_t x_origin, const y_align_t y_origin,
734                        const short padding, const short limit,
735                        const byte team, const bool force_opaque) {
736 	byte drawn = 0;
737 	for (size_t i = 0;i < sortedPlayers().size();i++) {
738 		// Make sure we're not overrunning our limit.
739 		if (limit != 0 && drawn >= limit) {
740 			break;
741 		}
742 
743 		player_t* player = sortedPlayers()[i];
744 		if (inTeamPlayer(player, team)) {
745 			int color = CR_GREY;
746 			if (sv_gametype == GM_TEAMDM || sv_gametype == GM_CTF) {
747 				if (player->userinfo.team == TEAM_BLUE) {
748 					if (player->flags[it_redflag]) {
749 						color = CR_RED;
750 					} else if (player->flags[it_blueflag]) {
751 						color = CR_BLUE;
752 					} else if (player->ready) {
753 						color = CR_GREEN;
754 					} else if (player->id == displayplayer().id) {
755 						color = CR_GOLD;
756 					}
757 				} else if (player->userinfo.team == TEAM_RED) {
758 					if (player->flags[it_blueflag]) {
759 						color = CR_BLUE;
760 					} else if (player->flags[it_redflag]) {
761 						color = CR_RED;
762 					} else if (player->ready) {
763 						color = CR_GREEN;
764 					} else if (player->id == displayplayer().id) {
765 						color = CR_GOLD;
766 					}
767 				}
768 			} else {
769 				if (player->id == displayplayer().id) {
770 					color = CR_GOLD;
771 				}
772 			}
773 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
774 			              player->userinfo.netname.c_str(), color, force_opaque);
775 			y += 7 + padding;
776 			drawn += 1;
777 		}
778 	}
779 }
780 
781 // Draw a list of spectators.
EASpectatorNames(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,short skip,const short limit,const bool force_opaque)782 void EASpectatorNames(int x, int y, const float scale,
783                       const x_align_t x_align, const y_align_t y_align,
784                       const x_align_t x_origin, const y_align_t y_origin,
785                       const short padding, short skip, const short limit,
786                       const bool force_opaque) {
787 	byte drawn = 0;
788 	for (size_t i = 0;i < sortedPlayers().size();i++) {
789 		// Make sure we're not overrunning our limit.
790 		if (limit != 0 && drawn >= limit) {
791 			break;
792 		}
793 
794 		player_t* player = sortedPlayers()[i];
795 		if (spectatingPlayer(player)) {
796 			if (skip <= 0) {
797 				int color = CR_GREY;
798 				if (sv_gametype == GM_TEAMDM || sv_gametype == GM_CTF) {
799 					if (player->ready) {
800 						color = CR_GREEN;
801 					} else if (player->id == displayplayer().id) {
802 						color = CR_GOLD;
803 					}
804 				} else {
805 					if (player->id == displayplayer().id) {
806 						color = CR_GOLD;
807 					}
808 				}
809 				hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
810 				              player->userinfo.netname.c_str(), color, force_opaque);
811 				y += 7 + padding;
812 				drawn += 1;
813 			} else {
814 				skip -= 1;
815 			}
816 		}
817 	}
818 }
819 
820 // Draw a list of frags in the game.  Lines up with player names.
EAPlayerFrags(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const bool force_opaque)821 void EAPlayerFrags(int x, int y, const float scale,
822                    const x_align_t x_align, const y_align_t y_align,
823                    const x_align_t x_origin, const y_align_t y_origin,
824                    const short padding, const short limit,
825                    const bool force_opaque) {
826 	byte drawn = 0;
827 	for (size_t i = 0;i < sortedPlayers().size();i++) {
828 		// Make sure we're not overrunning our limit.
829 		if (limit != 0 && drawn >= limit) {
830 			break;
831 		}
832 
833 		player_t* player = sortedPlayers()[i];
834 		if (ingamePlayer(player)) {
835 			std::ostringstream buffer;
836 			buffer << player->fragcount;
837 
838 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
839 			              buffer.str().c_str(), CR_GREY, force_opaque);
840 			y += 7 + padding;
841 			drawn += 1;
842 		}
843 	}
844 }
845 
846 // Draw a list of frags on a team.  Lines up with player names.
EATeamPlayerFrags(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const byte team,const bool force_opaque)847 void EATeamPlayerFrags(int x, int y, const float scale,
848                        const x_align_t x_align, const y_align_t y_align,
849                        const x_align_t x_origin, const y_align_t y_origin,
850                        const short padding, const short limit,
851                        const byte team, const bool force_opaque) {
852 	byte drawn = 0;
853 	for (size_t i = 0;i < sortedPlayers().size();i++) {
854 		// Make sure we're not overrunning our limit.
855 		if (limit != 0 && drawn >= limit) {
856 			break;
857 		}
858 
859 		player_t* player = sortedPlayers()[i];
860 		if (inTeamPlayer(player, team)) {
861 			std::ostringstream buffer;
862 			buffer << player->fragcount;
863 
864 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
865 			              buffer.str().c_str(), CR_GREY, force_opaque);
866 			y += 7 + padding;
867 			drawn += 1;
868 		}
869 	}
870 }
871 
872 // Draw a list of kills in the game.  Lines up with player names.
EAPlayerKills(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const bool force_opaque)873 void EAPlayerKills(int x, int y, const float scale,
874                    const x_align_t x_align, const y_align_t y_align,
875                    const x_align_t x_origin, const y_align_t y_origin,
876                    const short padding, const short limit,
877                    const bool force_opaque) {
878 	byte drawn = 0;
879 	for (size_t i = 0;i < sortedPlayers().size();i++) {
880 		// Make sure we're not overrunning our limit.
881 		if (limit != 0 && drawn >= limit) {
882 			break;
883 		}
884 
885 		player_t* player = sortedPlayers()[i];
886 		if (ingamePlayer(player)) {
887 			std::ostringstream buffer;
888 			buffer << player->killcount;
889 
890 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
891 			              buffer.str().c_str(), CR_GREY, force_opaque);
892 			y += 7 + padding;
893 			drawn += 1;
894 		}
895 	}
896 }
897 
898 // Draw a list of deaths in the game.  Lines up with player names.
EAPlayerDeaths(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const bool force_opaque)899 void EAPlayerDeaths(int x, int y, const float scale,
900                     const x_align_t x_align, const y_align_t y_align,
901                     const x_align_t x_origin, const y_align_t y_origin,
902                     const short padding, const short limit,
903                     const bool force_opaque) {
904 	byte drawn = 0;
905 	for (size_t i = 0;i < sortedPlayers().size();i++) {
906 		// Make sure we're not overrunning our limit.
907 		if (limit != 0 && drawn >= limit) {
908 			break;
909 		}
910 
911 		player_t* player = sortedPlayers()[i];
912 		if (ingamePlayer(player)) {
913 			std::ostringstream buffer;
914 			buffer << player->deathcount;
915 
916 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
917 			              buffer.str().c_str(), CR_GREY, force_opaque);
918 			y += 7 + padding;
919 			drawn += 1;
920 		}
921 	}
922 }
923 
924 // Draw a list of points on a team.  Lines up with player names.
EATeamPlayerPoints(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const byte team,const bool force_opaque)925 void EATeamPlayerPoints(int x, int y, const float scale,
926                         const x_align_t x_align, const y_align_t y_align,
927                         const x_align_t x_origin, const y_align_t y_origin,
928                         const short padding, const short limit,
929                         const byte team, const bool force_opaque) {
930 	byte drawn = 0;
931 	for (size_t i = 0;i < sortedPlayers().size();i++) {
932 		// Make sure we're not overrunning our limit.
933 		if (limit != 0 && drawn >= limit) {
934 			break;
935 		}
936 
937 		player_t* player = sortedPlayers()[i];
938 		if (inTeamPlayer(player, team)) {
939 			std::ostringstream buffer;
940 			buffer << player->points;
941 
942 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
943 			              buffer.str().c_str(), CR_GREY, force_opaque);
944 			y += 7 + padding;
945 			drawn += 1;
946 		}
947 	}
948 }
949 
950 // Draw a list of K/D ratios in the game.  Lines up with player names.
951 // FIXME: Nothing in the player struct holds player kills, so we use
952 //        frags instead.
EAPlayerKD(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const bool force_opaque)953 void EAPlayerKD(int x, int y, const float scale,
954                 const x_align_t x_align, const y_align_t y_align,
955                 const x_align_t x_origin, const y_align_t y_origin,
956                 const short padding, const short limit,
957                 const bool force_opaque) {
958 	byte drawn = 0;
959 	for (size_t i = 0;i < sortedPlayers().size();i++) {
960 		// Make sure we're not overrunning our limit.
961 		if (limit != 0 && drawn >= limit) {
962 			break;
963 		}
964 
965 		player_t* player = sortedPlayers()[i];
966 		if (ingamePlayer(player)) {
967 			std::ostringstream buffer;
968 			buffer.precision(2);
969 			buffer << std::fixed;
970 			float kd;
971 			if (player->deathcount == 0)
972 				kd = (float)player->fragcount / 1.0f;
973 			else {
974 				kd = (float)player->fragcount / (float)player->deathcount;
975 			}
976 			buffer << kd;
977 
978 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
979 			              buffer.str().c_str(), CR_GREY, force_opaque);
980 			y += 7 + padding;
981 			drawn += 1;
982 		}
983 	}
984 }
985 
986 // Draw a list of K/D ratios on a team.  Lines up with player names.
987 // FIXME: Nothing in the player struct holds player kills, so we use
988 //        frags instead.
EATeamPlayerKD(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const byte team,const bool force_opaque)989 void EATeamPlayerKD(int x, int y, const float scale,
990                     const x_align_t x_align, const y_align_t y_align,
991                     const x_align_t x_origin, const y_align_t y_origin,
992                     const short padding, const short limit,
993                     const byte team, const bool force_opaque) {
994 	byte drawn = 0;
995 	for (size_t i = 0;i < sortedPlayers().size();i++) {
996 		// Make sure we're not overrunning our limit.
997 		if (limit != 0 && drawn >= limit) {
998 			break;
999 		}
1000 
1001 		player_t* player = sortedPlayers()[i];
1002 		if (inTeamPlayer(player, team)) {
1003 			std::ostringstream buffer;
1004 			buffer.precision(2);
1005 			buffer << std::fixed;
1006 			float kd;
1007 			if (player->deathcount == 0)
1008 				kd = (float)player->fragcount / 1.0f;
1009 			else {
1010 				kd = (float)player->fragcount / (float)player->deathcount;
1011 			}
1012 			buffer << kd;
1013 
1014 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
1015 			              buffer.str().c_str(), CR_GREY, force_opaque);
1016 			y += 7 + padding;
1017 			drawn += 1;
1018 		}
1019 	}
1020 }
1021 
1022 // Draw a list of player times in the game (in minutes).  Lines up with
1023 // player names.
EAPlayerTimes(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const bool force_opaque)1024 void EAPlayerTimes(int x, int y, const float scale,
1025                    const x_align_t x_align, const y_align_t y_align,
1026                    const x_align_t x_origin, const y_align_t y_origin,
1027                    const short padding, const short limit,
1028                    const bool force_opaque) {
1029 	byte drawn = 0;
1030 	for (size_t i = 0;i < sortedPlayers().size();i++) {
1031 		// Make sure we're not overrunning our limit.
1032 		if (limit != 0 && drawn >= limit) {
1033 			break;
1034 		}
1035 
1036 		player_t* player = sortedPlayers()[i];
1037 		if (ingamePlayer(player)) {
1038 			std::ostringstream buffer;
1039 			buffer << player->GameTime / 60;
1040 
1041 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
1042 			              buffer.str().c_str(), CR_GREY, force_opaque);
1043 			y += 7 + padding;
1044 			drawn += 1;
1045 		}
1046 	}
1047 }
1048 
1049 // Draw a list of player times on a team (in minutes).  Lines up with
1050 // player names.
EATeamPlayerTimes(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const byte team,const bool force_opaque)1051 void EATeamPlayerTimes(int x, int y, const float scale,
1052                        const x_align_t x_align, const y_align_t y_align,
1053                        const x_align_t x_origin, const y_align_t y_origin,
1054                        const short padding, const short limit,
1055                        const byte team, const bool force_opaque) {
1056 	byte drawn = 0;
1057 	for (size_t i = 0;i < sortedPlayers().size();i++) {
1058 		// Make sure we're not overrunning our limit.
1059 		if (limit != 0 && drawn >= limit) {
1060 			break;
1061 		}
1062 
1063 		player_t* player = sortedPlayers()[i];
1064 		if (inTeamPlayer(player, team)) {
1065 			std::ostringstream buffer;
1066 			buffer << player->GameTime / 60;
1067 
1068 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
1069 			              buffer.str().c_str(), CR_GREY, force_opaque);
1070 			y += 7 + padding;
1071 			drawn += 1;
1072 		}
1073 	}
1074 }
1075 
1076 // Draw a list of pings in the game.  Lines up with player names.
EAPlayerPings(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const bool force_opaque)1077 void EAPlayerPings(int x, int y, const float scale,
1078                    const x_align_t x_align, const y_align_t y_align,
1079                    const x_align_t x_origin, const y_align_t y_origin,
1080                    const short padding, const short limit,
1081                    const bool force_opaque) {
1082 	byte drawn = 0;
1083 	for (size_t i = 0;i < sortedPlayers().size();i++) {
1084 		// Make sure we're not overrunning our limit.
1085 		if (limit != 0 && drawn >= limit) {
1086 			break;
1087 		}
1088 
1089 		player_t* player = sortedPlayers()[i];
1090 		if (ingamePlayer(player)) {
1091 			std::ostringstream buffer;
1092 			buffer << player->ping;
1093 
1094 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
1095 			              buffer.str().c_str(), pingTextColor(player->ping),
1096 			              force_opaque);
1097 			y += 7 + padding;
1098 			drawn += 1;
1099 		}
1100 	}
1101 }
1102 
1103 // Draw a list of pings on a team.  Lines up with player names.
EATeamPlayerPings(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const byte team,const bool force_opaque)1104 void EATeamPlayerPings(int x, int y, const float scale,
1105                        const x_align_t x_align, const y_align_t y_align,
1106                        const x_align_t x_origin, const y_align_t y_origin,
1107                        const short padding, const short limit,
1108                        const byte team, const bool force_opaque) {
1109 	byte drawn = 0;
1110 	for (size_t i = 0;i < sortedPlayers().size();i++) {
1111 		// Make sure we're not overrunning our limit.
1112 		if (limit != 0 && drawn >= limit) {
1113 			break;
1114 		}
1115 
1116 		player_t* player = sortedPlayers()[i];
1117 		if (inTeamPlayer(player, team)) {
1118 			std::ostringstream buffer;
1119 			buffer << player->ping;
1120 
1121 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
1122 			              buffer.str().c_str(), pingTextColor(player->ping),
1123 			              force_opaque);
1124 			y += 7 + padding;
1125 			drawn += 1;
1126 		}
1127 	}
1128 }
1129 
1130 // Draw a list of spectator pings.  Lines up with spectators given the same
1131 // skip and limit.
EASpectatorPings(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,short skip,const short limit,const bool force_opaque)1132 void EASpectatorPings(int x, int y, const float scale,
1133                       const x_align_t x_align, const y_align_t y_align,
1134                       const x_align_t x_origin, const y_align_t y_origin,
1135                       const short padding, short skip, const short limit,
1136                       const bool force_opaque) {
1137 	byte drawn = 0;
1138 	for (size_t i = 0;i < sortedPlayers().size();i++) {
1139 		// Make sure we're not overrunning our limit.
1140 		if (limit != 0 && drawn >= limit) {
1141 			break;
1142 		}
1143 
1144 		player_t* player = sortedPlayers()[i];
1145 		if (spectatingPlayer(player)) {
1146 			if (skip <= 0) {
1147 				std::ostringstream buffer;
1148 				buffer << player->ping;
1149 
1150 				hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
1151 				              buffer.str().c_str(), pingTextColor(player->ping),
1152 				              force_opaque);
1153 				y += 7 + padding;
1154 				drawn += 1;
1155 			} else {
1156 				skip -= 1;
1157 			}
1158 		}
1159 	}
1160 }
1161 
1162 // Target structure.
1163 typedef struct {
1164 	player_t* PlayPtr;
1165 	int Distance;
1166 	int Color;
1167 } TargetInfo_t;
1168 
1169 // Draw a list of targets.  Thanks to GhostlyDeath for the original function.
EATargets(int x,int y,const float scale,const x_align_t x_align,const y_align_t y_align,const x_align_t x_origin,const y_align_t y_origin,const short padding,const short limit,const bool force_opaque)1170 void EATargets(int x, int y, const float scale,
1171                const x_align_t x_align, const y_align_t y_align,
1172                const x_align_t x_origin, const y_align_t y_origin,
1173                const short padding, const short limit,
1174                const bool force_opaque) {
1175 	if (gamestate != GS_LEVEL) {
1176 		// We're not in the game.
1177 		return;
1178 	}
1179 
1180 	if(!displayplayer().mo) {
1181 		// For some reason, the displayed player doesn't have a mobj, so
1182 		// if we let this function run we're going to crash.
1183 		return;
1184 	}
1185 
1186 	if (!hud_targetnames) {
1187 		// We don't want to see target names.
1188 		return;
1189 	}
1190 
1191 	if (!consoleplayer().spectator && !sv_allowtargetnames) {
1192 		// The server doesn't want us to use target names.
1193 		return;
1194 	}
1195 
1196 	std::vector<TargetInfo_t> Targets;
1197 
1198 	// What players should be drawn?
1199 	for (Players::iterator it = players.begin();it != players.end();++it)
1200 	{
1201 		if (it->spectator || !(it->mo) || it->mo->health <= 0)
1202 			continue;
1203 
1204 		// We don't care about the player whose eyes we are looking through.
1205 		if (&*it == &(displayplayer()))
1206 			continue;
1207 
1208 		if (!P_ActorInFOV(displayplayer().mo, it->mo, 45.0f, 512 * FRACUNIT))
1209 			continue;
1210 
1211 		// Pick a decent color for the player name.
1212 		int color;
1213 		if (sv_gametype == GM_TEAMDM || sv_gametype == GM_CTF) {
1214 			// In teamgames, we want to use team colors for targets.
1215 			color = teamTextColor(it->userinfo.team);
1216 		} else {
1217 			color = CR_GREY;
1218 		}
1219 
1220 		// Ok, make the temporary player info then add it
1221 		TargetInfo_t temp = {
1222 			&*it,
1223 			P_AproxDistance2(displayplayer().mo, it->mo) >> FRACBITS,
1224 			color
1225 		};
1226 		Targets.push_back(temp);
1227 	}
1228 
1229 	// GhostlyDeath -- Now Sort (hopefully I got my selection sort working!)
1230 	for (size_t i = 0; i < Targets.size(); i++) {
1231 		for (size_t j = i + 1; j < Targets.size(); j++) {
1232 			if (Targets[j].Distance < Targets[i].Distance) {
1233 				player_t* PlayPtr = Targets[i].PlayPtr;
1234 				int Distance = Targets[i].Distance;
1235 				int Color = Targets[i].Color;
1236 				Targets[i].PlayPtr = Targets[j].PlayPtr;
1237 				Targets[i].Distance = Targets[j].Distance;
1238 				Targets[i].Color = Targets[j].Color;
1239 				Targets[j].PlayPtr = PlayPtr;
1240 				Targets[j].Distance = Distance;
1241 				Targets[j].Color = Color;
1242 			}
1243 		}
1244 	}
1245 
1246 	// [AM] New ElementArray drawing function
1247 	byte drawn = 0;
1248 	for (size_t i = 0;i < Targets.size();i++) {
1249 		// Make sure we're not overrunning our limit.
1250 		if (limit != 0 && drawn >= limit) {
1251 			break;
1252 		}
1253 
1254 		if (Targets[i].PlayPtr == &(consoleplayer())) {
1255 			// You're looking at yourself.
1256 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
1257 			              "You", Targets[i].Color);
1258 		} else {
1259 			hud::DrawText(x, y, scale, x_align, y_align, x_origin, y_origin,
1260 			              Targets[i].PlayPtr->userinfo.netname.c_str(),
1261 			              Targets[i].Color);
1262 		}
1263 
1264 		y += 7 + padding;
1265 		drawn += 1;
1266 	}
1267 }
1268 
1269 }
1270