1 //
2 // Copyright(C) 1993-1996 Id Software, Inc.
3 // Copyright(C) 2005-2014 Simon Howard
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // DESCRIPTION: Heads-up displays
16 //
17
18
19 #include <ctype.h>
20
21 #include "doomdef.h"
22 #include "doomkeys.h"
23
24 #include "z_zone.h"
25
26 #include "deh_main.h"
27 #include "i_input.h"
28 #include "i_swap.h"
29 #include "i_video.h"
30
31 #include "hu_stuff.h"
32 #include "hu_lib.h"
33 #include "m_controls.h"
34 #include "m_misc.h"
35 #include "w_wad.h"
36
37 #include "s_sound.h"
38
39 #include "doomstat.h"
40
41 // Data.
42 #include "dstrings.h"
43 #include "sounds.h"
44
45 //
46 // Locally used constants, shortcuts.
47 //
48 #define HU_TITLE (mapnames[gamemap-1])
49 #define HU_TITLEHEIGHT 1
50 #define HU_TITLEX 0
51
52 // haleyjd 09/01/10: [STRIFE] 167 -> 160 to move up level name
53 #define HU_TITLEY (160 - SHORT(hu_font[0]->height))
54
55 #define HU_INPUTTOGGLE 't'
56 #define HU_INPUTX HU_MSGX
57 #define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT*(SHORT(hu_font[0]->height) +1))
58 #define HU_INPUTWIDTH 64
59 #define HU_INPUTHEIGHT 1
60
61 char *chat_macros[10] =
62 {
63 HUSTR_CHATMACRO0,
64 HUSTR_CHATMACRO1,
65 HUSTR_CHATMACRO2,
66 HUSTR_CHATMACRO3,
67 HUSTR_CHATMACRO4,
68 HUSTR_CHATMACRO5,
69 HUSTR_CHATMACRO6,
70 HUSTR_CHATMACRO7,
71 HUSTR_CHATMACRO8,
72 HUSTR_CHATMACRO9
73 };
74
75 // villsa [STRIFE]
76 char player_names[8][16] =
77 {
78 "1: ",
79 "2: ",
80 "3: ",
81 "4: ",
82 "5: ",
83 "6: ",
84 "7: ",
85 "8: "
86 };
87
88 char chat_char; // remove later.
89 static player_t* plr;
90 patch_t* hu_font[HU_FONTSIZE];
91 patch_t* yfont[HU_FONTSIZE]; // haleyjd 09/18/10: [STRIFE]
92 static hu_textline_t w_title;
93 boolean chat_on;
94 static hu_itext_t w_chat;
95 static boolean always_off = false;
96 static char chat_dest[MAXPLAYERS];
97 static hu_itext_t w_inputbuffer[MAXPLAYERS];
98
99 static boolean message_on;
100 boolean message_dontfuckwithme;
101 static boolean message_nottobefuckedwith;
102
103 static hu_stext_t w_message;
104 static int message_counter;
105
106 //extern int showMessages; [STRIFE] no such variable
107
108 static boolean headsupactive = false;
109
110 // haleyjd 20130915 [STRIFE]: need nickname
111 extern char *nickname;
112
113 // haleyjd 20130915 [STRIFE]: true if setting nickname
114 static boolean hu_setting_name = false;
115
116 //
117 // Builtin map names.
118 // The actual names can be found in DStrings.h.
119 //
120
121 // haleyjd 08/31/10: [STRIFE] Changed for Strife level names.
122 // List of names for levels.
123
124 char *mapnames[] =
125 {
126 // Strife map names
127
128 // First "episode" - Quest to destroy the Order's Castle
129 HUSTR_1,
130 HUSTR_2,
131 HUSTR_3,
132 HUSTR_4,
133 HUSTR_5,
134 HUSTR_6,
135 HUSTR_7,
136 HUSTR_8,
137 HUSTR_9,
138
139 // Second "episode" - Kill the Bishop and Make a Choice
140 HUSTR_10,
141 HUSTR_11,
142 HUSTR_12,
143 HUSTR_13,
144 HUSTR_14,
145 HUSTR_15,
146 HUSTR_16,
147 HUSTR_17,
148 HUSTR_18,
149 HUSTR_19,
150
151 // Third "episode" - Shut down Factory, kill Loremaster and Entity
152 HUSTR_20,
153 HUSTR_21,
154 HUSTR_22,
155 HUSTR_23,
156 HUSTR_24,
157 HUSTR_25,
158 HUSTR_26,
159 HUSTR_27,
160 HUSTR_28,
161 HUSTR_29,
162
163 // "Secret" levels - Abandoned Base and Training Facility
164 HUSTR_30,
165 HUSTR_31,
166
167 // Demo version maps
168 HUSTR_32,
169 HUSTR_33,
170 HUSTR_34
171 };
172
173 //
174 // HU_Init
175 //
176 // haleyjd 09/18/10: [STRIFE]
177 // * Modified to load yfont along with hu_font.
178 //
HU_Init(void)179 void HU_Init(void)
180 {
181 int i;
182 int j;
183 char buffer[9];
184
185 // load the heads-up font
186 j = HU_FONTSTART;
187 for (i=0;i<HU_FONTSIZE;i++)
188 {
189 DEH_snprintf(buffer, 9, "STCFN%.3d", j++);
190 hu_font[i] = (patch_t *) W_CacheLumpName(buffer, PU_STATIC);
191
192 // haleyjd 09/18/10: load yfont as well; and yes, this is exactly
193 // how Rogue did it :P
194 buffer[2] = 'B';
195 yfont[i] = (patch_t *) W_CacheLumpName(buffer, PU_STATIC);
196 }
197 }
198
199 //
200 // HU_Stop
201 //
202 // [STRIFE] Verified unmodified.
203 //
HU_Stop(void)204 void HU_Stop(void)
205 {
206 headsupactive = false;
207 }
208
209 //
210 // HU_Start
211 //
212 // haleyjd 09/18/10: [STRIFE] Added a hack for nickname at the end.
213 //
HU_Start(void)214 void HU_Start(void)
215 {
216 int i;
217 char* s;
218
219 // haleyjd 20120211: [STRIFE] not called here.
220 //if (headsupactive)
221 // HU_Stop();
222
223 // haleyjd 20120211: [STRIFE] moved up
224 // create the map title widget
225 HUlib_initTextLine(&w_title,
226 HU_TITLEX, HU_TITLEY,
227 hu_font,
228 HU_FONTSTART);
229
230 // haleyjd 08/31/10: [STRIFE] Get proper map name.
231 s = HU_TITLE;
232
233 // [STRIFE] Removed Chex Quest stuff.
234
235 // dehacked substitution to get modified level name
236 s = DEH_String(s);
237
238 while (*s)
239 HUlib_addCharToTextLine(&w_title, *(s++));
240
241 // haleyjd 20120211: [STRIFE] check for headsupactive
242 if(!headsupactive)
243 {
244 plr = &players[consoleplayer];
245 message_on = false;
246 message_dontfuckwithme = false;
247 message_nottobefuckedwith = false;
248 chat_on = false;
249
250 // create the message widget
251 HUlib_initSText(&w_message,
252 HU_MSGX, HU_MSGY, HU_MSGHEIGHT,
253 hu_font,
254 HU_FONTSTART, &message_on);
255
256 // create the chat widget
257 HUlib_initIText(&w_chat,
258 HU_INPUTX, HU_INPUTY,
259 hu_font,
260 HU_FONTSTART, &chat_on);
261
262 // create the inputbuffer widgets
263 for (i=0 ; i<MAXPLAYERS ; i++)
264 HUlib_initIText(&w_inputbuffer[i], 0, 0, 0, 0, &always_off);
265
266 headsupactive = true;
267
268 // haleyjd 09/18/10: [STRIFE] nickname weirdness.
269 if(nickname != player_names[consoleplayer])
270 {
271 if(nickname != NULL && *nickname)
272 {
273 DEH_printf("have one\n");
274 nickname = player_names[consoleplayer];
275 }
276 }
277 }
278 }
279
280 //
281 // HU_Drawer
282 //
283 // [STRIFE] Verified unmodified.
284 //
HU_Drawer(void)285 void HU_Drawer(void)
286 {
287 HUlib_drawSText(&w_message);
288 HUlib_drawIText(&w_chat);
289 if (automapactive)
290 HUlib_drawTextLine(&w_title, false);
291 }
292
293 //
294 // HU_Erase
295 //
296 // [STRIFE] Verified unmodified.
297 //
HU_Erase(void)298 void HU_Erase(void)
299 {
300 HUlib_eraseSText(&w_message);
301 HUlib_eraseIText(&w_chat);
302 HUlib_eraseTextLine(&w_title);
303 }
304
305 //
306 // HU_addMessage
307 //
308 // haleyjd 09/18/10: [STRIFE] New function
309 // See if you can tell whether or not I had trouble with this :P
310 // Looks to be extremely buggy, hackish, and error-prone.
311 //
312 // <Markov> This is definitely not the best that Rogue had to offer. Markov.
313 //
314 // Fastcall Registers: edx ebx
315 // Temp Registers: esi edi
HU_addMessage(char * prefix,char * message)316 void HU_addMessage(char *prefix, char *message)
317 {
318 char c; // eax
319 int width = 0; // edx
320 char *rover1; // ebx (in first loop)
321 char *rover2; // ecx (in second loop)
322 char *bufptr; // ebx (in second loop)
323 char buffer[HU_MAXLINELENGTH+2]; // esp+52h
324
325 // Loop 1: Total up width of prefix.
326 rover1 = prefix;
327 if(rover1)
328 {
329 while((c = *rover1))
330 {
331 c = toupper(c) - HU_FONTSTART;
332 ++rover1;
333
334 if(c < 0 || c >= HU_FONTSIZE)
335 width += 4;
336 else
337 width += SHORT(hu_font[(int) c]->width);
338 }
339 }
340
341 // Loop 2: Copy as much of message into buffer as will fit on screen
342 bufptr = buffer;
343 rover2 = message;
344 while((c = *rover2))
345 {
346 if((c == ' ' || c == '-') && width > 285)
347 break;
348
349 *bufptr = c;
350 ++bufptr; // BUG: No check for overflow.
351 ++rover2;
352 c = toupper(c);
353
354 if(c == ' ' || c < '!' || c >= '_')
355 width += 4;
356 else
357 {
358 c -= HU_FONTSTART;
359 width += SHORT(hu_font[(int) c]->width);
360 }
361 }
362
363 // Too big to fit?
364 // BUG: doesn't consider by how much it's over.
365 if(width > 320)
366 {
367 // backup a char... hell if I know why.
368 --bufptr;
369 --rover2;
370 }
371
372 // rover2 is not at the end?
373 if((c = *rover2))
374 {
375 // if not ON a space...
376 if(c != ' ')
377 {
378 // back up both pointers til one is found.
379 // BUG: no check against LHS of buffer. Hurr!
380 while(*bufptr != ' ')
381 {
382 --bufptr;
383 --rover2;
384 }
385 }
386 }
387
388 *bufptr = '\0';
389
390 // Add two message lines.
391 HUlib_addMessageToSText(&w_message, prefix, buffer);
392 HUlib_addMessageToSText(&w_message, NULL, rover2);
393 }
394
395 //
396 // HU_Ticker
397 //
398 // haleyjd 09/18/10: [STRIFE] Changes to split up message into two lines,
399 // and support for player names (STRIFE-TODO: unfinished!)
400 //
HU_Ticker(void)401 void HU_Ticker(void)
402 {
403 int i, rc;
404 char c;
405 //char *prefix; STRIFE-TODO
406
407 // tick down message counter if message is up
408 if (message_counter && !--message_counter)
409 {
410 message_on = false;
411 message_nottobefuckedwith = false;
412 }
413
414 // haleyjd 20110219: [STRIFE] this condition was removed
415 //if (showMessages || message_dontfuckwithme)
416 //{
417 // display message if necessary
418 if ((plr->message && !message_nottobefuckedwith)
419 || (plr->message && message_dontfuckwithme))
420 {
421 //HUlib_addMessageToSText(&w_message, 0, plr->message);
422 HU_addMessage(NULL, plr->message); // haleyjd [STRIFE]
423 plr->message = 0;
424 message_on = true;
425 message_counter = HU_MSGTIMEOUT;
426 message_nottobefuckedwith = message_dontfuckwithme;
427 message_dontfuckwithme = 0;
428 }
429 //} // else message_on = false;
430
431 // check for incoming chat characters
432 if (netgame)
433 {
434 for (i=0 ; i<MAXPLAYERS; i++)
435 {
436 if (!playeringame[i])
437 continue;
438 if (i != consoleplayer
439 && (c = players[i].cmd.chatchar))
440 {
441 if (c <= HU_CHANGENAME) // [STRIFE]: allow HU_CHANGENAME here
442 chat_dest[i] = c;
443 else
444 {
445 rc = HUlib_keyInIText(&w_inputbuffer[i], c);
446 if (rc && c == KEY_ENTER)
447 {
448 if (w_inputbuffer[i].l.len
449 && (chat_dest[i] == consoleplayer+1
450 || chat_dest[i] == HU_BROADCAST))
451 {
452 HU_addMessage(player_names[i],
453 w_inputbuffer[i].l.l);
454
455 message_nottobefuckedwith = true;
456 message_on = true;
457 message_counter = HU_MSGTIMEOUT;
458 S_StartSound(0, sfx_radio);
459 }
460 else if(chat_dest[i] == HU_CHANGENAME)
461 {
462 // haleyjd 20130915 [STRIFE]: set player name
463 DEH_snprintf(player_names[i], sizeof(player_names[i]),
464 "%.13s: ", w_inputbuffer[i].l.l);
465 }
466 HUlib_resetIText(&w_inputbuffer[i]);
467 }
468 }
469 players[i].cmd.chatchar = 0;
470 }
471 }
472 }
473 }
474
475 #define QUEUESIZE 128
476
477 static char chatchars[QUEUESIZE];
478 static int head = 0;
479 static int tail = 0;
480
481 //
482 // HU_queueChatChar
483 //
484 // haleyjd 09/18/10: [STRIFE]
485 // * No message is given if a chat queue overflow occurs.
486 //
HU_queueChatChar(char c)487 void HU_queueChatChar(char c)
488 {
489 chatchars[head] = c;
490 if (((head + 1) & (QUEUESIZE-1)) != tail)
491 {
492 head = (head + 1) & (QUEUESIZE-1);
493 }
494 }
495
496 //
497 // HU_dequeueChatChar
498 //
499 // [STRIFE] Verified unmodified.
500 //
HU_dequeueChatChar(void)501 char HU_dequeueChatChar(void)
502 {
503 char c;
504
505 if (head != tail)
506 {
507 c = chatchars[tail];
508 tail = (tail + 1) & (QUEUESIZE-1);
509 }
510 else
511 {
512 c = 0;
513 }
514
515 return c;
516 }
517
518 // fraggle 01/05/15: New functions to support the Chocolate input interface.
StartChatInput(void)519 static void StartChatInput(void)
520 {
521 chat_on = true;
522 I_StartTextInput(HU_INPUTX, HU_INPUTY, SCREENWIDTH, HU_INPUTY + 8);
523 }
524
StopChatInput(void)525 static void StopChatInput(void)
526 {
527 chat_on = false;
528 I_StopTextInput();
529 }
530
531 //
532 // HU_Responder
533 //
534 // haleyjd 09/18/10: [STRIFE]
535 // * Mostly unmodified, except:
536 // - The default value of key_message_refresh is changed. That is handled
537 // elsewhere in Choco, however.
538 // - There is support for setting the player name through the chat
539 // mechanism.
540 //
HU_Responder(event_t * ev)541 boolean HU_Responder(event_t *ev)
542 {
543 static char lastmessage[HU_MAXLINELENGTH+1];
544 char* macromessage;
545 boolean eatkey = false;
546 static boolean altdown = false;
547 unsigned char c;
548 int i;
549 int numplayers;
550
551 static int num_nobrainers = 0;
552
553 numplayers = 0;
554 for (i=0 ; i<MAXPLAYERS ; i++)
555 numplayers += playeringame[i];
556
557 if (ev->data1 == KEY_RSHIFT)
558 {
559 return false;
560 }
561 else if (ev->data1 == KEY_RALT || ev->data1 == KEY_LALT)
562 {
563 altdown = ev->type == ev_keydown;
564 return false;
565 }
566
567 if (ev->type != ev_keydown)
568 return false;
569
570 if (!chat_on)
571 {
572 if (ev->data1 == key_message_refresh)
573 {
574 message_on = true;
575 message_counter = HU_MSGTIMEOUT;
576 eatkey = true;
577 }
578 else if (netgame && ev->data2 == key_multi_msg)
579 {
580 StartChatInput();
581 eatkey = true;
582 HUlib_resetIText(&w_chat);
583 HU_queueChatChar(HU_BROADCAST);
584 }
585 // [STRIFE]: You cannot go straight to chatting with a particular
586 // player from here... you must press 't' first. See below.
587 }
588 else
589 {
590 c = ev->data3;
591 // send a macro
592 if (altdown)
593 {
594 c = c - '0';
595 if (c > 9)
596 return false;
597 // fprintf(stderr, "got here\n");
598 macromessage = chat_macros[c];
599
600 // kill last message with a '\n'
601 HU_queueChatChar(KEY_ENTER); // DEBUG!!!
602
603 // send the macro message
604 while (*macromessage)
605 HU_queueChatChar(*macromessage++);
606 HU_queueChatChar(KEY_ENTER);
607
608 // leave chat mode and notify that it was sent
609 StopChatInput();
610 M_StringCopy(lastmessage, chat_macros[c], sizeof(lastmessage));
611 plr->message = lastmessage;
612 eatkey = true;
613 }
614 else
615 {
616 if(w_chat.l.len) // [STRIFE]: past first char of chat?
617 {
618 eatkey = HUlib_keyInIText(&w_chat, c);
619 if (eatkey)
620 HU_queueChatChar(c);
621 }
622 else
623 {
624 // [STRIFE]: check for player-specific message;
625 // slightly different than vanilla, to allow keys to be customized
626 for(i = 0; i < MAXPLAYERS; i++)
627 {
628 if (ev->data1 == key_multi_msgplayer[i])
629 break;
630 }
631 if(i < MAXPLAYERS)
632 {
633 // talking to self?
634 if(i == consoleplayer)
635 {
636 num_nobrainers++;
637 if (num_nobrainers < 3)
638 plr->message = DEH_String(HUSTR_TALKTOSELF1);
639 else if (num_nobrainers < 6)
640 plr->message = DEH_String(HUSTR_TALKTOSELF2);
641 else if (num_nobrainers < 9)
642 plr->message = DEH_String(HUSTR_TALKTOSELF3);
643 else if (num_nobrainers < 32)
644 plr->message = DEH_String(HUSTR_TALKTOSELF4);
645 else
646 plr->message = DEH_String(HUSTR_TALKTOSELF5);
647 }
648 else
649 {
650 eatkey = true;
651 HU_queueChatChar(i+1);
652 DEH_snprintf(lastmessage, sizeof(lastmessage),
653 "Talking to: %c", '1' + i);
654 plr->message = lastmessage;
655 }
656 }
657 else if(c == '$') // [STRIFE]: name changing
658 {
659 eatkey = true;
660 HU_queueChatChar(HU_CHANGENAME);
661 M_StringCopy(lastmessage, DEH_String("Changing Name:"),
662 sizeof(lastmessage));
663 plr->message = lastmessage;
664 hu_setting_name = true;
665 }
666 else
667 {
668 eatkey = HUlib_keyInIText(&w_chat, c);
669 if (eatkey)
670 HU_queueChatChar(c);
671 }
672 }
673
674 if (c == KEY_ENTER)
675 {
676 StopChatInput();
677 if (w_chat.l.len)
678 {
679 // [STRIFE]: name setting
680 if(hu_setting_name)
681 {
682 DEH_snprintf(lastmessage, sizeof(lastmessage),
683 "%s now %.13s", player_names[consoleplayer],
684 w_chat.l.l);
685 // haleyjd 20141024: missing name set for local client
686 DEH_snprintf(player_names[consoleplayer],
687 sizeof(player_names[consoleplayer]),
688 "%.13s: ", w_chat.l.l);
689 hu_setting_name = false;
690 }
691 else
692 {
693 M_StringCopy(lastmessage, w_chat.l.l,
694 sizeof(lastmessage));
695 }
696 plr->message = lastmessage;
697 }
698 }
699 else if (c == KEY_ESCAPE)
700 {
701 StopChatInput();
702 }
703 }
704 }
705
706 return eatkey;
707 }
708