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