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