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:
16 //	DOOM selection menu, options, episode etc.
17 //	Sliders and icons. Kinda widget stuff.
18 //
19 
20 
21 #include <stdlib.h>
22 #include <ctype.h>
23 
24 
25 #include "doomdef.h"
26 #include "doomkeys.h"
27 #include "dstrings.h"
28 
29 #include "d_main.h"
30 #include "deh_main.h"
31 
32 #include "i_input.h"
33 #include "i_swap.h"
34 #include "i_system.h"
35 #include "i_timer.h"
36 #include "i_video.h"
37 #include "z_zone.h"
38 #include "v_video.h"
39 #include "w_wad.h"
40 
41 #include "r_local.h"
42 
43 
44 #include "hu_stuff.h"
45 
46 #include "g_game.h"
47 
48 #include "m_argv.h"
49 #include "m_controls.h"
50 #include "m_misc.h"
51 #include "m_saves.h"    // [STRIFE]
52 #include "p_saveg.h"
53 
54 #include "s_sound.h"
55 
56 #include "doomstat.h"
57 
58 // Data.
59 #include "sounds.h"
60 
61 #include "m_menu.h"
62 #include "p_dialog.h"
63 
64 
65 extern void M_QuitStrife(int);
66 
67 extern patch_t*         hu_font[HU_FONTSIZE];
68 extern boolean          message_dontfuckwithme;
69 
70 extern boolean          chat_on;        // in heads-up code
71 extern boolean          sendsave;       // [STRIFE]
72 
73 //
74 // defaulted values
75 //
76 int			mouseSensitivity = 5;
77 
78 // [STRIFE]: removed this entirely
79 // Show messages has default, 0 = off, 1 = on
80 //int			showMessages = 1;
81 
82 
83 // Blocky mode, has default, 0 = high, 1 = normal
84 int			detailLevel = 0;
85 int			screenblocks = 10; // [STRIFE] default 10, not 9
86 
87 // temp for screenblocks (0-9)
88 int			screenSize;
89 
90 // -1 = no quicksave slot picked!
91 int			quickSaveSlot;
92 
93  // 1 = message to be printed
94 int			messageToPrint;
95 // ...and here is the message string!
96 char*			messageString;
97 
98 // message x & y
99 int			messx;
100 int			messy;
101 int			messageLastMenuActive;
102 
103 // timed message = no input from user
104 boolean			messageNeedsInput;
105 
106 void    (*messageRoutine)(int response);
107 
108 char gammamsg[5][26] =
109 {
110     GAMMALVL0,
111     GAMMALVL1,
112     GAMMALVL2,
113     GAMMALVL3,
114     GAMMALVL4
115 };
116 
117 // we are going to be entering a savegame string
118 int			saveStringEnter;
119 int             	saveSlot;	// which slot to save in
120 int			saveCharIndex;	// which char we're editing
121 // old save description before edit
122 char			saveOldString[SAVESTRINGSIZE];
123 
124 boolean                 inhelpscreens;
125 boolean                 menuactive;
126 boolean                 menupause;      // haleyjd 08/29/10: [STRIFE] New global
127 int                     menupausetime;  // haleyjd 09/04/10: [STRIFE] New global
128 boolean                 menuindialog;   // haleyjd 09/04/10: ditto
129 
130 // haleyjd 08/27/10: [STRIFE] SKULLXOFF == -28, LINEHEIGHT == 19
131 #define CURSORXOFF		-28
132 #define LINEHEIGHT		19
133 
134 extern boolean		sendpause;
135 char			savegamestrings[10][SAVESTRINGSIZE];
136 
137 char	endstring[160];
138 
139 // haleyjd 09/04/10: [STRIFE] Moved menuitem / menu structures into header
140 // because they are needed externally by the dialog engine.
141 
142 // haleyjd 08/27/10: [STRIFE] skull* stuff changed to cursor* stuff
143 short		itemOn;			// menu item skull is on
144 short		cursorAnimCounter;	// skull animation counter
145 short		whichCursor;		// which skull to draw
146 
147 // graphic name of cursors
148 // haleyjd 08/27/10: [STRIFE] M_SKULL* -> M_CURS*
149 char    *cursorName[8] = {"M_CURS1", "M_CURS2", "M_CURS3", "M_CURS4",
150                           "M_CURS5", "M_CURS6", "M_CURS7", "M_CURS8" };
151 
152 // haleyjd 20110210 [STRIFE]: skill level for menus
153 int menuskill;
154 
155 // current menudef
156 menu_t*	currentMenu;
157 
158 // haleyjd 03/01/13: [STRIFE] v1.31-only:
159 // Keeps track of whether the save game menu is being used to name a new
160 // character slot, or to just save the current game. In the v1.31 disassembly
161 // this was the new dword_8632C variable.
162 boolean namingCharacter;
163 
164 //
165 // PROTOTYPES
166 //
167 void M_NewGame(int choice);
168 void M_Episode(int choice);
169 void M_ChooseSkill(int choice);
170 void M_LoadGame(int choice);
171 void M_SaveGame(int choice);
172 void M_Options(int choice);
173 void M_EndGame(int choice);
174 void M_ReadThis(int choice);
175 void M_ReadThis2(int choice);
176 void M_ReadThis3(int choice); // [STRIFE]
177 
178 //void M_ChangeMessages(int choice); [STRIFE]
179 void M_ChangeSensitivity(int choice);
180 void M_SfxVol(int choice);
181 void M_VoiceVol(int choice); // [STRIFE]
182 void M_MusicVol(int choice);
183 void M_SizeDisplay(int choice);
184 void M_StartGame(int choice);
185 void M_Sound(int choice);
186 
187 //void M_FinishReadThis(int choice); - [STRIFE] unused
188 void M_SaveSelect(int choice);
189 void M_ReadSaveStrings(void);
190 void M_QuickSave(void);
191 void M_QuickLoad(void);
192 
193 void M_DrawMainMenu(void);
194 void M_DrawReadThis1(void);
195 void M_DrawReadThis2(void);
196 void M_DrawReadThis3(void); // [STRIFE]
197 void M_DrawNewGame(void);
198 void M_DrawEpisode(void);
199 void M_DrawOptions(void);
200 void M_DrawSound(void);
201 void M_DrawLoad(void);
202 void M_DrawSave(void);
203 
204 void M_DrawSaveLoadBorder(int x,int y);
205 void M_SetupNextMenu(menu_t *menudef);
206 void M_DrawThermo(int x,int y,int thermWidth,int thermDot);
207 void M_DrawEmptyCell(menu_t *menu,int item);
208 void M_DrawSelCell(menu_t *menu,int item);
209 int  M_StringWidth(char *string);
210 int  M_StringHeight(char *string);
211 void M_StartMessage(char *string,void *routine,boolean input);
212 void M_StopMessage(void);
213 
214 
215 
216 
217 //
218 // DOOM MENU
219 //
220 enum
221 {
222     newgame = 0,
223     options,
224     loadgame,
225     savegame,
226     readthis,
227     quitdoom,
228     main_end
229 } main_e;
230 
231 menuitem_t MainMenu[]=
232 {
233     {1,"M_NGAME",M_NewGame,'n'},
234     {1,"M_OPTION",M_Options,'o'},
235     {1,"M_LOADG",M_LoadGame,'l'},
236     {1,"M_SAVEG",M_SaveGame,'s'},
237     // Another hickup with Special edition.
238     {1,"M_RDTHIS",M_ReadThis,'h'}, // haleyjd 08/28/10: 'r' -> 'h'
239     {1,"M_QUITG",M_QuitStrife,'q'}
240 };
241 
242 menu_t  MainDef =
243 {
244     main_end,
245     NULL,
246     MainMenu,
247     M_DrawMainMenu,
248     97,45, // haleyjd 08/28/10: [STRIFE] changed y coord
249     0
250 };
251 
252 
253 //
254 // EPISODE SELECT
255 //
256 /*
257 enum
258 {
259     ep1,
260     ep2,
261     ep3,
262     ep4,
263     ep_end
264 } episodes_e;
265 
266 menuitem_t EpisodeMenu[]=
267 {
268     {1,"M_EPI1", M_Episode,'k'},
269     {1,"M_EPI2", M_Episode,'t'},
270     {1,"M_EPI3", M_Episode,'i'},
271     {1,"M_EPI4", M_Episode,'t'}
272 };
273 
274 menu_t  EpiDef =
275 {
276     ep_end,		// # of menu items
277     &MainDef,		// previous menu
278     EpisodeMenu,	// menuitem_t ->
279     M_DrawEpisode,	// drawing routine ->
280     48,63,              // x,y
281     ep1			// lastOn
282 };
283 */
284 
285 //
286 // NEW GAME
287 //
288 enum
289 {
290     killthings,
291     toorough,
292     hurtme,
293     violence,
294     nightmare,
295     newg_end
296 } newgame_e;
297 
298 menuitem_t NewGameMenu[]=
299 {
300     // haleyjd 08/28/10: [STRIFE] changed all shortcut letters
301     {1,"M_JKILL",   M_ChooseSkill, 't'},
302     {1,"M_ROUGH",   M_ChooseSkill, 'r'},
303     {1,"M_HURT",    M_ChooseSkill, 'v'},
304     {1,"M_ULTRA",   M_ChooseSkill, 'e'},
305     {1,"M_NMARE",   M_ChooseSkill, 'b'}
306 };
307 
308 menu_t  NewDef =
309 {
310     newg_end,           // # of menu items
311     &MainDef,           // previous menu - haleyjd [STRIFE] changed to MainDef
312     NewGameMenu,        // menuitem_t ->
313     M_DrawNewGame,      // drawing routine ->
314     48,63,              // x,y
315     toorough            // lastOn - haleyjd [STRIFE]: default to skill 1
316 };
317 
318 //
319 // OPTIONS MENU
320 //
321 enum
322 {
323     // haleyjd 08/28/10: [STRIFE] Removed messages, mouse sens., detail.
324     endgame,
325     scrnsize,
326     option_empty1,
327     soundvol,
328     opt_end
329 } options_e;
330 
331 menuitem_t OptionsMenu[]=
332 {
333     // haleyjd 08/28/10: [STRIFE] Removed messages, mouse sens., detail.
334     {1,"M_ENDGAM",	M_EndGame,'e'},
335     {2,"M_SCRNSZ",	M_SizeDisplay,'s'},
336     {-1,"",0,'\0'},
337     {1,"M_SVOL",	M_Sound,'s'}
338 };
339 
340 menu_t  OptionsDef =
341 {
342     opt_end,
343     &MainDef,
344     OptionsMenu,
345     M_DrawOptions,
346     60,37,
347     0
348 };
349 
350 //
351 // Read This! MENU 1 & 2 & [STRIFE] 3
352 //
353 enum
354 {
355     rdthsempty1,
356     read1_end
357 } read_e;
358 
359 menuitem_t ReadMenu1[] =
360 {
361     {1,"",M_ReadThis2,0}
362 };
363 
364 menu_t  ReadDef1 =
365 {
366     read1_end,
367     &MainDef,
368     ReadMenu1,
369     M_DrawReadThis1,
370     280,185,
371     0
372 };
373 
374 enum
375 {
376     rdthsempty2,
377     read2_end
378 } read_e2;
379 
380 menuitem_t ReadMenu2[]=
381 {
382     {1,"",M_ReadThis3,0} // haleyjd 08/28/10: [STRIFE] Go to ReadThis3
383 };
384 
385 menu_t  ReadDef2 =
386 {
387     read2_end,
388     &ReadDef1,
389     ReadMenu2,
390     M_DrawReadThis2,
391     250,185, // haleyjd 08/28/10: [STRIFE] changed coords
392     0
393 };
394 
395 // haleyjd 08/28/10: Added Read This! menu 3
396 enum
397 {
398     rdthsempty3,
399     read3_end
400 } read_e3;
401 
402 menuitem_t ReadMenu3[]=
403 {
404     {1,"",M_ClearMenus,0}
405 };
406 
407 menu_t  ReadDef3 =
408 {
409     read3_end,
410     &ReadDef2,
411     ReadMenu3,
412     M_DrawReadThis3,
413     250, 185,
414     0
415 };
416 
417 //
418 // SOUND VOLUME MENU
419 //
420 enum
421 {
422     sfx_vol,
423     sfx_empty1,
424     music_vol,
425     sfx_empty2,
426     voice_vol,
427     sfx_empty3,
428     sfx_mouse,
429     sfx_empty4,
430     sound_end
431 } sound_e;
432 
433 // haleyjd 08/29/10:
434 // [STRIFE]
435 // * Added voice volume
436 // * Moved mouse sensitivity here (who knows why...)
437 menuitem_t SoundMenu[]=
438 {
439     {2,"M_SFXVOL",M_SfxVol,'s'},
440     {-1,"",0,'\0'},
441     {2,"M_MUSVOL",M_MusicVol,'m'},
442     {-1,"",0,'\0'},
443     {2,"M_VOIVOL",M_VoiceVol,'v'},
444     {-1,"",0,'\0'},
445     {2,"M_MSENS",M_ChangeSensitivity,'m'},
446     {-1,"",0,'\0'}
447 };
448 
449 menu_t  SoundDef =
450 {
451     sound_end,
452     &OptionsDef,
453     SoundMenu,
454     M_DrawSound,
455     80,35,       // [STRIFE] changed y coord 64 -> 35
456     0
457 };
458 
459 //
460 // LOAD GAME MENU
461 //
462 enum
463 {
464     load1,
465     load2,
466     load3,
467     load4,
468     load5,
469     load6,
470     load_end
471 } load_e;
472 
473 menuitem_t LoadMenu[]=
474 {
475     {1,"", M_LoadSelect,'1'},
476     {1,"", M_LoadSelect,'2'},
477     {1,"", M_LoadSelect,'3'},
478     {1,"", M_LoadSelect,'4'},
479     {1,"", M_LoadSelect,'5'},
480     {1,"", M_LoadSelect,'6'}
481 };
482 
483 menu_t  LoadDef =
484 {
485     load_end,
486     &MainDef,
487     LoadMenu,
488     M_DrawLoad,
489     80,54,
490     0
491 };
492 
493 //
494 // SAVE GAME MENU
495 //
496 menuitem_t SaveMenu[]=
497 {
498     {1,"", M_SaveSelect,'1'},
499     {1,"", M_SaveSelect,'2'},
500     {1,"", M_SaveSelect,'3'},
501     {1,"", M_SaveSelect,'4'},
502     {1,"", M_SaveSelect,'5'},
503     {1,"", M_SaveSelect,'6'}
504 };
505 
506 menu_t  SaveDef =
507 {
508     load_end,
509     &MainDef,
510     SaveMenu,
511     M_DrawSave,
512     80,54,
513     0
514 };
515 
516 void M_DrawNameChar(void);
517 
518 //
519 // NAME CHARACTER MENU
520 //
521 // [STRIFE]
522 // haleyjd 20110210: New "Name Your Character" Menu
523 //
524 menu_t NameCharDef =
525 {
526     load_end,
527     &NewDef,
528     SaveMenu,
529     M_DrawNameChar,
530     80,54,
531     0
532 };
533 
534 
535 //
536 // M_ReadSaveStrings
537 //  read the strings from the savegame files
538 //
539 // [STRIFE]
540 // haleyjd 20110210: Rewritten to read "name" file in each slot directory
541 //
M_ReadSaveStrings(void)542 void M_ReadSaveStrings(void)
543 {
544     FILE *handle;
545     int   i;
546     char *fname = NULL;
547 
548     for(i = 0; i < load_end; i++)
549     {
550         if(fname)
551             Z_Free(fname);
552         fname = M_SafeFilePath(savegamedir, M_MakeStrifeSaveDir(i, "\\name"));
553 
554         handle = fopen(fname, "rb");
555         if(handle == NULL)
556         {
557             M_StringCopy(savegamestrings[i], EMPTYSTRING,
558                          sizeof(savegamestrings[i]));
559             LoadMenu[i].status = 0;
560             continue;
561         }
562         fread(savegamestrings[i], 1, SAVESTRINGSIZE, handle);
563         fclose(handle);
564         LoadMenu[i].status = 1;
565     }
566 
567     if(fname)
568         Z_Free(fname);
569 }
570 
571 //
572 // M_DrawNameChar
573 //
574 // haleyjd 09/22/10: [STRIFE] New function
575 // Handler for drawing the "Name Your Character" menu.
576 //
M_DrawNameChar(void)577 void M_DrawNameChar(void)
578 {
579     int i;
580 
581     M_WriteText(72, 28, DEH_String("Name Your Character"));
582 
583     for (i = 0;i < load_end; i++)
584     {
585         M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
586         M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
587     }
588 
589     if (saveStringEnter)
590     {
591         i = M_StringWidth(savegamestrings[quickSaveSlot]);
592         M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*quickSaveSlot,"_");
593     }
594 }
595 
596 //
597 // M_DoNameChar
598 //
599 // haleyjd 09/22/10: [STRIFE] New function
600 // Handler for items in the "Name Your Character" menu.
601 //
M_DoNameChar(int choice)602 void M_DoNameChar(int choice)
603 {
604     int map;
605 
606     // 20130301: clear naming character flag for 1.31 save logic
607     if(gameversion == exe_strife_1_31)
608         namingCharacter = false;
609     sendsave = 1;
610     ClearTmp();
611     G_WriteSaveName(choice, savegamestrings[choice]);
612     quickSaveSlot = choice;
613     SaveDef.lastOn = choice;
614     ClearSlot();
615     FromCurr();
616 
617     if(isdemoversion)
618         map = 33;
619     else
620         map = 2;
621 
622     G_DeferedInitNew(menuskill, map);
623     M_ClearMenus(0);
624 }
625 
626 //
627 // M_LoadGame & Cie.
628 //
M_DrawLoad(void)629 void M_DrawLoad(void)
630 {
631     int             i;
632 
633     V_DrawPatchDirect(72, 28,
634                       W_CacheLumpName(DEH_String("M_LOADG"), PU_CACHE));
635 
636     for (i = 0;i < load_end; i++)
637     {
638         M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
639         M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
640     }
641 }
642 
643 
644 
645 //
646 // Draw border for the savegame description
647 //
M_DrawSaveLoadBorder(int x,int y)648 void M_DrawSaveLoadBorder(int x,int y)
649 {
650     int             i;
651 
652     V_DrawPatchDirect(x - 8, y + 7,
653                       W_CacheLumpName(DEH_String("M_LSLEFT"), PU_CACHE));
654 
655     for (i = 0;i < 24;i++)
656     {
657         V_DrawPatchDirect(x, y + 7,
658                           W_CacheLumpName(DEH_String("M_LSCNTR"), PU_CACHE));
659         x += 8;
660     }
661 
662     V_DrawPatchDirect(x, y + 7,
663                       W_CacheLumpName(DEH_String("M_LSRGHT"), PU_CACHE));
664 }
665 
666 
667 
668 //
669 // User wants to load this game
670 //
M_LoadSelect(int choice)671 void M_LoadSelect(int choice)
672 {
673     // [STRIFE]: completely rewritten
674     char *name = NULL;
675 
676     G_WriteSaveName(choice, savegamestrings[choice]);
677     ToCurr();
678 
679     // use safe & portable filepath concatenation for Choco
680     name = M_SafeFilePath(savegamedir, M_MakeStrifeSaveDir(choice, ""));
681 
682     G_ReadCurrent(name);
683     quickSaveSlot = choice;
684     M_ClearMenus(0);
685 
686     Z_Free(name);
687 }
688 
689 //
690 // Selected from DOOM menu
691 //
692 // [STRIFE] Verified unmodified
693 //
M_LoadGame(int choice)694 void M_LoadGame (int choice)
695 {
696     if (netgame)
697     {
698         M_StartMessage(DEH_String(LOADNET), NULL, false);
699         return;
700     }
701 
702     M_SetupNextMenu(&LoadDef);
703     M_ReadSaveStrings();
704 }
705 
706 
707 //
708 //  M_SaveGame & Cie.
709 //
M_DrawSave(void)710 void M_DrawSave(void)
711 {
712     int             i;
713 
714     V_DrawPatchDirect(72, 28, W_CacheLumpName(DEH_String("M_SAVEG"), PU_CACHE));
715     for (i = 0;i < load_end; i++)
716     {
717         M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
718         M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
719     }
720 
721     if (saveStringEnter)
722     {
723         i = M_StringWidth(savegamestrings[quickSaveSlot]);
724         M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*quickSaveSlot,"_");
725     }
726 }
727 
728 //
729 // M_Responder calls this when user is finished
730 //
M_DoSave(int slot)731 void M_DoSave(int slot)
732 {
733     // [STRIFE]: completely rewritten
734     if(slot >= 0)
735     {
736         sendsave = 1;
737         G_WriteSaveName(slot, savegamestrings[slot]);
738         M_ClearMenus(0);
739         quickSaveSlot = slot;
740         // haleyjd 20130922: slight divergence. We clear the destination slot
741         // of files here, which vanilla did not do. As a result, 1.31 had
742         // broken save behavior to the point of unusability. fraggle agrees
743         // this is detrimental enough to be fixed - unconditionally, for now.
744         ClearSlot();
745         FromCurr();
746     }
747     else
748         M_StartMessage(DEH_String(QSAVESPOT), NULL, false);
749 }
750 
751 //
752 // User wants to save. Start string input for M_Responder
753 //
M_SaveSelect(int choice)754 void M_SaveSelect(int choice)
755 {
756     int x, y;
757 
758     // we are going to be intercepting all chars
759     saveStringEnter = 1;
760 
761     // We need to turn on text input:
762     x = LoadDef.x - 11;
763     y = LoadDef.y + choice * LINEHEIGHT - 4;
764     I_StartTextInput(x, y, x + 8 + 24 * 8 + 8, y + LINEHEIGHT - 2);
765 
766     // [STRIFE]
767     quickSaveSlot = choice;
768     //saveSlot = choice;
769 
770     M_StringCopy(saveOldString, savegamestrings[choice], sizeof(saveOldString));
771     if (!strcmp(savegamestrings[choice],EMPTYSTRING))
772         savegamestrings[choice][0] = 0;
773     saveCharIndex = strlen(savegamestrings[choice]);
774 }
775 
776 //
777 // Selected from DOOM menu
778 //
M_SaveGame(int choice)779 void M_SaveGame (int choice)
780 {
781     // [STRIFE]
782     if (netgame)
783     {
784         // haleyjd 20110211: Hooray for Rogue's awesome multiplayer support...
785         M_StartMessage(DEH_String("You can't save a netgame"), NULL, false);
786         return;
787     }
788     if (!usergame)
789     {
790         M_StartMessage(DEH_String(SAVEDEAD),NULL,false);
791         return;
792     }
793 
794     if (gamestate != GS_LEVEL)
795         return;
796 
797     // [STRIFE]
798     if(gameversion == exe_strife_1_31)
799     {
800         // haleyjd 20130301: in 1.31, we can choose a slot again.
801         M_SetupNextMenu(&SaveDef);
802         M_ReadSaveStrings();
803     }
804     else
805     {
806         // In 1.2 and lower, you save over your character slot exclusively
807         M_ReadSaveStrings();
808         M_DoSave(quickSaveSlot);
809     }
810 }
811 
812 
813 
814 //
815 //      M_QuickSave
816 //
817 char    tempstring[80];
818 
M_QuickSaveResponse(int key)819 void M_QuickSaveResponse(int key)
820 {
821     if (key == key_menu_confirm)
822     {
823         M_DoSave(quickSaveSlot);
824         S_StartSound(NULL, sfx_mtalht); // [STRIFE] sound
825     }
826 }
827 
M_QuickSave(void)828 void M_QuickSave(void)
829 {
830     if (netgame)
831     {
832         // haleyjd 20110211 [STRIFE]: More fun...
833         M_StartMessage(DEH_String("You can't save a netgame"), NULL, false);
834         return;
835     }
836 
837     if (!usergame)
838     {
839         S_StartSound(NULL, sfx_oof);
840         return;
841     }
842 
843     if (gamestate != GS_LEVEL)
844         return;
845 
846     if (quickSaveSlot < 0)
847     {
848         M_StartControlPanel();
849         M_ReadSaveStrings();
850         M_SetupNextMenu(&SaveDef);
851         quickSaveSlot = -2;	// means to pick a slot now
852         return;
853     }
854     DEH_snprintf(tempstring, 80, QSPROMPT, savegamestrings[quickSaveSlot]);
855     M_StartMessage(tempstring,M_QuickSaveResponse,true);
856 }
857 
858 
859 
860 //
861 // M_QuickLoadResponse
862 //
M_QuickLoadResponse(int key)863 void M_QuickLoadResponse(int key)
864 {
865     if (key == key_menu_confirm)
866     {
867         M_LoadSelect(quickSaveSlot);
868         S_StartSound(NULL, sfx_mtalht); // [STRIFE] sound
869     }
870 }
871 
872 //
873 // M_QuickLoad
874 //
875 // [STRIFE] Verified unmodified
876 //
M_QuickLoad(void)877 void M_QuickLoad(void)
878 {
879     if (netgame)
880     {
881         M_StartMessage(DEH_String(QLOADNET),NULL,false);
882         return;
883     }
884 
885     if (quickSaveSlot < 0)
886     {
887         M_StartMessage(DEH_String(QSAVESPOT),NULL,false);
888         return;
889     }
890     DEH_snprintf(tempstring, 80, QLPROMPT, savegamestrings[quickSaveSlot]);
891     M_StartMessage(tempstring,M_QuickLoadResponse,true);
892 }
893 
894 
895 
896 
897 //
898 // Read This Menus
899 // Had a "quick hack to fix romero bug"
900 // haleyjd 08/28/10: [STRIFE] Draw HELP1, unconditionally.
901 //
M_DrawReadThis1(void)902 void M_DrawReadThis1(void)
903 {
904     inhelpscreens = true;
905 
906     V_DrawPatchDirect (0, 0, W_CacheLumpName(DEH_String("HELP1"), PU_CACHE));
907 }
908 
909 
910 
911 //
912 // Read This Menus
913 // haleyjd 08/28/10: [STRIFE] Not optional, draws HELP2
914 //
M_DrawReadThis2(void)915 void M_DrawReadThis2(void)
916 {
917     inhelpscreens = true;
918 
919     V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP2"), PU_CACHE));
920 }
921 
922 
923 //
924 // Read This Menus
925 // haleyjd 08/28/10: [STRIFE] New function to draw HELP3.
926 //
M_DrawReadThis3(void)927 void M_DrawReadThis3(void)
928 {
929     inhelpscreens = true;
930 
931     V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP3"), PU_CACHE));
932 }
933 
934 //
935 // Change Sfx & Music volumes
936 //
937 // haleyjd 08/29/10: [STRIFE]
938 // * Changed title graphic coordinates
939 // * Added voice volume and sensitivity sliders
940 //
M_DrawSound(void)941 void M_DrawSound(void)
942 {
943     V_DrawPatchDirect (100, 10, W_CacheLumpName(DEH_String("M_SVOL"), PU_CACHE));
944 
945     M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_vol+1),
946                  16,sfxVolume);
947 
948     M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(music_vol+1),
949                  16,musicVolume);
950 
951     M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(voice_vol+1),
952                  16,voiceVolume);
953 
954     M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_mouse+1),
955                  16,mouseSensitivity);
956 }
957 
M_Sound(int choice)958 void M_Sound(int choice)
959 {
960     M_SetupNextMenu(&SoundDef);
961 }
962 
M_SfxVol(int choice)963 void M_SfxVol(int choice)
964 {
965     switch(choice)
966     {
967     case 0:
968         if (sfxVolume)
969             sfxVolume--;
970         break;
971     case 1:
972         if (sfxVolume < 15)
973             sfxVolume++;
974         break;
975     }
976 
977     S_SetSfxVolume(sfxVolume * 8);
978 }
979 
980 //
981 // M_VoiceVol
982 //
983 // haleyjd 08/29/10: [STRIFE] New function
984 // Sets voice volume level.
985 //
M_VoiceVol(int choice)986 void M_VoiceVol(int choice)
987 {
988     switch(choice)
989     {
990     case 0:
991         if (voiceVolume)
992             voiceVolume--;
993         break;
994     case 1:
995         if (voiceVolume < 15)
996             voiceVolume++;
997         break;
998     }
999 
1000     S_SetVoiceVolume(voiceVolume * 8);
1001 }
1002 
M_MusicVol(int choice)1003 void M_MusicVol(int choice)
1004 {
1005     switch(choice)
1006     {
1007     case 0:
1008         if (musicVolume)
1009             musicVolume--;
1010         break;
1011     case 1:
1012         if (musicVolume < 15)
1013             musicVolume++;
1014         break;
1015     }
1016 
1017     S_SetMusicVolume(musicVolume * 8);
1018 }
1019 
1020 
1021 
1022 
1023 //
1024 // M_DrawMainMenu
1025 //
1026 // haleyjd 08/27/10: [STRIFE] Changed x coordinate; M_DOOM -> M_STRIFE
1027 //
M_DrawMainMenu(void)1028 void M_DrawMainMenu(void)
1029 {
1030     V_DrawPatchDirect(84, 2,
1031                       W_CacheLumpName(DEH_String("M_STRIFE"), PU_CACHE));
1032 }
1033 
1034 
1035 
1036 
1037 //
1038 // M_NewGame
1039 //
1040 // haleyjd 08/31/10: [STRIFE] Changed M_NEWG -> M_NGAME
1041 //
M_DrawNewGame(void)1042 void M_DrawNewGame(void)
1043 {
1044     V_DrawPatchDirect(96, 14, W_CacheLumpName(DEH_String("M_NGAME"), PU_CACHE));
1045     V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_SKILL"), PU_CACHE));
1046 }
1047 
M_NewGame(int choice)1048 void M_NewGame(int choice)
1049 {
1050     if (netgame && !demoplayback)
1051     {
1052         M_StartMessage(DEH_String(NEWGAME),NULL,false);
1053         return;
1054     }
1055     // haleyjd 09/07/10: [STRIFE] Removed Chex Quest and DOOM gamemodes
1056     if(gameversion == exe_strife_1_31)
1057        namingCharacter = true; // for 1.31 save logic
1058     M_SetupNextMenu(&NewDef);
1059 }
1060 
1061 
1062 //
1063 //      M_Episode
1064 //
1065 
1066 // haleyjd: [STRIFE] Unused
1067 /*
1068 int     epi;
1069 
1070 void M_DrawEpisode(void)
1071 {
1072     V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_EPISOD"), PU_CACHE));
1073 }
1074 
1075 void M_VerifyNightmare(int key)
1076 {
1077     if (key != key_menu_confirm)
1078         return;
1079 
1080     G_DeferedInitNew(nightmare, 1);
1081     M_ClearMenus (0);
1082 }
1083 */
1084 
M_ChooseSkill(int choice)1085 void M_ChooseSkill(int choice)
1086 {
1087     // haleyjd 09/07/10: Removed nightmare confirmation
1088     // [STRIFE]: start "Name Your Character" menu
1089     menuskill = choice;
1090     currentMenu = &NameCharDef;
1091     itemOn = NameCharDef.lastOn;
1092     M_ReadSaveStrings();
1093 }
1094 
1095 /*
1096 // haleyjd [STRIFE] Unused
1097 void M_Episode(int choice)
1098 {
1099     if ( (gamemode == shareware)
1100 	 && choice)
1101     {
1102 	M_StartMessage(DEH_String(SWSTRING),NULL,false);
1103 	M_SetupNextMenu(&ReadDef1);
1104 	return;
1105     }
1106 
1107     // Yet another hack...
1108     if ( (gamemode == registered)
1109 	 && (choice > 2))
1110     {
1111       fprintf( stderr,
1112 	       "M_Episode: 4th episode requires UltimateDOOM\n");
1113       choice = 0;
1114     }
1115 
1116     epi = choice;
1117     M_SetupNextMenu(&NewDef);
1118 }
1119 */
1120 
1121 
1122 //
1123 // M_Options
1124 //
1125 char    detailNames[2][9]	= {"M_GDHIGH","M_GDLOW"};
1126 char	msgNames[2][9]		= {"M_MSGOFF","M_MSGON"};
1127 
1128 
M_DrawOptions(void)1129 void M_DrawOptions(void)
1130 {
1131     // haleyjd 08/27/10: [STRIFE] M_OPTTTL -> M_OPTION
1132     V_DrawPatchDirect(108, 15,
1133                       W_CacheLumpName(DEH_String("M_OPTION"), PU_CACHE));
1134 
1135     // haleyjd 08/26/10: [STRIFE] Removed messages, sensitivity, detail.
1136 
1137     M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(scrnsize+1),
1138                  9,screenSize);
1139 }
1140 
M_Options(int choice)1141 void M_Options(int choice)
1142 {
1143     M_SetupNextMenu(&OptionsDef);
1144 }
1145 
1146 //
1147 // M_AutoUseHealth
1148 //
1149 // [STRIFE] New function
1150 // haleyjd 20110211: toggle autouse health state
1151 //
M_AutoUseHealth(void)1152 void M_AutoUseHealth(void)
1153 {
1154     if(!netgame && usergame)
1155     {
1156         players[consoleplayer].cheats ^= CF_AUTOHEALTH;
1157 
1158         if(players[consoleplayer].cheats & CF_AUTOHEALTH)
1159             players[consoleplayer].message = DEH_String("Auto use health ON");
1160         else
1161             players[consoleplayer].message = DEH_String("Auto use health OFF");
1162     }
1163 }
1164 
1165 //
1166 // M_ChangeShowText
1167 //
1168 // [STRIFE] New function
1169 //
M_ChangeShowText(void)1170 void M_ChangeShowText(void)
1171 {
1172     dialogshowtext ^= true;
1173 
1174     if(dialogshowtext)
1175         players[consoleplayer].message = DEH_String("Conversation Text On");
1176     else
1177         players[consoleplayer].message = DEH_String("Conversation Text Off");
1178 }
1179 
1180 //
1181 //      Toggle messages on/off
1182 //
1183 // [STRIFE] Messages cannot be disabled in Strife
1184 /*
1185 void M_ChangeMessages(int choice)
1186 {
1187     // warning: unused parameter `int choice'
1188     choice = 0;
1189     showMessages = 1 - showMessages;
1190 
1191     if (!showMessages)
1192         players[consoleplayer].message = DEH_String(MSGOFF);
1193     else
1194         players[consoleplayer].message = DEH_String(MSGON);
1195 
1196     message_dontfuckwithme = true;
1197 }
1198 */
1199 
1200 
1201 //
1202 // M_EndGame
1203 //
M_EndGameResponse(int key)1204 void M_EndGameResponse(int key)
1205 {
1206     if (key != key_menu_confirm)
1207         return;
1208 
1209     currentMenu->lastOn = itemOn;
1210     M_ClearMenus (0);
1211     D_StartTitle ();
1212 }
1213 
M_EndGame(int choice)1214 void M_EndGame(int choice)
1215 {
1216     choice = 0;
1217     if (!usergame)
1218     {
1219         S_StartSound(NULL,sfx_oof);
1220         return;
1221     }
1222 
1223     if (netgame)
1224     {
1225         M_StartMessage(DEH_String(NETEND),NULL,false);
1226         return;
1227     }
1228 
1229     M_StartMessage(DEH_String(ENDGAME),M_EndGameResponse,true);
1230 }
1231 
1232 
1233 
1234 
1235 //
1236 // M_ReadThis
1237 //
M_ReadThis(int choice)1238 void M_ReadThis(int choice)
1239 {
1240     choice = 0;
1241     M_SetupNextMenu(&ReadDef1);
1242 }
1243 
1244 //
1245 // M_ReadThis2
1246 //
1247 // haleyjd 08/28/10: [STRIFE] Eliminated DOOM stuff.
1248 //
M_ReadThis2(int choice)1249 void M_ReadThis2(int choice)
1250 {
1251     choice = 0;
1252     M_SetupNextMenu(&ReadDef2);
1253 }
1254 
1255 //
1256 // M_ReadThis3
1257 //
1258 // haleyjd 08/28/10: [STRIFE] New function.
1259 //
M_ReadThis3(int choice)1260 void M_ReadThis3(int choice)
1261 {
1262     choice = 0;
1263     M_SetupNextMenu(&ReadDef3);
1264 }
1265 
1266 /*
1267 // haleyjd 08/28/10: [STRIFE] Not used.
1268 void M_FinishReadThis(int choice)
1269 {
1270     choice = 0;
1271     M_SetupNextMenu(&MainDef);
1272 }
1273 */
1274 
1275 #if 0
1276 extern void F_StartCast(void);
1277 
1278 //
1279 // M_CheckStartCast
1280 //
1281 // [STRIFE] New but unused function. Was going to start a cast
1282 //   call from within the menu system... not functional even in
1283 //   the earliest demo version.
1284 //
1285 void M_CheckStartCast()
1286 {
1287     if(usergame)
1288     {
1289         M_StartMessage(DEH_String("You have to end your game first."), NULL, false);
1290         return;
1291     }
1292 
1293     F_StartCast();
1294     M_ClearMenus(0);
1295 }
1296 #endif
1297 
1298 //
1299 // M_QuitResponse
1300 //
1301 // haleyjd 09/11/10: [STRIFE] Modifications to start up endgame
1302 // demosequence.
1303 //
M_QuitResponse(int key)1304 void M_QuitResponse(int key)
1305 {
1306     char buffer[20];
1307 
1308     if (key != key_menu_confirm)
1309         return;
1310 
1311     if(netgame)
1312         I_Quit();
1313     else
1314     {
1315         DEH_snprintf(buffer, sizeof(buffer), "qfmrm%i", gametic % 8 + 1);
1316         I_StartVoice(buffer);
1317         D_QuitGame();
1318     }
1319 }
1320 
1321 /*
1322 // haleyjd 09/11/10: [STRIFE] Unused
1323 static char *M_SelectEndMessage(void)
1324 {
1325 }
1326 */
1327 
1328 //
1329 // M_QuitStrife
1330 //
1331 // [STRIFE] Renamed from M_QuitDOOM
1332 // haleyjd 09/11/10: No randomized text message; that's taken care of
1333 // by the randomized voice message after confirmation.
1334 //
M_QuitStrife(int choice)1335 void M_QuitStrife(int choice)
1336 {
1337     DEH_snprintf(endstring, sizeof(endstring),
1338                  "Do you really want to leave?\n\n" DOSY);
1339 
1340     M_StartMessage(endstring, M_QuitResponse, true);
1341 }
1342 
1343 
1344 
1345 
M_ChangeSensitivity(int choice)1346 void M_ChangeSensitivity(int choice)
1347 {
1348     switch(choice)
1349     {
1350     case 0:
1351         if (mouseSensitivity)
1352             mouseSensitivity--;
1353         break;
1354     case 1:
1355         if (mouseSensitivity < 9)
1356             mouseSensitivity++;
1357         break;
1358     }
1359 }
1360 
1361 /*
1362 // haleyjd [STRIFE] Unused
1363 void M_ChangeDetail(int choice)
1364 {
1365     choice = 0;
1366     detailLevel = 1 - detailLevel;
1367 
1368     R_SetViewSize (screenblocks, detailLevel);
1369 
1370     if (!detailLevel)
1371 	players[consoleplayer].message = DEH_String(DETAILHI);
1372     else
1373 	players[consoleplayer].message = DEH_String(DETAILLO);
1374 }
1375 */
1376 
1377 // [STRIFE] Verified unmodified
M_SizeDisplay(int choice)1378 void M_SizeDisplay(int choice)
1379 {
1380     switch(choice)
1381     {
1382     case 0:
1383         if (screenSize > 0)
1384         {
1385             screenblocks--;
1386             screenSize--;
1387         }
1388         break;
1389     case 1:
1390         if (screenSize < 8)
1391         {
1392             screenblocks++;
1393             screenSize++;
1394         }
1395         break;
1396     }
1397 
1398     R_SetViewSize (screenblocks, detailLevel);
1399 }
1400 
1401 
1402 
1403 
1404 //
1405 //      Menu Functions
1406 //
1407 
1408 //
1409 // M_DrawThermo
1410 //
1411 // haleyjd 08/28/10: [STRIFE] Changes to some patch coordinates.
1412 //
1413 void
M_DrawThermo(int x,int y,int thermWidth,int thermDot)1414 M_DrawThermo
1415 ( int	x,
1416   int	y,
1417   int	thermWidth,
1418   int	thermDot )
1419 {
1420     int         xx;
1421     int         yy; // [STRIFE] Needs a temp y coordinate variable
1422     int         i;
1423 
1424     xx = x;
1425     yy = y + 6; // [STRIFE] +6 to y coordinate
1426     V_DrawPatchDirect(xx, yy, W_CacheLumpName(DEH_String("M_THERML"), PU_CACHE));
1427     xx += 8;
1428     for (i=0;i<thermWidth;i++)
1429     {
1430         V_DrawPatchDirect(xx, yy, W_CacheLumpName(DEH_String("M_THERMM"), PU_CACHE));
1431         xx += 8;
1432     }
1433     V_DrawPatchDirect(xx, yy, W_CacheLumpName(DEH_String("M_THERMR"), PU_CACHE));
1434 
1435     // [STRIFE] +2 to initial y coordinate
1436     V_DrawPatchDirect((x + 8) + thermDot * 8, y + 2,
1437                       W_CacheLumpName(DEH_String("M_THERMO"), PU_CACHE));
1438 }
1439 
1440 
1441 // haleyjd: These are from DOOM v0.5 and the prebeta! They drew those ugly red &
1442 // blue checkboxes... preserved for historical interest, as not in Strife.
1443 void
M_DrawEmptyCell(menu_t * menu,int item)1444 M_DrawEmptyCell
1445 ( menu_t*	menu,
1446   int		item )
1447 {
1448     V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1,
1449                       W_CacheLumpName(DEH_String("M_CELL1"), PU_CACHE));
1450 }
1451 
1452 void
M_DrawSelCell(menu_t * menu,int item)1453 M_DrawSelCell
1454 ( menu_t*	menu,
1455   int		item )
1456 {
1457     V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1,
1458                       W_CacheLumpName(DEH_String("M_CELL2"), PU_CACHE));
1459 }
1460 
1461 
1462 void
M_StartMessage(char * string,void * routine,boolean input)1463 M_StartMessage
1464 ( char*		string,
1465   void*		routine,
1466   boolean	input )
1467 {
1468     messageLastMenuActive = menuactive;
1469     messageToPrint = 1;
1470     messageString = string;
1471     messageRoutine = routine;
1472     messageNeedsInput = input;
1473     menuactive = true;
1474     return;
1475 }
1476 
1477 
1478 
M_StopMessage(void)1479 void M_StopMessage(void)
1480 {
1481     menuactive = messageLastMenuActive;
1482     messageToPrint = 0;
1483 }
1484 
1485 
1486 
1487 //
1488 // Find string width from hu_font chars
1489 //
M_StringWidth(char * string)1490 int M_StringWidth(char* string)
1491 {
1492     size_t             i;
1493     int             w = 0;
1494     int             c;
1495 
1496     for (i = 0;i < strlen(string);i++)
1497     {
1498         c = toupper(string[i]) - HU_FONTSTART;
1499         if (c < 0 || c >= HU_FONTSIZE)
1500             w += 4;
1501         else
1502             w += SHORT (hu_font[c]->width);
1503     }
1504 
1505     return w;
1506 }
1507 
1508 
1509 
1510 //
1511 //      Find string height from hu_font chars
1512 //
M_StringHeight(char * string)1513 int M_StringHeight(char* string)
1514 {
1515     size_t             i;
1516     int             h;
1517     int             height = SHORT(hu_font[0]->height);
1518 
1519     h = height;
1520     for (i = 0;i < strlen(string);i++)
1521         if (string[i] == '\n')
1522             h += height;
1523 
1524     return h;
1525 }
1526 
1527 
1528 //
1529 // M_WriteText
1530 //
1531 // Write a string using the hu_font
1532 // haleyjd 09/04/10: [STRIFE]
1533 // * Rogue made a lot of changes to this for the dialog system.
1534 //
1535 int
M_WriteText(int x,int y,const char * string)1536 M_WriteText
1537 ( int           x,
1538   int           y,
1539   const char*   string) // haleyjd: made const for safety w/dialog engine
1540 {
1541     int	        w;
1542     const char* ch;
1543     int         c;
1544     int         cx;
1545     int         cy;
1546 
1547     ch = string;
1548     cx = x;
1549     cy = y;
1550 
1551     while(1)
1552     {
1553         c = *ch++;
1554         if (!c)
1555             break;
1556 
1557         // haleyjd 09/04/10: [STRIFE] Don't draw spaces at the start of lines.
1558         if(c == ' ' && cx == x)
1559             continue;
1560 
1561         if (c == '\n')
1562         {
1563             cx = x;
1564             cy += 11; // haleyjd 09/04/10: [STRIFE]: Changed 12 -> 11
1565             continue;
1566         }
1567 
1568         c = toupper(c) - HU_FONTSTART;
1569         if (c < 0 || c>= HU_FONTSIZE)
1570         {
1571             cx += 4;
1572             continue;
1573         }
1574 
1575         w = SHORT (hu_font[c]->width);
1576 
1577         // haleyjd 09/04/10: [STRIFE] Different linebreak handling
1578         if (cx + w > SCREENWIDTH - 20)
1579         {
1580             cx = x;
1581             cy += 11;
1582             --ch;
1583         }
1584         else
1585         {
1586             V_DrawPatchDirect(cx, cy, hu_font[c]);
1587             cx += w;
1588         }
1589     }
1590 
1591     // [STRIFE] Return final y coordinate.
1592     return cy + 12;
1593 }
1594 
1595 //
1596 // M_DialogDimMsg
1597 //
1598 // [STRIFE] New function
1599 // haleyjd 09/04/10: Painstakingly transformed from the assembly code, as the
1600 // decompiler could not touch it. Redimensions a string to fit on screen, leaving
1601 // at least a 20 pixel margin on the right side. The string passed in must be
1602 // writable.
1603 //
M_DialogDimMsg(int x,int y,char * str,boolean useyfont)1604 void M_DialogDimMsg(int x, int y, char *str, boolean useyfont)
1605 {
1606     int rightbound = (SCREENWIDTH - 20) - x;
1607     patch_t **fontarray;  // ebp
1608     int linewidth = 0;    // esi
1609     int i = 0;            // edx
1610     char *message = str;  // edi
1611     char  bl;             // bl
1612 
1613     if(useyfont)
1614        fontarray = yfont;
1615     else
1616        fontarray = hu_font;
1617 
1618     bl = toupper(*message);
1619 
1620     if(!bl)
1621         return;
1622 
1623     // outer loop - run to end of string
1624     do
1625     {
1626         if(bl != '\n')
1627         {
1628             int charwidth; // eax
1629             int tempwidth; // ecx
1630 
1631             if(bl < HU_FONTSTART || bl > HU_FONTEND)
1632                 charwidth = 4;
1633             else
1634                 charwidth = SHORT(fontarray[bl - HU_FONTSTART]->width);
1635 
1636             tempwidth = linewidth + charwidth;
1637 
1638             // Test if the line still fits within the boundary...
1639             if(tempwidth >= rightbound)
1640             {
1641                 // Doesn't fit...
1642                 char *tempptr = &message[i]; // ebx
1643                 char  al;                    // al
1644 
1645                 // inner loop - run backward til a space (or the start of the
1646                 // string) is found, subtracting width off the current line.
1647                 // BUG: shouldn't we stop at a previous '\n' too?
1648                 while(*tempptr != ' ' && i > 0)
1649                 {
1650                     tempptr--;
1651                     // BUG: they didn't add the first char to linewidth yet...
1652                     linewidth -= charwidth;
1653                     i--;
1654                     al = toupper(*tempptr);
1655                     if(al < HU_FONTSTART || al > HU_FONTEND)
1656                         charwidth = 4;
1657                     else
1658                         charwidth = SHORT(fontarray[al - HU_FONTSTART]->width);
1659                 }
1660                 // Replace the space with a linebreak.
1661                 // BUG: what if i is zero? ... infinite loop time!
1662                 message[i] = '\n';
1663                 linewidth = 0;
1664             }
1665             else
1666             {
1667                 // The line does fit.
1668                 // Spaces at the start of a line don't count though.
1669                 if(!(bl == ' ' && linewidth == 0))
1670                     linewidth += charwidth;
1671             }
1672         }
1673         else
1674             linewidth = 0; // '\n' seen, so reset the line width
1675     }
1676     while((bl = toupper(message[++i])) != 0); // step to the next character
1677 }
1678 
1679 // These keys evaluate to a "null" key in Vanilla Doom that allows weird
1680 // jumping in the menus. Preserve this behavior for accuracy.
1681 
IsNullKey(int key)1682 static boolean IsNullKey(int key)
1683 {
1684     return key == KEY_PAUSE || key == KEY_CAPSLOCK
1685         || key == KEY_SCRLCK || key == KEY_NUMLOCK;
1686 }
1687 
1688 //
1689 // CONTROL PANEL
1690 //
1691 
1692 //
1693 // M_Responder
1694 //
M_Responder(event_t * ev)1695 boolean M_Responder (event_t* ev)
1696 {
1697     int             ch;
1698     int             key;
1699     int             i;
1700     static  int     joywait = 0;
1701     static  int     mousewait = 0;
1702     static  int     mousey = 0;
1703     static  int     lasty = 0;
1704     static  int     mousex = 0;
1705     static  int     lastx = 0;
1706 
1707     // In testcontrols mode, none of the function keys should do anything
1708     // - the only key is escape to quit.
1709 
1710     if (testcontrols)
1711     {
1712         if (ev->type == ev_quit
1713          || (ev->type == ev_keydown
1714           && (ev->data1 == key_menu_activate || ev->data1 == key_menu_quit)))
1715         {
1716             I_Quit();
1717             return true;
1718         }
1719 
1720         return false;
1721     }
1722 
1723     // "close" button pressed on window?
1724     if (ev->type == ev_quit)
1725     {
1726         // First click on close button = bring up quit confirm message.
1727         // Second click on close button = confirm quit
1728 
1729         if (menuactive && messageToPrint && messageRoutine == M_QuitResponse)
1730         {
1731             M_QuitResponse(key_menu_confirm);
1732         }
1733         else
1734         {
1735             S_StartSound(NULL, sfx_swtchn);
1736             M_QuitStrife(0);
1737         }
1738 
1739         return true;
1740     }
1741 
1742     // key is the key pressed, ch is the actual character typed
1743 
1744     ch = 0;
1745     key = -1;
1746 
1747     if (ev->type == ev_joystick && joywait < I_GetTime())
1748     {
1749         if (ev->data3 < 0)
1750         {
1751             key = key_menu_up;
1752             joywait = I_GetTime() + 5;
1753         }
1754         else if (ev->data3 > 0)
1755         {
1756             key = key_menu_down;
1757             joywait = I_GetTime() + 5;
1758         }
1759 
1760         if (ev->data2 < 0)
1761         {
1762             key = key_menu_left;
1763             joywait = I_GetTime() + 2;
1764         }
1765         else if (ev->data2 > 0)
1766         {
1767             key = key_menu_right;
1768             joywait = I_GetTime() + 2;
1769         }
1770 
1771         if (ev->data1&1)
1772         {
1773             key = key_menu_forward;
1774             joywait = I_GetTime() + 5;
1775         }
1776         if (ev->data1&2)
1777         {
1778             key = key_menu_back;
1779             joywait = I_GetTime() + 5;
1780         }
1781         if (joybmenu >= 0 && (ev->data1 & (1 << joybmenu)) != 0)
1782         {
1783             key = key_menu_activate;
1784             joywait = I_GetTime() + 5;
1785         }
1786     }
1787     else
1788     {
1789         if (ev->type == ev_mouse && mousewait < I_GetTime())
1790         {
1791             mousey += ev->data3;
1792             if (mousey < lasty-30)
1793             {
1794                 key = key_menu_down;
1795                 mousewait = I_GetTime() + 5;
1796                 mousey = lasty -= 30;
1797             }
1798             else if (mousey > lasty+30)
1799             {
1800                 key = key_menu_up;
1801                 mousewait = I_GetTime() + 5;
1802                 mousey = lasty += 30;
1803             }
1804 
1805             mousex += ev->data2;
1806             if (mousex < lastx-30)
1807             {
1808                 key = key_menu_left;
1809                 mousewait = I_GetTime() + 5;
1810                 mousex = lastx -= 30;
1811             }
1812             else if (mousex > lastx+30)
1813             {
1814                 key = key_menu_right;
1815                 mousewait = I_GetTime() + 5;
1816                 mousex = lastx += 30;
1817             }
1818 
1819             if (ev->data1&1)
1820             {
1821                 key = key_menu_forward;
1822                 mousewait = I_GetTime() + 15;
1823                 mouse_fire_countdown = 5;   // villsa [STRIFE]
1824             }
1825 
1826             if (ev->data1&2)
1827             {
1828                 key = key_menu_back;
1829                 mousewait = I_GetTime() + 15;
1830             }
1831         }
1832         else
1833         {
1834             if (ev->type == ev_keydown)
1835             {
1836                 key = ev->data1;
1837                 ch = ev->data2;
1838             }
1839         }
1840     }
1841 
1842     if (key == -1)
1843         return false;
1844 
1845     // Save Game string input
1846     if (saveStringEnter)
1847     {
1848         switch(key)
1849         {
1850         case KEY_BACKSPACE:
1851             if (saveCharIndex > 0)
1852             {
1853                 saveCharIndex--;
1854                 savegamestrings[quickSaveSlot][saveCharIndex] = 0;
1855             }
1856             break;
1857 
1858         case KEY_ESCAPE:
1859             saveStringEnter = 0;
1860             I_StopTextInput();
1861             M_StringCopy(savegamestrings[quickSaveSlot], saveOldString,
1862                          sizeof(savegamestrings[quickSaveSlot]));
1863             break;
1864 
1865         case KEY_ENTER:
1866             // [STRIFE]
1867             saveStringEnter = 0;
1868             I_StopTextInput();
1869             if(gameversion == exe_strife_1_31 && !namingCharacter)
1870             {
1871                // In 1.31, we can be here as a result of normal saving again,
1872                // whereas in 1.2 this only ever happens when naming your
1873                // character to begin a new game.
1874                M_DoSave(quickSaveSlot);
1875                return true;
1876             }
1877             if (savegamestrings[quickSaveSlot][0])
1878                 M_DoNameChar(quickSaveSlot);
1879             break;
1880 
1881         default:
1882             // Savegame name entry. This is complicated.
1883             // Vanilla has a bug where the shift key is ignored when entering
1884             // a savegame name. If vanilla_keyboard_mapping is on, we want
1885             // to emulate this bug by using ev->data1. But if it's turned off,
1886             // it implies the user doesn't care about Vanilla emulation:
1887             // instead, use ev->data3 which gives the fully-translated and
1888             // modified key input.
1889 
1890             if (ev->type != ev_keydown)
1891             {
1892                 break;
1893             }
1894 
1895             if (vanilla_keyboard_mapping)
1896             {
1897                 ch = ev->data1;
1898             }
1899             else
1900             {
1901                 ch = ev->data3;
1902             }
1903 
1904             ch = toupper(ch);
1905 
1906             if (ch != ' '
1907                 && (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE))
1908             {
1909                 break;
1910             }
1911 
1912             if (ch >= 32 && ch <= 127 &&
1913                 saveCharIndex < SAVESTRINGSIZE-1 &&
1914                 M_StringWidth(savegamestrings[quickSaveSlot]) <
1915                 (SAVESTRINGSIZE-2)*8)
1916             {
1917                 savegamestrings[quickSaveSlot][saveCharIndex++] = ch;
1918                 savegamestrings[quickSaveSlot][saveCharIndex] = 0;
1919             }
1920             break;
1921         }
1922         return true;
1923     }
1924 
1925     // Take care of any messages that need input
1926     if (messageToPrint)
1927     {
1928         if (messageNeedsInput)
1929         {
1930             if (key != ' ' && key != KEY_ESCAPE
1931                 && key != key_menu_confirm && key != key_menu_abort)
1932             {
1933                 return false;
1934             }
1935         }
1936 
1937         menuactive = messageLastMenuActive;
1938         messageToPrint = 0;
1939         if (messageRoutine)
1940             messageRoutine(key);
1941 
1942         menupause = false;                // [STRIFE] unpause
1943         menuactive = false;
1944         S_StartSound(NULL, sfx_mtalht);   // [STRIFE] sound
1945         return true;
1946     }
1947 
1948     // [STRIFE]:
1949     // * In v1.2 this is moved to F9 (quickload)
1950     // * In v1.31 it is moved to F12 with DM spy, and quicksave
1951     //   functionality is restored separate from normal saving
1952     /*
1953     if (devparm && key == key_menu_help)
1954     {
1955         G_ScreenShot ();
1956         return true;
1957     }
1958     */
1959 
1960     // F-Keys
1961     if (!menuactive)
1962     {
1963         if (key == key_menu_decscreen)      // Screen size down
1964         {
1965             if (automapactive || chat_on)
1966                 return false;
1967             M_SizeDisplay(0);
1968             S_StartSound(NULL, sfx_stnmov);
1969             return true;
1970         }
1971         else if (key == key_menu_incscreen) // Screen size up
1972         {
1973             if (automapactive || chat_on)
1974                 return false;
1975             M_SizeDisplay(1);
1976             S_StartSound(NULL, sfx_stnmov);
1977             return true;
1978         }
1979         else if (key == key_menu_help)     // Help key
1980         {
1981             M_StartControlPanel ();
1982             // haleyjd 08/29/10: [STRIFE] always ReadDef1
1983             currentMenu = &ReadDef1;
1984 
1985             itemOn = 0;
1986             S_StartSound(NULL, sfx_swtchn);
1987             return true;
1988         }
1989         else if (key == key_menu_save)     // Save
1990         {
1991             // [STRIFE]: Hub saves
1992             if(gameversion == exe_strife_1_31)
1993                 namingCharacter = false; // just saving normally, in 1.31
1994 
1995             if(netgame || players[consoleplayer].health <= 0 ||
1996                 players[consoleplayer].cheats & CF_ONFIRE)
1997             {
1998                 S_StartSound(NULL, sfx_oof);
1999             }
2000             else
2001             {
2002                 M_StartControlPanel();
2003                 S_StartSound(NULL, sfx_swtchn);
2004                 M_SaveGame(0);
2005             }
2006             return true;
2007         }
2008         else if (key == key_menu_load)     // Load
2009         {
2010             // [STRIFE]: Hub saves
2011             if(gameversion == exe_strife_1_31)
2012             {
2013                 // 1.31: normal save loading
2014                 namingCharacter = false;
2015                 M_StartControlPanel();
2016                 M_LoadGame(0);
2017                 S_StartSound(NULL, sfx_swtchn);
2018             }
2019             else
2020             {
2021                 // Pre 1.31: quickload only
2022                 S_StartSound(NULL, sfx_swtchn);
2023                 M_QuickLoad();
2024             }
2025             return true;
2026         }
2027         else if (key == key_menu_volume)   // Sound Volume
2028         {
2029             M_StartControlPanel ();
2030             currentMenu = &SoundDef;
2031             itemOn = sfx_vol;
2032             S_StartSound(NULL, sfx_swtchn);
2033             return true;
2034         }
2035         else if (key == key_menu_detail)   // Detail toggle
2036         {
2037             //M_ChangeDetail(0);
2038             M_AutoUseHealth(); // [STRIFE]
2039             S_StartSound(NULL, sfx_swtchn);
2040             return true;
2041         }
2042         else if (key == key_menu_qsave)    // Quicksave
2043         {
2044             // [STRIFE]: Hub saves
2045             if(gameversion == exe_strife_1_31)
2046                 namingCharacter = false; // for 1.31 save changes
2047 
2048             if(netgame || players[consoleplayer].health <= 0 ||
2049                players[consoleplayer].cheats & CF_ONFIRE)
2050             {
2051                 S_StartSound(NULL, sfx_oof);
2052             }
2053             else
2054             {
2055                 S_StartSound(NULL, sfx_swtchn);
2056                 M_QuickSave();
2057             }
2058             return true;
2059         }
2060         else if (key == key_menu_endgame)  // End game
2061         {
2062             S_StartSound(NULL, sfx_swtchn);
2063             M_EndGame(0);
2064             return true;
2065         }
2066         else if (key == key_menu_messages) // Toggle messages
2067         {
2068             //M_ChangeMessages(0);
2069             M_ChangeShowText(); // [STRIFE]
2070             S_StartSound(NULL, sfx_swtchn);
2071             return true;
2072         }
2073         else if (key == key_menu_qload)    // Quickload
2074         {
2075             // [STRIFE]
2076             // * v1.2: takes a screenshot
2077             // * v1.31: does quickload again
2078             if(gameversion == exe_strife_1_31)
2079             {
2080                 namingCharacter = false;
2081                 S_StartSound(NULL, sfx_swtchn);
2082                 M_QuickLoad();
2083             }
2084             else
2085                 G_ScreenShot();
2086             return true;
2087         }
2088         else if (key == key_menu_quit)     // Quit DOOM
2089         {
2090             S_StartSound(NULL, sfx_swtchn);
2091             M_QuitStrife(0);
2092             return true;
2093         }
2094         else if (key == key_menu_gamma)    // gamma toggle
2095         {
2096             usegamma++;
2097             if (usegamma > 4)
2098                 usegamma = 0;
2099             players[consoleplayer].message = DEH_String(gammamsg[usegamma]);
2100             I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE));
2101             return true;
2102         }
2103         else if(gameversion == exe_strife_1_31 && key == key_spy)
2104         {
2105             // haleyjd 20130301: 1.31 moved screenshots to F12.
2106             G_ScreenShot();
2107             return true;
2108         }
2109         else if (key != 0 && key == key_menu_screenshot)
2110         {
2111             G_ScreenShot();
2112             return true;
2113         }
2114     }
2115 
2116     // Pop-up menu?
2117     if (!menuactive)
2118     {
2119         if (key == key_menu_activate)
2120         {
2121             M_StartControlPanel ();
2122             S_StartSound(NULL, sfx_swtchn);
2123             return true;
2124         }
2125         return false;
2126     }
2127 
2128 
2129     // Keys usable within menu
2130 
2131     if (key == key_menu_down)
2132     {
2133         // Move down to next item
2134 
2135         do
2136         {
2137             if (itemOn+1 > currentMenu->numitems-1)
2138                 itemOn = 0;
2139             else itemOn++;
2140             S_StartSound(NULL, sfx_pstop);
2141         } while(currentMenu->menuitems[itemOn].status==-1);
2142 
2143         return true;
2144     }
2145     else if (key == key_menu_up)
2146     {
2147         // Move back up to previous item
2148 
2149         do
2150         {
2151             if (!itemOn)
2152                 itemOn = currentMenu->numitems-1;
2153             else itemOn--;
2154             S_StartSound(NULL, sfx_pstop);
2155         } while(currentMenu->menuitems[itemOn].status==-1);
2156 
2157         return true;
2158     }
2159     else if (key == key_menu_left)
2160     {
2161         // Slide slider left
2162 
2163         if (currentMenu->menuitems[itemOn].routine &&
2164             currentMenu->menuitems[itemOn].status == 2)
2165         {
2166             S_StartSound(NULL, sfx_stnmov);
2167             currentMenu->menuitems[itemOn].routine(0);
2168         }
2169         return true;
2170     }
2171     else if (key == key_menu_right)
2172     {
2173         // Slide slider right
2174 
2175         if (currentMenu->menuitems[itemOn].routine &&
2176             currentMenu->menuitems[itemOn].status == 2)
2177         {
2178             S_StartSound(NULL, sfx_stnmov);
2179             currentMenu->menuitems[itemOn].routine(1);
2180         }
2181         return true;
2182     }
2183     else if (key == key_menu_forward)
2184     {
2185         // Activate menu item
2186 
2187         if (currentMenu->menuitems[itemOn].routine &&
2188             currentMenu->menuitems[itemOn].status)
2189         {
2190             currentMenu->lastOn = itemOn;
2191             if (currentMenu->menuitems[itemOn].status == 2)
2192             {
2193                 currentMenu->menuitems[itemOn].routine(1);      // right arrow
2194                 S_StartSound(NULL, sfx_stnmov);
2195             }
2196             else
2197             {
2198                 currentMenu->menuitems[itemOn].routine(itemOn);
2199                 //S_StartSound(NULL, sfx_swish); [STRIFE] No sound is played here.
2200             }
2201         }
2202         return true;
2203     }
2204     else if (key == key_menu_activate)
2205     {
2206         // Deactivate menu
2207         if(gameversion == exe_strife_1_31) // [STRIFE]: 1.31 saving
2208             namingCharacter = false;
2209 
2210         if(menuindialog) // [STRIFE] - Get out of dialog engine semi-gracefully
2211             P_DialogDoChoice(-1);
2212 
2213         currentMenu->lastOn = itemOn;
2214         M_ClearMenus (0);
2215         S_StartSound(NULL, sfx_mtalht); // villsa [STRIFE]: sounds
2216         return true;
2217     }
2218     else if (key == key_menu_back)
2219     {
2220         // Go back to previous menu
2221 
2222         currentMenu->lastOn = itemOn;
2223         if (currentMenu->prevMenu)
2224         {
2225             currentMenu = currentMenu->prevMenu;
2226             itemOn = currentMenu->lastOn;
2227             S_StartSound(NULL, sfx_swtchn);
2228         }
2229         return true;
2230     }
2231 
2232     // Keyboard shortcut?
2233     // Vanilla Strife has a weird behavior where it jumps to the scroll bars
2234     // when certain keys are pressed, so emulate this.
2235 
2236     else if (ch != 0 || IsNullKey(key))
2237     {
2238         // Keyboard shortcut?
2239 
2240         for (i = itemOn+1;i < currentMenu->numitems;i++)
2241         {
2242             if (currentMenu->menuitems[i].alphaKey == ch)
2243             {
2244                 itemOn = i;
2245                 S_StartSound(NULL, sfx_pstop);
2246                 return true;
2247             }
2248         }
2249 
2250         for (i = 0;i <= itemOn;i++)
2251         {
2252             if (currentMenu->menuitems[i].alphaKey == ch)
2253             {
2254                 itemOn = i;
2255                 S_StartSound(NULL, sfx_pstop);
2256                 return true;
2257             }
2258         }
2259     }
2260 
2261     return false;
2262 }
2263 
2264 
2265 
2266 //
2267 // M_StartControlPanel
2268 //
M_StartControlPanel(void)2269 void M_StartControlPanel (void)
2270 {
2271     // intro might call this repeatedly
2272     if (menuactive)
2273         return;
2274 
2275     menuactive = 1;
2276     menupause = true;
2277     currentMenu = &MainDef;         // JDC
2278     itemOn = currentMenu->lastOn;   // JDC
2279 }
2280 
2281 
2282 //
2283 // M_Drawer
2284 // Called after the view has been rendered,
2285 // but before it has been blitted.
2286 //
M_Drawer(void)2287 void M_Drawer (void)
2288 {
2289     static short	x;
2290     static short	y;
2291     unsigned int	i;
2292     unsigned int	max;
2293     char		string[80];
2294     char               *name;
2295     int			start;
2296 
2297     inhelpscreens = false;
2298 
2299     // Horiz. & Vertically center string and print it.
2300     if (messageToPrint)
2301     {
2302         start = 0;
2303         y = 100 - M_StringHeight(messageString) / 2;
2304         while (messageString[start] != '\0')
2305         {
2306             int foundnewline = 0;
2307 
2308             for (i = 0; i < strlen(messageString + start); i++)
2309             {
2310                 if (messageString[start + i] == '\n')
2311                 {
2312                     M_StringCopy(string, messageString + start,
2313                                  sizeof(string));
2314                     if (i < sizeof(string))
2315                     {
2316                         string[i] = '\0';
2317                     }
2318 
2319                     foundnewline = 1;
2320                     start += i + 1;
2321                     break;
2322                 }
2323             }
2324 
2325             if (!foundnewline)
2326             {
2327                 M_StringCopy(string, messageString + start,
2328                              sizeof(string));
2329                 start += strlen(string);
2330             }
2331 
2332             x = 160 - M_StringWidth(string) / 2;
2333             M_WriteText(x, y, string);
2334             y += SHORT(hu_font[0]->height);
2335         }
2336 
2337         return;
2338     }
2339 
2340     if (!menuactive)
2341         return;
2342 
2343     if (currentMenu->routine)
2344         currentMenu->routine();         // call Draw routine
2345 
2346     // DRAW MENU
2347     x = currentMenu->x;
2348     y = currentMenu->y;
2349     max = currentMenu->numitems;
2350 
2351     for (i=0;i<max;i++)
2352     {
2353         name = DEH_String(currentMenu->menuitems[i].name);
2354 
2355         if (name[0])
2356         {
2357             V_DrawPatchDirect (x, y, W_CacheLumpName(name, PU_CACHE));
2358         }
2359         y += LINEHEIGHT;
2360     }
2361 
2362 
2363     // haleyjd 08/27/10: [STRIFE] Adjust to draw spinning Sigil
2364     // DRAW SIGIL
2365     V_DrawPatchDirect(x + CURSORXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT,
2366                       W_CacheLumpName(DEH_String(cursorName[whichCursor]),
2367                                       PU_CACHE));
2368 
2369 }
2370 
2371 
2372 //
2373 // M_ClearMenus
2374 //
2375 // haleyjd 08/28/10: [STRIFE] Added an int param so this can be called by menus.
2376 //         09/08/10: Added menupause.
2377 //
M_ClearMenus(int choice)2378 void M_ClearMenus (int choice)
2379 {
2380     choice = 0;     // haleyjd: for no warning; not from decompilation.
2381     menuactive = 0;
2382     menupause = 0;
2383 }
2384 
2385 
2386 
2387 
2388 //
2389 // M_SetupNextMenu
2390 //
M_SetupNextMenu(menu_t * menudef)2391 void M_SetupNextMenu(menu_t *menudef)
2392 {
2393     currentMenu = menudef;
2394     itemOn = currentMenu->lastOn;
2395 }
2396 
2397 
2398 //
2399 // M_Ticker
2400 //
2401 // haleyjd 08/27/10: [STRIFE] Rewritten for Sigil cursor
2402 //
M_Ticker(void)2403 void M_Ticker (void)
2404 {
2405     if (--cursorAnimCounter <= 0)
2406     {
2407         whichCursor = (whichCursor + 1) % 8;
2408         cursorAnimCounter = 5;
2409     }
2410 }
2411 
2412 
2413 //
2414 // M_Init
2415 //
2416 // haleyjd 08/27/10: [STRIFE] Removed DOOM gamemode stuff
2417 //
M_Init(void)2418 void M_Init (void)
2419 {
2420     currentMenu = &MainDef;
2421     menuactive = 0;
2422     itemOn = currentMenu->lastOn;
2423     whichCursor = 0;
2424     cursorAnimCounter = 10;
2425     screenSize = screenblocks - 3;
2426     messageToPrint = 0;
2427     messageString = NULL;
2428     messageLastMenuActive = menuactive; // STRIFE-FIXME: assigns 0 here...
2429     quickSaveSlot = -1;
2430 
2431     // [STRIFE]: Initialize savegame paths and clear temporary directory
2432     G_WriteSaveName(5, "ME");
2433     ClearTmp();
2434 
2435     // Here we could catch other version dependencies,
2436     //  like HELP1/2, and four episodes.
2437 }
2438 
2439