// // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005-2014 Simon Howard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // DESCRIPTION: // DOOM selection menu, options, episode etc. // Sliders and icons. Kinda widget stuff. // #include #include #include "doomdef.h" #include "doomkeys.h" #include "dstrings.h" #include "d_main.h" #include "deh_main.h" #include "i_swap.h" #include "i_system.h" #include "i_timer.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_controls.h" #include "m_misc.h" #include "m_saves.h" // [STRIFE] #include "p_saveg.h" #include "s_sound.h" #include "doomstat.h" // Data. #include "sounds.h" #include "m_menu.h" #include "p_dialog.h" extern void M_QuitStrife(int); extern patch_t* hu_font[HU_FONTSIZE]; extern boolean message_dontfuckwithme; extern boolean chat_on; // in heads-up code extern boolean sendsave; // [STRIFE] // // defaulted values // int mouseSensitivity = 5; // [STRIFE]: removed this entirely // Show messages has default, 0 = off, 1 = on //int showMessages = 1; // Blocky mode, has default, 0 = high, 1 = normal int detailLevel = 0; int screenblocks = 10; // [STRIFE] default 10, not 9 // 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 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]; boolean inhelpscreens; boolean menuactive; boolean menupause; // haleyjd 08/29/10: [STRIFE] New global int menupausetime; // haleyjd 09/04/10: [STRIFE] New global boolean menuindialog; // haleyjd 09/04/10: ditto // haleyjd 08/27/10: [STRIFE] SKULLXOFF == -28, LINEHEIGHT == 19 #define CURSORXOFF -28 #define LINEHEIGHT 19 extern boolean sendpause; char savegamestrings[10][SAVESTRINGSIZE]; char endstring[160]; // haleyjd 09/04/10: [STRIFE] Moved menuitem / menu structures into header // because they are needed externally by the dialog engine. // haleyjd 08/27/10: [STRIFE] skull* stuff changed to cursor* stuff short itemOn; // menu item skull is on short cursorAnimCounter; // skull animation counter short whichCursor; // which skull to draw // graphic name of cursors // haleyjd 08/27/10: [STRIFE] M_SKULL* -> M_CURS* char *cursorName[8] = {"M_CURS1", "M_CURS2", "M_CURS3", "M_CURS4", "M_CURS5", "M_CURS6", "M_CURS7", "M_CURS8" }; // haleyjd 20110210 [STRIFE]: skill level for menus int menuskill; // current menudef menu_t* currentMenu; // haleyjd 03/01/13: [STRIFE] v1.31-only: // Keeps track of whether the save game menu is being used to name a new // character slot, or to just save the current game. In the v1.31 disassembly // this was the new dword_8632C variable. boolean namingCharacter; // // 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_ReadThis3(int choice); // [STRIFE] //void M_ChangeMessages(int choice); [STRIFE] void M_ChangeSensitivity(int choice); void M_SfxVol(int choice); void M_VoiceVol(int choice); // [STRIFE] void M_MusicVol(int choice); void M_SizeDisplay(int choice); void M_StartGame(int choice); void M_Sound(int choice); //void M_FinishReadThis(int choice); - [STRIFE] unused 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_DrawReadThis3(void); // [STRIFE] 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); int M_StringWidth(char *string); int M_StringHeight(char *string); void M_StartMessage(char *string,void *routine,boolean input); void M_StopMessage(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,'h'}, // haleyjd 08/28/10: 'r' -> 'h' {1,"M_QUITG",M_QuitStrife,'q'} }; menu_t MainDef = { main_end, NULL, MainMenu, M_DrawMainMenu, 97,45, // haleyjd 08/28/10: [STRIFE] changed y coord 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[]= { // haleyjd 08/28/10: [STRIFE] changed all shortcut letters {1,"M_JKILL", M_ChooseSkill, 't'}, {1,"M_ROUGH", M_ChooseSkill, 'r'}, {1,"M_HURT", M_ChooseSkill, 'v'}, {1,"M_ULTRA", M_ChooseSkill, 'e'}, {1,"M_NMARE", M_ChooseSkill, 'b'} }; menu_t NewDef = { newg_end, // # of menu items &MainDef, // previous menu - haleyjd [STRIFE] changed to MainDef NewGameMenu, // menuitem_t -> M_DrawNewGame, // drawing routine -> 48,63, // x,y toorough // lastOn - haleyjd [STRIFE]: default to skill 1 }; // // OPTIONS MENU // enum { // haleyjd 08/28/10: [STRIFE] Removed messages, mouse sens., detail. endgame, scrnsize, option_empty1, soundvol, opt_end } options_e; menuitem_t OptionsMenu[]= { // haleyjd 08/28/10: [STRIFE] Removed messages, mouse sens., detail. {1,"M_ENDGAM", M_EndGame,'e'}, {2,"M_SCRNSZ", M_SizeDisplay,'s'}, {-1,"",0,'\0'}, {1,"M_SVOL", M_Sound,'s'} }; menu_t OptionsDef = { opt_end, &MainDef, OptionsMenu, M_DrawOptions, 60,37, 0 }; // // Read This! MENU 1 & 2 & [STRIFE] 3 // 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_ReadThis3,0} // haleyjd 08/28/10: [STRIFE] Go to ReadThis3 }; menu_t ReadDef2 = { read2_end, &ReadDef1, ReadMenu2, M_DrawReadThis2, 250,185, // haleyjd 08/28/10: [STRIFE] changed coords 0 }; // haleyjd 08/28/10: Added Read This! menu 3 enum { rdthsempty3, read3_end } read_e3; menuitem_t ReadMenu3[]= { {1,"",M_ClearMenus,0} }; menu_t ReadDef3 = { read3_end, &ReadDef2, ReadMenu3, M_DrawReadThis3, 250, 185, 0 }; // // SOUND VOLUME MENU // enum { sfx_vol, sfx_empty1, music_vol, sfx_empty2, voice_vol, sfx_empty3, sfx_mouse, sfx_empty4, sound_end } sound_e; // haleyjd 08/29/10: // [STRIFE] // * Added voice volume // * Moved mouse sensitivity here (who knows why...) menuitem_t SoundMenu[]= { {2,"M_SFXVOL",M_SfxVol,'s'}, {-1,"",0,'\0'}, {2,"M_MUSVOL",M_MusicVol,'m'}, {-1,"",0,'\0'}, {2,"M_VOIVOL",M_VoiceVol,'v'}, {-1,"",0,'\0'}, {2,"M_MSENS",M_ChangeSensitivity,'m'}, {-1,"",0,'\0'} }; menu_t SoundDef = { sound_end, &OptionsDef, SoundMenu, M_DrawSound, 80,35, // [STRIFE] changed y coord 64 -> 35 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 }; void M_DrawNameChar(void); // // NAME CHARACTER MENU // // [STRIFE] // haleyjd 20110210: New "Name Your Character" Menu // menu_t NameCharDef = { load_end, &NewDef, SaveMenu, M_DrawNameChar, 80,54, 0 }; // // M_ReadSaveStrings // read the strings from the savegame files // // [STRIFE] // haleyjd 20110210: Rewritten to read "name" file in each slot directory // void M_ReadSaveStrings(void) { FILE *handle; int i; char *fname = NULL; for(i = 0; i < load_end; i++) { if(fname) Z_Free(fname); fname = M_SafeFilePath(savegamedir, M_MakeStrifeSaveDir(i, "\\name")); handle = fopen(fname, "rb"); if(handle == NULL) { M_StringCopy(savegamestrings[i], EMPTYSTRING, sizeof(savegamestrings[i])); LoadMenu[i].status = 0; continue; } fread(savegamestrings[i], 1, SAVESTRINGSIZE, handle); fclose(handle); LoadMenu[i].status = 1; } if(fname) Z_Free(fname); } // // M_DrawNameChar // // haleyjd 09/22/10: [STRIFE] New function // Handler for drawing the "Name Your Character" menu. // void M_DrawNameChar(void) { int i; M_WriteText(72, 28, DEH_String("Name Your Character")); 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[quickSaveSlot]); M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*quickSaveSlot,"_"); } } // // M_DoNameChar // // haleyjd 09/22/10: [STRIFE] New function // Handler for items in the "Name Your Character" menu. // void M_DoNameChar(int choice) { int map; // 20130301: clear naming character flag for 1.31 save logic if(gameversion == exe_strife_1_31) namingCharacter = false; sendsave = 1; ClearTmp(); G_WriteSaveName(choice, savegamestrings[choice]); quickSaveSlot = choice; SaveDef.lastOn = choice; ClearSlot(); FromCurr(); if(isdemoversion) map = 33; else map = 2; G_DeferedInitNew(menuskill, map); M_ClearMenus(0); } // // M_LoadGame & Cie. // void M_DrawLoad(void) { int i; V_DrawPatchDirect(72, 28, W_CacheLumpName(DEH_String("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, W_CacheLumpName(DEH_String("M_LSLEFT"), PU_CACHE)); for (i = 0;i < 24;i++) { V_DrawPatchDirect(x, y + 7, W_CacheLumpName(DEH_String("M_LSCNTR"), PU_CACHE)); x += 8; } V_DrawPatchDirect(x, y + 7, W_CacheLumpName(DEH_String("M_LSRGHT"), PU_CACHE)); } // // User wants to load this game // void M_LoadSelect(int choice) { // [STRIFE]: completely rewritten char *name = NULL; G_WriteSaveName(choice, savegamestrings[choice]); ToCurr(); // use safe & portable filepath concatenation for Choco name = M_SafeFilePath(savegamedir, M_MakeStrifeSaveDir(choice, "")); G_ReadCurrent(name); quickSaveSlot = choice; M_ClearMenus(0); Z_Free(name); } // // Selected from DOOM menu // // [STRIFE] Verified unmodified // void M_LoadGame (int choice) { if (netgame) { M_StartMessage(DEH_String(LOADNET), NULL, false); return; } M_SetupNextMenu(&LoadDef); M_ReadSaveStrings(); } // // M_SaveGame & Cie. // void M_DrawSave(void) { int i; V_DrawPatchDirect(72, 28, W_CacheLumpName(DEH_String("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[quickSaveSlot]); M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*quickSaveSlot,"_"); } } // // M_Responder calls this when user is finished // void M_DoSave(int slot) { // [STRIFE]: completely rewritten if(slot >= 0) { sendsave = 1; G_WriteSaveName(slot, savegamestrings[slot]); M_ClearMenus(0); quickSaveSlot = slot; // haleyjd 20130922: slight divergence. We clear the destination slot // of files here, which vanilla did not do. As a result, 1.31 had // broken save behavior to the point of unusability. fraggle agrees // this is detrimental enough to be fixed - unconditionally, for now. ClearSlot(); FromCurr(); } else M_StartMessage(DEH_String(QSAVESPOT), NULL, false); } // // 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; // [STRIFE] quickSaveSlot = choice; //saveSlot = choice; M_StringCopy(saveOldString, savegamestrings[choice], sizeof(saveOldString)); if (!strcmp(savegamestrings[choice],EMPTYSTRING)) savegamestrings[choice][0] = 0; saveCharIndex = strlen(savegamestrings[choice]); } // // Selected from DOOM menu // void M_SaveGame (int choice) { // [STRIFE] if (netgame) { // haleyjd 20110211: Hooray for Rogue's awesome multiplayer support... M_StartMessage(DEH_String("You can't save a netgame"), NULL, false); return; } if (!usergame) { M_StartMessage(DEH_String(SAVEDEAD),NULL,false); return; } if (gamestate != GS_LEVEL) return; // [STRIFE] if(gameversion == exe_strife_1_31) { // haleyjd 20130301: in 1.31, we can choose a slot again. M_SetupNextMenu(&SaveDef); M_ReadSaveStrings(); } else { // In 1.2 and lower, you save over your character slot exclusively M_ReadSaveStrings(); M_DoSave(quickSaveSlot); } } // // M_QuickSave // char tempstring[80]; void M_QuickSaveResponse(int key) { if (key == key_menu_confirm) { M_DoSave(quickSaveSlot); S_StartSound(NULL, sfx_mtalht); // [STRIFE] sound } } void M_QuickSave(void) { if (netgame) { // haleyjd 20110211 [STRIFE]: More fun... M_StartMessage(DEH_String("You can't save a netgame"), NULL, false); return; } if (!usergame) { S_StartSound(NULL, 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; } DEH_snprintf(tempstring, 80, QSPROMPT, savegamestrings[quickSaveSlot]); M_StartMessage(tempstring,M_QuickSaveResponse,true); } // // M_QuickLoadResponse // void M_QuickLoadResponse(int key) { if (key == key_menu_confirm) { M_LoadSelect(quickSaveSlot); S_StartSound(NULL, sfx_mtalht); // [STRIFE] sound } } // // M_QuickLoad // // [STRIFE] Verified unmodified // void M_QuickLoad(void) { if (netgame) { M_StartMessage(DEH_String(QLOADNET),NULL,false); return; } if (quickSaveSlot < 0) { M_StartMessage(DEH_String(QSAVESPOT),NULL,false); return; } DEH_snprintf(tempstring, 80, QLPROMPT, savegamestrings[quickSaveSlot]); M_StartMessage(tempstring,M_QuickLoadResponse,true); } // // Read This Menus // Had a "quick hack to fix romero bug" // haleyjd 08/28/10: [STRIFE] Draw HELP1, unconditionally. // void M_DrawReadThis1(void) { inhelpscreens = true; V_DrawPatchDirect (0, 0, W_CacheLumpName(DEH_String("HELP1"), PU_CACHE)); } // // Read This Menus // haleyjd 08/28/10: [STRIFE] Not optional, draws HELP2 // void M_DrawReadThis2(void) { inhelpscreens = true; V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP2"), PU_CACHE)); } // // Read This Menus // haleyjd 08/28/10: [STRIFE] New function to draw HELP3. // void M_DrawReadThis3(void) { inhelpscreens = true; V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP3"), PU_CACHE)); } // // Change Sfx & Music volumes // // haleyjd 08/29/10: [STRIFE] // * Changed title graphic coordinates // * Added voice volume and sensitivity sliders // void M_DrawSound(void) { V_DrawPatchDirect (100, 10, W_CacheLumpName(DEH_String("M_SVOL"), PU_CACHE)); M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_vol+1), 16,sfxVolume); M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(music_vol+1), 16,musicVolume); M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(voice_vol+1), 16,voiceVolume); M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_mouse+1), 16,mouseSensitivity); } void M_Sound(int choice) { M_SetupNextMenu(&SoundDef); } void M_SfxVol(int choice) { switch(choice) { case 0: if (sfxVolume) sfxVolume--; break; case 1: if (sfxVolume < 15) sfxVolume++; break; } S_SetSfxVolume(sfxVolume * 8); } // // M_VoiceVol // // haleyjd 08/29/10: [STRIFE] New function // Sets voice volume level. // void M_VoiceVol(int choice) { switch(choice) { case 0: if (voiceVolume) voiceVolume--; break; case 1: if (voiceVolume < 15) voiceVolume++; break; } S_SetVoiceVolume(voiceVolume * 8); } void M_MusicVol(int choice) { switch(choice) { case 0: if (musicVolume) musicVolume--; break; case 1: if (musicVolume < 15) musicVolume++; break; } S_SetMusicVolume(musicVolume * 8); } // // M_DrawMainMenu // // haleyjd 08/27/10: [STRIFE] Changed x coordinate; M_DOOM -> M_STRIFE // void M_DrawMainMenu(void) { V_DrawPatchDirect(84, 2, W_CacheLumpName(DEH_String("M_STRIFE"), PU_CACHE)); } // // M_NewGame // // haleyjd 08/31/10: [STRIFE] Changed M_NEWG -> M_NGAME // void M_DrawNewGame(void) { V_DrawPatchDirect(96, 14, W_CacheLumpName(DEH_String("M_NGAME"), PU_CACHE)); V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_SKILL"), PU_CACHE)); } void M_NewGame(int choice) { if (netgame && !demoplayback) { M_StartMessage(DEH_String(NEWGAME),NULL,false); return; } // haleyjd 09/07/10: [STRIFE] Removed Chex Quest and DOOM gamemodes if(gameversion == exe_strife_1_31) namingCharacter = true; // for 1.31 save logic M_SetupNextMenu(&NewDef); } // // M_Episode // // haleyjd: [STRIFE] Unused /* int epi; void M_DrawEpisode(void) { V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_EPISOD"), PU_CACHE)); } void M_VerifyNightmare(int key) { if (key != key_menu_confirm) return; G_DeferedInitNew(nightmare, 1); M_ClearMenus (0); } */ void M_ChooseSkill(int choice) { // haleyjd 09/07/10: Removed nightmare confirmation // [STRIFE]: start "Name Your Character" menu menuskill = choice; currentMenu = &NameCharDef; itemOn = NameCharDef.lastOn; M_ReadSaveStrings(); } /* // haleyjd [STRIFE] Unused void M_Episode(int choice) { if ( (gamemode == shareware) && choice) { M_StartMessage(DEH_String(SWSTRING),NULL,false); M_SetupNextMenu(&ReadDef1); return; } // Yet another hack... if ( (gamemode == registered) && (choice > 2)) { fprintf( stderr, "M_Episode: 4th episode requires UltimateDOOM\n"); choice = 0; } epi = choice; M_SetupNextMenu(&NewDef); } */ // // M_Options // char detailNames[2][9] = {"M_GDHIGH","M_GDLOW"}; char msgNames[2][9] = {"M_MSGOFF","M_MSGON"}; void M_DrawOptions(void) { // haleyjd 08/27/10: [STRIFE] M_OPTTTL -> M_OPTION V_DrawPatchDirect(108, 15, W_CacheLumpName(DEH_String("M_OPTION"), PU_CACHE)); // haleyjd 08/26/10: [STRIFE] Removed messages, sensitivity, detail. M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(scrnsize+1), 9,screenSize); } void M_Options(int choice) { M_SetupNextMenu(&OptionsDef); } // // M_AutoUseHealth // // [STRIFE] New function // haleyjd 20110211: toggle autouse health state // void M_AutoUseHealth(void) { if(!netgame && usergame) { players[consoleplayer].cheats ^= CF_AUTOHEALTH; if(players[consoleplayer].cheats & CF_AUTOHEALTH) players[consoleplayer].message = DEH_String("Auto use health ON"); else players[consoleplayer].message = DEH_String("Auto use health OFF"); } } // // M_ChangeShowText // // [STRIFE] New function // void M_ChangeShowText(void) { dialogshowtext ^= true; if(dialogshowtext) players[consoleplayer].message = DEH_String("Conversation Text On"); else players[consoleplayer].message = DEH_String("Conversation Text Off"); } // // Toggle messages on/off // // [STRIFE] Messages cannot be disabled in Strife /* void M_ChangeMessages(int choice) { // warning: unused parameter `int choice' choice = 0; showMessages = 1 - showMessages; if (!showMessages) players[consoleplayer].message = DEH_String(MSGOFF); else players[consoleplayer].message = DEH_String(MSGON); message_dontfuckwithme = true; } */ // // M_EndGame // void M_EndGameResponse(int key) { if (key != key_menu_confirm) return; currentMenu->lastOn = itemOn; M_ClearMenus (0); D_StartTitle (); } void M_EndGame(int choice) { choice = 0; if (!usergame) { S_StartSound(NULL,sfx_oof); return; } if (netgame) { M_StartMessage(DEH_String(NETEND),NULL,false); return; } M_StartMessage(DEH_String(ENDGAME),M_EndGameResponse,true); } // // M_ReadThis // void M_ReadThis(int choice) { choice = 0; M_SetupNextMenu(&ReadDef1); } // // M_ReadThis2 // // haleyjd 08/28/10: [STRIFE] Eliminated DOOM stuff. // void M_ReadThis2(int choice) { choice = 0; M_SetupNextMenu(&ReadDef2); } // // M_ReadThis3 // // haleyjd 08/28/10: [STRIFE] New function. // void M_ReadThis3(int choice) { choice = 0; M_SetupNextMenu(&ReadDef3); } /* // haleyjd 08/28/10: [STRIFE] Not used. void M_FinishReadThis(int choice) { choice = 0; M_SetupNextMenu(&MainDef); } */ #if 0 extern void F_StartCast(void); // // M_CheckStartCast // // [STRIFE] New but unused function. Was going to start a cast // call from within the menu system... not functional even in // the earliest demo version. // void M_CheckStartCast() { if(usergame) { M_StartMessage(DEH_String("You have to end your game first."), NULL, false); return; } F_StartCast(); M_ClearMenus(0); } #endif // // M_QuitResponse // // haleyjd 09/11/10: [STRIFE] Modifications to start up endgame // demosequence. // void M_QuitResponse(int key) { char buffer[20]; if (key != key_menu_confirm) return; if(netgame) I_Quit(); else { DEH_snprintf(buffer, sizeof(buffer), "qfmrm%i", gametic % 8 + 1); I_StartVoice(buffer); D_QuitGame(); } } /* // haleyjd 09/11/10: [STRIFE] Unused static char *M_SelectEndMessage(void) { } */ // // M_QuitStrife // // [STRIFE] Renamed from M_QuitDOOM // haleyjd 09/11/10: No randomized text message; that's taken care of // by the randomized voice message after confirmation. // void M_QuitStrife(int choice) { DEH_snprintf(endstring, sizeof(endstring), "Do you really want to leave?\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; } } /* // haleyjd [STRIFE] Unused void M_ChangeDetail(int choice) { choice = 0; detailLevel = 1 - detailLevel; R_SetViewSize (screenblocks, detailLevel); if (!detailLevel) players[consoleplayer].message = DEH_String(DETAILHI); else players[consoleplayer].message = DEH_String(DETAILLO); } */ // [STRIFE] Verified unmodified 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 // // // M_DrawThermo // // haleyjd 08/28/10: [STRIFE] Changes to some patch coordinates. // void M_DrawThermo ( int x, int y, int thermWidth, int thermDot ) { int xx; int yy; // [STRIFE] Needs a temp y coordinate variable int i; xx = x; yy = y + 6; // [STRIFE] +6 to y coordinate V_DrawPatchDirect(xx, yy, W_CacheLumpName(DEH_String("M_THERML"), PU_CACHE)); xx += 8; for (i=0;ix - 10, menu->y + item * LINEHEIGHT - 1, W_CacheLumpName(DEH_String("M_CELL1"), PU_CACHE)); } void M_DrawSelCell ( menu_t* menu, int item ) { V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1, W_CacheLumpName(DEH_String("M_CELL2"), PU_CACHE)); } void M_StartMessage ( char* string, void* routine, 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) { size_t i; int w = 0; int c; for (i = 0;i < strlen(string);i++) { c = 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) { size_t i; int h; int height = SHORT(hu_font[0]->height); h = height; for (i = 0;i < strlen(string);i++) if (string[i] == '\n') h += height; return h; } // // M_WriteText // // Write a string using the hu_font // haleyjd 09/04/10: [STRIFE] // * Rogue made a lot of changes to this for the dialog system. // int M_WriteText ( int x, int y, const char* string) // haleyjd: made const for safety w/dialog engine { int w; const char* ch; int c; int cx; int cy; ch = string; cx = x; cy = y; while(1) { c = *ch++; if (!c) break; // haleyjd 09/04/10: [STRIFE] Don't draw spaces at the start of lines. if(c == ' ' && cx == x) continue; if (c == '\n') { cx = x; cy += 11; // haleyjd 09/04/10: [STRIFE]: Changed 12 -> 11 continue; } c = toupper(c) - HU_FONTSTART; if (c < 0 || c>= HU_FONTSIZE) { cx += 4; continue; } w = SHORT (hu_font[c]->width); // haleyjd 09/04/10: [STRIFE] Different linebreak handling if (cx + w > SCREENWIDTH - 20) { cx = x; cy += 11; --ch; } else { V_DrawPatchDirect(cx, cy, hu_font[c]); cx += w; } } // [STRIFE] Return final y coordinate. return cy + 12; } // // M_DialogDimMsg // // [STRIFE] New function // haleyjd 09/04/10: Painstakingly transformed from the assembly code, as the // decompiler could not touch it. Redimensions a string to fit on screen, leaving // at least a 20 pixel margin on the right side. The string passed in must be // writable. // void M_DialogDimMsg(int x, int y, char *str, boolean useyfont) { int rightbound = (SCREENWIDTH - 20) - x; patch_t **fontarray; // ebp int linewidth = 0; // esi int i = 0; // edx char *message = str; // edi char bl; // bl if(useyfont) fontarray = yfont; else fontarray = hu_font; bl = toupper(*message); if(!bl) return; // outer loop - run to end of string do { if(bl != '\n') { int charwidth; // eax int tempwidth; // ecx if(bl < HU_FONTSTART || bl > HU_FONTEND) charwidth = 4; else charwidth = SHORT(fontarray[bl - HU_FONTSTART]->width); tempwidth = linewidth + charwidth; // Test if the line still fits within the boundary... if(tempwidth >= rightbound) { // Doesn't fit... char *tempptr = &message[i]; // ebx char al; // al // inner loop - run backward til a space (or the start of the // string) is found, subtracting width off the current line. // BUG: shouldn't we stop at a previous '\n' too? while(*tempptr != ' ' && i > 0) { tempptr--; // BUG: they didn't add the first char to linewidth yet... linewidth -= charwidth; i--; al = toupper(*tempptr); if(al < HU_FONTSTART || al > HU_FONTEND) charwidth = 4; else charwidth = SHORT(fontarray[al - HU_FONTSTART]->width); } // Replace the space with a linebreak. // BUG: what if i is zero? ... infinite loop time! message[i] = '\n'; linewidth = 0; } else { // The line does fit. // Spaces at the start of a line don't count though. if(!(bl == ' ' && linewidth == 0)) linewidth += charwidth; } } else linewidth = 0; // '\n' seen, so reset the line width } while((bl = toupper(message[++i])) != 0); // step to the next character } // These keys evaluate to a "null" key in Vanilla Doom that allows weird // jumping in the menus. Preserve this behavior for accuracy. static boolean IsNullKey(int key) { return key == KEY_PAUSE || key == KEY_CAPSLOCK || key == KEY_SCRLCK || key == KEY_NUMLOCK; } // // CONTROL PANEL // // // M_Responder // boolean M_Responder (event_t* ev) { int ch; int key; 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; // In testcontrols mode, none of the function keys should do anything // - the only key is escape to quit. if (testcontrols) { if (ev->type == ev_quit || (ev->type == ev_keydown && (ev->data1 == key_menu_activate || ev->data1 == key_menu_quit))) { I_Quit(); return true; } return false; } // "close" button pressed on window? if (ev->type == ev_quit) { // First click on close button = bring up quit confirm message. // Second click on close button = confirm quit if (menuactive && messageToPrint && messageRoutine == M_QuitResponse) { M_QuitResponse(key_menu_confirm); } else { S_StartSound(NULL, sfx_swtchn); M_QuitStrife(0); } return true; } // key is the key pressed, ch is the actual character typed ch = 0; key = -1; if (ev->type == ev_joystick && joywait < I_GetTime()) { if (ev->data3 < 0) { key = key_menu_up; joywait = I_GetTime() + 5; } else if (ev->data3 > 0) { key = key_menu_down; joywait = I_GetTime() + 5; } if (ev->data2 < 0) { key = key_menu_left; joywait = I_GetTime() + 2; } else if (ev->data2 > 0) { key = key_menu_right; joywait = I_GetTime() + 2; } if (ev->data1&1) { key = key_menu_forward; joywait = I_GetTime() + 5; } if (ev->data1&2) { key = key_menu_back; joywait = I_GetTime() + 5; } if (joybmenu >= 0 && (ev->data1 & (1 << joybmenu)) != 0) { key = key_menu_activate; joywait = I_GetTime() + 5; } } else { if (ev->type == ev_mouse && mousewait < I_GetTime()) { mousey += ev->data3; if (mousey < lasty-30) { key = key_menu_down; mousewait = I_GetTime() + 5; mousey = lasty -= 30; } else if (mousey > lasty+30) { key = key_menu_up; mousewait = I_GetTime() + 5; mousey = lasty += 30; } mousex += ev->data2; if (mousex < lastx-30) { key = key_menu_left; mousewait = I_GetTime() + 5; mousex = lastx -= 30; } else if (mousex > lastx+30) { key = key_menu_right; mousewait = I_GetTime() + 5; mousex = lastx += 30; } if (ev->data1&1) { key = key_menu_forward; mousewait = I_GetTime() + 15; mouse_fire_countdown = 5; // villsa [STRIFE] } if (ev->data1&2) { key = key_menu_back; mousewait = I_GetTime() + 15; } } else { if (ev->type == ev_keydown) { key = ev->data1; ch = ev->data2; } } } if (key == -1) return false; // Save Game string input if (saveStringEnter) { switch(key) { case KEY_BACKSPACE: if (saveCharIndex > 0) { saveCharIndex--; savegamestrings[quickSaveSlot][saveCharIndex] = 0; } break; case KEY_ESCAPE: saveStringEnter = 0; M_StringCopy(savegamestrings[quickSaveSlot], saveOldString, sizeof(savegamestrings[quickSaveSlot])); break; case KEY_ENTER: // [STRIFE] saveStringEnter = 0; if(gameversion == exe_strife_1_31 && !namingCharacter) { // In 1.31, we can be here as a result of normal saving again, // whereas in 1.2 this only ever happens when naming your // character to begin a new game. M_DoSave(quickSaveSlot); return true; } if (savegamestrings[quickSaveSlot][0]) M_DoNameChar(quickSaveSlot); break; default: // This is complicated. // Vanilla has a bug where the shift key is ignored when entering // a savegame name. If vanilla_keyboard_mapping is on, we want // to emulate this bug by using 'data1'. But if it's turned off, // it implies the user doesn't care about Vanilla emulation: just // use the correct 'data2'. if (vanilla_keyboard_mapping) { ch = key; } ch = toupper(ch); if (ch != ' ' && (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE)) { break; } if (ch >= 32 && ch <= 127 && saveCharIndex < SAVESTRINGSIZE-1 && M_StringWidth(savegamestrings[quickSaveSlot]) < (SAVESTRINGSIZE-2)*8) { savegamestrings[quickSaveSlot][saveCharIndex++] = ch; savegamestrings[quickSaveSlot][saveCharIndex] = 0; } break; } return true; } // Take care of any messages that need input if (messageToPrint) { if (messageNeedsInput) { if (key != ' ' && key != KEY_ESCAPE && key != key_menu_confirm && key != key_menu_abort) { return false; } } menuactive = messageLastMenuActive; messageToPrint = 0; if (messageRoutine) messageRoutine(key); menupause = false; // [STRIFE] unpause menuactive = false; S_StartSound(NULL, sfx_mtalht); // [STRIFE] sound return true; } // [STRIFE]: // * In v1.2 this is moved to F9 (quickload) // * In v1.31 it is moved to F12 with DM spy, and quicksave // functionality is restored separate from normal saving /* if (devparm && key == key_menu_help) { G_ScreenShot (); return true; } */ // F-Keys if (!menuactive) { if (key == key_menu_decscreen) // Screen size down { if (automapactive || chat_on) return false; M_SizeDisplay(0); S_StartSound(NULL, sfx_stnmov); return true; } else if (key == key_menu_incscreen) // Screen size up { if (automapactive || chat_on) return false; M_SizeDisplay(1); S_StartSound(NULL, sfx_stnmov); return true; } else if (key == key_menu_help) // Help key { M_StartControlPanel (); // haleyjd 08/29/10: [STRIFE] always ReadDef1 currentMenu = &ReadDef1; itemOn = 0; S_StartSound(NULL, sfx_swtchn); return true; } else if (key == key_menu_save) // Save { // [STRIFE]: Hub saves if(gameversion == exe_strife_1_31) namingCharacter = false; // just saving normally, in 1.31 if(netgame || players[consoleplayer].health <= 0 || players[consoleplayer].cheats & CF_ONFIRE) { S_StartSound(NULL, sfx_oof); } else { M_StartControlPanel(); S_StartSound(NULL, sfx_swtchn); M_SaveGame(0); } return true; } else if (key == key_menu_load) // Load { // [STRIFE]: Hub saves if(gameversion == exe_strife_1_31) { // 1.31: normal save loading namingCharacter = false; M_StartControlPanel(); M_LoadGame(0); S_StartSound(NULL, sfx_swtchn); } else { // Pre 1.31: quickload only S_StartSound(NULL, sfx_swtchn); M_QuickLoad(); } return true; } else if (key == key_menu_volume) // Sound Volume { M_StartControlPanel (); currentMenu = &SoundDef; itemOn = sfx_vol; S_StartSound(NULL, sfx_swtchn); return true; } else if (key == key_menu_detail) // Detail toggle { //M_ChangeDetail(0); M_AutoUseHealth(); // [STRIFE] S_StartSound(NULL, sfx_swtchn); return true; } else if (key == key_menu_qsave) // Quicksave { // [STRIFE]: Hub saves if(gameversion == exe_strife_1_31) namingCharacter = false; // for 1.31 save changes if(netgame || players[consoleplayer].health <= 0 || players[consoleplayer].cheats & CF_ONFIRE) { S_StartSound(NULL, sfx_oof); } else { S_StartSound(NULL, sfx_swtchn); M_QuickSave(); } return true; } else if (key == key_menu_endgame) // End game { S_StartSound(NULL, sfx_swtchn); M_EndGame(0); return true; } else if (key == key_menu_messages) // Toggle messages { //M_ChangeMessages(0); M_ChangeShowText(); // [STRIFE] S_StartSound(NULL, sfx_swtchn); return true; } else if (key == key_menu_qload) // Quickload { // [STRIFE] // * v1.2: takes a screenshot // * v1.31: does quickload again if(gameversion == exe_strife_1_31) { namingCharacter = false; S_StartSound(NULL, sfx_swtchn); M_QuickLoad(); } else G_ScreenShot(); return true; } else if (key == key_menu_quit) // Quit DOOM { S_StartSound(NULL, sfx_swtchn); M_QuitStrife(0); return true; } else if (key == key_menu_gamma) // gamma toggle { usegamma++; if (usegamma > 4) usegamma = 0; players[consoleplayer].message = DEH_String(gammamsg[usegamma]); I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE)); return true; } else if(gameversion == exe_strife_1_31 && key == key_spy) { // haleyjd 20130301: 1.31 moved screenshots to F12. G_ScreenShot(); return true; } else if (key != 0 && key == key_menu_screenshot) { G_ScreenShot(); return true; } } // Pop-up menu? if (!menuactive) { if (key == key_menu_activate) { M_StartControlPanel (); S_StartSound(NULL, sfx_swtchn); return true; } return false; } // Keys usable within menu if (key == key_menu_down) { // Move down to next item do { if (itemOn+1 > currentMenu->numitems-1) itemOn = 0; else itemOn++; S_StartSound(NULL, sfx_pstop); } while(currentMenu->menuitems[itemOn].status==-1); return true; } else if (key == key_menu_up) { // Move back up to previous item do { if (!itemOn) itemOn = currentMenu->numitems-1; else itemOn--; S_StartSound(NULL, sfx_pstop); } while(currentMenu->menuitems[itemOn].status==-1); return true; } else if (key == key_menu_left) { // Slide slider left if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { S_StartSound(NULL, sfx_stnmov); currentMenu->menuitems[itemOn].routine(0); } return true; } else if (key == key_menu_right) { // Slide slider right if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { S_StartSound(NULL, sfx_stnmov); currentMenu->menuitems[itemOn].routine(1); } return true; } else if (key == key_menu_forward) { // Activate menu item 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(NULL, sfx_stnmov); } else { currentMenu->menuitems[itemOn].routine(itemOn); //S_StartSound(NULL, sfx_swish); [STRIFE] No sound is played here. } } return true; } else if (key == key_menu_activate) { // Deactivate menu if(gameversion == exe_strife_1_31) // [STRIFE]: 1.31 saving namingCharacter = false; if(menuindialog) // [STRIFE] - Get out of dialog engine semi-gracefully P_DialogDoChoice(-1); currentMenu->lastOn = itemOn; M_ClearMenus (0); S_StartSound(NULL, sfx_mtalht); // villsa [STRIFE]: sounds return true; } else if (key == key_menu_back) { // Go back to previous menu currentMenu->lastOn = itemOn; if (currentMenu->prevMenu) { currentMenu = currentMenu->prevMenu; itemOn = currentMenu->lastOn; S_StartSound(NULL, sfx_swtchn); } return true; } // Keyboard shortcut? // Vanilla Strife has a weird behavior where it jumps to the scroll bars // when certain keys are pressed, so emulate this. else if (ch != 0 || IsNullKey(key)) { // Keyboard shortcut? for (i = itemOn+1;i < currentMenu->numitems;i++) { if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; S_StartSound(NULL, sfx_pstop); return true; } } for (i = 0;i <= itemOn;i++) { if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; S_StartSound(NULL, sfx_pstop); return true; } } } return false; } // // M_StartControlPanel // void M_StartControlPanel (void) { // intro might call this repeatedly if (menuactive) return; menuactive = 1; menupause = true; 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; unsigned int i; unsigned int max; char string[80]; char *name; int start; inhelpscreens = false; // Horiz. & Vertically center string and print it. if (messageToPrint) { start = 0; y = 100 - M_StringHeight(messageString) / 2; while (messageString[start] != '\0') { int foundnewline = 0; for (i = 0; i < strlen(messageString + start); i++) { if (messageString[start + i] == '\n') { M_StringCopy(string, messageString + start, sizeof(string)); if (i < sizeof(string)) { string[i] = '\0'; } foundnewline = 1; start += i + 1; break; } } if (!foundnewline) { M_StringCopy(string, messageString + start, sizeof(string)); start += strlen(string); } x = 160 - M_StringWidth(string) / 2; M_WriteText(x, y, string); y += SHORT(hu_font[0]->height); } return; } if (!menuactive) return; if (currentMenu->routine) currentMenu->routine(); // call Draw routine // DRAW MENU x = currentMenu->x; y = currentMenu->y; max = currentMenu->numitems; for (i=0;imenuitems[i].name); if (name[0]) { V_DrawPatchDirect (x, y, W_CacheLumpName(name, PU_CACHE)); } y += LINEHEIGHT; } // haleyjd 08/27/10: [STRIFE] Adjust to draw spinning Sigil // DRAW SIGIL V_DrawPatchDirect(x + CURSORXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT, W_CacheLumpName(DEH_String(cursorName[whichCursor]), PU_CACHE)); } // // M_ClearMenus // // haleyjd 08/28/10: [STRIFE] Added an int param so this can be called by menus. // 09/08/10: Added menupause. // void M_ClearMenus (int choice) { choice = 0; // haleyjd: for no warning; not from decompilation. menuactive = 0; menupause = 0; } // // M_SetupNextMenu // void M_SetupNextMenu(menu_t *menudef) { currentMenu = menudef; itemOn = currentMenu->lastOn; } // // M_Ticker // // haleyjd 08/27/10: [STRIFE] Rewritten for Sigil cursor // void M_Ticker (void) { if (--cursorAnimCounter <= 0) { whichCursor = (whichCursor + 1) % 8; cursorAnimCounter = 5; } } // // M_Init // // haleyjd 08/27/10: [STRIFE] Removed DOOM gamemode stuff // void M_Init (void) { currentMenu = &MainDef; menuactive = 0; itemOn = currentMenu->lastOn; whichCursor = 0; cursorAnimCounter = 10; screenSize = screenblocks - 3; messageToPrint = 0; messageString = NULL; messageLastMenuActive = menuactive; // STRIFE-FIXME: assigns 0 here... quickSaveSlot = -1; // [STRIFE]: Initialize savegame paths and clear temporary directory G_WriteSaveName(5, "ME"); ClearTmp(); // Here we could catch other version dependencies, // like HELP1/2, and four episodes. }