1 //----------------------------------------------------------------------------
2 // EDGE Main Menu Code
3 //----------------------------------------------------------------------------
4 //
5 // Copyright (c) 1999-2009 The EDGE Team.
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.
11 //
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.
16 //
17 //----------------------------------------------------------------------------
18 //
19 // Based on the DOOM source code, released by Id Software under the
20 // following copyright:
21 //
22 // Copyright (C) 1993-1996 by id Software, Inc.
23 //
24 //----------------------------------------------------------------------------
25 //
26 // See M_Option.C for text built menus.
27 //
28 // -KM- 1998/07/21 Add support for message input.
29 //
30
31 #include "i_defs.h"
32
33 #include "epi/str_format.h"
34
35 #include "ddf/main.h"
36
37 #include "con_main.h"
38 #include "dm_defs.h"
39 #include "dm_state.h"
40 #include "dstrings.h"
41 #include "e_main.h"
42 #include "g_game.h"
43 #include "f_interm.h"
44 #include "hu_draw.h"
45 #include "hu_stuff.h"
46 #include "hu_style.h"
47 #include "m_argv.h"
48 #include "m_menu.h"
49 #include "m_misc.h"
50 #include "m_netgame.h"
51 #include "m_option.h"
52 #include "m_random.h"
53 #include "n_network.h"
54 #include "p_setup.h"
55 #include "am_map.h"
56 #include "r_local.h"
57 #include "r_draw.h"
58 #include "r_modes.h"
59 #include "r_colormap.h"
60 #include "s_sound.h"
61 #include "s_music.h"
62 #include "sv_chunk.h"
63 #include "sv_main.h"
64 #include "w_wad.h"
65 #include "z_zone.h"
66
67
68 //
69 // defaulted values
70 //
71
72 // Show messages has default, 0 = off, 1 = on
73 int showMessages;
74
75 cvar_c m_language;
76
77 int screen_hud; // has default
78
79 static std::string msg_string;
80 static int msg_lastmenu;
81 static int msg_mode;
82
83 static std::string input_string;
84
85 bool menuactive;
86
87 #define SKULLXOFF -24
88 #define LINEHEIGHT 15 //!!!!
89
90 // timed message = no input from user
91 static bool msg_needsinput;
92
93 static void (* message_key_routine)(int response) = NULL;
94 static void (* message_input_routine)(const char *response) = NULL;
95
96 static int chosen_epi;
97
98 // SOUNDS
99 sfx_t * sfx_swtchn;
100 sfx_t * sfx_tink;
101 sfx_t * sfx_radio;
102 sfx_t * sfx_oof;
103 sfx_t * sfx_pstop;
104 sfx_t * sfx_stnmov;
105 sfx_t * sfx_pistol;
106 sfx_t * sfx_swtchx;
107 //
108 // IMAGES USED
109 //
110 static const image_c *therm_l;
111 static const image_c *therm_m;
112 static const image_c *therm_r;
113 static const image_c *therm_o;
114
115 static const image_c *menu_loadg;
116 static const image_c *menu_saveg;
117 static const image_c *menu_svol;
118 static const image_c *menu_doom;
119 static const image_c *menu_newgame;
120 static const image_c *menu_skill;
121 static const image_c *menu_episode;
122 static const image_c *menu_skull[2];
123 static const image_c *menu_readthis[2];
124
125 static style_c *menu_def_style;
126 static style_c *main_menu_style;
127 static style_c *episode_style;
128 static style_c *skill_style;
129 static style_c *load_style;
130 static style_c *save_style;
131 static style_c *dialog_style;
132 static style_c *sound_vol_style;
133
134 //
135 // SAVE STUFF
136 //
137 #define SAVESTRINGSIZE 24
138
139 #define SAVE_SLOTS 8
140 #define SAVE_PAGES 100 // more would be rather unwieldy
141
142 // -1 = no quicksave slot picked!
143 int quickSaveSlot;
144 int quickSavePage;
145
146 // 25-6-98 KM Lots of save games... :-)
147 int save_page = 0;
148 int save_slot = 0;
149
150 // we are going to be entering a savegame string
151 static int saveStringEnter;
152
153 // which char we're editing
154 static int saveCharIndex;
155
156 // old save description before edit
157 static char saveOldString[SAVESTRINGSIZE];
158
159 typedef struct slot_extra_info_s
160 {
161 bool empty;
162 bool corrupt;
163
164 char desc[SAVESTRINGSIZE];
165 char timestr[32];
166
167 char mapname[10];
168 char gamename[32];
169
170 int skill;
171 int netgame;
172 bool has_view;
173 }
174 slot_extra_info_t;
175
176 static slot_extra_info_t ex_slots[SAVE_SLOTS];
177
178 // 98-7-10 KM New defines for slider left.
179 // Part of savegame changes.
180 #define SLIDERLEFT -1
181 #define SLIDERRIGHT -2
182
183
184 //
185 // MENU TYPEDEFS
186 //
187 typedef struct
188 {
189 // 0 = no cursor here, 1 = ok, 2 = arrows ok
190 int status;
191
192 // image for menu entry
193 char patch_name[10];
194 const image_c *image;
195
196 // choice = menu item #.
197 // if status = 2, choice can be SLIDERLEFT or SLIDERRIGHT
198 void (* select_func)(int choice);
199
200 // hotkey in menu
201 char alpha_key;
202 }
203 menuitem_t;
204
205 typedef struct menu_s
206 {
207 // # of menu items
208 int numitems;
209
210 // previous menu
211 struct menu_s *prevMenu;
212
213 // menu items
214 menuitem_t *menuitems;
215
216 // style variable
217 style_c **style_var;
218
219 // draw routine
220 void (* draw_func)(void);
221
222 // x,y of menu
223 int x, y;
224
225 // last item user was on in menu
226 int lastOn;
227 }
228 menu_t;
229
230 // menu item skull is on
231 static int itemOn;
232
233 // skull animation counter
234 static int skullAnimCounter;
235
236 // which skull to draw
237 static int whichSkull;
238
239 // current menudef
240 static menu_t *currentMenu;
241
242 //
243 // PROTOTYPES
244 //
245 static void M_NewGame(int choice);
246 static void M_Episode(int choice);
247 static void M_ChooseSkill(int choice);
248 static void M_LoadGame(int choice);
249 static void M_SaveGame(int choice);
250
251 // 25-6-98 KM
252 extern void M_Options(int choice);
253 static void M_LoadSavePage(int choice);
254 static void M_ReadThis(int choice);
255 static void M_ReadThis2(int choice);
256 void M_EndGame(int choice);
257
258 static void M_ChangeMessages(int choice);
259 static void M_SfxVol(int choice);
260 static void M_MusicVol(int choice);
261 // static void M_Sound(int choice);
262
263 static void M_FinishReadThis(int choice);
264 static void M_LoadSelect(int choice);
265 static void M_SaveSelect(int choice);
266 static void M_ReadSaveStrings(void);
267 static void M_QuickSave(void);
268 static void M_QuickLoad(void);
269
270 static void M_DrawMainMenu(void);
271 static void M_DrawReadThis1(void);
272 static void M_DrawReadThis2(void);
273 static void M_DrawNewGame(void);
274 static void M_DrawEpisode(void);
275 static void M_DrawSound(void);
276 static void M_DrawLoad(void);
277 static void M_DrawSave(void);
278
279 static void M_DrawSaveLoadBorder(float x, float y, int len);
280 static void M_SetupNextMenu(menu_t * menudef);
281 void M_ClearMenus(void);
282 void M_StartControlPanel(void);
283 // static void M_StopMessage(void);
284
285 //
286 // DOOM MENU
287 //
288 typedef enum
289 {
290 newgame = 0,
291 options,
292 loadgame,
293 savegame,
294 readthis,
295 quitdoom,
296 main_end
297 }
298 main_e;
299
300 static menuitem_t MainMenu[] =
301 {
302 {1, "M_NGAME", NULL, M_NewGame, 'n'},
303 {1, "M_OPTION", NULL, M_Options, 'o'},
304 {1, "M_LOADG", NULL, M_LoadGame, 'l'},
305 {1, "M_SAVEG", NULL, M_SaveGame, 's'},
306 // Another hickup with Special edition.
307 {1, "M_RDTHIS", NULL, M_ReadThis, 'r'},
308 {1, "M_QUITG", NULL, M_QuitEDGE, 'q'}
309 };
310
311 static menu_t MainDef =
312 {
313 main_end,
314 NULL,
315 MainMenu,
316 &main_menu_style,
317 M_DrawMainMenu,
318 97, 64,
319 0
320 };
321
322 //
323 // EPISODE SELECT
324 //
325 // -KM- 1998/12/16 This is generated dynamically.
326 //
327 static menuitem_t *EpisodeMenu = NULL;
328
329 static menuitem_t DefaultEpiMenu =
330 {
331 1, // status
332 "Working", // name
333 NULL, // image
334 NULL, // select_func
335 'w' // alphakey
336 };
337
338 static menu_t EpiDef =
339 {
340 0, //ep_end, // # of menu items
341 &MainDef, // previous menu
342 &DefaultEpiMenu, // menuitem_t ->
343 &episode_style,
344 M_DrawEpisode, // drawing routine ->
345 48, 63, // x,y
346 0 // lastOn
347 };
348
349 static menuitem_t SkillMenu[] =
350 {
351 {1, "M_JKILL", NULL, M_ChooseSkill, 'p'},
352 {1, "M_ROUGH", NULL, M_ChooseSkill, 'r'},
353 {1, "M_HURT", NULL, M_ChooseSkill, 'h'},
354 {1, "M_ULTRA", NULL, M_ChooseSkill, 'u'},
355 {1, "M_NMARE", NULL, M_ChooseSkill, 'n'}
356 };
357
358 static menu_t SkillDef =
359 {
360 sk_numtypes, // # of menu items
361 &EpiDef, // previous menu
362 SkillMenu, // menuitem_t ->
363 &skill_style,
364 M_DrawNewGame, // drawing routine ->
365 48, 63, // x,y
366 sk_medium // lastOn
367 };
368
369 //
370 // OPTIONS MENU
371 //
372 typedef enum
373 {
374 endgame,
375 messages,
376 scrnsize,
377 option_empty1,
378 mousesens,
379 option_empty2,
380 soundvol,
381 opt_end
382 }
383 options_e;
384
385 //
386 // Read This! MENU 1 & 2
387 //
388
389 static menuitem_t ReadMenu1[] =
390 {
391 {1, "", NULL, M_ReadThis2, 0}
392 };
393
394 static menu_t ReadDef1 =
395 {
396 1,
397 &MainDef,
398 ReadMenu1,
399 &menu_def_style, // FIXME: maybe have READ_1 and READ_2 styles ??
400 M_DrawReadThis1,
401 280, 185,
402 0
403 };
404
405 static menuitem_t ReadMenu2[] =
406 {
407 {1, "", NULL, M_FinishReadThis, 0}
408 };
409
410 static menu_t ReadDef2 =
411 {
412 1,
413 &ReadDef1,
414 ReadMenu2,
415 &menu_def_style, // FIXME: maybe have READ_1 and READ_2 styles ??
416 M_DrawReadThis2,
417 330, 175,
418 0
419 };
420
421 //
422 // SOUND VOLUME MENU
423 //
424 typedef enum
425 {
426 sfx_vol,
427 sfx_empty1,
428 music_vol,
429 sfx_empty2,
430 sound_end
431 }
432 sound_e;
433
434 static menuitem_t SoundMenu[] =
435 {
436 {2, "M_SFXVOL", NULL, M_SfxVol, 's'},
437 {-1, "", NULL, 0},
438 {2, "M_MUSVOL", NULL, M_MusicVol, 'm'},
439 {-1, "", NULL, 0}
440 };
441
442 static menu_t SoundDef =
443 {
444 sound_end,
445 &MainDef, /// &OptionsDef,
446 SoundMenu,
447 &sound_vol_style,
448 M_DrawSound,
449 80, 64,
450 0
451 };
452
453 //
454 // LOAD GAME MENU
455 //
456 // Note: upto 10 slots per page
457 //
458 static menuitem_t LoadingMenu[] =
459 {
460 {2, "", NULL, M_LoadSelect, '1'},
461 {2, "", NULL, M_LoadSelect, '2'},
462 {2, "", NULL, M_LoadSelect, '3'},
463 {2, "", NULL, M_LoadSelect, '4'},
464 {2, "", NULL, M_LoadSelect, '5'},
465 {2, "", NULL, M_LoadSelect, '6'},
466 {2, "", NULL, M_LoadSelect, '7'},
467 {2, "", NULL, M_LoadSelect, '8'},
468 {2, "", NULL, M_LoadSelect, '9'},
469 {2, "", NULL, M_LoadSelect, '0'}
470 };
471
472 static menu_t LoadDef =
473 {
474 SAVE_SLOTS,
475 &MainDef,
476 LoadingMenu,
477 &load_style,
478 M_DrawLoad,
479 30, 34,
480 0
481 };
482
483 //
484 // SAVE GAME MENU
485 //
486 static menuitem_t SavingMenu[] =
487 {
488 {2, "", NULL, M_SaveSelect, '1'},
489 {2, "", NULL, M_SaveSelect, '2'},
490 {2, "", NULL, M_SaveSelect, '3'},
491 {2, "", NULL, M_SaveSelect, '4'},
492 {2, "", NULL, M_SaveSelect, '5'},
493 {2, "", NULL, M_SaveSelect, '6'},
494 {2, "", NULL, M_SaveSelect, '7'},
495 {2, "", NULL, M_SaveSelect, '8'},
496 {2, "", NULL, M_SaveSelect, '9'},
497 {2, "", NULL, M_SaveSelect, '0'}
498 };
499
500 static menu_t SaveDef =
501 {
502 SAVE_SLOTS,
503 &MainDef,
504 SavingMenu,
505 &save_style,
506 M_DrawSave,
507 30, 34,
508 0
509 };
510
511 // 98-7-10 KM Chooses the page of savegames to view
M_LoadSavePage(int choice)512 void M_LoadSavePage(int choice)
513 {
514 switch (choice)
515 {
516 case SLIDERLEFT:
517 // -AJA- could use `OOF' sound...
518 if (save_page == 0)
519 return;
520
521 save_page--;
522 break;
523
524 case SLIDERRIGHT:
525 if (save_page >= SAVE_PAGES-1)
526 return;
527
528 save_page++;
529 break;
530 }
531
532 S_StartFX(sfx_swtchn);
533 M_ReadSaveStrings();
534 }
535
536 //
537 // Read the strings from the savegame files
538 //
539 // 98-7-10 KM Savegame slots increased
540 //
M_ReadSaveStrings(void)541 void M_ReadSaveStrings(void)
542 {
543 int i, version;
544
545 saveglobals_t *globs;
546
547 for (i=0; i < SAVE_SLOTS; i++)
548 {
549 ex_slots[i].empty = false;
550 ex_slots[i].corrupt = true;
551
552 ex_slots[i].skill = -1;
553 ex_slots[i].netgame = -1;
554 ex_slots[i].has_view = false;
555
556 ex_slots[i].desc[0] = 0;
557 ex_slots[i].timestr[0] = 0;
558 ex_slots[i].mapname[0] = 0;
559 ex_slots[i].gamename[0] = 0;
560
561 int slot = save_page * SAVE_SLOTS + i;
562 std::string fn(SV_FileName(SV_SlotName(slot), "head"));
563
564 if (! SV_OpenReadFile(fn.c_str()))
565 {
566 ex_slots[i].empty = true;
567 ex_slots[i].corrupt = false;
568 continue;
569 }
570
571 if (! SV_VerifyHeader(&version))
572 {
573 SV_CloseReadFile();
574 continue;
575 }
576
577 globs = SV_LoadGLOB();
578
579 // close file now -- we only need the globals
580 SV_CloseReadFile();
581
582 if (! globs)
583 continue;
584
585 // --- pull info from global structure ---
586
587 if (!globs->game || !globs->level || !globs->description)
588 {
589 SV_FreeGLOB(globs);
590 continue;
591 }
592
593 ex_slots[i].corrupt = false;
594
595 Z_StrNCpy(ex_slots[i].gamename, globs->game, 32-1);
596 Z_StrNCpy(ex_slots[i].mapname, globs->level, 10-1);
597
598 Z_StrNCpy(ex_slots[i].desc, globs->description, SAVESTRINGSIZE-1);
599
600 if (globs->desc_date)
601 Z_StrNCpy(ex_slots[i].timestr, globs->desc_date, 32-1);
602
603 ex_slots[i].skill = globs->skill;
604 ex_slots[i].netgame = globs->netgame;
605
606 SV_FreeGLOB(globs);
607
608 #if 0
609 // handle screenshot
610 if (globs->view_pixels)
611 {
612 int x, y;
613
614 for (y=0; y < 100; y++)
615 for (x=0; x < 160; x++)
616 {
617 save_screenshot[x][y] = SV_GetShort();
618 }
619 }
620 #endif
621 }
622
623 // fix up descriptions
624 for (i=0; i < SAVE_SLOTS; i++)
625 {
626 if (ex_slots[i].corrupt)
627 {
628 strncpy(ex_slots[i].desc, language["Corrupt_Slot"],
629 SAVESTRINGSIZE - 1);
630 continue;
631 }
632 else if (ex_slots[i].empty)
633 {
634 strncpy(ex_slots[i].desc, language["EmptySlot"],
635 SAVESTRINGSIZE - 1);
636 continue;
637 }
638 }
639 }
640
M_DrawSaveLoadCommon(int row,int row2,style_c * style)641 static void M_DrawSaveLoadCommon(int row, int row2, style_c *style)
642 {
643 int y = LoadDef.y + LINEHEIGHT * row;
644
645 slot_extra_info_t *info;
646
647 char mbuffer[200];
648
649
650 sprintf(mbuffer, "PAGE %d", save_page + 1);
651
652 // -KM- 1998/06/25 This could quite possibly be replaced by some graphics...
653 if (save_page > 0)
654 HL_WriteText(style,2, LoadDef.x - 4, y, "< PREV");
655
656 HL_WriteText(style,2, LoadDef.x + 94 - style->fonts[2]->StringWidth(mbuffer) / 2, y,
657 mbuffer);
658
659 if (save_page < SAVE_PAGES-1)
660 HL_WriteText(style,2, LoadDef.x + 192 - style->fonts[2]->StringWidth("NEXT >"), y,
661 "NEXT >");
662
663 info = ex_slots + itemOn;
664 SYS_ASSERT(0 <= itemOn && itemOn < SAVE_SLOTS);
665
666 if (saveStringEnter || info->empty || info->corrupt)
667 return;
668
669 // show some info about the savegame
670
671 y = LoadDef.y + LINEHEIGHT * (row2 + 1);
672
673 mbuffer[0] = 0;
674
675 strcat(mbuffer, info->timestr);
676
677 HL_WriteText(style,3, 310 - style->fonts[3]->StringWidth(mbuffer), y, mbuffer);
678
679
680 y -= LINEHEIGHT;
681
682 mbuffer[0] = 0;
683
684 // FIXME: use the patches (but shrink them)
685 switch (info->skill)
686 {
687 case 0: strcat(mbuffer, "Too Young To Die"); break;
688 case 1: strcat(mbuffer, "Not Too Rough"); break;
689 case 2: strcat(mbuffer, "Hurt Me Plenty"); break;
690 case 3: strcat(mbuffer, "Ultra Violence"); break;
691 default: strcat(mbuffer, "NIGHTMARE"); break;
692 }
693
694 HL_WriteText(style,3, 310 - style->fonts[3]->StringWidth(mbuffer), y, mbuffer);
695
696
697 y -= LINEHEIGHT;
698
699 mbuffer[0] = 0;
700
701 switch (info->netgame)
702 {
703 case 0: strcat(mbuffer, "SP MODE"); break;
704 case 1: strcat(mbuffer, "COOP MODE"); break;
705 default: strcat(mbuffer, "DM MODE"); break;
706 }
707
708 HL_WriteText(style,3, 310 - style->fonts[3]->StringWidth(mbuffer), y, mbuffer);
709
710
711 y -= LINEHEIGHT;
712
713 mbuffer[0] = 0;
714
715 strcat(mbuffer, info->mapname);
716
717 HL_WriteText(style,3, 310 - style->fonts[3]->StringWidth(mbuffer), y, mbuffer);
718 }
719
720 //
721 // 1998/07/10 KM Savegame slots increased
722 //
M_DrawLoad(void)723 void M_DrawLoad(void)
724 {
725 int i;
726
727 HUD_DrawImage(72, 8, menu_loadg);
728
729 for (i = 0; i < SAVE_SLOTS; i++)
730 M_DrawSaveLoadBorder(LoadDef.x + 8, LoadDef.y + LINEHEIGHT * (i), 24);
731
732 // draw screenshot ?
733
734 for (i = 0; i < SAVE_SLOTS; i++)
735 HL_WriteText(load_style, ex_slots[i].corrupt ? 3 : 0,
736 LoadDef.x + 8, LoadDef.y + LINEHEIGHT * (i),
737 ex_slots[i].desc);
738
739 M_DrawSaveLoadCommon(i, i+1, load_style);
740 }
741
742
743 //
744 // Draw border for the savegame description
745 //
M_DrawSaveLoadBorder(float x,float y,int len)746 void M_DrawSaveLoadBorder(float x, float y, int len)
747 {
748 const image_c *L = W_ImageLookup("M_LSLEFT");
749 const image_c *C = W_ImageLookup("M_LSCNTR");
750 const image_c *R = W_ImageLookup("M_LSRGHT");
751
752 HUD_DrawImage(x - IM_WIDTH(L), y + 7, L);
753
754 for (int i = 0; i < len; i++, x += IM_WIDTH(C))
755 HUD_DrawImage(x, y + 7, C);
756
757 HUD_DrawImage(x, y + 7, R);
758 }
759
760 //
761 // User wants to load this game
762 //
763 // 98-7-10 KM Savegame slots increased
764 //
M_LoadSelect(int choice)765 void M_LoadSelect(int choice)
766 {
767 if (choice < 0)
768 {
769 M_LoadSavePage(choice);
770 return;
771 }
772
773 G_DeferredLoadGame(save_page * SAVE_SLOTS + choice);
774 M_ClearMenus();
775 }
776
777 //
778 // Selected from DOOM menu
779 //
M_LoadGame(int choice)780 void M_LoadGame(int choice)
781 {
782 if (netgame)
783 {
784 M_StartMessage(language["NoLoadInNetGame"], NULL, false);
785 return;
786 }
787
788 M_SetupNextMenu(&LoadDef);
789 M_ReadSaveStrings();
790 }
791
792 //
793 // 98-7-10 KM Savegame slots increased
794 //
M_DrawSave(void)795 void M_DrawSave(void)
796 {
797 int i, len;
798
799 HUD_DrawImage(72, 8, menu_saveg);
800
801 for (i = 0; i < SAVE_SLOTS; i++)
802 {
803 int y = LoadDef.y + LINEHEIGHT * i;
804
805 M_DrawSaveLoadBorder(LoadDef.x + 8, y, 24);
806
807 if (saveStringEnter && i == save_slot)
808 {
809 len = save_style->fonts[1]->StringWidth(ex_slots[save_slot].desc);
810
811 HL_WriteText(save_style,1, LoadDef.x + 8, y, ex_slots[i].desc);
812 HL_WriteText(save_style,1, LoadDef.x + len + 8, y, "_");
813 }
814 else
815 HL_WriteText(save_style,0, LoadDef.x + 8, y, ex_slots[i].desc);
816 }
817
818 M_DrawSaveLoadCommon(i, i+1, save_style);
819 }
820
821 //
822 // M_Responder calls this when user is finished
823 //
824 // 98-7-10 KM Savegame slots increased
825 //
M_DoSave(int page,int slot)826 static void M_DoSave(int page, int slot)
827 {
828 G_DeferredSaveGame(page * SAVE_SLOTS + slot, ex_slots[slot].desc);
829 M_ClearMenus();
830
831 // PICK QUICKSAVE SLOT YET?
832 if (quickSaveSlot == -2)
833 {
834 quickSavePage = page;
835 quickSaveSlot = slot;
836 }
837
838 LoadDef.lastOn = SaveDef.lastOn;
839 }
840
841 //
842 // User wants to save. Start string input for M_Responder
843 //
M_SaveSelect(int choice)844 void M_SaveSelect(int choice)
845 {
846 if (choice < 0)
847 {
848 M_LoadSavePage(choice);
849 return;
850 }
851
852 // we are going to be intercepting all chars
853 saveStringEnter = 1;
854
855 save_slot = choice;
856 strcpy(saveOldString, ex_slots[choice].desc);
857
858 if (ex_slots[choice].empty)
859 ex_slots[choice].desc[0] = 0;
860
861 saveCharIndex = strlen(ex_slots[choice].desc);
862 }
863
864 //
865 // Selected from DOOM menu
866 //
M_SaveGame(int choice)867 void M_SaveGame(int choice)
868 {
869 if (gamestate != GS_LEVEL)
870 {
871 M_StartMessage(language["SaveWhenNotPlaying"], NULL, false);
872 return;
873 }
874
875 // -AJA- big cop-out here (add RTS menu stuff to savegame ?)
876 if (rts_menuactive)
877 {
878 M_StartMessage("You can't save during an RTS menu.\n\npress a key.", NULL, false);
879 return;
880 }
881
882 M_ReadSaveStrings();
883 M_SetupNextMenu(&SaveDef);
884
885 need_save_screenshot = true;
886 save_screenshot_valid = false;
887 }
888
889 //
890 // M_QuickSave
891 //
892
QuickSaveResponse(int ch)893 static void QuickSaveResponse(int ch)
894 {
895 if (ch == 'y')
896 {
897 M_DoSave(quickSavePage, quickSaveSlot);
898 S_StartFX(sfx_swtchx);
899 }
900 }
901
M_QuickSave(void)902 void M_QuickSave(void)
903 {
904 if (gamestate != GS_LEVEL)
905 {
906 S_StartFX(sfx_oof);
907 return;
908 }
909
910 if (quickSaveSlot < 0)
911 {
912 M_StartControlPanel();
913 M_ReadSaveStrings();
914 M_SetupNextMenu(&SaveDef);
915
916 need_save_screenshot = true;
917 save_screenshot_valid = false;
918
919 quickSaveSlot = -2; // means to pick a slot now
920 return;
921 }
922
923 std::string s(epi::STR_Format(language["QuickSaveOver"],
924 ex_slots[quickSaveSlot].desc));
925
926 M_StartMessage(s.c_str(), QuickSaveResponse, true);
927 }
928
QuickLoadResponse(int ch)929 static void QuickLoadResponse(int ch)
930 {
931 if (ch == 'y')
932 {
933 int tempsavepage = save_page;
934
935 save_page = quickSavePage;
936 M_LoadSelect(quickSaveSlot);
937
938 save_page = tempsavepage;
939 S_StartFX(sfx_swtchx);
940 }
941 }
942
M_QuickLoad(void)943 void M_QuickLoad(void)
944 {
945 if (netgame)
946 {
947 M_StartMessage(language["NoQLoadInNet"], NULL, false);
948 return;
949 }
950
951 if (quickSaveSlot < 0)
952 {
953 M_StartMessage(language["NoQuickSaveSlot"], NULL, false);
954 return;
955 }
956
957 std::string s(epi::STR_Format(language["QuickLoad"],
958 ex_slots[quickSaveSlot].desc));
959
960 M_StartMessage(s.c_str(), QuickLoadResponse, true);
961 }
962
963 //
964 // Read This Menus
965 // Had a "quick hack to fix romero bug"
966 //
M_DrawReadThis1(void)967 void M_DrawReadThis1(void)
968 {
969 HUD_StretchImage(0, 0, 320, 200, menu_readthis[0]);
970 }
971
972 //
973 // Read This Menus - optional second page.
974 //
M_DrawReadThis2(void)975 void M_DrawReadThis2(void)
976 {
977 HUD_StretchImage(0, 0, 320, 200, menu_readthis[1]);
978 }
979
980
M_DrawSound(void)981 void M_DrawSound(void)
982 {
983 HUD_DrawImage(60, 38, menu_svol);
984
985 M_DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (sfx_vol + 1), SND_SLIDER_NUM, sfx_volume, 1);
986 M_DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (music_vol + 1), SND_SLIDER_NUM, mus_volume, 1);
987 }
988
989 #if 0
990 void M_Sound(int choice)
991 {
992 M_SetupNextMenu(&SoundDef);
993 }
994 #endif
995
996 // -ACB- 1999/10/10 Sound API Volume re-added
M_SfxVol(int choice)997 void M_SfxVol(int choice)
998 {
999 switch (choice)
1000 {
1001 case SLIDERLEFT:
1002 if (sfx_volume > 0)
1003 sfx_volume--;
1004
1005 break;
1006
1007 case SLIDERRIGHT:
1008 if (sfx_volume < SND_SLIDER_NUM-1)
1009 sfx_volume++;
1010
1011 break;
1012 }
1013
1014 S_ChangeSoundVolume();
1015 }
1016
1017 // -ACB- 1999/10/07 Removed sound references: New Sound API
M_MusicVol(int choice)1018 void M_MusicVol(int choice)
1019 {
1020 switch (choice)
1021 {
1022 case SLIDERLEFT:
1023 if (mus_volume > 0)
1024 mus_volume--;
1025
1026 break;
1027
1028 case SLIDERRIGHT:
1029 if (mus_volume < SND_SLIDER_NUM-1)
1030 mus_volume++;
1031
1032 break;
1033 }
1034
1035 S_ChangeMusicVolume();
1036 }
1037
M_DrawMainMenu(void)1038 void M_DrawMainMenu(void)
1039 {
1040 HUD_DrawImage(94, 2, menu_doom);
1041 }
1042
M_DrawNewGame(void)1043 void M_DrawNewGame(void)
1044 {
1045 HUD_DrawImage(96, 14, menu_newgame);
1046 HUD_DrawImage(54, 38, menu_skill);
1047 }
1048
M_NewGame(int choice)1049 void M_NewGame(int choice)
1050 {
1051 if (netgame)
1052 {
1053 M_StartMessage(language["NewNetGame"], NULL, false);
1054 return;
1055 }
1056
1057 M_SetupNextMenu(&EpiDef);
1058 }
1059
1060 //
1061 // M_Episode
1062 //
1063
1064 // -KM- 1998/12/16 Generates EpiDef menu dynamically.
CreateEpisodeMenu(void)1065 static void CreateEpisodeMenu(void)
1066 {
1067 if (gamedefs.GetSize() == 0)
1068 I_Error("No defined episodes !\n");
1069
1070 EpisodeMenu = Z_New(menuitem_t, gamedefs.GetSize());
1071
1072 Z_Clear(EpisodeMenu, menuitem_t, gamedefs.GetSize());
1073
1074 int e = 0;
1075 epi::array_iterator_c it;
1076
1077 for (it = gamedefs.GetBaseIterator(); it.IsValid(); it++)
1078 {
1079 gamedef_c *g = ITERATOR_TO_TYPE(it, gamedef_c*);
1080 if (! g) continue;
1081
1082 if (W_CheckNumForName(g->firstmap.c_str()) == -1)
1083 continue;
1084
1085 EpisodeMenu[e].status = 1;
1086 EpisodeMenu[e].select_func = M_Episode;
1087 EpisodeMenu[e].image = NULL;
1088 EpisodeMenu[e].alpha_key = '1' + e;
1089
1090 Z_StrNCpy(EpisodeMenu[e].patch_name, g->namegraphic.c_str(), 8);
1091 EpisodeMenu[e].patch_name[8] = 0;
1092
1093 e++;
1094 }
1095
1096 if (e == 0)
1097 I_Error("No available episodes !\n");
1098
1099 EpiDef.numitems = e;
1100 EpiDef.menuitems = EpisodeMenu;
1101 }
1102
1103
M_DrawEpisode(void)1104 void M_DrawEpisode(void)
1105 {
1106 if (!EpisodeMenu)
1107 CreateEpisodeMenu();
1108
1109 HUD_DrawImage(54, 38, menu_episode);
1110 }
1111
ReallyDoStartLevel(skill_t skill,gamedef_c * g)1112 static void ReallyDoStartLevel(skill_t skill, gamedef_c *g)
1113 {
1114 newgame_params_c params;
1115
1116 params.skill = skill;
1117 params.deathmatch = 0;
1118
1119 params.random_seed = I_PureRandom();
1120
1121 params.SinglePlayer(0);
1122
1123 params.map = G_LookupMap(g->firstmap.c_str());
1124
1125 if (! params.map)
1126 {
1127 // 23-6-98 KM Fixed this.
1128 M_SetupNextMenu(&EpiDef);
1129 M_StartMessage(language["EpisodeNonExist"], NULL, false);
1130 return;
1131 }
1132
1133 SYS_ASSERT(G_MapExists(params.map));
1134 SYS_ASSERT(params.map->episode);
1135
1136 G_DeferredNewGame(params);
1137
1138 M_ClearMenus();
1139 }
1140
DoStartLevel(skill_t skill)1141 static void DoStartLevel(skill_t skill)
1142 {
1143 // -KM- 1998/12/17 Clear the intermission.
1144 WI_Clear();
1145
1146 // find episode
1147 gamedef_c *g = NULL;
1148 epi::array_iterator_c it;
1149
1150 for (it = gamedefs.GetBaseIterator(); it.IsValid(); it++)
1151 {
1152 g = ITERATOR_TO_TYPE(it, gamedef_c*);
1153
1154 if (!strcmp(g->namegraphic.c_str(), EpisodeMenu[chosen_epi].patch_name))
1155 {
1156 break;
1157 }
1158 }
1159
1160 // Sanity checking...
1161 if (! g)
1162 {
1163 I_Warning("Internal Error: no episode for '%s'.\n",
1164 EpisodeMenu[chosen_epi].patch_name);
1165 M_ClearMenus();
1166 return;
1167 }
1168
1169 const mapdef_c * map = G_LookupMap(g->firstmap.c_str());
1170 if (! map)
1171 {
1172 I_Warning("Cannot find map for '%s' (episode %s)\n",
1173 g->firstmap.c_str(),
1174 EpisodeMenu[chosen_epi].patch_name);
1175 M_ClearMenus();
1176 return;
1177 }
1178
1179 ReallyDoStartLevel(skill, g);
1180 }
1181
VerifyNightmare(int ch)1182 static void VerifyNightmare(int ch)
1183 {
1184 if (ch != 'y')
1185 return;
1186
1187 DoStartLevel(sk_nightmare);
1188 }
1189
M_ChooseSkill(int choice)1190 void M_ChooseSkill(int choice)
1191 {
1192 if (choice == sk_nightmare)
1193 {
1194 M_StartMessage(language["NightMareCheck"], VerifyNightmare, true);
1195 return;
1196 }
1197
1198 DoStartLevel((skill_t)choice);
1199 }
1200
M_Episode(int choice)1201 void M_Episode(int choice)
1202 {
1203 chosen_epi = choice;
1204 M_SetupNextMenu(&SkillDef);
1205 }
1206
1207 //
1208 // Toggle messages on/off
1209 //
M_ChangeMessages(int choice)1210 void M_ChangeMessages(int choice)
1211 {
1212 // warning: unused parameter `int choice'
1213 (void) choice;
1214
1215 showMessages = 1 - showMessages;
1216
1217 if (showMessages)
1218 CON_Printf("%s\n", language["MessagesOn"]);
1219 else
1220 CON_Printf("%s\n", language["MessagesOff"]);
1221 }
1222
EndGameResponse(int ch)1223 static void EndGameResponse(int ch)
1224 {
1225 if (ch != 'y')
1226 return;
1227
1228 G_DeferredEndGame();
1229
1230 currentMenu->lastOn = itemOn;
1231 M_ClearMenus();
1232 }
1233
M_EndGame(int choice)1234 void M_EndGame(int choice)
1235 {
1236 if (gamestate != GS_LEVEL)
1237 {
1238 S_StartFX(sfx_oof);
1239 return;
1240 }
1241
1242 option_menuon = 0;
1243 netgame_menuon = 0;
1244
1245 if (netgame)
1246 {
1247 M_StartMessage(language["EndNetGame"], NULL, false);
1248 return;
1249 }
1250
1251 M_StartMessage(language["EndGameCheck"], EndGameResponse, true);
1252 }
1253
M_ReadThis(int choice)1254 void M_ReadThis(int choice)
1255 {
1256 M_SetupNextMenu(&ReadDef1);
1257 }
1258
M_ReadThis2(int choice)1259 void M_ReadThis2(int choice)
1260 {
1261 M_SetupNextMenu(&ReadDef2);
1262 }
1263
M_FinishReadThis(int choice)1264 void M_FinishReadThis(int choice)
1265 {
1266 M_SetupNextMenu(&MainDef);
1267 }
1268
1269 //
1270 // -KM- 1998/12/16 Handle sfx that don't exist in this version.
1271 // -KM- 1999/01/31 Generate quitsounds from default.ldf
1272 //
QuitResponse(int ch)1273 static void QuitResponse(int ch)
1274 {
1275 if (ch != 'y')
1276 return;
1277
1278 if (!netgame)
1279 {
1280 int numsounds = 0;
1281 char refname[16];
1282 char sound[16];
1283 int i, start;
1284
1285 // Count the quit messages
1286 do
1287 {
1288 sprintf(refname, "QuitSnd%d", numsounds + 1);
1289 if (language.IsValidRef(refname))
1290 numsounds++;
1291 else
1292 break;
1293 }
1294 while (true);
1295
1296 if (numsounds)
1297 {
1298 // cycle through all the quit sounds, until one of them exists
1299 // (some of the default quit sounds do not exist in DOOM 1)
1300 start = i = M_Random() % numsounds;
1301 do
1302 {
1303 sprintf(refname, "QuitSnd%d", i + 1);
1304 sprintf(sound, "DS%s", language[refname]);
1305 if (W_CheckNumForName(sound) != -1)
1306 {
1307 S_StartFX(sfxdefs.GetEffect(language[refname]));
1308 break;
1309 }
1310 i = (i + 1) % numsounds;
1311 }
1312 while (i != start);
1313 }
1314 }
1315
1316 // -ACB- 1999/09/20 New exit code order
1317 // Write the default config file first
1318 I_Printf("Saving system defaults...\n");
1319 M_SaveDefaults();
1320
1321 I_Printf("Exiting...\n");
1322
1323 E_EngineShutdown();
1324 I_SystemShutdown();
1325
1326 I_DisplayExitScreen();
1327 I_CloseProgram(0);
1328 }
1329
1330 //
1331 // -ACB- 1998/07/19 Removed offensive messages selection (to some people);
1332 // Better Random Selection.
1333 //
1334 // -KM- 1998/07/21 Reinstated counting quit messages, so adding them to dstrings.c
1335 // is all you have to do. Using P_Random for the random number
1336 // automatically kills the demo sync...
1337 // (hence M_Random()... -AJA-).
1338 //
1339 // -KM- 1998/07/31 Removed Limit. So there.
1340 // -KM- 1999/01/31 Load quit messages from default.ldf
1341 //
M_QuitEDGE(int choice)1342 void M_QuitEDGE(int choice)
1343 {
1344 char ref[64];
1345
1346 std::string msg;
1347
1348 int num_quitmessages = 0;
1349
1350 // Count the quit messages
1351 do
1352 {
1353 num_quitmessages++;
1354
1355 sprintf(ref, "QUITMSG%d", num_quitmessages);
1356 }
1357 while (language.IsValidRef(ref));
1358
1359 // we stopped at one higher than the last
1360 num_quitmessages--;
1361
1362 // -ACB- 2004/08/14 Allow fallback to just the "PressToQuit" message
1363 if (num_quitmessages > 0)
1364 {
1365 // Pick one at random
1366 sprintf(ref, "QUITMSG%d", 1 + (M_Random() % num_quitmessages));
1367
1368 // Construct the quit message in full
1369 msg = epi::STR_Format("%s\n\n%s", language[ref], language["PressToQuit"]);
1370 }
1371 else
1372 {
1373 msg = std::string(language["PressToQuit"]);
1374 }
1375
1376 // Trigger the message
1377 M_StartMessage(msg.c_str(), QuitResponse, true);
1378 }
1379
1380
1381 //----------------------------------------------------------------------------
1382 // MENU FUNCTIONS
1383 //----------------------------------------------------------------------------
1384
M_DrawThermo(int x,int y,int thermWidth,int thermDot,int div)1385 void M_DrawThermo(int x, int y, int thermWidth, int thermDot, int div)
1386 {
1387 int i, basex = x;
1388 int step = (8 / div);
1389
1390 // Note: the (step+1) here is for compatibility with the original
1391 // code. It seems required to make the thermo bar tile properly.
1392
1393 HUD_StretchImage(x, y, step+1, IM_HEIGHT(therm_l)/div, therm_l);
1394
1395 for (i=0, x += step; i < thermWidth; i++, x += step)
1396 {
1397 HUD_StretchImage(x, y, step+1, IM_HEIGHT(therm_m)/div, therm_m);
1398 }
1399
1400 HUD_StretchImage(x, y, step+1, IM_HEIGHT(therm_r)/div, therm_r);
1401
1402 x = basex + step + thermDot * step;
1403
1404 HUD_StretchImage(x, y, step+1, IM_HEIGHT(therm_o)/div, therm_o);
1405 }
1406
M_StartMessage(const char * string,void (* routine)(int response),bool input)1407 void M_StartMessage(const char *string, void (* routine)(int response),
1408 bool input)
1409 {
1410 msg_lastmenu = menuactive;
1411 msg_mode = 1;
1412 msg_string = std::string(string);
1413 message_key_routine = routine;
1414 message_input_routine = NULL;
1415 msg_needsinput = input;
1416 menuactive = true;
1417 CON_SetVisible(vs_notvisible);
1418 return;
1419 }
1420
1421 //
1422 // -KM- 1998/07/21 Call M_StartMesageInput to start a message that needs a
1423 // string input. (You can convert it to a number if you want to.)
1424 //
1425 // string: The prompt.
1426 //
1427 // routine: Format is void routine(char *s) Routine will be called
1428 // with a pointer to the input in s. s will be NULL if the user
1429 // pressed ESCAPE to cancel the input.
1430 //
M_StartMessageInput(const char * string,void (* routine)(const char * response))1431 void M_StartMessageInput(const char *string,
1432 void (* routine)(const char *response))
1433 {
1434 msg_lastmenu = menuactive;
1435 msg_mode = 2;
1436 msg_string = std::string(string);
1437 message_input_routine = routine;
1438 message_key_routine = NULL;
1439 msg_needsinput = true;
1440 menuactive = true;
1441 CON_SetVisible(vs_notvisible);
1442 return;
1443 }
1444
1445 #if 0
1446 void M_StopMessage(void)
1447 {
1448 menuactive = msg_lastmenu?true:false;
1449 msg_string.Clear();
1450 msg_mode = 0;
1451
1452 if (!menuactive)
1453 save_screenshot_valid = false;
1454 }
1455 #endif
1456
1457 //
1458 // CONTROL PANEL
1459 //
1460
1461 //
1462 // -KM- 1998/09/01 Analogue binding, and hat support
1463 //
M_Responder(event_t * ev)1464 bool M_Responder(event_t * ev)
1465 {
1466 int i;
1467
1468 if (ev->type != ev_keydown)
1469 return false;
1470
1471 int ch = ev->value.key.sym;
1472
1473 // -ACB- 1999/10/11 F1 is responsible for print screen at any time
1474 if (ch == KEYD_F1 || ch == KEYD_PRTSCR)
1475 {
1476 G_DeferredScreenShot();
1477 return true;
1478 }
1479
1480 // Take care of any messages that need input
1481 // -KM- 1998/07/21 Message Input
1482 if (msg_mode == 1)
1483 {
1484 if (msg_needsinput == true &&
1485 !(ch == ' ' || ch == 'n' || ch == 'y' || ch == KEYD_ESCAPE))
1486 return false;
1487
1488 msg_mode = 0;
1489 // -KM- 1998/07/31 Moved this up here to fix bugs.
1490 menuactive = msg_lastmenu?true:false;
1491
1492 if (message_key_routine)
1493 (* message_key_routine)(ch);
1494
1495 S_StartFX(sfx_swtchx);
1496 return true;
1497 }
1498 else if (msg_mode == 2)
1499 {
1500 if (ch == KEYD_ENTER)
1501 {
1502 menuactive = msg_lastmenu?true:false;
1503 msg_mode = 0;
1504
1505 if (message_input_routine)
1506 (* message_input_routine)(input_string.c_str());
1507
1508 input_string.clear();
1509
1510 M_ClearMenus();
1511 S_StartFX(sfx_swtchx);
1512 return true;
1513 }
1514
1515 if (ch == KEYD_ESCAPE)
1516 {
1517 menuactive = msg_lastmenu?true:false;
1518 msg_mode = 0;
1519
1520 if (message_input_routine)
1521 (* message_input_routine)(NULL);
1522
1523 input_string.clear();
1524
1525 M_ClearMenus();
1526 S_StartFX(sfx_swtchx);
1527 return true;
1528 }
1529
1530 if ((ch == KEYD_BACKSPACE || ch == KEYD_DELETE) && !input_string.empty())
1531 {
1532 std::string s = input_string.c_str();
1533
1534 if (input_string.size() > 0)
1535 {
1536 input_string.resize(input_string.size() - 1);
1537 }
1538
1539 return true;
1540 }
1541
1542 ch = toupper(ch);
1543 if (ch == '-')
1544 ch = '_';
1545
1546 if (ch >= 32 && ch <= 126) // FIXME: international characters ??
1547 {
1548 // Set the input_string only if fits
1549 if (input_string.size() < 64)
1550 {
1551 input_string += ch;
1552 }
1553 }
1554
1555 return true;
1556 }
1557
1558 // new options menu on - use that responder
1559 if (option_menuon)
1560 return M_OptResponder(ev, ch);
1561
1562 if (netgame_menuon)
1563 return M_NetGameResponder(ev, ch);
1564
1565 // Save Game string input
1566 if (saveStringEnter)
1567 {
1568 switch (ch)
1569 {
1570 case KEYD_BACKSPACE:
1571 if (saveCharIndex > 0)
1572 {
1573 saveCharIndex--;
1574 ex_slots[save_slot].desc[saveCharIndex] = 0;
1575 }
1576 break;
1577
1578 case KEYD_ESCAPE:
1579 saveStringEnter = 0;
1580 strcpy(ex_slots[save_slot].desc, saveOldString);
1581 break;
1582
1583 case KEYD_ENTER:
1584 saveStringEnter = 0;
1585 if (ex_slots[save_slot].desc[0])
1586 M_DoSave(save_page, save_slot);
1587 break;
1588
1589 default:
1590 ch = toupper(ch);
1591 SYS_ASSERT(save_style);
1592 if (ch >= 32 && ch <= 127 &&
1593 saveCharIndex < SAVESTRINGSIZE - 1 &&
1594 save_style->fonts[1]->StringWidth(ex_slots[save_slot].desc) <
1595 (SAVESTRINGSIZE - 2) * 8)
1596 {
1597 ex_slots[save_slot].desc[saveCharIndex++] = ch;
1598 ex_slots[save_slot].desc[saveCharIndex] = 0;
1599 }
1600 break;
1601 }
1602 return true;
1603 }
1604
1605 // F-Keys
1606 if (!menuactive)
1607 {
1608 switch (ch)
1609 {
1610 case KEYD_MINUS: // Screen size down
1611
1612 if (automapactive || chat_on)
1613 return false;
1614
1615 screen_hud = (screen_hud - 1 + NUMHUD) % NUMHUD;
1616
1617 S_StartFX(sfx_stnmov);
1618 return true;
1619
1620 case KEYD_EQUALS: // Screen size up
1621
1622 if (automapactive || chat_on)
1623 return false;
1624
1625 screen_hud = (screen_hud + 1) % NUMHUD;
1626
1627 S_StartFX(sfx_stnmov);
1628 return true;
1629
1630 case KEYD_F2: // Save
1631
1632 M_StartControlPanel();
1633 S_StartFX(sfx_swtchn);
1634 M_SaveGame(0);
1635 return true;
1636
1637 case KEYD_F3: // Load
1638
1639 M_StartControlPanel();
1640 S_StartFX(sfx_swtchn);
1641 M_LoadGame(0);
1642 return true;
1643
1644 case KEYD_F4: // Sound Volume
1645
1646 M_StartControlPanel();
1647 currentMenu = &SoundDef;
1648 itemOn = sfx_vol;
1649 S_StartFX(sfx_swtchn);
1650 return true;
1651
1652 case KEYD_F5: // Detail toggle, now loads options menu
1653 // -KM- 1998/07/31 F5 now loads options menu, detail is obsolete.
1654
1655 S_StartFX(sfx_swtchn);
1656 M_StartControlPanel();
1657 M_Options(0);
1658 return true;
1659
1660 case KEYD_F6: // Quicksave
1661
1662 S_StartFX(sfx_swtchn);
1663 M_QuickSave();
1664 return true;
1665
1666 case KEYD_F7: // End game
1667
1668 S_StartFX(sfx_swtchn);
1669 M_EndGame(0);
1670 return true;
1671
1672 case KEYD_F8: // Toggle messages
1673
1674 M_ChangeMessages(0);
1675 S_StartFX(sfx_swtchn);
1676 return true;
1677
1678 case KEYD_F9: // Quickload
1679
1680 S_StartFX(sfx_swtchn);
1681 M_QuickLoad();
1682 return true;
1683
1684 case KEYD_F10: // Quit DOOM
1685
1686 S_StartFX(sfx_swtchn);
1687 M_QuitEDGE(0);
1688 return true;
1689
1690 case KEYD_F11: // gamma toggle
1691
1692 var_gamma++;
1693 if (var_gamma > 5)
1694 var_gamma = 0;
1695
1696 const char *msg = NULL;
1697
1698 switch(var_gamma)
1699 {
1700 case 0: { msg = language["GammaOff"]; break; }
1701 case 1: { msg = language["GammaLevelOne"]; break; }
1702 case 2: { msg = language["GammaLevelTwo"]; break; }
1703 case 3: { msg = language["GammaLevelThree"]; break; }
1704 case 4: { msg = language["GammaLevelFour"]; break; }
1705 case 5: { msg = language["GammaLevelFive"]; break; }
1706
1707 default: { msg = NULL; break; }
1708 }
1709
1710 if (msg)
1711 CON_PlayerMessage(consoleplayer, "%s", msg);
1712
1713 // -AJA- 1999/07/03: removed PLAYPAL reference.
1714 return true;
1715
1716 }
1717
1718 // Pop-up menu?
1719 if (ch == KEYD_ESCAPE)
1720 {
1721 M_StartControlPanel();
1722 S_StartFX(sfx_swtchn);
1723 return true;
1724 }
1725 return false;
1726 }
1727
1728 // Keys usable within menu
1729 switch (ch)
1730 {
1731 case KEYD_DOWNARROW:
1732 case KEYD_WHEEL_DN:
1733 do
1734 {
1735 if (itemOn + 1 > currentMenu->numitems - 1)
1736 itemOn = 0;
1737 else
1738 itemOn++;
1739 S_StartFX(sfx_pstop);
1740 }
1741 while (currentMenu->menuitems[itemOn].status == -1);
1742 return true;
1743
1744 case KEYD_UPARROW:
1745 case KEYD_WHEEL_UP:
1746 do
1747 {
1748 if (itemOn == 0)
1749 itemOn = currentMenu->numitems - 1;
1750 else
1751 itemOn--;
1752 S_StartFX(sfx_pstop);
1753 }
1754 while (currentMenu->menuitems[itemOn].status == -1);
1755 return true;
1756
1757 case KEYD_PGUP:
1758 case KEYD_LEFTARROW:
1759 if (currentMenu->menuitems[itemOn].select_func &&
1760 currentMenu->menuitems[itemOn].status == 2)
1761 {
1762 S_StartFX(sfx_stnmov);
1763 // 98-7-10 KM Use new defines
1764 (* currentMenu->menuitems[itemOn].select_func)(SLIDERLEFT);
1765 }
1766 return true;
1767
1768 case KEYD_PGDN:
1769 case KEYD_RIGHTARROW:
1770 if (currentMenu->menuitems[itemOn].select_func &&
1771 currentMenu->menuitems[itemOn].status == 2)
1772 {
1773 S_StartFX(sfx_stnmov);
1774 // 98-7-10 KM Use new defines
1775 (* currentMenu->menuitems[itemOn].select_func)(SLIDERRIGHT);
1776 }
1777 return true;
1778
1779 case KEYD_ENTER:
1780 case KEYD_MOUSE1:
1781 if (currentMenu->menuitems[itemOn].select_func &&
1782 currentMenu->menuitems[itemOn].status)
1783 {
1784 currentMenu->lastOn = itemOn;
1785 (* currentMenu->menuitems[itemOn].select_func)(itemOn);
1786 S_StartFX(sfx_pistol);
1787 }
1788 return true;
1789
1790 case KEYD_ESCAPE:
1791 case KEYD_MOUSE2:
1792 case KEYD_MOUSE3:
1793 currentMenu->lastOn = itemOn;
1794 M_ClearMenus();
1795 S_StartFX(sfx_swtchx);
1796 return true;
1797
1798 case KEYD_BACKSPACE:
1799 currentMenu->lastOn = itemOn;
1800 if (currentMenu->prevMenu)
1801 {
1802 currentMenu = currentMenu->prevMenu;
1803 itemOn = currentMenu->lastOn;
1804 S_StartFX(sfx_swtchn);
1805 }
1806 return true;
1807
1808 default:
1809 for (i = itemOn + 1; i < currentMenu->numitems; i++)
1810 if (currentMenu->menuitems[i].alpha_key == ch)
1811 {
1812 itemOn = i;
1813 S_StartFX(sfx_pstop);
1814 return true;
1815 }
1816 for (i = 0; i <= itemOn; i++)
1817 if (currentMenu->menuitems[i].alpha_key == ch)
1818 {
1819 itemOn = i;
1820 S_StartFX(sfx_pstop);
1821 return true;
1822 }
1823 break;
1824
1825 }
1826
1827 return false;
1828 }
1829
1830
M_StartControlPanel(void)1831 void M_StartControlPanel(void)
1832 {
1833 // intro might call this repeatedly
1834 if (menuactive)
1835 return;
1836
1837 menuactive = true;
1838 CON_SetVisible(vs_notvisible);
1839
1840 currentMenu = &MainDef; // JDC
1841 itemOn = currentMenu->lastOn; // JDC
1842
1843 M_OptCheckNetgame();
1844 }
1845
1846
FindChar(std::string & str,char ch,int pos)1847 static int FindChar(std::string& str, char ch, int pos)
1848 {
1849 SYS_ASSERT(pos <= (int)str.size());
1850
1851 const char *scan = strchr(str.c_str() + pos, ch);
1852
1853 if (! scan)
1854 return -1;
1855
1856 return (int)(scan - str.c_str());
1857 }
1858
1859
GetMiddle(std::string & str,int pos,int len)1860 static std::string GetMiddle(std::string& str, int pos, int len)
1861 {
1862 SYS_ASSERT(pos >= 0 && len >= 0);
1863 SYS_ASSERT(pos + len <= (int)str.size());
1864
1865 if (len == 0)
1866 return std::string();
1867
1868 return std::string(str.c_str() + pos, len);
1869 }
1870
1871
DrawMessage(void)1872 static void DrawMessage(void)
1873 {
1874 short x, y;
1875
1876 HUD_SetAlpha(0.64f);
1877 HUD_SolidBox(0, 0, 320, 200, T_BLACK);
1878 HUD_SetAlpha();
1879
1880 // FIXME: HU code should support center justification: this
1881 // would remove the code duplication below...
1882
1883 std::string msg(msg_string);
1884
1885 std::string input(input_string);
1886
1887 if (msg_mode == 2)
1888 input += "_";
1889
1890 // Calc required height
1891 SYS_ASSERT(dialog_style);
1892
1893 std::string s = msg + input;
1894
1895 y = 100 - (dialog_style->fonts[0]->StringLines(s.c_str()) *
1896 dialog_style->fonts[0]->NominalHeight()/ 2);
1897
1898 if (!msg.empty())
1899 {
1900 int oldpos = 0;
1901 int pos;
1902
1903 do
1904 {
1905 pos = FindChar(msg, '\n', oldpos);
1906
1907 if (pos < 0)
1908 s = std::string(msg, oldpos);
1909 else
1910 s = GetMiddle(msg, oldpos, pos-oldpos);
1911
1912 if (s.size() > 0)
1913 {
1914 x = 160 - (dialog_style->fonts[0]->StringWidth(s.c_str()) / 2);
1915 HL_WriteText(dialog_style,0, x, y, s.c_str());
1916 }
1917
1918 y += dialog_style->fonts[0]->NominalHeight();
1919
1920 oldpos = pos + 1;
1921 }
1922 while (pos >= 0 && oldpos < (int)msg.size());
1923 }
1924
1925 if (! input.empty())
1926 {
1927 int oldpos = 0;
1928 int pos;
1929
1930 do
1931 {
1932 pos = FindChar(input, '\n', oldpos);
1933
1934 if (pos < 0)
1935 s = std::string(input, oldpos);
1936 else
1937 s = GetMiddle(input, oldpos, pos-oldpos);
1938
1939 if (s.size() > 0)
1940 {
1941 x = 160 - (dialog_style->fonts[1]->StringWidth(s.c_str()) / 2);
1942 HL_WriteText(dialog_style,1, x, y, s.c_str());
1943 }
1944
1945 y += dialog_style->fonts[1]->NominalHeight();
1946
1947 oldpos = pos + 1;
1948 }
1949 while (pos >= 0 && oldpos < (int)input.size());
1950 }
1951 }
1952
1953 //
1954 // Called after the view has been rendered,
1955 // but before it has been blitted.
1956 //
M_Drawer(void)1957 void M_Drawer(void)
1958 {
1959 short x, y;
1960
1961 unsigned int i;
1962 unsigned int max;
1963
1964 if (!menuactive)
1965 return;
1966
1967 // Horiz. & Vertically center string and print it.
1968 if (msg_mode)
1969 {
1970 DrawMessage();
1971 return;
1972 }
1973
1974 // new options menu enable, use that drawer instead
1975 if (option_menuon)
1976 {
1977 M_OptDrawer();
1978 return;
1979 }
1980
1981 if (netgame_menuon)
1982 {
1983 M_NetGameDrawer();
1984 return;
1985 }
1986
1987 style_c *style = currentMenu->style_var[0];
1988 SYS_ASSERT(style);
1989
1990 HUD_SetAlpha(0.64f);
1991 HUD_SolidBox(0, 0, 320, 200, T_BLACK);
1992 HUD_SetAlpha();
1993
1994 // call Draw routine
1995 if (currentMenu->draw_func)
1996 (* currentMenu->draw_func)();
1997
1998 // DRAW MENU
1999 x = currentMenu->x;
2000 y = currentMenu->y;
2001 max = currentMenu->numitems;
2002
2003 for (i = 0; i < max; i++, y += LINEHEIGHT)
2004 {
2005 // ignore blank lines
2006 if (! currentMenu->menuitems[i].patch_name[0])
2007 continue;
2008
2009 if (! currentMenu->menuitems[i].image)
2010 currentMenu->menuitems[i].image = W_ImageLookup(
2011 currentMenu->menuitems[i].patch_name);
2012
2013 const image_c *image = currentMenu->menuitems[i].image;
2014
2015 HUD_DrawImage(x, y, image);
2016 }
2017
2018 // DRAW SKULL
2019 {
2020 int sx = x + SKULLXOFF;
2021 int sy = currentMenu->y - 5 + itemOn * LINEHEIGHT;
2022
2023 HUD_DrawImage(sx, sy, menu_skull[whichSkull]);
2024 }
2025 }
2026
M_ClearMenus(void)2027 void M_ClearMenus(void)
2028 {
2029 // -AJA- 2007/12/24: save user changes ASAP (in case of crash)
2030 if (menuactive)
2031 {
2032 M_SaveDefaults();
2033 }
2034
2035 menuactive = false;
2036 save_screenshot_valid = false;
2037 }
2038
M_SetupNextMenu(menu_t * menudef)2039 void M_SetupNextMenu(menu_t * menudef)
2040 {
2041 currentMenu = menudef;
2042 itemOn = currentMenu->lastOn;
2043 }
2044
M_Ticker(void)2045 void M_Ticker(void)
2046 {
2047 // update language if it changed
2048 if (m_language.CheckModified())
2049 if (! language.Select(m_language.str))
2050 I_Printf("Unknown language: %s\n", m_language.str);
2051
2052 if (option_menuon)
2053 {
2054 M_OptTicker();
2055 return;
2056 }
2057
2058 if (netgame_menuon)
2059 {
2060 M_NetGameTicker();
2061 return;
2062 }
2063
2064 if (--skullAnimCounter <= 0)
2065 {
2066 whichSkull ^= 1;
2067 skullAnimCounter = 8;
2068 }
2069 }
2070
M_Init(void)2071 void M_Init(void)
2072 {
2073 E_ProgressMessage(language["MiscInfo"]);
2074
2075 currentMenu = &MainDef;
2076 menuactive = false;
2077 itemOn = currentMenu->lastOn;
2078 whichSkull = 0;
2079 skullAnimCounter = 10;
2080 msg_mode = 0;
2081 msg_string.clear();
2082 msg_lastmenu = menuactive;
2083 quickSaveSlot = -1;
2084
2085 // lookup styles
2086 styledef_c *def;
2087
2088 def = styledefs.Lookup("MENU");
2089 if (! def) def = default_style;
2090 menu_def_style = hu_styles.Lookup(def);
2091
2092 def = styledefs.Lookup("MAIN MENU");
2093 main_menu_style = def ? hu_styles.Lookup(def) : menu_def_style;
2094
2095 def = styledefs.Lookup("CHOOSE EPISODE");
2096 episode_style = def ? hu_styles.Lookup(def) : menu_def_style;
2097
2098 def = styledefs.Lookup("CHOOSE SKILL");
2099 skill_style = def ? hu_styles.Lookup(def) : menu_def_style;
2100
2101 def = styledefs.Lookup("LOAD MENU");
2102 load_style = def ? hu_styles.Lookup(def) : menu_def_style;
2103
2104 def = styledefs.Lookup("SAVE MENU");
2105 save_style = def ? hu_styles.Lookup(def) : menu_def_style;
2106
2107 def = styledefs.Lookup("DIALOG");
2108 dialog_style = def ? hu_styles.Lookup(def) : menu_def_style;
2109
2110 def = styledefs.Lookup("SOUND VOLUME");
2111 if (! def) def = styledefs.Lookup("OPTIONS");
2112 if (! def) def = default_style;
2113 sound_vol_style = hu_styles.Lookup(def);
2114
2115 // lookup required images
2116 therm_l = W_ImageLookup("M_THERML");
2117 therm_m = W_ImageLookup("M_THERMM");
2118 therm_r = W_ImageLookup("M_THERMR");
2119 therm_o = W_ImageLookup("M_THERMO");
2120
2121 menu_loadg = W_ImageLookup("M_LOADG");
2122 menu_saveg = W_ImageLookup("M_SAVEG");
2123 menu_svol = W_ImageLookup("M_SVOL");
2124 menu_newgame = W_ImageLookup("M_NEWG");
2125 menu_skill = W_ImageLookup("M_SKILL");
2126 menu_episode = W_ImageLookup("M_EPISOD");
2127 menu_skull[0] = W_ImageLookup("M_SKULL1");
2128 menu_skull[1] = W_ImageLookup("M_SKULL2");
2129
2130 if (W_CheckNumForName("M_HTIC") >= 0)
2131 menu_doom = W_ImageLookup("M_HTIC");
2132 else
2133 menu_doom = W_ImageLookup("M_DOOM");
2134
2135 // Here we could catch other version dependencies,
2136 // like HELP1/2, and four episodes.
2137 // if (W_CheckNumForName("M_EPI4") < 0)
2138 // EpiDef.numitems -= 2;
2139 // else if (W_CheckNumForName("M_EPI5") < 0)
2140 // EpiDef.numitems--;
2141
2142 if (W_CheckNumForName("HELP") >= 0)
2143 menu_readthis[0] = W_ImageLookup("HELP");
2144 else
2145 menu_readthis[0] = W_ImageLookup("HELP1");
2146
2147 if (W_CheckNumForName("HELP2") >= 0)
2148 menu_readthis[1] = W_ImageLookup("HELP2");
2149 else
2150 {
2151 menu_readthis[1] = W_ImageLookup("CREDIT");
2152
2153 // This is used because DOOM 2 had only one HELP
2154 // page. I use CREDIT as second page now, but
2155 // kept this hack for educational purposes.
2156
2157 MainMenu[readthis] = MainMenu[quitdoom];
2158 MainDef.numitems--;
2159 MainDef.y += 8; // FIXME
2160 SkillDef.prevMenu = &MainDef;
2161 ReadDef1.draw_func = M_DrawReadThis1;
2162 ReadDef1.x = 330;
2163 ReadDef1.y = 165;
2164 ReadMenu1[0].select_func = M_FinishReadThis;
2165 }
2166
2167 sfx_swtchn = sfxdefs.GetEffect("SWTCHN");
2168 sfx_tink = sfxdefs.GetEffect("TINK");
2169 sfx_radio = sfxdefs.GetEffect("RADIO");
2170 sfx_oof = sfxdefs.GetEffect("OOF");
2171 sfx_pstop = sfxdefs.GetEffect("PSTOP");
2172 sfx_stnmov = sfxdefs.GetEffect("STNMOV");
2173 sfx_pistol = sfxdefs.GetEffect("PISTOL");
2174 sfx_swtchx = sfxdefs.GetEffect("SWTCHX");
2175
2176 M_OptMenuInit();
2177 M_NetGameInit();
2178 }
2179
2180
2181 //--- editor settings ---
2182 // vi:ts=4:sw=4:noexpandtab
2183