doom

a minimalistic implementation of doom
git clone git://ssnf.xyz/doom
Log | Files | Refs

m_menu.c (24509B)


      1 #include <unistd.h>
      2 #include <sys/types.h>
      3 #include <sys/stat.h>
      4 #include <fcntl.h>
      5 #include <stdlib.h>
      6 #include <ctype.h>
      7 
      8 #include "m_swap.h"
      9 #include "doomdef.h"
     10 #include "d_main.h"
     11 #include "i_system.h"
     12 #include "i_video.h"
     13 #include "z_zone.h"
     14 #include "v_video.h"
     15 #include "w_wad.h"
     16 #include "r_local.h"
     17 #include "hu_stuff.h"
     18 #include "g_game.h"
     19 #include "m_argv.h"
     20 #include "s_sound.h"
     21 #include "doomstat.h"
     22 #include "sounds.h"
     23 #include "m_menu.h"
     24 
     25 #define SAVESTRINGSIZE 	24
     26 #define SKULLXOFF		-32
     27 #define LINEHEIGHT		16
     28 
     29 typedef struct {
     30 	short	status;
     31 	char	name[10];
     32 	void	(*routine)(int choice);
     33 	char	alphaKey;			
     34 } menuitem_t;
     35 
     36 typedef struct menu_s {
     37 	short		numitems;	
     38 	struct menu_s*	prevMenu;	
     39 	menuitem_t*		menuitems;	
     40 	void		(*routine)();	
     41 	short		x;
     42 	short		y;		
     43 	short		lastOn;		
     44 } menu_t;
     45 
     46 extern patch_t*		hu_font[HU_FONTSIZE];
     47 extern boolean		message_dontfuckwithme;
     48 extern boolean		chat_on;		
     49 int			mouseSensitivity;       
     50 int			showMessages;
     51 int			detailLevel;		
     52 int			screenblocks;		
     53 int			screenSize;		
     54 int			quickSaveSlot;          
     55 int			messageToPrint;
     56 char*          messageString;		
     57 int			messx;			
     58 int			messy;
     59 int			messageLastMenuActive;
     60 boolean			messageNeedsInput;     
     61 void   (*messageRoutine)(int response);
     62 int saveStringEnter;              
     63 int saveSlot;	
     64 int saveCharIndex;	
     65 char			saveOldString[SAVESTRINGSIZE];  
     66 boolean			inhelpscreens;
     67 boolean			menuactive;
     68 extern boolean		sendpause;
     69 char			savegamestrings[10][SAVESTRINGSIZE];
     70 char    tempstring[160];
     71 char	endstring[160];
     72 short		itemOn;			
     73 short		skullAnimCounter;	
     74 short		whichSkull;		
     75 menu_t*	currentMenu;                          
     76 int     epi;
     77 
     78 int gammamsg[] = {
     79 	GAMMALVL0,
     80 	GAMMALVL1,
     81 	GAMMALVL2,
     82 	GAMMALVL3,
     83 	GAMMALVL4
     84 };
     85 
     86 char skullName[2][/*8*/9] = {"M_SKULL1", "M_SKULL2"};
     87 char    detailNames[2][9]	= {"M_GDHIGH", "M_GDLOW"};
     88 char	msgNames[2][9]		= {"M_MSGOFF", "M_MSGON"};
     89 int quitsounds[8] = {
     90 	sfx_pldeth,
     91 	sfx_dmpain,
     92 	sfx_popain,
     93 	sfx_slop,
     94 	sfx_telept,
     95 	sfx_posit1,
     96 	sfx_posit3,
     97 	sfx_sgtatk
     98 };
     99 
    100 int quitsounds2[8] = {
    101 	sfx_vilact,
    102 	sfx_getpow,
    103 	sfx_boscub,
    104 	sfx_slop,
    105 	sfx_skeswg,
    106 	sfx_kntdth,
    107 	sfx_bspact,
    108 	sfx_sgtatk
    109 };
    110 
    111 
    112 void M_NewGame(int choice);
    113 void M_Episode(int choice);
    114 void M_ChooseSkill(int choice);
    115 void M_LoadGame(int choice);
    116 void M_SaveGame(int choice);
    117 void M_Options(int choice);
    118 void M_EndGame(int choice);
    119 void M_ReadThis(int choice);
    120 void M_ReadThis2(int choice);
    121 void M_QuitDOOM(int choice);
    122 void M_ChangeMessages(int choice);
    123 void M_ChangeSensitivity(int choice);
    124 void M_SfxVol(int choice);
    125 void M_MusicVol(int choice);
    126 void M_ChangeDetail(int choice);
    127 void M_SizeDisplay(int choice);
    128 void M_StartGame(int choice);
    129 void M_Sound(int choice);
    130 void M_FinishReadThis(int choice);
    131 void M_LoadSelect(int choice);
    132 void M_SaveSelect(int choice);
    133 void M_ReadSaveStrings();
    134 void M_QuickSave();
    135 void M_QuickLoad();
    136 void M_DrawMainMenu();
    137 void M_DrawReadThis1();
    138 void M_DrawReadThis2();
    139 void M_DrawNewGame();
    140 void M_DrawEpisode();
    141 void M_DrawOptions();
    142 void M_DrawSound();
    143 void M_DrawLoad();
    144 void M_DrawSave();
    145 void M_DrawSaveLoadBorder(int x, int y);
    146 void M_SetupNextMenu(menu_t* menudef);
    147 void M_DrawThermo(int x, int y, int thermWidth, int thermDot);
    148 void M_DrawEmptyCell(menu_t* menu, int item);
    149 void M_DrawSelCell(menu_t* menu, int item);
    150 void M_WriteText(int x, int y, char* string);
    151 int  M_StringWidth(char* string);
    152 int  M_StringHeight(char* string);
    153 void M_StartControlPanel();
    154 void M_StartMessage(char* string, void (*routine)(int), boolean input);
    155 void M_StopMessage();
    156 void M_ClearMenus();
    157 
    158 enum {
    159 	newgame = 0,
    160 	options,
    161 	loadgame,
    162 	savegame,
    163 	readthis,
    164 	quitdoom,
    165 	main_end
    166 } main_e;
    167 
    168 menuitem_t MainMenu[] = {
    169 	{1, "M_NGAME", M_NewGame, 'n'},
    170 	{1, "M_OPTION", M_Options, 'o'},
    171 	{1, "M_LOADG", M_LoadGame, 'l'},
    172 	{1, "M_SAVEG", M_SaveGame, 's'},
    173 	
    174 	{1, "M_RDTHIS", M_ReadThis, 'r'},
    175 	{1, "M_QUITG", M_QuitDOOM, 'q'}
    176 };
    177 
    178 menu_t MainDef = {
    179 	main_end,
    180 	NULL,
    181 	MainMenu,
    182 	M_DrawMainMenu,
    183 	97, 64,
    184 	0
    185 };
    186 
    187 enum {
    188 	ep1,
    189 	ep2,
    190 	ep3,
    191 	ep4,
    192 	ep_end
    193 } episodes_e;
    194 
    195 menuitem_t EpisodeMenu[] = {
    196 	{1, "M_EPI1", M_Episode, 'k'},
    197 	{1, "M_EPI2", M_Episode, 't'},
    198 	{1, "M_EPI3", M_Episode, 'i'},
    199 	{1, "M_EPI4", M_Episode, 't'}
    200 };
    201 
    202 menu_t EpiDef = {
    203 	ep_end,
    204 	&MainDef,
    205 	EpisodeMenu,
    206 	M_DrawEpisode,
    207 	48, 63,              
    208 	ep1			
    209 };
    210 
    211 enum {
    212 	killthings,
    213 	toorough,
    214 	hurtme,
    215 	violence,
    216 	nightmare,
    217 	newg_end
    218 } newgame_e;
    219 
    220 menuitem_t NewGameMenu[] = {
    221 	{1, "M_JKILL", 	M_ChooseSkill, 'i'},
    222 	{1, "M_ROUGH", 	M_ChooseSkill, 'h'},
    223 	{1, "M_HURT", 	M_ChooseSkill, 'h'},
    224 	{1, "M_ULTRA", 	M_ChooseSkill, 'u'},
    225 	{1, "M_NMARE", 	M_ChooseSkill, 'n'}
    226 };
    227 
    228 menu_t NewDef = {
    229 	newg_end, 		
    230 	&EpiDef, 		
    231 	NewGameMenu, 	
    232 	M_DrawNewGame, 	
    233 	48, 63,              
    234 	hurtme		
    235 };
    236 
    237 enum {
    238 	endgame,
    239 	messages,
    240 	detail,
    241 	scrnsize,
    242 	option_empty1,
    243 	mousesens,
    244 	option_empty2,
    245 	soundvol,
    246 	opt_end
    247 } options_e;
    248 
    249 menuitem_t OptionsMenu[] = {
    250 	{1, "M_ENDGAM", 	M_EndGame, 'e'},
    251 	{1, "M_MESSG", 	M_ChangeMessages, 'm'},
    252 	{1, "M_DETAIL", 	M_ChangeDetail, 'g'},
    253 	{2, "M_SCRNSZ", 	M_SizeDisplay, 's'},
    254 	{-1, "", 0},
    255 	{2, "M_MSENS", 	M_ChangeSensitivity, 'm'},
    256 	{-1, "", 0},
    257 	{1, "M_SVOL", 	M_Sound, 's'}
    258 };
    259 
    260 menu_t OptionsDef = {
    261 	opt_end,
    262 	&MainDef,
    263 	OptionsMenu,
    264 	M_DrawOptions,
    265 	60, 37,
    266 	0
    267 };
    268 
    269 enum {
    270 	rdthsempty1,
    271 	read1_end
    272 } read_e;
    273 
    274 menuitem_t ReadMenu1[] = {
    275 	{1, "", M_ReadThis2, 0}
    276 };
    277 
    278 menu_t  ReadDef1 = {
    279 	read1_end,
    280 	&MainDef,
    281 	ReadMenu1,
    282 	M_DrawReadThis1,
    283 	280, 185,
    284 	0
    285 };
    286 
    287 enum {
    288 	rdthsempty2,
    289 	read2_end
    290 } read_e2;
    291 
    292 menuitem_t ReadMenu2[] = {
    293 	{1, "", M_FinishReadThis, 0}
    294 };
    295 
    296 menu_t ReadDef2 = {
    297 	read2_end,
    298 	&ReadDef1,
    299 	ReadMenu2,
    300 	M_DrawReadThis2,
    301 	330, 175,
    302 	0
    303 };
    304 
    305 enum {
    306 	sfx_vol,
    307 	sfx_empty1,
    308 	music_vol,
    309 	sfx_empty2,
    310 	sound_end
    311 } sound_e;
    312 
    313 menuitem_t SoundMenu[] = {
    314 	{2, "M_SFXVOL", M_SfxVol, 's'},
    315 	{-1, "", 0},
    316 	{2, "M_MUSVOL", M_MusicVol, 'm'},
    317 	{-1, "", 0}
    318 };
    319 
    320 menu_t SoundDef = {
    321 	sound_end,
    322 	&OptionsDef,
    323 	SoundMenu,
    324 	M_DrawSound,
    325 	80, 64,
    326 	0
    327 };
    328 
    329 enum {
    330 	load1,
    331 	load2,
    332 	load3,
    333 	load4,
    334 	load5,
    335 	load6,
    336 	load_end
    337 } load_e;
    338 
    339 menuitem_t LoadMenu[] = {
    340 	{1, "", M_LoadSelect, '1'},
    341 	{1, "", M_LoadSelect, '2'},
    342 	{1, "", M_LoadSelect, '3'},
    343 	{1, "", M_LoadSelect, '4'},
    344 	{1, "", M_LoadSelect, '5'},
    345 	{1, "", M_LoadSelect, '6'}
    346 };
    347 
    348 menu_t LoadDef = {
    349 	load_end,
    350 	&MainDef,
    351 	LoadMenu,
    352 	M_DrawLoad,
    353 	80, 54,
    354 	0
    355 };
    356 
    357 menuitem_t SaveMenu[] = {
    358 	{1, "", M_SaveSelect, '1'},
    359 	{1, "", M_SaveSelect, '2'},
    360 	{1, "", M_SaveSelect, '3'},
    361 	{1, "", M_SaveSelect, '4'},
    362 	{1, "", M_SaveSelect, '5'},
    363 	{1, "", M_SaveSelect, '6'}
    364 };
    365 
    366 menu_t SaveDef = {
    367 	load_end,
    368 	&MainDef,
    369 	SaveMenu,
    370 	M_DrawSave,
    371 	80, 54,
    372 	0
    373 };
    374 
    375 void
    376 M_ReadSaveStrings()
    377 {
    378 	int  i, handle;
    379 	char name[256];
    380 	
    381 	for (i = 0;i < load_end;i++) {
    382 		if (M_CheckParm("-cdrom"))
    383 			sprintf(name, "c:\\doomdata\\%s%d.dsg", lang[SAVEGAMENAME], i);
    384 		else
    385 			sprintf(name, "%s%d.dsg", lang[SAVEGAMENAME], i);
    386 		handle = open(name, O_RDONLY | 0, 0666);
    387 		if (handle == -1) {
    388 			strcpy(&savegamestrings[i][0], lang[EMPTYSTRING]);
    389 			LoadMenu[i].status = 0;
    390 			continue;
    391 		}
    392 		read(handle, &savegamestrings[i], SAVESTRINGSIZE);
    393 		close(handle);
    394 		LoadMenu[i].status = 1;
    395 	}
    396 }
    397 
    398 void
    399 M_DrawLoad()
    400 {
    401 	int i;
    402 	
    403 	V_DrawPatch(72, 28, 0, W_CacheLumpName("M_LOADG", PU_CACHE));
    404 	for (i = 0; i < load_end; ++i) {
    405 		M_DrawSaveLoadBorder(LoadDef.x, LoadDef.y+LINEHEIGHT*i);
    406 		M_WriteText(LoadDef.x, LoadDef.y+LINEHEIGHT*i, savegamestrings[i]);
    407 	}
    408 }
    409 
    410 void
    411 M_DrawSaveLoadBorder(int x, int y)
    412 {
    413 	int i;
    414 	
    415 	V_DrawPatch(x - 8, y + 7, 0, W_CacheLumpName("M_LSLEFT", PU_CACHE));
    416 	for (i = 0; i < 24; ++i) {
    417 		V_DrawPatch(x, y+7, 0, W_CacheLumpName("M_LSCNTR", PU_CACHE));
    418 		x += 8;
    419 	}
    420 	V_DrawPatch(x, y + 7, 0, W_CacheLumpName("M_LSRGHT", PU_CACHE));
    421 }
    422 
    423 void
    424 M_LoadSelect(int choice)
    425 {
    426 	char name[256];
    427 	
    428 	if (M_CheckParm("-cdrom"))
    429 		sprintf(name, "c:\\doomdata\\%s%d.dsg", lang[SAVEGAMENAME], choice);
    430 	else
    431 		sprintf(name, "%s%d.dsg", lang[SAVEGAMENAME], choice);
    432 	G_LoadGame(name);
    433 	M_ClearMenus();
    434 }
    435 
    436 void
    437 M_LoadGame(int choice)
    438 {
    439 	if (netgame) {
    440 		M_StartMessage(lang[LOADNET], NULL, false);
    441 		return;
    442 	}
    443 	M_SetupNextMenu(&LoadDef);
    444 	M_ReadSaveStrings();
    445 }
    446 
    447 void
    448 M_DrawSave()
    449 {
    450 	int i;
    451 	
    452 	V_DrawPatch(72, 28, 0, W_CacheLumpName("M_SAVEG", PU_CACHE));
    453 	for (i = 0; i < load_end; ++i) {
    454 		M_DrawSaveLoadBorder(LoadDef.x, LoadDef.y+LINEHEIGHT*i);
    455 		M_WriteText(LoadDef.x, LoadDef.y+LINEHEIGHT*i, savegamestrings[i]);
    456 	}
    457 	if (saveStringEnter) {
    458 		i = M_StringWidth(savegamestrings[saveSlot]);
    459 		M_WriteText(LoadDef.x + i, LoadDef.y+LINEHEIGHT*saveSlot, "_");
    460 	}
    461 }
    462 
    463 void
    464 M_DoSave(int slot)
    465 {
    466 	G_SaveGame(slot, savegamestrings[slot]);
    467 	M_ClearMenus();
    468 	if (quickSaveSlot == -2) quickSaveSlot = slot;
    469 }
    470 
    471 void
    472 M_SaveSelect(int choice)
    473 {
    474 	saveStringEnter = 1;
    475 	saveSlot = choice;
    476 	strcpy(saveOldString, savegamestrings[choice]);
    477 	if (!strcmp(savegamestrings[choice], lang[EMPTYSTRING]))
    478 		savegamestrings[choice][0] = 0;
    479 	saveCharIndex = strlen(savegamestrings[choice]);
    480 }
    481 
    482 void
    483 M_SaveGame(int choice)
    484 {
    485 	if (!usergame) {
    486 		M_StartMessage(lang[SAVEDEAD], NULL, false);
    487 		return;
    488 	}
    489 	if (gamestate != GS_LEVEL)
    490 		return;
    491 	M_SetupNextMenu(&SaveDef);
    492 	M_ReadSaveStrings();
    493 }
    494 
    495 void
    496 M_QuickSaveResponse(int ch)
    497 {
    498 	if (ch == 'y') {
    499 		M_DoSave(quickSaveSlot);
    500 		S_StartSound(NULL, sfx_swtchx);
    501 	}
    502 }
    503 
    504 void
    505 M_QuickSave()
    506 {
    507 	if (!usergame) {
    508 		S_StartSound(NULL, sfx_oof);
    509 		return;
    510 	}
    511 	if (gamestate != GS_LEVEL) return;
    512 	if (quickSaveSlot < 0) {
    513 		M_StartControlPanel();
    514 		M_ReadSaveStrings();
    515 		M_SetupNextMenu(&SaveDef);
    516 		quickSaveSlot = -2;	
    517 		return;
    518 	}
    519 	sprintf(tempstring, lang[QSPROMPT], savegamestrings[quickSaveSlot]);
    520 	M_StartMessage(tempstring, M_QuickSaveResponse, true);
    521 }
    522 
    523 void
    524 M_QuickLoadResponse(int ch)
    525 {
    526 	if (ch == 'y') {
    527 		M_LoadSelect(quickSaveSlot);
    528 		S_StartSound(NULL, sfx_swtchx);
    529 	}
    530 }
    531 
    532 void
    533 M_QuickLoad()
    534 {
    535 	if (netgame) {
    536 		M_StartMessage(lang[QLOADNET], NULL, false);
    537 		return;
    538 	}
    539 	if (quickSaveSlot < 0) {
    540 		M_StartMessage(lang[QSAVESPOT], NULL, false);
    541 		return;
    542 	}
    543 	sprintf(tempstring, lang[QLPROMPT], savegamestrings[quickSaveSlot]);
    544 	M_StartMessage(tempstring, M_QuickLoadResponse, true);
    545 }
    546 
    547 void
    548 M_DrawReadThis1()
    549 {
    550 	inhelpscreens = true;
    551 	V_DrawPatch(0, 0, 0, W_CacheLumpName("HELP1", PU_CACHE));
    552 	return;
    553 }
    554 
    555 void
    556 M_DrawReadThis2()
    557 {
    558 	inhelpscreens = true;
    559 	V_DrawPatch(0, 0, 0, W_CacheLumpName("HELP2", PU_CACHE));
    560 	return;
    561 }
    562 
    563 void
    564 M_DrawSound()
    565 {
    566 	V_DrawPatch(60, 38, 0, W_CacheLumpName("M_SVOL", PU_CACHE));
    567 	M_DrawThermo(SoundDef.x, SoundDef.y+LINEHEIGHT*(sfx_vol+1), 16, snd_SfxVolume);
    568 	M_DrawThermo(SoundDef.x, SoundDef.y+LINEHEIGHT*(music_vol+1), 16, snd_MusicVolume);
    569 }
    570 
    571 void
    572 M_Sound(int choice)
    573 {
    574 	M_SetupNextMenu(&SoundDef);
    575 }
    576 
    577 void
    578 M_SfxVol(int choice)
    579 {
    580 	switch (choice) {
    581 		case 0:
    582 			if (snd_SfxVolume)
    583 			snd_SfxVolume--;
    584 			break;
    585 		case 1:
    586 			if (snd_SfxVolume < 15)
    587 			snd_SfxVolume++;
    588 			break;
    589 		}
    590 	S_SetSfxVolume(snd_SfxVolume /* *8 */);
    591 }
    592 
    593 void
    594 M_MusicVol(int choice)
    595 {
    596 	switch (choice) {
    597 		case 0:
    598 			if (snd_MusicVolume) snd_MusicVolume--;
    599 			break;
    600 		case 1:
    601 			if (snd_MusicVolume < 15) snd_MusicVolume++;
    602 			break;
    603 	}
    604 	S_SetMusicVolume(snd_MusicVolume /* *8 */);
    605 }
    606 
    607 void
    608 M_DrawMainMenu()
    609 {
    610 	V_DrawPatch(94, 2, 0, W_CacheLumpName("M_DOOM", PU_CACHE));
    611 }
    612 
    613 void
    614 M_DrawNewGame()
    615 {
    616 	V_DrawPatch(96, 14, 0, W_CacheLumpName("M_NEWG", PU_CACHE));
    617 	V_DrawPatch(54, 38, 0, W_CacheLumpName("M_SKILL", PU_CACHE));
    618 }
    619 
    620 void
    621 M_NewGame(int choice)
    622 {
    623 	if (netgame && !demoplayback) {
    624 		M_StartMessage(lang[NEWGAME], NULL, false);
    625 		return;
    626 	}
    627 	M_SetupNextMenu(&EpiDef);
    628 }
    629 
    630 
    631 void M_DrawEpisode()
    632 {
    633 	V_DrawPatch(54, 38, 0, W_CacheLumpName("M_EPISOD", PU_CACHE));
    634 }
    635 
    636 void
    637 M_VerifyNightmare(int ch)
    638 {
    639 	if (ch != 'y') return;
    640 	G_DeferedInitNew(nightmare, epi+1, 1);
    641 	M_ClearMenus();
    642 }
    643 
    644 void
    645 M_ChooseSkill(int choice)
    646 {
    647 	if (choice == nightmare) {
    648 		M_StartMessage(lang[NIGHTMARE], M_VerifyNightmare, true);
    649 		return;
    650 	}
    651 	G_DeferedInitNew(choice, epi+1, 1);
    652 	M_ClearMenus();
    653 }
    654 
    655 void
    656 M_Episode(int choice)
    657 {
    658 	if (choice) {
    659 		M_StartMessage(lang[SWSTRING], NULL, false);
    660 		M_SetupNextMenu(&ReadDef1);
    661 		return;
    662 	}
    663 	epi = choice;
    664 	M_SetupNextMenu(&NewDef);
    665 }
    666 
    667 
    668 void
    669 M_DrawOptions()
    670 {
    671 	V_DrawPatch(108, 15, 0, W_CacheLumpName("M_OPTTTL", PU_CACHE));
    672 	V_DrawPatch(OptionsDef.x + 175, OptionsDef.y+LINEHEIGHT*detail, 0, W_CacheLumpName(detailNames[detailLevel], PU_CACHE));
    673 	V_DrawPatch(OptionsDef.x + 120, OptionsDef.y+LINEHEIGHT*messages, 0, W_CacheLumpName(msgNames[showMessages], PU_CACHE));
    674 	M_DrawThermo(OptionsDef.x, OptionsDef.y+LINEHEIGHT*(mousesens+1), 10, mouseSensitivity);
    675 	M_DrawThermo(OptionsDef.x, OptionsDef.y+LINEHEIGHT*(scrnsize+1), 9, screenSize);
    676 }
    677 
    678 void
    679 M_Options(int choice)
    680 {
    681 	M_SetupNextMenu(&OptionsDef);
    682 }
    683 
    684 void
    685 M_ChangeMessages(int choice)
    686 {
    687 	choice = 0;
    688 	showMessages = 1 - showMessages;
    689 	if (!showMessages)
    690 		players[consoleplayer].message = lang[MSGOFF];
    691 	else
    692 		players[consoleplayer].message = lang[MSGON];
    693 	message_dontfuckwithme = true;
    694 }
    695 
    696 void
    697 M_EndGameResponse(int ch)
    698 {
    699 	if (ch != 'y') return;
    700 	currentMenu->lastOn = itemOn;
    701 	M_ClearMenus();
    702 	D_StartTitle();
    703 }
    704 
    705 void
    706 M_EndGame(int choice)
    707 {
    708 	choice = 0;
    709 	if (!usergame) {
    710 		S_StartSound(NULL, sfx_oof);
    711 		return;
    712 	}
    713 	if (netgame) {
    714 		M_StartMessage(lang[NETEND], NULL, false);
    715 		return;
    716 	}
    717 	M_StartMessage(lang[ENDGAME], M_EndGameResponse, true);
    718 }
    719 
    720 void
    721 M_ReadThis(int choice)
    722 {
    723 	choice = 0;
    724 	M_SetupNextMenu(&ReadDef1);
    725 }
    726 
    727 void
    728 M_ReadThis2(int choice)
    729 {
    730 	choice = 0;
    731 	M_SetupNextMenu(&ReadDef2);
    732 }
    733 
    734 void
    735 M_FinishReadThis(int choice)
    736 {
    737 	choice = 0;
    738 	M_SetupNextMenu(&MainDef);
    739 }
    740 
    741 
    742 
    743 void
    744 M_QuitResponse(int ch)
    745 {
    746 	if (ch != 'y') return;
    747 	if (!netgame) {
    748 		S_StartSound(NULL, quitsounds[(gametic>>2)&7]);
    749 		I_WaitVBL(105);
    750 	}
    751 	I_Quit();
    752 }
    753 
    754 void
    755 M_QuitDOOM(int choice)
    756 {
    757 	sprintf(endstring, "%s\n\n%s", lang[DOSY], lang[QUITMSG0 + (gametic % (QUITMSG21 - QUITMSG0 + 2))]);
    758 	M_StartMessage(endstring, M_QuitResponse, true);
    759 }
    760 
    761 void
    762 M_ChangeSensitivity(int choice)
    763 {
    764 	switch (choice) {
    765 		case 0:
    766 			if (mouseSensitivity) mouseSensitivity--;
    767 			break;
    768 		case 1:
    769 			if (mouseSensitivity < 9) mouseSensitivity++;
    770 			break;
    771 	}
    772 }
    773 
    774 void
    775 M_ChangeDetail(int choice)
    776 {
    777 	choice = 0;
    778 	detailLevel = 1 - detailLevel;
    779 	fprintf(stderr, "M_ChangeDetail: low detail mode n.a.\n");
    780 	return;
    781 }
    782 
    783 void
    784 M_SizeDisplay(int choice)
    785 {
    786 	if (!choice && screenSize > 0) {
    787 		--screenblocks;
    788 		--screenSize;
    789 	}
    790 	if (choice == 1 && screenSize < 8) {
    791 		++screenblocks;
    792 		++screenSize;
    793 	}
    794 	R_SetViewSize(screenblocks, detailLevel);
    795 }
    796 
    797 void
    798 M_DrawThermo(int x, int y, int thermWidth, int thermDot)
    799 {
    800 	int		xx;
    801 	int		i;
    802 
    803 	xx = x;
    804 	V_DrawPatch(xx, y, 0, W_CacheLumpName("M_THERML", PU_CACHE));
    805 	xx += 8;
    806 	for (i = 0; i < thermWidth; ++i) {
    807 		V_DrawPatch(xx, y, 0, W_CacheLumpName("M_THERMM", PU_CACHE));
    808 		xx += 8;
    809 	}
    810 	V_DrawPatch(xx, y, 0, W_CacheLumpName("M_THERMR", PU_CACHE));
    811 	V_DrawPatch((x+8) + thermDot*8, y, 0, W_CacheLumpName("M_THERMO", PU_CACHE));
    812 }
    813 
    814 void
    815 M_DrawEmptyCell(menu_t* menu, int item)
    816 {
    817 	V_DrawPatch(menu->x - 10, menu->y+item*LINEHEIGHT - 1, 0, W_CacheLumpName("M_CELL1", PU_CACHE));
    818 }
    819 
    820 void
    821 M_DrawSelCell(menu_t* menu, int item)
    822 {
    823 	V_DrawPatch(menu->x - 10, menu->y+item*LINEHEIGHT - 1, 0, W_CacheLumpName("M_CELL2", PU_CACHE));
    824 }
    825 
    826 void
    827 M_StartMessage(char* string, void (*routine)(int), boolean input)
    828 {
    829 	messageLastMenuActive = menuactive;
    830 	messageToPrint = 1;
    831 	messageString = string;
    832 	messageRoutine = routine;
    833 	messageNeedsInput = input;
    834 	menuactive = true;
    835 	return;
    836 }
    837 
    838 void
    839 M_StopMessage()
    840 {
    841 	menuactive = messageLastMenuActive;
    842 	messageToPrint = 0;
    843 }
    844 
    845 int
    846 M_StringWidth(char* string)
    847 {
    848 	int i, w, c;
    849 	
    850 	w = 0;
    851 	for (i = 0;i < strlen(string);i++) {
    852 		c = toupper(string[i]) - HU_FONTSTART;
    853 		if (c < 0 || c >= HU_FONTSIZE) w += 4;
    854 		else w += SHORT(hu_font[c]->width);
    855 	}
    856 	return w;
    857 }
    858 
    859 int
    860 M_StringHeight(char* string)
    861 {
    862 	int i, h, height;
    863 
    864 	height = SHORT(hu_font[0]->height);
    865 	h = height;
    866 	for (i = 0;i < strlen(string);i++)
    867 		if (string[i] == '\n') h += height;
    868 	return h;
    869 }
    870 
    871 void
    872 M_WriteText(int x, int y, char* string)
    873 {
    874 	char*	ch;
    875 	int		c, cx, cy, w;
    876 		
    877 	ch = string;
    878 	cx = x;
    879 	cy = y;
    880 	while (1) {
    881 		c = *ch++;
    882 		if (!c) break;
    883 		if (c == '\n') {
    884 			cx = x;
    885 			cy += 12;
    886 			continue;
    887 		}
    888 		c = toupper(c) - HU_FONTSTART;
    889 		if (c < 0 || c>= HU_FONTSIZE) {
    890 			cx += 4;
    891 			continue;
    892 		}
    893 		w = SHORT(hu_font[c]->width);
    894 		if (cx+w > SCREENWIDTH) break;
    895 		V_DrawPatch(cx, cy, 0, hu_font[c]);
    896 		cx+=w;
    897 	}
    898 }
    899 
    900 boolean
    901 M_Responder(event_t* ev)
    902 {
    903 	int        i, ch;
    904 	static int joywait, mousewait, mousey, mousex, lasty, lastx;
    905 	
    906 	joywait = mousewait = mousey = mousex = lasty = lastx = 0;
    907 	ch = -1;
    908 	if (ev->type == ev_joystick && joywait < I_GetTime()) {
    909 		if (ev->data3 == -1) {
    910 			ch = KEY_UPARROW;
    911 			joywait = I_GetTime() + 5;
    912 		}
    913 		else if (ev->data3 == 1) {
    914 			ch = KEY_DOWNARROW;
    915 			joywait = I_GetTime() + 5;
    916 		}
    917 		if (ev->data2 == -1) {
    918 			ch = KEY_LEFTARROW;
    919 			joywait = I_GetTime() + 2;
    920 		}
    921 		else if (ev->data2 == 1) {
    922 			ch = KEY_RIGHTARROW;
    923 			joywait = I_GetTime() + 2;
    924 		}
    925 		if (ev->data1&1) {
    926 			ch = KEY_ENTER;
    927 			joywait = I_GetTime() + 5;
    928 		}
    929 		if (ev->data1&2) {
    930 			ch = KEY_BACKSPACE;
    931 			joywait = I_GetTime() + 5;
    932 		}
    933 	} else {
    934 		if (ev->type == ev_mouse && mousewait < I_GetTime()) {
    935 			mousey += ev->data3;
    936 			if (mousey < lasty-30) {
    937 				ch = KEY_DOWNARROW;
    938 				mousewait = I_GetTime() + 5;
    939 				mousey = lasty -= 30;
    940 			} else if (mousey > lasty+30) {
    941 				ch = KEY_UPARROW;
    942 				mousewait = I_GetTime() + 5;
    943 				mousey = lasty += 30;
    944 			}
    945 			mousex += ev->data2;
    946 			if (mousex < lastx-30) {
    947 				ch = KEY_LEFTARROW;
    948 				mousewait = I_GetTime() + 5;
    949 				mousex = lastx -= 30;
    950 			}
    951 			else if (mousex > lastx+30) {
    952 				ch = KEY_RIGHTARROW;
    953 				mousewait = I_GetTime() + 5;
    954 				mousex = lastx += 30;
    955 			}
    956 			if (ev->data1&1) {
    957 				ch = KEY_ENTER;
    958 				mousewait = I_GetTime() + 15;
    959 			}
    960 			if (ev->data1&2) {
    961 				ch = KEY_BACKSPACE;
    962 				mousewait = I_GetTime() + 15;
    963 			}
    964 		} else {
    965 			if (ev->type == ev_keydown) ch = ev->data1;
    966 		}
    967 	}
    968 	if (ch == -1) return false;
    969 	if (saveStringEnter) {
    970 		switch (ch) {
    971 		case KEY_BACKSPACE:
    972 			if (saveCharIndex > 0) {
    973 				saveCharIndex--;
    974 				savegamestrings[saveSlot][saveCharIndex] = 0;
    975 			}
    976 			break;
    977 		case KEY_ESCAPE:
    978 			saveStringEnter = 0;
    979 			strcpy(&savegamestrings[saveSlot][0], saveOldString);
    980 			break;
    981 		case KEY_ENTER:
    982 			saveStringEnter = 0;
    983 			if (savegamestrings[saveSlot][0]) M_DoSave(saveSlot);
    984 			break;
    985 		default:
    986 			ch = toupper(ch);
    987 			if (ch != 32)
    988 				if (ch-HU_FONTSTART < 0 || ch-HU_FONTSTART >= HU_FONTSIZE)
    989 					break;
    990 			if (ch >= 32 && ch <= 127 && saveCharIndex < SAVESTRINGSIZE-1 && M_StringWidth(savegamestrings[saveSlot]) < (SAVESTRINGSIZE-2)*8) {
    991 				savegamestrings[saveSlot][saveCharIndex++] = ch;
    992 				savegamestrings[saveSlot][saveCharIndex] = 0;
    993 			}
    994 			break;
    995 		}
    996 		return true;
    997 	}
    998 	if (messageToPrint) {
    999 		if (messageNeedsInput && !(ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE)) return false;
   1000 		menuactive = messageLastMenuActive;
   1001 		messageToPrint = 0;
   1002 		if (messageRoutine) messageRoutine(ch);
   1003 		menuactive = false;
   1004 		S_StartSound(NULL, sfx_swtchx);
   1005 		return true;
   1006 	}
   1007 	if (!menuactive) {
   1008 		switch (ch) {
   1009 			case KEY_MINUS:         
   1010 				if (automapactive || chat_on) return false;
   1011 				M_SizeDisplay(0);
   1012 				S_StartSound(NULL, sfx_stnmov);
   1013 				return true;
   1014 			case KEY_EQUALS:        
   1015 				if (automapactive || chat_on) return false;
   1016 				M_SizeDisplay(1);
   1017 				S_StartSound(NULL, sfx_stnmov);
   1018 				return true;
   1019 			case KEY_F1:
   1020 				M_StartControlPanel();
   1021 				currentMenu = &ReadDef1;
   1022 				itemOn = 0;
   1023 				S_StartSound(NULL, sfx_swtchn);
   1024 				return true;
   1025 			case KEY_F2:            
   1026 				M_StartControlPanel();
   1027 				S_StartSound(NULL, sfx_swtchn);
   1028 				M_SaveGame(0);
   1029 				return true;
   1030 			case KEY_F3:            
   1031 				M_StartControlPanel();
   1032 				S_StartSound(NULL, sfx_swtchn);
   1033 				M_LoadGame(0);
   1034 				return true;
   1035 			case KEY_F4:            
   1036 				M_StartControlPanel();
   1037 				currentMenu = &SoundDef;
   1038 				itemOn = sfx_vol;
   1039 				S_StartSound(NULL, sfx_swtchn);
   1040 				return true;
   1041 			case KEY_F5:            
   1042 				M_ChangeDetail(0);
   1043 				S_StartSound(NULL, sfx_swtchn);
   1044 				return true;
   1045 			case KEY_F6:            
   1046 				S_StartSound(NULL, sfx_swtchn);
   1047 				M_QuickSave();
   1048 				return true;
   1049 			case KEY_F7:            
   1050 				S_StartSound(NULL, sfx_swtchn);
   1051 				M_EndGame(0);
   1052 				return true;
   1053 			case KEY_F8:            
   1054 				M_ChangeMessages(0);
   1055 				S_StartSound(NULL, sfx_swtchn);
   1056 				return true;
   1057 			case KEY_F9:            
   1058 				S_StartSound(NULL, sfx_swtchn);
   1059 				M_QuickLoad();
   1060 				return true;
   1061 			case KEY_F10:           
   1062 				S_StartSound(NULL, sfx_swtchn);
   1063 				M_QuitDOOM(0);
   1064 				return true;
   1065 			case KEY_F11:           
   1066 				usegamma++;
   1067 				if (usegamma > 4) usegamma = 0;
   1068 				players[consoleplayer].message = lang[gammamsg[usegamma]];
   1069 				I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE));
   1070 				return true;
   1071 		}
   1072 	}
   1073 	if (!menuactive) {
   1074 		if (ch == KEY_ESCAPE) {
   1075 			M_StartControlPanel();
   1076 			S_StartSound(NULL, sfx_swtchn);
   1077 			return true;
   1078 		}
   1079 		return false;
   1080 	}
   1081 	switch (ch) {
   1082 		case KEY_DOWNARROW:
   1083 			do {
   1084 				if (itemOn+1 > currentMenu->numitems-1) itemOn = 0;
   1085 				else itemOn++;
   1086 				S_StartSound(NULL, sfx_pstop);
   1087 			} while (currentMenu->menuitems[itemOn].status==-1);
   1088 			return true;
   1089 		case KEY_UPARROW:
   1090 			do {
   1091 				if (!itemOn) itemOn = currentMenu->numitems-1;
   1092 				else itemOn--;
   1093 				S_StartSound(NULL, sfx_pstop);
   1094 			} while (currentMenu->menuitems[itemOn].status==-1);
   1095 			return true;
   1096 		case KEY_LEFTARROW:
   1097 			if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) {
   1098 				S_StartSound(NULL, sfx_stnmov);
   1099 				currentMenu->menuitems[itemOn].routine(0);
   1100 			}
   1101 			return true;
   1102 		case KEY_RIGHTARROW:
   1103 			if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) {
   1104 				S_StartSound(NULL, sfx_stnmov);
   1105 				currentMenu->menuitems[itemOn].routine(1);
   1106 			}
   1107 			return true;
   1108 		case KEY_ENTER:
   1109 			if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status) {
   1110 				currentMenu->lastOn = itemOn;
   1111 				if (currentMenu->menuitems[itemOn].status == 2) {
   1112 					currentMenu->menuitems[itemOn].routine(1);      
   1113 					S_StartSound(NULL, sfx_stnmov);
   1114 				} else {
   1115 					currentMenu->menuitems[itemOn].routine(itemOn);
   1116 					S_StartSound(NULL, sfx_pistol);
   1117 				}
   1118 			}
   1119 			return true;
   1120 		case KEY_ESCAPE:
   1121 			currentMenu->lastOn = itemOn;
   1122 			M_ClearMenus();
   1123 			S_StartSound(NULL, sfx_swtchx);
   1124 			return true;
   1125 		case KEY_BACKSPACE:
   1126 			currentMenu->lastOn = itemOn;
   1127 			if (currentMenu->prevMenu) {
   1128 				currentMenu = currentMenu->prevMenu;
   1129 				itemOn = currentMenu->lastOn;
   1130 				S_StartSound(NULL, sfx_swtchn);
   1131 			}
   1132 			return true;
   1133 		default:
   1134 			for (i = itemOn+1;i < currentMenu->numitems;i++)
   1135 				if (currentMenu->menuitems[i].alphaKey == ch) {
   1136 					itemOn = i;
   1137 					S_StartSound(NULL, sfx_pstop);
   1138 					return true;
   1139 				}
   1140 			for (i = 0;i <= itemOn;i++)
   1141 				if (currentMenu->menuitems[i].alphaKey == ch) {
   1142 					itemOn = i;
   1143 					S_StartSound(NULL, sfx_pstop);
   1144 					return true;
   1145 				}
   1146 			break;
   1147 		}
   1148 	return false;
   1149 }
   1150 
   1151 void
   1152 M_StartControlPanel()
   1153 {
   1154 	if (menuactive) return;
   1155 	menuactive = 1;
   1156 	currentMenu = &MainDef;         
   1157 	itemOn = currentMenu->lastOn;   
   1158 }
   1159 
   1160 void
   1161 M_Drawer()
   1162 {
   1163 	static short x, y;
   1164 	char         string[40];
   1165 	int          start;
   1166 	short        i, max;
   1167 
   1168 	inhelpscreens = false;
   1169 	if (messageToPrint) {
   1170 		start = 0;
   1171 		y = 100 - M_StringHeight(messageString)/2;
   1172 		while (messageString[start]) {
   1173 			for (i = 0; i < strlen(messageString+start); ++i)
   1174 				if ((messageString[start+i]) == '\n') {
   1175 					memset(string, 0, 40);
   1176 					strncpy(string, messageString+start, i);
   1177 					start += i + 1;
   1178 					break;
   1179 				}
   1180 			if (i == strlen(messageString+start)) {
   1181 				strcpy(string, messageString+start);
   1182 				start += i;
   1183 			}
   1184 			x = 160 - M_StringWidth(string)/2;
   1185 			M_WriteText(x, y, string);
   1186 			y += SHORT(hu_font[0]->height);
   1187 		}
   1188 		return;
   1189 	}
   1190 	if (!menuactive) return;
   1191 	if (currentMenu->routine) currentMenu->routine();         
   1192 	x = currentMenu->x;
   1193 	y = currentMenu->y;
   1194 	max = currentMenu->numitems;
   1195 	for (i = 0; i < max; ++i) {
   1196 		if (currentMenu->menuitems[i].name[0]) V_DrawPatch(x, y, 0, W_CacheLumpName(currentMenu->menuitems[i].name, PU_CACHE));
   1197 		y += LINEHEIGHT;
   1198 	}
   1199 	V_DrawPatch(x + SKULLXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT,  0, W_CacheLumpName(skullName[whichSkull], PU_CACHE));
   1200 }
   1201 
   1202 void
   1203 M_ClearMenus()
   1204 {
   1205 	menuactive = 0;
   1206 }
   1207 
   1208 void
   1209 M_SetupNextMenu(menu_t* menudef)
   1210 {
   1211 	currentMenu = menudef;
   1212 	itemOn = currentMenu->lastOn;
   1213 }
   1214 
   1215 void
   1216 M_Ticker()
   1217 {
   1218 	if (--skullAnimCounter <= 0) {
   1219 		whichSkull ^= 1;
   1220 		skullAnimCounter = 8;
   1221 	}
   1222 }
   1223 
   1224 void
   1225 M_Init()
   1226 {
   1227 	currentMenu = &MainDef;
   1228 	menuactive = 0;
   1229 	itemOn = currentMenu->lastOn;
   1230 	whichSkull = 0;
   1231 	skullAnimCounter = 10;
   1232 	screenSize = screenblocks - 3;
   1233 	messageToPrint = 0;
   1234 	messageString = NULL;
   1235 	messageLastMenuActive = menuactive;
   1236 	quickSaveSlot = -1;
   1237 	EpiDef.numitems--;
   1238 }