// 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. // //----------------------------------------------------------------------------- // HEADER FILES ------------------------------------------------------------ #include #include #include #include #if defined(_WIN32) #include #else #include #include #include #endif #include "doomdef.h" #include "gstrings.h" #include "c_console.h" #include "c_dispatch.h" #include "d_main.h" #include "i_system.h" #include "i_video.h" #include "v_video.h" #include "v_palette.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 "m_random.h" #include "s_sound.h" #include "doomstat.h" #include "m_menu.h" #include "v_text.h" #include "st_stuff.h" #include "d_gui.h" #include "version.h" #include "m_png.h" #include "templates.h" #include "lists.h" #include "gi.h" #include "p_tick.h" #include "st_start.h" #include "teaminfo.h" #include "r_translate.h" #include "g_level.h" #include "d_event.h" #include "colormatcher.h" #include "d_netinf.h" // MACROS ------------------------------------------------------------------ #define SKULLXOFF -32 #define SELECTOR_XOFFSET (-28) #define SELECTOR_YOFFSET (-1) #define KEY_REPEAT_DELAY (TICRATE*5/12) #define KEY_REPEAT_RATE (3) #define INPUTGRID_WIDTH 13 #define INPUTGRID_HEIGHT 5 // TYPES ------------------------------------------------------------------- struct FSaveGameNode : public Node { char Title[SAVESTRINGSIZE]; FString Filename; bool bOldVersion; bool bMissingWads; }; struct FBackdropTexture : public FTexture { public: FBackdropTexture(); const BYTE *GetColumn(unsigned int column, const Span **spans_out); const BYTE *GetPixels(); void Unload(); bool CheckModified(); protected: BYTE Pixels[144*160]; static const Span DummySpan[2]; int LastRenderTic; angle_t time1, time2, time3, time4; angle_t t1ang, t2ang, z1ang, z2ang; void Render(); }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- void R_GetPlayerTranslation (int color, const FPlayerColorSet *colorset, FPlayerSkin *skin, FRemapTable *table); // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- int M_StringHeight (const char *string); void M_ClearMenus (); // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- static void M_NewGame (int choice); static void M_Episode (int choice); static void M_ChooseSkill (int choice); static void M_LoadGame (int choice); static void M_SaveGame (int choice); static void M_Options (int choice); static void M_EndGame (int choice); static void M_ReadThis (int choice); static void M_ReadThisMore (int choice); static void M_QuitGame (int choice); static void M_GameFiles (int choice); static void M_ClearSaveStuff (); static void SCClass (int choice); static void M_ChooseClass (int choice); static void M_FinishReadThis (int choice); static void M_QuickSave (); static void M_QuickLoad (); static void M_LoadSelect (const FSaveGameNode *file); static void M_SaveSelect (const FSaveGameNode *file); static void M_ReadSaveStrings (); static void M_UnloadSaveStrings (); static FSaveGameNode *M_RemoveSaveSlot (FSaveGameNode *file); static void M_ExtractSaveData (const FSaveGameNode *file); static void M_UnloadSaveData (); static void M_InsertSaveNode (FSaveGameNode *node); static bool M_SaveLoadResponder (event_t *ev); static void M_SaveLoadButtonHandler(EMenuKey key); static void M_DeleteSaveResponse (int choice); static void M_DrawMainMenu (); static void M_DrawReadThis (); static void M_DrawNewGame (); static void M_DrawEpisode (); static void M_DrawLoad (); static void M_DrawSave (); static void DrawClassMenu (); static void DrawHexenSkillMenu (); static void M_DrawClassMenu (); static void M_DrawHereticMainMenu (); static void M_DrawFiles (); void M_DrawFrame (int x, int y, int width, int height); static void M_DrawSaveLoadBorder (int x,int y, int len); static void M_DrawSaveLoadCommon (); static void M_DrawInputGrid(); static void M_SetupNextMenu (oldmenu_t *menudef); static void M_StartMessage (const char *string, void(*routine)(int)); static void M_EndMessage (int key); // [RH] For player setup menu. void M_PlayerSetup (); static void M_PlayerSetupTicker (); static void M_PlayerSetupDrawer (); static void M_EditPlayerName (int choice); static void M_ChangePlayerTeam (int choice); static void M_PlayerNameChanged (FSaveGameNode *dummy); static void M_PlayerNameNotChanged (); static void M_ChangeColorSet (int choice); static void M_SlidePlayerRed (int choice); static void M_SlidePlayerGreen (int choice); static void M_SlidePlayerBlue (int choice); static void M_ChangeClass (int choice); static void M_ChangeGender (int choice); static void M_ChangeSkin (int choice); static void M_ChangeAutoAim (int choice); static void PickPlayerClass (); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- EXTERN_CVAR (String, playerclass) EXTERN_CVAR (String, name) EXTERN_CVAR (Int, team) extern bool sendpause; extern int flagsvar; // PUBLIC DATA DEFINITIONS ------------------------------------------------- EMenuState menuactive; menustack_t MenuStack[16]; int MenuStackDepth; int skullAnimCounter; // skull animation counter bool drawSkull; // [RH] don't always draw skull bool M_DemoNoPlay; bool OptionsActive; FButtonStatus MenuButtons[NUM_MKEYS]; int MenuButtonTickers[NUM_MKEYS]; // PRIVATE DATA DEFINITIONS ------------------------------------------------ static char tempstring[80]; static char underscore[2]; static FSaveGameNode *quickSaveSlot; // NULL = no quicksave slot picked! static FSaveGameNode *lastSaveSlot; // Used for highlighting the most recently used slot in the menu static int messageToPrint; // 1 = message to be printed static const char *messageString; // ...and here is the message string! static EMenuState messageLastMenuActive; static void (*messageRoutine)(int response); // Non-NULL if only Y/N should close message static int showSharewareMessage; static int messageSelection; // 0 {Yes) or 1 (No) [if messageRoutine is non-NULL] static int genStringEnter; // we are going to be entering a savegame string static size_t genStringLen; // [RH] Max # of chars that can be entered static void (*genStringEnd)(FSaveGameNode *); static void (*genStringCancel)(); static int saveSlot; // which slot to save in static size_t saveCharIndex; // which char we're editing static int LINEHEIGHT; static const int PLAYERSETUP_LINEHEIGHT = 16; static char savegamestring[SAVESTRINGSIZE]; static FString EndString; static short itemOn; // menu item skull is on static int MenuTime; static int InfoType; static int InfoTic; static oldmenu_t *currentMenu; // current menudef static oldmenu_t *TopLevelMenu; // The main menu everything hangs off of static FBackdropTexture *FireTexture; static FRemapTable FireRemap(256); static const char *genders[3] = { "male", "female", "other" }; static FPlayerClass *PlayerClass; static int PlayerSkin; static FState *PlayerState; static int PlayerTics; static int PlayerRotation; static TArray PlayerColorSets; static FTexture *SavePic; static FBrokenLines *SaveComment; static List SaveGames; static FSaveGameNode *TopSaveGame; static FSaveGameNode *SelSaveGame; static FSaveGameNode NewSaveNode; static int epi; // Selected episode static const char *saved_playerclass = NULL; // Heretic and Hexen do not, by default, come with glyphs for all of these // characters. Oh well. Doom and Strife do. static const char InputGridChars[INPUTGRID_WIDTH * INPUTGRID_HEIGHT] = "ABCDEFGHIJKLM" "NOPQRSTUVWXYZ" "0123456789+-=" ".,!?@'\":;[]()" "<>^#$%&*/_ \b"; static int InputGridX = INPUTGRID_WIDTH - 1; static int InputGridY = INPUTGRID_HEIGHT - 1; static bool InputGridOkay; // Last input was with a controller. // PRIVATE MENU DEFINITIONS ------------------------------------------------ // // DOOM MENU // static oldmenuitem_t MainMenu[]= { {1,0,'n',"M_NGAME",M_NewGame, CR_UNTRANSLATED}, {1,0,'l',"M_LOADG",M_LoadGame, CR_UNTRANSLATED}, {1,0,'s',"M_SAVEG",M_SaveGame, CR_UNTRANSLATED}, {1,0,'o',"M_OPTION",M_Options, CR_UNTRANSLATED}, // [RH] Moved {1,0,'r',"M_RDTHIS",M_ReadThis, CR_UNTRANSLATED}, // Another hickup with Special edition. {1,0,'q',"M_QUITG",M_QuitGame, CR_UNTRANSLATED} }; static oldmenu_t MainDef = { countof(MainMenu), MainMenu, M_DrawMainMenu, 97,64, 0 }; // // HERETIC MENU // static oldmenuitem_t HereticMainMenu[] = { {1,1,'n',"$MNU_NEWGAME",M_NewGame, CR_UNTRANSLATED}, {1,1,'o',"$MNU_OPTIONS",M_Options, CR_UNTRANSLATED}, {1,1,'f',"$MNU_GAMEFILES",M_GameFiles, CR_UNTRANSLATED}, {1,1,'i',"$MNU_INFO",M_ReadThis, CR_UNTRANSLATED}, {1,1,'q',"$MNU_QUITGAME",M_QuitGame, CR_UNTRANSLATED} }; static oldmenu_t HereticMainDef = { countof(HereticMainMenu), HereticMainMenu, M_DrawHereticMainMenu, 110, 56, 0 }; // // HEXEN "NEW GAME" MENU // static oldmenuitem_t ClassItems[] = { { 1,1, 'f', "$MNU_FIGHTER", SCClass, CR_UNTRANSLATED }, { 1,1, 'c', "$MNU_CLERIC", SCClass, CR_UNTRANSLATED }, { 1,1, 'm', "$MNU_MAGE", SCClass, CR_UNTRANSLATED }, { 1,1, 'r', "$MNU_RANDOM", SCClass, CR_UNTRANSLATED } // [RH] }; static oldmenu_t ClassMenu = { 4, ClassItems, DrawClassMenu, 66, 58, 0 }; // // [GRB] CLASS SELECT // oldmenuitem_t ClassMenuItems[8] = { {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, }; oldmenu_t ClassMenuDef = { 0, ClassMenuItems, M_DrawClassMenu, 48,63, 0 }; // // EPISODE SELECT // oldmenuitem_t EpisodeMenu[MAX_EPISODES] = { {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, }; char EpisodeMaps[MAX_EPISODES][9]; bool EpisodeNoSkill[MAX_EPISODES]; oldmenu_t EpiDef = { 0, EpisodeMenu, // oldmenuitem_t -> M_DrawEpisode, // drawing routine -> 48,63, // x,y 0 // lastOn }; // // GAME FILES // static oldmenuitem_t FilesItems[] = { {1,1,'l',"$MNU_LOADGAME",M_LoadGame, CR_UNTRANSLATED}, {1,1,'s',"$MNU_SAVEGAME",M_SaveGame, CR_UNTRANSLATED} }; static oldmenu_t FilesMenu = { countof(FilesItems), FilesItems, M_DrawFiles, 110,60, 0 }; // // DOOM SKILL SELECT // static oldmenuitem_t SkillSelectMenu[]={ { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, }; static oldmenu_t SkillDef = { 0, SkillSelectMenu, // oldmenuitem_t -> M_DrawNewGame, // drawing routine -> 48,63, // x,y -1 // lastOn }; static oldmenu_t HexenSkillMenu = { 0, SkillSelectMenu, DrawHexenSkillMenu, 120, 44, -1 }; void M_StartupSkillMenu(const char *playerclass) { if (gameinfo.gametype & GAME_Raven) { SkillDef.x = 38; SkillDef.y = 30; if (gameinfo.gametype == GAME_Hexen) { HexenSkillMenu.x = 38; if (playerclass != NULL) { if (!stricmp(playerclass, "fighter")) HexenSkillMenu.x = 120; else if (!stricmp(playerclass, "cleric")) HexenSkillMenu.x = 116; else if (!stricmp(playerclass, "mage")) HexenSkillMenu.x = 112; } } } SkillDef.numitems = HexenSkillMenu.numitems = 0; for(unsigned int i = 0; i < AllSkills.Size() && i < 8; i++) { FSkillInfo &skill = AllSkills[i]; if (skill.PicName.Len() != 0) { SkillSelectMenu[i].name = skill.PicName; SkillSelectMenu[i].fulltext = false; } else { SkillSelectMenu[i].name = skill.MenuName; SkillSelectMenu[i].fulltext = true; } SkillSelectMenu[i].textcolor = skill.GetTextColor(); SkillSelectMenu[i].alphaKey = skill.Shortcut; if (playerclass != NULL) { FString * pmnm = skill.MenuNamesForPlayerClass.CheckKey(playerclass); if (pmnm != NULL) { SkillSelectMenu[i].name = GStrings(*pmnm); SkillSelectMenu[i].fulltext = true; if (skill.Shortcut == 0) SkillSelectMenu[i].alphaKey = tolower(SkillSelectMenu[i].name[0]); } } SkillDef.numitems++; HexenSkillMenu.numitems++; } int defskill = DefaultSkill; if ((unsigned int)defskill >= AllSkills.Size()) { defskill = (AllSkills.Size() - 1) / 2; } // The default skill is only set the first time the menu is opened. // After that, it opens on whichever skill you last selected. if (SkillDef.lastOn < 0) { SkillDef.lastOn = defskill; } if (HexenSkillMenu.lastOn < 0) { HexenSkillMenu.lastOn = defskill; } // Hexen needs some manual coordinate adjustments based on player class if (gameinfo.gametype == GAME_Hexen) { M_SetupNextMenu(&HexenSkillMenu); } else { M_SetupNextMenu(&SkillDef); } } // // [RH] Player Setup Menu // static oldmenuitem_t PlayerSetupMenu[] = { { 1,0,'n',NULL,M_EditPlayerName, CR_UNTRANSLATED}, { 2,0,'t',NULL,M_ChangePlayerTeam, CR_UNTRANSLATED}, { 2,0,'c',NULL,M_ChangeColorSet, CR_UNTRANSLATED}, { 2,0,'r',NULL,M_SlidePlayerRed, CR_UNTRANSLATED}, { 2,0,'g',NULL,M_SlidePlayerGreen, CR_UNTRANSLATED}, { 2,0,'b',NULL,M_SlidePlayerBlue, CR_UNTRANSLATED}, { 2,0,'t',NULL,M_ChangeClass, CR_UNTRANSLATED}, { 2,0,'s',NULL,M_ChangeSkin, CR_UNTRANSLATED}, { 2,0,'e',NULL,M_ChangeGender, CR_UNTRANSLATED}, { 2,0,'a',NULL,M_ChangeAutoAim, CR_UNTRANSLATED} }; enum { // These must be changed if the menu definition is altered PSM_RED = 3, PSM_GREEN = 4, PSM_BLUE = 5, }; static oldmenu_t PSetupDef = { countof(PlayerSetupMenu), PlayerSetupMenu, M_PlayerSetupDrawer, 48, 47, 0 }; // // Read This! MENU 1 & 2 // static oldmenuitem_t ReadMenu[] = { {1,0,0,NULL,M_ReadThisMore} }; static oldmenu_t ReadDef = { 1, ReadMenu, M_DrawReadThis, 280,185, 0 }; // // LOAD GAME MENU // static oldmenuitem_t LoadMenu[]= { {1,0,'1',NULL, NULL, CR_UNTRANSLATED}, {1,0,'2',NULL, NULL, CR_UNTRANSLATED}, {1,0,'3',NULL, NULL, CR_UNTRANSLATED}, {1,0,'4',NULL, NULL, CR_UNTRANSLATED}, {1,0,'5',NULL, NULL, CR_UNTRANSLATED}, {1,0,'6',NULL, NULL, CR_UNTRANSLATED}, {1,0,'7',NULL, NULL, CR_UNTRANSLATED}, {1,0,'8',NULL, NULL, CR_UNTRANSLATED}, }; static oldmenu_t LoadDef = { countof(LoadMenu), LoadMenu, M_DrawLoad, 80,54, 0 }; // // SAVE GAME MENU // static oldmenuitem_t SaveMenu[] = { {1,0,'1',NULL, NULL, CR_UNTRANSLATED}, {1,0,'2',NULL, NULL, CR_UNTRANSLATED}, {1,0,'3',NULL, NULL, CR_UNTRANSLATED}, {1,0,'4',NULL, NULL, CR_UNTRANSLATED}, {1,0,'5',NULL, NULL, CR_UNTRANSLATED}, {1,0,'6',NULL, NULL, CR_UNTRANSLATED}, {1,0,'7',NULL, NULL, CR_UNTRANSLATED}, {1,0,'8',NULL, NULL, CR_UNTRANSLATED}, }; static oldmenu_t SaveDef = { countof(LoadMenu), SaveMenu, M_DrawSave, 80,54, 0 }; // CODE -------------------------------------------------------------------- // [RH] Most menus can now be accessed directly // through console commands. CCMD (menu_main) { M_StartControlPanel (true, true); } CCMD (menu_load) { // F3 M_StartControlPanel (true); M_LoadGame (0); } CCMD (menu_save) { // F2 M_StartControlPanel (true); M_SaveGame (0); } CCMD (menu_help) { // F1 M_StartControlPanel (true); M_ReadThis (0); } CCMD (quicksave) { // F6 //M_StartControlPanel (true); S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); M_QuickSave(); } CCMD (quickload) { // F9 //M_StartControlPanel (true); S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); M_QuickLoad(); } CCMD (menu_endgame) { // F7 //M_StartControlPanel (true); S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); M_EndGame(0); } CCMD (menu_quit) { // F10 //M_StartControlPanel (true); S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); M_QuitGame(0); } CCMD (menu_game) { M_StartControlPanel (true); M_NewGame(0); } CCMD (menu_options) { M_StartControlPanel (true); M_Options(0); } CCMD (menu_player) { M_StartControlPanel (true); M_PlayerSetup (); } CCMD (bumpgamma) { // [RH] Gamma correction tables are now generated // on the fly for *any* gamma level. // Q: What are reasonable limits to use here? float newgamma = Gamma + 0.1f; if (newgamma > 3.0) newgamma = 1.0; Gamma = newgamma; Printf ("Gamma correction level %g\n", *Gamma); } void M_ActivateMenuInput () { ResetButtonStates (); menuactive = MENU_On; // Pause sound effects before we play the menu switch sound. // That way, it won't be paused. P_CheckTickerPaused (); } void M_DeactivateMenuInput () { menuactive = MENU_Off; } void M_DrawFiles () { } void M_GameFiles (int choice) { M_SetupNextMenu (&FilesMenu); } // // M_ReadSaveStrings // // Find savegames and read their titles // static void M_ReadSaveStrings () { if (SaveGames.IsEmpty ()) { void *filefirst; findstate_t c_file; FString filter; atterm (M_UnloadSaveStrings); filter = G_BuildSaveName ("*.zds", -1); filefirst = I_FindFirst (filter.GetChars(), &c_file); if (filefirst != ((void *)(-1))) { do { // I_FindName only returns the file's name and not its full path FString filepath = G_BuildSaveName (I_FindName(&c_file), -1); FILE *file = fopen (filepath, "rb"); if (file != NULL) { PNGHandle *png; char sig[16]; char title[SAVESTRINGSIZE+1]; bool oldVer = true; bool addIt = false; bool missing = false; // ZDoom 1.23 betas 21-33 have the savesig first. // Earlier versions have the savesig second. // Later versions have the savegame encapsulated inside a PNG. // // Old savegame versions are always added to the menu so // the user can easily delete them if desired. title[SAVESTRINGSIZE] = 0; if (NULL != (png = M_VerifyPNG (file))) { char *ver = M_GetPNGText (png, "ZDoom Save Version"); char *engine = M_GetPNGText (png, "Engine"); if (ver != NULL) { if (!M_GetPNGText (png, "Title", title, SAVESTRINGSIZE)) { strncpy (title, I_FindName(&c_file), SAVESTRINGSIZE); } if (strncmp (ver, SAVESIG, 9) == 0 && atoi (ver+9) >= MINSAVEVER && engine != NULL) { // Was saved with a compatible ZDoom version, // so check if it's for the current game. // If it is, add it. Otherwise, ignore it. char *iwad = M_GetPNGText (png, "Game WAD"); if (iwad != NULL) { if (stricmp (iwad, Wads.GetWadName (FWadCollection::IWAD_FILENUM)) == 0) { addIt = true; oldVer = false; missing = !G_CheckSaveGameWads (png, false); } delete[] iwad; } } else { // An old version addIt = true; } delete[] ver; } if (engine != NULL) { delete[] engine; } delete png; } else { fseek (file, 0, SEEK_SET); if (fread (sig, 1, 16, file) == 16) { if (strncmp (sig, "ZDOOMSAVE", 9) == 0) { if (fread (title, 1, SAVESTRINGSIZE, file) == SAVESTRINGSIZE) { addIt = true; } } else { memcpy (title, sig, 16); if (fread (title + 16, 1, SAVESTRINGSIZE-16, file) == SAVESTRINGSIZE-16 && fread (sig, 1, 16, file) == 16 && strncmp (sig, "ZDOOMSAVE", 9) == 0) { addIt = true; } } } } if (addIt) { FSaveGameNode *node = new FSaveGameNode; node->Filename = filepath; node->bOldVersion = oldVer; node->bMissingWads = missing; memcpy (node->Title, title, SAVESTRINGSIZE); M_InsertSaveNode (node); } fclose (file); } } while (I_FindNext (filefirst, &c_file) == 0); I_FindClose (filefirst); } } if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) { SelSaveGame = static_cast(SaveGames.Head); } } static void M_UnloadSaveStrings() { M_UnloadSaveData(); while (!SaveGames.IsEmpty()) { M_RemoveSaveSlot (static_cast(SaveGames.Head)); } } static FSaveGameNode *M_RemoveSaveSlot (FSaveGameNode *file) { FSaveGameNode *next = static_cast(file->Succ); if (file == TopSaveGame) { TopSaveGame = next; } if (quickSaveSlot == file) { quickSaveSlot = NULL; } if (lastSaveSlot == file) { lastSaveSlot = NULL; } file->Remove (); delete file; return next; } void M_InsertSaveNode (FSaveGameNode *node) { FSaveGameNode *probe; if (SaveGames.IsEmpty ()) { SaveGames.AddHead (node); return; } if (node->bOldVersion) { // Add node at bottom of list probe = static_cast(SaveGames.TailPred); while (probe->Pred != NULL && probe->bOldVersion && stricmp (node->Title, probe->Title) < 0) { probe = static_cast(probe->Pred); } node->Insert (probe); } else { // Add node at top of list probe = static_cast(SaveGames.Head); while (probe->Succ != NULL && !probe->bOldVersion && stricmp (node->Title, probe->Title) > 0) { probe = static_cast(probe->Succ); } node->InsertBefore (probe); } } void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave) { FSaveGameNode *node; if (file == NULL) return; M_ReadSaveStrings (); // See if the file is already in our list for (node = static_cast(SaveGames.Head); node->Succ != NULL; node = static_cast(node->Succ)) { #ifdef unix if (node->Filename.Compare (file) == 0) #else if (node->Filename.CompareNoCase (file) == 0) #endif { strcpy (node->Title, title); node->bOldVersion = false; node->bMissingWads = false; break; } } if (node->Succ == NULL) { node = new FSaveGameNode; strcpy (node->Title, title); node->Filename = file; node->bOldVersion = false; node->bMissingWads = false; M_InsertSaveNode (node); SelSaveGame = node; } if (okForQuicksave) { if (quickSaveSlot == NULL) quickSaveSlot = node; lastSaveSlot = node; } } // // M_LoadGame & Cie. // void M_DrawLoad (void) { if (gameinfo.gametype & (GAME_DoomStrifeChex)) { FTexture *title = TexMan["M_LOADG"]; screen->DrawTexture (title, (SCREENWIDTH - title->GetScaledWidth()*CleanXfac)/2, 20*CleanYfac, DTA_CleanNoMove, true, TAG_DONE); } else { const char *loadgame = GStrings("MNU_LOADGAME"); screen->DrawText (BigFont, CR_UNTRANSLATED, (SCREENWIDTH - BigFont->StringWidth (loadgame)*CleanXfac)/2, 10*CleanYfac, loadgame, DTA_CleanNoMove, true, TAG_DONE); } M_DrawSaveLoadCommon (); } // // Draw border for the savegame description // [RH] Width of the border is variable // void M_DrawSaveLoadBorder (int x, int y, int len) { if (gameinfo.gametype & (GAME_DoomStrifeChex)) { int i; screen->DrawTexture (TexMan["M_LSLEFT"], x-8, y+7, DTA_Clean, true, TAG_DONE); for (i = 0; i < len; i++) { screen->DrawTexture (TexMan["M_LSCNTR"], x, y+7, DTA_Clean, true, TAG_DONE); x += 8; } screen->DrawTexture (TexMan["M_LSRGHT"], x, y+7, DTA_Clean, true, TAG_DONE); } else { screen->DrawTexture (TexMan["M_FSLOT"], x, y+1, DTA_Clean, true, TAG_DONE); } } static void M_ExtractSaveData (const FSaveGameNode *node) { FILE *file; PNGHandle *png; M_UnloadSaveData (); if (node != NULL && node->Succ != NULL && !node->Filename.IsEmpty() && !node->bOldVersion && (file = fopen (node->Filename.GetChars(), "rb")) != NULL) { if (NULL != (png = M_VerifyPNG (file))) { char *time, *pcomment, *comment; size_t commentlen, totallen, timelen; // Extract comment time = M_GetPNGText (png, "Creation Time"); pcomment = M_GetPNGText (png, "Comment"); if (pcomment != NULL) { commentlen = strlen (pcomment); } else { commentlen = 0; } if (time != NULL) { timelen = strlen (time); totallen = timelen + commentlen + 3; } else { timelen = 0; totallen = commentlen + 1; } if (totallen != 0) { comment = new char[totallen]; if (timelen) { memcpy (comment, time, timelen); comment[timelen] = '\n'; comment[timelen+1] = '\n'; timelen += 2; } if (commentlen) { memcpy (comment + timelen, pcomment, commentlen); } comment[timelen+commentlen] = 0; SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, comment); delete[] comment; delete[] time; delete[] pcomment; } // Extract pic SavePic = PNGTexture_CreateFromFile(png, node->Filename); delete png; } fclose (file); } } static void M_UnloadSaveData () { if (SavePic != NULL) { delete SavePic; } if (SaveComment != NULL) { V_FreeBrokenLines (SaveComment); } SavePic = NULL; SaveComment = NULL; } static void M_DrawSaveLoadCommon () { const int savepicLeft = 10; const int savepicTop = 54*CleanYfac; const int savepicWidth = 216*screen->GetWidth()/640; const int savepicHeight = 135*screen->GetHeight()/400; const int rowHeight = (SmallFont->GetHeight() + 1) * CleanYfac; const int listboxLeft = savepicLeft + savepicWidth + 14; const int listboxTop = savepicTop; const int listboxWidth = screen->GetWidth() - listboxLeft - 10; const int listboxHeight1 = screen->GetHeight() - listboxTop - 10; const int listboxRows = (listboxHeight1 - 1) / rowHeight; const int listboxHeight = listboxRows * rowHeight + 1; const int listboxRight = listboxLeft + listboxWidth; const int listboxBottom = listboxTop + listboxHeight; const int commentLeft = savepicLeft; const int commentTop = savepicTop + savepicHeight + 16; const int commentWidth = savepicWidth; const int commentHeight = (51+(screen->GetHeight()>200?10:0))*CleanYfac; const int commentRight = commentLeft + commentWidth; const int commentBottom = commentTop + commentHeight; FSaveGameNode *node; int i; bool didSeeSelected = false; // Draw picture area if (gameaction == ga_loadgame || gameaction == ga_savegame) { return; } M_DrawFrame (savepicLeft, savepicTop, savepicWidth, savepicHeight); if (SavePic != NULL) { screen->DrawTexture(SavePic, savepicLeft, savepicTop, DTA_DestWidth, savepicWidth, DTA_DestHeight, savepicHeight, DTA_Masked, false, TAG_DONE); } else { screen->Clear (savepicLeft, savepicTop, savepicLeft+savepicWidth, savepicTop+savepicHeight, 0, 0); if (!SaveGames.IsEmpty ()) { const char *text = (SelSaveGame == NULL || !SelSaveGame->bOldVersion) ? GStrings("MNU_NOPICTURE") : GStrings("MNU_DIFFVERSION"); const int textlen = SmallFont->StringWidth (text)*CleanXfac; screen->DrawText (SmallFont, CR_GOLD, savepicLeft+(savepicWidth-textlen)/2, savepicTop+(savepicHeight-rowHeight)/2, text, DTA_CleanNoMove, true, TAG_DONE); } } // Draw comment area M_DrawFrame (commentLeft, commentTop, commentWidth, commentHeight); screen->Clear (commentLeft, commentTop, commentRight, commentBottom, 0, 0); if (SaveComment != NULL) { // I'm not sure why SaveComment would go NULL in this loop, but I got // a crash report where it was NULL when i reached 1, so now I check // for that. for (i = 0; SaveComment != NULL && SaveComment[i].Width >= 0 && i < 6; ++i) { screen->DrawText (SmallFont, CR_GOLD, commentLeft, commentTop + SmallFont->GetHeight()*i*CleanYfac, SaveComment[i].Text, DTA_CleanNoMove, true, TAG_DONE); } } // Draw file area do { M_DrawFrame (listboxLeft, listboxTop, listboxWidth, listboxHeight); screen->Clear (listboxLeft, listboxTop, listboxRight, listboxBottom, 0, 0); if (SaveGames.IsEmpty ()) { const char * text = GStrings("MNU_NOFILES"); const int textlen = SmallFont->StringWidth (text)*CleanXfac; screen->DrawText (SmallFont, CR_GOLD, listboxLeft+(listboxWidth-textlen)/2, listboxTop+(listboxHeight-rowHeight)/2, text, DTA_CleanNoMove, true, TAG_DONE); return; } for (i = 0, node = TopSaveGame; i < listboxRows && node->Succ != NULL; ++i, node = static_cast(node->Succ)) { int color; if (node->bOldVersion) { color = CR_BLUE; } else if (node->bMissingWads) { color = CR_ORANGE; } else if (node == SelSaveGame) { color = CR_WHITE; } else { color = CR_TAN; } if (node == SelSaveGame) { screen->Clear (listboxLeft, listboxTop+rowHeight*i, listboxRight, listboxTop+rowHeight*(i+1), -1, genStringEnter ? MAKEARGB(255,255,0,0) : MAKEARGB(255,0,0,255)); didSeeSelected = true; if (!genStringEnter) { screen->DrawText (SmallFont, color, listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, DTA_CleanNoMove, true, TAG_DONE); } else { screen->DrawText (SmallFont, CR_WHITE, listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, savegamestring, DTA_CleanNoMove, true, TAG_DONE); screen->DrawText (SmallFont, CR_WHITE, listboxLeft+1+SmallFont->StringWidth (savegamestring)*CleanXfac, listboxTop+rowHeight*i+CleanYfac, underscore, DTA_CleanNoMove, true, TAG_DONE); } } else { screen->DrawText (SmallFont, color, listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, DTA_CleanNoMove, true, TAG_DONE); } } // This is dumb: If the selected node was not visible, // scroll down and redraw. M_SaveLoadResponder() // guarantees that if the node is not visible, it will // always be below the visible list instead of above it. // This should not really be done here, but I don't care. if (!didSeeSelected) { for (i = 1; node->Succ != NULL && node != SelSaveGame; ++i) { node = static_cast(node->Succ); } if (node->Succ == NULL) { // SelSaveGame is invalid didSeeSelected = true; } else { do { TopSaveGame = static_cast(TopSaveGame->Succ); } while (--i); } } } while (!didSeeSelected); } // Draw a frame around the specified area using the view border // frame graphics. The border is drawn outside the area, not in it. void M_DrawFrame (int left, int top, int width, int height) { FTexture *p; const gameborder_t *border = gameinfo.border; // Sanity check for incomplete gameinfo if (border == NULL) return; int offset = border->offset; int right = left + width; int bottom = top + height; // Draw top and bottom sides. p = TexMan[border->t]; screen->FlatFill(left, top - p->GetHeight(), right, top, p, true); p = TexMan[border->b]; screen->FlatFill(left, bottom, right, bottom + p->GetHeight(), p, true); // Draw left and right sides. p = TexMan[border->l]; screen->FlatFill(left - p->GetWidth(), top, left, bottom, p, true); p = TexMan[border->r]; screen->FlatFill(right, top, right + p->GetWidth(), bottom, p, true); // Draw beveled corners. screen->DrawTexture (TexMan[border->tl], left-offset, top-offset, TAG_DONE); screen->DrawTexture (TexMan[border->tr], left+width, top-offset, TAG_DONE); screen->DrawTexture (TexMan[border->bl], left-offset, top+height, TAG_DONE); screen->DrawTexture (TexMan[border->br], left+width, top+height, TAG_DONE); } // // Selected from DOOM menu // void M_LoadGame (int choice) { if (netgame) { if(gameinfo.gametype == GAME_Chex) M_StartMessage (GStrings("CLOADNET"), NULL); else M_StartMessage (GStrings("LOADNET"), NULL); return; } M_SetupNextMenu (&LoadDef); drawSkull = false; M_ReadSaveStrings (); TopSaveGame = static_cast(SaveGames.Head); M_ExtractSaveData (SelSaveGame); } // // M_SaveGame & Cie. // void M_DrawSave() { if (gameinfo.gametype & (GAME_DoomStrifeChex)) { FTexture *title = TexMan["M_SAVEG"]; screen->DrawTexture (title, (SCREENWIDTH-title->GetScaledWidth()*CleanXfac)/2, 20*CleanYfac, DTA_CleanNoMove, true, TAG_DONE); } else { const char *text = GStrings("MNU_SAVEGAME"); screen->DrawText (BigFont, CR_UNTRANSLATED, (SCREENWIDTH - BigFont->StringWidth (text)*CleanXfac)/2, 10*CleanYfac, text, DTA_CleanNoMove, true, TAG_DONE); } M_DrawSaveLoadCommon (); } // // M_Responder calls this when the user is finished // void M_DoSave (FSaveGameNode *node) { if (node != &NewSaveNode) { G_SaveGame (node->Filename.GetChars(), savegamestring); } else { // Find an unused filename and save as that FString filename; int i; FILE *test; for (i = 0;; ++i) { filename = G_BuildSaveName ("save", i); test = fopen (filename, "rb"); if (test == NULL) { break; } fclose (test); } G_SaveGame (filename, savegamestring); } M_ClearMenus (); BorderNeedRefresh = screen->GetPageCount (); } // // Selected from DOOM menu // void M_SaveGame (int choice) { if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) { M_StartMessage (GStrings("SAVEDEAD"), NULL); return; } if (gamestate != GS_LEVEL) return; M_SetupNextMenu(&SaveDef); drawSkull = false; M_ReadSaveStrings(); SaveGames.AddHead (&NewSaveNode); TopSaveGame = static_cast(SaveGames.Head); if (lastSaveSlot == NULL) { SelSaveGame = &NewSaveNode; } else { SelSaveGame = lastSaveSlot; } M_ExtractSaveData (SelSaveGame); } // // M_QuickSave // void M_QuickSaveResponse (int ch) { if (ch == 'y') { M_DoSave (quickSaveSlot); S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); } } void M_QuickSave () { if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) { S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", 1, ATTN_NONE); return; } if (gamestate != GS_LEVEL) return; if (quickSaveSlot == NULL) { M_StartControlPanel(false); M_SaveGame (0); return; } if(gameinfo.gametype == GAME_Chex) mysnprintf (tempstring, countof(tempstring), GStrings("CQSPROMPT"), quickSaveSlot->Title); else mysnprintf (tempstring, countof(tempstring), GStrings("QSPROMPT"), quickSaveSlot->Title); strcpy (savegamestring, quickSaveSlot->Title); M_StartMessage (tempstring, M_QuickSaveResponse); } // // M_QuickLoad // void M_QuickLoadResponse (int ch) { if (ch == 'y') { M_LoadSelect (quickSaveSlot); S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); } } void M_QuickLoad () { if (netgame) { if(gameinfo.gametype == GAME_Chex) M_StartMessage (GStrings("CQLOADNET"), NULL); else M_StartMessage (GStrings("QLOADNET"), NULL); return; } if (quickSaveSlot == NULL) { M_StartControlPanel(false); // signal that whatever gets loaded should be the new quicksave quickSaveSlot = (FSaveGameNode *)1; M_LoadGame (0); return; } if(gameinfo.gametype == GAME_Chex) mysnprintf (tempstring, countof(tempstring), GStrings("CQLPROMPT"), quickSaveSlot->Title); else mysnprintf (tempstring, countof(tempstring), GStrings("QLPROMPT"), quickSaveSlot->Title); M_StartMessage (tempstring, M_QuickLoadResponse); } // // Read This Menus // void M_DrawReadThis () { FTexture *tex = NULL, *prevpic = NULL; fixed_t alpha; // Did the mapper choose a custom help page via MAPINFO? if ((level.info != NULL) && level.info->f1[0] != 0) { tex = TexMan.FindTexture(level.info->f1); InfoType = 1; } if (tex == NULL) { tex = TexMan[gameinfo.infoPages[InfoType-1].GetChars()]; } if (InfoType > 1) { prevpic = TexMan[gameinfo.infoPages[InfoType-2].GetChars()]; } alpha = MIN (Scale (gametic - InfoTic, OPAQUE, TICRATE/3), OPAQUE); if (alpha < OPAQUE && prevpic != NULL) { screen->DrawTexture (prevpic, 0, 0, DTA_DestWidth, screen->GetWidth(), DTA_DestHeight, screen->GetHeight(), TAG_DONE); } screen->DrawTexture (tex, 0, 0, DTA_DestWidth, screen->GetWidth(), DTA_DestHeight, screen->GetHeight(), DTA_Alpha, alpha, TAG_DONE); } // // M_DrawMainMenu // void M_DrawMainMenu (void) { if (gameinfo.gametype & GAME_DoomChex) { screen->DrawTexture (TexMan["M_DOOM"], 94, 2, DTA_Clean, true, TAG_DONE); } else { screen->DrawTexture (TexMan["M_STRIFE"], 84, 2, DTA_Clean, true, TAG_DONE); } } void M_DrawHereticMainMenu () { char name[9]; screen->DrawTexture (TexMan["M_HTIC"], 88, 0, DTA_Clean, true, TAG_DONE); if (gameinfo.gametype == GAME_Hexen) { int frame = (MenuTime / 5) % 7; mysnprintf (name, countof(name), "FBUL%c0", (frame+2)%7 + 'A'); screen->DrawTexture (TexMan[name], 37, 80, DTA_Clean, true, TAG_DONE); mysnprintf (name, countof(name), "FBUL%c0", frame + 'A'); screen->DrawTexture (TexMan[name], 278, 80, DTA_Clean, true, TAG_DONE); } else { int frame = (MenuTime / 3) % 18; mysnprintf (name, countof(name), "M_SKL%.2d", 17 - frame); screen->DrawTexture (TexMan[name], 40, 10, DTA_Clean, true, TAG_DONE); mysnprintf (name, countof(name), "M_SKL%.2d", frame); screen->DrawTexture (TexMan[name], 232, 10, DTA_Clean, true, TAG_DONE); } } // // M_NewGame // void M_DrawNewGame(void) { if (gameinfo.gametype & (GAME_DoomStrifeChex)) { screen->DrawTexture (TexMan[gameinfo.gametype & GAME_DoomChex ? "M_NEWG" : "M_NGAME"], 96, 14, DTA_Clean, true, TAG_DONE); screen->DrawTexture (TexMan["M_SKILL"], 54, 38, DTA_Clean, true, TAG_DONE); } } void M_NewGame(int choice) { if (netgame && !demoplayback) { if(gameinfo.gametype == GAME_Chex) M_StartMessage (GStrings("CNEWGAME"), NULL); else M_StartMessage (GStrings("NEWGAME"), NULL); return; } // Set up episode menu positioning if (gameinfo.gametype & (GAME_DoomStrifeChex)) { EpiDef.x = 48; EpiDef.y = 63; } else { EpiDef.x = 80; EpiDef.y = 50; } if (EpiDef.numitems > 4) { EpiDef.y -= LINEHEIGHT; } epi = 0; if (gameinfo.gametype == GAME_Hexen && ClassMenuDef.numitems == 0) { // [RH] Make the default entry the last class the player used. ClassMenu.lastOn = players[consoleplayer].userinfo.PlayerClass; if (ClassMenu.lastOn < 0) { ClassMenu.lastOn = 3; } M_SetupNextMenu (&ClassMenu); } // [GRB] Class select else if (ClassMenuDef.numitems > 1) { ClassMenuDef.lastOn = ClassMenuDef.numitems - 1; if (players[consoleplayer].userinfo.PlayerClass >= 0) { int n = 0; for (int i = 0; i < (int)PlayerClasses.Size () && n < 7; i++) { if (!(PlayerClasses[i].Flags & PCF_NOMENU)) { if (i == players[consoleplayer].userinfo.PlayerClass) { ClassMenuDef.lastOn = n; break; } n++; } } } PickPlayerClass (); PlayerState = GetDefaultByType (PlayerClass->Type)->SeeState; PlayerTics = PlayerState->GetTics(); if (FireTexture == NULL) { FireTexture = new FBackdropTexture; } M_SetupNextMenu (&ClassMenuDef); } else if (EpiDef.numitems <= 1) { if (AllSkills.Size() == 1) { M_ChooseSkill(0); } else if (EpisodeNoSkill[0]) { M_ChooseSkill(AllSkills.Size() == 2? 1:2); } else { M_StartupSkillMenu(NULL); } } else { M_SetupNextMenu (&EpiDef); } } //========================================================================== // // DrawClassMenu // //========================================================================== static void DrawClassMenu(void) { char name[9]; int classnum; static const char boxLumpName[3][7] = { "M_FBOX", "M_CBOX", "M_MBOX" }; static const char walkLumpName[3][10] = { "M_FWALK%d", "M_CWALK%d", "M_MWALK%d" }; const char *text = GStrings("MNU_CHOOSECLASS"); screen->DrawText (BigFont, CR_UNTRANSLATED, 34, 24, text, DTA_Clean, true, TAG_DONE); classnum = itemOn; if (classnum > 2) { classnum = (MenuTime>>2) % 3; } screen->DrawTexture (TexMan[boxLumpName[classnum]], 174, 8, DTA_Clean, true, TAG_DONE); mysnprintf (name, countof(name), walkLumpName[classnum], ((MenuTime >> 3) & 3) + 1); screen->DrawTexture (TexMan[name], 174+24, 8+12, DTA_Clean, true, TAG_DONE); } // [GRB] Class select drawer static void M_DrawClassMenu () { int tit_y = 15; const char * text = GStrings("MNU_CHOOSECLASS"); if (ClassMenuDef.numitems > 4 && gameinfo.gametype & GAME_Raven) tit_y = 2; screen->DrawText (BigFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, 160 - BigFont->StringWidth (text)/2, tit_y, text, DTA_Clean, true, TAG_DONE); int x = (200-160)*CleanXfac+(SCREENWIDTH>>1); int y = (ClassMenuDef.y-100)*CleanYfac+(SCREENHEIGHT>>1); if (!FireTexture) { screen->Clear (x, y, x + 72 * CleanXfac, y + 80 * CleanYfac-1, 0, 0); } else { screen->DrawTexture (FireTexture, x, y - 1, DTA_DestWidth, 72 * CleanXfac, DTA_DestHeight, 80 * CleanYfac, DTA_Translation, &FireRemap, DTA_Masked, true, TAG_DONE); } M_DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1); spriteframe_t *sprframe = &SpriteFrames[sprites[PlayerState->sprite].spriteframes + PlayerState->GetFrame()]; fixed_t scaleX = GetDefaultByType (PlayerClass->Type)->scaleX; fixed_t scaleY = GetDefaultByType (PlayerClass->Type)->scaleY; if (sprframe != NULL) { FTexture *tex = TexMan(sprframe->Texture[0]); if (tex != NULL && tex->UseType != FTexture::TEX_Null) { screen->DrawTexture (tex, x + 36*CleanXfac, y + 71*CleanYfac, DTA_DestWidth, MulScale16 (tex->GetWidth() * CleanXfac, scaleX), DTA_DestHeight, MulScale16 (tex->GetHeight() * CleanYfac, scaleY), TAG_DONE); } } } //--------------------------------------------------------------------------- // // PROC DrawSkillMenu // //--------------------------------------------------------------------------- static void DrawHexenSkillMenu() { screen->DrawText (BigFont, CR_UNTRANSLATED, 74, 16, GStrings("MNU_CHOOSESKILL"), DTA_Clean, true, TAG_DONE); } // // M_Episode // void M_DrawEpisode () { if (gameinfo.gametype & (GAME_DoomStrifeChex)) { screen->DrawTexture (TexMan["M_EPISOD"], 54, 38, DTA_Clean, true, TAG_DONE); } } static int confirmskill; void M_VerifyNightmare (int ch) { if (ch != 'y') return; G_DeferedInitNew (EpisodeMaps[epi], confirmskill); if (gamestate == GS_FULLCONSOLE) { gamestate = GS_HIDECONSOLE; gameaction = ga_newgame; } M_ClearMenus (); } void M_ChooseSkill (int choice) { if (AllSkills[choice].MustConfirm) { const char *msg = AllSkills[choice].MustConfirmText; if (*msg==0) msg = GStrings("NIGHTMARE"); if (*msg=='$') msg = GStrings(msg+1); confirmskill = choice; M_StartMessage (msg, M_VerifyNightmare); return; } G_DeferedInitNew (EpisodeMaps[epi], choice); if (gamestate == GS_FULLCONSOLE) { gamestate = GS_HIDECONSOLE; gameaction = ga_newgame; } M_ClearMenus (); } void M_Episode (int choice) { if ((gameinfo.flags & GI_SHAREWARE) && choice) { if (gameinfo.gametype == GAME_Doom) { M_StartMessage(GStrings("SWSTRING"), NULL); //M_SetupNextMenu(&ReadDef); } else if (gameinfo.gametype == GAME_Chex) { M_StartMessage(GStrings("CSWSTRING"), NULL); } else { showSharewareMessage = 3*TICRATE; } return; } epi = choice; if (AllSkills.Size() == 1) { saved_playerclass = NULL; M_ChooseSkill(0); return; } else if (EpisodeNoSkill[choice]) { saved_playerclass = NULL; M_ChooseSkill(AllSkills.Size() == 2? 1:2); return; } M_StartupSkillMenu(saved_playerclass); saved_playerclass = NULL; } //========================================================================== // // SCClass // //========================================================================== static void SCClass (int option) { if (netgame) { if(gameinfo.gametype == GAME_Chex) M_StartMessage (GStrings("CNEWGAME"), NULL); else M_StartMessage (GStrings("NEWGAME"), NULL); return; } if (option == 3) playerclass = "Random"; else playerclass = PlayerClasses[option].Type->Meta.GetMetaString (APMETA_DisplayName); if (EpiDef.numitems > 1) { saved_playerclass = playerclass; M_SetupNextMenu (&EpiDef); } else if (AllSkills.Size() == 1) { M_ChooseSkill(0); } else if (!EpisodeNoSkill[0]) { M_StartupSkillMenu(playerclass); } else { M_ChooseSkill(AllSkills.Size() == 2? 1:2); } } // [GRB] static void M_ChooseClass (int choice) { if (netgame) { if(gameinfo.gametype == GAME_Chex) M_StartMessage (GStrings("CNEWGAME"), NULL); else M_StartMessage (GStrings("NEWGAME"), NULL); return; } playerclass = (choice < ClassMenuDef.numitems-1) ? ClassMenuItems[choice].name : "Random"; if (EpiDef.numitems > 1) { saved_playerclass = playerclass; M_SetupNextMenu (&EpiDef); } else if (AllSkills.Size() == 1) { M_ChooseSkill(0); } else if (EpisodeNoSkill[0]) { M_ChooseSkill(AllSkills.Size() == 2? 1:2); } else { M_StartupSkillMenu(playerclass); } } void M_Options (int choice) { OptionsActive = M_StartOptionsMenu (); } // // 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_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", 1, ATTN_NONE); return; } if (netgame) { if(gameinfo.gametype == GAME_Chex) M_StartMessage(GStrings("CNETEND"), NULL); else M_StartMessage(GStrings("NETEND"), NULL); return; } if(gameinfo.gametype == GAME_Chex) M_StartMessage(GStrings("CENDGAME"), M_EndGameResponse); else M_StartMessage(GStrings("ENDGAME"), M_EndGameResponse); } // // M_ReadThis // void M_ReadThis (int choice) { drawSkull = false; InfoType = 1; InfoTic = gametic; M_SetupNextMenu (&ReadDef); } void M_ReadThisMore (int choice) { InfoType++; InfoTic = gametic; if ((level.info != NULL && level.info->f1[0] != 0) || InfoType > int(gameinfo.infoPages.Size())) { M_FinishReadThis (0); } } void M_FinishReadThis (int choice) { drawSkull = true; M_PopMenuStack (); } // // M_QuitGame // void M_QuitResponse(int ch) { if (ch != 'y') return; if (!netgame) { if (gameinfo.quitSound.IsNotEmpty()) { S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.quitSound, 1, ATTN_NONE); I_WaitVBL (105); } } ST_Endoom(); } void M_QuitGame (int choice) { if (gameinfo.gametype & (GAME_DoomStrifeChex)) { int quitmsg = 0; if (gameinfo.gametype == GAME_Doom) { quitmsg = gametic % (NUM_QUITDOOMMESSAGES + 1); } else if (gameinfo.gametype == GAME_Strife) { quitmsg = gametic % (NUM_QUITSTRIFEMESSAGES + 1); if (quitmsg != 0) quitmsg += NUM_QUITDOOMMESSAGES; } else { quitmsg = gametic % (NUM_QUITCHEXMESSAGES + 1); if (quitmsg != 0) quitmsg += NUM_QUITDOOMMESSAGES + NUM_QUITSTRIFEMESSAGES; } if (quitmsg != 0) { EndString.Format("QUITMSG%d", quitmsg); EndString.Format("%s\n\n%s", GStrings(EndString), GStrings("DOSY")); } else { EndString.Format("%s\n\n%s", GStrings("QUITMSG"), GStrings("DOSY")); } } else { EndString = GStrings("RAVENQUITMSG"); } M_StartMessage (EndString, M_QuitResponse); } // // [RH] Player Setup Menu code // void M_PlayerSetup (void) { OptionsActive = false; drawSkull = true; strcpy (savegamestring, name); M_DemoNoPlay = true; if (demoplayback) G_CheckDemoStatus (); M_SetupNextMenu (&PSetupDef); if (players[consoleplayer].mo != NULL) { PlayerClass = &PlayerClasses[players[consoleplayer].CurrentPlayerClass]; } PlayerSkin = players[consoleplayer].userinfo.skin; R_GetPlayerTranslation (players[consoleplayer].userinfo.color, P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset), &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); PlayerState = GetDefaultByType (PlayerClass->Type)->SeeState; PlayerTics = PlayerState->GetTics(); if (FireTexture == NULL) { FireTexture = new FBackdropTexture; } P_EnumPlayerColorSets(PlayerClass->Type->TypeName, &PlayerColorSets); } static void M_PlayerSetupTicker (void) { // Based on code in f_finale.c FPlayerClass *oldclass = PlayerClass; if (currentMenu == &ClassMenuDef) { int item; if (itemOn < ClassMenuDef.numitems-1) item = itemOn; else item = (MenuTime>>2) % (ClassMenuDef.numitems-1); PlayerClass = &PlayerClasses[D_PlayerClassToInt (ClassMenuItems[item].name)]; P_EnumPlayerColorSets(PlayerClass->Type->TypeName, &PlayerColorSets); } else { PickPlayerClass (); } if (PlayerClass != oldclass) { PlayerState = GetDefaultByType (PlayerClass->Type)->SeeState; PlayerTics = PlayerState->GetTics(); PlayerSkin = R_FindSkin (skins[PlayerSkin].name, int(PlayerClass - &PlayerClasses[0])); R_GetPlayerTranslation (players[consoleplayer].userinfo.color, P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset), &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); } if (PlayerState->GetTics () != -1 && PlayerState->GetNextState () != NULL) { if (--PlayerTics > 0) return; PlayerState = PlayerState->GetNextState(); PlayerTics = PlayerState->GetTics(); } } static void M_DrawPlayerSlider (int x, int y, int cur) { const int range = 255; x = (x - 160) * CleanXfac + screen->GetWidth() / 2; y = (y - 100) * CleanYfac + screen->GetHeight() / 2; screen->DrawText (ConFont, CR_WHITE, x, y, "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12", DTA_CellX, 8 * CleanXfac, DTA_CellY, 8 * CleanYfac, TAG_DONE); screen->DrawText (ConFont, CR_ORANGE, x + (5 + (int)((cur * 78) / range)) * CleanXfac, y, "\x13", DTA_CellX, 8 * CleanXfac, DTA_CellY, 8 * CleanYfac, TAG_DONE); } static void M_PlayerSetupDrawer () { const int LINEHEIGHT = PLAYERSETUP_LINEHEIGHT; int x, xo, yo; EColorRange label, value; DWORD color; if (!(gameinfo.gametype & (GAME_DoomStrifeChex))) { xo = 5; yo = 5; label = CR_GREEN; value = CR_UNTRANSLATED; } else { xo = yo = 0; label = CR_UNTRANSLATED; value = CR_GREY; } // Draw title const char *text = GStrings("MNU_PLAYERSETUP"); screen->DrawText (BigFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, 160 - BigFont->StringWidth (text)/2, 15, text, DTA_Clean, true, TAG_DONE); // Draw player name box screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y+yo, "Name", DTA_Clean, true, TAG_DONE); M_DrawSaveLoadBorder (PSetupDef.x + 56, PSetupDef.y, MAXPLAYERNAME+1); screen->DrawText (SmallFont, CR_UNTRANSLATED, PSetupDef.x + 56 + xo, PSetupDef.y+yo, savegamestring, DTA_Clean, true, TAG_DONE); // Draw cursor for player name box if (genStringEnter) screen->DrawText (SmallFont, CR_UNTRANSLATED, PSetupDef.x + SmallFont->StringWidth(savegamestring) + 56+xo, PSetupDef.y + yo, underscore, DTA_Clean, true, TAG_DONE); // Draw player team setting x = SmallFont->StringWidth ("Team") + 8 + PSetupDef.x; screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT+yo, "Team", DTA_Clean, true, TAG_DONE); screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT+yo, !TeamLibrary.IsValidTeam (players[consoleplayer].userinfo.team) ? "None" : Teams[players[consoleplayer].userinfo.team].GetName (), DTA_Clean, true, TAG_DONE); // Draw player character { int x = 320 - 88 - 32 + xo, y = PSetupDef.y + LINEHEIGHT*3 - 18 + yo; x = (x-160)*CleanXfac+(SCREENWIDTH>>1); y = (y-100)*CleanYfac+(SCREENHEIGHT>>1); if (!FireTexture) { screen->Clear (x, y, x + 72 * CleanXfac, y + 80 * CleanYfac-1, 0, 0); } else { screen->DrawTexture (FireTexture, x, y - 1, DTA_DestWidth, 72 * CleanXfac, DTA_DestHeight, 80 * CleanYfac, DTA_Translation, &FireRemap, DTA_Masked, false, TAG_DONE); } M_DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1); } { spriteframe_t *sprframe; fixed_t ScaleX, ScaleY; if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || players[consoleplayer].userinfo.PlayerClass == -1 || PlayerState->sprite != GetDefaultByType (PlayerClass->Type)->SpawnState->sprite) { sprframe = &SpriteFrames[sprites[PlayerState->sprite].spriteframes + PlayerState->GetFrame()]; ScaleX = GetDefaultByType(PlayerClass->Type)->scaleX; ScaleY = GetDefaultByType(PlayerClass->Type)->scaleY; } else { sprframe = &SpriteFrames[sprites[skins[PlayerSkin].sprite].spriteframes + PlayerState->GetFrame()]; ScaleX = skins[PlayerSkin].ScaleX; ScaleY = skins[PlayerSkin].ScaleY; } if (sprframe != NULL) { FTexture *tex = TexMan(sprframe->Texture[0]); if (tex != NULL && tex->UseType != FTexture::TEX_Null) { if (tex->Rotations != 0xFFFF) { tex = TexMan(SpriteFrames[tex->Rotations].Texture[PlayerRotation]); } screen->DrawTexture (tex, (320 - 52 - 32 + xo - 160)*CleanXfac + (SCREENWIDTH)/2, (PSetupDef.y + LINEHEIGHT*3 + 57 - 104)*CleanYfac + (SCREENHEIGHT/2), DTA_DestWidth, MulScale16 (tex->GetWidth() * CleanXfac, ScaleX), DTA_DestHeight, MulScale16 (tex->GetHeight() * CleanYfac, ScaleY), DTA_Translation, translationtables[TRANSLATION_Players](MAXPLAYERS), TAG_DONE); } } const char *str = "PRESS " TEXTCOLOR_WHITE "SPACE"; screen->DrawText (SmallFont, CR_GOLD, 320 - 52 - 32 - SmallFont->StringWidth (str)/2, PSetupDef.y + LINEHEIGHT*3 + 76, str, DTA_Clean, true, TAG_DONE); str = PlayerRotation ? "TO SEE FRONT" : "TO SEE BACK"; screen->DrawText (SmallFont, CR_GOLD, 320 - 52 - 32 - SmallFont->StringWidth (str)/2, PSetupDef.y + LINEHEIGHT*3 + 76 + SmallFont->GetHeight (), str, DTA_Clean, true, TAG_DONE); } // Draw player color selection and sliders FPlayerColorSet *colorset = P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset); x = SmallFont->StringWidth("Color") + 8 + PSetupDef.x; screen->DrawText(SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*2+yo, "Color", DTA_Clean, true, TAG_DONE); screen->DrawText(SmallFont, value, x, PSetupDef.y + LINEHEIGHT*2+yo, colorset != NULL ? colorset->Name.GetChars() : "Custom", DTA_Clean, true, TAG_DONE); // Only show the sliders for a custom color set. if (colorset == NULL) { screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + int(LINEHEIGHT*2.875)+yo, "Red", DTA_Clean, true, TAG_DONE); screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + int(LINEHEIGHT*3.5)+yo, "Green", DTA_Clean, true, TAG_DONE); screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + int(LINEHEIGHT*4.125)+yo, "Blue", DTA_Clean, true, TAG_DONE); x = SmallFont->StringWidth ("Green") + 8 + PSetupDef.x; color = players[consoleplayer].userinfo.color; M_DrawPlayerSlider (x, PSetupDef.y + int(LINEHEIGHT*2.875)+yo, RPART(color)); M_DrawPlayerSlider (x, PSetupDef.y + int(LINEHEIGHT*3.5)+yo, GPART(color)); M_DrawPlayerSlider (x, PSetupDef.y + int(LINEHEIGHT*4.125)+yo, BPART(color)); } // [GRB] Draw class setting int pclass = players[consoleplayer].userinfo.PlayerClass; x = SmallFont->StringWidth ("Class") + 8 + PSetupDef.x; screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*5+yo, "Class", DTA_Clean, true, TAG_DONE); screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*5+yo, pclass == -1 ? "Random" : PlayerClasses[pclass].Type->Meta.GetMetaString (APMETA_DisplayName), DTA_Clean, true, TAG_DONE); // Draw skin setting x = SmallFont->StringWidth ("Skin") + 8 + PSetupDef.x; screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*6+yo, "Skin", DTA_Clean, true, TAG_DONE); if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || players[consoleplayer].userinfo.PlayerClass == -1) { screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*6+yo, "Base", DTA_Clean, true, TAG_DONE); } else { screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*6+yo, skins[PlayerSkin].name, DTA_Clean, true, TAG_DONE); } // Draw gender setting x = SmallFont->StringWidth ("Gender") + 8 + PSetupDef.x; screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*7+yo, "Gender", DTA_Clean, true, TAG_DONE); screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*7+yo, genders[players[consoleplayer].userinfo.gender], DTA_Clean, true, TAG_DONE); // Draw autoaim setting x = SmallFont->StringWidth ("Autoaim") + 8 + PSetupDef.x; screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*8+yo, "Autoaim", DTA_Clean, true, TAG_DONE); screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*8+yo, autoaim == 0 ? "Never" : autoaim <= 0.25 ? "Very Low" : autoaim <= 0.5 ? "Low" : autoaim <= 1 ? "Medium" : autoaim <= 2 ? "High" : autoaim <= 3 ? "Very High" : "Always", DTA_Clean, true, TAG_DONE); } // A 32x32 cloud rendered with Photoshop, plus some other filters static BYTE pattern1[1024] = { 5, 9, 7,10, 9,15, 9, 7, 8,10, 5, 3, 5, 7, 9, 8,14, 8, 4, 7, 8, 9, 5, 7,14, 7, 0, 7,13,13, 9, 6, 2, 7, 9, 7, 7,10, 8, 8,11,10, 6, 7,10, 7, 5, 6, 6, 4, 7,13,15,16,11,15,11, 8, 0, 4,13,22,17,11, 5, 9, 9, 7, 9,10, 4, 3, 6, 7, 8, 6, 5, 4, 2, 2, 1, 4, 6,11,15,15,14,13,17, 9, 5, 9,11,12,17,20, 9,16, 9, 8,12,13, 7, 3, 7, 9, 5, 4, 2, 5, 5, 5, 7,11, 6, 7, 6,13,17,10,10, 9,12,17,14,12,16,15, 15,13, 5, 3, 9,10, 4,10,12,12, 7, 9, 8, 8, 8,10, 7, 6, 5, 5, 5, 6,11, 9, 3,13,16,18,21,16,23,18, 23,13, 0, 0, 0, 0, 0,12,18,14,15,16,13, 7, 7, 5, 9, 6, 6, 8, 4, 0, 0, 0, 0,14,19,17,14,20,21,25, 19,20,14,13, 7, 5,13,19,14,13,17,15,14, 7, 3, 5, 6,11, 7, 7, 8, 8,10, 9, 9,18,17,15,14,15,18,16, 16,29,24,23,18, 9,17,20,11, 5,12,15,15,12, 6, 3, 4, 6, 7,10,13,18,18,19,16,12,17,19,23,16,14,14, 9,18,20,26,19, 5,18,18,10, 5,12,15,14,17,11, 6,11, 9,10,13,10,20,24,20,21,20,14,18,15,22,20,19, 0, 6,16,18, 8, 7,15,18,10,13,17,17,13,11,15,11,19,12,13,10, 4,15,19,21,21,24,14, 9,17,20,24,17, 18,17, 7, 7,16,21,22,15, 5,14,20,14,13,21,13, 8,12,14, 7, 8,11,15,13,11,16,17, 7, 5,12,17,19,14, 25,23,17,16,23,18,15, 7, 0, 6,11, 6,11,15,11, 7,12, 7, 4,10,16,13, 7, 7,15,13, 9,15,21,14, 5, 0, 18,22,21,21,21,22,12, 6,14,20,15, 6,10,19,13, 8, 7, 3, 7,12,14,16, 9,12,22,15,12,18,24,19,17, 9, 0,15,18,21,17,25,14,13,19,21,21,11, 6,13,16,16,12,10,12,11,13,20,14,13,18,13, 9,15,16,25,31,20, 5,20,24,16, 7,14,14,11,18,19,19, 6, 0, 5,11,14,17,16,19,14,15,21,19,15,14,14, 8, 0, 7,24,18,16, 9,17,15, 7, 6,14,12, 7,14,16,11, 4, 7, 6,13,16,15,13,12,20,21,20,21,17,18,26,14, 0,13,23,21,11, 9,12,18,11,15,21,13, 8,13,13,10, 7,13, 8, 8,19,13, 7, 4,15,19,18,14,12,14,15, 8, 6,16,22,22,15, 9,17,14,19,15,14,15, 9,11, 9, 6, 8,14,13,13,12, 5, 0, 0, 6,12,13, 7, 7, 9, 7, 0,12,21,16,15,18, 15,16,18,11, 6, 8,15, 9, 2, 0, 5,10,10,16, 9, 0, 4,12,15, 9,12, 9, 7, 7,12, 7, 0, 6,12, 6, 9,13, 12,19,15,14,11, 7, 8, 9,12,10, 5, 5, 7,12,12,10,14,16,16,11, 8,12,10,12,10, 8,10,10,14,12,16,16, 16,17,20,22,12,15,12,14,19,11, 6, 5,10,13,17,17,21,19,15, 9, 6, 9,15,18,10,10,18,14,20,15,16,17, 11,19,19,18,19,14,17,13,12,12, 7,11,18,17,16,15,19,19,10, 2, 0, 8,15,12, 8,11,12,10,19,20,19,19, 6,14,18,13,13,16,16,12, 5, 8,10,12,10,13,18,12, 9,10, 7, 6, 5,11, 8, 6, 7,13,16,13,10,15,20,14, 0, 5,12,12, 4, 0, 9,16, 9,10,12, 8, 0, 9,13, 9, 0, 2, 4, 7,10, 6, 7, 3, 4,11,16,18,10,11,21,21, 16,13,11,15, 8, 0, 5, 9, 8, 7, 6, 3, 0, 9,17, 9, 0, 0, 0, 3, 5, 4, 3, 5, 7,15,16,16,17,14,22,22, 24,14,15,12, 9, 0, 5,10, 8, 4, 7,12,10,11,12, 7, 6, 8, 6, 5, 7, 8, 8,11,13,10,15,14,12,18,20,16, 16,17,17,18,12, 9,12,16,10, 5, 6,20,13,15, 8, 4, 8, 9, 8, 7, 9,11,12,17,16,16,11,10, 9,10, 5, 0, 0,14,18,18,15,16,14, 9,10, 9, 9,15,14,10, 4, 6,10, 8, 8, 7,10, 9,10,16,18,10, 0, 0, 7,12,10, 8, 0,14,19,14, 9,11,11, 8, 8,10,15, 9,10, 7, 4,10,13, 9, 7, 5, 5, 7, 7, 7,13,13, 5, 5,14,22,18,16, 0,10,14,10, 3, 6, 5, 6, 8, 9, 8, 9, 5, 9, 8, 9, 6, 8, 8, 8, 1, 0, 0, 0, 9,17,12,12,17,19,20,13, 6,11,17,11, 5, 5, 8,10, 6, 5, 6, 6, 3, 7, 9, 7, 6, 8,12,10, 4, 8, 6, 6,11,16,16,15,16,17,17,16, 11, 9,10,10, 5, 6,12,10, 5, 1, 6,10, 5, 3, 3, 5, 4, 7,15,10, 7,13, 7, 8,15,11,15,15,15, 8,11,15, }; // Just a 32x32 cloud rendered with the standard Photoshop filter static BYTE pattern2[1024] = { 9, 9, 8, 8, 8, 8, 6, 6,13,13,11,21,19,21,23,18,23,24,19,19,24,17,18,12, 9,14, 8,12,12, 5, 8, 6, 11,10, 6, 7, 8, 8, 9,13,10,11,17,15,23,22,23,22,20,26,27,26,17,21,20,14,12, 8,11, 8,11, 7, 8, 7, 6, 9,13,13,10, 9,13, 7,12,13,16,19,16,20,22,25,22,25,27,22,21,23,15,10,14,14,15,13,12, 8,12, 6, 6, 7,12,12,12,16, 9,12,12,15,16,11,21,24,19,24,23,26,28,27,26,21,14,15, 7, 7,10,15,12,11,10, 9, 7,14,11,16,12,18,16,14,16,14,11,14,15,21,23,17,20,18,26,24,27,18,20,11,11,14,10,17,17,10, 6,10, 13, 9,14,10,13,11,14,15,18,15,15,12,19,19,20,18,22,20,19,22,19,19,19,20,17,15,15,11,16,14,10, 8, 13,16,12,16,17,19,17,18,15,19,14,18,15,14,15,17,21,19,23,18,23,22,18,18,17,15,15,16,12,12,15,10, 10,12,14,10,16,11,18,15,21,20,20,17,18,19,16,19,14,20,19,14,19,25,22,21,22,24,18,12, 9, 9, 8, 6, 10,10,13, 9,15,13,20,19,22,18,18,17,17,21,21,13,13,12,19,18,16,17,27,26,22,23,20,17,12,11, 8, 9, 7,13,14,15,11,13,18,22,19,23,23,20,22,24,21,14,12,16,17,19,18,18,22,18,24,23,19,17,16,14, 8, 7, 12,12, 8, 8,16,20,26,25,28,28,22,29,23,22,21,18,13,16,15,15,20,17,25,24,19,17,17,17,15,10, 8, 9, 7,12,15,11,17,20,25,25,25,29,30,31,28,26,18,16,17,18,20,21,22,20,23,19,18,19,10,16,16,11,11, 8, 5, 6, 8,14,14,17,17,21,27,23,27,31,27,22,23,21,19,19,21,19,20,19,17,22,13,17,12,15,10,10,12, 6, 8, 9, 8,14,15,16,15,18,27,26,23,25,23,22,18,21,20,17,19,20,20,16,20,14,15,13,12, 8, 8, 7,11,13, 7, 6,11,11,11,13,15,22,25,24,26,22,24,26,23,18,24,24,20,18,20,16,17,12,12,12,10, 8,11, 9, 6, 8, 9,10, 9, 6, 5,14,16,19,17,21,26,20,23,19,19,17,20,21,26,25,23,21,17,13,12, 5,13,11, 7,12,10,12, 6, 5, 4,10,11, 9,10,13,17,20,20,18,23,26,27,20,21,24,20,19,24,20,18,10,11, 3, 6,13, 9, 6, 8, 8, 1, 2, 2,11,13,13,11,16,16,16,19,21,20,23,22,28,21,20,19,18,23,16,18, 7, 5, 9, 7, 6, 5,10, 8, 8, 0, 0, 6, 9,11,15,12,12,19,18,19,26,22,24,26,30,23,22,22,16,20,19,12,12, 3, 4, 6, 5, 4, 7, 2, 4, 2, 0, 0, 7,11, 8,14,13,15,21,26,28,25,24,27,26,23,24,22,22,15,17,12, 8,10, 7, 7, 4, 0, 5, 0, 1, 1, 2, 0, 1, 9,14,13,10,19,24,22,29,30,28,30,30,31,23,24,19,17,14,13, 8, 8, 8, 1, 4, 0, 0, 0, 3, 5, 2, 4, 2, 9, 8, 8, 8,18,23,20,27,30,27,31,25,28,30,28,24,24,15,11,14,10, 3, 4, 3, 0, 0, 1, 3, 9, 3, 4, 3, 5, 6, 8,13,14,23,21,27,28,27,28,27,27,29,30,24,22,23,13,15, 8, 6, 2, 0, 4, 3, 4, 1, 6, 5, 5, 3, 9, 3, 6,14,13,16,23,26,28,23,30,31,28,29,26,27,21,20,15,15,13, 9, 1, 0, 2, 0, 5, 8, 8, 4, 3, 7, 2, 0,10, 7,10,14,21,21,29,28,25,27,30,28,25,24,27,22,19,13,10, 5, 0, 0, 0, 0, 0, 7, 7, 6, 7, 0, 2, 2, 5, 6,15,11,19,24,22,29,27,31,30,30,31,28,23,18,14,14, 7, 5, 0, 0, 1, 0, 1, 0, 5, 5, 5, 0, 0, 4, 5,11, 7,10,13,20,21,21,28,31,28,30,26,28,25,21, 9,12, 3, 3, 0, 2, 2, 2, 0, 1, 3, 3, 0, 2, 0, 3, 5, 3,11,11,16,19,19,27,26,26,30,27,28,26,23,22,16, 6, 2, 2, 3, 2, 0, 2, 4, 0, 0, 0, 0, 3, 3, 1, 0, 4, 5, 9,11,16,24,20,28,26,28,24,28,25,22,21,16, 5, 7, 5, 7, 3, 2, 3, 3, 6, 0, 0, 2, 0, 2, 0, 4, 3, 8,12, 9,17,16,23,23,27,27,22,26,22,21,21,13,14, 5, 3, 7, 3, 2, 4, 6, 1, 2, 5, 6, 4, 0, 1, 5, 8, 7, 6,15,17,22,20,24,28,23,25,20,21,18,16,13,15,13,10, 8, 5, 5, 9, 3, 7, 7, 7, 0, 5, 1, 6, 7, 9,12, 9,12,21,22,25,24,22,23,25,24,18,24,22,17,13,10, 9,10, 9, 6,11, 6, 5, }; const FTexture::Span FBackdropTexture::DummySpan[2] = { { 0, 160 }, { 0, 0 } }; FBackdropTexture::FBackdropTexture() { Width = 144; Height = 160; WidthBits = 8; HeightBits = 8; WidthMask = 255; LastRenderTic = 0; time1 = ANGLE_1*180; time2 = ANGLE_1*56; time3 = ANGLE_1*99; time4 = ANGLE_1*1; t1ang = ANGLE_90; t2ang = 0; z1ang = 0; z2ang = ANGLE_90/2; } bool FBackdropTexture::CheckModified() { return LastRenderTic != gametic; } void FBackdropTexture::Unload() { } const BYTE *FBackdropTexture::GetColumn(unsigned int column, const Span **spans_out) { if (LastRenderTic != gametic) { Render(); } column = clamp(column, 0u, 143u); if (spans_out != NULL) { *spans_out = DummySpan; } return Pixels + column*160; } const BYTE *FBackdropTexture::GetPixels() { if (LastRenderTic != gametic) { Render(); } return Pixels; } // This is one plasma and two rotozoomers. I think it turned out quite awesome. void FBackdropTexture::Render() { BYTE *from; int width, height, pitch; width = 160; height = 144; pitch = width; int x, y; const angle_t a1add = ANGLE_1/2; const angle_t a2add = ANGLE_MAX-ANGLE_1; const angle_t a3add = ANGLE_1*5/7; const angle_t a4add = ANGLE_MAX-ANGLE_1*4/3; const angle_t t1add = ANGLE_MAX-ANGLE_1*2; const angle_t t2add = ANGLE_MAX-ANGLE_1*3+ANGLE_1/6; const angle_t t3add = ANGLE_1*16/7; const angle_t t4add = ANGLE_MAX-ANGLE_1*2/3; const angle_t x1add = 5<>ANGLETOFINESHIFT]>>2)+FRACUNIT/2; fixed_t z2 = (finecosine[z1ang>>ANGLETOFINESHIFT]>>2)+FRACUNIT*3/4; tc = MulScale5 (finecosine[t1ang>>ANGLETOFINESHIFT], z1); ts = MulScale5 (finesine[t1ang>>ANGLETOFINESHIFT], z1); uc = MulScale5 (finecosine[t2ang>>ANGLETOFINESHIFT], z2); us = MulScale5 (finesine[t2ang>>ANGLETOFINESHIFT], z2); ltx = -width/2*tc; lty = -width/2*ts; lux = -width/2*uc; luy = -width/2*us; for (y = 0; y < height; ++y) { a1 = time1; a2 = time2; c3 = finecosine[a3>>ANGLETOFINESHIFT]; c4 = finecosine[a4>>ANGLETOFINESHIFT]; tx = ltx - (y-height/2)*ts; ty = lty + (y-height/2)*tc; ux = lux - (y-height/2)*us; uy = luy + (y-height/2)*uc; for (x = 0; x < width; ++x) { c1 = finecosine[a1>>ANGLETOFINESHIFT]; c2 = finecosine[a2>>ANGLETOFINESHIFT]; from[x] = ((c1 + c2 + c3 + c4) >> (FRACBITS+3-7)) + 128 // plasma + pattern1[(tx>>27)+((ty>>22)&992)] // rotozoomer 1 + pattern2[(ux>>27)+((uy>>22)&992)]; // rotozoomer 2 tx += tc; ty += ts; ux += uc; uy += us; a1 += a1add; a2 += a2add; } a3 += a3add; a4 += a4add; from += pitch; } time1 += t1add; time2 += t2add; time3 += t3add; time4 += t4add; t1ang += x1add; t2ang += x2add; z1ang += z1add; z2ang += z2add; LastRenderTic = gametic; } static void M_ChangeClass (int choice) { if (PlayerClasses.Size () == 1) { return; } int type = players[consoleplayer].userinfo.PlayerClass; if (!choice) type = (type < 0) ? (int)PlayerClasses.Size () - 1 : type - 1; else type = (type < (int)PlayerClasses.Size () - 1) ? type + 1 : -1; cvar_set ("playerclass", type < 0 ? "Random" : PlayerClasses[type].Type->Meta.GetMetaString (APMETA_DisplayName)); } static void M_ChangeSkin (int choice) { if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || players[consoleplayer].userinfo.PlayerClass == -1) { return; } do { if (!choice) PlayerSkin = (PlayerSkin == 0) ? (int)numskins - 1 : PlayerSkin - 1; else PlayerSkin = (PlayerSkin < (int)numskins - 1) ? PlayerSkin + 1 : 0; } while (!PlayerClass->CheckSkin (PlayerSkin)); R_GetPlayerTranslation (players[consoleplayer].userinfo.color, P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset), &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); cvar_set ("skin", skins[PlayerSkin].name); } static void M_ChangeGender (int choice) { int gender = players[consoleplayer].userinfo.gender; if (!choice) gender = (gender == 0) ? 2 : gender - 1; else gender = (gender == 2) ? 0 : gender + 1; cvar_set ("gender", genders[gender]); } static void M_ChangeAutoAim (int choice) { static const float ranges[] = { 0, 0.25, 0.5, 1, 2, 3, 5000 }; float aim = autoaim; int i; if (!choice) { // Select a lower autoaim for (i = 6; i >= 1; i--) { if (aim >= ranges[i]) { aim = ranges[i - 1]; break; } } } else { // Select a higher autoaim for (i = 5; i >= 0; i--) { if (aim >= ranges[i]) { aim = ranges[i + 1]; break; } } } autoaim = aim; } static void M_EditPlayerName (int choice) { // we are going to be intercepting all chars genStringEnter = 2; genStringEnd = M_PlayerNameChanged; genStringCancel = M_PlayerNameNotChanged; genStringLen = MAXPLAYERNAME; saveSlot = 0; saveCharIndex = strlen (savegamestring); } static void M_PlayerNameNotChanged () { strcpy (savegamestring, name); } static void M_PlayerNameChanged (FSaveGameNode *dummy) { const char *p; FString command("name \""); // Escape any backslashes or quotation marks before sending the name to the console. for (p = savegamestring; *p != '\0'; ++p) { if (*p == '"' || *p == '\\') { command << '\\'; } command << *p; } command << '"'; C_DoCommand (command); } static void M_ChangePlayerTeam (int choice) { if (!choice) { if (team == 0) { team = TEAM_NONE; } else if (team == TEAM_NONE) { team = Teams.Size () - 1; } else { team = team - 1; } } else { if (team == int(Teams.Size () - 1)) { team = TEAM_NONE; } else if (team == TEAM_NONE) { team = 0; } else { team = team + 1; } } } static void M_ChangeColorSet (int choice) { int curpos = (int)PlayerColorSets.Size(); int mycolorset = players[consoleplayer].userinfo.colorset; while (--curpos >= 0) { if (PlayerColorSets[curpos] == mycolorset) break; } if (choice == 0) { curpos--; } else { curpos++; } if (curpos < -1) { curpos = (int)PlayerColorSets.Size() - 1; } else if (curpos >= (int)PlayerColorSets.Size()) { curpos = -1; } mycolorset = (curpos >= 0) ? PlayerColorSets[curpos] : -1; // disable the sliders if a valid colorset is selected PlayerSetupMenu[PSM_RED].status = PlayerSetupMenu[PSM_GREEN].status = PlayerSetupMenu[PSM_BLUE].status = (mycolorset == -1? 2:-1); char command[24]; mysnprintf(command, countof(command), "colorset %d", mycolorset); C_DoCommand(command); R_GetPlayerTranslation(players[consoleplayer].userinfo.color, P_GetPlayerColorSet(PlayerClass->Type->TypeName, mycolorset), &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); } static void SendNewColor (int red, int green, int blue) { char command[24]; mysnprintf (command, countof(command), "color \"%02x %02x %02x\"", red, green, blue); C_DoCommand (command); R_GetPlayerTranslation(MAKERGB (red, green, blue), P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset), &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); } static void M_SlidePlayerRed (int choice) { int color = players[consoleplayer].userinfo.color; int red = RPART(color); if (choice == 0) { red -= 16; if (red < 0) red = 0; } else { red += 16; if (red > 255) red = 255; } SendNewColor (red, GPART(color), BPART(color)); } static void M_SlidePlayerGreen (int choice) { int color = players[consoleplayer].userinfo.color; int green = GPART(color); if (choice == 0) { green -= 16; if (green < 0) green = 0; } else { green += 16; if (green > 255) green = 255; } SendNewColor (RPART(color), green, BPART(color)); } static void M_SlidePlayerBlue (int choice) { int color = players[consoleplayer].userinfo.color; int blue = BPART(color); if (choice == 0) { blue -= 16; if (blue < 0) blue = 0; } else { blue += 16; if (blue > 255) blue = 255; } SendNewColor (RPART(color), GPART(color), blue); } // // Menu Functions // void M_StartMessage (const char *string, void (*routine)(int)) { C_HideConsole (); messageLastMenuActive = menuactive; messageToPrint = 1; messageString = string; messageRoutine = routine; messageSelection = 0; if (menuactive == MENU_Off) { M_ActivateMenuInput (); } if (messageRoutine != NULL) { S_StopSound (CHAN_VOICE); S_Sound (CHAN_VOICE | CHAN_UI, "menu/prompt", 1, ATTN_NONE); } return; } void M_EndMessage(int key) { menuactive = messageLastMenuActive; messageToPrint = 0; if (messageRoutine != NULL) { messageRoutine(key); } if (menuactive != MENU_Off) { M_DeactivateMenuInput(); } SB_state = screen->GetPageCount(); // refresh the status bar BorderNeedRefresh = screen->GetPageCount(); S_Sound(CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); } // // Find string height from hu_font chars // int M_StringHeight (FFont *font, const char *string) { int h; int height = font->GetHeight (); h = height; while (*string) { if ((*string++) == '\n') h += height; } return h; } // // CONTROL PANEL // // // M_Responder // bool M_Responder (event_t *ev) { int ch; int i; EMenuKey mkey = NUM_MKEYS; bool keyup = true; ch = -1; if (chatmodeon) { return false; } if (menuactive == MENU_Off && ev->type == EV_KeyDown) { // Pop-up menu? if (ev->data1 == KEY_ESCAPE) { M_StartControlPanel(true, true); return true; } // If devparm is set, pressing F1 always takes a screenshot no matter // what it's bound to. (for those who don't bother to read the docs) if (devparm && ev->data1 == KEY_F1) { G_ScreenShot(NULL); return true; } return false; } if (menuactive == MENU_WaitKey && OptionsActive) { M_OptResponder(ev); return true; } if (menuactive != MENU_On && menuactive != MENU_OnNoPause && !genStringEnter && !messageToPrint) { return false; } // There are a few input sources we are interested in: // // EV_KeyDown / EV_KeyUp : joysticks/gamepads/controllers // EV_GUI_KeyDown / EV_GUI_KeyUp : the keyboard // EV_GUI_Char : printable characters, which we want in string input mode // // This code previously listened for EV_GUI_KeyRepeat to handle repeating // in the menus, but that doesn't work with gamepads, so now we combine // the multiple inputs into buttons and handle the repetition manually. if (ev->type == EV_GUI_Event) { // Save game and player name string input if (genStringEnter) { if (ev->subtype == EV_GUI_Char) { InputGridOkay = false; if (saveCharIndex < genStringLen && (genStringEnter == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(savegamestring) < (genStringLen-1)*8)) { savegamestring[saveCharIndex] = (char)ev->data1; savegamestring[++saveCharIndex] = 0; } return true; } ch = ev->data1; if ((ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) && ch == '\b') { if (saveCharIndex > 0) { saveCharIndex--; savegamestring[saveCharIndex] = 0; } } else if (ev->subtype == EV_GUI_KeyDown) { if (ch == GK_ESCAPE) { genStringEnter = 0; genStringCancel(); // [RH] Function to call when escape is pressed } else if (ch == '\r') { if (savegamestring[0]) { genStringEnter = 0; if (messageToPrint) M_ClearMenus (); genStringEnd (SelSaveGame); // [RH] Function to call when enter is pressed } } } if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) { return true; } } if (ev->subtype != EV_GUI_KeyDown && ev->subtype != EV_GUI_KeyUp) { return false; } if (ev->subtype == EV_GUI_KeyRepeat) { // We do our own key repeat handling but still want to eat the // OS's repeated keys. return true; } ch = ev->data1; keyup = ev->subtype == EV_GUI_KeyUp; if (messageToPrint && messageRoutine == NULL) { if (!keyup && !OptionsActive) { D_RemoveNextCharEvent(); M_EndMessage(ch); return true; } } switch (ch) { case GK_ESCAPE: mkey = MKEY_Back; break; case GK_RETURN: mkey = MKEY_Enter; break; case GK_UP: mkey = MKEY_Up; break; case GK_DOWN: mkey = MKEY_Down; break; case GK_LEFT: mkey = MKEY_Left; break; case GK_RIGHT: mkey = MKEY_Right; break; case GK_BACKSPACE: mkey = MKEY_Clear; break; case GK_PGUP: mkey = MKEY_PageUp; break; case GK_PGDN: mkey = MKEY_PageDown; break; default: if (ch == ' ' && currentMenu == &PSetupDef) { mkey = MKEY_Clear; } else if (!keyup) { if (OptionsActive) { M_OptResponder(ev); } else { ch = tolower (ch); if (messageToPrint) { // Take care of any messages that need input ch = tolower (ch); assert(messageRoutine != NULL); if (ch != ' ' && ch != 'n' && ch != 'y') { return false; } D_RemoveNextCharEvent(); M_EndMessage(ch); return true; } else { // Search for a menu item associated with the pressed key. for (i = (itemOn + 1) % currentMenu->numitems; i != itemOn; i = (i + 1) % currentMenu->numitems) { if (currentMenu->menuitems[i].alphaKey == ch) { break; } } if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); return true; } } } } break; } if (!keyup) { InputGridOkay = false; } } else if (ev->type == EV_KeyDown || ev->type == EV_KeyUp) { keyup = ev->type == EV_KeyUp; // If this is a button down, it's okay to show the input grid if the // next action causes us to enter genStringEnter mode. If we are // already in that mode, then we let M_ButtonHandler() turn it on so // that it will know if a button press happened while the input grid // was turned off. if (!keyup && !genStringEnter) { InputGridOkay = true; } ch = ev->data1; switch (ch) { case KEY_JOY1: case KEY_PAD_A: mkey = MKEY_Enter; break; case KEY_JOY2: case KEY_PAD_B: mkey = MKEY_Back; break; case KEY_JOY3: case KEY_PAD_X: mkey = MKEY_Clear; break; case KEY_JOY5: case KEY_PAD_LSHOULDER: mkey = MKEY_PageUp; break; case KEY_JOY6: case KEY_PAD_RSHOULDER: mkey = MKEY_PageDown; break; case KEY_PAD_DPAD_UP: case KEY_PAD_LTHUMB_UP: case KEY_JOYAXIS1MINUS: case KEY_JOYPOV1_UP: mkey = MKEY_Up; break; case KEY_PAD_DPAD_DOWN: case KEY_PAD_LTHUMB_DOWN: case KEY_JOYAXIS1PLUS: case KEY_JOYPOV1_DOWN: mkey = MKEY_Down; break; case KEY_PAD_DPAD_LEFT: case KEY_PAD_LTHUMB_LEFT: case KEY_JOYAXIS2MINUS: case KEY_JOYPOV1_LEFT: mkey = MKEY_Left; break; case KEY_PAD_DPAD_RIGHT: case KEY_PAD_LTHUMB_RIGHT: case KEY_JOYAXIS2PLUS: case KEY_JOYPOV1_RIGHT: mkey = MKEY_Right; break; } // Any button press will work for messages without callbacks if (!keyup && messageToPrint && messageRoutine == NULL) { M_EndMessage(ch); return true; } } if (mkey != NUM_MKEYS) { if (keyup) { MenuButtons[mkey].ReleaseKey(ch); } else { MenuButtons[mkey].PressKey(ch); if (mkey <= MKEY_PageDown) { MenuButtonTickers[mkey] = KEY_REPEAT_DELAY; } M_ButtonHandler(mkey, false); } } if (ev->type == EV_GUI_Event && (currentMenu == &SaveDef || currentMenu == &LoadDef)) { return M_SaveLoadResponder (ev); } // Eat key downs, but let the rest through. return !keyup; } void M_ButtonHandler(EMenuKey key, bool repeat) { if (OptionsActive) { M_OptButtonHandler(key, repeat); return; } if (key == MKEY_Back) { if (genStringEnter) { // Cancel string entry. genStringEnter = 0; genStringCancel(); } else if (messageToPrint) { M_EndMessage(GK_ESCAPE); } else { // Save the cursor position on the current menu, and pop it off the stack // to go back to the previous menu. currentMenu->lastOn = itemOn; M_PopMenuStack(); } return; } if (messageToPrint) { if (key == MKEY_Down || key == MKEY_Up) { messageSelection ^= 1; } else if (key == MKEY_Enter) { M_EndMessage(messageSelection == 0 ? 'y' : 'n'); } return; } if (genStringEnter) { int ch; switch (key) { case MKEY_Down: InputGridY = (InputGridY + 1) % INPUTGRID_HEIGHT; break; case MKEY_Up: InputGridY = (InputGridY + INPUTGRID_HEIGHT - 1) % INPUTGRID_HEIGHT; break; case MKEY_Right: InputGridX = (InputGridX + 1) % INPUTGRID_WIDTH; break; case MKEY_Left: InputGridX = (InputGridX + INPUTGRID_WIDTH - 1) % INPUTGRID_WIDTH; break; case MKEY_Clear: if (saveCharIndex > 0) { savegamestring[--saveCharIndex] = 0; } break; case MKEY_Enter: assert(unsigned(InputGridX) < INPUTGRID_WIDTH && unsigned(InputGridY) < INPUTGRID_HEIGHT); if (InputGridOkay) { ch = InputGridChars[InputGridX + InputGridY * INPUTGRID_WIDTH]; if (ch == 0) // end { if (savegamestring[0] != '\0') { genStringEnter = 0; if (messageToPrint) { M_ClearMenus(); } genStringEnd(SelSaveGame); } } else if (ch == '\b') // bs { if (saveCharIndex > 0) { savegamestring[--saveCharIndex] = 0; } } else if (saveCharIndex < genStringLen && (genStringEnter == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(savegamestring) < (genStringLen-1)*8)) { savegamestring[saveCharIndex] = ch; savegamestring[++saveCharIndex] = 0; } } break; default: break; // Keep GCC quiet } InputGridOkay = true; return; } if (currentMenu == &SaveDef || currentMenu == &LoadDef) { M_SaveLoadButtonHandler(key); return; } switch (key) { case MKEY_Down: do { if (itemOn + 1 >= currentMenu->numitems) itemOn = 0; else itemOn++; S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); } while (currentMenu->menuitems[itemOn].status == -1); break; case MKEY_Up: do { if (itemOn == 0) itemOn = currentMenu->numitems - 1; else itemOn--; S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); } while (currentMenu->menuitems[itemOn].status == -1); break; case MKEY_Left: if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); currentMenu->menuitems[itemOn].routine(0); } break; case MKEY_Right: if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); currentMenu->menuitems[itemOn].routine(1); } break; case MKEY_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_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); } else { currentMenu->menuitems[itemOn].routine(itemOn); S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); } } break; case MKEY_Clear: if (currentMenu == &PSetupDef) { PlayerRotation ^= 8; } break; default: break; // Keep GCC quiet } } static void M_SaveLoadButtonHandler(EMenuKey key) { if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) { return; } switch (key) { case MKEY_Up: if (SelSaveGame != SaveGames.Head) { if (SelSaveGame == TopSaveGame) { TopSaveGame = static_cast(TopSaveGame->Pred); } SelSaveGame = static_cast(SelSaveGame->Pred); } else { SelSaveGame = static_cast(SaveGames.TailPred); } M_UnloadSaveData (); M_ExtractSaveData (SelSaveGame); break; case MKEY_Down: if (SelSaveGame != SaveGames.TailPred) { SelSaveGame = static_cast(SelSaveGame->Succ); } else { SelSaveGame = TopSaveGame = static_cast(SaveGames.Head); } M_UnloadSaveData (); M_ExtractSaveData (SelSaveGame); break; case MKEY_Enter: if (currentMenu == &LoadDef) { M_LoadSelect (SelSaveGame); } else { M_SaveSelect (SelSaveGame); } break; default: break; // Keep GCC quiet } } static bool M_SaveLoadResponder (event_t *ev) { if (ev->subtype != EV_GUI_KeyDown) { return false; } if (SelSaveGame != NULL && SelSaveGame->Succ != NULL) { switch (ev->data1) { case GK_F1: if (!SelSaveGame->Filename.IsEmpty()) { char workbuf[512]; mysnprintf (workbuf, countof(workbuf), "File on disk:\n%s", SelSaveGame->Filename.GetChars()); if (SaveComment != NULL) { V_FreeBrokenLines (SaveComment); } SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, workbuf); } break; case GK_DEL: case '\b': if (SelSaveGame != &NewSaveNode) { EndString.Format("%s" TEXTCOLOR_WHITE "%s" TEXTCOLOR_NORMAL "?\n\n%s", GStrings("MNU_DELETESG"), SelSaveGame->Title, GStrings("PRESSYN")); M_StartMessage (EndString, M_DeleteSaveResponse); } break; case 'N': if (currentMenu == &SaveDef) { SelSaveGame = TopSaveGame = &NewSaveNode; M_UnloadSaveData (); } break; } } return true; } static void M_LoadSelect (const FSaveGameNode *file) { G_LoadGame (file->Filename.GetChars()); if (gamestate == GS_FULLCONSOLE) { gamestate = GS_HIDECONSOLE; } if (quickSaveSlot == (FSaveGameNode *)1) { quickSaveSlot = SelSaveGame; } M_ClearMenus (); BorderNeedRefresh = screen->GetPageCount (); } // // User wants to save. Start string input for M_Responder // static void M_CancelSaveName () { } static void M_SaveSelect (const FSaveGameNode *file) { // we are going to be intercepting all chars genStringEnter = 1; genStringEnd = M_DoSave; genStringCancel = M_CancelSaveName; genStringLen = SAVESTRINGSIZE-1; if (file != &NewSaveNode) { strcpy (savegamestring, file->Title); } else { // If we are naming a new save, don't start the cursor on "end". if (InputGridX == INPUTGRID_WIDTH - 1 && InputGridY == INPUTGRID_HEIGHT - 1) { InputGridX = 0; InputGridY = 0; } savegamestring[0] = 0; } saveCharIndex = strlen (savegamestring); } static void M_DeleteSaveResponse (int choice) { M_ClearSaveStuff (); if (choice == 'y') { FSaveGameNode *next = static_cast(SelSaveGame->Succ); if (next->Succ == NULL) { next = static_cast(SelSaveGame->Pred); if (next->Pred == NULL) { next = NULL; } } remove (SelSaveGame->Filename.GetChars()); M_UnloadSaveData (); SelSaveGame = M_RemoveSaveSlot (SelSaveGame); M_ExtractSaveData (SelSaveGame); } } // // M_StartControlPanel // void M_StartControlPanel (bool makeSound, bool wantTop) { // intro might call this repeatedly if (menuactive == MENU_On) return; for (int i = 0; i < NUM_MKEYS; ++i) { MenuButtons[i].ReleaseKey(0); } drawSkull = true; MenuStackDepth = 0; if (wantTop) { M_SetupNextMenu(TopLevelMenu); } else { // Just a default. The caller ought to call M_SetupNextMenu() next. currentMenu = TopLevelMenu; itemOn = currentMenu->lastOn; } C_HideConsole (); // [RH] Make sure console goes bye bye. OptionsActive = false; // [RH] Make sure none of the options menus appear. M_ActivateMenuInput (); if (makeSound) { S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); } } // // M_Drawer // Called after the view has been rendered, // but before it has been blitted. // void M_Drawer () { int i, x, y, max; PalEntry fade = 0; player_t *player = &players[consoleplayer]; AActor *camera = player->camera; if (!screen->Accel2D && camera != NULL && (gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL)) { if (camera->player != NULL) { player = camera->player; } fade = PalEntry (BYTE(player->BlendA*255), BYTE(player->BlendR*255), BYTE(player->BlendG*255), BYTE(player->BlendB*255)); } // Horiz. & Vertically center string and print it. if (messageToPrint) { int fontheight = SmallFont->GetHeight(); screen->Dim (fade); BorderNeedRefresh = screen->GetPageCount (); SB_state = screen->GetPageCount (); FBrokenLines *lines = V_BreakLines (SmallFont, 320, messageString); y = 100; for (i = 0; lines[i].Width >= 0; i++) y -= SmallFont->GetHeight () / 2; for (i = 0; lines[i].Width >= 0; i++) { screen->DrawText (SmallFont, CR_UNTRANSLATED, 160 - lines[i].Width/2, y, lines[i].Text, DTA_Clean, true, TAG_DONE); y += fontheight; } V_FreeBrokenLines (lines); if (messageRoutine != NULL) { y += fontheight; screen->DrawText(SmallFont, CR_UNTRANSLATED, 160, y, GStrings["TXT_YES"], DTA_Clean, true, TAG_DONE); screen->DrawText(SmallFont, CR_UNTRANSLATED, 160, y + fontheight + 1, GStrings["TXT_NO"], DTA_Clean, true, TAG_DONE); if (skullAnimCounter < 6) { screen->DrawText(ConFont, CR_RED, (150 - 160) * CleanXfac + screen->GetWidth() / 2, (y + (fontheight + 1) * messageSelection - 100) * CleanYfac + screen->GetHeight() / 2, "\xd", DTA_CellX, 8 * CleanXfac, DTA_CellY, 8 * CleanYfac, TAG_DONE); } } } else if (menuactive != MENU_Off) { if (InfoType == 0 && !OptionsActive) { screen->Dim (fade); } // For Heretic shareware message: if (showSharewareMessage) { const char *text = GStrings("MNU_ONLYREGISTERED"); screen->DrawText (SmallFont, CR_WHITE, 160 - SmallFont->StringWidth(text)/2, 8, text, DTA_Clean, true, TAG_DONE); } BorderNeedRefresh = screen->GetPageCount (); SB_state = screen->GetPageCount (); if (OptionsActive) { M_OptDrawer (); } else { 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++) { if (currentMenu->menuitems[i].name) { if (currentMenu->menuitems[i].fulltext) { int color = currentMenu->menuitems[i].textcolor; if (color == CR_UNTRANSLATED) { // The default DBIGFONT is white but Doom's default should be red. if (gameinfo.gametype & GAME_DoomChex) { color = CR_RED; } } const char *text = currentMenu->menuitems[i].name; if (*text == '$') text = GStrings(text+1); screen->DrawText (BigFont, color, x, y, text, DTA_Clean, true, TAG_DONE); } else { screen->DrawTexture (TexMan[currentMenu->menuitems[i].name], x, y, DTA_Clean, true, TAG_DONE); } } y += LINEHEIGHT; } // DRAW CURSOR if (drawSkull) { if (currentMenu == &PSetupDef) { // [RH] Use options menu cursor for the player setup menu. if (skullAnimCounter < 6) { double item; // The green slider is halfway between lines, and the red and // blue ones are offset slightly to make room for it. if (itemOn < 3) { item = itemOn; } else if (itemOn > 5) { item = itemOn - 1; } else if (itemOn == 3) { item = 2.875; } else if (itemOn == 4) { item = 3.5; } else { item = 4.125; } screen->DrawText (ConFont, CR_RED, x - 16, currentMenu->y + int(item*PLAYERSETUP_LINEHEIGHT) + (!(gameinfo.gametype & (GAME_DoomStrifeChex)) ? 6 : -1), "\xd", DTA_Clean, true, TAG_DONE); } } else if (gameinfo.gametype & GAME_DoomChex) { screen->DrawTexture (TexMan("M_SKULL1"), x + SKULLXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT, DTA_Clean, true, TAG_DONE); } else if (gameinfo.gametype == GAME_Strife) { screen->DrawTexture (TexMan("M_CURS1"), x - 28, currentMenu->y - 5 + itemOn*LINEHEIGHT, DTA_Clean, true, TAG_DONE); } else { screen->DrawTexture (TexMan("M_SLCTR1"), x + SELECTOR_XOFFSET, currentMenu->y + itemOn*LINEHEIGHT + SELECTOR_YOFFSET, DTA_Clean, true, TAG_DONE); } } } if (genStringEnter && InputGridOkay) { M_DrawInputGrid(); } } } static void M_ClearSaveStuff () { M_UnloadSaveData (); if (SaveGames.Head == &NewSaveNode) { SaveGames.RemHead (); if (SelSaveGame == &NewSaveNode) { SelSaveGame = static_cast(SaveGames.Head); } if (TopSaveGame == &NewSaveNode) { TopSaveGame = static_cast(SaveGames.Head); } } if (quickSaveSlot == (FSaveGameNode *)1) { quickSaveSlot = NULL; } } static void M_DrawInputGrid() { const int cell_width = 18 * CleanXfac; const int cell_height = 12 * CleanYfac; const int top_padding = cell_height / 2 - SmallFont->GetHeight() * CleanYfac / 2; // Darken the background behind the character grid. // Unless we frame it with a border, I think it looks better to extend the // background across the full width of the screen. screen->Dim(0, 0.8f, 0 /*screen->GetWidth()/2 - 13 * cell_width / 2*/, screen->GetHeight() - 5 * cell_height, screen->GetWidth() /*13 * cell_width*/, 5 * cell_height); // Highlight the background behind the selected character. screen->Dim(MAKERGB(255,248,220), 0.6f, InputGridX * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2, InputGridY * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(), cell_width, cell_height); for (int y = 0; y < INPUTGRID_HEIGHT; ++y) { const int yy = y * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(); for (int x = 0; x < INPUTGRID_WIDTH; ++x) { int width; const int xx = x * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2; const int ch = InputGridChars[y * INPUTGRID_WIDTH + x]; FTexture *pic = SmallFont->GetChar(ch, &width); EColorRange color; FRemapTable *remap; // The highlighted character is yellow; the rest are dark gray. color = (x == InputGridX && y == InputGridY) ? CR_YELLOW : CR_DARKGRAY; remap = SmallFont->GetColorTranslation(color); if (pic != NULL) { // Draw a normal character. screen->DrawTexture(pic, xx + cell_width/2 - width*CleanXfac/2, yy + top_padding, DTA_Translation, remap, DTA_CleanNoMove, true, TAG_DONE); } else if (ch == ' ') { // Draw the space as a box outline. We also draw it 50% wider than it really is. const int x1 = xx + cell_width/2 - width * CleanXfac * 3 / 4; const int x2 = x1 + width * 3 * CleanXfac / 2; const int y1 = yy + top_padding; const int y2 = y1 + SmallFont->GetHeight() * CleanYfac; const int palentry = remap->Remap[remap->NumEntries*2/3]; const uint32 palcolor = remap->Palette[remap->NumEntries*2/3]; screen->Clear(x1, y1, x2, y1+CleanYfac, palentry, palcolor); // top screen->Clear(x1, y2, x2, y2+CleanYfac, palentry, palcolor); // bottom screen->Clear(x1, y1+CleanYfac, x1+CleanXfac, y2, palentry, palcolor); // left screen->Clear(x2-CleanXfac, y1+CleanYfac, x2, y2, palentry, palcolor); // right } else if (ch == '\b' || ch == 0) { // Draw the backspace and end "characters". const char *const str = ch == '\b' ? "BS" : "ED"; screen->DrawText(SmallFont, color, xx + cell_width/2 - SmallFont->StringWidth(str)*CleanXfac/2, yy + top_padding, str, DTA_CleanNoMove, true, TAG_DONE); } } } } // // M_ClearMenus // void M_ClearMenus () { if (FireTexture) { delete FireTexture; FireTexture = NULL; } M_ClearSaveStuff (); M_DeactivateMenuInput (); MenuStackDepth = 0; OptionsActive = false; InfoType = 0; drawSkull = true; M_DemoNoPlay = false; BorderNeedRefresh = screen->GetPageCount (); } // // M_SetupNextMenu // void M_SetupNextMenu (oldmenu_t *menudef) { MenuStack[MenuStackDepth].menu.old = menudef; MenuStack[MenuStackDepth].isNewStyle = false; MenuStack[MenuStackDepth].drawSkull = drawSkull; MenuStackDepth++; currentMenu = menudef; itemOn = currentMenu->lastOn; } void M_PopMenuStack (void) { M_DemoNoPlay = false; InfoType = 0; M_ClearSaveStuff (); flagsvar = 0; if (MenuStackDepth > 1) { MenuStackDepth -= 2; if (MenuStack[MenuStackDepth].isNewStyle) { OptionsActive = true; CurrentMenu = MenuStack[MenuStackDepth].menu.newmenu; CurrentItem = CurrentMenu->lastOn; } else { OptionsActive = false; currentMenu = MenuStack[MenuStackDepth].menu.old; itemOn = currentMenu->lastOn; } drawSkull = MenuStack[MenuStackDepth].drawSkull; ++MenuStackDepth; S_Sound (CHAN_VOICE | CHAN_UI, "menu/backup", 1, ATTN_NONE); } else { M_ClearMenus (); S_Sound (CHAN_VOICE | CHAN_UI, "menu/clear", 1, ATTN_NONE); } } // // M_Ticker // void M_Ticker (void) { if (showSharewareMessage) { --showSharewareMessage; } if (menuactive == MENU_Off) { return; } MenuTime++; if (--skullAnimCounter <= 0) { skullAnimCounter = 8; } if (currentMenu == &PSetupDef || currentMenu == &ClassMenuDef) { M_PlayerSetupTicker(); } for (int i = 0; i < NUM_MKEYS; ++i) { if (MenuButtons[i].bDown) { if (MenuButtonTickers[i] > 0 && --MenuButtonTickers[i] <= 0) { MenuButtonTickers[i] = KEY_REPEAT_RATE; M_ButtonHandler(EMenuKey(i), true); } } } } // // M_Init // EXTERN_CVAR (Int, screenblocks) void M_Init (void) { unsigned int i; atterm (M_Deinit); if (gameinfo.gametype & (GAME_DoomStrifeChex)) { TopLevelMenu = currentMenu = &MainDef; if (gameinfo.gametype == GAME_Strife) { MainDef.y = 45; //NewDef.lastOn = 1; } } else { TopLevelMenu = currentMenu = &HereticMainDef; PSetupDef.y -= 7; LoadDef.y -= 20; SaveDef.y -= 20; } PickPlayerClass (); OptionsActive = false; menuactive = MENU_Off; InfoType = 0; itemOn = currentMenu->lastOn; skullAnimCounter = 10; drawSkull = true; messageToPrint = 0; messageString = NULL; messageLastMenuActive = menuactive; quickSaveSlot = NULL; lastSaveSlot = NULL; strcpy (NewSaveNode.Title, ""); underscore[0] = (gameinfo.gametype & (GAME_DoomStrifeChex)) ? '_' : '['; underscore[1] = '\0'; if (gameinfo.gametype & GAME_DoomChex) { LINEHEIGHT = 16; } else if (gameinfo.gametype == GAME_Strife) { LINEHEIGHT = 19; } else { LINEHEIGHT = 20; } if (!gameinfo.drawreadthis) { MainMenu[MainDef.numitems-2] = MainMenu[MainDef.numitems-1]; MainDef.numitems--; MainDef.y += 8; ReadDef.routine = M_DrawReadThis; ReadDef.x = 330; ReadDef.y = 165; //ReadMenu[0].routine = M_FinishReadThis; } M_OptInit (); // [GRB] Set up player class menu if (!(gameinfo.gametype == GAME_Hexen && PlayerClasses.Size () == 3 && PlayerClasses[0].Type->IsDescendantOf (PClass::FindClass (NAME_FighterPlayer)) && PlayerClasses[1].Type->IsDescendantOf (PClass::FindClass (NAME_ClericPlayer)) && PlayerClasses[2].Type->IsDescendantOf (PClass::FindClass (NAME_MagePlayer)))) { int n = 0; for (i = 0; i < PlayerClasses.Size () && n < 7; i++) { if (!(PlayerClasses[i].Flags & PCF_NOMENU)) { ClassMenuItems[n].name = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); n++; } } if (n > 1) { ClassMenuItems[n].name = "Random"; ClassMenuDef.numitems = n+1; } else { if (n == 0) { ClassMenuItems[0].name = PlayerClasses[0].Type->Meta.GetMetaString (APMETA_DisplayName); } ClassMenuDef.numitems = 1; } if (gameinfo.gametype & (GAME_DoomStrifeChex)) { ClassMenuDef.x = 48; ClassMenuDef.y = 63; } else { ClassMenuDef.x = 80; ClassMenuDef.y = 50; } if (ClassMenuDef.numitems > 4) { ClassMenuDef.y -= LINEHEIGHT; } } // [RH] Build a palette translation table for the player setup effect if (gameinfo.gametype != GAME_Hexen) { for (i = 0; i < 256; i++) { FireRemap.Remap[i] = ColorMatcher.Pick (i/2+32, 0, i/4); FireRemap.Palette[i] = PalEntry(255, i/2+32, 0, i/4); } } else { // The reddish color ramp above doesn't look too good with the // Hexen palette, so Hexen gets a greenish one instead. for (i = 0; i < 256; ++i) { FireRemap.Remap[i] = ColorMatcher.Pick (i/4, i*13/40+7, i/4); FireRemap.Palette[i] = PalEntry(255, i/4, i*13/40+7, i/4); } } } static void PickPlayerClass () { int pclass = 0; // [GRB] Pick a class from player class list if (PlayerClasses.Size () > 1) { pclass = players[consoleplayer].userinfo.PlayerClass; if (pclass < 0) { pclass = (MenuTime>>7) % PlayerClasses.Size (); } } PlayerClass = &PlayerClasses[pclass]; P_EnumPlayerColorSets(PlayerClass->Type->TypeName, &PlayerColorSets); }