1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 1999-2020 by Sonic Team Junior.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file  hu_stuff.c
12 /// \brief Heads up display
13 
14 #include "doomdef.h"
15 #include "byteptr.h"
16 #include "hu_stuff.h"
17 
18 #include "m_menu.h" // gametype_cons_t
19 #include "m_cond.h" // emblems
20 #include "m_misc.h" // word jumping
21 
22 #include "d_clisrv.h"
23 
24 #include "g_game.h"
25 #include "g_input.h"
26 
27 #include "i_video.h"
28 #include "i_system.h"
29 
30 #include "st_stuff.h" // ST_HEIGHT
31 #include "r_local.h"
32 
33 #include "keys.h"
34 #include "v_video.h"
35 
36 #include "w_wad.h"
37 #include "z_zone.h"
38 
39 #include "console.h"
40 #include "am_map.h"
41 #include "d_main.h"
42 
43 #include "p_local.h" // camera, camera2
44 #include "p_tick.h"
45 
46 #ifdef HWRENDER
47 #include "hardware/hw_main.h"
48 #endif
49 
50 #include "lua_hud.h"
51 #include "lua_hook.h"
52 
53 // coords are scaled
54 #define HU_INPUTX 0
55 #define HU_INPUTY 0
56 
57 #define HU_SERVER_SAY 1 // Server message (dedicated).
58 #define HU_CSAY       2 // Server CECHOes to everyone.
59 
60 //-------------------------------------------
61 //              heads up font
62 //-------------------------------------------
63 patch_t *hu_font[HU_FONTSIZE];
64 patch_t *tny_font[HU_FONTSIZE];
65 patch_t *tallnum[10]; // 0-9
66 patch_t *nightsnum[10]; // 0-9
67 
68 // Level title and credits fonts
69 patch_t *lt_font[LT_FONTSIZE];
70 patch_t *cred_font[CRED_FONTSIZE];
71 patch_t *ttlnum[10]; // act numbers (0-9)
72 
73 // Name tag fonts
74 patch_t *ntb_font[NT_FONTSIZE];
75 patch_t *nto_font[NT_FONTSIZE];
76 
77 static player_t *plr;
78 boolean chat_on; // entering a chat message?
79 static char w_chat[HU_MAXMSGLEN];
80 static size_t c_input = 0; // let's try to make the chat input less shitty.
81 static boolean headsupactive = false;
82 boolean hu_showscores; // draw rankings
83 static char hu_tick;
84 
85 patch_t *rflagico;
86 patch_t *bflagico;
87 patch_t *rmatcico;
88 patch_t *bmatcico;
89 patch_t *tagico;
90 patch_t *tallminus;
91 patch_t *tallinfin;
92 
93 //-------------------------------------------
94 //              coop hud
95 //-------------------------------------------
96 
97 patch_t *emeraldpics[3][8]; // 0 = normal, 1 = tiny, 2 = coinbox
98 static patch_t *emblemicon;
99 patch_t *tokenicon;
100 static patch_t *exiticon;
101 static patch_t *nopingicon;
102 
103 //-------------------------------------------
104 //              misc vars
105 //-------------------------------------------
106 
107 // crosshair 0 = off, 1 = cross, 2 = angle, 3 = point, see m_menu.c
108 static patch_t *crosshair[HU_CROSSHAIRS]; // 3 precached crosshair graphics
109 
110 // -------
111 // protos.
112 // -------
113 static void HU_DrawRankings(void);
114 static void HU_DrawCoopOverlay(void);
115 static void HU_DrawNetplayCoopOverlay(void);
116 
117 //======================================================================
118 //                 KEYBOARD LAYOUTS FOR ENTERING TEXT
119 //======================================================================
120 
121 char *shiftxform;
122 
123 char english_shiftxform[] =
124 {
125 	0,
126 	1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
127 	11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
128 	21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
129 	31,
130 	' ', '!', '"', '#', '$', '%', '&',
131 	'"', // shift-'
132 	'(', ')', '*', '+',
133 	'<', // shift-,
134 	'_', // shift--
135 	'>', // shift-.
136 	'?', // shift-/
137 	')', // shift-0
138 	'!', // shift-1
139 	'@', // shift-2
140 	'#', // shift-3
141 	'$', // shift-4
142 	'%', // shift-5
143 	'^', // shift-6
144 	'&', // shift-7
145 	'*', // shift-8
146 	'(', // shift-9
147 	':',
148 	':', // shift-;
149 	'<',
150 	'+', // shift-=
151 	'>', '?', '@',
152 	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
153 	'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
154 	'{', // shift-[
155 	'|', // shift-backslash - OH MY GOD DOES WATCOM SUCK
156 	'}', // shift-]
157 	'"', '_',
158 	'~', // shift-`
159 	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
160 	'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
161 	'{', '|', '}', '~', 127
162 };
163 
164 static char cechotext[1024];
165 static tic_t cechotimer = 0;
166 static tic_t cechoduration = 5*TICRATE;
167 static INT32 cechoflags = 0;
168 
169 //======================================================================
170 //                          HEADS UP INIT
171 //======================================================================
172 
173 #ifndef NONET
174 // just after
175 static void Command_Say_f(void);
176 static void Command_Sayto_f(void);
177 static void Command_Sayteam_f(void);
178 static void Command_CSay_f(void);
179 static void Got_Saycmd(UINT8 **p, INT32 playernum);
180 #endif
181 
HU_LoadGraphics(void)182 void HU_LoadGraphics(void)
183 {
184 	char buffer[9];
185 	INT32 i, j;
186 
187 	if (dedicated)
188 		return;
189 
190 	j = HU_FONTSTART;
191 	for (i = 0; i < HU_FONTSIZE; i++, j++)
192 	{
193 		// cache the heads-up font for entire game execution
194 		sprintf(buffer, "STCFN%.3d", j);
195 		if (W_CheckNumForName(buffer) == LUMPERROR)
196 			hu_font[i] = NULL;
197 		else
198 			hu_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
199 
200 		// tiny version of the heads-up font
201 		sprintf(buffer, "TNYFN%.3d", j);
202 		if (W_CheckNumForName(buffer) == LUMPERROR)
203 			tny_font[i] = NULL;
204 		else
205 			tny_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
206 	}
207 
208 	j = LT_FONTSTART;
209 	for (i = 0; i < LT_FONTSIZE; i++)
210 	{
211 		sprintf(buffer, "LTFNT%.3d", j);
212 		j++;
213 
214 		if (W_CheckNumForName(buffer) == LUMPERROR)
215 			lt_font[i] = NULL;
216 		else
217 			lt_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
218 	}
219 
220 	// cache the credits font for entire game execution (why not?)
221 	j = CRED_FONTSTART;
222 	for (i = 0; i < CRED_FONTSIZE; i++)
223 	{
224 		sprintf(buffer, "CRFNT%.3d", j);
225 		j++;
226 
227 		if (W_CheckNumForName(buffer) == LUMPERROR)
228 			cred_font[i] = NULL;
229 		else
230 			cred_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
231 	}
232 
233 	//cache numbers too!
234 	for (i = 0; i < 10; i++)
235 	{
236 		sprintf(buffer, "STTNUM%d", i);
237 		tallnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
238 		sprintf(buffer, "NGTNUM%d", i);
239 		nightsnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX);
240 	}
241 
242 	// minus for negative tallnums
243 	tallminus = (patch_t *)W_CachePatchName("STTMINUS", PU_HUDGFX);
244 	tallinfin = (patch_t *)W_CachePatchName("STTINFIN", PU_HUDGFX);
245 
246 	// cache act numbers for level titles
247 	for (i = 0; i < 10; i++)
248 	{
249 		sprintf(buffer, "TTL%.2d", i);
250 		ttlnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
251 	}
252 
253 	// cache the base name tag font for entire game execution
254 	j = NT_FONTSTART;
255 	for (i = 0; i < NT_FONTSIZE; i++)
256 	{
257 		sprintf(buffer, "NTFNT%.3d", j);
258 		j++;
259 
260 		if (W_CheckNumForName(buffer) == LUMPERROR)
261 			ntb_font[i] = NULL;
262 		else
263 			ntb_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
264 	}
265 
266 	// cache the outline name tag font for entire game execution
267 	j = NT_FONTSTART;
268 	for (i = 0; i < NT_FONTSIZE; i++)
269 	{
270 		sprintf(buffer, "NTFNO%.3d", j);
271 		j++;
272 
273 		if (W_CheckNumForName(buffer) == LUMPERROR)
274 			nto_font[i] = NULL;
275 		else
276 			nto_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
277 	}
278 
279 	// cache the crosshairs, don't bother to know which one is being used,
280 	// just cache all 3, they're so small anyway.
281 	for (i = 0; i < HU_CROSSHAIRS; i++)
282 	{
283 		sprintf(buffer, "CROSHAI%c", '1'+i);
284 		crosshair[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
285 	}
286 
287 	emblemicon = W_CachePatchName("EMBLICON", PU_HUDGFX);
288 	tokenicon = W_CachePatchName("TOKNICON", PU_HUDGFX);
289 	exiticon = W_CachePatchName("EXITICON", PU_HUDGFX);
290 	nopingicon = W_CachePatchName("NOPINGICON", PU_HUDGFX);
291 
292 	emeraldpics[0][0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
293 	emeraldpics[0][1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
294 	emeraldpics[0][2] = W_CachePatchName("CHAOS3", PU_HUDGFX);
295 	emeraldpics[0][3] = W_CachePatchName("CHAOS4", PU_HUDGFX);
296 	emeraldpics[0][4] = W_CachePatchName("CHAOS5", PU_HUDGFX);
297 	emeraldpics[0][5] = W_CachePatchName("CHAOS6", PU_HUDGFX);
298 	emeraldpics[0][6] = W_CachePatchName("CHAOS7", PU_HUDGFX);
299 	emeraldpics[0][7] = W_CachePatchName("CHAOS8", PU_HUDGFX);
300 
301 	emeraldpics[1][0] = W_CachePatchName("TEMER1", PU_HUDGFX);
302 	emeraldpics[1][1] = W_CachePatchName("TEMER2", PU_HUDGFX);
303 	emeraldpics[1][2] = W_CachePatchName("TEMER3", PU_HUDGFX);
304 	emeraldpics[1][3] = W_CachePatchName("TEMER4", PU_HUDGFX);
305 	emeraldpics[1][4] = W_CachePatchName("TEMER5", PU_HUDGFX);
306 	emeraldpics[1][5] = W_CachePatchName("TEMER6", PU_HUDGFX);
307 	emeraldpics[1][6] = W_CachePatchName("TEMER7", PU_HUDGFX);
308 	//emeraldpics[1][7] = W_CachePatchName("TEMER8", PU_HUDGFX); -- unused
309 
310 	emeraldpics[2][0] = W_CachePatchName("EMBOX1", PU_HUDGFX);
311 	emeraldpics[2][1] = W_CachePatchName("EMBOX2", PU_HUDGFX);
312 	emeraldpics[2][2] = W_CachePatchName("EMBOX3", PU_HUDGFX);
313 	emeraldpics[2][3] = W_CachePatchName("EMBOX4", PU_HUDGFX);
314 	emeraldpics[2][4] = W_CachePatchName("EMBOX5", PU_HUDGFX);
315 	emeraldpics[2][5] = W_CachePatchName("EMBOX6", PU_HUDGFX);
316 	emeraldpics[2][6] = W_CachePatchName("EMBOX7", PU_HUDGFX);
317 	//emeraldpics[2][7] = W_CachePatchName("EMBOX8", PU_HUDGFX); -- unused
318 }
319 
320 // Initialise Heads up
321 // once at game startup.
322 //
HU_Init(void)323 void HU_Init(void)
324 {
325 #ifndef NONET
326 	COM_AddCommand("say", Command_Say_f);
327 	COM_AddCommand("sayto", Command_Sayto_f);
328 	COM_AddCommand("sayteam", Command_Sayteam_f);
329 	COM_AddCommand("csay", Command_CSay_f);
330 	RegisterNetXCmd(XD_SAY, Got_Saycmd);
331 #endif
332 
333 	// set shift translation table
334 	shiftxform = english_shiftxform;
335 }
336 
HU_Stop(void)337 static inline void HU_Stop(void)
338 {
339 	headsupactive = false;
340 }
341 
342 //
343 // Reset Heads up when consoleplayer spawns
344 //
HU_Start(void)345 void HU_Start(void)
346 {
347 	if (headsupactive)
348 		HU_Stop();
349 
350 	plr = &players[consoleplayer];
351 
352 	headsupactive = true;
353 }
354 
355 //======================================================================
356 //                            EXECUTION
357 //======================================================================
358 
359 #ifndef NONET
360 
361 // EVERY CHANGE IN THIS SCRIPT IS LOL XD! BY VINCYTM
362 
363 static UINT32 chat_nummsg_log = 0;
364 static UINT32 chat_nummsg_min = 0;
365 static UINT32 chat_scroll = 0;
366 static tic_t chat_scrolltime = 0;
367 
368 static UINT32 chat_maxscroll = 0; // how far can we scroll?
369 
370 //static chatmsg_t chat_mini[CHAT_BUFSIZE]; // Display the last few messages sent.
371 //static chatmsg_t chat_log[CHAT_BUFSIZE]; // Keep every message sent to us in memory so we can scroll n shit, it's cool.
372 
373 static char chat_log[CHAT_BUFSIZE][255]; // hold the last 48 or so messages in that log.
374 static char chat_mini[8][255]; // display up to 8 messages that will fade away / get overwritten
375 static tic_t chat_timers[8];
376 
377 static boolean chat_scrollmedown = false; // force instant scroll down on the chat log. Happens when you open it / send a message.
378 
379 // remove text from minichat table
380 
381 static INT16 addy = 0; // use this to make the messages scroll smoothly when one fades away
382 
HU_removeChatText_Mini(void)383 static void HU_removeChatText_Mini(void)
384 {
385     // MPC: Don't create new arrays, just iterate through an existing one
386 	size_t i;
387     for(i=0;i<chat_nummsg_min-1;i++) {
388         strcpy(chat_mini[i], chat_mini[i+1]);
389         chat_timers[i] = chat_timers[i+1];
390     }
391 	chat_nummsg_min--; // lost 1 msg.
392 
393 	// use addy and make shit slide smoothly af.
394 	addy += (vid.width < 640) ? 8 : 6;
395 
396 }
397 
398 // same but w the log. TODO: optimize this and maybe merge in a single func? im bad at C.
HU_removeChatText_Log(void)399 static void HU_removeChatText_Log(void)
400 {
401 	// MPC: Don't create new arrays, just iterate through an existing one
402 	size_t i;
403     for(i=0;i<chat_nummsg_log-1;i++) {
404         strcpy(chat_log[i], chat_log[i+1]);
405     }
406     chat_nummsg_log--; // lost 1 msg.
407 }
408 #endif
409 
HU_AddChatText(const char * text,boolean playsound)410 void HU_AddChatText(const char *text, boolean playsound)
411 {
412 #ifndef NONET
413 	if (playsound && cv_consolechat.value != 2) // Don't play the sound if we're using hidden chat.
414 		S_StartSound(NULL, sfx_radio);
415 	// reguardless of our preferences, put all of this in the chat buffer in case we decide to change from oldchat mid-game.
416 
417 	if (chat_nummsg_log >= CHAT_BUFSIZE) // too many messages!
418 		HU_removeChatText_Log();
419 
420 	strcpy(chat_log[chat_nummsg_log], text);
421 	chat_nummsg_log++;
422 
423 	if (chat_nummsg_min >= 8)
424 		HU_removeChatText_Mini();
425 
426 	strcpy(chat_mini[chat_nummsg_min], text);
427 	chat_timers[chat_nummsg_min] = TICRATE*cv_chattime.value;
428 	chat_nummsg_min++;
429 
430 	if (OLDCHAT) // if we're using oldchat, print directly in console
431 		CONS_Printf("%s\n", text);
432 	else			// if we aren't, still save the message to log.txt
433 		CON_LogMessage(va("%s\n", text));
434 #else
435 	(void)playsound;
436 	CONS_Printf("%s\n", text);
437 #endif
438 }
439 
440 #ifndef NONET
441 
442 /** Runs a say command, sending an ::XD_SAY message.
443   * A say command consists of a signed 8-bit integer for the target, an
444   * unsigned 8-bit flag variable, and then the message itself.
445   *
446   * The target is 0 to say to everyone, 1 to 32 to say to that player, or -1
447   * to -32 to say to everyone on that player's team. Note: This means you
448   * have to add 1 to the player number, since they are 0 to 31 internally.
449   *
450   * The flag HU_SERVER_SAY will be set if it is the dedicated server speaking.
451   *
452   * This function obtains the message using COM_Argc() and COM_Argv().
453   *
454   * \param target    Target to send message to.
455   * \param usedargs  Number of arguments to ignore.
456   * \param flags     Set HU_CSAY for server/admin to CECHO everyone.
457   * \sa Command_Say_f, Command_Sayteam_f, Command_Sayto_f, Got_Saycmd
458   * \author Graue <graue@oceanbase.org>
459   */
460 
461 
DoSayCommand(SINT8 target,size_t usedargs,UINT8 flags)462 static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags)
463 {
464 	char buf[254];
465 	size_t numwords, ix;
466 	char *msg = &buf[2];
467 	const size_t msgspace = sizeof buf - 2;
468 
469 	numwords = COM_Argc() - usedargs;
470 	I_Assert(numwords > 0);
471 
472 	if (CHAT_MUTE) // TODO: Per Player mute.
473 	{
474 		HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
475 		return;
476 	}
477 
478 	// Only servers/admins can CSAY.
479 	if(!server && !(IsPlayerAdmin(consoleplayer)))
480 		flags &= ~HU_CSAY;
481 
482 	// We handle HU_SERVER_SAY, not the caller.
483 	flags &= ~HU_SERVER_SAY;
484 	if(dedicated && !(flags & HU_CSAY))
485 		flags |= HU_SERVER_SAY;
486 
487 	buf[0] = target;
488 	buf[1] = flags;
489 	msg[0] = '\0';
490 
491 	for (ix = 0; ix < numwords; ix++)
492 	{
493 		if (ix > 0)
494 			strlcat(msg, " ", msgspace);
495 		strlcat(msg, COM_Argv(ix + usedargs), msgspace);
496 	}
497 
498 	if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
499 	{
500 		// what we're gonna do now is check if the player exists
501 		// with that logic, characters 4 and 5 are our numbers:
502 		const char *newmsg;
503 		char playernum[3];
504 		INT32 spc = 1; // used if playernum[1] is a space.
505 
506 		strncpy(playernum, msg+3, 3);
507 		// check for undesirable characters in our "number"
508 		if (((playernum[0] < '0') || (playernum[0] > '9')) || ((playernum[1] < '0') || (playernum[1] > '9')))
509 		{
510 			// check if playernum[1] is a space
511 			if (playernum[1] == ' ')
512 				spc = 0;
513 			// let it slide
514 			else
515 			{
516 				HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<playernum> \'.", false);
517 				return;
518 			}
519 		}
520 		// I'm very bad at C, I swear I am, additional checks eww!
521 		if (spc != 0 && msg[5] != ' ')
522 		{
523 			HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<playernum> \'.", false);
524 			return;
525 		}
526 
527 		target = atoi(playernum); // turn that into a number
528 		//CONS_Printf("%d\n", target);
529 
530 		// check for target player, if it doesn't exist then we can't send the message!
531 		if (target < MAXPLAYERS && playeringame[target]) // player exists
532 			target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
533 		else
534 		{
535 			HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
536 			return;
537 		}
538 		buf[0] = target;
539 		newmsg = msg+5+spc;
540 		strlcpy(msg, newmsg, 252);
541 	}
542 
543 	SendNetXCmd(XD_SAY, buf, strlen(msg) + 1 + msg-buf);
544 }
545 
546 /** Send a message to everyone.
547   * \sa DoSayCommand, Command_Sayteam_f, Command_Sayto_f
548   * \author Graue <graue@oceanbase.org>
549   */
Command_Say_f(void)550 static void Command_Say_f(void)
551 {
552 	if (COM_Argc() < 2)
553 	{
554 		CONS_Printf(M_GetText("say <message>: send a message\n"));
555 		return;
556 	}
557 
558 	DoSayCommand(0, 1, 0);
559 }
560 
561 /** Send a message to a particular person.
562   * \sa DoSayCommand, Command_Sayteam_f, Command_Say_f
563   * \author Graue <graue@oceanbase.org>
564   */
Command_Sayto_f(void)565 static void Command_Sayto_f(void)
566 {
567 	INT32 target;
568 
569 	if (COM_Argc() < 3)
570 	{
571 		CONS_Printf(M_GetText("sayto <playername|playernum> <message>: send a message to a player\n"));
572 		return;
573 	}
574 
575 	target = nametonum(COM_Argv(1));
576 	if (target == -1)
577 	{
578 		CONS_Alert(CONS_NOTICE, M_GetText("No player with that name!\n"));
579 		return;
580 	}
581 	target++; // Internally we use 0 to 31, but say command uses 1 to 32.
582 
583 	DoSayCommand((SINT8)target, 2, 0);
584 }
585 
586 /** Send a message to members of the player's team.
587   * \sa DoSayCommand, Command_Say_f, Command_Sayto_f
588   * \author Graue <graue@oceanbase.org>
589   */
Command_Sayteam_f(void)590 static void Command_Sayteam_f(void)
591 {
592 	if (COM_Argc() < 2)
593 	{
594 		CONS_Printf(M_GetText("sayteam <message>: send a message to your team\n"));
595 		return;
596 	}
597 
598 	if (dedicated)
599 	{
600 		CONS_Alert(CONS_NOTICE, M_GetText("Dedicated servers can't send team messages. Use \"say\".\n"));
601 		return;
602 	}
603 
604 	DoSayCommand(-1, 1, 0);
605 }
606 
607 /** Send a message to everyone, to be displayed by CECHO. Only
608   * permitted to servers and admins.
609   */
Command_CSay_f(void)610 static void Command_CSay_f(void)
611 {
612 	if (COM_Argc() < 2)
613 	{
614 		CONS_Printf(M_GetText("csay <message>: send a message to be shown in the middle of the screen\n"));
615 		return;
616 	}
617 
618 	if(!server && !IsPlayerAdmin(consoleplayer))
619 	{
620 		CONS_Alert(CONS_NOTICE, M_GetText("Only servers and admins can use csay.\n"));
621 		return;
622 	}
623 
624 	DoSayCommand(0, 1, HU_CSAY);
625 }
626 static tic_t stop_spamming[MAXPLAYERS];
627 
628 /** Receives a message, processing an ::XD_SAY command.
629   * \sa DoSayCommand
630   * \author Graue <graue@oceanbase.org>
631   */
Got_Saycmd(UINT8 ** p,INT32 playernum)632 static void Got_Saycmd(UINT8 **p, INT32 playernum)
633 {
634 	SINT8 target;
635 	UINT8 flags;
636 	const char *dispname;
637 	char *msg;
638 	boolean action = false;
639 	char *ptr;
640 	INT32 spam_eatmsg = 0;
641 
642 	CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
643 
644 	target = READSINT8(*p);
645 	flags = READUINT8(*p);
646 	msg = (char *)*p;
647 	SKIPSTRING(*p);
648 
649 	if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !(IsPlayerAdmin(playernum)))
650 	{
651 		CONS_Alert(CONS_WARNING, cv_mute.value ?
652 			M_GetText("Illegal say command received from %s while muted\n") : M_GetText("Illegal csay command received from non-admin %s\n"),
653 			player_names[playernum]);
654 		if (server)
655 			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
656 		return;
657 	}
658 
659 	//check for invalid characters (0x80 or above)
660 	{
661 		size_t i;
662 		const size_t j = strlen(msg);
663 		for (i = 0; i < j; i++)
664 		{
665 			if (msg[i] & 0x80)
666 			{
667 				CONS_Alert(CONS_WARNING, M_GetText("Illegal say command received from %s containing invalid characters\n"), player_names[playernum]);
668 				if (server)
669 					SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
670 				return;
671 			}
672 		}
673 	}
674 
675 	// before we do anything, let's verify the guy isn't spamming, get this easier on us.
676 
677 	//if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
678 	if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY))
679 	{
680 		CONS_Debug(DBG_NETPLAY,"Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message.\n", playernum+1, player_names[playernum]);
681 		stop_spamming[playernum] = 4;
682 		spam_eatmsg = 1;
683 	}
684 	else
685 		stop_spamming[playernum] = 4; // you can hold off for 4 tics, can you?
686 
687 	// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
688 
689 	if (LUAh_PlayerMsg(playernum, target, flags, msg))
690 		return;
691 
692 	if (spam_eatmsg)
693 		return; // don't proceed if we were supposed to eat the message.
694 
695 	// If it's a CSAY, just CECHO and be done with it.
696 	if (flags & HU_CSAY)
697 	{
698 		HU_SetCEchoDuration(5);
699 		I_OutputMsg("Server message: ");
700 		HU_DoCEcho(msg);
701 		return;
702 	}
703 
704 	// Handle "/me" actions, but only in messages to everyone.
705 	if (target == 0 && strlen(msg) > 4 && strnicmp(msg, "/me ", 4) == 0)
706 	{
707 		msg += 4;
708 		action = true;
709 	}
710 
711 	if (flags & HU_SERVER_SAY)
712 		dispname = "SERVER";
713 	else
714 		dispname = player_names[playernum];
715 
716 	// Clean up message a bit
717 	// If you use a \r character, you can remove your name
718 	// from before the text and then pretend to be someone else!
719 	ptr = msg;
720 	while (*ptr != '\0')
721 	{
722 		if (*ptr == '\r')
723 			*ptr = ' ';
724 
725 		ptr++;
726 	}
727 
728 	// Show messages sent by you, to you, to your team, or to everyone:
729 	if (playernum == consoleplayer // By you
730 	|| (target == -1 && ST_SameTeam(&players[consoleplayer], &players[playernum])) // To your team
731 	|| target == 0 // To everyone
732 	|| consoleplayer == target-1) // To you
733 	{
734 		const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt2, *textcolor = "\x80";
735 		char *tempchar = NULL;
736 
737 		// player is a spectator?
738         if (players[playernum].spectator)
739 		{
740 			cstart = "\x86";    // grey name
741 			textcolor = "\x86";
742 		}
743 		else if (target == -1) // say team
744 		{
745 			if (players[playernum].ctfteam == 1) // red
746 			{
747 				cstart = "\x85";
748 				textcolor = "\x85";
749 			}
750 			else // blue
751 			{
752 				cstart = "\x84";
753 				textcolor = "\x84";
754 			}
755 		}
756 		else
757         {
758 			UINT16 chatcolor = skincolors[players[playernum].skincolor].chatcolor;
759 
760 			if (!chatcolor || chatcolor%0x1000 || chatcolor>V_INVERTMAP)
761 				cstart = "\x80";
762 			else if (chatcolor == V_MAGENTAMAP)
763 				cstart = "\x81";
764 			else if (chatcolor == V_YELLOWMAP)
765 				cstart = "\x82";
766 			else if (chatcolor == V_GREENMAP)
767 				cstart = "\x83";
768 			else if (chatcolor == V_BLUEMAP)
769 				cstart = "\x84";
770 			else if (chatcolor == V_REDMAP)
771 				cstart = "\x85";
772 			else if (chatcolor == V_GRAYMAP)
773 				cstart = "\x86";
774 			else if (chatcolor == V_ORANGEMAP)
775 				cstart = "\x87";
776 			else if (chatcolor == V_SKYMAP)
777 				cstart = "\x88";
778 			else if (chatcolor == V_PURPLEMAP)
779 				cstart = "\x89";
780 			else if (chatcolor == V_AQUAMAP)
781 				cstart = "\x8a";
782 			else if (chatcolor == V_PERIDOTMAP)
783 				cstart = "\x8b";
784 			else if (chatcolor == V_AZUREMAP)
785 				cstart = "\x8c";
786 			else if (chatcolor == V_BROWNMAP)
787 				cstart = "\x8d";
788 			else if (chatcolor == V_ROSYMAP)
789 				cstart = "\x8e";
790 			else if (chatcolor == V_INVERTMAP)
791 				cstart = "\x8f";
792         }
793 		prefix = cstart;
794 
795 		// Give admins and remote admins their symbols.
796 		if (playernum == serverplayer)
797 			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(adminchar) + 1, PU_STATIC, NULL);
798 		else if (IsPlayerAdmin(playernum))
799 			tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL);
800 		if (tempchar)
801 		{
802 			if (playernum == serverplayer)
803 				strcat(tempchar, adminchar);
804 			else
805 				strcat(tempchar, remotechar);
806 			strcat(tempchar, cstart);
807 			cstart = tempchar;
808 		}
809 
810 		// Choose the proper format string for display.
811 		// Each format includes four strings: color start, display
812 		// name, color end, and the message itself.
813 		// '\4' makes the message yellow and beeps; '\3' just beeps.
814 		if (action)
815 			fmt2 = "* %s%s%s%s \x82%s%s";
816 		else if (target-1 == consoleplayer) // To you
817 		{
818 			prefix = "\x82[PM]";
819 			cstart = "\x82";
820 			textcolor = "\x82";
821 			fmt2 = "%s<%s%s>%s\x80 %s%s";
822 		}
823 		else if (target > 0) // By you, to another player
824 		{
825 			// Use target's name.
826 			dispname = player_names[target-1];
827 			prefix = "\x82[TO]";
828 			cstart = "\x82";
829 			fmt2 = "%s<%s%s>%s\x80 %s%s";
830 
831 		}
832 		else if (target == 0) // To everyone
833 			fmt2 = "%s<%s%s%s>\x80 %s%s";
834 		else // To your team
835 		{
836 			if (players[playernum].ctfteam == 1) // red
837 				prefix = "\x85[TEAM]";
838 			else if (players[playernum].ctfteam == 2) // blue
839 				prefix = "\x84[TEAM]";
840 			else
841 				prefix = "\x83"; // makes sure this doesn't implode if you sayteam on non-team gamemodes
842 
843 			fmt2 = "%s<%s%s>\x80%s %s%s";
844 		}
845 
846 		HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, textcolor, msg), cv_chatnotifications.value); // add to chat
847 
848 		if (tempchar)
849 			Z_Free(tempchar);
850 	}
851 #ifdef _DEBUG
852 	// I just want to point out while I'm here that because the data is still
853 	// sent to all players, techincally anyone can see your chat if they really
854 	// wanted to, even if you used sayto or sayteam.
855 	// You should never send any sensitive info through sayto for that reason.
856 	else
857 		CONS_Printf("Dropped chat: %d %d %s\n", playernum, target, msg);
858 #endif
859 }
860 
861 // Handles key input and string input
862 //
HU_keyInChatString(char * s,char ch)863 static inline boolean HU_keyInChatString(char *s, char ch)
864 {
865 	size_t l;
866 
867 	if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART])
868 	  || ch == ' ') // Allow spaces, of course
869 	{
870 		l = strlen(s);
871 		if (l < HU_MAXMSGLEN - 1)
872 		{
873 			if (c_input >= strlen(s)) // don't do anything complicated
874 			{
875 				s[l++] = ch;
876 				s[l]=0;
877 			}
878 			else
879 			{
880 
881 				// move everything past c_input for new characters:
882 				size_t m = HU_MAXMSGLEN-1;
883 				while (m>=c_input)
884 				{
885 					if (s[m])
886 						s[m+1] = (s[m]);
887 					if (m == 0) // prevent overflow
888 						break;
889 					m--;
890 				}
891 				s[c_input] = ch; // and replace this.
892 			}
893 			c_input++;
894 			return true;
895 		}
896 		return false;
897 	}
898 	else if (ch == KEY_BACKSPACE)
899 	{
900 		size_t i = c_input;
901 
902 		if (c_input <= 0)
903 			return false;
904 
905 		if (!s[i-1])
906 			return false;
907 
908 		if (i >= strlen(s)-1)
909 		{
910 			s[strlen(s)-1] = 0;
911 			c_input--;
912 			return false;
913 		}
914 
915 		for (; (i < HU_MAXMSGLEN); i++)
916 		{
917 			s[i-1] = s[i];
918 		}
919 		c_input--;
920 	}
921 	else if (ch != KEY_ENTER)
922 		return false; // did not eat key
923 
924 	return true; // ate the key
925 }
926 
927 #endif
928 
929 //
930 //
HU_Ticker(void)931 void HU_Ticker(void)
932 {
933 	if (dedicated)
934 		return;
935 
936 	hu_tick++;
937 	hu_tick &= 7; // currently only to blink chat input cursor
938 
939 	if (PLAYER1INPUTDOWN(gc_scores))
940 		hu_showscores = !chat_on;
941 	else
942 		hu_showscores = false;
943 }
944 
945 #ifndef NONET
946 
947 static boolean teamtalk = false;
948 
949 // Clear spaces so we don't end up with messages only made out of emptiness
HU_clearChatSpaces(void)950 static boolean HU_clearChatSpaces(void)
951 {
952 	size_t i = 0; // Used to just check our message
953 	char c; // current character we're iterating.
954 	boolean nothingbutspaces = true;
955 
956 	for (; i < strlen(w_chat); i++) // iterate through message and eradicate all spaces that don't belong.
957 	{
958 		c = w_chat[i];
959 		if (!c)
960 			break; // if there's nothing, it's safe to assume our message has ended, so let's not waste any more time here.
961 
962 		if (c != ' ') // Isn't a space
963 		{
964 			nothingbutspaces = false;
965 		}
966 	}
967 	return nothingbutspaces;
968 }
969 
970 //
971 //
HU_queueChatChar(char c)972 static void HU_queueChatChar(char c)
973 {
974 	// send automaticly the message (no more chat char)
975 	if (c == KEY_ENTER)
976 	{
977 		char buf[2+256];
978 		char *msg = &buf[2];
979 		size_t i = 0;
980 		size_t ci = 2;
981 		INT32 target = 0;
982 
983 		if (HU_clearChatSpaces()) // Avoids being able to send empty messages, or something.
984 			return; // If this returns true, that means our message was NOTHING but spaces, so don't send it period.
985 
986 		do {
987 			c = w_chat[-2+ci++];
988 			if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only.
989 				buf[ci-1]=c;
990 		} while (c);
991 
992 		for (;(i<HU_MAXMSGLEN);i++)
993 			w_chat[i] = 0; // reset this.
994 
995 		c_input = 0;
996 
997 		// last minute mute check
998 		if (CHAT_MUTE)
999 		{
1000 			HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false);
1001 			return;
1002 		}
1003 
1004 		if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm
1005 		{
1006 			INT32 spc = 1; // used if playernum[1] is a space.
1007 			char playernum[3];
1008 			const char *newmsg;
1009 
1010 			// what we're gonna do now is check if the player exists
1011 			// with that logic, characters 4 and 5 are our numbers:
1012 
1013 			// teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko.
1014 			if (teamtalk)
1015 			{
1016 				HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false);
1017 				return;
1018 			}
1019 
1020 			strncpy(playernum, msg+3, 3);
1021 			// check for undesirable characters in our "number"
1022 			if (((playernum[0] < '0') || (playernum[0] > '9')) || ((playernum[1] < '0') || (playernum[1] > '9')))
1023 			{
1024 				// check if playernum[1] is a space
1025 				if (playernum[1] == ' ')
1026 					spc = 0;
1027 					// let it slide
1028 				else
1029 				{
1030 					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<player num> \'.", false);
1031 					return;
1032 				}
1033 			}
1034 			// I'm very bad at C, I swear I am, additional checks eww!
1035 			if (spc != 0)
1036 			{
1037 				if (msg[5] != ' ')
1038 				{
1039 					HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<player num> \'.", false);
1040 					return;
1041 				}
1042 			}
1043 
1044 			target = atoi(playernum); // turn that into a number
1045 			//CONS_Printf("%d\n", target);
1046 
1047 			// check for target player, if it doesn't exist then we can't send the message!
1048 			if (target < MAXPLAYERS && playeringame[target]) // player exists
1049 				target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work!
1050 			else
1051 			{
1052 				HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same
1053 				return;
1054 			}
1055 
1056 			// we need to get rid of the /pm<player num>
1057 			newmsg = msg+5+spc;
1058 			strlcpy(msg, newmsg, 255);
1059 		}
1060 		if (ci > 3) // don't send target+flags+empty message.
1061 		{
1062 			if (teamtalk)
1063 				buf[0] = -1; // target
1064 			else
1065 				buf[0] = target;
1066 
1067 			buf[1] = 0; // flags
1068 			SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1);
1069 		}
1070 		return;
1071 	}
1072 }
1073 #endif
1074 
HU_clearChatChars(void)1075 void HU_clearChatChars(void)
1076 {
1077 	size_t i = 0;
1078 	for (;i<HU_MAXMSGLEN;i++)
1079 		w_chat[i] = 0; // reset this.
1080 	chat_on = false;
1081 	c_input = 0;
1082 
1083 	I_UpdateMouseGrab();
1084 }
1085 
1086 #ifndef NONET
1087 static boolean justscrolleddown;
1088 static boolean justscrolledup;
1089 static INT16 typelines = 1; // number of drawfill lines we need when drawing the chat. it's some weird hack and might be one frame off but I'm lazy to make another loop.
1090 // It's up here since it has to be reset when we open the chat.
1091 #endif
1092 
1093 //
1094 // Returns true if key eaten
1095 //
HU_Responder(event_t * ev)1096 boolean HU_Responder(event_t *ev)
1097 {
1098 #ifndef NONET
1099 	INT32 c=0;
1100 #endif
1101 
1102 	if (ev->type != ev_keydown)
1103 		return false;
1104 
1105 	// only KeyDown events now...
1106 
1107 	/*// Shoot, to prevent P1 chatting from ruining the game for everyone else, it's either:
1108 	// A. completely disallow opening chat entirely in online splitscreen
1109 	// or B. iterate through all controls to make sure it's bound to player 1 before eating
1110 	// You can see which one I chose.
1111 	// (Unless if you're sharing a keyboard, since you probably establish when you start chatting that you have dibs on it...)
1112 	// (Ahhh, the good ol days when I was a kid who couldn't afford an extra USB controller...)
1113 
1114 	if (ev->data1 >= KEY_MOUSE1)
1115 	{
1116 		INT32 i;
1117 		for (i = 0; i < num_gamecontrols; i++)
1118 		{
1119 			if (gamecontrol[i][0] == ev->data1 || gamecontrol[i][1] == ev->data1)
1120 				break;
1121 		}
1122 
1123 		if (i == num_gamecontrols)
1124 			return false;
1125 	}*/	//We don't actually care about that unless we get splitscreen netgames. :V
1126 
1127 #ifndef NONET
1128 	c = (INT32)ev->data1;
1129 
1130 	if (!chat_on)
1131 	{
1132 		// enter chat mode
1133 		if ((ev->data1 == gamecontrol[gc_talkkey][0] || ev->data1 == gamecontrol[gc_talkkey][1])
1134 			&& netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise.
1135 		{
1136 			chat_on = true;
1137 			w_chat[0] = 0;
1138 			teamtalk = false;
1139 			chat_scrollmedown = true;
1140 			typelines = 1;
1141 			return true;
1142 		}
1143 		if ((ev->data1 == gamecontrol[gc_teamkey][0] || ev->data1 == gamecontrol[gc_teamkey][1])
1144 			&& netgame && !OLD_MUTE)
1145 		{
1146 			chat_on = true;
1147 			w_chat[0] = 0;
1148 			teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams.
1149 			chat_scrollmedown = true;
1150 			typelines = 1;
1151 			return true;
1152 		}
1153 	}
1154 	else // if chat_on
1155 	{
1156 
1157 		// Ignore modifier keys
1158 		// Note that we do this here so users can still set
1159 		// their chat keys to one of these, if they so desire.
1160 		if (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT
1161 		 || ev->data1 == KEY_LCTRL || ev->data1 == KEY_RCTRL
1162 		 || ev->data1 == KEY_LALT || ev->data1 == KEY_RALT)
1163 			return true;
1164 
1165 		c = (INT32)ev->data1;
1166 
1167 		// I know this looks very messy but this works. If it ain't broke, don't fix it!
1168 		// shift LETTERS to uppercase if we have capslock or are holding shift
1169 		if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
1170 		{
1171 			if (shiftdown ^ capslock)
1172 				c = shiftxform[c];
1173 		}
1174 		else	// if we're holding shift we should still shift non letter symbols
1175 		{
1176 			if (shiftdown)
1177 				c = shiftxform[c];
1178 		}
1179 
1180 		// pasting. pasting is cool. chat is a bit limited, though :(
1181 		if (((c == 'v' || c == 'V') && ctrldown) && !CHAT_MUTE)
1182 		{
1183 			const char *paste = I_ClipboardPaste();
1184 			size_t chatlen;
1185 			size_t pastelen;
1186 
1187 			// create a dummy string real quickly
1188 
1189 			if (paste == NULL)
1190 				return true;
1191 
1192 			chatlen = strlen(w_chat);
1193 			pastelen = strlen(paste);
1194 			if (chatlen+pastelen > HU_MAXMSGLEN)
1195 				return true; // we can't paste this!!
1196 
1197 			if (c_input >= strlen(w_chat)) // add it at the end of the string.
1198 			{
1199 				memcpy(&w_chat[chatlen], paste, pastelen); // copy all of that.
1200 				c_input += pastelen;
1201 				/*size_t i = 0;
1202 				for (;i<pastelen;i++)
1203 				{
1204 					HU_queueChatChar(paste[i]); // queue it so that it's actually sent. (this chat write thing is REALLY messy.)
1205 				}*/
1206 				return true;
1207 			}
1208 			else	// otherwise, we need to shift everything and make space, etc etc
1209 			{
1210 				size_t i = HU_MAXMSGLEN-1;
1211 				while (i >= c_input)
1212 				{
1213 					if (w_chat[i])
1214 						w_chat[i+pastelen] = w_chat[i];
1215 					if (i == 0) // prevent overflow
1216 						break;
1217 					i--;
1218 				}
1219 				memcpy(&w_chat[c_input], paste, pastelen); // copy all of that.
1220 				c_input += pastelen;
1221 				return true;
1222 			}
1223 		}
1224 
1225 		if (!CHAT_MUTE && HU_keyInChatString(w_chat,c))
1226 		{
1227 			HU_queueChatChar(c);
1228 		}
1229 		if (c == KEY_ENTER)
1230 		{
1231 			chat_on = false;
1232 			c_input = 0; // reset input cursor
1233 			chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :)
1234 			I_UpdateMouseGrab();
1235 		}
1236 		else if (c == KEY_ESCAPE
1237 			|| ((c == gamecontrol[gc_talkkey][0] || c == gamecontrol[gc_talkkey][1]
1238 			|| c == gamecontrol[gc_teamkey][0] || c == gamecontrol[gc_teamkey][1])
1239 			&& c >= KEY_MOUSE1)) // If it's not a keyboard key, then the chat button is used as a toggle.
1240 		{
1241 			chat_on = false;
1242 			c_input = 0; // reset input cursor
1243 			I_UpdateMouseGrab();
1244 		}
1245 		else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0 && !OLDCHAT) // CHAT SCROLLING YAYS!
1246 		{
1247 			chat_scroll--;
1248 			justscrolledup = true;
1249 			chat_scrolltime = 4;
1250 		}
1251 		else if ((c == KEY_DOWNARROW || c == KEY_MOUSEWHEELDOWN) && chat_scroll < chat_maxscroll && chat_maxscroll > 0 && !OLDCHAT)
1252 		{
1253 			chat_scroll++;
1254 			justscrolleddown = true;
1255 			chat_scrolltime = 4;
1256 		}
1257 		else if (c == KEY_LEFTARROW && c_input != 0 && !OLDCHAT) // i said go back
1258 		{
1259 			if (ctrldown)
1260 				c_input = M_JumpWordReverse(w_chat, c_input);
1261 			else
1262 				c_input--;
1263 		}
1264 		else if (c == KEY_RIGHTARROW && c_input < strlen(w_chat) && !OLDCHAT) // don't need to check for admin or w/e here since the chat won't ever contain anything if it's muted.
1265 		{
1266 			if (ctrldown)
1267 				c_input += M_JumpWord(&w_chat[c_input]);
1268 			else
1269 				c_input++;
1270 		}
1271 		return true;
1272 	}
1273 #endif
1274 
1275 	return false;
1276 }
1277 
1278 
1279 //======================================================================
1280 //                         HEADS UP DRAWING
1281 //======================================================================
1282 
1283 #ifndef NONET
1284 
1285 // Precompile a wordwrapped string to any given width.
1286 // This is a muuuch better method than V_WORDWRAP.
1287 // again stolen and modified a bit from video.c, don't mind me, will need to rearrange this one day.
1288 // this one is simplified for the chat drawer.
CHAT_WordWrap(INT32 x,INT32 w,INT32 option,const char * string)1289 static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string)
1290 {
1291 	INT32 c;
1292 	size_t chw, i, lastusablespace = 0;
1293 	size_t slen;
1294 	char *newstring = Z_StrDup(string);
1295 	INT32 spacewidth = (vid.width < 640) ? 8 : 4, charwidth = (vid.width < 640) ? 8 : 4;
1296 
1297 	slen = strlen(string);
1298 	x = 0;
1299 
1300 	for (i = 0; i < slen; ++i)
1301 	{
1302 		c = newstring[i];
1303 		if ((UINT8)c >= 0x80 && (UINT8)c <= 0x89) //color parsing! -Inuyasha 2.16.09
1304 			continue;
1305 
1306 		if (c == '\n')
1307 		{
1308 			x = 0;
1309 			lastusablespace = 0;
1310 			continue;
1311 		}
1312 
1313 		if (!(option & V_ALLOWLOWERCASE))
1314 			c = toupper(c);
1315 		c -= HU_FONTSTART;
1316 
1317 		if (c < 0 || c >= HU_FONTSIZE || !hu_font[c])
1318 		{
1319 			chw = spacewidth;
1320 			lastusablespace = i;
1321 		}
1322 		else
1323 			chw = charwidth;
1324 
1325 		x += chw;
1326 
1327 		if (lastusablespace != 0 && x > w)
1328 		{
1329 			//CONS_Printf("Wrap at index %d\n", i);
1330 			newstring[lastusablespace] = '\n';
1331 			i = lastusablespace+1;
1332 			lastusablespace = 0;
1333 			x = 0;
1334 		}
1335 	}
1336 	return newstring;
1337 }
1338 
1339 
1340 // 30/7/18: chaty is now the distance at which the lowest point of the chat will be drawn if that makes any sense.
1341 
1342 INT16 chatx = 13, chaty = 169; // let's use this as our coordinates
1343 
1344 // chat stuff by VincyTM LOL XD!
1345 
1346 // HU_DrawMiniChat
1347 
HU_drawMiniChat(void)1348 static void HU_drawMiniChat(void)
1349 {
1350 	INT32 x = chatx+2;
1351 	INT32 charwidth = 4, charheight = 6;
1352 	INT32 boxw = cv_chatwidth.value;
1353 	INT32 dx = 0, dy = 0;
1354 	size_t i = chat_nummsg_min;
1355 	boolean prev_linereturn = false; // a hack to prevent double \n while I have no idea why they happen in the first place.
1356 
1357 	INT32 msglines = 0;
1358 	// process all messages once without rendering anything or doing anything fancy so that we know how many lines each message has...
1359 	INT32 y;
1360 
1361 	if (!chat_nummsg_min)
1362 		return; // needless to say it's useless to do anything if we don't have anything to draw.
1363 
1364 	/*if (splitscreen > 1)
1365 		boxw = max(64, boxw/2);*/
1366 
1367 	for (; i>0; i--)
1368 	{
1369 		char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]);
1370 		size_t j = 0;
1371 		INT32 linescount = 0;
1372 
1373 		while(msg[j]) // iterate through msg
1374 		{
1375 			if (msg[j] < HU_FONTSTART) // don't draw
1376 			{
1377 				if (msg[j] == '\n') // get back down.
1378 				{
1379 					++j;
1380 					if (!prev_linereturn)
1381 					{
1382 						linescount += 1;
1383 						dx = 0;
1384 					}
1385 					prev_linereturn = true;
1386 					continue;
1387 				}
1388 				else if (msg[j] & 0x80) // stolen from video.c, nice.
1389 				{
1390 					++j;
1391 					continue;
1392 				}
1393 
1394 				++j;
1395 			}
1396 			else
1397 			{
1398 				j++;
1399 			}
1400 			prev_linereturn = false;
1401 			dx += charwidth;
1402 			if (dx >= boxw)
1403 			{
1404 				dx = 0;
1405 				linescount += 1;
1406 			}
1407 		}
1408 		dy = 0;
1409 		dx = 0;
1410 		msglines += linescount+1;
1411 
1412 		if (msg)
1413 			Z_Free(msg);
1414 	}
1415 
1416 	y = chaty - charheight*(msglines+1);
1417 
1418 	/*if (splitscreen)
1419 	{
1420 		y -= BASEVIDHEIGHT/2;
1421 		if (splitscreen > 1)
1422 			y += 16;
1423 	}*/
1424 
1425 	dx = 0;
1426 	dy = 0;
1427 	i = 0;
1428 	prev_linereturn = false;
1429 
1430 	for (; i<=(chat_nummsg_min-1); i++) // iterate through our hot messages
1431 	{
1432 		INT32 clrflag = 0;
1433 		INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9; // see below...
1434 		INT32 transflag = (timer >= 0 && timer <= 9) ? (timer*V_10TRANS) : 0; // you can make bad jokes out of this one.
1435 		size_t j = 0;
1436 		char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]); // get the current message, and word wrap it.
1437 		UINT8 *colormap = NULL;
1438 
1439 		while(msg[j]) // iterate through msg
1440 		{
1441 			if (msg[j] < HU_FONTSTART) // don't draw
1442 			{
1443 				if (msg[j] == '\n') // get back down.
1444 				{
1445 					++j;
1446 					if (!prev_linereturn)
1447 					{
1448 						dy += charheight;
1449 						dx = 0;
1450 					}
1451 					prev_linereturn = true;
1452 					continue;
1453 				}
1454 				else if (msg[j] & 0x80) // stolen from video.c, nice.
1455 				{
1456 					clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
1457 					colormap = V_GetStringColormap(clrflag);
1458 					++j;
1459 					continue;
1460 				}
1461 
1462 				++j;
1463 			}
1464 			else
1465 			{
1466 				if (cv_chatbacktint.value) // on request of wolfy
1467 					V_DrawFillConsoleMap(x + dx + 2, y+dy, charwidth, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT);
1468 
1469 				V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, true, colormap);
1470 			}
1471 
1472 			dx += charwidth;
1473 			prev_linereturn = false;
1474 			if (dx >= boxw)
1475 			{
1476 				dx = 0;
1477 				dy += charheight;
1478 			}
1479 		}
1480 		dy += charheight;
1481 		dx = 0;
1482 
1483 		if (msg)
1484 			Z_Free(msg);
1485 	}
1486 
1487 	// decrement addy and make that shit smooth:
1488 	addy /= 2;
1489 
1490 }
1491 
1492 // HU_DrawChatLog
1493 
HU_drawChatLog(INT32 offset)1494 static void HU_drawChatLog(INT32 offset)
1495 {
1496 	INT32 charwidth = 4, charheight = 6;
1497 	INT32 boxw = cv_chatwidth.value, boxh = cv_chatheight.value;
1498 	INT32 x = chatx+2, y, dx = 0, dy = 0;
1499 	UINT32 i = 0;
1500 	INT32 chat_topy, chat_bottomy;
1501 	boolean atbottom = false;
1502 
1503 	// make sure that our scroll position isn't "illegal";
1504 	if (chat_scroll > chat_maxscroll)
1505 		chat_scroll = chat_maxscroll;
1506 
1507 #ifdef NETSPLITSCREEN
1508 	if (splitscreen)
1509 	{
1510 		boxh = max(6, boxh/2);
1511 		if (splitscreen > 1)
1512 			boxw = max(64, boxw/2);
1513 	}
1514 #endif
1515 
1516 	y = chaty - offset*charheight - (chat_scroll*charheight) - boxh*charheight - 12;
1517 
1518 #ifdef NETSPLITSCREEN
1519 	if (splitscreen)
1520 	{
1521 		y -= BASEVIDHEIGHT/2;
1522 		//if (splitscreen > 1)
1523 			//y += 16;
1524 	}
1525 #endif
1526 
1527 	chat_topy = y + chat_scroll*charheight;
1528 	chat_bottomy = chat_topy + boxh*charheight;
1529 
1530 	V_DrawFillConsoleMap(chatx, chat_topy, boxw, boxh*charheight +2, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT); // log box
1531 
1532 	for (i=0; i<chat_nummsg_log; i++) // iterate through our chatlog
1533 	{
1534 		INT32 clrflag = 0;
1535 		INT32 j = 0;
1536 		char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]); // get the current message, and word wrap it.
1537 		UINT8 *colormap = NULL;
1538 		while(msg[j]) // iterate through msg
1539 		{
1540 			if (msg[j] < HU_FONTSTART) // don't draw
1541 			{
1542 				if (msg[j] == '\n') // get back down.
1543 				{
1544 					++j;
1545 					dy += charheight;
1546 					dx = 0;
1547 					continue;
1548 				}
1549 				else if (msg[j] & 0x80) // stolen from video.c, nice.
1550 				{
1551 					clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK;
1552 					colormap = V_GetStringColormap(clrflag);
1553 					++j;
1554 					continue;
1555 				}
1556 
1557 				++j;
1558 			}
1559 			else
1560 			{
1561 				if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy)))
1562 					V_DrawChatCharacter(x + dx + 2, y+dy+2, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, true, colormap);
1563 				else
1564 					j++; // don't forget to increment this or we'll get stuck in the limbo.
1565 			}
1566 
1567 			dx += charwidth;
1568 			if (dx >= boxw-charwidth-2 && i<chat_nummsg_log && msg[j] >= HU_FONTSTART) // end of message shouldn't count, nor should invisible characters!!!!
1569 			{
1570 				dx = 0;
1571 				dy += charheight;
1572 			}
1573 		}
1574 		dy += charheight;
1575 		dx = 0;
1576 
1577 		if (msg)
1578 			Z_Free(msg);
1579 	}
1580 
1581 
1582 	if (((chat_scroll >= chat_maxscroll) || (chat_scrollmedown)) && !(justscrolleddown || justscrolledup || chat_scrolltime)) // was already at the bottom of the page before new maxscroll calculation and was NOT scrolling.
1583 	{
1584 		atbottom = true; // we should scroll
1585 	}
1586 	chat_scrollmedown = false;
1587 
1588 	// getmaxscroll through a lazy hack. We do all these loops,
1589 	// so let's not do more loops that are gonna lag the game more. :P
1590 	chat_maxscroll = max(dy / charheight - cv_chatheight.value, 0);
1591 
1592 	// if we're not bound by the time, autoscroll for next frame:
1593 	if (atbottom)
1594 		chat_scroll = chat_maxscroll;
1595 
1596 	// draw arrows to indicate that we can (or not) scroll.
1597 	// account for Y = -1 offset in tinyfont
1598 	if (chat_scroll > 0)
1599 		V_DrawThinString(chatx-8, ((justscrolledup) ? (chat_topy-1) : (chat_topy)) - 1, V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_YELLOWMAP, "\x1A"); // up arrow
1600 	if (chat_scroll < chat_maxscroll)
1601 		V_DrawThinString(chatx-8, chat_bottomy-((justscrolleddown) ? 5 : 6) - 1, V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_YELLOWMAP, "\x1B"); // down arrow
1602 
1603 	justscrolleddown = false;
1604 	justscrolledup = false;
1605 }
1606 
1607 //
1608 // HU_DrawChat
1609 //
1610 // Draw chat input
1611 //
1612 
HU_DrawChat(void)1613 static void HU_DrawChat(void)
1614 {
1615 	INT32 charwidth = 4, charheight = 6;
1616 	INT32 boxw = cv_chatwidth.value;
1617 	INT32 t = 0, c = 0, y = chaty - (typelines*charheight);
1618 	UINT32 i = 0, saylen = strlen(w_chat); // You learn new things everyday!
1619 	INT32 cflag = 0;
1620 	const char *ntalk = "Say: ", *ttalk = "Team: ";
1621 	const char *talk = ntalk;
1622 	const char *mute = "Chat has been muted.";
1623 
1624 #ifdef NETSPLITSCREEN
1625 	if (splitscreen)
1626 	{
1627 		y -= BASEVIDHEIGHT/2;
1628 		if (splitscreen > 1)
1629 		{
1630 			y += 16;
1631 			boxw = max(64, boxw/2);
1632 		}
1633 	}
1634 #endif
1635 
1636 	if (teamtalk)
1637 	{
1638 		talk = ttalk;
1639 #if 0
1640 		if (players[consoleplayer].ctfteam == 1)
1641 			t = 0x500;  // Red
1642 		else if (players[consoleplayer].ctfteam == 2)
1643 			t = 0x400; // Blue
1644 #endif
1645 	}
1646 
1647 	if (CHAT_MUTE)
1648 	{
1649 		talk = mute;
1650 		typelines = 1;
1651 		cflag = V_GRAYMAP; // set text in gray if chat is muted.
1652 	}
1653 
1654 	V_DrawFillConsoleMap(chatx, y-1, boxw, (typelines*charheight), 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT);
1655 
1656 	while (talk[i])
1657 	{
1658 		if (talk[i] < HU_FONTSTART)
1659 			++i;
1660 		else
1661 		{
1662 			V_DrawChatCharacter(chatx + c + 2, y, talk[i] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|cflag, true, V_GetStringColormap(talk[i]|cflag));
1663 			i++;
1664 		}
1665 
1666 		c += charwidth;
1667 	}
1668 
1669 	// if chat is muted, just draw the log and get it over with, no need to draw anything else.
1670 	if (CHAT_MUTE)
1671 	{
1672 		HU_drawChatLog(0);
1673 		return;
1674 	}
1675 
1676 	i = 0;
1677 	typelines = 1;
1678 
1679 	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
1680 		V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, true, NULL);
1681 
1682 	while (w_chat[i])
1683 	{
1684 		boolean skippedline = false;
1685 		if (c_input == (i+1))
1686 		{
1687 			INT32 cursorx = (c+charwidth < boxw-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1); // we may have to go down.
1688 			INT32 cursory = (cursorx != chatx+1) ? (y) : (y+charheight);
1689 			if (hu_tick < 4)
1690 				V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, true, NULL);
1691 
1692 			if (cursorx == chatx+1 && saylen == i) // a weirdo hack
1693 			{
1694 				typelines += 1;
1695 				skippedline = true;
1696 			}
1697 		}
1698 
1699 		//Hurdler: isn't it better like that?
1700 		if (w_chat[i] < HU_FONTSTART)
1701 			++i;
1702 		else
1703 			V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, true, NULL);
1704 
1705 		c += charwidth;
1706 		if (c > boxw-(charwidth*2) && !skippedline)
1707 		{
1708 			c = 0;
1709 			y += charheight;
1710 			typelines += 1;
1711 		}
1712 	}
1713 
1714 	// handle /pm list. It's messy, horrible and I don't care.
1715 	if (strnicmp(w_chat, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk) // 320x200 unsupported kthxbai
1716 	{
1717 		INT32 count = 0;
1718 		INT32 p_dispy = chaty - charheight -1;
1719 #ifdef NETSPLITSCREEN
1720 		if (splitscreen)
1721 		{
1722 			p_dispy -= BASEVIDHEIGHT/2;
1723 			if (splitscreen > 1)
1724 				p_dispy += 16;
1725 		}
1726 #endif
1727 
1728 		i = 0;
1729 		for(i=0; (i<MAXPLAYERS); i++)
1730 		{
1731 			// filter: (code needs optimization pls help I'm bad with C)
1732 			if (w_chat[3])
1733 			{
1734 				char playernum[3];
1735 				UINT32 n;
1736 				// right, that's half important: (w_chat[4] may be a space since /pm0 msg is perfectly acceptable!)
1737 				if ( ( ((w_chat[3] != 0) && ((w_chat[3] < '0') || (w_chat[3] > '9'))) || ((w_chat[4] != 0) && (((w_chat[4] < '0') || (w_chat[4] > '9'))))) && (w_chat[4] != ' '))
1738 					break;
1739 
1740 				strncpy(playernum, w_chat+3, 3);
1741 				n = atoi(playernum); // turn that into a number
1742 				// special cases:
1743 
1744 				if ((n == 0) && !(w_chat[4] == '0'))
1745 				{
1746 					if (!(i<10))
1747 						continue;
1748 				}
1749 				else if ((n == 1) && !(w_chat[3] == '0'))
1750 				{
1751 					if (!((i == 1) || ((i >= 10) && (i <= 19))))
1752 						continue;
1753 				}
1754 				else if ((n == 2) && !(w_chat[3] == '0'))
1755 				{
1756 					if (!((i == 2) || ((i >= 20) && (i <= 29))))
1757 						continue;
1758 				}
1759 				else if ((n == 3) && !(w_chat[3] == '0'))
1760 				{
1761 					if (!((i == 3) || ((i >= 30) && (i <= 31))))
1762 						continue;
1763 				}
1764 				else	// general case.
1765 				{
1766 					if (i != n)
1767 						continue;
1768 				}
1769 			}
1770 
1771 			if (playeringame[i])
1772 			{
1773 				char name[MAXPLAYERNAME+1];
1774 				strlcpy(name, player_names[i], 7); // shorten name to 7 characters.
1775 				V_DrawFillConsoleMap(chatx+ boxw + 2, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT); // fill it like the chat so the text doesn't become hard to read because of the hud.
1776 				V_DrawSmallString(chatx+ boxw + 4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, va("\x82%d\x80 - %s", i, name));
1777 				count++;
1778 			}
1779 		}
1780 		if (count == 0) // no results.
1781 		{
1782 			V_DrawFillConsoleMap(chatx+boxw+2, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT); // fill it like the chat so the text doesn't become hard to read because of the hud.
1783 			V_DrawSmallString(chatx+boxw+4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, "NO RESULT.");
1784 		}
1785 	}
1786 
1787 	HU_drawChatLog(typelines-1); // typelines is the # of lines we're typing. If there's more than 1 then the log should scroll up to give us more space.
1788 }
1789 
1790 
1791 // For anyone who, for some godforsaken reason, likes oldchat.
1792 
HU_DrawChat_Old(void)1793 static void HU_DrawChat_Old(void)
1794 {
1795 	INT32 t = 0, c = 0, y = HU_INPUTY;
1796 	size_t i = 0;
1797 	const char *ntalk = "Say: ", *ttalk = "Say-Team: ";
1798 	const char *talk = ntalk;
1799 	INT32 charwidth = 8 * con_scalefactor; //(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
1800 	INT32 charheight = 8 * con_scalefactor; //(hu_font['A'-HU_FONTSTART]->height) * con_scalefactor;
1801 	if (teamtalk)
1802 	{
1803 		talk = ttalk;
1804 #if 0
1805 		if (players[consoleplayer].ctfteam == 1)
1806 			t = 0x500;  // Red
1807 		else if (players[consoleplayer].ctfteam == 2)
1808 			t = 0x400; // Blue
1809 #endif
1810 	}
1811 
1812 	while (talk[i])
1813 	{
1814 		if (talk[i] < HU_FONTSTART)
1815 		{
1816 			++i;
1817 			//charwidth = 4 * con_scalefactor;
1818 		}
1819 		else
1820 		{
1821 			//charwidth = (hu_font[talk[i]-HU_FONTSTART]->width) * con_scalefactor;
1822 			V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, true);
1823 		}
1824 		c += charwidth;
1825 	}
1826 
1827 	if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4)
1828 		V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, true);
1829 
1830 	i = 0;
1831 	while (w_chat[i])
1832 	{
1833 
1834 		if (c_input == (i+1) && hu_tick < 4)
1835 		{
1836 			INT32 cursorx = (HU_INPUTX+c+charwidth < vid.width) ? (HU_INPUTX + c + charwidth) : (HU_INPUTX); // we may have to go down.
1837 			INT32 cursory = (cursorx != HU_INPUTX) ? (y) : (y+charheight);
1838 			V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, true);
1839 		}
1840 
1841 		//Hurdler: isn't it better like that?
1842 		if (w_chat[i] < HU_FONTSTART)
1843 		{
1844 			++i;
1845 			//charwidth = 4 * con_scalefactor;
1846 		}
1847 		else
1848 		{
1849 			//charwidth = (hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor;
1850 			V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, true);
1851 		}
1852 
1853 		c += charwidth;
1854 		if (c >= vid.width)
1855 		{
1856 			c = 0;
1857 			y += charheight;
1858 		}
1859 	}
1860 
1861 	if (hu_tick < 4)
1862 		V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, true);
1863 }
1864 #endif
1865 
1866 // draw the Crosshair, at the exact center of the view.
1867 //
1868 // Crosshairs are pre-cached at HU_Init
1869 
HU_DrawCrosshair(void)1870 static inline void HU_DrawCrosshair(void)
1871 {
1872 	INT32 i, y;
1873 
1874 	i = cv_crosshair.value & 3;
1875 	if (!i)
1876 		return;
1877 
1878 	if ((netgame || multiplayer) && players[displayplayer].spectator)
1879 		return;
1880 
1881 #ifdef HWRENDER
1882 	if (rendermode != render_soft)
1883 		y = (INT32)gl_basewindowcentery;
1884 	else
1885 #endif
1886 		y = viewwindowy + (viewheight>>1);
1887 
1888 	V_DrawScaledPatch(vid.width>>1, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
1889 }
1890 
HU_DrawCrosshair2(void)1891 static inline void HU_DrawCrosshair2(void)
1892 {
1893 	INT32 i, y;
1894 
1895 	i = cv_crosshair2.value & 3;
1896 	if (!i)
1897 		return;
1898 
1899 	if ((netgame || multiplayer) && players[secondarydisplayplayer].spectator)
1900 		return;
1901 
1902 #ifdef HWRENDER
1903 	if (rendermode != render_soft)
1904 		y = (INT32)gl_basewindowcentery;
1905 	else
1906 #endif
1907 		y = viewwindowy + (viewheight>>1);
1908 
1909 	if (splitscreen)
1910 	{
1911 #ifdef HWRENDER
1912 		if (rendermode != render_soft)
1913 			y += (INT32)gl_viewheight;
1914 		else
1915 #endif
1916 			y += viewheight;
1917 
1918 		V_DrawScaledPatch(vid.width>>1, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
1919 	}
1920 }
1921 
HU_DrawCEcho(void)1922 static void HU_DrawCEcho(void)
1923 {
1924 	INT32 i = 0;
1925 	INT32 y = (BASEVIDHEIGHT/2)-4;
1926 	INT32 pnumlines = 0;
1927 
1928 	UINT32 realflags = cechoflags|V_PERPLAYER; // requested as part of splitscreen's stuff
1929 	INT32 realalpha = (INT32)((cechoflags & V_ALPHAMASK) >> V_ALPHASHIFT);
1930 
1931 	char *line;
1932 	char *echoptr;
1933 	char temp[1024];
1934 
1935 	for (i = 0; cechotext[i] != '\0'; ++i)
1936 		if (cechotext[i] == '\\')
1937 			pnumlines++;
1938 
1939 	y -= (pnumlines-1)*((realflags & V_RETURN8) ? 4 : 6);
1940 
1941 	// Prevent crashing because I'm sick of this
1942 	if (y < 0)
1943 	{
1944 		CONS_Alert(CONS_WARNING, "CEcho contained too many lines, not displaying\n");
1945 		cechotimer = 0;
1946 		return;
1947 	}
1948 
1949 	// Automatic fadeout
1950 	if (realflags & V_AUTOFADEOUT)
1951 	{
1952 		UINT32 tempalpha = (UINT32)max((INT32)(10 - cechotimer), realalpha);
1953 
1954 		realflags &= ~V_ALPHASHIFT;
1955 		realflags |= (tempalpha << V_ALPHASHIFT);
1956 	}
1957 
1958 	strcpy(temp, cechotext);
1959 	echoptr = &temp[0];
1960 
1961 	while (*echoptr != '\0')
1962 	{
1963 		line = strchr(echoptr, '\\');
1964 
1965 		if (line == NULL)
1966 			break;
1967 
1968 		*line = '\0';
1969 
1970 		V_DrawCenteredString(BASEVIDWIDTH/2, y, realflags, echoptr);
1971 		if (splitscreen)
1972 		{
1973 			stplyr = ((stplyr == &players[displayplayer]) ? &players[secondarydisplayplayer] : &players[displayplayer]);
1974 			V_DrawCenteredString(BASEVIDWIDTH/2, y, realflags, echoptr);
1975 			stplyr = ((stplyr == &players[displayplayer]) ? &players[secondarydisplayplayer] : &players[displayplayer]);
1976 		}
1977 		y += ((realflags & V_RETURN8) ? 8 : 12);
1978 
1979 		echoptr = line;
1980 		echoptr++;
1981 	}
1982 
1983 	--cechotimer;
1984 }
1985 
HU_drawGametype(void)1986 static void HU_drawGametype(void)
1987 {
1988 	const char *strvalue = NULL;
1989 
1990 	if (gametype < 0 || gametype >= gametypecount)
1991 		return; // not a valid gametype???
1992 
1993 	strvalue = Gametype_Names[gametype];
1994 
1995 	if (splitscreen)
1996 		V_DrawString(4, 184, 0, strvalue);
1997 	else
1998 		V_DrawString(4, 192, 0, strvalue);
1999 }
2000 
2001 //
2002 // demo info stuff
2003 //
2004 UINT32 hu_demoscore;
2005 UINT32 hu_demotime;
2006 UINT16 hu_demorings;
2007 
HU_DrawDemoInfo(void)2008 static void HU_DrawDemoInfo(void)
2009 {
2010 	INT32 h = 188;
2011 	if (modeattacking == ATTACKING_NIGHTS)
2012 		h -= 12;
2013 
2014 	V_DrawString(4, h-24, V_YELLOWMAP|V_ALLOWLOWERCASE, va(M_GetText("%s's replay"), player_names[0]));
2015 	if (modeattacking)
2016 	{
2017 		V_DrawString(4, h-16, V_YELLOWMAP|V_MONOSPACE, "SCORE:");
2018 		V_DrawRightAlignedString(120, h-16, V_MONOSPACE, va("%d", hu_demoscore));
2019 
2020 		V_DrawString(4, h-8, V_YELLOWMAP|V_MONOSPACE, "TIME:");
2021 		if (hu_demotime != UINT32_MAX)
2022 			V_DrawRightAlignedString(120, h-8, V_MONOSPACE, va("%i:%02i.%02i",
2023 				G_TicsToMinutes(hu_demotime,true),
2024 				G_TicsToSeconds(hu_demotime),
2025 				G_TicsToCentiseconds(hu_demotime)));
2026 		else
2027 			V_DrawRightAlignedString(120, h-8, V_MONOSPACE, "--:--.--");
2028 
2029 		if (modeattacking == ATTACKING_RECORD)
2030 		{
2031 			V_DrawString(4, h, V_YELLOWMAP|V_MONOSPACE, "RINGS:");
2032 			V_DrawRightAlignedString(120, h, V_MONOSPACE, va("%d", hu_demorings));
2033 		}
2034 	}
2035 }
2036 
2037 // Heads up displays drawer, call each frame
2038 //
HU_Drawer(void)2039 void HU_Drawer(void)
2040 {
2041 #ifndef NONET
2042 	// draw chat string plus cursor
2043 	if (chat_on)
2044 	{
2045 		// count down the scroll timer.
2046 		if (chat_scrolltime > 0)
2047 			chat_scrolltime--;
2048 		if (!OLDCHAT)
2049 			HU_DrawChat();
2050 		else
2051 			HU_DrawChat_Old();
2052 	}
2053 	else
2054 	{
2055 		typelines = 1;
2056 		chat_scrolltime = 0;
2057 		if (!OLDCHAT && cv_consolechat.value < 2 && netgame) // Don't display minimized chat if you set the mode to Window (Hidden)
2058 			HU_drawMiniChat(); // draw messages in a cool fashion.
2059 	}
2060 
2061 	if (netgame) // would handle that in hu_drawminichat, but it's actually kinda awkward when you're typing a lot of messages. (only handle that in netgames duh)
2062 	{
2063 		size_t i = 0;
2064 
2065 		// handle spam while we're at it:
2066 		for(; (i<MAXPLAYERS); i++)
2067 		{
2068 			if (stop_spamming[i] > 0)
2069 				stop_spamming[i]--;
2070 		}
2071 
2072 		// handle chat timers
2073 		for (i=0; (i<chat_nummsg_min); i++)
2074 		{
2075 			if (chat_timers[i] > 0)
2076 				chat_timers[i]--;
2077 			else
2078 				HU_removeChatText_Mini();
2079 		}
2080 	}
2081 #endif
2082 
2083 	if (cechotimer)
2084 		HU_DrawCEcho();
2085 
2086 	if (demoplayback && hu_showscores)
2087 		HU_DrawDemoInfo();
2088 
2089 	if (!Playing()
2090 	 || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE
2091 	 || gamestate == GS_CREDITS      || gamestate == GS_EVALUATION
2092 	 || gamestate == GS_ENDING       || gamestate == GS_GAMEEND)
2093 		return;
2094 
2095 	// draw multiplayer rankings
2096 	if (hu_showscores)
2097 	{
2098 		if (netgame || multiplayer)
2099 		{
2100 			if (LUA_HudEnabled(hud_rankings))
2101 				HU_DrawRankings();
2102 			if (gametyperules & GTR_CAMPAIGN)
2103 				HU_DrawNetplayCoopOverlay();
2104 		}
2105 		else
2106 			HU_DrawCoopOverlay();
2107 		LUAh_ScoresHUD();
2108 	}
2109 
2110 	if (gamestate != GS_LEVEL)
2111 		return;
2112 
2113 	// draw the crosshair, not when viewing demos nor with chasecam
2114 	if (LUA_HudEnabled(hud_crosshair))
2115 	{
2116 		if (!automapactive && cv_crosshair.value && !demoplayback &&
2117 			(!camera.chase || ticcmd_ztargetfocus[0])
2118 		&& !players[displayplayer].spectator)
2119 			HU_DrawCrosshair();
2120 
2121 		if (!automapactive && cv_crosshair2.value && !demoplayback &&
2122 			(!camera2.chase || ticcmd_ztargetfocus[1])
2123 		&& !players[secondarydisplayplayer].spectator)
2124 			HU_DrawCrosshair2();
2125 	}
2126 
2127 	// draw desynch text
2128 	if (hu_redownloadinggamestate)
2129 	{
2130 		static UINT32 resynch_ticker = 0;
2131 		char resynch_text[14];
2132 		UINT32 i;
2133 
2134 		// Animate the dots
2135 		resynch_ticker++;
2136 		strcpy(resynch_text, "Resynching");
2137 		for (i = 0; i < (resynch_ticker / 16) % 4; i++)
2138 			strcat(resynch_text, ".");
2139 
2140 		V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP | V_ALLOWLOWERCASE, resynch_text);
2141 	}
2142 
2143 	if (modeattacking && pausedelay > 0 && !pausebreakkey)
2144 	{
2145 		INT32 strength = ((pausedelay - 1 - NEWTICRATE/2)*10)/(NEWTICRATE/3);
2146 		INT32 y = hudinfo[HUD_LIVES].y - 13;
2147 
2148 		if (players[consoleplayer].powers[pw_carry] == CR_NIGHTSMODE)
2149 			y -= 16;
2150 		else
2151 		{
2152 			if (players[consoleplayer].pflags & PF_AUTOBRAKE)
2153 				y -= 8;
2154 			if (players[consoleplayer].pflags & PF_ANALOGMODE)
2155 				y -= 8;
2156 		}
2157 
2158 		V_DrawThinString(hudinfo[HUD_LIVES].x-2, y,
2159 			hudinfo[HUD_LIVES].f|((leveltime & 4) ? V_SKYMAP : V_BLUEMAP),
2160 			"HOLD TO RETRY...");
2161 
2162 		if (strength > 9)
2163 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0);
2164 		else if (strength > 0)
2165 			V_DrawFadeScreen(0, strength);
2166 	}
2167 }
2168 
2169 //======================================================================
2170 //                 HUD MESSAGES CLEARING FROM SCREEN
2171 //======================================================================
2172 
2173 // Clear old messages from the borders around the view window
2174 // (only for reduced view, refresh the borders when needed)
2175 //
2176 // startline: y coord to start clear,
2177 // clearlines: how many lines to clear.
2178 //
2179 static INT32 oldclearlines;
2180 
HU_Erase(void)2181 void HU_Erase(void)
2182 {
2183 	INT32 topline, bottomline;
2184 	INT32 y, yoffset;
2185 
2186 #ifdef HWRENDER
2187 	// clear hud msgs on double buffer (OpenGL mode)
2188 	boolean secondframe;
2189 	static INT32 secondframelines;
2190 #endif
2191 
2192 	if (con_clearlines == oldclearlines && !con_hudupdate && !chat_on)
2193 		return;
2194 
2195 #ifdef HWRENDER
2196 	// clear the other frame in double-buffer modes
2197 	secondframe = (con_clearlines != oldclearlines);
2198 	if (secondframe)
2199 		secondframelines = oldclearlines;
2200 #endif
2201 
2202 	// clear the message lines that go away, so use _oldclearlines_
2203 	bottomline = oldclearlines;
2204 	oldclearlines = con_clearlines;
2205 	if (chat_on && OLDCHAT)
2206 		if (bottomline < 8)
2207 			bottomline = 8; // only do it for consolechat. consolechat is gay.
2208 
2209 	if (automapactive || viewwindowx == 0) // hud msgs don't need to be cleared
2210 		return;
2211 
2212 	// software mode copies view border pattern & beveled edges from the backbuffer
2213 	if (rendermode == render_soft)
2214 	{
2215 		topline = 0;
2216 		for (y = topline, yoffset = y*vid.width; y < bottomline; y++, yoffset += vid.width)
2217 		{
2218 			if (y < viewwindowy || y >= viewwindowy + viewheight)
2219 				R_VideoErase(yoffset, vid.width); // erase entire line
2220 			else
2221 			{
2222 				R_VideoErase(yoffset, viewwindowx); // erase left border
2223 				// erase right border
2224 				R_VideoErase(yoffset + viewwindowx + viewwidth, viewwindowx);
2225 			}
2226 		}
2227 		con_hudupdate = false; // if it was set..
2228 	}
2229 #ifdef HWRENDER
2230 	else if (rendermode != render_none)
2231 	{
2232 		// refresh just what is needed from the view borders
2233 		HWR_DrawViewBorder(secondframelines);
2234 		con_hudupdate = secondframe;
2235 	}
2236 #endif
2237 }
2238 
2239 //======================================================================
2240 //                   IN-LEVEL MULTIPLAYER RANKINGS
2241 //======================================================================
2242 
2243 #define supercheckdef (!(players[tab[i].num].charflags & SF_NOSUPERSPRITES) && ((players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS1] || players[tab[i].num].mo->state >= &states[S_PLAY_SUPER_TRANS6])) || (players[tab[i].num].powers[pw_carry] == CR_NIGHTSMODE && skins[players[tab[i].num].skin].flags & SF_SUPER)))
2244 #define greycheckdef (players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD || (G_IsSpecialStage(gamemap) && players[tab[i].num].exiting))
2245 
2246 //
2247 // HU_drawPing
2248 //
HU_drawPing(INT32 x,INT32 y,UINT32 ping,boolean notext,INT32 flags)2249 void HU_drawPing(INT32 x, INT32 y, UINT32 ping, boolean notext, INT32 flags)
2250 {
2251 	UINT8 numbars = 0; // how many ping bars do we draw?
2252 	UINT8 barcolor = 31; // color we use for the bars (green, yellow, red or black)
2253 	SINT8 i = 0;
2254 	SINT8 yoffset = 6;
2255 	INT32 dx = x+1 - (V_SmallStringWidth(va("%dms", ping),
2256 				V_ALLOWLOWERCASE|flags)/2);
2257 
2258 	if (ping < 128)
2259 	{
2260 		numbars = 3;
2261 		barcolor = 112;
2262 	}
2263 	else if (ping < 256)
2264 	{
2265 		numbars = 2;
2266 		barcolor = 73;
2267 	}
2268 	else if (ping < UINT32_MAX)
2269 	{
2270 		numbars = 1;
2271 		barcolor = 35;
2272 	}
2273 
2274 	if (ping < UINT32_MAX && (!notext || vid.width >= 640)) // how sad, we're using a shit resolution.
2275 		V_DrawSmallString(dx, y+4, V_ALLOWLOWERCASE|flags, va("%dms", ping));
2276 
2277 	for (i=0; (i<3); i++) // Draw the ping bar
2278 	{
2279 		V_DrawFill(x+2 *(i-1), y+yoffset-4, 2, 8-yoffset, 31|flags);
2280 		if (i < numbars)
2281 			V_DrawFill(x+2 *(i-1), y+yoffset-3, 1, 8-yoffset-1, barcolor|flags);
2282 
2283 		yoffset -= 2;
2284 	}
2285 
2286 	if (ping == UINT32_MAX)
2287 		V_DrawSmallScaledPatch(x + 4 - nopingicon->width/2, y + 9 - nopingicon->height/2, 0, nopingicon);
2288 }
2289 
2290 //
2291 // HU_DrawTabRankings
2292 //
HU_DrawTabRankings(INT32 x,INT32 y,playersort_t * tab,INT32 scorelines,INT32 whiteplayer)2293 void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer)
2294 {
2295 	INT32 i;
2296 	const UINT8 *colormap;
2297 	boolean greycheck, supercheck;
2298 
2299 	//this function is designed for 9 or less score lines only
2300 	I_Assert(scorelines <= 9);
2301 
2302 	V_DrawFill(1, 26, 318, 1, 0); //Draw a horizontal line because it looks nice!
2303 
2304 	for (i = 0; i < scorelines; i++)
2305 	{
2306 		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
2307 			continue; //ignore them.
2308 
2309 		greycheck = greycheckdef;
2310 		supercheck = supercheckdef;
2311 
2312 		if (!splitscreen) // don't draw it on splitscreen,
2313 		{
2314 			if (tab[i].num != serverplayer)
2315 				HU_drawPing(x + 253, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
2316 			//else
2317 			//	V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER");
2318 		}
2319 
2320 		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
2321 			V_DrawString(x + 20, y,
2322 		                 ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
2323 		                 | (greycheck ? V_60TRANS : 0)
2324 		                 | V_ALLOWLOWERCASE, tab[i].name);
2325 
2326 		// Draw emeralds
2327 		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
2328 			HU_DrawEmeralds(x-12,y+2,255);
2329 		else if (!players[tab[i].num].powers[pw_super]
2330 			|| ((leveltime/7) & 1))
2331 		{
2332 			HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
2333 		}
2334 
2335 		if (greycheck)
2336 			V_DrawSmallTranslucentPatch (x, y-4, V_80TRANS, livesback);
2337 		else
2338 			V_DrawSmallScaledPatch (x, y-4, 0, livesback);
2339 
2340 		if (tab[i].color == 0)
2341 		{
2342 			colormap = colormaps;
2343 			if (supercheck)
2344 				V_DrawSmallScaledPatch(x, y-4, 0, superprefix[players[tab[i].num].skin]);
2345 			else
2346 			{
2347 				if (greycheck)
2348 					V_DrawSmallTranslucentPatch(x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin]);
2349 				else
2350 					V_DrawSmallScaledPatch(x, y-4, 0, faceprefix[players[tab[i].num].skin]);
2351 			}
2352 		}
2353 		else
2354 		{
2355 			if (supercheck)
2356 			{
2357 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
2358 				V_DrawSmallMappedPatch (x, y-4, 0, superprefix[players[tab[i].num].skin], colormap);
2359 			}
2360 			else
2361 			{
2362 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2363 				if (greycheck)
2364 					V_DrawSmallTranslucentMappedPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin], colormap);
2365 				else
2366 					V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
2367 			}
2368 		}
2369 
2370 		if (G_GametypeUsesLives() && !(G_GametypeUsesCoopLives() && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
2371 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|(greycheck ? V_60TRANS : 0), va("%dx", players[tab[i].num].lives));
2372 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
2373 		{
2374 			if (greycheck)
2375 				V_DrawSmallTranslucentPatch(x-32, y-4, V_60TRANS, tagico);
2376 			else
2377 				V_DrawSmallScaledPatch(x-32, y-4, 0, tagico);
2378 		}
2379 
2380 		if (players[tab[i].num].exiting || (players[tab[i].num].pflags & PF_FINISHED))
2381 			V_DrawSmallScaledPatch(x - exiticon->width/2 - 1, y-3, 0, exiticon);
2382 
2383 		if (gametyperankings[gametype] == GT_RACE)
2384 		{
2385 			if (circuitmap)
2386 			{
2387 				if (players[tab[i].num].exiting)
2388 					V_DrawRightAlignedString(x+240, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
2389 				else
2390 					V_DrawRightAlignedString(x+240, y, (greycheck ? V_60TRANS : 0), va("%u", tab[i].count));
2391 			}
2392 			else
2393 				V_DrawRightAlignedString(x+240, y, (greycheck ? V_60TRANS : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
2394 		}
2395 		else
2396 			V_DrawRightAlignedString(x+240, y, (greycheck ? V_60TRANS : 0), va("%u", tab[i].count));
2397 
2398 		y += 16;
2399 	}
2400 }
2401 
2402 //
2403 // HU_Draw32Emeralds
2404 //
HU_Draw32Emeralds(INT32 x,INT32 y,INT32 pemeralds)2405 static void HU_Draw32Emeralds(INT32 x, INT32 y, INT32 pemeralds)
2406 {
2407 	//Draw the emeralds, in the CORRECT order, using tiny emerald sprites.
2408 	if (pemeralds & EMERALD1)
2409 		V_DrawSmallScaledPatch(x  , y, 0, emeraldpics[1][0]);
2410 
2411 	if (pemeralds & EMERALD2)
2412 		V_DrawSmallScaledPatch(x+4, y, 0, emeraldpics[1][1]);
2413 
2414 	if (pemeralds & EMERALD3)
2415 		V_DrawSmallScaledPatch(x+8, y, 0, emeraldpics[1][2]);
2416 
2417 	if (pemeralds & EMERALD4)
2418 		V_DrawSmallScaledPatch(x+12  , y, 0, emeraldpics[1][3]);
2419 
2420 	if (pemeralds & EMERALD5)
2421 		V_DrawSmallScaledPatch(x+16, y, 0, emeraldpics[1][4]);
2422 
2423 	if (pemeralds & EMERALD6)
2424 		V_DrawSmallScaledPatch(x+20, y, 0, emeraldpics[1][5]);
2425 
2426 	if (pemeralds & EMERALD7)
2427 		V_DrawSmallScaledPatch(x+24,   y,   0, emeraldpics[1][6]);
2428 }
2429 
2430 //
2431 // HU_Draw32TeamTabRankings
2432 //
HU_Draw32TeamTabRankings(playersort_t * tab,INT32 whiteplayer)2433 static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer)
2434 {
2435 	INT32 i,x,y;
2436 	INT32 redplayers = 0, blueplayers = 0;
2437 	const UINT8 *colormap;
2438 	char name[MAXPLAYERNAME+1];
2439 	boolean greycheck, supercheck;
2440 
2441 	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two teams.
2442 	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
2443 	V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom.
2444 
2445 	for (i = 0; i < MAXPLAYERS; i++)
2446 	{
2447 		if (players[tab[i].num].spectator)
2448 			continue; //ignore them.
2449 
2450 		greycheck = greycheckdef;
2451 		supercheck = supercheckdef;
2452 
2453 		if (tab[i].color == skincolor_redteam) //red
2454 		{
2455 			redplayers++;
2456 			x = 14 + (BASEVIDWIDTH/2);
2457 			y = (redplayers * 9) + 20;
2458 		}
2459 		else if (tab[i].color == skincolor_blueteam) //blue
2460 		{
2461 			blueplayers++;
2462 			x = 14;
2463 			y = (blueplayers * 9) + 20;
2464 		}
2465 		else //er?  not on red or blue, so ignore them
2466 			continue;
2467 
2468 		greycheck = greycheckdef;
2469 		supercheck = supercheckdef;
2470 
2471 		strlcpy(name, tab[i].name, 8);
2472 		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
2473 			V_DrawString(x + 10, y,
2474 			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
2475 			             | (greycheck ? 0 : V_TRANSLUCENT)
2476 			             | V_ALLOWLOWERCASE, name);
2477 
2478 		if (gametyperules & GTR_TEAMFLAGS)
2479 		{
2480 			if (players[tab[i].num].gotflag & GF_REDFLAG) // Red
2481 				V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, rflagico, 0);
2482 			else if (players[tab[i].num].gotflag & GF_BLUEFLAG) // Blue
2483 				V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, bflagico, 0);
2484 		}
2485 
2486 		// Draw emeralds
2487 		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
2488 		{
2489 			HU_Draw32Emeralds(x+60, y+2, 255);
2490 			//HU_DrawEmeralds(x-12,y+2,255);
2491 		}
2492 		else if (!players[tab[i].num].powers[pw_super]
2493 			|| ((leveltime/7) & 1))
2494 		{
2495 			HU_Draw32Emeralds(x+60, y+2, tab[i].emeralds);
2496 			//HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
2497 		}
2498 
2499 		if (supercheck)
2500 		{
2501 			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2502 			V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], colormap);
2503 		}
2504 		else
2505 		{
2506 			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2507 			if (players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD)
2508 				V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], colormap);
2509 			else
2510 				V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], colormap);
2511 		}
2512 		V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].spectator || players[tab[i].num].playerstate == PST_DEAD) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
2513 		if (!splitscreen)
2514 		{
2515 			if (tab[i].num != serverplayer)
2516 				HU_drawPing(x + 135, y+1, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], true, 0);
2517 			//else
2518 				//V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
2519 		}
2520 	}
2521 }
2522 
2523 //
2524 // HU_DrawTeamTabRankings
2525 //
HU_DrawTeamTabRankings(playersort_t * tab,INT32 whiteplayer)2526 void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
2527 {
2528 	INT32 i,x,y;
2529 	INT32 redplayers = 0, blueplayers = 0;
2530 	boolean smol = false;
2531 	const UINT8 *colormap;
2532 	char name[MAXPLAYERNAME+1];
2533 	boolean greycheck, supercheck;
2534 
2535 	// before we draw, we must count how many players are in each team. It makes an additional loop, but we need to know if we have to draw a big or a small ranking.
2536 	for (i = 0; i < MAXPLAYERS; i++)
2537 	{
2538 		if (players[tab[i].num].spectator)
2539 			continue; //ignore them.
2540 
2541 		if (tab[i].color == skincolor_redteam) //red
2542 		{
2543 			if (redplayers++ > 8)
2544 			{
2545 				smol = true;
2546 				break; // don't make more loops than we need to.
2547 			}
2548 		}
2549 		else if (tab[i].color == skincolor_blueteam) //blue
2550 		{
2551 			if (blueplayers++ > 8)
2552 			{
2553 				smol = true;
2554 				break;
2555 			}
2556 		}
2557 		else //er?  not on red or blue, so ignore them
2558 			continue;
2559 
2560 	}
2561 
2562 	// I'll be blunt with you, this may add more lines, but I'm not adding weird cases for this, so we're executing a separate function.
2563 	if (smol == true || cv_compactscoreboard.value)
2564 	{
2565 		HU_Draw32TeamTabRankings(tab, whiteplayer);
2566 		return;
2567 	}
2568 
2569 	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two teams.
2570 	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
2571 	V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom.
2572 
2573 	i=0, redplayers=0, blueplayers=0;
2574 
2575 	for (i = 0; i < MAXPLAYERS; i++)
2576 	{
2577 		if (players[tab[i].num].spectator)
2578 			continue; //ignore them.
2579 
2580 		if (tab[i].color == skincolor_redteam) //red
2581 		{
2582 			if (redplayers++ > 8)
2583 				continue;
2584 			x = 32 + (BASEVIDWIDTH/2);
2585 			y = (redplayers * 16) + 16;
2586 		}
2587 		else if (tab[i].color == skincolor_blueteam) //blue
2588 		{
2589 			if (blueplayers++ > 8)
2590 				continue;
2591 			x = 32;
2592 			y = (blueplayers * 16) + 16;
2593 		}
2594 		else //er?  not on red or blue, so ignore them
2595 			continue;
2596 
2597 		greycheck = greycheckdef;
2598 		supercheck = supercheckdef;
2599 
2600 		strlcpy(name, tab[i].name, 7);
2601 		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
2602 			V_DrawString(x + 20, y,
2603 			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
2604 			             | (greycheck ? V_TRANSLUCENT : 0)
2605 			             | V_ALLOWLOWERCASE, name);
2606 
2607 		if (gametyperules & GTR_TEAMFLAGS)
2608 		{
2609 			if (players[tab[i].num].gotflag & GF_REDFLAG) // Red
2610 				V_DrawSmallScaledPatch(x-28, y-4, 0, rflagico);
2611 			else if (players[tab[i].num].gotflag & GF_BLUEFLAG) // Blue
2612 				V_DrawSmallScaledPatch(x-28, y-4, 0, bflagico);
2613 		}
2614 
2615 		// Draw emeralds
2616 		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
2617 			HU_DrawEmeralds(x-12,y+2,255);
2618 		else if (!players[tab[i].num].powers[pw_super]
2619 			|| ((leveltime/7) & 1))
2620 		{
2621 			HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
2622 		}
2623 
2624 		if (supercheck)
2625 		{
2626 			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2627 			V_DrawSmallMappedPatch (x, y-4, 0, superprefix[players[tab[i].num].skin], colormap);
2628 		}
2629 		else
2630 		{
2631 			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2632 			if (greycheck)
2633 				V_DrawSmallTranslucentMappedPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin], colormap);
2634 			else
2635 				V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
2636 		}
2637 		V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
2638 		if (!splitscreen)
2639 		{
2640 			if (tab[i].num != serverplayer)
2641 				HU_drawPing(x+ 113, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
2642 			//else
2643 			//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
2644 		}
2645 	}
2646 }
2647 
2648 //
2649 // HU_DrawDualTabRankings
2650 //
HU_DrawDualTabRankings(INT32 x,INT32 y,playersort_t * tab,INT32 scorelines,INT32 whiteplayer)2651 void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer)
2652 {
2653 	INT32 i;
2654 	const UINT8 *colormap;
2655 	char name[MAXPLAYERNAME+1];
2656 	boolean greycheck, supercheck;
2657 
2658 	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two sides.
2659 	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
2660 	V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom.
2661 
2662 	for (i = 0; i < scorelines; i++)
2663 	{
2664 		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
2665 			continue; //ignore them.
2666 
2667 		greycheck = greycheckdef;
2668 		supercheck = supercheckdef;
2669 
2670 		strlcpy(name, tab[i].name, 7);
2671 		if (tab[i].num != serverplayer)
2672 			HU_drawPing(x+ 113, y, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], false, 0);
2673 		//else
2674 		//	V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER");
2675 
2676 		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
2677 			V_DrawString(x + 20, y,
2678 			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
2679 			             | (greycheck ? V_TRANSLUCENT : 0)
2680 			             | V_ALLOWLOWERCASE, name);
2681 
2682 		if (G_GametypeUsesLives() && !(G_GametypeUsesCoopLives() && (cv_cooplives.value == 0 || cv_cooplives.value == 3)) && (players[tab[i].num].lives != INFLIVES)) //show lives
2683 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE, va("%dx", players[tab[i].num].lives));
2684 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
2685 			V_DrawSmallScaledPatch(x-28, y-4, 0, tagico);
2686 
2687 		if (players[tab[i].num].exiting || (players[tab[i].num].pflags & PF_FINISHED))
2688 			V_DrawSmallScaledPatch(x - exiticon->width/2 - 1, y-3, 0, exiticon);
2689 
2690 		// Draw emeralds
2691 		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
2692 			HU_DrawEmeralds(x-12,y+2,255);
2693 		else if (!players[tab[i].num].powers[pw_super]
2694 			|| ((leveltime/7) & 1))
2695 		{
2696 			HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
2697 		}
2698 
2699 		//V_DrawSmallScaledPatch (x, y-4, 0, livesback);
2700 		if (tab[i].color == 0)
2701 		{
2702 			colormap = colormaps;
2703 			if (supercheck)
2704 				V_DrawSmallScaledPatch (x, y-4, 0, superprefix[players[tab[i].num].skin]);
2705 			else
2706 			{
2707 				if (greycheck)
2708 					V_DrawSmallTranslucentPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin]);
2709 				else
2710 					V_DrawSmallScaledPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin]);
2711 			}
2712 		}
2713 		else
2714 		{
2715 			if (supercheck)
2716 			{
2717 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2718 				V_DrawSmallMappedPatch (x, y-4, 0, superprefix[players[tab[i].num].skin], colormap);
2719 			}
2720 			else
2721 			{
2722 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2723 				if (greycheck)
2724 					V_DrawSmallTranslucentMappedPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin], colormap);
2725 				else
2726 					V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
2727 			}
2728 		}
2729 
2730 		// All data drawn with thin string for space.
2731 		if (gametyperankings[gametype] == GT_RACE)
2732 		{
2733 			if (circuitmap)
2734 			{
2735 				if (players[tab[i].num].exiting)
2736 					V_DrawRightAlignedThinString(x+146, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
2737 				else
2738 					V_DrawRightAlignedThinString(x+146, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
2739 			}
2740 			else
2741 				V_DrawRightAlignedThinString(x+146, y, (greycheck ? V_TRANSLUCENT : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
2742 		}
2743 		else
2744 			V_DrawRightAlignedThinString(x+100, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
2745 
2746 		y += 16;
2747 		if (y > 160)
2748 		{
2749 			y = 32;
2750 			x += BASEVIDWIDTH/2;
2751 		}
2752 	}
2753 }
2754 
2755 //
2756 // HU_Draw32TabRankings
2757 //
HU_Draw32TabRankings(INT32 x,INT32 y,playersort_t * tab,INT32 scorelines,INT32 whiteplayer)2758 static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer)
2759 {
2760 	INT32 i;
2761 	const UINT8 *colormap;
2762 	char name[MAXPLAYERNAME+1];
2763 	boolean greycheck, supercheck;
2764 
2765 	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two sides.
2766 	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
2767 	V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom.
2768 
2769 	for (i = 0; i < scorelines; i++)
2770 	{
2771 		if (players[tab[i].num].spectator && gametyperankings[gametype] != GT_COOP)
2772 			continue; //ignore them.
2773 
2774 		greycheck = greycheckdef;
2775 		supercheck = supercheckdef;
2776 
2777 		strlcpy(name, tab[i].name, 7);
2778 		if (!splitscreen) // don't draw it on splitscreen,
2779 		{
2780 			if (tab[i].num != serverplayer)
2781 				HU_drawPing(x+ 135, y+1, players[tab[i].num].quittime ? UINT32_MAX : playerpingtable[tab[i].num], true, 0);
2782 			//else
2783 			//	V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST");
2784 		}
2785 
2786 		if (!players[tab[i].num].quittime || (leveltime / (TICRATE/2) & 1))
2787 			V_DrawString(x + 10, y,
2788 			             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
2789 			             | (greycheck ? 0 : V_TRANSLUCENT)
2790 			             | V_ALLOWLOWERCASE, name);
2791 
2792 		if (G_GametypeUsesLives()) //show lives
2793 			V_DrawRightAlignedThinString(x-1, y, V_ALLOWLOWERCASE, va("%d", players[tab[i].num].lives));
2794 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
2795 			V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, tagico, 0);
2796 
2797 		// Draw emeralds
2798 		if (players[tab[i].num].powers[pw_invulnerability] && (players[tab[i].num].powers[pw_invulnerability] == players[tab[i].num].powers[pw_sneakers]) && ((leveltime/7) & 1))
2799 		{
2800 			HU_Draw32Emeralds(x+60, y+2, 255);
2801 			//HU_DrawEmeralds(x-12,y+2,255);
2802 		}
2803 		else if (!players[tab[i].num].powers[pw_super]
2804 			|| ((leveltime/7) & 1))
2805 		{
2806 			HU_Draw32Emeralds(x+60, y+2, tab[i].emeralds);
2807 			//HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
2808 		}
2809 
2810 		//V_DrawSmallScaledPatch (x, y-4, 0, livesback);
2811 		if (tab[i].color == 0)
2812 		{
2813 			colormap = colormaps;
2814 			if (players[tab[i].num].powers[pw_super] && !(players[tab[i].num].charflags & SF_NOSUPERSPRITES))
2815 				V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], 0);
2816 			else
2817 			{
2818 				if (greycheck)
2819 					V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], 0);
2820 				else
2821 					V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], 0);
2822 			}
2823 		}
2824 		else
2825 		{
2826 			if (supercheck)
2827 			{
2828 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2829 				V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], colormap);
2830 			}
2831 			else
2832 			{
2833 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
2834 				if (greycheck)
2835 					V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], colormap);
2836 				else
2837 					V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], colormap);
2838 			}
2839 		}
2840 
2841 		// All data drawn with thin string for space.
2842 		if (gametyperankings[gametype] == GT_RACE)
2843 		{
2844 			if (circuitmap)
2845 			{
2846 				if (players[tab[i].num].exiting)
2847 					V_DrawRightAlignedThinString(x+128, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
2848 				else
2849 					V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
2850 			}
2851 			else
2852 				V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
2853 		}
2854 		else
2855 			V_DrawRightAlignedThinString(x+128, y, (greycheck ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
2856 
2857 		y += 9;
2858 		if (i == 16)
2859 		{
2860 			y = 32;
2861 			x += BASEVIDWIDTH/2;
2862 		}
2863 	}
2864 }
2865 
2866 //
2867 // HU_DrawEmeralds
2868 //
HU_DrawEmeralds(INT32 x,INT32 y,INT32 pemeralds)2869 void HU_DrawEmeralds(INT32 x, INT32 y, INT32 pemeralds)
2870 {
2871 	//Draw the emeralds, in the CORRECT order, using tiny emerald sprites.
2872 	if (pemeralds & EMERALD1)
2873 		V_DrawSmallScaledPatch(x  , y-6, 0, emeraldpics[1][0]);
2874 
2875 	if (pemeralds & EMERALD2)
2876 		V_DrawSmallScaledPatch(x+4, y-3, 0, emeraldpics[1][1]);
2877 
2878 	if (pemeralds & EMERALD3)
2879 		V_DrawSmallScaledPatch(x+4, y+3, 0, emeraldpics[1][2]);
2880 
2881 	if (pemeralds & EMERALD4)
2882 		V_DrawSmallScaledPatch(x  , y+6, 0, emeraldpics[1][3]);
2883 
2884 	if (pemeralds & EMERALD5)
2885 		V_DrawSmallScaledPatch(x-4, y+3, 0, emeraldpics[1][4]);
2886 
2887 	if (pemeralds & EMERALD6)
2888 		V_DrawSmallScaledPatch(x-4, y-3, 0, emeraldpics[1][5]);
2889 
2890 	if (pemeralds & EMERALD7)
2891 		V_DrawSmallScaledPatch(x,   y,   0, emeraldpics[1][6]);
2892 }
2893 
2894 //
2895 // HU_DrawSpectatorTicker
2896 //
HU_DrawSpectatorTicker(void)2897 static inline void HU_DrawSpectatorTicker(void)
2898 {
2899 	int i;
2900 	int length = 0, height = 174;
2901 	int totallength = 0, templength = 0;
2902 
2903 	for (i = 0; i < MAXPLAYERS; i++)
2904 		if (playeringame[i] && players[i].spectator)
2905 			totallength += (signed)strlen(player_names[i]) * 8 + 16;
2906 
2907 	length -= (leveltime % (totallength + BASEVIDWIDTH));
2908 	length += BASEVIDWIDTH;
2909 
2910 	for (i = 0; i < MAXPLAYERS; i++)
2911 		if (playeringame[i] && players[i].spectator)
2912 		{
2913 			char *pos;
2914 			char initial[MAXPLAYERNAME+1];
2915 			char current[MAXPLAYERNAME+1];
2916 
2917 			strcpy(initial, player_names[i]);
2918 			pos = initial;
2919 
2920 			if (length >= -((signed)strlen(player_names[i]) * 8 + 16) && length <= BASEVIDWIDTH)
2921 			{
2922 				if (length < 0)
2923 				{
2924 					UINT8 eatenchars = (UINT8)(abs(length) / 8 + 1);
2925 
2926 					if (eatenchars <= strlen(initial))
2927 					{
2928 						// Eat one letter off the left side,
2929 						// then compensate the drawing position.
2930 						pos += eatenchars;
2931 						strcpy(current, pos);
2932 						templength = length % 8 + 8;
2933 					}
2934 					else
2935 					{
2936 						strcpy(current, " ");
2937 						templength = length;
2938 					}
2939 				}
2940 				else
2941 				{
2942 					strcpy(current, initial);
2943 					templength = length;
2944 				}
2945 
2946 				V_DrawString(templength, height + 8, V_TRANSLUCENT|V_ALLOWLOWERCASE, current);
2947 			}
2948 
2949 			length += (signed)strlen(player_names[i]) * 8 + 16;
2950 		}
2951 }
2952 
2953 //
2954 // HU_DrawRankings
2955 //
HU_DrawRankings(void)2956 static void HU_DrawRankings(void)
2957 {
2958 	playersort_t tab[MAXPLAYERS];
2959 	INT32 i, j, scorelines;
2960 	boolean completed[MAXPLAYERS];
2961 	UINT32 whiteplayer;
2962 
2963 	// draw the current gametype in the lower right
2964 	HU_drawGametype();
2965 
2966 	if (gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT))
2967 	{
2968 		if ((gametyperules & GTR_TIMELIMIT) && cv_timelimit.value && timelimitintics > 0)
2969 		{
2970 			V_DrawCenteredString(64, 8, 0, "TIME");
2971 			V_DrawCenteredString(64, 16, 0, va("%i:%02i", G_TicsToMinutes(stplyr->realtime, true), G_TicsToSeconds(stplyr->realtime)));
2972 		}
2973 
2974 		if ((gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0)
2975 		{
2976 			V_DrawCenteredString(256, 8, 0, "POINT LIMIT");
2977 			V_DrawCenteredString(256, 16, 0, va("%d", cv_pointlimit.value));
2978 		}
2979 	}
2980 	else if (gametyperankings[gametype] == GT_COOP)
2981 	{
2982 		INT32 totalscore = 0;
2983 		for (i = 0; i < MAXPLAYERS; i++)
2984 		{
2985 			if (playeringame[i])
2986 				totalscore += players[i].score;
2987 		}
2988 
2989 		V_DrawCenteredString(256, 8, 0, "TOTAL SCORE");
2990 		V_DrawCenteredString(256, 16, 0, va("%u", totalscore));
2991 	}
2992 	else
2993 	{
2994 		if (circuitmap)
2995 		{
2996 			V_DrawCenteredString(64, 8, 0, "NUMBER OF LAPS");
2997 			V_DrawCenteredString(64, 16, 0, va("%d", cv_numlaps.value));
2998 		}
2999 	}
3000 
3001 	// When you play, you quickly see your score because your name is displayed in white.
3002 	// When playing back a demo, you quickly see who's the view.
3003 	whiteplayer = demoplayback ? displayplayer : consoleplayer;
3004 
3005 	scorelines = 0;
3006 	memset(completed, 0, sizeof (completed));
3007 	memset(tab, 0, sizeof (playersort_t)*MAXPLAYERS);
3008 
3009 	for (i = 0; i < MAXPLAYERS; i++)
3010 	{
3011 		tab[i].num = -1;
3012 		tab[i].name = 0;
3013 
3014 		if (gametyperankings[gametype] == GT_RACE && !circuitmap)
3015 			tab[i].count = INT32_MAX;
3016 	}
3017 
3018 	for (j = 0; j < MAXPLAYERS; j++)
3019 	{
3020 		if (!playeringame[j])
3021 			continue;
3022 
3023 		if (!G_PlatformGametype() && players[j].spectator)
3024 			continue;
3025 
3026 		for (i = 0; i < MAXPLAYERS; i++)
3027 		{
3028 			if (!playeringame[i])
3029 				continue;
3030 
3031 			if (!G_PlatformGametype() && players[i].spectator)
3032 				continue;
3033 
3034 			if (gametyperankings[gametype] == GT_RACE)
3035 			{
3036 				if (circuitmap)
3037 				{
3038 					if ((unsigned)players[i].laps+1 >= tab[scorelines].count && completed[i] == false)
3039 					{
3040 						tab[scorelines].count = players[i].laps+1;
3041 						tab[scorelines].num = i;
3042 						tab[scorelines].color = players[i].skincolor;
3043 						tab[scorelines].name = player_names[i];
3044 					}
3045 				}
3046 				else
3047 				{
3048 					if (players[i].realtime <= tab[scorelines].count && completed[i] == false)
3049 					{
3050 						tab[scorelines].count = players[i].realtime;
3051 						tab[scorelines].num = i;
3052 						tab[scorelines].color = players[i].skincolor;
3053 						tab[scorelines].name = player_names[i];
3054 					}
3055 				}
3056 			}
3057 			else if (gametyperankings[gametype] == GT_COMPETITION)
3058 			{
3059 				// todo put something more fitting for the gametype here, such as current
3060 				// number of categories led
3061 				if (players[i].score >= tab[scorelines].count && completed[i] == false)
3062 				{
3063 					tab[scorelines].count = players[i].score;
3064 					tab[scorelines].num = i;
3065 					tab[scorelines].color = players[i].skincolor;
3066 					tab[scorelines].name = player_names[i];
3067 					tab[scorelines].emeralds = players[i].powers[pw_emeralds];
3068 				}
3069 			}
3070 			else
3071 			{
3072 				if (players[i].score >= tab[scorelines].count && completed[i] == false)
3073 				{
3074 					tab[scorelines].count = players[i].score;
3075 					tab[scorelines].num = i;
3076 					tab[scorelines].color = players[i].skincolor;
3077 					tab[scorelines].name = player_names[i];
3078 					tab[scorelines].emeralds = players[i].powers[pw_emeralds];
3079 				}
3080 			}
3081 		}
3082 		completed[tab[scorelines].num] = true;
3083 		scorelines++;
3084 	}
3085 
3086 	//if (scorelines > 20)
3087 	//	scorelines = 20; //dont draw past bottom of screen, show the best only
3088 	// shush, we'll do it anyway.
3089 
3090 	if (G_GametypeHasTeams())
3091 		HU_DrawTeamTabRankings(tab, whiteplayer);
3092 	else if (scorelines <= 9 && !cv_compactscoreboard.value)
3093 		HU_DrawTabRankings(40, 32, tab, scorelines, whiteplayer);
3094 	else if (scorelines <= 20 && !cv_compactscoreboard.value)
3095 		HU_DrawDualTabRankings(32, 32, tab, scorelines, whiteplayer);
3096 	else
3097 		HU_Draw32TabRankings(14, 28, tab, scorelines, whiteplayer);
3098 
3099 	// draw spectators in a ticker across the bottom
3100 	if (!splitscreen && G_GametypeHasSpectators())
3101 		HU_DrawSpectatorTicker();
3102 }
3103 
HU_DrawCoopOverlay(void)3104 static void HU_DrawCoopOverlay(void)
3105 {
3106 	if (token && LUA_HudEnabled(hud_tokens))
3107 	{
3108 		V_DrawString(168, 176, 0, va("- %d", token));
3109 		V_DrawSmallScaledPatch(148, 172, 0, tokenicon);
3110 	}
3111 
3112 	if (LUA_HudEnabled(hud_tabemblems) && (!modifiedgame || savemoddata))
3113 	{
3114 		V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(), numemblems+numextraemblems));
3115 		V_DrawScaledPatch(128, 144 - emblemicon->height/4, 0, emblemicon);
3116 	}
3117 
3118 	if (!LUA_HudEnabled(hud_coopemeralds))
3119 		return;
3120 
3121 	if (emeralds & EMERALD1)
3122 		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)-32, 0, emeraldpics[0][0]);
3123 	if (emeralds & EMERALD2)
3124 		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[0][1]);
3125 	if (emeralds & EMERALD3)
3126 		V_DrawScaledPatch((BASEVIDWIDTH/2)-8+24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[0][2]);
3127 	if (emeralds & EMERALD4)
3128 		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)+32, 0, emeraldpics[0][3]);
3129 	if (emeralds & EMERALD5)
3130 		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)+16, 0, emeraldpics[0][4]);
3131 	if (emeralds & EMERALD6)
3132 		V_DrawScaledPatch((BASEVIDWIDTH/2)-8-24, (BASEVIDHEIGHT/3)-16, 0, emeraldpics[0][5]);
3133 	if (emeralds & EMERALD7)
3134 		V_DrawScaledPatch((BASEVIDWIDTH/2)-8   , (BASEVIDHEIGHT/3)   , 0, emeraldpics[0][6]);
3135 }
3136 
HU_DrawNetplayCoopOverlay(void)3137 static void HU_DrawNetplayCoopOverlay(void)
3138 {
3139 	int i;
3140 
3141 	if (token && LUA_HudEnabled(hud_tokens))
3142 	{
3143 		V_DrawString(168, 10, 0, va("- %d", token));
3144 		V_DrawSmallScaledPatch(148, 6, 0, tokenicon);
3145 	}
3146 
3147 	if (!LUA_HudEnabled(hud_coopemeralds))
3148 		return;
3149 
3150 	for (i = 0; i < 7; ++i)
3151 	{
3152 		if (emeralds & (1 << i))
3153 			V_DrawScaledPatch(20 + (i * 10), 9, 0, emeraldpics[1][i]);
3154 	}
3155 }
3156 
3157 
3158 // Interface to CECHO settings for the outside world, avoiding the
3159 // expense (and security problems) of going via the console buffer.
HU_ClearCEcho(void)3160 void HU_ClearCEcho(void)
3161 {
3162 	cechotimer = 0;
3163 }
3164 
HU_SetCEchoDuration(INT32 seconds)3165 void HU_SetCEchoDuration(INT32 seconds)
3166 {
3167 	cechoduration = seconds * TICRATE;
3168 }
3169 
HU_SetCEchoFlags(INT32 flags)3170 void HU_SetCEchoFlags(INT32 flags)
3171 {
3172 	// Don't allow cechoflags to contain any bits in V_PARAMMASK
3173 	cechoflags = (flags & ~V_PARAMMASK);
3174 }
3175 
HU_DoCEcho(const char * msg)3176 void HU_DoCEcho(const char *msg)
3177 {
3178 	I_OutputMsg("%s\n", msg); // print to log
3179 
3180 	strncpy(cechotext, msg, sizeof(cechotext));
3181 	strncat(cechotext, "\\", sizeof(cechotext) - strlen(cechotext) - 1);
3182 	cechotext[sizeof(cechotext) - 1] = '\0';
3183 	cechotimer = cechoduration;
3184 }
3185