// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // $Id:$ // // Copyright (C) 1993-1996 by id Software, Inc. // // This source is available for distribution and/or modification // only under the terms of the DOOM Source Code License as // published by id Software. All rights reserved. // // The source is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License // for more details. // // $Log:$ // // DESCRIPTION: // DOOM selection menu, options, episode etc. // Sliders and icons. Kinda widget stuff. // //----------------------------------------------------------------------------- #include "doom_config.h" #include "doomdef.h" #include "dstrings.h" #include "d_main.h" #include "i_system.h" #include "i_video.h" #include "z_zone.h" #include "v_video.h" #include "w_wad.h" #include "r_local.h" #include "hu_stuff.h" #include "g_game.h" #include "m_argv.h" #include "m_swap.h" #include "s_sound.h" #include "doomstat.h" #include "sounds.h" // Data. #include "m_menu.h" #define SAVESTRINGSIZE 24 #define SKULLXOFF -32 #define LINEHEIGHT 16 // // MENU TYPEDEFS // typedef struct { // 0 = no cursor here, 1 = ok, 2 = arrows ok short status; char name[10]; // choice = menu item #. // if status = 2, // choice=0:leftarrow,1:rightarrow void (*routine)(int choice); // hotkey in menu char alphaKey; } menuitem_t; typedef struct menu_s { short numitems; // # of menu items struct menu_s* prevMenu; // previous menu menuitem_t* menuitems; // menu items void (*routine)(); // draw routine short x; short y; // x,y of menu short lastOn; // last item user was on in menu } menu_t; typedef struct { char* lump; int x, w; int offx; int offy; } menu_custom_text_seg_t; typedef struct { char* name; menu_custom_text_seg_t segs[16]; } menu_custom_text_t; extern int doom_flags; extern patch_t* hu_font[HU_FONTSIZE]; extern doom_boolean message_dontfuckwithme; extern doom_boolean chat_on; // in heads-up code extern int mousemove; // // defaulted values // int mouseSensitivity; // has default // Show messages has default, 0 = off, 1 = on int showMessages; // Blocky mode, has default, 0 = high, 1 = normal int detailLevel; int screenblocks; // has default // temp for screenblocks (0-9) int screenSize; // -1 = no quicksave slot picked! int quickSaveSlot; // 1 = message to be printed int messageToPrint; // ...and here is the message string! char* messageString; // message x & y int messx; int messy; int messageLastMenuActive; // timed message = no input from user doom_boolean messageNeedsInput; void (*messageRoutine)(int response); char gammamsg[5][26] = { GAMMALVL0, GAMMALVL1, GAMMALVL2, GAMMALVL3, GAMMALVL4 }; // we are going to be entering a savegame string int saveStringEnter; int saveSlot; // which slot to save in int saveCharIndex; // which char we're editing // old save description before edit char saveOldString[SAVESTRINGSIZE]; doom_boolean inhelpscreens; doom_boolean menuactive; extern doom_boolean sendpause; char savegamestrings[10][SAVESTRINGSIZE]; char endstring[160]; short itemOn; // menu item skull is on short skullAnimCounter; // skull animation counter short whichSkull; // which skull to draw // graphic name of skulls // warning: initializer-string for array of chars is too long char skullName[2][/*8*/9] = { "M_SKULL1","M_SKULL2" }; // current menudef menu_t* currentMenu; // We create new menu text by cutting into existing graphics and pasting them to create the new text. // This way we don't ship code with embeded graphics that come from WAD files. menu_custom_text_t menu_custom_texts[] = { {"TXT_MMOV", { {"M_MSENS", 0, 74, 0, 0}, // Mouse {"M_MSENS", 0, 31, 83, 0}, // Mo {"M_MSENS", 160, 14, 83 + 31, 0}, // v {"M_MSENS", 60, 14, 83 + 31 + 14, 0}, // e {"M_DETAIL", 169, 5, 83 + 31 + 14 + 14, 0}, // : {0} }}, {"TXT_MOPT", { {"M_MSENS", 0, 74, 0, 0}, // Mouse {"M_OPTION", 0, 92, 74 + 9, 0}, // Options {0} }}, {"TXT_CROS", { {"M_SKILL", 0, 16, 0, 0}, // C {"M_DETAIL", 14, 15, 16, 0}, // r {"M_SKILL", 46, 30, 16 + 15, 0}, // os {"M_SKILL", 62, 14, 16 + 15 + 30, 0}, // s {"M_SKILL", 16, 15, 16 + 15 + 30 + 14, 0}, // h {"M_DETAIL", 140, 19, 16 + 15 + 30 + 14 + 15, 0}, // ai {"M_DETAIL", 14, 15, 16 + 15 + 30 + 14 + 15 + 19, 0}, // r {"M_DETAIL", 169, 5, 16 + 15 + 30 + 14 + 15 + 19 + 15, 0}, // : {0} }}, {"TXT_ARUN", { {"M_SGTTL", 90, 17, 0, 0}, // A {"M_GDLOW", 0, 10, 17, 3}, // l {"M_GDLOW", 26, 16, 17 + 10, 3}, // {"M_DISP", 57, 30, 17 + 10 + 16, 0}, // ay {"M_RDTHIS", 99, 14, 17 + 10 + 16 + 30, 0}, // s {"M_RDTHIS", 0, 16, 17 + 10 + 16 + 30 + 14 + 7, 0}, // R {"M_SFXVOL", 90, 15, 17 + 10 + 16 + 30 + 14 + 7 + 16, 0}, // u {"M_OPTION", 62, 15, 17 + 10 + 16 + 30 + 14 + 7 + 16 + 15, 0}, // n {"M_DETAIL", 169, 5, 17 + 10 + 16 + 30 + 14 + 7 + 16 + 15 + 15, 0}, // : {0} }}, }; const int custom_texts_count = sizeof(menu_custom_texts) / sizeof(menu_custom_text_t); char tempstring[80]; int epi; char detailNames[2][9] = { "M_GDHIGH","M_GDLOW" }; char msgNames[2][9] = { "M_MSGOFF","M_MSGON" }; int quitsounds[8] = { sfx_pldeth, sfx_dmpain, sfx_popain, sfx_slop, sfx_telept, sfx_posit1, sfx_posit3, sfx_sgtatk }; int quitsounds2[8] = { sfx_vilact, sfx_getpow, sfx_boscub, sfx_slop, sfx_skeswg, sfx_kntdth, sfx_bspact, sfx_sgtatk }; // // PROTOTYPES // void M_NewGame(int choice); void M_Episode(int choice); void M_ChooseSkill(int choice); void M_LoadGame(int choice); void M_SaveGame(int choice); void M_Options(int choice); void M_EndGame(int choice); void M_ReadThis(int choice); void M_ReadThis2(int choice); void M_QuitDOOM(int choice); void M_ChangeMessages(int choice); void M_SfxVol(int choice); void M_MusicVol(int choice); void M_ChangeDetail(int choice); void M_MouseOptions(int choice); void M_SizeDisplay(int choice); void M_StartGame(int choice); void M_Sound(int choice); void M_ChangeCrosshair(int choice); void M_ChangeAlwaysRun(int choice); void M_MouseMove(int choice); void M_ChangeSensitivity(int choice); void M_FinishReadThis(int choice); void M_LoadSelect(int choice); void M_SaveSelect(int choice); void M_ReadSaveStrings(void); void M_QuickSave(void); void M_QuickLoad(void); void M_DrawMainMenu(void); void M_DrawReadThis1(void); void M_DrawReadThis2(void); void M_DrawNewGame(void); void M_DrawEpisode(void); void M_DrawOptions(void); void M_DrawSound(void); void M_DrawLoad(void); void M_DrawSave(void); void M_DrawSaveLoadBorder(int x, int y); void M_SetupNextMenu(menu_t* menudef); void M_DrawThermo(int x, int y, int thermWidth, int thermDot); void M_DrawEmptyCell(menu_t* menu, int item); void M_DrawSelCell(menu_t* menu, int item); void M_WriteText(int x, int y, char* string); int M_StringWidth(char* string); int M_StringHeight(char* string); void M_StartControlPanel(void); void M_StartMessage(char* string, void* routine, doom_boolean input); void M_StopMessage(void); void M_ClearMenus(void); void M_DrawMouseOptions(void); // // DOOM MENU // enum { newgame = 0, options, loadgame, savegame, readthis, quitdoom, main_end } main_e; menuitem_t MainMenu[] = { {1,"M_NGAME",M_NewGame,'n'}, {1,"M_OPTION",M_Options,'o'}, {1,"M_LOADG",M_LoadGame,'l'}, {1,"M_SAVEG",M_SaveGame,'s'}, // Another hickup with Special edition. {1,"M_RDTHIS",M_ReadThis,'r'}, {1,"M_QUITG",M_QuitDOOM,'q'} }; menu_t MainDef = { main_end, 0, MainMenu, M_DrawMainMenu, 97,64, 0 }; // // EPISODE SELECT // enum { ep1, ep2, ep3, ep4, ep_end } episodes_e; menuitem_t EpisodeMenu[] = { {1,"M_EPI1", M_Episode,'k'}, {1,"M_EPI2", M_Episode,'t'}, {1,"M_EPI3", M_Episode,'i'}, {1,"M_EPI4", M_Episode,'t'} }; menu_t EpiDef = { ep_end, // # of menu items &MainDef, // previous menu EpisodeMenu, // menuitem_t -> M_DrawEpisode, // drawing routine -> 48,63, // x,y ep1 // lastOn }; // // NEW GAME // enum { killthings, toorough, hurtme, violence, nightmare, newg_end } newgame_e; menuitem_t NewGameMenu[] = { {1,"M_JKILL", M_ChooseSkill, 'i'}, {1,"M_ROUGH", M_ChooseSkill, 'h'}, {1,"M_HURT", M_ChooseSkill, 'h'}, {1,"M_ULTRA", M_ChooseSkill, 'u'}, {1,"M_NMARE", M_ChooseSkill, 'n'} }; menu_t NewDef = { newg_end, // # of menu items &EpiDef, // previous menu NewGameMenu, // menuitem_t -> M_DrawNewGame, // drawing routine -> 48,63, // x,y hurtme // lastOn }; // // OPTIONS MENU // menuitem_t* OptionsMenu; enum { endgame, messages, crosshair_opt, always_run_opt, //detail, // Details do nothing? scrnsize, option_empty1, mouseoptions, soundvol, opt_end } options_e; menuitem_t OptionsMenuFull[] = { {1,"M_ENDGAM", M_EndGame,'e'}, {1,"M_MESSG", M_ChangeMessages,'m'}, {1,"TXT_CROS", M_ChangeCrosshair,'c'}, {1,"TXT_ARUN", M_ChangeAlwaysRun,'r'}, //{1,"M_DETAIL", M_ChangeDetail,'g'}, // Details do nothing? {2,"M_SCRNSZ", M_SizeDisplay,'s'}, {-1,"",0}, {1,"TXT_MOPT", M_MouseOptions,'f'}, {1,"M_SVOL", M_Sound,'s'} }; menu_t OptionsDef = { opt_end, &MainDef, OptionsMenuFull, M_DrawOptions, 60,37, 0 }; enum { endgame_no_mouse, messages_no_mouse, crosshair_opt_no_mouse, always_run_opt_no_mouse, //detail_no_mouse, // Details do nothing? scrnsize_no_mouse, option_empty1_no_mouse, soundvol_no_mouse, opt_end_no_mouse } options_e_no_mouse; menuitem_t OptionsMenuNoMouse[] = { {1,"M_ENDGAM", M_EndGame,'e'}, {1,"M_MESSG", M_ChangeMessages,'m'}, {1,"TXT_CROS", M_ChangeCrosshair,'c'}, {1,"TXT_ARUN", M_ChangeAlwaysRun,'r'}, //{1,"M_DETAIL", M_ChangeDetail,'g'}, // Details do nothing? {2,"M_SCRNSZ", M_SizeDisplay,'s'}, {-1,"",0}, {1,"M_SVOL", M_Sound,'s'} }; menu_t OptionsNoMouseDef = { opt_end_no_mouse, &MainDef, OptionsMenuNoMouse, M_DrawOptions, 60,37, 0 }; enum { endgame_no_sound, messages_no_sound, crosshair_opt_no_sound, always_run_opt_no_sound, //detail_no_sound, // Details do nothing? scrnsize_no_sound, option_empty1_no_sound, mouseoptions_no_sound, opt_end_no_sound } options_e_no_sound; menuitem_t OptionsMenuNoSound[] = { {1,"M_ENDGAM", M_EndGame,'e'}, {1,"M_MESSG", M_ChangeMessages,'m'}, {1,"TXT_CROS", M_ChangeCrosshair,'c'}, {1,"TXT_ARUN", M_ChangeAlwaysRun,'r'}, //{1,"M_DETAIL", M_ChangeDetail,'g'}, // Details do nothing? {2,"M_SCRNSZ", M_SizeDisplay,'s'}, {-1,"",0}, {1,"TXT_MOPT", M_MouseOptions,'f'} }; menu_t OptionsNoSoundDef = { opt_end_no_sound, &MainDef, OptionsMenuNoSound, M_DrawOptions, 60,37, 0 }; enum { endgame_no_sound_no_mouse, messages_no_sound_no_mouse, crosshair_opt_no_sound_no_mouse, always_run_top_no_sound_no_mouse, //detail_no_sound_no_mouse, // Details do nothing? scrnsize_no_sound_no_mouse, option_empty1_no_sound_no_mouse, opt_end_no_sound_no_mouse } options_e_no_sound_no_mouse; menuitem_t OptionsMenuNoSoundNoMouse[] = { {1,"M_ENDGAM", M_EndGame,'e'}, {1,"M_MESSG", M_ChangeMessages,'m'}, {1,"TXT_CROS", M_ChangeCrosshair,'c'}, {1,"TXT_ARUN", M_ChangeAlwaysRun,'r'}, //{1,"M_DETAIL", M_ChangeDetail,'g'}, // Details do nothing? {2,"M_SCRNSZ", M_SizeDisplay,'s'}, {-1,"",0} }; menu_t OptionsNoSoundNoMouseDef = { opt_end_no_sound_no_mouse, &MainDef, OptionsMenuNoSoundNoMouse, M_DrawOptions, 60,37, 0 }; // // MOUSE OPTIONS // enum { mousemov, mousesens, mouse_option_empty1, mouse_opt_end } mouseoptions_e; menuitem_t MouseOptionsMenu[] = { {1,"TXT_MMOV", M_MouseMove,'f'}, {2,"M_MSENS", M_ChangeSensitivity,'m'}, {-1,"",0}, }; menu_t MouseOptionsDef = { mouse_opt_end, &OptionsDef, MouseOptionsMenu, M_DrawMouseOptions, 60,70, 0 }; // // Read This! MENU 1 & 2 // enum { rdthsempty1, read1_end } read_e; menuitem_t ReadMenu1[] = { {1,"",M_ReadThis2,0} }; menu_t ReadDef1 = { read1_end, &MainDef, ReadMenu1, M_DrawReadThis1, 280,185, 0 }; enum { rdthsempty2, read2_end } read_e2; menuitem_t ReadMenu2[] = { {1,"",M_FinishReadThis,0} }; menu_t ReadDef2 = { read2_end, &ReadDef1, ReadMenu2, M_DrawReadThis2, 330,175, 0 }; // // SOUND VOLUME MENU // menuitem_t* SoundMenu; enum { sfx_vol, sfx_empty1, music_vol, sfx_empty2, sound_end } sound_e; menuitem_t SoundMenuFull[] = { {2,"M_SFXVOL",M_SfxVol,'s'}, {-1,"",0}, {2,"M_MUSVOL",M_MusicVol,'m'}, {-1,"",0} }; menu_t SoundDef = { sound_end, &OptionsDef, SoundMenuFull, M_DrawSound, 80,64, 0 }; enum { music_vol_no_sfx, sfx_empty2_no_sfx, sound_end_no_sfx } sound_e_no_sfx; menuitem_t SoundMenuNoSFX[] = { {2,"M_MUSVOL",M_MusicVol,'m'}, {-1,"",0} }; menu_t SoundNoSFXDef = { sound_end_no_sfx, &OptionsDef, SoundMenuNoSFX, M_DrawSound, 80,64, 0 }; enum { sfx_vol_no_music, sfx_empty1_no_music, sound_end_no_music } sound_e_no_music; menuitem_t SoundMenuNoMusic[] = { {2,"M_SFXVOL",M_SfxVol,'s'}, {-1,"",0} }; menu_t SoundNoMusicDef = { sound_end_no_music, &OptionsDef, SoundMenuNoMusic, M_DrawSound, 80,64, 0 }; // // LOAD GAME MENU // enum { load1, load2, load3, load4, load5, load6, load_end } load_e; menuitem_t LoadMenu[] = { {1,"", M_LoadSelect,'1'}, {1,"", M_LoadSelect,'2'}, {1,"", M_LoadSelect,'3'}, {1,"", M_LoadSelect,'4'}, {1,"", M_LoadSelect,'5'}, {1,"", M_LoadSelect,'6'} }; menu_t LoadDef = { load_end, &MainDef, LoadMenu, M_DrawLoad, 80,54, 0 }; // // SAVE GAME MENU // menuitem_t SaveMenu[] = { {1,"", M_SaveSelect,'1'}, {1,"", M_SaveSelect,'2'}, {1,"", M_SaveSelect,'3'}, {1,"", M_SaveSelect,'4'}, {1,"", M_SaveSelect,'5'}, {1,"", M_SaveSelect,'6'} }; menu_t SaveDef = { load_end, &MainDef, SaveMenu, M_DrawSave, 80,54, 0 }; // // M_DrawCustomMenuText // Draw several segments of patches to make up new text // void M_DrawCustomMenuText(char* name, int x, int y) { for (int i = 0; i < custom_texts_count; ++i) { menu_custom_text_t* custom_text = menu_custom_texts + i; if (doom_strcmp(custom_text->name, name) == 0) { menu_custom_text_seg_t* seg = custom_text->segs; while (seg->lump) { void* lump = W_CacheLumpName(seg->lump, PU_CACHE); V_DrawPatchRectDirect(x + seg->offx, y, 0, lump, seg->x, seg->w); ++seg; } break; } } } // // M_ReadSaveStrings // read the strings from the savegame files // void M_ReadSaveStrings(void) { void* handle; int count; int i; char name[256]; for (i = 0; i < load_end; i++) { #if 0 if (M_CheckParm("-cdrom")) //doom_sprintf(name, "c:\\doomdata\\" SAVEGAMENAME "%d.dsg", i); else #endif { //doom_sprintf(name, SAVEGAMENAME"%d.dsg", i); doom_strcpy(name, SAVEGAMENAME); doom_concat(name, doom_itoa(i, 10)); doom_strcpy(name, ".dsg"); } handle = doom_open(name, "r"); if (handle == 0) { doom_strcpy(&savegamestrings[i][0], EMPTYSTRING); LoadMenu[i].status = 0; continue; } count = doom_read(handle, &savegamestrings[i], SAVESTRINGSIZE); doom_close(handle); LoadMenu[i].status = 1; } } // // M_LoadGame & Cie. // void M_DrawLoad(void) { int i; V_DrawPatchDirect(72, 28, 0, W_CacheLumpName("M_LOADG", PU_CACHE)); for (i = 0; i < load_end; i++) { M_DrawSaveLoadBorder(LoadDef.x, LoadDef.y + LINEHEIGHT * i); M_WriteText(LoadDef.x, LoadDef.y + LINEHEIGHT * i, savegamestrings[i]); } } // // Draw border for the savegame description // void M_DrawSaveLoadBorder(int x, int y) { int i; V_DrawPatchDirect(x - 8, y + 7, 0, W_CacheLumpName("M_LSLEFT", PU_CACHE)); for (i = 0; i < 24; i++) { V_DrawPatchDirect(x, y + 7, 0, W_CacheLumpName("M_LSCNTR", PU_CACHE)); x += 8; } V_DrawPatchDirect(x, y + 7, 0, W_CacheLumpName("M_LSRGHT", PU_CACHE)); } // // User wants to load this game // void M_LoadSelect(int choice) { char name[256]; #if 0 if (M_CheckParm("-cdrom")) //doom_sprintf(name, "c:\\doomdata\\"SAVEGAMENAME"%d.dsg", choice); else #endif { //doom_sprintf(name, SAVEGAMENAME"%d.dsg", choice); doom_strcpy(name, SAVEGAMENAME); doom_concat(name, doom_itoa(choice, 10)); doom_strcpy(name, ".dsg"); } G_LoadGame(name); M_ClearMenus(); } // // Selected from DOOM menu // void M_LoadGame(int choice) { if (netgame) { M_StartMessage(LOADNET, 0, false); return; } M_SetupNextMenu(&LoadDef); M_ReadSaveStrings(); } // // M_SaveGame & Cie. // void M_DrawSave(void) { int i; V_DrawPatchDirect(72, 28, 0, W_CacheLumpName("M_SAVEG", PU_CACHE)); for (i = 0; i < load_end; i++) { M_DrawSaveLoadBorder(LoadDef.x, LoadDef.y + LINEHEIGHT * i); M_WriteText(LoadDef.x, LoadDef.y + LINEHEIGHT * i, savegamestrings[i]); } if (saveStringEnter) { i = M_StringWidth(savegamestrings[saveSlot]); M_WriteText(LoadDef.x + i, LoadDef.y + LINEHEIGHT * saveSlot, "_"); } } // // M_Responder calls this when user is finished // void M_DoSave(int slot) { G_SaveGame(slot, savegamestrings[slot]); M_ClearMenus(); // PICK QUICKSAVE SLOT YET? if (quickSaveSlot == -2) quickSaveSlot = slot; } // // User wants to save. Start string input for M_Responder // void M_SaveSelect(int choice) { // we are going to be intercepting all chars saveStringEnter = 1; saveSlot = choice; doom_strcpy(saveOldString, savegamestrings[choice]); if (!doom_strcmp(savegamestrings[choice], EMPTYSTRING)) savegamestrings[choice][0] = 0; saveCharIndex = (int)doom_strlen(savegamestrings[choice]); } // // Selected from DOOM menu // void M_SaveGame(int choice) { if (!usergame) { M_StartMessage(SAVEDEAD, 0, false); return; } if (gamestate != GS_LEVEL) return; M_SetupNextMenu(&SaveDef); M_ReadSaveStrings(); } // // M_QuickSave // void M_QuickSaveResponse(int ch) { if (ch == 'y') { M_DoSave(quickSaveSlot); S_StartSound(0, sfx_swtchx); } } void M_QuickSave(void) { if (!usergame) { S_StartSound(0, sfx_oof); return; } if (gamestate != GS_LEVEL) return; if (quickSaveSlot < 0) { M_StartControlPanel(); M_ReadSaveStrings(); M_SetupNextMenu(&SaveDef); quickSaveSlot = -2; // means to pick a slot now return; } //doom_sprintf(tempstring, QSPROMPT, savegamestrings[quickSaveSlot]); doom_strcpy(tempstring, QSPROMPT_1); doom_concat(tempstring, savegamestrings[quickSaveSlot]); doom_strcpy(tempstring, QSPROMPT_2); M_StartMessage(tempstring, M_QuickSaveResponse, true); } // // M_QuickLoad // void M_QuickLoadResponse(int ch) { if (ch == 'y') { M_LoadSelect(quickSaveSlot); S_StartSound(0, sfx_swtchx); } } void M_QuickLoad(void) { if (netgame) { M_StartMessage(QLOADNET, 0, false); return; } if (quickSaveSlot < 0) { M_StartMessage(QSAVESPOT, 0, false); return; } //doom_sprintf(tempstring, QLPROMPT, savegamestrings[quickSaveSlot]); doom_strcpy(tempstring, QLPROMPT_1); doom_concat(tempstring, savegamestrings[quickSaveSlot]); doom_strcpy(tempstring, QLPROMPT_2); M_StartMessage(tempstring, M_QuickLoadResponse, true); } // // Read This Menus // Had a "quick hack to fix romero bug" // void M_DrawReadThis1(void) { inhelpscreens = true; switch (gamemode) { case commercial: V_DrawPatchDirect(0, 0, 0, W_CacheLumpName("HELP", PU_CACHE)); break; case shareware: case registered: case retail: V_DrawPatchDirect(0, 0, 0, W_CacheLumpName("HELP1", PU_CACHE)); break; default: break; } return; } // // Read This Menus - optional second page. // void M_DrawReadThis2(void) { inhelpscreens = true; switch (gamemode) { case retail: case commercial: // This hack keeps us from having to change menus. V_DrawPatchDirect(0, 0, 0, W_CacheLumpName("CREDIT", PU_CACHE)); break; case shareware: case registered: V_DrawPatchDirect(0, 0, 0, W_CacheLumpName("HELP2", PU_CACHE)); break; default: break; } return; } // // Change Sfx & Music volumes // void M_DrawSound(void) { V_DrawPatchDirect(60, 38, 0, W_CacheLumpName("M_SVOL", PU_CACHE)); if (!(doom_flags & DOOM_FLAG_HIDE_SOUND_OPTIONS)) { int offset = (doom_flags & DOOM_FLAG_HIDE_MUSIC_OPTIONS) ? sfx_vol_no_music : sfx_vol; M_DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (offset + 1), 16, snd_SfxVolume); } if (!(doom_flags & DOOM_FLAG_HIDE_MUSIC_OPTIONS)) { int offset = (doom_flags & DOOM_FLAG_HIDE_SOUND_OPTIONS) ? music_vol_no_sfx : music_vol; M_DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (offset + 1), 16, snd_MusicVolume); } } void M_Sound(int choice) { M_SetupNextMenu(&SoundDef); } void M_MouseOptions(int choice) { M_SetupNextMenu(&MouseOptionsDef); } void M_SfxVol(int choice) { switch (choice) { case 0: if (snd_SfxVolume) snd_SfxVolume--; break; case 1: if (snd_SfxVolume < 15) snd_SfxVolume++; break; } S_SetSfxVolume(snd_SfxVolume /* *8 */); } void M_MusicVol(int choice) { switch (choice) { case 0: if (snd_MusicVolume) snd_MusicVolume--; break; case 1: if (snd_MusicVolume < 15) snd_MusicVolume++; break; } S_SetMusicVolume(snd_MusicVolume /* *8 */); } // // M_DrawMainMenu // void M_DrawMainMenu(void) { V_DrawPatchDirect(94, 2, 0, W_CacheLumpName("M_DOOM", PU_CACHE)); } // // M_NewGame // void M_DrawNewGame(void) { V_DrawPatchDirect(96, 14, 0, W_CacheLumpName("M_NEWG", PU_CACHE)); V_DrawPatchDirect(54, 38, 0, W_CacheLumpName("M_SKILL", PU_CACHE)); } void M_NewGame(int choice) { if (netgame && !demoplayback) { M_StartMessage(NEWGAME, 0, false); return; } if (gamemode == commercial) M_SetupNextMenu(&NewDef); else M_SetupNextMenu(&EpiDef); } // // M_Episode // void M_DrawEpisode(void) { V_DrawPatchDirect(54, 38, 0, W_CacheLumpName("M_EPISOD", PU_CACHE)); } void M_VerifyNightmare(int ch) { if (ch != 'y') return; G_DeferedInitNew(nightmare, epi + 1, 1); M_ClearMenus(); } void M_ChooseSkill(int choice) { if (choice == nightmare) { M_StartMessage(NIGHTMARE, M_VerifyNightmare, true); return; } G_DeferedInitNew(choice, epi + 1, 1); M_ClearMenus(); } void M_Episode(int choice) { if ((gamemode == shareware) && choice) { M_StartMessage(SWSTRING, 0, false); M_SetupNextMenu(&ReadDef1); return; } // Yet another hack... if ((gamemode == registered) && (choice > 2)) { doom_print( "M_Episode: 4th episode requires UltimateDOOM\n"); choice = 0; } epi = choice; M_SetupNextMenu(&NewDef); } // // M_Options // void M_DrawOptions(void) { V_DrawPatchDirect(108, 15, 0, W_CacheLumpName("M_OPTTTL", PU_CACHE)); //V_DrawPatchDirect (OptionsDef.x + 175,OptionsDef.y+LINEHEIGHT*detail,0, // W_CacheLumpName(detailNames[detailLevel],PU_CACHE)); // Details do nothing? V_DrawPatchDirect(OptionsDef.x + 120, OptionsDef.y + LINEHEIGHT * messages, 0, W_CacheLumpName(msgNames[showMessages], PU_CACHE)); extern int crosshair; V_DrawPatchDirect(OptionsDef.x + 131, OptionsDef.y + LINEHEIGHT * crosshair_opt, 0, W_CacheLumpName(msgNames[crosshair], PU_CACHE)); extern int always_run; V_DrawPatchDirect(OptionsDef.x + 147, OptionsDef.y + LINEHEIGHT * always_run_opt, 0, W_CacheLumpName(msgNames[always_run], PU_CACHE)); M_DrawThermo(OptionsDef.x, OptionsDef.y + LINEHEIGHT * (scrnsize + 1), 9, screenSize); } void M_DrawMouseOptions(void) { M_DrawCustomMenuText("TXT_MOPT", 74, 45); V_DrawPatchDirect(MouseOptionsDef.x + 149, MouseOptionsDef.y + LINEHEIGHT * mousemov, 0, W_CacheLumpName(msgNames[mousemove], PU_CACHE)); M_DrawThermo(MouseOptionsDef.x, MouseOptionsDef.y + LINEHEIGHT * (mousesens + 1), 10, mouseSensitivity); } void M_Options(int choice) { M_SetupNextMenu(&OptionsDef); } // // Toggle messages on/off // void M_ChangeMessages(int choice) { // warning: unused parameter `int choice' choice = 0; showMessages = 1 - showMessages; if (!showMessages) players[consoleplayer].message = MSGOFF; else players[consoleplayer].message = MSGON; message_dontfuckwithme = true; } // // Toggle crosshair on/off // void M_ChangeCrosshair(int choice) { extern int crosshair; // warning: unused parameter `int choice' choice = 0; crosshair = 1 - crosshair; if (!crosshair) players[consoleplayer].message = CROSSOFF; else players[consoleplayer].message = CROSSON; message_dontfuckwithme = true; } // // Toggle always-run on/off // void M_ChangeAlwaysRun(int choice) { extern int always_run; // warning: unused parameter `int choice' choice = 0; always_run = 1 - always_run; if (!always_run) players[consoleplayer].message = ALWAYSRUNOFF; else players[consoleplayer].message = ALWAYSRUNON; message_dontfuckwithme = true; } // // M_EndGame // void M_EndGameResponse(int ch) { if (ch != 'y') return; currentMenu->lastOn = itemOn; M_ClearMenus(); D_StartTitle(); } void M_EndGame(int choice) { choice = 0; if (!usergame) { S_StartSound(0, sfx_oof); return; } if (netgame) { M_StartMessage(NETEND, 0, false); return; } M_StartMessage(ENDGAME, M_EndGameResponse, true); } // // M_ReadThis // void M_ReadThis(int choice) { choice = 0; M_SetupNextMenu(&ReadDef1); } void M_ReadThis2(int choice) { choice = 0; M_SetupNextMenu(&ReadDef2); } void M_FinishReadThis(int choice) { choice = 0; M_SetupNextMenu(&MainDef); } // // M_QuitDOOM // void M_QuitResponse(int ch) { if (ch != 'y') return; if (!netgame) { if (gamemode == commercial) S_StartSound(0, quitsounds2[(gametic >> 2) & 7]); else S_StartSound(0, quitsounds[(gametic >> 2) & 7]); I_WaitVBL(105); } I_Quit(); } void M_QuitDOOM(int choice) { // We pick index 0 which is language sensitive, // or one at random, between 1 and maximum number. if (language != english) { //doom_sprintf(endstring, "%s\n\n"DOSY, endmsg[0]); doom_strcpy(endstring, endmsg[0]); doom_concat(endstring, "\n\n" DOSY); } else { //doom_sprintf(endstring, "%s\n\n" DOSY, endmsg[gametic % (NUM_QUITMESSAGES - 2) + 1]); doom_strcpy(endstring, endmsg[gametic % (NUM_QUITMESSAGES - 2) + 1]); doom_concat(endstring, "\n\n" DOSY); } M_StartMessage(endstring, M_QuitResponse, true); } void M_ChangeSensitivity(int choice) { switch (choice) { case 0: if (mouseSensitivity) mouseSensitivity--; break; case 1: if (mouseSensitivity < 9) mouseSensitivity++; break; } } void M_MouseMove(int choice) { choice = 0; mousemove = 1 - mousemove; return; } void M_ChangeDetail(int choice) { choice = 0; detailLevel = 1 - detailLevel; // FIXME - does not work. Remove anyway? doom_print("M_ChangeDetail: low detail mode n.a.\n"); } void M_SizeDisplay(int choice) { switch (choice) { case 0: if (screenSize > 0) { screenblocks--; screenSize--; } break; case 1: if (screenSize < 8) { screenblocks++; screenSize++; } break; } R_SetViewSize(screenblocks, detailLevel); } // // Menu Functions // void M_DrawThermo(int x, int y, int thermWidth, int thermDot) { int xx; int i; xx = x; V_DrawPatchDirect(xx, y, 0, W_CacheLumpName("M_THERML", PU_CACHE)); xx += 8; for (i = 0; i < thermWidth; i++) { V_DrawPatchDirect(xx, y, 0, W_CacheLumpName("M_THERMM", PU_CACHE)); xx += 8; } V_DrawPatchDirect(xx, y, 0, W_CacheLumpName("M_THERMR", PU_CACHE)); V_DrawPatchDirect((x + 8) + thermDot * 8, y, 0, W_CacheLumpName("M_THERMO", PU_CACHE)); } void M_DrawEmptyCell(menu_t* menu, int item) { V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1, 0, W_CacheLumpName("M_CELL1", PU_CACHE)); } void M_DrawSelCell(menu_t* menu, int item) { V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1, 0, W_CacheLumpName("M_CELL2", PU_CACHE)); } void M_StartMessage(char* string, void* routine, doom_boolean input) { messageLastMenuActive = menuactive; messageToPrint = 1; messageString = string; messageRoutine = routine; messageNeedsInput = input; menuactive = true; return; } void M_StopMessage(void) { menuactive = messageLastMenuActive; messageToPrint = 0; } // // Find string width from hu_font chars // int M_StringWidth(char* string) { int i; int w = 0; int c; for (i = 0; i < doom_strlen(string); i++) { c = doom_toupper(string[i]) - HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE) w += 4; else w += SHORT(hu_font[c]->width); } return w; } // // Find string height from hu_font chars // int M_StringHeight(char* string) { int i; int h; int height = SHORT(hu_font[0]->height); h = height; for (i = 0; i < doom_strlen(string); i++) if (string[i] == '\n') h += height; return h; } // // Write a string using the hu_font // void M_WriteText(int x, int y, char* string) { int w; char* ch; int c; int cx; int cy; ch = string; cx = x; cy = y; while (1) { c = *ch++; if (!c) break; if (c == '\n') { cx = x; cy += 12; continue; } c = doom_toupper(c) - HU_FONTSTART; if (c < 0 || c >= HU_FONTSIZE) { cx += 4; continue; } w = SHORT(hu_font[c]->width); if (cx + w > SCREENWIDTH) break; V_DrawPatchDirect(cx, cy, 0, hu_font[c]); cx += w; } } // // CONTROL PANEL // // // M_Responder // doom_boolean M_Responder(event_t* ev) { int ch; int i; static int joywait = 0; static int mousewait = 0; static int mousey = 0; static int lasty = 0; static int mousex = 0; static int lastx = 0; ch = -1; if (ev->type == ev_joystick && joywait < I_GetTime()) { if (ev->data3 == -1) { ch = KEY_UPARROW; joywait = I_GetTime() + 5; } else if (ev->data3 == 1) { ch = KEY_DOWNARROW; joywait = I_GetTime() + 5; } if (ev->data2 == -1) { ch = KEY_LEFTARROW; joywait = I_GetTime() + 2; } else if (ev->data2 == 1) { ch = KEY_RIGHTARROW; joywait = I_GetTime() + 2; } if (ev->data1 & 1) { ch = KEY_ENTER; joywait = I_GetTime() + 5; } if (ev->data1 & 2) { ch = KEY_BACKSPACE; joywait = I_GetTime() + 5; } } else { if (ev->type == ev_mouse && mousewait < I_GetTime()) { mousey += ev->data3; if (mousey < lasty - 30) { ch = KEY_DOWNARROW; mousewait = I_GetTime() + 5; mousey = lasty -= 30; } else if (mousey > lasty + 30) { ch = KEY_UPARROW; mousewait = I_GetTime() + 5; mousey = lasty += 30; } mousex += ev->data2; if (mousex < lastx - 30) { ch = KEY_LEFTARROW; mousewait = I_GetTime() + 5; mousex = lastx -= 30; } else if (mousex > lastx + 30) { ch = KEY_RIGHTARROW; mousewait = I_GetTime() + 5; mousex = lastx += 30; } if (ev->data1 & 1) { ch = KEY_ENTER; mousewait = I_GetTime() + 15; } if (ev->data1 & 2) { ch = KEY_BACKSPACE; mousewait = I_GetTime() + 15; } } else if (ev->type == ev_keydown) { ch = ev->data1; } } if (ch == -1) return false; // Save Game string input if (saveStringEnter) { switch (ch) { case KEY_BACKSPACE: if (saveCharIndex > 0) { saveCharIndex--; savegamestrings[saveSlot][saveCharIndex] = 0; } break; case KEY_ESCAPE: saveStringEnter = 0; doom_strcpy(&savegamestrings[saveSlot][0], saveOldString); break; case KEY_ENTER: saveStringEnter = 0; if (savegamestrings[saveSlot][0]) M_DoSave(saveSlot); break; default: ch = doom_toupper(ch); if (ch != 32) if (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE) break; if (ch >= 32 && ch <= 127 && saveCharIndex < SAVESTRINGSIZE - 1 && M_StringWidth(savegamestrings[saveSlot]) < (SAVESTRINGSIZE - 2) * 8) { savegamestrings[saveSlot][saveCharIndex++] = ch; savegamestrings[saveSlot][saveCharIndex] = 0; } break; } return true; } // Take care of any messages that need input if (messageToPrint) { if (messageNeedsInput == true && !(ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE)) return false; menuactive = messageLastMenuActive; messageToPrint = 0; if (messageRoutine) messageRoutine(ch); menuactive = false; S_StartSound(0, sfx_swtchx); return true; } if (devparm && ch == KEY_F1) { G_ScreenShot(); return true; } // F-Keys if (!menuactive) switch (ch) { case KEY_MINUS: // Screen size down if (automapactive || chat_on) return false; M_SizeDisplay(0); S_StartSound(0, sfx_stnmov); return true; case KEY_EQUALS: // Screen size up if (automapactive || chat_on) return false; M_SizeDisplay(1); S_StartSound(0, sfx_stnmov); return true; case KEY_F1: // Help key M_StartControlPanel(); if (gamemode == retail) currentMenu = &ReadDef2; else currentMenu = &ReadDef1; itemOn = 0; S_StartSound(0, sfx_swtchn); return true; case KEY_F2: // Save M_StartControlPanel(); S_StartSound(0, sfx_swtchn); M_SaveGame(0); return true; case KEY_F3: // Load M_StartControlPanel(); S_StartSound(0, sfx_swtchn); M_LoadGame(0); return true; case KEY_F4: // Sound Volume M_StartControlPanel(); currentMenu = &SoundDef; itemOn = sfx_vol; S_StartSound(0, sfx_swtchn); return true; // case KEY_F5: // Detail toggle // M_ChangeDetail(0); // S_StartSound(0, sfx_swtchn); // return true; case KEY_F5: // Crosshair toggle M_ChangeCrosshair(0); S_StartSound(0, sfx_swtchn); return true; case KEY_F6: // Quicksave S_StartSound(0, sfx_swtchn); M_QuickSave(); return true; case KEY_F7: // End game S_StartSound(0, sfx_swtchn); M_EndGame(0); return true; case KEY_F8: // Toggle messages M_ChangeMessages(0); S_StartSound(0, sfx_swtchn); return true; case KEY_F9: // Quickload S_StartSound(0, sfx_swtchn); M_QuickLoad(); return true; case KEY_F10: // Quit DOOM S_StartSound(0, sfx_swtchn); M_QuitDOOM(0); return true; case KEY_F11: // gamma toggle usegamma++; if (usegamma > 4) usegamma = 0; players[consoleplayer].message = gammamsg[usegamma]; I_SetPalette(W_CacheLumpName("PLAYPAL", PU_CACHE)); return true; } // Pop-up menu? if (!menuactive) { if (ch == KEY_ESCAPE) { M_StartControlPanel(); S_StartSound(0, sfx_swtchn); return true; } return false; } // Keys usable within menu switch (ch) { case KEY_DOWNARROW: do { if (itemOn + 1 > currentMenu->numitems - 1) itemOn = 0; else itemOn++; S_StartSound(0, sfx_pstop); } while (currentMenu->menuitems[itemOn].status == -1); return true; case KEY_UPARROW: do { if (!itemOn) itemOn = currentMenu->numitems - 1; else itemOn--; S_StartSound(0, sfx_pstop); } while (currentMenu->menuitems[itemOn].status == -1); return true; case KEY_LEFTARROW: if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { S_StartSound(0, sfx_stnmov); currentMenu->menuitems[itemOn].routine(0); } return true; case KEY_RIGHTARROW: if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { S_StartSound(0, sfx_stnmov); currentMenu->menuitems[itemOn].routine(1); } return true; case KEY_ENTER: if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status) { currentMenu->lastOn = itemOn; if (currentMenu->menuitems[itemOn].status == 2) { currentMenu->menuitems[itemOn].routine(1); // right arrow S_StartSound(0, sfx_stnmov); } else { currentMenu->menuitems[itemOn].routine(itemOn); S_StartSound(0, sfx_pistol); } } return true; case KEY_ESCAPE: currentMenu->lastOn = itemOn; M_ClearMenus(); S_StartSound(0, sfx_swtchx); return true; case KEY_BACKSPACE: currentMenu->lastOn = itemOn; if (currentMenu->prevMenu) { currentMenu = currentMenu->prevMenu; itemOn = currentMenu->lastOn; S_StartSound(0, sfx_swtchn); } return true; default: for (i = itemOn + 1; i < currentMenu->numitems; i++) if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; S_StartSound(0, sfx_pstop); return true; } for (i = 0; i <= itemOn; i++) if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; S_StartSound(0, sfx_pstop); return true; } break; } return false; } // // M_StartControlPanel // void M_StartControlPanel(void) { // intro might call this repeatedly if (menuactive) return; menuactive = 1; currentMenu = &MainDef; // JDC itemOn = currentMenu->lastOn; // JDC } // // M_Drawer // Called after the view has been rendered, // but before it has been blitted. // void M_Drawer(void) { static short x; static short y; short i; short max; char string[40]; int start; inhelpscreens = false; // Horiz. & Vertically center string and print it. if (messageToPrint) { start = 0; y = 100 - M_StringHeight(messageString) / 2; while (*(messageString + start)) { for (i = 0; i < doom_strlen(messageString + start); i++) if (*(messageString + start + i) == '\n') { doom_memset(string, 0, 40); doom_strncpy(string, messageString + start, i); start += i + 1; break; } if (i == doom_strlen(messageString + start)) { doom_strcpy(string, messageString + start); start += i; } x = 160 - M_StringWidth(string) / 2; M_WriteText(x, y, string); y += SHORT(hu_font[0]->height); } return; } if (!menuactive) return; // Darken background so the menu is more readable. if (doom_flags & DOOM_FLAG_MENU_DARKEN_BG) { extern byte* screens[5]; extern unsigned char screen_palette[256 * 3]; extern lighttable_t* colormaps; for (int j = 0, len = SCREENWIDTH * SCREENHEIGHT; j < len; ++j) { byte color = screens[0][j]; color = colormaps[color + (20 * 256)]; screens[0][j] = color; } } if (currentMenu->routine) currentMenu->routine(); // call Draw routine // DRAW MENU x = currentMenu->x; y = currentMenu->y; max = currentMenu->numitems; for (i = 0; i < max; i++) { menuitem_t* menuitem = currentMenu->menuitems + i; if (menuitem->name[0]) { if (doom_strncmp(menuitem->name, "TXT_", 4) == 0) { M_DrawCustomMenuText(menuitem->name, x, y); } else { V_DrawPatchDirect(x, y, 0, W_CacheLumpName(menuitem->name, PU_CACHE)); } } y += LINEHEIGHT; } // DRAW SKULL V_DrawPatchDirect(x + SKULLXOFF, currentMenu->y - 5 + itemOn * LINEHEIGHT, 0, W_CacheLumpName(skullName[whichSkull], PU_CACHE)); } // // M_ClearMenus // void M_ClearMenus(void) { menuactive = 0; } // // M_SetupNextMenu // void M_SetupNextMenu(menu_t* menudef) { currentMenu = menudef; itemOn = currentMenu->lastOn; } // // M_Ticker // void M_Ticker(void) { if (--skullAnimCounter <= 0) { whichSkull ^= 1; skullAnimCounter = 8; } } // // M_Init // void M_Init(void) { doom_boolean hide_mouse = (doom_flags & DOOM_FLAG_HIDE_MOUSE_OPTIONS) ? true : false; doom_boolean hide_sound = ((doom_flags & DOOM_FLAG_HIDE_MUSIC_OPTIONS) && (doom_flags & DOOM_FLAG_HIDE_SOUND_OPTIONS)) ? true : false; OptionsMenu = OptionsMenuFull; if (hide_mouse && !hide_sound) { OptionsMenu = OptionsMenuNoMouse; doom_memcpy(&OptionsDef, &OptionsNoMouseDef, sizeof(OptionsDef)); } else if (!hide_mouse && hide_sound) { OptionsMenu = OptionsMenuNoSound; doom_memcpy(&OptionsDef, &OptionsNoSoundDef, sizeof(OptionsDef)); } else if (hide_mouse && hide_sound) { OptionsMenu = OptionsMenuNoSoundNoMouse; doom_memcpy(&OptionsDef, &OptionsNoSoundNoMouseDef, sizeof(OptionsDef)); } SoundMenu = SoundMenuFull; if (doom_flags & DOOM_FLAG_HIDE_MUSIC_OPTIONS) { SoundMenu = SoundMenuNoMusic; doom_memcpy(&SoundDef, &SoundNoMusicDef, sizeof(SoundDef)); } else if (doom_flags & DOOM_FLAG_HIDE_SOUND_OPTIONS) { SoundMenu = SoundMenuNoSFX; doom_memcpy(&SoundDef, &SoundNoSFXDef, sizeof(SoundDef)); } currentMenu = &MainDef; menuactive = 0; itemOn = currentMenu->lastOn; whichSkull = 0; skullAnimCounter = 10; screenSize = screenblocks - 3; messageToPrint = 0; messageString = 0; messageLastMenuActive = menuactive; quickSaveSlot = -1; // Here we could catch other version dependencies, // like HELP1/2, and four episodes. switch (gamemode) { case commercial: // This is used because DOOM 2 had only one HELP // page. I use CREDIT as second page now, but // kept this hack for educational purposes. MainMenu[readthis] = MainMenu[quitdoom]; MainDef.numitems--; MainDef.y += 8; NewDef.prevMenu = &MainDef; ReadDef1.routine = M_DrawReadThis1; ReadDef1.x = 330; ReadDef1.y = 165; ReadMenu1[0].routine = M_FinishReadThis; break; case shareware: // Episode 2 and 3 are handled, // branching to an ad screen. case registered: // We need to remove the fourth episode. EpiDef.numitems--; break; case retail: // We are fine. default: break; } }