Add key bindings for pause, message refresh.
[chocolate-doom.git] / src / m_menu.c
blob1ced1d16e94903bc7ac8c2a66eb5cee3dc3b16eb
1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // Copyright(C) 1993-1996 Id Software, Inc.
5 // Copyright(C) 2005 Simon Howard
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 // 02111-1307, USA.
22 // DESCRIPTION:
23 // DOOM selection menu, options, episode etc.
24 // Sliders and icons. Kinda widget stuff.
26 //-----------------------------------------------------------------------------
29 #include <stdlib.h>
30 #include <ctype.h>
33 #include "doomdef.h"
34 #include "dstrings.h"
36 #include "d_main.h"
37 #include "deh_main.h"
39 #include "i_swap.h"
40 #include "i_system.h"
41 #include "i_timer.h"
42 #include "i_video.h"
43 #include "z_zone.h"
44 #include "v_video.h"
45 #include "w_wad.h"
47 #include "r_local.h"
50 #include "hu_stuff.h"
52 #include "g_game.h"
54 #include "m_argv.h"
55 #include "p_saveg.h"
57 #include "s_sound.h"
59 #include "doomstat.h"
61 // Data.
62 #include "sounds.h"
64 #include "m_menu.h"
67 extern void M_QuitDOOM(int);
69 extern patch_t* hu_font[HU_FONTSIZE];
70 extern boolean message_dontfuckwithme;
72 extern boolean chat_on; // in heads-up code
75 // menu keys:
78 int key_menu_activate = KEY_ESCAPE;
79 int key_menu_up = KEY_UPARROW;
80 int key_menu_down = KEY_DOWNARROW;
81 int key_menu_left = KEY_LEFTARROW;
82 int key_menu_right = KEY_RIGHTARROW;
83 int key_menu_back = KEY_BACKSPACE;
84 int key_menu_forward = KEY_ENTER;
85 int key_menu_confirm = 'y';
86 int key_menu_abort = 'n';
88 int key_menu_help = KEY_F1;
89 int key_menu_save = KEY_F2;
90 int key_menu_load = KEY_F3;
91 int key_menu_volume = KEY_F4;
92 int key_menu_detail = KEY_F5;
93 int key_menu_qsave = KEY_F6;
94 int key_menu_endgame = KEY_F7;
95 int key_menu_messages = KEY_F8;
96 int key_menu_qload = KEY_F9;
97 int key_menu_quit = KEY_F10;
98 int key_menu_gamma = KEY_F11;
100 int key_menu_incscreen = KEY_EQUALS;
101 int key_menu_decscreen = KEY_MINUS;
104 // defaulted values
106 int mouseSensitivity = 5;
108 // Show messages has default, 0 = off, 1 = on
109 int showMessages = 1;
112 // Blocky mode, has default, 0 = high, 1 = normal
113 int detailLevel = 0;
114 int screenblocks = 9;
116 // temp for screenblocks (0-9)
117 int screenSize;
119 // -1 = no quicksave slot picked!
120 int quickSaveSlot;
122 // 1 = message to be printed
123 int messageToPrint;
124 // ...and here is the message string!
125 char* messageString;
127 // message x & y
128 int messx;
129 int messy;
130 int messageLastMenuActive;
132 // timed message = no input from user
133 boolean messageNeedsInput;
135 void (*messageRoutine)(int response);
137 char gammamsg[5][26] =
139 GAMMALVL0,
140 GAMMALVL1,
141 GAMMALVL2,
142 GAMMALVL3,
143 GAMMALVL4
146 // we are going to be entering a savegame string
147 int saveStringEnter;
148 int saveSlot; // which slot to save in
149 int saveCharIndex; // which char we're editing
150 // old save description before edit
151 char saveOldString[SAVESTRINGSIZE];
153 boolean inhelpscreens;
154 boolean menuactive;
156 #define SKULLXOFF -32
157 #define LINEHEIGHT 16
159 extern boolean sendpause;
160 char savegamestrings[10][SAVESTRINGSIZE];
162 char endstring[160];
166 // MENU TYPEDEFS
168 typedef struct
170 // 0 = no cursor here, 1 = ok, 2 = arrows ok
171 short status;
173 char name[10];
175 // choice = menu item #.
176 // if status = 2,
177 // choice=0:leftarrow,1:rightarrow
178 void (*routine)(int choice);
180 // hotkey in menu
181 char alphaKey;
182 } menuitem_t;
186 typedef struct menu_s
188 short numitems; // # of menu items
189 struct menu_s* prevMenu; // previous menu
190 menuitem_t* menuitems; // menu items
191 void (*routine)(); // draw routine
192 short x;
193 short y; // x,y of menu
194 short lastOn; // last item user was on in menu
195 } menu_t;
197 short itemOn; // menu item skull is on
198 short skullAnimCounter; // skull animation counter
199 short whichSkull; // which skull to draw
201 // graphic name of skulls
202 // warning: initializer-string for array of chars is too long
203 char *skullName[2] = {"M_SKULL1","M_SKULL2"};
205 // current menudef
206 menu_t* currentMenu;
209 // PROTOTYPES
211 void M_NewGame(int choice);
212 void M_Episode(int choice);
213 void M_ChooseSkill(int choice);
214 void M_LoadGame(int choice);
215 void M_SaveGame(int choice);
216 void M_Options(int choice);
217 void M_EndGame(int choice);
218 void M_ReadThis(int choice);
219 void M_ReadThis2(int choice);
220 void M_QuitDOOM(int choice);
222 void M_ChangeMessages(int choice);
223 void M_ChangeSensitivity(int choice);
224 void M_SfxVol(int choice);
225 void M_MusicVol(int choice);
226 void M_ChangeDetail(int choice);
227 void M_SizeDisplay(int choice);
228 void M_StartGame(int choice);
229 void M_Sound(int choice);
231 void M_FinishReadThis(int choice);
232 void M_LoadSelect(int choice);
233 void M_SaveSelect(int choice);
234 void M_ReadSaveStrings(void);
235 void M_QuickSave(void);
236 void M_QuickLoad(void);
238 void M_DrawMainMenu(void);
239 void M_DrawReadThis1(void);
240 void M_DrawReadThis2(void);
241 void M_DrawNewGame(void);
242 void M_DrawEpisode(void);
243 void M_DrawOptions(void);
244 void M_DrawSound(void);
245 void M_DrawLoad(void);
246 void M_DrawSave(void);
248 void M_DrawSaveLoadBorder(int x,int y);
249 void M_SetupNextMenu(menu_t *menudef);
250 void M_DrawThermo(int x,int y,int thermWidth,int thermDot);
251 void M_DrawEmptyCell(menu_t *menu,int item);
252 void M_DrawSelCell(menu_t *menu,int item);
253 void M_WriteText(int x, int y, char *string);
254 int M_StringWidth(char *string);
255 int M_StringHeight(char *string);
256 void M_StartControlPanel(void);
257 void M_StartMessage(char *string,void *routine,boolean input);
258 void M_StopMessage(void);
259 void M_ClearMenus (void);
265 // DOOM MENU
267 enum
269 newgame = 0,
270 options,
271 loadgame,
272 savegame,
273 readthis,
274 quitdoom,
275 main_end
276 } main_e;
278 menuitem_t MainMenu[]=
280 {1,"M_NGAME",M_NewGame,'n'},
281 {1,"M_OPTION",M_Options,'o'},
282 {1,"M_LOADG",M_LoadGame,'l'},
283 {1,"M_SAVEG",M_SaveGame,'s'},
284 // Another hickup with Special edition.
285 {1,"M_RDTHIS",M_ReadThis,'r'},
286 {1,"M_QUITG",M_QuitDOOM,'q'}
289 menu_t MainDef =
291 main_end,
292 NULL,
293 MainMenu,
294 M_DrawMainMenu,
295 97,64,
301 // EPISODE SELECT
303 enum
305 ep1,
306 ep2,
307 ep3,
308 ep4,
309 ep_end
310 } episodes_e;
312 menuitem_t EpisodeMenu[]=
314 {1,"M_EPI1", M_Episode,'k'},
315 {1,"M_EPI2", M_Episode,'t'},
316 {1,"M_EPI3", M_Episode,'i'},
317 {1,"M_EPI4", M_Episode,'t'}
320 menu_t EpiDef =
322 ep_end, // # of menu items
323 &MainDef, // previous menu
324 EpisodeMenu, // menuitem_t ->
325 M_DrawEpisode, // drawing routine ->
326 48,63, // x,y
327 ep1 // lastOn
331 // NEW GAME
333 enum
335 killthings,
336 toorough,
337 hurtme,
338 violence,
339 nightmare,
340 newg_end
341 } newgame_e;
343 menuitem_t NewGameMenu[]=
345 {1,"M_JKILL", M_ChooseSkill, 'i'},
346 {1,"M_ROUGH", M_ChooseSkill, 'h'},
347 {1,"M_HURT", M_ChooseSkill, 'h'},
348 {1,"M_ULTRA", M_ChooseSkill, 'u'},
349 {1,"M_NMARE", M_ChooseSkill, 'n'}
352 menu_t NewDef =
354 newg_end, // # of menu items
355 &EpiDef, // previous menu
356 NewGameMenu, // menuitem_t ->
357 M_DrawNewGame, // drawing routine ->
358 48,63, // x,y
359 hurtme // lastOn
365 // OPTIONS MENU
367 enum
369 endgame,
370 messages,
371 detail,
372 scrnsize,
373 option_empty1,
374 mousesens,
375 option_empty2,
376 soundvol,
377 opt_end
378 } options_e;
380 menuitem_t OptionsMenu[]=
382 {1,"M_ENDGAM", M_EndGame,'e'},
383 {1,"M_MESSG", M_ChangeMessages,'m'},
384 {1,"M_DETAIL", M_ChangeDetail,'g'},
385 {2,"M_SCRNSZ", M_SizeDisplay,'s'},
386 {-1,"",0,'\0'},
387 {2,"M_MSENS", M_ChangeSensitivity,'m'},
388 {-1,"",0,'\0'},
389 {1,"M_SVOL", M_Sound,'s'}
392 menu_t OptionsDef =
394 opt_end,
395 &MainDef,
396 OptionsMenu,
397 M_DrawOptions,
398 60,37,
403 // Read This! MENU 1 & 2
405 enum
407 rdthsempty1,
408 read1_end
409 } read_e;
411 menuitem_t ReadMenu1[] =
413 {1,"",M_ReadThis2,0}
416 menu_t ReadDef1 =
418 read1_end,
419 &MainDef,
420 ReadMenu1,
421 M_DrawReadThis1,
422 280,185,
426 enum
428 rdthsempty2,
429 read2_end
430 } read_e2;
432 menuitem_t ReadMenu2[]=
434 {1,"",M_FinishReadThis,0}
437 menu_t ReadDef2 =
439 read2_end,
440 &ReadDef1,
441 ReadMenu2,
442 M_DrawReadThis2,
443 330,175,
448 // SOUND VOLUME MENU
450 enum
452 sfx_vol,
453 sfx_empty1,
454 music_vol,
455 sfx_empty2,
456 sound_end
457 } sound_e;
459 menuitem_t SoundMenu[]=
461 {2,"M_SFXVOL",M_SfxVol,'s'},
462 {-1,"",0,'\0'},
463 {2,"M_MUSVOL",M_MusicVol,'m'},
464 {-1,"",0,'\0'}
467 menu_t SoundDef =
469 sound_end,
470 &OptionsDef,
471 SoundMenu,
472 M_DrawSound,
473 80,64,
478 // LOAD GAME MENU
480 enum
482 load1,
483 load2,
484 load3,
485 load4,
486 load5,
487 load6,
488 load_end
489 } load_e;
491 menuitem_t LoadMenu[]=
493 {1,"", M_LoadSelect,'1'},
494 {1,"", M_LoadSelect,'2'},
495 {1,"", M_LoadSelect,'3'},
496 {1,"", M_LoadSelect,'4'},
497 {1,"", M_LoadSelect,'5'},
498 {1,"", M_LoadSelect,'6'}
501 menu_t LoadDef =
503 load_end,
504 &MainDef,
505 LoadMenu,
506 M_DrawLoad,
507 80,54,
512 // SAVE GAME MENU
514 menuitem_t SaveMenu[]=
516 {1,"", M_SaveSelect,'1'},
517 {1,"", M_SaveSelect,'2'},
518 {1,"", M_SaveSelect,'3'},
519 {1,"", M_SaveSelect,'4'},
520 {1,"", M_SaveSelect,'5'},
521 {1,"", M_SaveSelect,'6'}
524 menu_t SaveDef =
526 load_end,
527 &MainDef,
528 SaveMenu,
529 M_DrawSave,
530 80,54,
536 // M_ReadSaveStrings
537 // read the strings from the savegame files
539 void M_ReadSaveStrings(void)
541 FILE *handle;
542 int count;
543 int i;
544 char name[256];
546 for (i = 0;i < load_end;i++)
548 strcpy(name, P_SaveGameFile(i));
550 handle = fopen(name, "rb");
551 if (handle == NULL)
553 strcpy(&savegamestrings[i][0], EMPTYSTRING);
554 LoadMenu[i].status = 0;
555 continue;
557 count = fread(&savegamestrings[i], 1, SAVESTRINGSIZE, handle);
558 fclose(handle);
559 LoadMenu[i].status = 1;
565 // M_LoadGame & Cie.
567 void M_DrawLoad(void)
569 int i;
571 V_DrawPatchDirect (72,28,0,W_CacheLumpName(DEH_String("M_LOADG"),PU_CACHE));
572 for (i = 0;i < load_end; i++)
574 M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
575 M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
582 // Draw border for the savegame description
584 void M_DrawSaveLoadBorder(int x,int y)
586 int i;
588 V_DrawPatchDirect (x-8,y+7,0,W_CacheLumpName(DEH_String("M_LSLEFT"),PU_CACHE));
590 for (i = 0;i < 24;i++)
592 V_DrawPatchDirect (x,y+7,0,W_CacheLumpName(DEH_String("M_LSCNTR"),PU_CACHE));
593 x += 8;
596 V_DrawPatchDirect (x,y+7,0,W_CacheLumpName(DEH_String("M_LSRGHT"),PU_CACHE));
602 // User wants to load this game
604 void M_LoadSelect(int choice)
606 char name[256];
608 strcpy(name, P_SaveGameFile(choice));
610 G_LoadGame (name);
611 M_ClearMenus ();
615 // Selected from DOOM menu
617 void M_LoadGame (int choice)
619 if (netgame)
621 M_StartMessage(DEH_String(LOADNET),NULL,false);
622 return;
625 M_SetupNextMenu(&LoadDef);
626 M_ReadSaveStrings();
631 // M_SaveGame & Cie.
633 void M_DrawSave(void)
635 int i;
637 V_DrawPatchDirect (72,28,0,W_CacheLumpName(DEH_String("M_SAVEG"),PU_CACHE));
638 for (i = 0;i < load_end; i++)
640 M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
641 M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
644 if (saveStringEnter)
646 i = M_StringWidth(savegamestrings[saveSlot]);
647 M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*saveSlot,"_");
652 // M_Responder calls this when user is finished
654 void M_DoSave(int slot)
656 G_SaveGame (slot,savegamestrings[slot]);
657 M_ClearMenus ();
659 // PICK QUICKSAVE SLOT YET?
660 if (quickSaveSlot == -2)
661 quickSaveSlot = slot;
665 // User wants to save. Start string input for M_Responder
667 void M_SaveSelect(int choice)
669 // we are going to be intercepting all chars
670 saveStringEnter = 1;
672 saveSlot = choice;
673 strcpy(saveOldString,savegamestrings[choice]);
674 if (!strcmp(savegamestrings[choice],EMPTYSTRING))
675 savegamestrings[choice][0] = 0;
676 saveCharIndex = strlen(savegamestrings[choice]);
680 // Selected from DOOM menu
682 void M_SaveGame (int choice)
684 if (!usergame)
686 M_StartMessage(DEH_String(SAVEDEAD),NULL,false);
687 return;
690 if (gamestate != GS_LEVEL)
691 return;
693 M_SetupNextMenu(&SaveDef);
694 M_ReadSaveStrings();
700 // M_QuickSave
702 char tempstring[80];
704 void M_QuickSaveResponse(int key)
706 if (key == key_menu_confirm)
708 M_DoSave(quickSaveSlot);
709 S_StartSound(NULL,sfx_swtchx);
713 void M_QuickSave(void)
715 if (!usergame)
717 S_StartSound(NULL,sfx_oof);
718 return;
721 if (gamestate != GS_LEVEL)
722 return;
724 if (quickSaveSlot < 0)
726 M_StartControlPanel();
727 M_ReadSaveStrings();
728 M_SetupNextMenu(&SaveDef);
729 quickSaveSlot = -2; // means to pick a slot now
730 return;
732 sprintf(tempstring,DEH_String(QSPROMPT),savegamestrings[quickSaveSlot]);
733 M_StartMessage(tempstring,M_QuickSaveResponse,true);
739 // M_QuickLoad
741 void M_QuickLoadResponse(int key)
743 if (key == key_menu_confirm)
745 M_LoadSelect(quickSaveSlot);
746 S_StartSound(NULL,sfx_swtchx);
751 void M_QuickLoad(void)
753 if (netgame)
755 M_StartMessage(DEH_String(QLOADNET),NULL,false);
756 return;
759 if (quickSaveSlot < 0)
761 M_StartMessage(DEH_String(QSAVESPOT),NULL,false);
762 return;
764 sprintf(tempstring,DEH_String(QLPROMPT),savegamestrings[quickSaveSlot]);
765 M_StartMessage(tempstring,M_QuickLoadResponse,true);
772 // Read This Menus
773 // Had a "quick hack to fix romero bug"
775 void M_DrawReadThis1(void)
777 char *lumpname = "CREDIT";
778 int skullx = 330, skully = 175;
780 inhelpscreens = true;
782 // Different versions of Doom 1.9 work differently
784 switch (gameversion)
786 case exe_doom_1_9:
787 if (gamemode == commercial)
789 // Doom 2
791 lumpname = "HELP";
793 skullx = 330;
794 skully = 165;
796 else
798 // Doom 1
799 // HELP2 is the first screen shown in Doom 1
801 lumpname = "HELP2";
803 skullx = 280;
804 skully = 185;
806 break;
808 case exe_ultimate:
809 case exe_chex:
811 // Ultimate Doom always displays "HELP1".
813 // Chex Quest version also uses "HELP1", even though it is based
814 // on Final Doom.
816 lumpname = "HELP1";
818 break;
820 case exe_final:
822 // Final Doom always displays "HELP".
824 lumpname = "HELP";
826 break;
829 lumpname = DEH_String(lumpname);
831 V_DrawPatchDirect (0, 0, 0, W_CacheLumpName(lumpname, PU_CACHE));
833 ReadDef1.x = skullx;
834 ReadDef1.y = skully;
840 // Read This Menus - optional second page.
842 void M_DrawReadThis2(void)
844 inhelpscreens = true;
846 // We only ever draw the second page if this is
847 // gameversion == exe_doom_1_9 and gamemode == registered
849 V_DrawPatchDirect(0, 0, 0, W_CacheLumpName(DEH_String("HELP1"), PU_CACHE));
854 // Change Sfx & Music volumes
856 void M_DrawSound(void)
858 V_DrawPatchDirect (60,38,0,W_CacheLumpName(DEH_String("M_SVOL"),PU_CACHE));
860 M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_vol+1),
861 16,sfxVolume);
863 M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(music_vol+1),
864 16,musicVolume);
867 void M_Sound(int choice)
869 M_SetupNextMenu(&SoundDef);
872 void M_SfxVol(int choice)
874 switch(choice)
876 case 0:
877 if (sfxVolume)
878 sfxVolume--;
879 break;
880 case 1:
881 if (sfxVolume < 15)
882 sfxVolume++;
883 break;
886 S_SetSfxVolume(sfxVolume * 8);
889 void M_MusicVol(int choice)
891 switch(choice)
893 case 0:
894 if (musicVolume)
895 musicVolume--;
896 break;
897 case 1:
898 if (musicVolume < 15)
899 musicVolume++;
900 break;
903 S_SetMusicVolume(musicVolume * 8);
910 // M_DrawMainMenu
912 void M_DrawMainMenu(void)
914 V_DrawPatchDirect (94,2,0,W_CacheLumpName(DEH_String("M_DOOM"),PU_CACHE));
921 // M_NewGame
923 void M_DrawNewGame(void)
925 V_DrawPatchDirect (96,14,0,W_CacheLumpName(DEH_String("M_NEWG"),PU_CACHE));
926 V_DrawPatchDirect (54,38,0,W_CacheLumpName(DEH_String("M_SKILL"),PU_CACHE));
929 void M_NewGame(int choice)
931 if (netgame && !demoplayback)
933 M_StartMessage(DEH_String(NEWGAME),NULL,false);
934 return;
937 // Chex Quest disabled the episode select screen, as did Doom II.
939 if (gamemode == commercial || gameversion == exe_chex)
940 M_SetupNextMenu(&NewDef);
941 else
942 M_SetupNextMenu(&EpiDef);
947 // M_Episode
949 int epi;
951 void M_DrawEpisode(void)
953 V_DrawPatchDirect (54,38,0,W_CacheLumpName(DEH_String("M_EPISOD"),PU_CACHE));
956 void M_VerifyNightmare(int key)
958 if (key != key_menu_confirm)
959 return;
961 G_DeferedInitNew(nightmare,epi+1,1);
962 M_ClearMenus ();
965 void M_ChooseSkill(int choice)
967 if (choice == nightmare)
969 M_StartMessage(DEH_String(NIGHTMARE),M_VerifyNightmare,true);
970 return;
973 G_DeferedInitNew(choice,epi+1,1);
974 M_ClearMenus ();
977 void M_Episode(int choice)
979 if ( (gamemode == shareware)
980 && choice)
982 M_StartMessage(DEH_String(SWSTRING),NULL,false);
983 M_SetupNextMenu(&ReadDef1);
984 return;
987 // Yet another hack...
988 if ( (gamemode == registered)
989 && (choice > 2))
991 fprintf( stderr,
992 "M_Episode: 4th episode requires UltimateDOOM\n");
993 choice = 0;
996 epi = choice;
997 M_SetupNextMenu(&NewDef);
1003 // M_Options
1005 char detailNames[2][9] = {"M_GDHIGH","M_GDLOW"};
1006 char msgNames[2][9] = {"M_MSGOFF","M_MSGON"};
1009 void M_DrawOptions(void)
1011 V_DrawPatchDirect (108,15,0,W_CacheLumpName(DEH_String("M_OPTTTL"),PU_CACHE));
1013 V_DrawPatchDirect (OptionsDef.x + 175,OptionsDef.y+LINEHEIGHT*detail,0,
1014 W_CacheLumpName(DEH_String(detailNames[detailLevel]),
1015 PU_CACHE));
1017 V_DrawPatchDirect (OptionsDef.x + 120,OptionsDef.y+LINEHEIGHT*messages,0,
1018 W_CacheLumpName(DEH_String(msgNames[showMessages]),
1019 PU_CACHE));
1021 M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(mousesens+1),
1022 10,mouseSensitivity);
1024 M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(scrnsize+1),
1025 9,screenSize);
1028 void M_Options(int choice)
1030 M_SetupNextMenu(&OptionsDef);
1036 // Toggle messages on/off
1038 void M_ChangeMessages(int choice)
1040 // warning: unused parameter `int choice'
1041 choice = 0;
1042 showMessages = 1 - showMessages;
1044 if (!showMessages)
1045 players[consoleplayer].message = DEH_String(MSGOFF);
1046 else
1047 players[consoleplayer].message = DEH_String(MSGON);
1049 message_dontfuckwithme = true;
1054 // M_EndGame
1056 void M_EndGameResponse(int key)
1058 if (key != key_menu_confirm)
1059 return;
1061 currentMenu->lastOn = itemOn;
1062 M_ClearMenus ();
1063 D_StartTitle ();
1066 void M_EndGame(int choice)
1068 choice = 0;
1069 if (!usergame)
1071 S_StartSound(NULL,sfx_oof);
1072 return;
1075 if (netgame)
1077 M_StartMessage(DEH_String(NETEND),NULL,false);
1078 return;
1081 M_StartMessage(DEH_String(ENDGAME),M_EndGameResponse,true);
1088 // M_ReadThis
1090 void M_ReadThis(int choice)
1092 choice = 0;
1093 M_SetupNextMenu(&ReadDef1);
1096 void M_ReadThis2(int choice)
1098 // Doom 1.9 had two menus when playing Doom 1
1099 // All others had only one
1101 if (gameversion == exe_doom_1_9 && gamemode != commercial)
1103 choice = 0;
1104 M_SetupNextMenu(&ReadDef2);
1106 else
1108 // Close the menu
1110 M_FinishReadThis(0);
1114 void M_FinishReadThis(int choice)
1116 choice = 0;
1117 M_SetupNextMenu(&MainDef);
1124 // M_QuitDOOM
1126 int quitsounds[8] =
1128 sfx_pldeth,
1129 sfx_dmpain,
1130 sfx_popain,
1131 sfx_slop,
1132 sfx_telept,
1133 sfx_posit1,
1134 sfx_posit3,
1135 sfx_sgtatk
1138 int quitsounds2[8] =
1140 sfx_vilact,
1141 sfx_getpow,
1142 sfx_boscub,
1143 sfx_slop,
1144 sfx_skeswg,
1145 sfx_kntdth,
1146 sfx_bspact,
1147 sfx_sgtatk
1152 void M_QuitResponse(int key)
1154 if (key != key_menu_confirm)
1155 return;
1156 if (!netgame)
1158 if (gamemode == commercial)
1159 S_StartSound(NULL,quitsounds2[(gametic>>2)&7]);
1160 else
1161 S_StartSound(NULL,quitsounds[(gametic>>2)&7]);
1162 I_WaitVBL(105);
1164 I_Quit ();
1168 static char *M_SelectEndMessage(void)
1170 char **endmsg;
1172 if (gamemission == doom)
1174 // Doom 1
1176 endmsg = doom1_endmsg;
1178 else
1180 // Doom 2
1182 endmsg = doom2_endmsg;
1185 return endmsg[gametic % NUM_QUITMESSAGES];
1189 void M_QuitDOOM(int choice)
1191 sprintf(endstring,
1192 DEH_String("%s\n\n" DOSY),
1193 DEH_String(M_SelectEndMessage()));
1195 M_StartMessage(endstring,M_QuitResponse,true);
1201 void M_ChangeSensitivity(int choice)
1203 switch(choice)
1205 case 0:
1206 if (mouseSensitivity)
1207 mouseSensitivity--;
1208 break;
1209 case 1:
1210 if (mouseSensitivity < 9)
1211 mouseSensitivity++;
1212 break;
1219 void M_ChangeDetail(int choice)
1221 choice = 0;
1222 detailLevel = 1 - detailLevel;
1224 R_SetViewSize (screenblocks, detailLevel);
1226 if (!detailLevel)
1227 players[consoleplayer].message = DEH_String(DETAILHI);
1228 else
1229 players[consoleplayer].message = DEH_String(DETAILLO);
1235 void M_SizeDisplay(int choice)
1237 switch(choice)
1239 case 0:
1240 if (screenSize > 0)
1242 screenblocks--;
1243 screenSize--;
1245 break;
1246 case 1:
1247 if (screenSize < 8)
1249 screenblocks++;
1250 screenSize++;
1252 break;
1256 R_SetViewSize (screenblocks, detailLevel);
1263 // Menu Functions
1265 void
1266 M_DrawThermo
1267 ( int x,
1268 int y,
1269 int thermWidth,
1270 int thermDot )
1272 int xx;
1273 int i;
1275 xx = x;
1276 V_DrawPatchDirect (xx,y,0,W_CacheLumpName(DEH_String("M_THERML"),PU_CACHE));
1277 xx += 8;
1278 for (i=0;i<thermWidth;i++)
1280 V_DrawPatchDirect (xx,y,0,W_CacheLumpName(DEH_String("M_THERMM"),PU_CACHE));
1281 xx += 8;
1283 V_DrawPatchDirect (xx,y,0,W_CacheLumpName(DEH_String("M_THERMR"),PU_CACHE));
1285 V_DrawPatchDirect ((x+8) + thermDot*8,y,
1286 0,W_CacheLumpName(DEH_String("M_THERMO"),PU_CACHE));
1291 void
1292 M_DrawEmptyCell
1293 ( menu_t* menu,
1294 int item )
1296 V_DrawPatchDirect (menu->x - 10, menu->y+item*LINEHEIGHT - 1, 0,
1297 W_CacheLumpName(DEH_String("M_CELL1"),PU_CACHE));
1300 void
1301 M_DrawSelCell
1302 ( menu_t* menu,
1303 int item )
1305 V_DrawPatchDirect (menu->x - 10, menu->y+item*LINEHEIGHT - 1, 0,
1306 W_CacheLumpName(DEH_String("M_CELL2"),PU_CACHE));
1310 void
1311 M_StartMessage
1312 ( char* string,
1313 void* routine,
1314 boolean input )
1316 messageLastMenuActive = menuactive;
1317 messageToPrint = 1;
1318 messageString = string;
1319 messageRoutine = routine;
1320 messageNeedsInput = input;
1321 menuactive = true;
1322 return;
1327 void M_StopMessage(void)
1329 menuactive = messageLastMenuActive;
1330 messageToPrint = 0;
1336 // Find string width from hu_font chars
1338 int M_StringWidth(char* string)
1340 size_t i;
1341 int w = 0;
1342 int c;
1344 for (i = 0;i < strlen(string);i++)
1346 c = toupper(string[i]) - HU_FONTSTART;
1347 if (c < 0 || c >= HU_FONTSIZE)
1348 w += 4;
1349 else
1350 w += SHORT (hu_font[c]->width);
1353 return w;
1359 // Find string height from hu_font chars
1361 int M_StringHeight(char* string)
1363 size_t i;
1364 int h;
1365 int height = SHORT(hu_font[0]->height);
1367 h = height;
1368 for (i = 0;i < strlen(string);i++)
1369 if (string[i] == '\n')
1370 h += height;
1372 return h;
1377 // Write a string using the hu_font
1379 void
1380 M_WriteText
1381 ( int x,
1382 int y,
1383 char* string)
1385 int w;
1386 char* ch;
1387 int c;
1388 int cx;
1389 int cy;
1392 ch = string;
1393 cx = x;
1394 cy = y;
1396 while(1)
1398 c = *ch++;
1399 if (!c)
1400 break;
1401 if (c == '\n')
1403 cx = x;
1404 cy += 12;
1405 continue;
1408 c = toupper(c) - HU_FONTSTART;
1409 if (c < 0 || c>= HU_FONTSIZE)
1411 cx += 4;
1412 continue;
1415 w = SHORT (hu_font[c]->width);
1416 if (cx+w > SCREENWIDTH)
1417 break;
1418 V_DrawPatchDirect(cx, cy, 0, hu_font[c]);
1419 cx+=w;
1426 // CONTROL PANEL
1430 // M_Responder
1432 boolean M_Responder (event_t* ev)
1434 int ch;
1435 int key;
1436 int i;
1437 static int joywait = 0;
1438 static int mousewait = 0;
1439 static int mousey = 0;
1440 static int lasty = 0;
1441 static int mousex = 0;
1442 static int lastx = 0;
1444 // key is the key pressed, ch is the actual character typed
1446 ch = 0;
1447 key = -1;
1449 if (ev->type == ev_joystick && joywait < I_GetTime())
1451 if (ev->data3 == -1)
1453 key = key_menu_up;
1454 joywait = I_GetTime() + 5;
1456 else if (ev->data3 == 1)
1458 key = key_menu_down;
1459 joywait = I_GetTime() + 5;
1462 if (ev->data2 == -1)
1464 key = key_menu_left;
1465 joywait = I_GetTime() + 2;
1467 else if (ev->data2 == 1)
1469 key = key_menu_right;
1470 joywait = I_GetTime() + 2;
1473 if (ev->data1&1)
1475 key = key_menu_forward;
1476 joywait = I_GetTime() + 5;
1478 if (ev->data1&2)
1480 key = key_menu_back;
1481 joywait = I_GetTime() + 5;
1484 else
1486 if (ev->type == ev_mouse && mousewait < I_GetTime())
1488 mousey += ev->data3;
1489 if (mousey < lasty-30)
1491 key = key_menu_down;
1492 mousewait = I_GetTime() + 5;
1493 mousey = lasty -= 30;
1495 else if (mousey > lasty+30)
1497 key = key_menu_up;
1498 mousewait = I_GetTime() + 5;
1499 mousey = lasty += 30;
1502 mousex += ev->data2;
1503 if (mousex < lastx-30)
1505 key = key_menu_left;
1506 mousewait = I_GetTime() + 5;
1507 mousex = lastx -= 30;
1509 else if (mousex > lastx+30)
1511 key = key_menu_right;
1512 mousewait = I_GetTime() + 5;
1513 mousex = lastx += 30;
1516 if (ev->data1&1)
1518 key = key_menu_forward;
1519 mousewait = I_GetTime() + 15;
1522 if (ev->data1&2)
1524 key = key_menu_back;
1525 mousewait = I_GetTime() + 15;
1528 else
1530 if (ev->type == ev_keydown)
1532 key = ev->data1;
1533 ch = ev->data2;
1538 if (key == -1)
1539 return false;
1541 // In testcontrols mode, none of the function keys should do anything
1542 // - the only key is escape to quit.
1544 if (testcontrols)
1546 if (key == key_menu_activate || key == key_menu_quit)
1548 I_Quit();
1549 return true;
1552 return false;
1555 // Save Game string input
1556 if (saveStringEnter)
1558 switch(key)
1560 case KEY_BACKSPACE:
1561 if (saveCharIndex > 0)
1563 saveCharIndex--;
1564 savegamestrings[saveSlot][saveCharIndex] = 0;
1566 break;
1568 case KEY_ESCAPE:
1569 saveStringEnter = 0;
1570 strcpy(&savegamestrings[saveSlot][0],saveOldString);
1571 break;
1573 case KEY_ENTER:
1574 saveStringEnter = 0;
1575 if (savegamestrings[saveSlot][0])
1576 M_DoSave(saveSlot);
1577 break;
1579 default:
1580 // Entering a character - use the 'ch' value, not the key
1582 ch = toupper(ch);
1584 if (ch != ' '
1585 && (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE))
1587 break;
1590 if (ch >= 32 && ch <= 127 &&
1591 saveCharIndex < SAVESTRINGSIZE-1 &&
1592 M_StringWidth(savegamestrings[saveSlot]) <
1593 (SAVESTRINGSIZE-2)*8)
1595 savegamestrings[saveSlot][saveCharIndex++] = ch;
1596 savegamestrings[saveSlot][saveCharIndex] = 0;
1598 break;
1600 return true;
1603 // Take care of any messages that need input
1604 if (messageToPrint)
1606 if (messageNeedsInput)
1608 if (key != ' ' && key != KEY_ESCAPE
1609 && key != key_menu_confirm && key != key_menu_abort)
1611 return false;
1615 menuactive = messageLastMenuActive;
1616 messageToPrint = 0;
1617 if (messageRoutine)
1618 messageRoutine(key);
1620 menuactive = false;
1621 S_StartSound(NULL,sfx_swtchx);
1622 return true;
1625 if (devparm && key == key_menu_help)
1627 G_ScreenShot ();
1628 return true;
1631 // F-Keys
1632 if (!menuactive)
1634 if (key == key_menu_decscreen) // Screen size down
1636 if (automapactive || chat_on)
1637 return false;
1638 M_SizeDisplay(0);
1639 S_StartSound(NULL,sfx_stnmov);
1640 return true;
1642 else if (key == key_menu_incscreen) // Screen size up
1644 if (automapactive || chat_on)
1645 return false;
1646 M_SizeDisplay(1);
1647 S_StartSound(NULL,sfx_stnmov);
1648 return true;
1650 else if (key == key_menu_help) // Help key
1652 M_StartControlPanel ();
1654 if ( gamemode == retail )
1655 currentMenu = &ReadDef2;
1656 else
1657 currentMenu = &ReadDef1;
1659 itemOn = 0;
1660 S_StartSound(NULL,sfx_swtchn);
1661 return true;
1663 else if (key == key_menu_save) // Save
1665 M_StartControlPanel();
1666 S_StartSound(NULL,sfx_swtchn);
1667 M_SaveGame(0);
1668 return true;
1670 else if (key == key_menu_load) // Load
1672 M_StartControlPanel();
1673 S_StartSound(NULL,sfx_swtchn);
1674 M_LoadGame(0);
1675 return true;
1677 else if (key == key_menu_volume) // Sound Volume
1679 M_StartControlPanel ();
1680 currentMenu = &SoundDef;
1681 itemOn = sfx_vol;
1682 S_StartSound(NULL,sfx_swtchn);
1683 return true;
1685 else if (key == key_menu_detail) // Detail toggle
1687 M_ChangeDetail(0);
1688 S_StartSound(NULL,sfx_swtchn);
1689 return true;
1691 else if (key == key_menu_qsave) // Quicksave
1693 S_StartSound(NULL,sfx_swtchn);
1694 M_QuickSave();
1695 return true;
1697 else if (key == key_menu_endgame) // End game
1699 S_StartSound(NULL,sfx_swtchn);
1700 M_EndGame(0);
1701 return true;
1703 else if (key == key_menu_messages) // Toggle messages
1705 M_ChangeMessages(0);
1706 S_StartSound(NULL,sfx_swtchn);
1707 return true;
1709 else if (key == key_menu_qload) // Quickload
1711 S_StartSound(NULL,sfx_swtchn);
1712 M_QuickLoad();
1713 return true;
1715 else if (key == key_menu_quit) // Quit DOOM
1717 S_StartSound(NULL,sfx_swtchn);
1718 M_QuitDOOM(0);
1719 return true;
1721 else if (key == key_menu_gamma) // gamma toggle
1723 usegamma++;
1724 if (usegamma > 4)
1725 usegamma = 0;
1726 players[consoleplayer].message = DEH_String(gammamsg[usegamma]);
1727 I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE));
1728 return true;
1732 // Pop-up menu?
1733 if (!menuactive)
1735 if (key == key_menu_activate)
1737 M_StartControlPanel ();
1738 S_StartSound(NULL,sfx_swtchn);
1739 return true;
1741 return false;
1745 // Keys usable within menu
1747 if (key == key_menu_down)
1749 // Move down to next item
1753 if (itemOn+1 > currentMenu->numitems-1)
1754 itemOn = 0;
1755 else itemOn++;
1756 S_StartSound(NULL,sfx_pstop);
1757 } while(currentMenu->menuitems[itemOn].status==-1);
1759 return true;
1761 else if (key == key_menu_up)
1763 // Move back up to previous item
1767 if (!itemOn)
1768 itemOn = currentMenu->numitems-1;
1769 else itemOn--;
1770 S_StartSound(NULL,sfx_pstop);
1771 } while(currentMenu->menuitems[itemOn].status==-1);
1773 return true;
1775 else if (key == key_menu_left)
1777 // Slide slider left
1779 if (currentMenu->menuitems[itemOn].routine &&
1780 currentMenu->menuitems[itemOn].status == 2)
1782 S_StartSound(NULL,sfx_stnmov);
1783 currentMenu->menuitems[itemOn].routine(0);
1785 return true;
1787 else if (key == key_menu_right)
1789 // Slide slider right
1791 if (currentMenu->menuitems[itemOn].routine &&
1792 currentMenu->menuitems[itemOn].status == 2)
1794 S_StartSound(NULL,sfx_stnmov);
1795 currentMenu->menuitems[itemOn].routine(1);
1797 return true;
1799 else if (key == key_menu_forward)
1801 // Activate menu item
1803 if (currentMenu->menuitems[itemOn].routine &&
1804 currentMenu->menuitems[itemOn].status)
1806 currentMenu->lastOn = itemOn;
1807 if (currentMenu->menuitems[itemOn].status == 2)
1809 currentMenu->menuitems[itemOn].routine(1); // right arrow
1810 S_StartSound(NULL,sfx_stnmov);
1812 else
1814 currentMenu->menuitems[itemOn].routine(itemOn);
1815 S_StartSound(NULL,sfx_pistol);
1818 return true;
1820 else if (key == key_menu_activate)
1822 // Deactivate menu
1824 currentMenu->lastOn = itemOn;
1825 M_ClearMenus ();
1826 S_StartSound(NULL,sfx_swtchx);
1827 return true;
1829 else if (key == key_menu_back)
1831 // Go back to previous menu
1833 currentMenu->lastOn = itemOn;
1834 if (currentMenu->prevMenu)
1836 currentMenu = currentMenu->prevMenu;
1837 itemOn = currentMenu->lastOn;
1838 S_StartSound(NULL,sfx_swtchn);
1840 return true;
1842 else if (ch != 0)
1844 // Keyboard shortcut?
1846 for (i = itemOn+1;i < currentMenu->numitems;i++)
1848 if (currentMenu->menuitems[i].alphaKey == ch)
1850 itemOn = i;
1851 S_StartSound(NULL,sfx_pstop);
1852 return true;
1856 for (i = 0;i <= itemOn;i++)
1858 if (currentMenu->menuitems[i].alphaKey == ch)
1860 itemOn = i;
1861 S_StartSound(NULL,sfx_pstop);
1862 return true;
1867 return false;
1873 // M_StartControlPanel
1875 void M_StartControlPanel (void)
1877 // intro might call this repeatedly
1878 if (menuactive)
1879 return;
1881 menuactive = 1;
1882 currentMenu = &MainDef; // JDC
1883 itemOn = currentMenu->lastOn; // JDC
1888 // M_Drawer
1889 // Called after the view has been rendered,
1890 // but before it has been blitted.
1892 void M_Drawer (void)
1894 static short x;
1895 static short y;
1896 unsigned int i;
1897 unsigned int max;
1898 char string[80];
1899 char *name;
1900 int start;
1902 inhelpscreens = false;
1904 // Horiz. & Vertically center string and print it.
1905 if (messageToPrint)
1907 start = 0;
1908 y = 100 - M_StringHeight(messageString) / 2;
1909 while (messageString[start] != '\0')
1911 int foundnewline = 0;
1913 for (i = 0; i < strlen(messageString + start); i++)
1914 if (messageString[start + i] == '\n')
1916 memset(string, 0, sizeof(string));
1917 strncpy(string, messageString + start, i);
1918 foundnewline = 1;
1919 start += i + 1;
1920 break;
1923 if (!foundnewline)
1925 strcpy(string, messageString + start);
1926 start += strlen(string);
1929 x = 160 - M_StringWidth(string) / 2;
1930 M_WriteText(x, y, string);
1931 y += SHORT(hu_font[0]->height);
1934 return;
1937 if (!menuactive)
1938 return;
1940 if (currentMenu->routine)
1941 currentMenu->routine(); // call Draw routine
1943 // DRAW MENU
1944 x = currentMenu->x;
1945 y = currentMenu->y;
1946 max = currentMenu->numitems;
1948 for (i=0;i<max;i++)
1950 name = DEH_String(currentMenu->menuitems[i].name);
1952 if (name[0])
1954 V_DrawPatchDirect (x,y,0, W_CacheLumpName(name, PU_CACHE));
1956 y += LINEHEIGHT;
1960 // DRAW SKULL
1961 V_DrawPatchDirect(x + SKULLXOFF,currentMenu->y - 5 + itemOn*LINEHEIGHT, 0,
1962 W_CacheLumpName(DEH_String(skullName[whichSkull]),
1963 PU_CACHE));
1969 // M_ClearMenus
1971 void M_ClearMenus (void)
1973 menuactive = 0;
1974 // if (!netgame && usergame && paused)
1975 // sendpause = true;
1982 // M_SetupNextMenu
1984 void M_SetupNextMenu(menu_t *menudef)
1986 currentMenu = menudef;
1987 itemOn = currentMenu->lastOn;
1992 // M_Ticker
1994 void M_Ticker (void)
1996 if (--skullAnimCounter <= 0)
1998 whichSkull ^= 1;
1999 skullAnimCounter = 8;
2005 // M_Init
2007 void M_Init (void)
2009 currentMenu = &MainDef;
2010 menuactive = 0;
2011 itemOn = currentMenu->lastOn;
2012 whichSkull = 0;
2013 skullAnimCounter = 10;
2014 screenSize = screenblocks - 3;
2015 messageToPrint = 0;
2016 messageString = NULL;
2017 messageLastMenuActive = menuactive;
2018 quickSaveSlot = -1;
2020 // Here we could catch other version dependencies,
2021 // like HELP1/2, and four episodes.
2024 switch ( gamemode )
2026 case commercial:
2027 // Commercial has no "read this" entry.
2028 MainMenu[readthis] = MainMenu[quitdoom];
2029 MainDef.numitems--;
2030 MainDef.y += 8;
2031 NewDef.prevMenu = &MainDef;
2032 break;
2033 case shareware:
2034 // Episode 2 and 3 are handled,
2035 // branching to an ad screen.
2036 case registered:
2037 // We need to remove the fourth episode.
2038 EpiDef.numitems--;
2039 break;
2040 case retail:
2041 // We are fine.
2042 default:
2043 break;