diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 52af19b7b..7111e0fdc 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -980,17 +980,19 @@ set (PCH_SOURCES core/music/s_advsound.cpp - core/menu/imagescroller.cpp + #core/menu/imagescroller.cpp core/menu/joystickmenu.cpp - core/menu/listmenu.cpp - core/menu/savegamemanager.cpp + #core/menu/listmenu.cpp + #core/menu/savegamemanager.cpp core/menu/loadsavemenu.cpp core/menu/menu.cpp core/menu/menudef.cpp - core/menu/menuinput.cpp + #core/menu/menuinput.cpp core/menu/messagebox.cpp core/menu/optionmenu.cpp + core/menu/playermenu.cpp core/menu/resolutionmenu.cpp + core/menu/razemenu.cpp ) if( ${HAVE_VM_JIT} ) diff --git a/source/blood/src/animatesprite.cpp b/source/blood/src/animatesprite.cpp index 2a34619b6..cf5b65665 100644 --- a/source/blood/src/animatesprite.cpp +++ b/source/blood/src/animatesprite.cpp @@ -39,7 +39,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "view.h" #include "nnexts.h" #include "zstring.h" -#include "menu.h" +#include "razemenu.h" #include "gstrings.h" #include "v_2ddrawer.h" #include "v_video.h" diff --git a/source/blood/src/blood.cpp b/source/blood/src/blood.cpp index 25d96ff29..9afbbcb3b 100644 --- a/source/blood/src/blood.cpp +++ b/source/blood/src/blood.cpp @@ -51,7 +51,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "gamecontrol.h" #include "m_argv.h" #include "statistics.h" -#include "menu.h" +#include "razemenu.h" #include "raze_sound.h" #include "nnexts.h" #include "secrets.h" @@ -61,6 +61,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "choke.h" #include "d_net.h" #include "v_video.h" +#include "v_draw.h" #include "statusbar.h" BEGIN_BLD_NS diff --git a/source/blood/src/controls.cpp b/source/blood/src/controls.cpp index a00e79f7e..7045e49c2 100644 --- a/source/blood/src/controls.cpp +++ b/source/blood/src/controls.cpp @@ -26,7 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "mmulti.h" #include "view.h" #include "gamestate.h" -#include "menu.h" +#include "razemenu.h" BEGIN_BLD_NS diff --git a/source/blood/src/credits.cpp b/source/blood/src/credits.cpp index 7e963f4d2..40d53543b 100644 --- a/source/blood/src/credits.cpp +++ b/source/blood/src/credits.cpp @@ -38,7 +38,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "screenjob.h" #include "gamestate.h" #include "seq.h" -#include "menu.h" +#include "razemenu.h" BEGIN_BLD_NS diff --git a/source/blood/src/d_menu.cpp b/source/blood/src/d_menu.cpp index d03e98d81..0b2eb64f2 100644 --- a/source/blood/src/d_menu.cpp +++ b/source/blood/src/d_menu.cpp @@ -27,7 +27,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "compat.h" #include "mmulti.h" #include "c_bind.h" -#include "menu.h" +#include "razemenu.h" #include "gamestate.h" #include "blood.h" @@ -36,6 +36,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "view.h" #include "sound.h" #include "v_video.h" +#include "v_draw.h" bool ShowOptionMenu(); @@ -124,6 +125,7 @@ static std::unique_ptr itemBloodQAV; // This must be global to void UpdateNetworkMenus(void) { +#if 0 // For now disable the network menu item as it is not yet functional. for (auto name : { NAME_Mainmenu, NAME_IngameMenu }) { @@ -140,8 +142,11 @@ void UpdateNetworkMenus(void) } } } +#endif } + +#if 0 //---------------------------------------------------------------------------- // // Implements the native looking menu used for the main menu @@ -196,6 +201,7 @@ class DBloodImageScrollerMenu : public DImageScrollerMenu } }; +#endif //---------------------------------------------------------------------------- // @@ -205,6 +211,7 @@ class DBloodImageScrollerMenu : public DImageScrollerMenu void GameInterface::DrawNativeMenuText(int fontnum, int state, double xpos, double ypos, float fontscale, const char* text, int flags) { +#if 0 if (!text) return; int shade = (state != NIT_InactiveState) ? 32 : 48; int pal = (state != NIT_InactiveState) ? 5 : 5; @@ -221,6 +228,7 @@ void GameInterface::DrawNativeMenuText(int fontnum, int state, double xpos, doub DrawText(twod, gamefont, CR_UNDEFINED, xpos, ypos, text, DTA_TranslationIndex, TRANSLATION(Translation_Remap, pal), DTA_Color, shadeToLight(shade), DTA_FullscreenScale, FSMode_Fit320x200, TAG_DONE); +#endif } @@ -245,7 +253,7 @@ bool GameInterface::StartGame(FNewGameStartup& gs) { if (g_gameType & GAMEFLAG_SHAREWARE) { - M_StartMessage(GStrings("BUYBLOOD"), 1, -1); // unreachable because we do not support Blood SW versions yet. + M_StartMessage(GStrings("BUYBLOOD"), 1, NAME_None); // unreachable because we do not support Blood SW versions yet. return false; } } @@ -304,20 +312,3 @@ void GameInterface::QuitToTitle() END_BLD_NS -//---------------------------------------------------------------------------- -// -// Class registration -// -//---------------------------------------------------------------------------- - - -static TMenuClassDescriptor _lm("Blood.ListMenu"); -static TMenuClassDescriptor _mm("Blood.MainMenu"); -static TMenuClassDescriptor _im("Blood.ImageScrollerMenu"); - -void RegisterBloodMenus() -{ - menuClasses.Push(&_lm); - menuClasses.Push(&_mm); - menuClasses.Push(&_im); -} diff --git a/source/blood/src/hudsprites.cpp b/source/blood/src/hudsprites.cpp index ca14fae03..30860b12d 100644 --- a/source/blood/src/hudsprites.cpp +++ b/source/blood/src/hudsprites.cpp @@ -39,7 +39,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "view.h" #include "nnexts.h" #include "zstring.h" -#include "menu.h" +#include "razemenu.h" #include "gstrings.h" #include "v_2ddrawer.h" #include "v_video.h" diff --git a/source/blood/src/levels.cpp b/source/blood/src/levels.cpp index 5b94ad66e..0e08167a6 100644 --- a/source/blood/src/levels.cpp +++ b/source/blood/src/levels.cpp @@ -38,7 +38,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "sound.h" #include "view.h" #include "eventq.h" -#include "menu.h" +#include "razemenu.h" BEGIN_BLD_NS diff --git a/source/blood/src/prediction.cpp b/source/blood/src/prediction.cpp index c28022998..db808be38 100644 --- a/source/blood/src/prediction.cpp +++ b/source/blood/src/prediction.cpp @@ -39,7 +39,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "view.h" #include "nnexts.h" #include "zstring.h" -#include "menu.h" +#include "razemenu.h" #include "gstrings.h" #include "v_2ddrawer.h" #include "v_video.h" diff --git a/source/blood/src/sbar.cpp b/source/blood/src/sbar.cpp index 6fd4cd610..ff993c522 100644 --- a/source/blood/src/sbar.cpp +++ b/source/blood/src/sbar.cpp @@ -39,7 +39,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "view.h" #include "nnexts.h" #include "zstring.h" -#include "menu.h" +#include "razemenu.h" #include "gstrings.h" #include "v_2ddrawer.h" #include "v_video.h" @@ -47,6 +47,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "glbackend/glbackend.h" #include "statusbar.h" #include "automap.h" +#include "v_draw.h" CVARD(Bool, hud_powerupduration, true, CVAR_ARCHIVE/*|CVAR_FRONTEND_BLOOD*/, "enable/disable displaying the remaining seconds for power-ups") diff --git a/source/blood/src/view.cpp b/source/blood/src/view.cpp index 4d6a81b44..e60f0736d 100644 --- a/source/blood/src/view.cpp +++ b/source/blood/src/view.cpp @@ -39,13 +39,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "view.h" #include "nnexts.h" #include "zstring.h" -#include "menu.h" +#include "razemenu.h" #include "gstrings.h" #include "v_2ddrawer.h" #include "v_video.h" #include "v_font.h" #include "statusbar.h" #include "automap.h" +#include "v_draw.h" #include "glbackend/glbackend.h" BEGIN_BLD_NS diff --git a/source/build/src/engine.cpp b/source/build/src/engine.cpp index 6aa23f294..d35a5a155 100644 --- a/source/build/src/engine.cpp +++ b/source/build/src/engine.cpp @@ -23,7 +23,7 @@ #include "v_2ddrawer.h" #include "v_draw.h" #include "stats.h" -#include "menu.h" +#include "razemenu.h" #include "version.h" #include "earcut.hpp" #include "gamestate.h" diff --git a/source/common/audio/music/music_midi_base.cpp b/source/common/audio/music/music_midi_base.cpp index 6ce987801..6e51103b6 100644 --- a/source/common/audio/music/music_midi_base.cpp +++ b/source/common/audio/music/music_midi_base.cpp @@ -35,7 +35,7 @@ #include "c_dispatch.h" #include "v_text.h" -#include "menu.h" +#include "razemenu.h" #include #include "s_music.h" #include "c_cvars.h" diff --git a/source/common/audio/sound/oalsound.cpp b/source/common/audio/sound/oalsound.cpp index d97c9d5a6..d8f2f7506 100644 --- a/source/common/audio/sound/oalsound.cpp +++ b/source/common/audio/sound/oalsound.cpp @@ -2078,7 +2078,7 @@ FSoundChan *OpenALSoundRenderer::FindLowestChannel() #endif // NO_OPENAL -#include "menu.h" +#include "razemenu.h" void I_BuildALDeviceList(FOptionValues* opt) { diff --git a/source/common/engine/d_event.cpp b/source/common/engine/d_event.cpp index effb5dc3a..38a17afd4 100644 --- a/source/common/engine/d_event.cpp +++ b/source/common/engine/d_event.cpp @@ -37,7 +37,7 @@ #include "d_eventbase.h" #include "c_console.h" #include "d_gui.h" -#include "menu.h" +#include "razemenu.h" #include "utf8.h" #include "m_joy.h" #include "vm.h" diff --git a/source/common/objects/dobjgc.cpp b/source/common/objects/dobjgc.cpp index 9afa0b0af..68d92f949 100644 --- a/source/common/objects/dobjgc.cpp +++ b/source/common/objects/dobjgc.cpp @@ -59,7 +59,7 @@ #include "dobject.h" #include "templates.h" #include "c_dispatch.h" -#include "menu.h" +#include "razemenu.h" #include "stats.h" #include "printf.h" diff --git a/source/common/platform/win32/i_mouse.cpp b/source/common/platform/win32/i_mouse.cpp index a3042236a..d4c40ea8b 100644 --- a/source/common/platform/win32/i_mouse.cpp +++ b/source/common/platform/win32/i_mouse.cpp @@ -42,7 +42,7 @@ #include "d_eventbase.h" #include "d_gui.h" #include "hardware.h" -#include "menu.h" +#include "razemenu.h" #include "menustate.h" #include "keydef.h" #include "i_interface.h" diff --git a/source/common/rendering/hwrenderer/data/hw_cvars.cpp b/source/common/rendering/hwrenderer/data/hw_cvars.cpp index 6faf39e0d..022cfef77 100644 --- a/source/common/rendering/hwrenderer/data/hw_cvars.cpp +++ b/source/common/rendering/hwrenderer/data/hw_cvars.cpp @@ -40,7 +40,7 @@ #include "c_dispatch.h" #include "v_video.h" #include "hw_cvars.h" -#include "menu.h" +#include "razemenu.h" #include "printf.h" diff --git a/source/common/rendering/v_video.cpp b/source/common/rendering/v_video.cpp index 3cf9f7bb1..660526c93 100644 --- a/source/common/rendering/v_video.cpp +++ b/source/common/rendering/v_video.cpp @@ -53,7 +53,7 @@ #include "cmdlib.h" #include "hardware.h" #include "m_png.h" -#include "menu.h" +#include "razemenu.h" #include "vm.h" #include "r_videoscale.h" #include "i_time.h" diff --git a/source/common/scripting/core/imports.cpp b/source/common/scripting/core/imports.cpp index b1b478b74..26b912ff3 100644 --- a/source/common/scripting/core/imports.cpp +++ b/source/common/scripting/core/imports.cpp @@ -36,7 +36,7 @@ #include "gstrings.h" #include "v_font.h" -#include "menu.h" +#include "razemenu.h" #include "types.h" #include "dictionary.h" #include "vm.h" diff --git a/source/common/scripting/interface/vmnatives.cpp b/source/common/scripting/interface/vmnatives.cpp index 4fa160ac5..0b054561f 100644 --- a/source/common/scripting/interface/vmnatives.cpp +++ b/source/common/scripting/interface/vmnatives.cpp @@ -42,7 +42,7 @@ #include "c_bind.h" #include "c_dispatch.h" #include "templates.h" -#include "menu.h" +#include "razemenu.h" #include "vm.h" #include "gstrings.h" #include "printf.h" diff --git a/source/core/console/c_console.cpp b/source/core/console/c_console.cpp index 87f626de6..0c82d4f7c 100644 --- a/source/core/console/c_console.cpp +++ b/source/core/console/c_console.cpp @@ -62,9 +62,10 @@ #include "v_video.h" #include "v_draw.h" #include "g_input.h" -#include "menu.h" +#include "razemenu.h" #include "raze_music.h" #include "gstrings.h" +#include "menustate.h" #define LEFTMARGIN 8 #define RIGHTMARGIN 8 diff --git a/source/core/console/d_event.cpp b/source/core/console/d_event.cpp index 7ad95a489..2af44f0b8 100644 --- a/source/core/console/d_event.cpp +++ b/source/core/console/d_event.cpp @@ -38,7 +38,7 @@ #include "c_console.h" #include "d_gui.h" #include "inputstate.h" -#include "menu.h" +#include "razemenu.h" #include "gamestate.h" #include "gamecontrol.h" #include "uiinput.h" diff --git a/source/core/ct_chat.cpp b/source/core/ct_chat.cpp index 049178dd4..0cce9c3c5 100644 --- a/source/core/ct_chat.cpp +++ b/source/core/ct_chat.cpp @@ -36,9 +36,10 @@ #include "vm.h" #include "c_buttons.h" #include "v_draw.h" -#include "menu.h" +#include "razemenu.h" #include "gamestruct.h" #include "gamecvars.h" +#include "menustate.h" enum { diff --git a/source/core/d_net.cpp b/source/core/d_net.cpp index d719a479c..e159034b4 100644 --- a/source/core/d_net.cpp +++ b/source/core/d_net.cpp @@ -46,7 +46,7 @@ #include #include "version.h" -#include "menu.h" +#include "razemenu.h" #include "i_video.h" #include "c_console.h" #include "d_net.h" diff --git a/source/core/gamecontrol.cpp b/source/core/gamecontrol.cpp index 23cd4b0eb..2e24955ff 100644 --- a/source/core/gamecontrol.cpp +++ b/source/core/gamecontrol.cpp @@ -41,7 +41,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "i_specialpaths.h" #include "raze_music.h" #include "statistics.h" -#include "menu.h" +#include "razemenu.h" #include "gstrings.h" #include "quotemgr.h" #include "mapinfo.h" @@ -71,6 +71,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "uiinput.h" #include "d_net.h" #include "automap.h" +#include "v_draw.h" CVAR(Bool, autoloadlights, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(Bool, autoloadbrightmaps, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -533,7 +534,7 @@ int GameMain() r = -1; } DeleteScreenJob(); - M_ClearMenus(true); + M_ClearMenus(); if (gi) { gi->FreeGameData(); // Must be done before taking down any subsystems. @@ -551,7 +552,6 @@ int GameMain() TileFiles.CloseAll(); // delete the texture data before shutting down graphics. GLInterface.Deinit(); I_ShutdownGraphics(); - M_DeinitMenus(); engineUnInit(); if (gi) { @@ -1791,3 +1791,63 @@ void playerProcessHelpers(fixed_t* q16ang, double* angAdjust, fixed_t* angTarget *q16horiz += FloatToFixed(scaleAdjust * *horizAdjust); } } + +void GameInterface::DrawCenteredTextScreen(const DVector2& origin, const char* text, int position, bool bg) +{ + double scale = SmallFontScale(); + int formatwidth = int(320 / scale); + auto lines = V_BreakLines(SmallFont, formatwidth, text, true); + auto fheight = bg ? 10 : SmallFont->GetHeight() * scale; // Fixme: Get spacing for text pages from elsewhere. + if (!bg) + { + auto totaltextheight = lines.Size() * fheight; + position -= totaltextheight / 2; + } + + double y = origin.Y + position; + for (auto& line : lines) + { + double x = origin.X + 160 - line.Width * scale * 0.5; + DrawText(twod, SmallFont, CR_UNTRANSLATED, x, y, line.Text, DTA_FullscreenScale, FSMode_Fit320x200, DTA_ScaleX, scale, DTA_ScaleY, scale, TAG_DONE); + y += fheight; + } +} + +bool M_Active() +{ + return CurrentMenu != nullptr || ConsoleState == c_down || ConsoleState == c_falling; +} + +struct gamefilter +{ + const char* gamename; + int gameflag; +}; + +static const gamefilter games[] = { + { "Duke", GAMEFLAG_DUKE}, + { "Nam", GAMEFLAG_NAM | GAMEFLAG_NAPALM}, + { "NamOnly", GAMEFLAG_NAM}, // for cases where the difference matters. + { "Napalm", GAMEFLAG_NAPALM}, + { "WW2GI", GAMEFLAG_WW2GI}, + { "Redneck", GAMEFLAG_RR}, + { "RedneckRides", GAMEFLAG_RRRA}, + { "Deer", GAMEFLAG_DEER}, + { "Blood", GAMEFLAG_BLOOD}, + { "ShadowWarrior", GAMEFLAG_SW}, + { "Exhumed", GAMEFLAG_POWERSLAVE | GAMEFLAG_EXHUMED}, + { "Worldtour", GAMEFLAG_WORLDTOUR}, +}; + +// for other parts that need to filter by game name. +bool validFilter(const char* str) +{ + for (auto& gf : games) + { + if (g_gameType & gf.gameflag) + { + if (!stricmp(str, gf.gamename)) return true; + } + } + return false; +} diff --git a/source/core/mainloop.cpp b/source/core/mainloop.cpp index 20949aace..292addd3b 100644 --- a/source/core/mainloop.cpp +++ b/source/core/mainloop.cpp @@ -67,7 +67,7 @@ #include "d_net.h" #include "gamecontrol.h" #include "c_console.h" -#include "menu.h" +#include "razemenu.h" #include "i_system.h" #include "raze_sound.h" #include "raze_music.h" @@ -76,7 +76,6 @@ #include "screenjob.h" #include "mmulti.h" #include "c_console.h" -#include "menu.h" #include "uiinput.h" #include "v_video.h" #include "glbackend/glbackend.h" @@ -86,6 +85,8 @@ #include "mapinfo.h" #include "automap.h" #include "statusbar.h" +#include "gamestruct.h" +#include "savegamehelp.h" CVAR(Bool, vid_activeinbackground, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Bool, r_ticstability, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -103,7 +104,7 @@ int entertic; int oldentertics; int gametic; -extern FString BackupSaveGame; +FString BackupSaveGame; void DoLoadGame(const char* name); @@ -146,7 +147,9 @@ static void GameTicker() C_ClearMessages(); if (BackupSaveGame.IsNotEmpty() && cl_resumesavegame) { +#if 0 DoLoadGame(BackupSaveGame); +#endif } else { diff --git a/source/core/mapinfo.cpp b/source/core/mapinfo.cpp index eaea5cf0f..4ddee3b0c 100644 --- a/source/core/mapinfo.cpp +++ b/source/core/mapinfo.cpp @@ -38,6 +38,12 @@ #include "filesystem.h" #include "printf.h" +FString gSkillNames[MAXSKILLS]; +FString gVolumeNames[MAXVOLUMES]; +FString gVolumeSubtitles[MAXVOLUMES]; +int32_t gVolumeFlags[MAXVOLUMES]; +int gDefaultVolume = 0, gDefaultSkill = 1; + MapRecord mapList[512]; // Due to how this gets used it needs to be static. EDuke defines 7 episode plus one spare episode with 64 potential levels each and relies on the static array which is freely accessible by scripts. MapRecord *currentLevel; // level that is currently played. (The real level, not what script hacks modfifying the current level index can pretend.) MapRecord* lastLevel; // Same here, for the last level. diff --git a/source/core/mapinfo.h b/source/core/mapinfo.h index f9f1d1b70..19d8fc202 100644 --- a/source/core/mapinfo.h +++ b/source/core/mapinfo.h @@ -7,6 +7,22 @@ #undef GetMessage // Windows strikes... #endif + +enum EMax +{ + MAXSKILLS = 7, + MAXVOLUMES = 7, + MAXMENUGAMEPLAYENTRIES = 7, +}; + +// These get filled in by the map definition parsers of the front ends. +extern FString gSkillNames[MAXSKILLS]; +extern FString gVolumeNames[MAXVOLUMES]; +extern FString gVolumeSubtitles[MAXVOLUMES]; +extern int32_t gVolumeFlags[MAXVOLUMES]; +extern int gDefaultVolume, gDefaultSkill; + + // Localization capable replacement of the game specific solutions. inline void MakeStringLocalizable(FString "e) diff --git a/source/core/menu/imagescroller.cpp b/source/core/menu/imagescroller.cpp deleted file mode 100644 index 47bfa7ff9..000000000 --- a/source/core/menu/imagescroller.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/* -** imagescroller.cpp -** Scrolls through multiple fullscreen image pages, -** -**--------------------------------------------------------------------------- -** Copyright 2019 Christoph Oelckers -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "v_font.h" -#include "cmdlib.h" -#include "gstrings.h" -#include "d_gui.h" -#include "d_event.h" -#include "menu.h" -#include "v_draw.h" -#include "gamecontrol.h" -#include "build.h" -#include "zstring.h" - -//============================================================================= -// -// Fullscreen image drawer (move to its own source file!) -// -//============================================================================= - -void ImageScreen::Drawer() -{ - if (mDesc == nullptr) - { - // don't let bogus definitions crash this. - } - else if (mDesc->type == 0) - { - auto tileindexp = NameToTileIndex.CheckKey(FName(mDesc->text, true)); - int tileindex = 0; - if (tileindexp == nullptr) - { - // If this isn't a name, try a literal tile index; - auto c = mDesc->text.GetChars(); - if (*c == '#') tileindex = (int)strtoll(c + 1, nullptr, 0); - // Error out if the screen cannot be found, this is always a definition error that needs to be reported. - else I_Error("Invalid menu screen '%s'", mDesc->text.GetChars()); - } - else tileindex = *tileindexp; - if (!gi->DrawSpecialScreen(origin, tileindex)) // allows the front end to do custom handling for a given image. - { - DrawTexture(twod, tileGetTexture(tileindex), origin.X, origin.Y, DTA_FullscreenScale, FSMode_Fit320x200, DTA_TopLeft, true, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE); - } - } - else if (mDesc->type > 0) - { - gi->DrawCenteredTextScreen(origin, mDesc->text, mDesc->type); - } - // QAVs are handled in the Blood frontend. Maybe they should be moved out? Stuff for later, but this is a feature where it is feasible. -} - -ImageScreen* DImageScrollerMenu::newImageScreen(FImageScrollerDescriptor::ScrollerItem* desc) -{ - return new ImageScreen(desc); -} - -void DImageScrollerMenu::Init(DMenu* parent, FImageScrollerDescriptor* desc) -{ - mParentMenu = parent; - index = 0; - mDesc = desc; - canAnimate = !!(mDesc->mFlags & LMF_Animate); - - mCurrent = newImageScreen(&mDesc->mItems[0]); - mCurrent->canAnimate = canAnimate; - isAnimated = true; -} - -bool DImageScrollerMenu::MenuEvent(int mkey, bool fromcontroller) -{ - if (mDesc->mItems.Size() <= 1) - { - if (mkey == MKEY_Enter) mkey = MKEY_Back; - else if (mkey == MKEY_Right || mkey == MKEY_Left) return true; - } - switch (mkey) - { - case MKEY_Back: - // Before going back the currently running transition must be terminated. - pageTransition.previous = nullptr; - if (pageTransition.current) pageTransition.current->origin = { 0,0 }; - return DMenu::MenuEvent(mkey, fromcontroller); - - - case MKEY_Left: - if (pageTransition.previous == nullptr) - { - if (--index < 0) index = mDesc->mItems.Size() - 1; - auto next = newImageScreen(&mDesc->mItems[index]); - next->canAnimate = canAnimate; - if (!pageTransition.StartTransition(mCurrent, next, MA_Return)) - { - delete mCurrent; - } - mCurrent = next; - gi->MenuSound(ChooseSound); - } - return true; - - case MKEY_Right: - case MKEY_Enter: - if (pageTransition.previous == nullptr) - { - int oldindex = index; - if (++index >= (int)mDesc->mItems.Size()) index = 0; - - auto next = newImageScreen(&mDesc->mItems[index]); - next->canAnimate = canAnimate; - if (!pageTransition.StartTransition(mCurrent, next, MA_Advance)) - { - delete mCurrent; - } - mCurrent = next; - gi->MenuSound(ChooseSound); - } - return true; - - default: - return DMenu::MenuEvent(mkey, fromcontroller); - } -} - -bool DImageScrollerMenu::MouseEvent(int type, int x, int y) -{ - // Todo: Implement some form of drag event to switch between pages. - if (type == MOUSE_Release) - { - return MenuEvent(MKEY_Enter, false); - } - - return DMenu::MouseEvent(type, x, y); -} - -void DImageScrollerMenu::Ticker() -{ -} - -void DImageScrollerMenu::Drawer() -{ - if (pageTransition.previous != nullptr) - { - auto res = pageTransition.Draw(); - if (res) return; - delete pageTransition.previous; - pageTransition.previous = nullptr; - } - mCurrent->origin = origin; - mCurrent->Drawer(); - mCurrent->origin = {}; -} - diff --git a/source/core/menu/joystickmenu.cpp b/source/core/menu/joystickmenu.cpp index 0c3163f53..4baea836f 100644 --- a/source/core/menu/joystickmenu.cpp +++ b/source/core/menu/joystickmenu.cpp @@ -32,313 +32,105 @@ ** */ -#include - #include "menu.h" -#include "c_dispatch.h" -#include "filesystem.h" -#include "sc_man.h" -#include "v_font.h" -#include "c_bind.h" -#include "d_event.h" -#include "d_gui.h" #include "m_joy.h" -#include "v_video.h" - -#define NO_IMP -#include "optionmenuitems.h" - +#include "vm.h" static TArray Joysticks; -IJoystickConfig *SELECTED_JOYSTICK; -FOptionMenuDescriptor *UpdateJoystickConfigMenu(IJoystickConfig *joy); - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuSliderJoySensitivity : public FOptionMenuSliderBase +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetSensitivity) { -public: - FOptionMenuSliderJoySensitivity(const char *label, double min, double max, double step, int showval) - : FOptionMenuSliderBase(label, min, max, step, showval) - { - } - - double GetSliderValue() - { - return SELECTED_JOYSTICK->GetSensitivity(); - } - - void SetSliderValue(double val) - { - SELECTED_JOYSTICK->SetSensitivity(float(val)); - } -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuSliderJoyScale : public FOptionMenuSliderBase -{ - int mAxis; - int mNeg; - -public: - FOptionMenuSliderJoyScale(const char *label, int axis, double min, double max, double step, int showval) - : FOptionMenuSliderBase(label, min, max, step, showval) - { - mAxis = axis; - mNeg = 1; - } - - double GetSliderValue() - { - double d = SELECTED_JOYSTICK->GetAxisScale(mAxis); - mNeg = d < 0? -1:1; - return d; - } - - void SetSliderValue(double val) - { - SELECTED_JOYSTICK->SetAxisScale(mAxis, float(val * mNeg)); - } -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuSliderJoyDeadZone : public FOptionMenuSliderBase -{ - int mAxis; - int mNeg; - -public: - FOptionMenuSliderJoyDeadZone(const char *label, int axis, double min, double max, double step, int showval) - : FOptionMenuSliderBase(label, min, max, step, showval) - { - mAxis = axis; - mNeg = 1; - } - - double GetSliderValue() - { - double d = SELECTED_JOYSTICK->GetAxisDeadZone(mAxis); - mNeg = d < 0? -1:1; - return d; - } - - void SetSliderValue(double val) - { - SELECTED_JOYSTICK->SetAxisDeadZone(mAxis, float(val * mNeg)); - } -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuItemJoyMap : public FOptionMenuItemOptionBase -{ - int mAxis; -public: - - FOptionMenuItemJoyMap(const char *label, int axis, const char *values, int center) - : FOptionMenuItemOptionBase(label, "none", values, NULL, center) - { - mAxis = axis; - } - - int GetSelection() - { - double f = SELECTED_JOYSTICK->GetAxisMap(mAxis); - FOptionValues **opt = OptionValues.CheckKey(mValues); - if (opt != NULL && *opt != NULL) - { - // Map from joystick axis to menu selection. - for(unsigned i = 0; i < (*opt)->mValues.Size(); i++) - { - if (fabs(f - (*opt)->mValues[i].Value) < FLT_EPSILON) - { - return i; - } - } - } - return -1; - } - - void SetSelection(int selection) - { - FOptionValues **opt = OptionValues.CheckKey(mValues); - // Map from menu selection to joystick axis. - if (opt == NULL || *opt == NULL || (unsigned)selection >= (*opt)->mValues.Size()) - { - selection = JOYAXIS_None; - } - else - { - selection = (int)(*opt)->mValues[selection].Value; - } - SELECTED_JOYSTICK->SetAxisMap(mAxis, (EJoyAxis)selection); - } -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuItemInverter : public FOptionMenuItemOptionBase -{ - int mAxis; -public: - - FOptionMenuItemInverter(const char *label, int axis, int center) - : FOptionMenuItemOptionBase(label, "none", "YesNo", NULL, center) - { - mAxis = axis; - } - - int GetSelection() - { - float f = SELECTED_JOYSTICK->GetAxisScale(mAxis); - return f > 0? 0:1; - } - - void SetSelection(int Selection) - { - float f = fabs(SELECTED_JOYSTICK->GetAxisScale(mAxis)); - if (Selection) f*=-1; - SELECTED_JOYSTICK->SetAxisScale(mAxis, f); - } -}; - -class DJoystickConfigMenu : public DOptionMenu -{ -}; - - -//============================================================================= -// -// Executes a CCMD, action is a CCMD name -// -//============================================================================= - -class FOptionMenuItemJoyConfigMenu : public FOptionMenuItemSubmenu -{ - IJoystickConfig *mJoy; -public: - FOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy) - : FOptionMenuItemSubmenu(label, "JoystickConfigMenu") - { - mJoy = joy; - } - - bool Activate(FName caller) override - { - UpdateJoystickConfigMenu(mJoy); - return FOptionMenuItemSubmenu::Activate(caller); - } -}; - - -/*======================================= - * - * Joystick Menu - * - *=======================================*/ - -FOptionMenuDescriptor *UpdateJoystickConfigMenu(IJoystickConfig *joy) -{ - FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_JoystickConfigMenu); - if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) - { - FOptionMenuDescriptor *opt = (FOptionMenuDescriptor *)*desc; - FOptionMenuItem *it; - for(unsigned i=0;imItems.Size();i++) - { - delete opt->mItems[i]; - opt->mItems.Clear(); - } - if (joy == NULL) - { - opt->mTitle = "$JOYMNU_TITLE"; - it = new FOptionMenuItemStaticText("$JOYMNU_INVALID"); - opt->mItems.Push(it); - } - else - { - opt->mTitle.Format("%s", joy->GetName().GetChars()); - - SELECTED_JOYSTICK = joy; - - it = new FOptionMenuSliderJoySensitivity("$JOYMNU_OVRSENS", 0, 2, 0.1, 3); - opt->mItems.Push(it); - it = new FOptionMenuItemStaticText(" "); - opt->mItems.Push(it); - - if (joy->GetNumAxes() > 0) - { - it = new FOptionMenuItemStaticText("$JOYMNU_AXIS"); - opt->mItems.Push(it); - - for (int i = 0; i < joy->GetNumAxes(); ++i) - { - it = new FOptionMenuItemStaticText(" "); - opt->mItems.Push(it); - - it = new FOptionMenuItemJoyMap(joy->GetAxisName(i), i, "JoyAxisMapNames", false); - opt->mItems.Push(it); - it = new FOptionMenuSliderJoyScale("$JOYMNU_OVRSENS", i, 0, 4, 0.1, 3); - opt->mItems.Push(it); - it = new FOptionMenuItemInverter("$JOYMNU_INVERT", i, false); - opt->mItems.Push(it); - it = new FOptionMenuSliderJoyDeadZone("$JOYMNU_DEADZONE", i, 0, 0.9, 0.05, 3); - opt->mItems.Push(it); - } - } - else - { - it = new FOptionMenuItemStaticText("$JOYMNU_NOAXES"); - opt->mItems.Push(it); - } - } - opt->mScrollPos = 0; - opt->mSelectedItem = -1; - opt->mIndent = 0; - opt->CalcIndent(); - return opt; - } - return NULL; + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + ACTION_RETURN_FLOAT(self->GetSensitivity()); } +DEFINE_ACTION_FUNCTION(IJoystickConfig, SetSensitivity) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_FLOAT(sens); + self->SetSensitivity((float)sens); + return 0; +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisScale) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + ACTION_RETURN_FLOAT(self->GetAxisScale(axis)); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisScale) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + PARAM_FLOAT(sens); + self->SetAxisScale(axis, (float)sens); + return 0; +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisDeadZone) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + ACTION_RETURN_FLOAT(self->GetAxisDeadZone(axis)); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisDeadZone) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + PARAM_FLOAT(dz); + self->SetAxisDeadZone(axis, (float)dz); + return 0; +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisMap) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + ACTION_RETURN_INT(self->GetAxisMap(axis)); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisMap) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + PARAM_INT(map); + self->SetAxisMap(axis, (EJoyAxis)map); + return 0; +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetName) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + ACTION_RETURN_STRING(self->GetName()); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisName) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_INT(axis); + ACTION_RETURN_STRING(self->GetAxisName(axis)); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetNumAxes) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + ACTION_RETURN_INT(self->GetNumAxes()); +} void UpdateJoystickMenu(IJoystickConfig *selected) { - FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_JoystickOptions); - if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_JoystickOptions); + DMenuDescriptor **ddesc = MenuDescriptors.CheckKey("JoystickOptionsDefaults"); + if (ddesc == nullptr) return; // without any data the menu cannot be set up and must remain empty. + if (desc != NULL && (*desc)->IsKindOf(RUNTIME_CLASS(DOptionMenuDescriptor))) { - FOptionMenuDescriptor *opt = (FOptionMenuDescriptor *)*desc; - FOptionMenuItem *it; - - for(unsigned i=0;imItems.Size();i++) - { - delete opt->mItems[i]; - } - opt->mItems.Clear(); + DOptionMenuDescriptor *opt = (DOptionMenuDescriptor *)*desc; + DOptionMenuDescriptor *dopt = (DOptionMenuDescriptor *)*ddesc; + if (dopt == nullptr) return; + DMenuItemBase *it; int i; int itemnum = -1; @@ -359,76 +151,48 @@ void UpdateJoystickMenu(IJoystickConfig *selected) } } } + opt->mItems = dopt->mItems; - // Todo: Block joystick for changing this one. - it = new FOptionMenuItemOption("$JOYMNU_ENABLE", "use_joystick", "YesNo", NULL, false); - opt->mItems.Push(it); - #if 0//def _WIN32 - it = new FOptionMenuItemOption("Enable DirectInput controllers", "joy_dinput", "YesNo", NULL, false); - opt->mItems.Push(it); - it = new FOptionMenuItemOption("Enable XInput controllers", "joy_xinput", "YesNo", NULL, false); - opt->mItems.Push(it); - it = new FOptionMenuItemOption("Enable raw PlayStation 2 adapters", "joy_ps2raw", "YesNo", NULL, false); - opt->mItems.Push(it); - #endif + it = opt->GetItem("ConfigureMessage"); + if (it != nullptr) it->SetValue(0, !!Joysticks.Size()); + it = opt->GetItem("ConnectMessage1"); + if (it != nullptr) it->SetValue(0, !use_joystick); + it = opt->GetItem("ConnectMessage2"); + if (it != nullptr) it->SetValue(0, !use_joystick); - it = new FOptionMenuItemStaticText(" "); - opt->mItems.Push(it); - - if (Joysticks.Size() == 0) + for (int i = 0; i < (int)Joysticks.Size(); ++i) { - it = new FOptionMenuItemStaticText("$JOYMNU_NOCON"); + it = CreateOptionMenuItemJoyConfigMenu(Joysticks[i]->GetName(), Joysticks[i]); + GC::WriteBarrier(opt, it); opt->mItems.Push(it); - if (!use_joystick) - { - it = new FOptionMenuItemStaticText("$JOYMNU_DISABLED1"); - opt->mItems.Push(it); - it = new FOptionMenuItemStaticText("$JOYMNU_DISABLED2"); - opt->mItems.Push(it); - } - } - else - { - it = new FOptionMenuItemStaticText("$JOYMNU_CONFIG"); - opt->mItems.Push(it); - - for (int i = 0; i < (int)Joysticks.Size(); ++i) - { - it = new FOptionMenuItemJoyConfigMenu(Joysticks[i]->GetName(), Joysticks[i]); - opt->mItems.Push(it); - if (i == itemnum) opt->mSelectedItem = opt->mItems.Size(); - } + if (i == itemnum) opt->mSelectedItem = opt->mItems.Size(); } if (opt->mSelectedItem >= (int)opt->mItems.Size()) { opt->mSelectedItem = opt->mItems.Size() - 1; } + //opt->CalcIndent(); - opt->CalcIndent(); - - // If the joystick config menu is open, close it if the device it's - // open for is gone. - for (i = 0; (unsigned)i < Joysticks.Size(); ++i) + // If the joystick config menu is open, close it if the device it's open for is gone. + if (CurrentMenu != nullptr && (CurrentMenu->IsKindOf("JoystickConfigMenu"))) { - if (Joysticks[i] == SELECTED_JOYSTICK) + auto p = CurrentMenu->PointerVar("mJoy"); + if (p != nullptr) { - break; - } - } - if (i == (int)Joysticks.Size()) - { - SELECTED_JOYSTICK = NULL; - if (CurrentMenu != NULL && dynamic_cast(CurrentMenu)) - { - CurrentMenu->Close(); + unsigned i; + for (i = 0; i < Joysticks.Size(); ++i) + { + if (Joysticks[i] == p) + { + break; + } + } + if (i == Joysticks.Size()) + { + CurrentMenu->Close(); + } } } } } -static TMenuClassDescriptor _im("JoystickConfigMenu"); - -void RegisterJoystickMenus() -{ - menuClasses.Push(&_im); -} diff --git a/source/core/menu/loadsavemenu.cpp b/source/core/menu/loadsavemenu.cpp index 944b928ea..e2e560cb5 100644 --- a/source/core/menu/loadsavemenu.cpp +++ b/source/core/menu/loadsavemenu.cpp @@ -4,7 +4,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2001-2010 Randy Heit -** Copyright 2010 Christoph Oelckers +** Copyright 2010-2017 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -33,612 +33,684 @@ ** */ -#include "menu.h" +#include "razemenu.h" #include "version.h" #include "m_png.h" #include "filesystem.h" #include "v_text.h" -#include "d_event.h" #include "gstrings.h" -#include "d_gui.h" -#include "v_draw.h" -#include "files.h" -#include "resourcefile.h" -#include "savegamehelp.h" -#include "i_specialpaths.h" -#include "findfile.h" +#include "serializer.h" +#include "vm.h" +#include "i_system.h" #include "v_video.h" +#include "findfile.h" +#include "v_draw.h" - - -class DLoadSaveMenu : public DListMenu -{ - using Super = DListMenu; - -protected: - - int Selected = 0; - int TopItem = 0; - - - int savepicLeft; - int savepicTop; - int savepicWidth; - int savepicHeight; - - int rowHeight; - int listboxLeft; - int listboxTop; - int listboxWidth; - - int listboxRows; - int listboxHeight; - int listboxRight; - int listboxBottom; - - int commentLeft; - int commentTop; - int commentWidth; - int commentHeight; - int commentRight; - int commentBottom; - int commentRows; - - double FontScale; - DTextEnterMenu *mInput = nullptr; - TArray BrokenSaveComment; - bool mEntering = false; - - - //============================================================================= - // - // End of static savegame maintenance code - // - //============================================================================= - - DLoadSaveMenu() - { - savegameManager.ReadSaveStrings(); - } - - void Init(DMenu* parent, FListMenuDescriptor* desc) override - { - Super::Init(parent, desc); - int Width43 = screen->GetHeight() * 4 / 3; - int Left43 = (screen->GetWidth() - Width43) / 2; - float wScale = Width43 / 640.; - savepicLeft = Left43 + int(20 * wScale); - savepicTop = mDesc->mYpos * screen->GetHeight() / 200 ; - savepicWidth = int(240 * wScale); - savepicHeight = int(180 * wScale); - - - FontScale = std::max(screen->GetHeight() / 480, 1); - rowHeight = std::max(int((NewConsoleFont->GetHeight() + 1) * FontScale), 1); - listboxLeft = savepicLeft + savepicWidth + int(20 * wScale); - listboxTop = savepicTop; - listboxWidth = Width43 + Left43 - listboxLeft - int(30 * wScale); - int listboxHeight1 = screen->GetHeight() - listboxTop - int(20*wScale); - listboxRows = (listboxHeight1 - 1) / rowHeight; - listboxHeight = listboxRows * rowHeight + 1; - listboxRight = listboxLeft + listboxWidth; - listboxBottom = listboxTop + listboxHeight; - - commentLeft = savepicLeft; - commentTop = savepicTop + savepicHeight + int(16 * wScale); - commentWidth = savepicWidth; - commentHeight = listboxHeight - savepicHeight - (16 * wScale); - commentRight = commentLeft + commentWidth; - commentBottom = commentTop + commentHeight; - commentRows = commentHeight / rowHeight; - UpdateSaveComment(); - } - - //============================================================================= - // - // - // - //============================================================================= - - void Drawer() override - { - Super::Drawer(); - - int i; - unsigned j; - bool didSeeSelected = false; - - // Draw picture area - /* - if (gameaction == ga_loadgame || gameaction == ga_loadgamehidecon || gameaction == ga_savegame) - { - return; - } - */ - - PalEntry frameColor(255, 80, 80, 80); // todo: pick a proper color per game. - PalEntry fillColor(160, 0, 0, 0); - DrawFrame(twod, frameColor, savepicLeft, savepicTop, savepicWidth, savepicHeight, -1); - if (!savegameManager.DrawSavePic(savepicLeft, savepicTop, savepicWidth, savepicHeight)) - { - twod->AddColorOnlyQuad(savepicLeft, savepicTop, savepicWidth, savepicHeight, fillColor); - - if (savegameManager.SavegameCount() > 0) - { - if (Selected >= savegameManager.SavegameCount()) Selected = 0; - FString text = (Selected == -1 || !savegameManager.GetSavegame(Selected)->bOldVersion) ? GStrings("MNU_NOPICTURE") : GStrings("MNU_DIFFVERSION"); - int textlen = NewSmallFont->StringWidth(text) * CleanXfac; - - DrawText(twod, NewSmallFont, CR_GOLD, savepicLeft + (savepicWidth - textlen) / 2, - savepicTop + (savepicHeight - rowHeight) / 2, text, DTA_CleanNoMove, true, TAG_DONE); - } - } - - // Draw comment area - DrawFrame(twod, frameColor, commentLeft, commentTop, commentWidth, commentHeight, -1); - twod->AddColorOnlyQuad(commentLeft, commentTop, commentWidth, commentHeight, fillColor); - - int numlinestoprint = std::min(commentRows, (int)BrokenSaveComment.Size()); - for (int i = 0; i < numlinestoprint; i++) - { - DrawText(twod, NewConsoleFont, CR_ORANGE, commentLeft / FontScale, (commentTop + rowHeight * i) / FontScale, BrokenSaveComment[i].Text, - DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); - } - - - // Draw file area - DrawFrame(twod, frameColor, listboxLeft, listboxTop, listboxWidth, listboxHeight, -1); - twod->AddColorOnlyQuad(listboxLeft, listboxTop, listboxWidth, listboxHeight, fillColor); - - if (savegameManager.SavegameCount() == 0) - { - FString text = GStrings("MNU_NOFILES"); - int textlen = int(NewConsoleFont->StringWidth(text) * FontScale); - - DrawText(twod, NewConsoleFont, CR_GOLD, (listboxLeft + (listboxWidth - textlen) / 2) / FontScale, (listboxTop + (listboxHeight - rowHeight) / 2) / FontScale, text, - DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); - return; - } - - j = TopItem; - for (i = 0; i < listboxRows && j < savegameManager.SavegameCount(); i++) - { - int colr; - auto& node = *savegameManager.GetSavegame(j); - if (node.bOldVersion) - { - colr = CR_RED; - } - else if (node.bMissingWads) - { - colr = CR_YELLOW; - } - else if (j == Selected) - { - colr = CR_WHITE; - } - else - { - colr = CR_TAN; - } - - //screen->SetClipRect(listboxLeft, listboxTop+rowHeight*i, listboxRight, listboxTop+rowHeight*(i+1)); - - if ((int)j == Selected) - { - twod->AddColorOnlyQuad(listboxLeft, listboxTop + rowHeight * i, listboxWidth, rowHeight, mEntering ? PalEntry(255, 255, 0, 0) : PalEntry(255, 0, 0, 255)); - didSeeSelected = true; - if (!mEntering) - { - DrawText(twod, NewConsoleFont, colr, (listboxLeft + 1) / FontScale, (listboxTop + rowHeight * i + FontScale) / FontScale, node.SaveTitle, - DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); - } - else - { - FStringf s("%s%c", mInput->GetText(), NewConsoleFont->GetCursor()); - int length = int(NewConsoleFont->StringWidth(s) * FontScale); - int displacement = std::min(0, listboxWidth - 2 - length); - DrawText(twod, NewConsoleFont, CR_WHITE, (listboxLeft + 1 + displacement) / FontScale, (listboxTop + rowHeight * i + FontScale) / FontScale, s, - DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); - } - } - else - { - DrawText(twod, NewConsoleFont, colr, (listboxLeft + 1) / FontScale, (listboxTop + rowHeight * i + FontScale) / FontScale, node.SaveTitle, - DTA_VirtualWidthF, screen->GetWidth() / FontScale, DTA_VirtualHeightF, screen->GetHeight() / FontScale, DTA_KeepRatio, true, TAG_DONE); - } - //screen->ClearClipRect(); - j++; - } - } - - void UpdateSaveComment() - { - BrokenSaveComment = V_BreakLines(NewConsoleFont, int(commentWidth / FontScale), savegameManager.SaveCommentString); - } - - //============================================================================= - // - // - // - //============================================================================= - - bool MenuEvent(int mkey, bool fromcontroller) override - { - auto& manager = savegameManager; - switch (mkey) - { - case MKEY_Up: - if (manager.SavegameCount() > 1) - { - if (Selected == -1) Selected = TopItem; - else - { - if (--Selected < 0) Selected = manager.SavegameCount() - 1; - if (Selected < TopItem) TopItem = Selected; - else if (Selected >= TopItem + listboxRows) TopItem = std::max(0, Selected - listboxRows + 1); - } - manager.UnloadSaveData(); - manager.ExtractSaveData(Selected); - UpdateSaveComment(); - } - return true; - - case MKEY_Down: - if (manager.SavegameCount() > 1) - { - if (Selected == -1) Selected = TopItem; - else - { - if (++Selected >= manager.SavegameCount()) Selected = 0; - if (Selected < TopItem) TopItem = Selected; - else if (Selected >= TopItem + listboxRows) TopItem = std::max(0, Selected - listboxRows + 1); - } - manager.UnloadSaveData(); - manager.ExtractSaveData(Selected); - UpdateSaveComment(); - } - return true; - - case MKEY_PageDown: - if (manager.SavegameCount() > 1) - { - if (TopItem >= manager.SavegameCount() - listboxRows) - { - TopItem = 0; - if (Selected != -1) Selected = 0; - } - else - { - TopItem = std::min(TopItem + listboxRows, int(manager.SavegameCount()) - listboxRows); - if (TopItem > Selected&& Selected != -1) Selected = TopItem; - } - manager.UnloadSaveData(); - manager.ExtractSaveData(Selected); - UpdateSaveComment(); - } - return true; - - case MKEY_PageUp: - if (manager.SavegameCount() > 1) - { - if (TopItem == 0) - { - TopItem = std::max(0, int(manager.SavegameCount()) - listboxRows); - if (Selected != -1) Selected = TopItem; - } - else - { - TopItem = std::max(int(TopItem - listboxRows), 0); - if (Selected >= TopItem + listboxRows) Selected = TopItem; - } - manager.UnloadSaveData(); - manager.ExtractSaveData(Selected); - UpdateSaveComment(); - } - return true; - - case MKEY_Enter: - return false; // This event will be handled by the subclasses - - case MKEY_MBYes: - { - if (Selected < manager.SavegameCount()) - { - Selected = manager.RemoveSaveSlot(Selected); - UpdateSaveComment(); - } - return true; - } - - default: - return Super::MenuEvent(mkey, fromcontroller); - } - } - - //============================================================================= - // - // - // - //============================================================================= - - bool MouseEvent(int type, int x, int y) override - { - auto& manager = savegameManager; - if (x >= listboxLeft && x < listboxLeft + listboxWidth && - y >= listboxTop && y < listboxTop + listboxHeight) - { - int lineno = (y - listboxTop) / rowHeight; - - if (TopItem + lineno < manager.SavegameCount()) - { - Selected = TopItem + lineno; - manager.UnloadSaveData(); - manager.ExtractSaveData(Selected); - UpdateSaveComment(); - if (type == MOUSE_Release) - { - if (MenuEvent(MKEY_Enter, true)) - { - return true; - } - } - } - else Selected = -1; - } - else Selected = -1; - - return Super::MouseEvent(type, x, y); - } - - //============================================================================= - // - // - // - //============================================================================= - - bool Responder(event_t * ev) override - { - auto& manager = savegameManager; - if (ev->type == EV_GUI_Event) - { - if (ev->subtype == EV_GUI_KeyDown) - { - if ((unsigned)Selected < manager.SavegameCount()) - { - switch (ev->data1) - { - case GK_F1: - manager.SetFileInfo(Selected); - UpdateSaveComment(); - return true; - - case GK_DEL: - case '\b': - { - FString EndString; - EndString.Format("%s" TEXTCOLOR_WHITE "%s" TEXTCOLOR_NORMAL "?\n\n%s", - GStrings("MNU_DELETESG"), manager.GetSavegame(Selected)->SaveTitle.GetChars(), GStrings("PRESSYN")); - M_StartMessage(EndString, 0, -1); - } - return true; - } - } - } - else if (ev->subtype == EV_GUI_WheelUp) - { - if (TopItem > 0) TopItem--; - return true; - } - else if (ev->subtype == EV_GUI_WheelDown) - { - if (TopItem < manager.SavegameCount() - listboxRows) TopItem++; - return true; - } - } - return Super::Responder(ev); - } -}; +// Save name length limit for old binary formats. +#define OLDSAVESTRINGSIZE 24 //============================================================================= // -// +// Save data maintenance // //============================================================================= -class DSaveMenu : public DLoadSaveMenu +void FSavegameManager::ClearSaveGames() { - using Super = DLoadSaveMenu; - FString mSaveName; -public: - - - //============================================================================= - // - // - // - //============================================================================= - - DSaveMenu() + for (unsigned i = 0; ibNoDelete) + delete SaveGames[i]; } - - //============================================================================= - // - // - // - //============================================================================= - - void Destroy() override - { - if (savegameManager.RemoveNewSaveNode()) - { - Selected--; - } - Super::Destroy(); - } - //============================================================================= - // - // - // - //============================================================================= - - bool MenuEvent (int mkey, bool fromcontroller) override - { - if (Super::MenuEvent(mkey, fromcontroller)) - { - return true; - } - if (Selected == -1) - { - return false; - } - - if (mkey == MKEY_Enter) - { - FString SavegameString = (Selected != 0)? savegameManager.GetSavegame(Selected)->SaveTitle : FString(); - mInput = new DTextEnterMenu(this, NewConsoleFont, SavegameString, listboxWidth, false, false); - M_ActivateMenu(mInput); - mEntering = true; - } - else if (mkey == MKEY_Input) - { - mEntering = false; - mSaveName = mInput->GetText(); - mInput = nullptr; - } - else if (mkey == MKEY_Abort) - { - mEntering = false; - mInput = nullptr; - } - return false; - } - - //============================================================================= - // - // - // - //============================================================================= - - bool MouseEvent(int type, int x, int y) override - { - if (mSaveName.Len() > 0) - { - // Do not process events when saving is in progress to avoid update of the current index, - // i.e. Selected member variable must remain unchanged - return true; - } - - return Super::MouseEvent(type, x, y); - } - - //============================================================================= - // - // - // - //============================================================================= - - bool Responder (event_t *ev) override - { - if (ev->subtype == EV_GUI_KeyDown) - { - if (Selected != -1) - { - switch (ev->data1) - { - case GK_DEL: - case '\b': - // cannot delete 'new save game' item - if (Selected == 0) return true; - break; - - case 'N': - Selected = TopItem = 0; - savegameManager.UnloadSaveData(); - return true; - } - } - } - return Super::Responder(ev); - } - - //============================================================================= - // - // - // - //============================================================================= - - void Ticker() override - { - if (mSaveName.Len() > 0) - { - savegameManager.DoSave(Selected, mSaveName); - } - } - -}; - -//============================================================================= -// -// -// -//============================================================================= - -class DLoadMenu : public DLoadSaveMenu -{ - using Super = DLoadSaveMenu; - -public: - - //============================================================================= - // - // - // - //============================================================================= - - DLoadMenu() - { - TopItem = 0; - Selected = savegameManager.ExtractSaveData(-1); - UpdateSaveComment(); - } - - //============================================================================= - // - // - // - //============================================================================= - - bool MenuEvent(int mkey, bool fromcontroller) override - { - if (Super::MenuEvent(mkey, fromcontroller)) - { - return true; - } - if (Selected == -1 || savegameManager.SavegameCount() == 0) - { - return false; - } - - if (mkey == MKEY_Enter) - { - savegameManager.LoadSavegame(Selected); - return true; - } - return false; - } -}; - -static TMenuClassDescriptor _lm("LoadMenu"); -static TMenuClassDescriptor _sm("SaveMenu"); - -void RegisterLoadsaveMenus() -{ - menuClasses.Push(&_sm); - menuClasses.Push(&_lm); + SaveGames.Clear(); } + +FSavegameManager::~FSavegameManager() +{ + ClearSaveGames(); +} + +//============================================================================= +// +// Save data maintenance +// +//============================================================================= + +int FSavegameManager::RemoveSaveSlot(int index) +{ + int listindex = SaveGames[0]->bNoDelete ? index - 1 : index; + if (listindex < 0) return index; + + remove(SaveGames[index]->Filename.GetChars()); + UnloadSaveData(); + + FSaveGameNode *file = SaveGames[index]; + + if (quickSaveSlot == SaveGames[index]) + { + quickSaveSlot = nullptr; + } + if (!file->bNoDelete) delete file; + + if (LastSaved == listindex) LastSaved = -1; + else if (LastSaved > listindex) LastSaved--; + if (LastAccessed == listindex) LastAccessed = -1; + else if (LastAccessed > listindex) LastAccessed--; + + SaveGames.Delete(index); + if ((unsigned)index >= SaveGames.Size()) index--; + ExtractSaveData(index); + return index; +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, RemoveSaveSlot) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + PARAM_INT(sel); + ACTION_RETURN_INT(self->RemoveSaveSlot(sel)); +} + + +//============================================================================= +// +// +// +//============================================================================= + +int FSavegameManager::InsertSaveNode(FSaveGameNode *node) +{ + if (SaveGames.Size() == 0) + { + return SaveGames.Push(node); + } + + if (node->bOldVersion) + { // Add node at bottom of list + return SaveGames.Push(node); + } + else + { // Add node at top of list + unsigned int i; + for (i = 0; i < SaveGames.Size(); i++) + { + if (SaveGames[i]->bOldVersion || node->SaveTitle.CompareNoCase(SaveGames[i]->SaveTitle) <= 0) + { + break; + } + } + SaveGames.Insert(i, node); + return i; + } +} + +//============================================================================= +// +// M_ReadSaveStrings +// +// Find savegames and read their titles +// +//============================================================================= + +void FSavegameManager::ReadSaveStrings() +{ +#if 0 + if (SaveGames.Size() == 0) + { + void *filefirst; + findstate_t c_file; + FString filter; + + LastSaved = LastAccessed = -1; + quickSaveSlot = nullptr; + filter = G_BuildSaveName("*." SAVEGAME_EXT, -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); + + std::unique_ptr savegame(FResourceFile::OpenResourceFile(filepath, true, true)); + if (savegame != nullptr) + { + bool oldVer = false; + bool missing = false; + FResourceLump *info = savegame->FindLump("info.json"); + if (info == nullptr) + { + // savegame info not found. This is not a savegame so leave it alone. + continue; + } + void *data = info->Lock(); + FSerializer arc; + if (arc.OpenReader((const char *)data, info->LumpSize)) + { + int savever = 0; + arc("Save Version", savever); + FString engine = arc.GetString("Engine"); + FString iwad = arc.GetString("Game WAD"); + FString title = arc.GetString("Title"); + + + if (engine.Compare(GAMESIG) != 0 || savever > SAVEVER) + { + // different engine or newer version: + // not our business. Leave it alone. + continue; + } + + if (savever < MINSAVEVER) + { + // old, incompatible savegame. List as not usable. + oldVer = true; + } + else if (iwad.CompareNoCase(fileSystem.GetResourceFileName(fileSystem.GetIwadNum())) == 0) + { + missing = !G_CheckSaveGameWads(arc, false); + } + else + { + // different game. Skip this. + continue; + } + + FSaveGameNode *node = new FSaveGameNode; + node->Filename = filepath; + node->bOldVersion = oldVer; + node->bMissingWads = missing; + node->SaveTitle = title; + InsertSaveNode(node); + } + + } + else // check for old formats. + { + FileReader file; + if (file.OpenFile(filepath)) + { + PNGHandle *png; + char sig[16]; + char title[OLDSAVESTRINGSIZE + 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[OLDSAVESTRINGSIZE] = 0; + + if (nullptr != (png = M_VerifyPNG(file))) + { + char *ver = M_GetPNGText(png, "ZDoom Save Version"); + if (ver != nullptr) + { + // An old version + if (!M_GetPNGText(png, "Title", title, OLDSAVESTRINGSIZE)) + { + strncpy(title, I_FindName(&c_file), OLDSAVESTRINGSIZE); + } + addIt = true; + delete[] ver; + } + delete png; + } + else + { + file.Seek(0, FileReader::SeekSet); + if (file.Read(sig, 16) == 16) + { + + if (strncmp(sig, "ZDOOMSAVE", 9) == 0) + { + if (file.Read(title, OLDSAVESTRINGSIZE) == OLDSAVESTRINGSIZE) + { + addIt = true; + } + } + else + { + memcpy(title, sig, 16); + if (file.Read(title + 16, OLDSAVESTRINGSIZE - 16) == OLDSAVESTRINGSIZE - 16 && + file.Read(sig, 16) == 16 && + strncmp(sig, "ZDOOMSAVE", 9) == 0) + { + addIt = true; + } + } + } + } + + if (addIt) + { + FSaveGameNode *node = new FSaveGameNode; + node->Filename = filepath; + node->bOldVersion = true; + node->bMissingWads = false; + node->SaveTitle = title; + InsertSaveNode(node); + } + } + } + } while (I_FindNext(filefirst, &c_file) == 0); + I_FindClose(filefirst); + } + } +#endif +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, ReadSaveStrings) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + self->ReadSaveStrings(); + return 0; +} + + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::NotifyNewSave(const FString &file, const FString &title, bool okForQuicksave, bool forceQuicksave) +{ + FSaveGameNode *node; + + if (file.IsEmpty()) + return; + + ReadSaveStrings(); + + // See if the file is already in our list + for (unsigned i = 0; iFilename.Compare(file) == 0) +#else + if (node->Filename.CompareNoCase(file) == 0) +#endif + { + node->SaveTitle = title; + node->bOldVersion = false; + node->bMissingWads = false; + if (okForQuicksave) + { + if (quickSaveSlot == nullptr || quickSaveSlot == (FSaveGameNode*)1 || forceQuicksave) quickSaveSlot = node; + LastAccessed = LastSaved = i; + } + return; + } + } + + node = new FSaveGameNode; + node->SaveTitle = title; + node->Filename = file; + node->bOldVersion = false; + node->bMissingWads = false; + int index = InsertSaveNode(node); + + if (okForQuicksave) + { + if (quickSaveSlot == nullptr || quickSaveSlot == (FSaveGameNode*)1 || forceQuicksave) quickSaveSlot = node; + LastAccessed = LastSaved = index; + } + else + { + LastAccessed = ++LastSaved; + } +} + +//============================================================================= +// +// Loads the savegame +// +//============================================================================= + +void FSavegameManager::LoadSavegame(int Selected) +{ + //G_LoadGame(SaveGames[Selected]->Filename.GetChars(), true); + if (quickSaveSlot == (FSaveGameNode*)1) + { + quickSaveSlot = SaveGames[Selected]; + } + M_ClearMenus(); + LastAccessed = Selected; +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, LoadSavegame) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + PARAM_INT(sel); + self->LoadSavegame(sel); + return 0; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::DoSave(int Selected, const char *savegamestring) +{ +#if 0 + if (Selected != 0) + { + auto node = SaveGames[Selected]; + G_SaveGame(node->Filename.GetChars(), savegamestring); + } + else + { + // Find an unused filename and save as that + FString filename; + int i; + + for (i = 0;; ++i) + { + filename = G_BuildSaveName("save", i); + if (!FileExists(filename)) + { + break; + } + } + G_SaveGame(filename, savegamestring); + } +#endif + M_ClearMenus(); +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, DoSave) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + PARAM_INT(sel); + PARAM_STRING(name); + self->DoSave(sel, name); + return 0; +} + +//============================================================================= +// +// +// +//============================================================================= + +unsigned FSavegameManager::ExtractSaveData(int index) +{ + FResourceFile *resf; + FSaveGameNode *node; + + if (index == -1) + { + if (SaveGames.Size() > 0 && SaveGames[0]->bNoDelete) + { + index = LastSaved + 1; + } + else + { + index = LastAccessed < 0? 0 : LastAccessed; + } + } + + UnloadSaveData(); + + if ((unsigned)index < SaveGames.Size() && + (node = SaveGames[index]) && + !node->Filename.IsEmpty() && + !node->bOldVersion && + (resf = FResourceFile::OpenResourceFile(node->Filename.GetChars(), true)) != nullptr) + { + FResourceLump *info = resf->FindLump("info.json"); + if (info == nullptr) + { + // this should not happen because the file has already been verified. + return index; + } + void *data = info->Lock(); + FSerializer arc; + if (arc.OpenReader((const char *)data, info->LumpSize)) + { + FString comment; + + FString time = arc.GetString("Creation Time"); + FString pcomment = arc.GetString("Comment"); + + comment = time; + if (time.Len() > 0) comment += "\n"; + comment += pcomment; + SaveCommentString = comment; + + // Extract pic + FResourceLump *pic = resf->FindLump("savepic.png"); + if (pic != nullptr) + { + FileReader picreader; + + picreader.OpenMemoryArray([=](TArray &array) + { + auto cache = pic->Lock(); + array.Resize(pic->LumpSize); + memcpy(&array[0], cache, pic->LumpSize); + return true; + }); + PNGHandle *png = M_VerifyPNG(picreader); + if (png != nullptr) + { + SavePic = PNGTexture_CreateFromFile(png, node->Filename); + delete png; + if (SavePic && SavePic->GetDisplayWidth() == 1 && SavePic->GetDisplayHeight() == 1) + { + delete SavePic; + SavePic = nullptr; + } + } + } + } + delete resf; + } + return index; +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, ExtractSaveData) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + PARAM_INT(sel); + ACTION_RETURN_INT(self->ExtractSaveData(sel)); +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::UnloadSaveData() +{ + if (SavePic != nullptr) + { + delete SavePic; + } + + SaveCommentString = ""; + SavePic = nullptr; +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, UnloadSaveData) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + self->UnloadSaveData(); + return 0; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::ClearSaveStuff() +{ + UnloadSaveData(); + if (quickSaveSlot == (FSaveGameNode*)1) + { + quickSaveSlot = nullptr; + } +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, ClearSaveStuff) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + self->ClearSaveStuff(); + return 0; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FSavegameManager::DrawSavePic(int x, int y, int w, int h) +{ + if (SavePic == nullptr) return false; + DrawTexture(twod, SavePic, x, y, DTA_DestWidth, w, DTA_DestHeight, h, DTA_Masked, false, TAG_DONE); + return true; +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, DrawSavePic) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + PARAM_INT(x); + PARAM_INT(y); + PARAM_INT(w); + PARAM_INT(h); + ACTION_RETURN_BOOL(self->DrawSavePic(x, y, w, h)); +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::SetFileInfo(int Selected) +{ + if (!SaveGames[Selected]->Filename.IsEmpty()) + { + SaveCommentString.Format("File on disk:\n%s", SaveGames[Selected]->Filename.GetChars()); + } +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, SetFileInfo) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + PARAM_INT(i); + self->SetFileInfo(i); + return 0; +} + + +//============================================================================= +// +// +// +//============================================================================= + +unsigned FSavegameManager::SavegameCount() +{ + return SaveGames.Size(); +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, SavegameCount) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + ACTION_RETURN_INT(self->SavegameCount()); +} + +//============================================================================= +// +// +// +//============================================================================= + +FSaveGameNode *FSavegameManager::GetSavegame(int i) +{ + return SaveGames[i]; +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, GetSavegame) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + PARAM_INT(i); + ACTION_RETURN_POINTER(self->GetSavegame(i)); +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSavegameManager::InsertNewSaveNode() +{ + NewSaveNode.SaveTitle = GStrings["NEWSAVE"]; + NewSaveNode.bNoDelete = true; + SaveGames.Insert(0, &NewSaveNode); +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, InsertNewSaveNode) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + self->InsertNewSaveNode(); + return 0; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FSavegameManager::RemoveNewSaveNode() +{ + if (SaveGames[0] == &NewSaveNode) + { + SaveGames.Delete(0); + return true; + } + return false; +} + +DEFINE_ACTION_FUNCTION(FSavegameManager, RemoveNewSaveNode) +{ + PARAM_SELF_STRUCT_PROLOGUE(FSavegameManager); + ACTION_RETURN_INT(self->RemoveNewSaveNode()); +} + + +FSavegameManager savegameManager; + +DEFINE_ACTION_FUNCTION(FSavegameManager, GetManager) +{ + PARAM_PROLOGUE; + ACTION_RETURN_POINTER(&savegameManager); +} + + + +DEFINE_FIELD(FSaveGameNode, SaveTitle); +DEFINE_FIELD(FSaveGameNode, Filename); +DEFINE_FIELD(FSaveGameNode, bOldVersion); +DEFINE_FIELD(FSaveGameNode, bMissingWads); +DEFINE_FIELD(FSaveGameNode, bNoDelete); + +DEFINE_FIELD(FSavegameManager, WindowSize); +DEFINE_FIELD(FSavegameManager, quickSaveSlot); +DEFINE_FIELD(FSavegameManager, SaveCommentString); + diff --git a/source/core/menu/menu.cpp b/source/core/menu/menu.cpp index b85375ffc..242e69ace 100644 --- a/source/core/menu/menu.cpp +++ b/source/core/menu/menu.cpp @@ -34,252 +34,218 @@ #include "c_dispatch.h" #include "d_gui.h" +#include "c_buttons.h" #include "c_console.h" #include "c_bind.h" -#include "c_cvars.h" -#include "d_event.h" -//#include "i_input.h" -#include "gameconfigfile.h" +#include "d_eventbase.h" +#include "g_input.h" +#include "configfile.h" #include "gstrings.h" #include "menu.h" -#include "textures.h" -#include "c_buttons.h" -#include "v_2ddrawer.h" -#include "printf.h" -#include "v_draw.h" -#include "gamecontrol.h" -#include "pragmas.h" -#include "build.h" -#include "statistics.h" -#include "m_joy.h" -#include "raze_sound.h" -#include "texturemanager.h" +#include "vm.h" #include "v_video.h" +#include "i_system.h" +#include "types.h" +#include "texturemanager.h" +#include "v_draw.h" +#include "vm.h" #include "gamestate.h" +#include "i_interface.h" +#include "menustate.h" +#include "i_time.h" +#include "printf.h" -void RegisterDuke3dMenus(); -void RegisterBloodMenus(); -void RegisterSWMenus(); -void RegisterPSMenus(); -void RegisterLoadsaveMenus(); -void RegisterOptionMenus(); -void RegisterJoystickMenus(); -void UpdateJoystickMenu(IJoystickConfig* joy); -bool help_disabled, credits_disabled; -int g_currentMenu; // accessible by CON scripts - contains the current menu's script ID if defined or INT_MAX if none given. -TArray toDelete; +void M_StartControlPanel(bool makeSound, bool scaleoverride = false); +int DMenu::InMenu; +static ScaleOverrider *CurrentScaleOverrider; +extern int chatmodeon; // // Todo: Move these elsewhere // -EXTERN_CVAR (Bool, show_messages) +CVAR (Int, m_showinputgrid, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, m_blockcontrollers, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) - -CVAR(Bool, menu_sounds, true, CVAR_ARCHIVE) // added mainly because RR's sounds are so supremely annoying. -//CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) the current sound engine cannot deal with this. +CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) CVAR(Int, m_use_mouse, 2, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(Int, m_show_backbutton, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Bool, m_cleanscale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -TArray menuClasses(TArray::ENoInit(0)); +static DMenu *GetCurrentMenu() +{ + return CurrentMenu; +} -DMenu *CurrentMenu; -bool DMenu::InMenu; +DEFINE_ACTION_FUNCTION_NATIVE(DMenu, GetCurrentMenu, GetCurrentMenu) +{ + ACTION_RETURN_OBJECT(CurrentMenu); +} + +static int GetMenuTime() +{ + return MenuTime; +} + +DEFINE_ACTION_FUNCTION_NATIVE(DMenu, MenuTime, GetMenuTime) +{ + ACTION_RETURN_INT(MenuTime); +} -FNewGameStartup NewGameStartupInfo; EMenuState menuactive; -bool M_DemoNoPlay; FButtonStatus MenuButtons[NUM_MKEYS]; int MenuButtonTickers[NUM_MKEYS]; bool MenuButtonOrigin[NUM_MKEYS]; -int BackbuttonTime; +int BackbuttonTime; float BackbuttonAlpha; static bool MenuEnabled = true; +DMenu *CurrentMenu; +int MenuTime; + +extern PClass *DefaultListMenuClass; +extern PClass *DefaultOptionMenuClass; -#define KEY_REPEAT_DELAY (MENU_TICRATE*5/12) +#define KEY_REPEAT_DELAY (GameTicRate*5/12) #define KEY_REPEAT_RATE (3) +bool OkForLocalization(FTextureID texnum, const char* substitute); -static MenuTransition transition; +//============================================================================ +// +// +// +//============================================================================ -bool MenuTransition::StartTransition(DMenu *from, DMenu *to, MenuTransitionType animtype) +IMPLEMENT_CLASS(DMenuDescriptor, false, false) +IMPLEMENT_CLASS(DListMenuDescriptor, false, false) +IMPLEMENT_CLASS(DOptionMenuDescriptor, false, false) + +DMenuDescriptor *GetMenuDescriptor(int name) { - if (!from->canAnimate || !to->canAnimate || animtype == MA_None) - { - return false; - } - else - { - start = I_GetTimeNS() * (120. / 1'000'000'000.); - length = 30; - dir = animtype == MA_Advance? 1 : -1; - previous = from; - current = to; - return true; - } + DMenuDescriptor **desc = MenuDescriptors.CheckKey(ENamedName(name)); + return desc ? *desc : nullptr; } -bool MenuTransition::Draw() +DEFINE_ACTION_FUNCTION_NATIVE(DMenuDescriptor, GetDescriptor, GetMenuDescriptor) { - double now = I_GetTimeNS() * (120. / 1'000'000'000); - if (now < start + length) - { - double factor = 120 * xdim / ydim; - double phase = (now - start) / double(length) * M_PI + M_PI/2; - - previous->origin.X = factor * dir * (sin(phase) - 1.); - current->origin.X = factor * dir * (sin(phase) + 1.); - previous->Drawer(); - current->Drawer(); - return true; - } - return false; + PARAM_PROLOGUE; + PARAM_NAME(name); + ACTION_RETURN_OBJECT(GetMenuDescriptor(name.GetIndex())); } +void DListMenuDescriptor::Reset() +{ + // Reset the default settings (ignore all other values in the struct) + mSelectOfsX = 0; + mSelectOfsY = 0; + mSelector.SetInvalid(); + mDisplayTop = 0; + mXpos = 0; + mYpos = 0; + mLinespacing = 0; + mNetgameMessage = ""; + mFont = NULL; + mFontColor = CR_UNTRANSLATED; + mFontColor2 = CR_UNTRANSLATED; + mFromEngine = false; + mVirtWidth = mVirtHeight = -1; // default to clean scaling +} + +DEFINE_ACTION_FUNCTION(DListMenuDescriptor, Reset) +{ + PARAM_SELF_PROLOGUE(DListMenuDescriptor); + self->Reset(); + return 0; +} + + +size_t DListMenuDescriptor::PropagateMark() +{ + for (auto item : mItems) GC::Mark(item); + return 0; +} + +void DOptionMenuDescriptor::Reset() +{ + // Reset the default settings (ignore all other values in the struct) + mPosition = 0; + mScrollTop = 0; + mIndent = 0; + mDontDim = 0; + mFont = BigUpper; +} + +size_t DOptionMenuDescriptor::PropagateMark() +{ + for (auto item : mItems) GC::Mark(item); + return 0; +} + +void M_MarkMenus() +{ + MenuDescriptorList::Iterator it(MenuDescriptors); + MenuDescriptorList::Pair *pair; + while (it.NextPair(pair)) + { + GC::Mark(pair->Value); + } + GC::Mark(CurrentMenu); +} //============================================================================ // // DMenu base class // //============================================================================ +IMPLEMENT_CLASS(DMenu, false, true) + +IMPLEMENT_POINTERS_START(DMenu) + IMPLEMENT_POINTER(mParentMenu) +IMPLEMENT_POINTERS_END DMenu::DMenu(DMenu *parent) { mParentMenu = parent; mMouseCapture = false; mBackbuttonSelected = false; + DontDim = false; + GC::WriteBarrier(this, parent); } -bool DMenu::Responder (event_t *ev) -{ - bool res = false; +//============================================================================= +// +// +// +//============================================================================= + +bool DMenu::CallResponder(event_t *ev) +{ if (ev->type == EV_GUI_Event) { - if (ev->subtype == EV_GUI_LButtonDown) + IFVIRTUAL(DMenu, OnUIEvent) { - res = MouseEventBack(MOUSE_Click, ev->data1, ev->data2); - // make the menu's mouse handler believe that the current coordinate is outside the valid range - if (res) ev->data2 = -1; - res |= MouseEvent(MOUSE_Click, ev->data1, ev->data2); - if (res) - { - SetCapture(); - } - + FUiEvent e = ev; + VMValue params[] = { (DObject*)this, &e }; + int retval; + VMReturn ret(&retval); + InMenu++; + VMCall(func, params, 2, &ret, 1); + InMenu--; + return !!retval; } - else if (ev->subtype == EV_GUI_MouseMove) - { - BackbuttonTime = BACKBUTTON_TIME; - if (mMouseCapture || m_use_mouse == 1) - { - res = MouseEventBack(MOUSE_Move, ev->data1, ev->data2); - if (res) ev->data2 = -1; - res |= MouseEvent(MOUSE_Move, ev->data1, ev->data2); - } - } - else if (ev->subtype == EV_GUI_LButtonUp) - { - if (mMouseCapture) - { - ReleaseCapture(); - res = MouseEventBack(MOUSE_Release, ev->data1, ev->data2); - if (res) ev->data2 = -1; - res |= MouseEvent(MOUSE_Release, ev->data1, ev->data2); - } - } - } - return false; -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DMenu::MenuEvent (int mkey, bool fromcontroller) -{ - switch (mkey) - { - case MKEY_Back: - { - //if (scriptID != 0) - { - M_MenuSound(CurrentMenu->mParentMenu? BackSound : CloseSound); - Close(); - if (!CurrentMenu && gamestate == GS_MENUSCREEN) C_FullConsole(); - return true; - } - } - } - return false; -} - -//============================================================================= -// -// -// -//============================================================================= - -void DMenu::Close () -{ - assert(CurrentMenu == this); - - CurrentMenu = mParentMenu; - if (mParentMenu && transition.StartTransition(this, mParentMenu, MA_Return)) - { - g_currentMenu = CurrentMenu->scriptID; } else { - Destroy(); - toDelete.Push(this); - if (CurrentMenu == NULL) + IFVIRTUAL(DMenu, OnInputEvent) { - M_ClearMenus(); - } - else - { - g_currentMenu = CurrentMenu->scriptID; - } - } -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DMenu::MouseEvent(int type, int x, int y) -{ - return true; -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DMenu::MouseEventBack(int type, int x, int y) -{ - if (m_show_backbutton >= 0) - { - auto texid = TexMan.CheckForTexture("engine/graphics/m_back.png", ETextureType::Any); - if (texid.isValid()) - { - auto tex = TexMan.GetGameTexture(texid); - if (m_show_backbutton&1) x -= screen->GetWidth() - tex->GetDisplayWidth() * CleanXfac; - if (m_show_backbutton&2) y -= screen->GetHeight() - tex->GetDisplayHeight() * CleanYfac; - mBackbuttonSelected = ( x >= 0 && x < tex->GetDisplayWidth() * CleanXfac && - y >= 0 && y < tex->GetDisplayHeight() * CleanYfac); - if (mBackbuttonSelected && type == MOUSE_Release) - { - if (m_use_mouse == 2) mBackbuttonSelected = false; - MenuEvent(MKEY_Back, true); - } - return mBackbuttonSelected; + FInputEvent e = ev; + VMValue params[] = { (DObject*)this, &e }; + int retval; + VMReturn ret(&retval); + InMenu++; + VMCall(func, params, 2, &ret, 1); + InMenu--; + return !!retval; } } return false; @@ -291,66 +257,110 @@ bool DMenu::MouseEventBack(int type, int x, int y) // //============================================================================= -void DMenu::SetCapture() +bool DMenu::CallMenuEvent(int mkey, bool fromcontroller) { - if (!mMouseCapture) + IFVIRTUAL(DMenu, MenuEvent) { - mMouseCapture = true; - I_SetMouseCapture(); + VMValue params[] = { (DObject*)this, mkey, fromcontroller }; + int retval; + VMReturn ret(&retval); + InMenu++; + VMCall(func, params, 3, &ret, 1); + InMenu--; + return !!retval; } + else return false; } - -void DMenu::ReleaseCapture() -{ - if (mMouseCapture) - { - mMouseCapture = false; - I_ReleaseMouseCapture(); - } -} - //============================================================================= // // // //============================================================================= -void DMenu::Ticker () +static void SetMouseCapture(bool on) { + if (on) I_SetMouseCapture(); + else I_ReleaseMouseCapture(); +} +DEFINE_ACTION_FUNCTION_NATIVE(DMenu, SetMouseCapture, SetMouseCapture) +{ + PARAM_PROLOGUE; + PARAM_BOOL(on); + SetMouseCapture(on); + return 0; } -void DMenu::Drawer () +void DMenu::Close () { - if (this == CurrentMenu && BackbuttonAlpha > 0 && m_show_backbutton >= 0 && m_use_mouse) + if (CurrentMenu == nullptr) return; // double closing can happen in the save menu. + assert(CurrentMenu == this); + CurrentMenu = mParentMenu; + Destroy(); + if (CurrentMenu != nullptr) { - auto texid = TexMan.CheckForTexture("engine/graphics/m_back.png", ETextureType::Any); - if (texid.isValid()) + GC::WriteBarrier(CurrentMenu); + IFVIRTUALPTR(CurrentMenu, DMenu, OnReturn) { - auto tex = TexMan.GetGameTexture(texid); - int w = tex->GetDisplayWidth() * CleanXfac; - int h = tex->GetDisplayHeight() * CleanYfac; - int x = (!(m_show_backbutton & 1)) ? 0 : screen->GetWidth() - w; - int y = (!(m_show_backbutton & 2)) ? 0 : screen->GetHeight() - h; - if (mBackbuttonSelected && (mMouseCapture || m_use_mouse == 1)) - { - DrawTexture(twod, tex, x, y, DTA_CleanNoMove, true, DTA_ColorOverlay, MAKEARGB(40, 255, 255, 255), TAG_DONE); - } - else - { - DrawTexture(twod, tex, x, y, DTA_CleanNoMove, true, DTA_Alpha, BackbuttonAlpha, TAG_DONE); - } + VMValue params[] = { CurrentMenu }; + VMCall(func, params, 1, nullptr, 0); } + + } + else + { + M_ClearMenus (); } } -bool DMenu::DimAllowed() +static void Close(DMenu *menu) { - return true; + menu->Close(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DMenu, Close, Close) +{ + PARAM_SELF_PROLOGUE(DMenu); + self->Close(); + return 0; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMenu::CallTicker() +{ + IFVIRTUAL(DMenu, Ticker) + { + VMValue params[] = { (DObject*)this }; + VMCall(func, params, 1, nullptr, 0); + } +} + + +void DMenu::CallDrawer() +{ + IFVIRTUAL(DMenu, Drawer) + { + VMValue params[] = { (DObject*)this }; + VMCall(func, params, 1, nullptr, 0); + twod->ClearClipRect(); // make sure the scripts don't leave a valid clipping rect behind. + } } bool DMenu::TranslateKeyboardEvents() { + IFVIRTUAL(DMenu, TranslateKeyboardEvents) + { + VMValue params[] = { (DObject*)this }; + int retval; + VMReturn ret(&retval); + VMCall(func, params, countof(params), &ret, 1); + return !!retval; + } return true; } @@ -360,23 +370,13 @@ bool DMenu::TranslateKeyboardEvents() // //============================================================================= -void M_StartControlPanel (bool makeSound) +void M_DoStartControlPanel (bool scaleoverride) { - static bool created = false; // intro might call this repeatedly - if (CurrentMenu != NULL) + if (CurrentMenu != nullptr) return; - if (!created) // Cannot do this earlier. - { - created = true; - M_CreateMenus(); - } - GSnd->SetSfxPaused(true, PAUSESFX_MENU); - gi->MenuOpened(); - if (makeSound) M_MenuSound(ActivateSound); - - inputState.ClearAllInput(); + buttonMap.ResetButtonStates (); for (int i = 0; i < NUM_MKEYS; ++i) { MenuButtons[i].ReleaseKey(0); @@ -384,17 +384,14 @@ void M_StartControlPanel (bool makeSound) C_HideConsole (); // [RH] Make sure console goes bye bye. menuactive = MENU_On; - // Pause sound effects before we play the menu switch sound. - // That way, it won't be paused. - //P_CheckTickerPaused (); - BackbuttonTime = 0; BackbuttonAlpha = 0; -} - -void Menu_Open(int playerid) -{ - M_StartControlPanel(CurrentMenu == nullptr); + if (scaleoverride && !CurrentScaleOverrider) CurrentScaleOverrider = new ScaleOverrider(twod); + else if (!scaleoverride && CurrentScaleOverrider) + { + delete CurrentScaleOverrider; + CurrentScaleOverrider = nullptr; + } } //============================================================================= @@ -405,14 +402,21 @@ void Menu_Open(int playerid) void M_ActivateMenu(DMenu *menu) { - g_currentMenu = menu->scriptID; if (menuactive == MENU_Off) menuactive = MENU_On; - if (CurrentMenu != NULL) + if (CurrentMenu != nullptr && CurrentMenu->mMouseCapture) { - CurrentMenu->ReleaseCapture(); - transition.StartTransition(CurrentMenu, menu, MA_Advance); + CurrentMenu->mMouseCapture = false; + I_ReleaseMouseCapture(); } CurrentMenu = menu; + GC::WriteBarrier(CurrentMenu); +} + +DEFINE_ACTION_FUNCTION(DMenu, ActivateMenu) +{ + PARAM_SELF_PROLOGUE(DMenu); + M_ActivateMenu(self); + return 0; } //============================================================================= @@ -421,216 +425,94 @@ void M_ActivateMenu(DMenu *menu) // //============================================================================= -bool M_SetMenu(FName menu, int param, FName caller) +bool M_SetSpecialMenu(FName& menu, int param); // game specific checks + +void M_SetMenu(FName menu, int param) { -#if 0 - // skip the menu and go right into the first level. - // For tracking memory leaks that normally require operating the menu to start the game so that they always get the same allocation number. - NewGameStartupInfo.Episode = NewGameStartupInfo.Skill = 0; - menu = NAME_Startgame; -#endif - // some menus need some special treatment (needs to be adjusted for the various frontends. - switch (caller.GetIndex()) + if (!M_SetSpecialMenu(menu, param)) return; + + DMenuDescriptor **desc = MenuDescriptors.CheckKey(menu); + if (desc != nullptr) { - case NAME_Episodemenu: - case NAME_TargetMenu: - // sent from the episode menu - NewGameStartupInfo.Episode = param; - NewGameStartupInfo.Level = 0; - NewGameStartupInfo.Skill = gDefaultSkill; - break; - case NAME_WeaponMenu: - NewGameStartupInfo.Skill = param; - break; - - case NAME_Skillmenu: - NewGameStartupInfo.Skill = param; - break; - - case NAME_EngineCredits: - case NAME_EngineCredits2: - { - auto m = CurrentMenu; - CurrentMenu = m->mParentMenu; - m->mParentMenu = nullptr; - toDelete.Push(m); - break; - } - } - - switch (menu.GetIndex()) - { - case NAME_Startgame: - if (caller == NAME_Mainmenu || caller == NAME_IngameMenu) NewGameStartupInfo.Episode = param; - if (gi->StartGame(NewGameStartupInfo)) + if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) { - M_ClearMenus(); - STAT_StartNewGame(gVolumeNames[NewGameStartupInfo.Episode], NewGameStartupInfo.Skill); - inputState.ClearAllInput(); - } - - return false; - - case NAME_CustomSubMenu1: - menu = ENamedName(menu.GetIndex() + param); - break; - -#if 0 - case NAME_StartgameConfirm: - { - // sent from the skill menu for a skill that needs to be confirmed - NewGameStartupInfo.Skill = param; - - const char *msg = AllSkills[param].MustConfirmText; - if (*msg==0) msg = GStrings("NIGHTMARE"); - M_StartMessage (msg, 0, -1, NAME_StartgameConfirmed); - return; - } -#endif - - case NAME_Savegamemenu: - if (!gi->CanSave()) - { - // cannot save outside the game. - M_StartMessage (GStrings("SAVEDEAD"), 1, -1); - return true; - } - break; - - case NAME_Quitmenu: - // This is no separate class - C_DoCommand("menu_quit"); - return true; - - case NAME_EndGameMenu: - // This is no separate class - C_DoCommand("menu_endgame"); - return true; - } - - // End of special checks - - FMenuDescriptor **desc = MenuDescriptors.CheckKey(menu); - if (desc != NULL) - { - /* - if ((*desc)->mNetgameMessage.IsNotEmpty() && netgame && !demoplayback) - { - M_StartMessage((*desc)->mNetgameMessage, 1); - return; - } - */ - - if ((*desc)->mType == MDESC_ListMenu) - { - FListMenuDescriptor *ld = static_cast(*desc); + DListMenuDescriptor *ld = static_cast(*desc); if (ld->mAutoselect >= 0 && ld->mAutoselect < (int)ld->mItems.Size()) { // recursively activate the autoselected item without ever creating this menu. - ld->mItems[ld->mAutoselect]->Activate(ld->mMenuName); + ld->mItems[ld->mAutoselect]->Activate(); } else { - DListMenu* newmenu; - if (ld->mClass != NAME_None) + PClass *cls = ld->mClass; + if (cls == nullptr) cls = DefaultListMenuClass; + if (cls == nullptr) cls = PClass::FindClass("ListMenu"); + + DMenu *newmenu = (DMenu *)cls->CreateNew(); + IFVIRTUALPTRNAME(newmenu, "ListMenu", Init) { - auto ndx = menuClasses.FindEx([=](const auto p) { return p->mName == ld->mClass; }); - if (ndx == menuClasses.Size()) - { - I_Error("Bad menu class %s\n", ld->mClass.GetChars()); - return true; - } - else - { - newmenu = (DListMenu*)menuClasses[ndx]->CreateNew(); - } + VMValue params[3] = { newmenu, CurrentMenu, ld }; + VMCall(func, params, 3, nullptr, 0); } - else - { - newmenu = new DListMenu; - } - newmenu->Init(CurrentMenu, ld); M_ActivateMenu(newmenu); } } - else if ((*desc)->mType == MDESC_OptionsMenu) + else if ((*desc)->IsKindOf(RUNTIME_CLASS(DOptionMenuDescriptor))) { - FOptionMenuDescriptor *ld = static_cast(*desc); - DOptionMenu* newmenu = nullptr; - if (ld->mClass != NAME_None) + DOptionMenuDescriptor *ld = static_cast(*desc); + PClass *cls = ld->mClass; + if (cls == nullptr) cls = DefaultOptionMenuClass; + if (cls == nullptr) cls = PClass::FindClass("OptionMenu"); + + DMenu *newmenu = (DMenu*)cls->CreateNew(); + IFVIRTUALPTRNAME(newmenu, "OptionMenu", Init) { - auto ndx = menuClasses.FindEx([=](const auto p) { return p->mName == ld->mClass; }); - if (ndx == menuClasses.Size()) - { - I_Error("Bad menu class %s\n", ld->mClass.GetChars()); - } - else - { - newmenu = (DOptionMenu*)menuClasses[ndx]->CreateNew(); - } + VMValue params[3] = { newmenu, CurrentMenu, ld }; + VMCall(func, params, 3, nullptr, 0); } - else - { - newmenu = new DOptionMenu; - } - newmenu->Init(CurrentMenu, ld); M_ActivateMenu(newmenu); } - else if ((*desc)->mType == MDESC_ImageScroller) - { - FImageScrollerDescriptor* ld = static_cast(*desc); - DImageScrollerMenu* newmenu; - if (ld->mClass != NAME_None) - { - auto ndx = menuClasses.FindEx([=](const auto p) { return p->mName == ld->mClass; }); - if (ndx == menuClasses.Size()) - { - I_Error("Bad menu class %s\n", ld->mClass.GetChars()); - return true; - } - else - { - newmenu = (DImageScrollerMenu*)menuClasses[ndx]->CreateNew(); - } - } - else - { - newmenu = new DImageScrollerMenu; - } - newmenu->Init(CurrentMenu, ld); - M_ActivateMenu(newmenu); - } - return true; + return; } else { - /* - const PClass *menuclass = PClass::FindClass(menu); - if (menuclass != NULL) + PClass *menuclass = PClass::FindClass(menu); + if (menuclass != nullptr) { - if (menuclass->IsDescendantOf(RUNTIME_CLASS(DMenu))) + if (menuclass->IsDescendantOf("GenericMenu")) { DMenu *newmenu = (DMenu*)menuclass->CreateNew(); - newmenu->mParentMenu = CurrentMenu; + + IFVIRTUALPTRNAME(newmenu, "GenericMenu", Init) + { + VMValue params[3] = { newmenu, CurrentMenu }; + VMCall(func, params, 2, nullptr, 0); + } M_ActivateMenu(newmenu); - return true; + return; } } - */ } Printf("Attempting to open menu of unknown type '%s'\n", menu.GetChars()); M_ClearMenus(); - return false; } +DEFINE_ACTION_FUNCTION(DMenu, SetMenu) +{ + PARAM_PROLOGUE; + PARAM_NAME(menu); + PARAM_INT(mparam); + M_SetMenu(menu, mparam); + return 0; +} //============================================================================= // // // //============================================================================= -bool M_DoResponder (event_t *ev) +bool M_Responder (event_t *ev) { int ch = 0; bool keyup = false; @@ -642,7 +524,7 @@ bool M_DoResponder (event_t *ev) return false; } - if (CurrentMenu != NULL && menuactive != MENU_Off) + if (CurrentMenu != nullptr && menuactive != MENU_Off) { // There are a few input sources we are interested in: // @@ -677,7 +559,7 @@ bool M_DoResponder (event_t *ev) } // pass everything else on to the current menu - return CurrentMenu->Responder(ev); + return CurrentMenu->CallResponder(ev); } else if (CurrentMenu->TranslateKeyboardEvents()) { @@ -698,7 +580,7 @@ bool M_DoResponder (event_t *ev) default: if (!keyup) { - return CurrentMenu->Responder(ev); + return CurrentMenu->CallResponder(ev); } break; } @@ -706,22 +588,28 @@ bool M_DoResponder (event_t *ev) } else if (menuactive != MENU_WaitKey && (ev->type == EV_KeyDown || ev->type == EV_KeyUp)) { + // eat blocked controller events without dispatching them. + if (ev->data1 >= KEY_FIRSTJOYBUTTON && m_blockcontrollers) return true; + keyup = ev->type == EV_KeyUp; ch = ev->data1; switch (ch) { case KEY_JOY1: + case KEY_JOY3: + case KEY_JOY15: case KEY_PAD_A: mkey = MKEY_Enter; break; case KEY_JOY2: + case KEY_JOY14: case KEY_PAD_B: mkey = MKEY_Back; break; - case KEY_JOY3: + case KEY_JOY4: case KEY_PAD_X: mkey = MKEY_Clear; break; @@ -738,28 +626,28 @@ bool M_DoResponder (event_t *ev) case KEY_PAD_DPAD_UP: case KEY_PAD_LTHUMB_UP: - case KEY_JOYAXIS1MINUS: + case KEY_JOYAXIS2MINUS: case KEY_JOYPOV1_UP: mkey = MKEY_Up; break; case KEY_PAD_DPAD_DOWN: case KEY_PAD_LTHUMB_DOWN: - case KEY_JOYAXIS1PLUS: + case KEY_JOYAXIS2PLUS: case KEY_JOYPOV1_DOWN: mkey = MKEY_Down; break; case KEY_PAD_DPAD_LEFT: case KEY_PAD_LTHUMB_LEFT: - case KEY_JOYAXIS2MINUS: + case KEY_JOYAXIS1MINUS: case KEY_JOYPOV1_LEFT: mkey = MKEY_Left; break; case KEY_PAD_DPAD_RIGHT: case KEY_PAD_LTHUMB_RIGHT: - case KEY_JOYAXIS2PLUS: + case KEY_JOYAXIS1PLUS: case KEY_JOYPOV1_RIGHT: mkey = MKEY_Right; break; @@ -781,31 +669,27 @@ bool M_DoResponder (event_t *ev) { MenuButtonTickers[mkey] = KEY_REPEAT_DELAY; } - CurrentMenu->MenuEvent(mkey, fromcontroller); + CurrentMenu->CallMenuEvent(mkey, fromcontroller); return true; } } - return CurrentMenu->Responder(ev) || !keyup; + return CurrentMenu->CallResponder(ev) || !keyup; } else if (MenuEnabled) { if (ev->type == EV_KeyDown) { // Pop-up menu? - if (ev->data1 == KEY_ESCAPE) // Should we let the games handle Escape for special actions, like backing out of cameras? + if (ev->data1 == KEY_ESCAPE) { - if (gamestate != GS_STARTUP && gamestate != GS_INTRO) - { - M_StartControlPanel(true); - M_SetMenu(gi->CanSave()? NAME_IngameMenu : NAME_Mainmenu, -1); - if (gamestate == GS_FULLCONSOLE) gamestate = GS_MENUSCREEN; - } + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); return true; } return false; } else if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_LButtonDown && - ConsoleState != c_down && m_use_mouse) + ConsoleState != c_down && gamestate != GS_LEVEL && m_use_mouse) { M_StartControlPanel(true); M_SetMenu(NAME_Mainmenu, -1); @@ -815,25 +699,6 @@ bool M_DoResponder (event_t *ev) return false; } -bool M_Responder(event_t* ev) -{ - bool res = false; - // delayed deletion, so that self-deleting menus don't crash if they are getting accessed after being closed. - try - { - res = M_DoResponder(ev); - } - catch (...) - { - for (auto p : toDelete) delete p; - toDelete.Clear(); - throw; - } - for (auto p : toDelete) delete p; - toDelete.Clear(); - return res; -} - //============================================================================= // // @@ -842,12 +707,15 @@ bool M_Responder(event_t* ev) void M_Ticker (void) { - if (CurrentMenu != NULL && menuactive != MENU_Off) + MenuTime++; + if (CurrentMenu != nullptr && menuactive != MENU_Off) { - if (transition.previous) transition.previous->Ticker(); - if (CurrentMenu == nullptr) return; // In case one of the sub-screens has closed the menu. - CurrentMenu->Ticker(); + CurrentMenu->CallTicker(); + } + // Check again because menu could be closed from Ticker() + if (CurrentMenu != nullptr && menuactive != MENU_Off) + { for (int i = 0; i < NUM_MKEYS; ++i) { if (MenuButtons[i].bDown) @@ -855,18 +723,19 @@ void M_Ticker (void) if (MenuButtonTickers[i] > 0 && --MenuButtonTickers[i] <= 0) { MenuButtonTickers[i] = KEY_REPEAT_RATE; - CurrentMenu->MenuEvent(i, MenuButtonOrigin[i]); + CurrentMenu->CallMenuEvent(i, MenuButtonOrigin[i]); } } } if (BackbuttonTime > 0) { - if (BackbuttonAlpha < 1.0f) BackbuttonAlpha += 0.1f; + if (BackbuttonAlpha < 1.f) BackbuttonAlpha += .1f; + if (BackbuttonAlpha > 1.f) BackbuttonAlpha = 1.f; BackbuttonTime--; } else { - if (BackbuttonAlpha > 0) BackbuttonAlpha -= 0.1f; + if (BackbuttonAlpha > 0) BackbuttonAlpha -= .1f; if (BackbuttonAlpha < 0) BackbuttonAlpha = 0; } } @@ -880,29 +749,16 @@ void M_Ticker (void) void M_Drawer (void) { - PalEntry fade = 0x70000000; + PalEntry fade = 0; - if (CurrentMenu != NULL && menuactive != MENU_Off) + if (CurrentMenu != nullptr && menuactive != MENU_Off) { - if (CurrentMenu->DimAllowed() && fade && gamestate != GS_MENUSCREEN) twod->AddColorOnlyQuad(0, 0, screen->GetWidth(), screen->GetHeight(), fade); - - bool going = false; - if (transition.previous) + if (!CurrentMenu->DontBlur) screen->BlurScene(menuBlurAmount); + if (!CurrentMenu->DontDim) { - going = transition.Draw(); - if (!going) - { - if (transition.dir == -1) delete transition.previous; - transition.previous = nullptr; - transition.current = nullptr; - } - } - if (!going) - { - assert(CurrentMenu); - CurrentMenu->origin = { 0,0 }; - CurrentMenu->Drawer(); + if (sysCallbacks.MenuDim) sysCallbacks.MenuDim(); } + CurrentMenu->CallDrawer(); } } @@ -912,49 +768,17 @@ void M_Drawer (void) // //============================================================================= -void M_UnpauseSound() +void M_ClearMenus() { - GSnd->SetSfxPaused(false, PAUSESFX_MENU); -} - -void M_ClearMenus (bool final) -{ - if (menuactive == MENU_Off) return; - M_DemoNoPlay = false; - transition.previous = transition.current = nullptr; - transition.dir = 0; - auto menu = CurrentMenu; - while (menu != nullptr) + while (CurrentMenu != nullptr) { - auto nextm = menu->mParentMenu; - menu->Destroy(); - delete menu; - menu = nextm; + DMenu* parent = CurrentMenu->mParentMenu; + CurrentMenu->Destroy(); + CurrentMenu = parent; } - CurrentMenu = nullptr; menuactive = MENU_Off; - M_UnpauseSound(); - inputState.ClearAllInput(); - if (!final) - { - gi->MenuClosed(); - } -} - -bool M_Active() -{ - return CurrentMenu != nullptr || ConsoleState == c_down || ConsoleState == c_falling; -} - -//============================================================================= -// -// -// -//============================================================================= - -void M_MenuSound(EMenuSounds snd) -{ - if (menu_sounds) gi->MenuSound(snd); + if (CurrentScaleOverrider) delete CurrentScaleOverrider; + CurrentScaleOverrider = nullptr; } //============================================================================= @@ -981,15 +805,18 @@ void M_PreviousMenu() void M_Init (void) { - RegisterDuke3dMenus(); - RegisterBloodMenus(); - RegisterSWMenus(); - RegisterPSMenus(); - RegisterLoadsaveMenus(); - RegisterOptionMenus(); - RegisterJoystickMenus(); - M_ParseMenuDefs(); - UpdateJoystickMenu(nullptr); + try + { + M_ParseMenuDefs(); + GC::AddMarkerFunc(M_MarkMenus); + } + catch (CVMAbortException &err) + { + err.MaybePrintMessage(); + Printf("%s", err.stacktrace.GetChars()); + I_FatalError("Failed to initialize menus"); + } + M_CreateMenus(); } @@ -1005,16 +832,6 @@ void M_EnableMenu (bool on) } -bool M_IsAnimated() -{ - if (ConsoleState == c_down) return false; - if (!CurrentMenu) return false; - if (CurrentMenu->IsAnimated()) return true; - if(transition.previous) return true; - return false; -} - - //============================================================================= // // [RH] Most menus can now be accessed directly @@ -1022,12 +839,139 @@ bool M_IsAnimated() // //============================================================================= +CCMD (menu_main) +{ + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); +} + +CCMD (menu_load) +{ // F3 + M_StartControlPanel (true); + M_SetMenu(NAME_Loadgamemenu, -1); +} + +CCMD (menu_save) +{ // F2 + M_StartControlPanel (true); + M_SetMenu(NAME_Savegamemenu, -1); +} + +CCMD (menu_help) +{ // F1 + M_StartControlPanel (true); + M_SetMenu(NAME_Readthismenu, -1); +} + +CCMD (menu_game) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Playerclassmenu, -1); // The playerclass menu is the first in the 'start game' chain +} + +CCMD (menu_options) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Optionsmenu, -1); +} + +CCMD (menu_player) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Playermenu, -1); +} + +CCMD (menu_messages) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MessageOptions, -1); +} + +CCMD (menu_automap) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_AutomapOptions, -1); +} + +CCMD (menu_scoreboard) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_ScoreboardOptions, -1); +} + +CCMD (menu_mapcolors) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MapColorMenu, -1); +} + +CCMD (menu_keys) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_CustomizeControls, -1); +} + +CCMD (menu_gameplay) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_GameplayOptions, -1); +} + +CCMD (menu_compatibility) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_CompatibilityOptions, -1); +} + +CCMD (menu_mouse) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MouseOptions, -1); +} + +CCMD (menu_joystick) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_JoystickOptions, -1); +} + +CCMD (menu_sound) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_SoundOptions, -1); +} + +CCMD (menu_advsound) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_AdvSoundOptions, -1); +} + +CCMD (menu_modreplayer) +{ + M_StartControlPanel(true); + M_SetMenu(NAME_ModReplayerOptions, -1); +} + +CCMD (menu_display) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_VideoOptions, -1); +} + +CCMD (menu_video) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_VideoModeMenu, -1); +} + + CCMD (openmenu) { if (argv.argc() < 2) { - Printf("Usage: openmenu \"menu_name\""); + Printf("Usage: openmenu \"menu_name\"\n"); return; } M_StartControlPanel (true); @@ -1039,7 +983,10 @@ CCMD (closemenu) M_ClearMenus(); } -EXTERN_CVAR (Int, screenblocks) +CCMD (prevmenu) +{ + M_PreviousMenu(); +} CCMD(menuconsole) { @@ -1047,45 +994,229 @@ CCMD(menuconsole) C_ToggleConsole(); } -CCMD(reset2defaults) +// This really should be in the script but we can't do scripted CCMDs yet. +CCMD(undocolorpic) { - C_SetDefaultBindings (); - C_SetCVarsToDefaults (); -} - -CCMD(reset2saved) -{ - GameConfig->DoGlobalSetup (); - GameConfig->DoGameSetup (currentGame); -} - -CCMD(menu_main) -{ - M_StartControlPanel(true); - M_SetMenu(gi->CanSave() ? NAME_IngameMenu : NAME_Mainmenu, -1); -} - -CCMD(openhelpmenu) -{ - if (!help_disabled) + if (CurrentMenu != NULL) { - M_StartControlPanel(true); - M_SetMenu(NAME_HelpMenu); + IFVIRTUALPTR(CurrentMenu, DMenu, ResetColor) + { + VMValue params[] = { (DObject*)CurrentMenu }; + VMCall(func, params, countof(params), nullptr, 0); + } } } -CCMD(opensavemenu) + +DEFINE_GLOBAL(menuactive) +DEFINE_GLOBAL(BackbuttonTime) +DEFINE_GLOBAL(BackbuttonAlpha) + +DEFINE_FIELD(DMenu, mParentMenu) +DEFINE_FIELD(DMenu, mMouseCapture); +DEFINE_FIELD(DMenu, mBackbuttonSelected); +DEFINE_FIELD(DMenu, DontDim); +DEFINE_FIELD(DMenu, DontBlur); + +DEFINE_FIELD(DMenuDescriptor, mMenuName) +DEFINE_FIELD(DMenuDescriptor, mNetgameMessage) +DEFINE_FIELD(DMenuDescriptor, mClass) + +DEFINE_FIELD(DMenuItemBase, mXpos) +DEFINE_FIELD(DMenuItemBase, mYpos) +DEFINE_FIELD(DMenuItemBase, mAction) +DEFINE_FIELD(DMenuItemBase, mEnabled) + +DEFINE_FIELD(DListMenuDescriptor, mItems) +DEFINE_FIELD(DListMenuDescriptor, mSelectedItem) +DEFINE_FIELD(DListMenuDescriptor, mSelectOfsX) +DEFINE_FIELD(DListMenuDescriptor, mSelectOfsY) +DEFINE_FIELD(DListMenuDescriptor, mSelector) +DEFINE_FIELD(DListMenuDescriptor, mDisplayTop) +DEFINE_FIELD(DListMenuDescriptor, mXpos) +DEFINE_FIELD(DListMenuDescriptor, mYpos) +DEFINE_FIELD(DListMenuDescriptor, mWLeft) +DEFINE_FIELD(DListMenuDescriptor, mWRight) +DEFINE_FIELD(DListMenuDescriptor, mLinespacing) +DEFINE_FIELD(DListMenuDescriptor, mAutoselect) +DEFINE_FIELD(DListMenuDescriptor, mFont) +DEFINE_FIELD(DListMenuDescriptor, mFontColor) +DEFINE_FIELD(DListMenuDescriptor, mFontColor2) +DEFINE_FIELD(DListMenuDescriptor, mCenter) +DEFINE_FIELD(DListMenuDescriptor, mVirtWidth) +DEFINE_FIELD(DListMenuDescriptor, mVirtHeight) + +DEFINE_FIELD(DOptionMenuDescriptor, mItems) +DEFINE_FIELD(DOptionMenuDescriptor, mTitle) +DEFINE_FIELD(DOptionMenuDescriptor, mSelectedItem) +DEFINE_FIELD(DOptionMenuDescriptor, mDrawTop) +DEFINE_FIELD(DOptionMenuDescriptor, mScrollTop) +DEFINE_FIELD(DOptionMenuDescriptor, mScrollPos) +DEFINE_FIELD(DOptionMenuDescriptor, mIndent) +DEFINE_FIELD(DOptionMenuDescriptor, mPosition) +DEFINE_FIELD(DOptionMenuDescriptor, mDontDim) +DEFINE_FIELD(DOptionMenuDescriptor, mFont) + +DEFINE_FIELD(FOptionMenuSettings, mTitleColor) +DEFINE_FIELD(FOptionMenuSettings, mFontColor) +DEFINE_FIELD(FOptionMenuSettings, mFontColorValue) +DEFINE_FIELD(FOptionMenuSettings, mFontColorMore) +DEFINE_FIELD(FOptionMenuSettings, mFontColorHeader) +DEFINE_FIELD(FOptionMenuSettings, mFontColorHighlight) +DEFINE_FIELD(FOptionMenuSettings, mFontColorSelection) +DEFINE_FIELD(FOptionMenuSettings, mLinespacing) + + +struct IJoystickConfig; +// These functions are used by dynamic menu creation. +DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, int v) { - if (gi->CanSave()) + auto c = PClass::FindClass("OptionMenuItemStaticText"); + auto p = c->CreateNew(); + FString namestr = name; + VMValue params[] = { p, &namestr, v }; + auto f = dyn_cast(c->FindSymbol("Init", false)); + VMCall(f->Variants[0].Implementation, params, countof(params), nullptr, 0); + return (DMenuItemBase*)p; +} + +DMenuItemBase * CreateOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy) +{ + auto c = PClass::FindClass("OptionMenuItemJoyConfigMenu"); + auto p = c->CreateNew(); + FString namestr = label; + VMValue params[] = { p, &namestr, joy }; + auto f = dyn_cast(c->FindSymbol("Init", false)); + VMCall(f->Variants[0].Implementation, params, countof(params), nullptr, 0); + return (DMenuItemBase*)p; +} + +DMenuItemBase * CreateOptionMenuItemSubmenu(const char *label, FName cmd, int center) +{ + auto c = PClass::FindClass("OptionMenuItemSubmenu"); + auto p = c->CreateNew(); + FString namestr = label; + VMValue params[] = { p, &namestr, cmd.GetIndex(), center, false }; + auto f = dyn_cast(c->FindSymbol("Init", false)); + VMCall(f->Variants[0].Implementation, params, countof(params), nullptr, 0); + return (DMenuItemBase*)p; +} + +DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBindings *bindings) +{ + auto c = PClass::FindClass("OptionMenuItemControlBase"); + auto p = c->CreateNew(); + FString namestr = label; + VMValue params[] = { p, &namestr, cmd.GetIndex(), bindings }; + auto f = dyn_cast(c->FindSymbol("Init", false)); + VMCall(f->Variants[0].Implementation, params, countof(params), nullptr, 0); + return (DMenuItemBase*)p; +} + +DMenuItemBase * CreateOptionMenuItemCommand(const char *label, FName cmd, bool centered) +{ + auto c = PClass::FindClass("OptionMenuItemCommand"); + auto p = c->CreateNew(); + FString namestr = label; + VMValue params[] = { p, &namestr, cmd.GetIndex(), centered, false }; + auto f = dyn_cast(c->FindSymbol("Init", false)); + VMCall(f->Variants[0].Implementation, params, countof(params), nullptr, 0); + auto unsafe = dyn_cast(c->FindSymbol("mUnsafe", false)); + unsafe->Type->SetValue(reinterpret_cast(p) + unsafe->Offset, 0); + return (DMenuItemBase*)p; +} + +DMenuItemBase * CreateListMenuItemPatch(double x, double y, int height, int hotkey, FTextureID tex, FName command, int param) +{ + auto c = PClass::FindClass("ListMenuItemPatchItem"); + auto p = c->CreateNew(); + FString keystr = FString(char(hotkey)); + VMValue params[] = { p, x, y, height, tex.GetIndex(), &keystr, command.GetIndex(), param }; + auto f = dyn_cast(c->FindSymbol("InitDirect", false)); + VMCall(f->Variants[0].Implementation, params, countof(params), nullptr, 0); + return (DMenuItemBase*)p; +} + +DMenuItemBase * CreateListMenuItemText(double x, double y, int height, int hotkey, const char *text, FFont *font, PalEntry color1, PalEntry color2, FName command, int param) +{ + auto c = PClass::FindClass("ListMenuItemTextItem"); + auto p = c->CreateNew(); + FString keystr = FString(char(hotkey)); + FString textstr = text; + VMValue params[] = { p, x, y, height, &keystr, &textstr, font, int(color1.d), int(color2.d), command.GetIndex(), param }; + auto f = dyn_cast(c->FindSymbol("InitDirect", false)); + VMCall(f->Variants[0].Implementation, params, countof(params), nullptr, 0); + return (DMenuItemBase*)p; +} + +bool DMenuItemBase::Activate() +{ + IFVIRTUAL(DMenuItemBase, Activate) { - M_StartControlPanel(true); - M_SetMenu(NAME_Savegamemenu); + VMValue params[] = { (DObject*)this }; + int retval; + VMReturn ret(&retval); + VMCall(func, params, countof(params), &ret, 1); + return !!retval; } + return false; } -CCMD(openloadmenu) +bool DMenuItemBase::SetString(int i, const char *s) { - M_StartControlPanel(true); - M_SetMenu(NAME_Loadgamemenu); + IFVIRTUAL(DMenuItemBase, SetString) + { + FString namestr = s; + VMValue params[] = { (DObject*)this, i, &namestr }; + int retval; + VMReturn ret(&retval); + VMCall(func, params, countof(params), &ret, 1); + return !!retval; + } + return false; } +bool DMenuItemBase::GetString(int i, char *s, int len) +{ + IFVIRTUAL(DMenuItemBase, GetString) + { + VMValue params[] = { (DObject*)this, i }; + int retval; + FString retstr; + VMReturn ret[2]; ret[0].IntAt(&retval); ret[1].StringAt(&retstr); + VMCall(func, params, countof(params), ret, 2); + strncpy(s, retstr, len); + return !!retval; + } + return false; +} + + +bool DMenuItemBase::SetValue(int i, int value) +{ + IFVIRTUAL(DMenuItemBase, SetValue) + { + VMValue params[] = { (DObject*)this, i, value }; + int retval; + VMReturn ret(&retval); + VMCall(func, params, countof(params), &ret, 1); + return !!retval; + } + return false; +} + +bool DMenuItemBase::GetValue(int i, int *pvalue) +{ + IFVIRTUAL(DMenuItemBase, GetValue) + { + VMValue params[] = { (DObject*)this, i }; + int retval[2]; + VMReturn ret[2]; ret[0].IntAt(&retval[0]); ret[1].IntAt(&retval[1]); + VMCall(func, params, countof(params), ret, 2); + *pvalue = retval[1]; + return !!retval[0]; + } + return false; +} + +IMPLEMENT_CLASS(DMenuItemBase, false, false) diff --git a/source/core/menu/menu.h b/source/core/menu/menu.h index 453ebe885..1cde8d089 100644 --- a/source/core/menu/menu.h +++ b/source/core/menu/menu.h @@ -4,108 +4,21 @@ -#include "v_font.h" +#include "dobject.h" #include "c_cvars.h" -#include "version.h" +#include "v_font.h" #include "textures.h" -#include "zstring.h" -#include "v_draw.h" -#include "menustate.h" -#include "gamestruct.h" EXTERN_CVAR(Float, snd_menuvolume) EXTERN_CVAR(Int, m_use_mouse); -enum EMax -{ - MAXSKILLS = 7, - MAXVOLUMES = 7, - MAXMENUGAMEPLAYENTRIES = 7, -}; - -// These get filled in by the map definition parsers of the front ends. -extern FString gSkillNames[MAXSKILLS]; -extern FString gVolumeNames[MAXVOLUMES]; -extern FString gVolumeSubtitles[MAXVOLUMES]; -extern int32_t gVolumeFlags[MAXVOLUMES]; -extern int gDefaultVolume, gDefaultSkill; - -const int MENU_TICRATE = 30; -extern bool help_disabled, credits_disabled; -extern int g_currentMenu; - -enum MenuTransitionType -{ // Note: This enum is for logical categories, not visual types. - MA_None, - MA_Return, - MA_Advance, -}; - -class DMenu; - -struct MenuTransition -{ - DMenu* previous; - DMenu* current; - - double start; - int32_t length; - int32_t dir; - - bool StartTransition(DMenu* from, DMenu* to, MenuTransitionType animtype); - bool Draw(); - -}; - -enum -{ - EF_HIDEFROMSP = 1 << 0, -}; - -enum MenuGameplayEntryFlags -{ - MGE_Locked = 1u << 0u, - MGE_Hidden = 1u << 1u, - MGE_UserContent = 1u << 2u, -}; - -typedef struct MenuGameplayEntry -{ - char name[64]; - uint8_t flags; - - bool isValid() const { return name[0] != '\0'; } -} MenuGameplayEntry; - -typedef struct MenuGameplayStemEntry -{ - MenuGameplayEntry entry; - MenuGameplayEntry subentries[MAXMENUGAMEPLAYENTRIES]; -} MenuGameplayStemEntry; - -extern MenuGameplayStemEntry g_MenuGameplayEntries[MAXMENUGAMEPLAYENTRIES]; - - -enum EMenuSounds : int -{ - ActivateSound, - CursorSound, - AdvanceSound, - BackSound, - CloseSound, - PageSound, - ChangeSound, - ChooseSound -}; - -EXTERN_CVAR(Bool, menu_sounds) struct event_t; -class FGameTexture; +class FTexture; class FFont; enum EColorRange : int; -class FPlayerClass; class FKeyBindings; +struct FBrokenLines; enum EMenuKey { @@ -129,24 +42,11 @@ enum EMenuKey MKEY_MBNo, }; -enum ENativeFontValues -{ - NIT_BigFont, - NIT_SmallFont, - - NIT_ActiveColor = -1, - NIT_InactiveColor = -2, - NIT_SelectedColor = -3, - - NIT_ActiveState = 1, - NIT_InactiveState = 2, - NIT_SelectedState = 3 - // positive values for color are direct palswap indices. -}; - -extern FNewGameStartup NewGameStartupInfo; -extern EMenuState menuactive; +class DMenu; +extern DMenu *CurrentMenu; +extern int MenuTime; +class DMenuItemBase; //============================================================================= // @@ -155,63 +55,44 @@ extern EMenuState menuactive; // //============================================================================= -enum EMenuDescriptorType +class DMenuDescriptor : public DObject { - MDESC_ListMenu, - MDESC_OptionsMenu, - MDESC_ImageScroller, -}; - -struct FMenuDescriptor -{ - FName mMenuName; + DECLARE_CLASS(DMenuDescriptor, DObject) +public: + FName mMenuName = NAME_None; FString mNetgameMessage; - int mType; - FName mClass; + PClass *mClass = nullptr; + bool mProtected = false; + TArray mItems; - virtual ~FMenuDescriptor() {} + virtual size_t PropagateMark() { return 0; } }; -class FListMenuItem; -class FOptionMenuItem; -enum ListMenuFlags +class DListMenuDescriptor : public DMenuDescriptor { - LMF_Centered = 1, - LMF_DontSpace = 2, - LMF_Animate = 4, -}; + DECLARE_CLASS(DListMenuDescriptor, DMenuDescriptor) -struct FListMenuDescriptor : public FMenuDescriptor -{ - TDeletingArray mItems; - FString mCaption; +public: int mSelectedItem; - int mSelectOfsX; - int mSelectOfsY; - FGameTexture *mSelector; + double mSelectOfsX; + double mSelectOfsY; + FTextureID mSelector; int mDisplayTop; - int mXpos, mYpos, mYbotton; + double mXpos, mYpos; int mWLeft, mWRight; int mLinespacing; // needs to be stored for dynamically created menus int mAutoselect; // this can only be set by internal menu creation functions - int mScriptId; - int mSecondaryId; - int mNativeFontNum, mNativePalNum; - float mNativeFontScale; FFont *mFont; EColorRange mFontColor; EColorRange mFontColor2; - FMenuDescriptor *mRedirect; // used to redirect overlong skill and episode menus to option menu based alternatives - int mFlags; - int mSpacing; - - FListMenuDescriptor() - { - Reset(); - } + bool mCenter; + bool mFromEngine; + int mVirtWidth; + int mVirtHeight; void Reset(); + size_t PropagateMark() override; }; struct FOptionMenuSettings @@ -226,9 +107,11 @@ struct FOptionMenuSettings int mLinespacing; }; -struct FOptionMenuDescriptor : public FMenuDescriptor +class DOptionMenuDescriptor : public DMenuDescriptor { - TDeletingArray mItems; + DECLARE_CLASS(DOptionMenuDescriptor, DMenuDescriptor) + +public: FString mTitle; int mSelectedItem; int mDrawTop; @@ -237,36 +120,17 @@ struct FOptionMenuDescriptor : public FMenuDescriptor int mIndent; int mPosition; bool mDontDim; + FFont *mFont; void CalcIndent(); - FOptionMenuItem *GetItem(FName name); - void Reset() - { - // Reset the default settings (ignore all other values in the struct) - mPosition = 0; - mScrollTop = 0; - mIndent = 0; - mDontDim = 0; - } - + DMenuItemBase *GetItem(FName name); + void Reset(); + size_t PropagateMark() override; + ~DOptionMenuDescriptor() = default; }; + -struct FImageScrollerDescriptor : public FMenuDescriptor -{ - struct ScrollerItem - { - int type; // 0: fullscreen image; 1: centered text - int scriptID; - FString text; - }; - int mFlags = 0; - - TArray mItems; -}; - - - -typedef TMap MenuDescriptorList; +typedef TMap MenuDescriptorList; extern FOptionMenuSettings OptionSettings; extern MenuDescriptorList MenuDescriptors; @@ -300,11 +164,12 @@ struct FMenuRect }; -class DMenu +class DMenu : public DObject { -protected: - bool mMouseCapture; - bool mBackbuttonSelected; + DECLARE_CLASS (DMenu, DObject) + HAS_OBJECT_POINTERS + + public: enum @@ -314,41 +179,21 @@ public: MOUSE_Release }; - enum - { - BACKBUTTON_TIME = 4*MENU_TICRATE - }; - - static bool InMenu; - - DMenu *mParentMenu; - DVector2 origin = { 0,0 }; - int scriptID = INT_MAX; - bool canAnimate = false; - bool isAnimated = false; // set to true when uncapped frame rate is needed. + TObjPtr mParentMenu; + bool mMouseCapture; + bool mBackbuttonSelected; + bool DontDim; + bool DontBlur; + static int InMenu; DMenu(DMenu *parent = NULL); - virtual ~DMenu() = default; - virtual bool Responder (event_t *ev); - virtual bool MenuEvent (int mkey, bool fromcontroller); - virtual void Ticker (); - virtual void PreDraw() {} - virtual void PostDraw() {} - virtual void Drawer (); - virtual bool DimAllowed (); - virtual bool TranslateKeyboardEvents(); + bool TranslateKeyboardEvents(); virtual void Close(); - virtual bool MouseEvent(int type, int x, int y); - virtual void Destroy() {} - bool IsAnimated() const { return isAnimated; } - bool MouseEventBack(int type, int x, int y); - void SetCapture(); - void ReleaseCapture(); - void SetOrigin(); - bool HasCapture() - { - return mMouseCapture; - } + + bool CallResponder(event_t *ev); + bool CallMenuEvent(int mkey, bool fromcontroller); + void CallTicker(); + void CallDrawer(); }; //============================================================================= @@ -357,229 +202,21 @@ public: // //============================================================================= -class DListMenu; - -class FListMenuItem +class DMenuItemBase : public DObject { -protected: - int mXpos, mYpos; - int mHeight; + DECLARE_CLASS(DMenuItemBase, DObject) +public: + double mXpos, mYpos; FName mAction; + bool mEnabled; -public: - bool mEnabled, mHidden; - - FListMenuItem(int xpos = 0, int ypos = 0, FName action = NAME_None) - { - mXpos = xpos; - mYpos = ypos; - mAction = action; - mEnabled = true; - mHidden = false; - } - - virtual ~FListMenuItem(); - - virtual bool CheckCoordinate(int x, int y); - virtual void Ticker(); - virtual void Drawer(DListMenu *menu, const DVector2& origin, bool selected); - virtual bool Selectable(); - virtual bool Activate(FName caller); - virtual FName GetAction(int *pparam); - virtual bool SetString(int i, const char *s); - virtual bool GetString(int i, char *s, int len); - virtual bool SetValue(int i, int value); - virtual bool GetValue(int i, int *pvalue); - virtual void Enable(bool on); - virtual bool MenuEvent (int mkey, bool fromcontroller); - virtual bool MouseEvent(int type, int x, int y); - virtual bool CheckHotkey(int c); - virtual int GetWidth(); - virtual void DrawSelector(int xofs, int yofs, FGameTexture *tex); + bool Activate(); + bool SetString(int i, const char *s); + bool GetString(int i, char *s, int len); + bool SetValue(int i, int value); + bool GetValue(int i, int *pvalue); void OffsetPositionY(int ydelta) { mYpos += ydelta; } - int GetY() { return mYpos; } - int GetX() { return mXpos; } - void SetX(int x) { mXpos = x; } - void SetY(int x) { mYpos = x; } - void SetHeight(int x) { mHeight = x; } - void SetAction(FName action) { mAction = action; } -}; - -class FListMenuItemStaticPatch : public FListMenuItem -{ -protected: - FGameTexture *mTexture; - bool mCentered; - -public: - FListMenuItemStaticPatch(int x, int y, FGameTexture *patch, bool centered); - void Drawer(DListMenu* menu, const DVector2& origin, bool selected); -}; - -class FListMenuItemStaticText : public FListMenuItem -{ -protected: - const char *mText; - FFont *mFont; - EColorRange mColor; - bool mCentered; - -public: - FListMenuItemStaticText(int x, int y, const char *text, FFont *font, EColorRange color, bool centered); - ~FListMenuItemStaticText(); - void Drawer(DListMenu* menu, const DVector2& origin, bool selected) override; -}; - -class FListMenuItemNativeStaticText : public FListMenuItem -{ -protected: - FString mText; - int mFontnum; - int mPalnum; - bool mCentered; - -public: - FListMenuItemNativeStaticText(int x, int y, const FString & text, int fontnum, int palnum, bool centered); - void Drawer(DListMenu* menu, const DVector2& origin, bool selected) override; -}; - -//============================================================================= -// -// selectable items -// -//============================================================================= - -class FListMenuItemSelectable : public FListMenuItem -{ -protected: - int mHotkey; - int mParam; - -public: - FListMenuItemSelectable(int x, int y, int height, FName childmenu, int mParam = -1); - bool CheckCoordinate(int x, int y) override; - bool Selectable() override; - bool CheckHotkey(int c) override; - bool Activate(FName caller) override; - bool MouseEvent(int type, int x, int y) override; - FName GetAction(int *pparam) override; -}; - -class FListMenuItemText : public FListMenuItemSelectable -{ - FString mText; - FFont *mFont; - EColorRange mColor; - EColorRange mColorSelected; -public: - FListMenuItemText(int x, int y, int height, int hotkey, const FString &text, FFont *font, EColorRange color, EColorRange color2, FName child, int param = 0); - ~FListMenuItemText(); - void Drawer(DListMenu* menu, const DVector2& origin, bool selected) override; - int GetWidth() override; -}; - -class FListMenuItemNativeText : public FListMenuItemSelectable -{ - // This draws the item with the game frontend's native text drawer and uses a front end defined font, it takes only symbolic constants as parameters. - FString mText; - int mFontnum; - int mPalnum; - float mFontscale; -public: - FListMenuItemNativeText(int x, int y, int height, int hotkey, const FString& text, int fontnum, int palnum, float fontscale, FName child, int param = 0); - ~FListMenuItemNativeText(); - void Drawer(DListMenu* menu, const DVector2& origin, bool selected) override; - int GetWidth() override; - void DrawSelector(int xofs, int yofs, FGameTexture* tex) override { } // The text drawer handles this itself. -}; - - -class FListMenuItemPatch : public FListMenuItemSelectable -{ - FGameTexture* mTexture; -public: - FListMenuItemPatch(int x, int y, int height, int hotkey, FGameTexture* patch, FName child, int param = 0); - void Drawer(DListMenu* menu, const DVector2& origin, bool selected) override; - int GetWidth() override; -}; - -//============================================================================= -// -// list menu class runs a menu described by a FListMenuDescriptor -// -//============================================================================= - -class DListMenu : public DMenu -{ - typedef DMenu Super; -protected: - FListMenuDescriptor *mDesc = nullptr; - FListMenuItem *mFocusControl = nullptr; - -public: - DListMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); - virtual void Init(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); - FListMenuItem *GetItem(FName name); - bool Responder (event_t *ev) override; - bool MenuEvent (int mkey, bool fromcontroller) override; - bool MouseEvent(int type, int x, int y) override; - void Ticker () override; - void Drawer () override; - void PreDraw() override; - virtual void SelectionChanged() {} - void SetFocus(FListMenuItem *fc) - { - mFocusControl = fc; - } - bool CheckFocus(FListMenuItem *fc) - { - return mFocusControl == fc; - } - void ReleaseFocus() - { - mFocusControl = NULL; - } - const FListMenuDescriptor* Descriptor() const - { - return mDesc; - } -}; - - -//============================================================================= -// -// base class for menu items -// -//============================================================================= - -class FOptionMenuItem : public FListMenuItem -{ -protected: - FString mLabel; - bool mCentered = false; - - void drawText(int x, int y, int color, const char * text, bool grayed = false); - - int drawLabel(int indent, int y, EColorRange color, bool grayed = false); - void drawValue(int indent, int y, int color, const char *text, bool grayed = false); - - int CursorSpace(); - - -public: - - FOptionMenuItem(const char *text, FName action = NAME_None, bool center = false) - : FListMenuItem(0, 0, action) - { - mLabel = text; - mCentered = center; - } - - ~FOptionMenuItem(); - virtual int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected); - virtual bool Selectable(); - virtual int GetIndent(); - virtual bool MouseEvent(int type, int x, int y); + double GetY() { return mYpos; } }; //============================================================================= @@ -606,239 +243,41 @@ extern FOptionMap OptionValues; //============================================================================= // -// Option menu class runs a menu described by a FOptionMenuDescriptor +// // //============================================================================= -class DOptionMenu : public DMenu -{ - using Super = DMenu; - bool CanScrollUp; - bool CanScrollDown; - int VisBottom; - FOptionMenuItem *mFocusControl; - -protected: - FOptionMenuDescriptor *mDesc; - - int GetPosition(); - -public: - FOptionMenuItem *GetItem(FName name); - DOptionMenu(DMenu *parent = NULL, FOptionMenuDescriptor *desc = NULL); - virtual void Init(DMenu *parent = NULL, FOptionMenuDescriptor *desc = NULL); - int FirstSelectable(); - bool Responder (event_t *ev); - bool MenuEvent (int mkey, bool fromcontroller); - bool MouseEvent(int type, int x, int y); - void Ticker (); - void Drawer (); - virtual int GetIndent(); - const FOptionMenuDescriptor *GetDescriptor() const { return mDesc; } - void SetFocus(FOptionMenuItem *fc) - { - mFocusControl = fc; - } - bool CheckFocus(FOptionMenuItem *fc) - { - return mFocusControl == fc; - } - void ReleaseFocus() - { - mFocusControl = NULL; - } -}; - -FFont *OptionFont(); -int OptionHeight(); -int OptionWidth(const char * s); -void DrawOptionText(int x, int y, int color, const char *text, bool grayed = false); - -//============================================================================= -// -// ImageScroller -// -//============================================================================= -class ImageScreen; - -class DImageScrollerMenu : public DMenu -{ - DMenu* mCurrent = nullptr; - FImageScrollerDescriptor* mDesc = nullptr; - int index = 0; - MenuTransition pageTransition = {}; - -protected: - virtual ImageScreen* newImageScreen(FImageScrollerDescriptor::ScrollerItem* desc); - -public: - void Init(DMenu* parent = nullptr, FImageScrollerDescriptor* desc = nullptr); - bool MenuEvent(int mkey, bool fromcontroller); - bool MouseEvent(int type, int x, int y); - void Ticker(); - void Drawer(); -}; - -//============================================================================= -// -// Input some text -// -//============================================================================= - -class DTextEnterMenu : public DMenu -{ - using Super = DMenu; - - FString mEnterString; - int mEnterSize; - bool mInputGridOkay; - int InputGridX; - int InputGridY; - int CursorSize; - bool AllowColors; - FFont *displayFont; - - - void AppendChar(int ch); - -public: - - // [TP] Added allowcolors - DTextEnterMenu(DMenu *parent, FFont *dpf, FString textbuffer, int maxlen, bool showgrid, bool allowcolors = false); - - void Drawer (); - bool MenuEvent (int mkey, bool fromcontroller); - bool Responder(event_t *ev); - bool TranslateKeyboardEvents(); - bool MouseEvent(int type, int x, int y); - const char* GetText() { return mEnterString.GetChars(); } - -}; - -//============================================================================= -// -// Show a fullscreen image / centered text screen for an image scroller -// -//============================================================================= - -class ImageScreen : public DMenu -{ -protected: - const FImageScrollerDescriptor::ScrollerItem* mDesc; -public: - ImageScreen(const FImageScrollerDescriptor::ScrollerItem* it) - { - mDesc = it; - } - void Drawer() override; -}; - - - struct event_t; void M_EnableMenu (bool on) ; bool M_Responder (event_t *ev); void M_Ticker (void); void M_Drawer (void); -void M_PreviousMenu(); void M_Init (void); void M_CreateMenus(); void M_ActivateMenu(DMenu *menu); -void M_ClearMenus (bool final = false); +void M_ClearMenus (); +void M_PreviousMenu (); void M_ParseMenuDefs(); -void M_StartupSkillMenu(FNewGameStartup *gs); -int M_GetDefaultSkill(); -void M_StartControlPanel (bool makeSound); -bool M_SetMenu(FName menu, int param = -1, FName callingMenu = NAME_None); -void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave); -void M_StartMessage(const char *message, int messagemode, int scriptId, FName action = NAME_None); -void M_UnhideCustomMenu(int menu, int itemmask); -void M_MenuSound(EMenuSounds snd); -void M_Autosave(); +void M_DoStartControlPanel(bool scaleoverride); +void M_SetMenu(FName menu, int param = -1); +void M_StartMessage(const char *message, int messagemode, FName action = NAME_None); +DMenu *StartPickerMenu(DMenu *parent, const char *name, FColorCVar *cvar); +void M_MarkMenus(); +FTextureID GetMenuTexture(const char* const name); +void DeinitMenus(); bool M_Active(); -void M_DeinitMenus(); -void M_UnpauseSound(); +inline bool M_IsAnimated() { return false; } -void I_SetMouseCapture(); -void I_ReleaseMouseCapture(); - -struct MenuClassDescriptor; -extern TArray menuClasses; - -using hFunc = std::function; -DMenu* CreateMessageBoxMenu(DMenu* parent, const char* message, int messagemode, int scriptID, bool playsound, FName action = NAME_None, hFunc handler = nullptr); - - -struct MenuClassDescriptor -{ - FName mName; - MenuClassDescriptor(const char* name) : mName(name) - { - //menuClasses.Push(this); - } - - virtual DMenu* CreateNew() = 0; -}; - -template struct TMenuClassDescriptor : public MenuClassDescriptor -{ - TMenuClassDescriptor(const char* name) : MenuClassDescriptor(name) - {} - DMenu* CreateNew() - { - return new Menu; - } -}; - - -struct FSavegameManager -{ -private: - TArray SaveGames; - FSaveGameNode NewSaveNode; - int LastSaved = -1; - int LastAccessed = -1; - TArray SavePicData; - FGameTexture *SavePic = nullptr; - -public: - int WindowSize = 0; - FString SaveCommentString; - FSaveGameNode *quickSaveSlot = nullptr; - ~FSavegameManager(); - -private: - int InsertSaveNode(FSaveGameNode *node); -public: - void NotifyNewSave(const FString &file, const FString &title, bool okForQuicksave, bool forceQuicksave); - void ClearSaveGames(); - - void ReadSaveStrings(); - void UnloadSaveData(); - - int RemoveSaveSlot(int index); - void LoadSavegame(int Selected); - void DoSave(int Selected, const char *savegamestring); - unsigned ExtractSaveData(int index); - void ClearSaveStuff(); - bool DrawSavePic(int x, int y, int w, int h); - void DrawSaveComment(FFont *font, int cr, int x, int y, int scalefactor); - void SetFileInfo(int Selected); - unsigned SavegameCount(); - FSaveGameNode *GetSavegame(int i); - void InsertNewSaveNode(); - bool RemoveNewSaveNode(); - - void LoadGame(FSaveGameNode* node); - void SaveGame(FSaveGameNode* node, bool ok4q, bool forceq); - -}; - -extern FSavegameManager savegameManager; -extern DMenu* CurrentMenu; - -bool M_IsAnimated(); +struct IJoystickConfig; +DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, int v = -1); +DMenuItemBase * CreateOptionMenuItemSubmenu(const char *label, FName cmd, int center); +DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBindings *bindings); +DMenuItemBase * CreateOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy); +DMenuItemBase * CreateListMenuItemPatch(double x, double y, int height, int hotkey, FTextureID tex, FName command, int param); +DMenuItemBase * CreateListMenuItemText(double x, double y, int height, int hotkey, const char *text, FFont *font, PalEntry color1, PalEntry color2, FName command, int param); +DMenuItemBase * CreateOptionMenuItemCommand(const char *label, FName cmd, bool centered = false); +void UpdateVRModes(bool considerQuadBuffered=true); #endif diff --git a/source/core/menu/menudef.cpp b/source/core/menu/menudef.cpp index bd0e551ee..b3d23397b 100644 --- a/source/core/menu/menudef.cpp +++ b/source/core/menu/menudef.cpp @@ -34,58 +34,111 @@ #include #include "menu.h" -#include "c_dispatch.h" #include "filesystem.h" -#include "sc_man.h" -#include "v_font.h" #include "c_bind.h" -#include "d_event.h" -#include "d_gui.h" -#include "printf.h" -#include "gamecontrol.h" +#include "i_music.h" +#include "i_sound.h" #include "cmdlib.h" -#include "c_cvars.h" -#include "optionmenuitems.h" +#include "vm.h" +#include "types.h" +#include "m_argv.h" #include "i_soundfont.h" -#include "zstring.h" -#include "texturemanager.h" +#include "i_system.h" #include "v_video.h" +#include "gstrings.h" #include +#include "texturemanager.h" +#include "printf.h" -// Menu-relevant content that gets filled in by scripts. This will get processed after the game has loaded. -FString gSkillNames[MAXSKILLS]; -FString gVolumeNames[MAXVOLUMES]; -FString gVolumeSubtitles[MAXVOLUMES]; -int32_t gVolumeFlags[MAXVOLUMES]; -int gDefaultVolume = 0, gDefaultSkill = 1; -MenuGameplayStemEntry g_MenuGameplayEntries[MAXMENUGAMEPLAYENTRIES]; + +bool CheckGame(const char* string, bool chexisdoom) { return false; } +bool CheckSkipGameOptionBlock(FScanner& sc) { return false; } +void SetDefaultMenuColors(); MenuDescriptorList MenuDescriptors; -static FListMenuDescriptor DefaultListMenuSettings; // contains common settings for all list menus -static FOptionMenuDescriptor DefaultOptionMenuSettings; // contains common settings for all Option menus +static DListMenuDescriptor *DefaultListMenuSettings; // contains common settings for all list menus +static DOptionMenuDescriptor *DefaultOptionMenuSettings; // contains common settings for all Option menus FOptionMenuSettings OptionSettings; FOptionMap OptionValues; +bool mustPrintErrors; +PClass *DefaultListMenuClass; +PClass *DefaultOptionMenuClass; -void I_BuildMIDIMenuList(FOptionValues* opt); void I_BuildALDeviceList(FOptionValues *opt); -void I_BuildALResamplersList(FOptionValues* opt); +void I_BuildALResamplersList(FOptionValues *opt); -bool IsOpenALPresent(); -void M_DeinitMenus() + +DEFINE_GLOBAL_NAMED(OptionSettings, OptionMenuSettings) + +DEFINE_ACTION_FUNCTION(FOptionValues, GetCount) { + PARAM_PROLOGUE; + PARAM_NAME(grp); + int cnt = 0; + FOptionValues **pGrp = OptionValues.CheckKey(grp); + if (pGrp != nullptr) { - MenuDescriptorList::Iterator it(MenuDescriptors); + cnt = (*pGrp)->mValues.Size(); + } + ACTION_RETURN_INT(cnt); +} - MenuDescriptorList::Pair *pair; - - while (it.NextPair(pair)) +DEFINE_ACTION_FUNCTION(FOptionValues, GetValue) +{ + PARAM_PROLOGUE; + PARAM_NAME(grp); + PARAM_UINT(index); + double val = 0; + FOptionValues **pGrp = OptionValues.CheckKey(grp); + if (pGrp != nullptr) + { + if (index < (*pGrp)->mValues.Size()) { - delete pair->Value; - pair->Value = NULL; + val = (*pGrp)->mValues[index].Value; } } + ACTION_RETURN_FLOAT(val); +} +DEFINE_ACTION_FUNCTION(FOptionValues, GetTextValue) +{ + PARAM_PROLOGUE; + PARAM_NAME(grp); + PARAM_UINT(index); + FString val; + FOptionValues **pGrp = OptionValues.CheckKey(grp); + if (pGrp != nullptr) + { + if (index < (*pGrp)->mValues.Size()) + { + val = (*pGrp)->mValues[index].TextValue; + } + } + ACTION_RETURN_STRING(val); +} + +DEFINE_ACTION_FUNCTION(FOptionValues, GetText) +{ + PARAM_PROLOGUE; + PARAM_NAME(grp); + PARAM_UINT(index); + FString val; + FOptionValues **pGrp = OptionValues.CheckKey(grp); + if (pGrp != nullptr) + { + if (index < (*pGrp)->mValues.Size()) + { + val = (*pGrp)->mValues[index].Text; + } + } + ACTION_RETURN_STRING(val); +} + + +void DeinitMenus() +{ + M_ClearMenus(); { FOptionMap::Iterator it(OptionValues); @@ -94,25 +147,23 @@ void M_DeinitMenus() while (it.NextPair(pair)) { delete pair->Value; - pair->Value = NULL; + pair->Value = nullptr; } } MenuDescriptors.Clear(); OptionValues.Clear(); - - CurrentMenu = NULL; - DefaultListMenuSettings.mItems.Clear(); } -static FGameTexture* GetMenuTexture(const char* const name) +FTextureID GetMenuTexture(const char* const name) { - auto texid = TexMan.CheckForTexture(name, ETextureType::Any); - if (!texid.isValid()) + const FTextureID texture = TexMan.CheckForTexture(name, ETextureType::MiscPatch); + + if (!texture.Exists() && mustPrintErrors) { Printf("Missing menu texture: \"%s\"\n", name); } - return TexMan.GetGameTexture(texid); + return texture; } //============================================================================= @@ -139,56 +190,18 @@ static void SkipSubBlock(FScanner &sc) // //============================================================================= -struct gamefilter +static bool CheckSkipGameBlock(FScanner &sc) { - const char* gamename; - int gameflag; -}; - -static const gamefilter games[] = { - { "Duke", GAMEFLAG_DUKE}, - { "Nam", GAMEFLAG_NAM|GAMEFLAG_NAPALM}, - { "NamOnly", GAMEFLAG_NAM}, // for cases where the difference matters. - { "Napalm", GAMEFLAG_NAPALM}, - { "WW2GI", GAMEFLAG_WW2GI}, - { "Redneck", GAMEFLAG_RR}, - { "RedneckRides", GAMEFLAG_RRRA}, - { "Deer", GAMEFLAG_DEER}, - { "Blood", GAMEFLAG_BLOOD}, - { "ShadowWarrior", GAMEFLAG_SW}, - { "Exhumed", GAMEFLAG_POWERSLAVE|GAMEFLAG_EXHUMED}, - { "Worldtour", GAMEFLAG_WORLDTOUR}, -}; - -// for other parts that need to filter by game name. -bool validFilter(const char *str) -{ - for (auto &gf : games) - { - if (g_gameType & gf.gameflag) - { - if (!stricmp(str, gf.gamename)) return true; - } - } - return false; -} - - -static bool CheckSkipGameBlock(FScanner &sc, bool negate = false) -{ - int filter = 0; + bool filter = false; sc.MustGetStringName("("); do { sc.MustGetString(); - int gi = sc.MustMatchString(&games[0].gamename, sizeof(games[0])); - - filter |= games[gi].gameflag; + filter |= CheckGame(sc.String, false); } while (sc.CheckString(",")); - if (negate) filter = ~filter; sc.MustGetStringName(")"); - if (!(filter & g_gameType)) + if (!filter) { SkipSubBlock(sc); return !sc.CheckString("else"); @@ -209,7 +222,8 @@ static bool CheckSkipOptionBlock(FScanner &sc) do { sc.MustGetString(); - if (sc.Compare("Windows")) + if (CheckSkipGameOptionBlock(sc)) filter = true; + else if (sc.Compare("Windows")) { #ifdef _WIN32 filter = true; @@ -227,11 +241,15 @@ static bool CheckSkipOptionBlock(FScanner &sc) filter = true; #endif } - - if (sc.Compare("openal")) + else if (sc.Compare("OpenAL")) { - if (IsOpenALPresent()) + filter |= IsOpenALPresent(); + } + else if (sc.Compare("MMX")) + { + #ifdef HAVE_MMX filter = true; + #endif } } while (sc.CheckString(",")); @@ -250,28 +268,10 @@ static bool CheckSkipOptionBlock(FScanner &sc) // //============================================================================= -static bool CheckSkipNoSwBlock(FScanner& sc) -{ - sc.MustGetStringName("("); - sc.MustGetString(); - bool res = sc.Compare("true"); - sc.MustGetStringName(")"); - if ((!(g_gameType & GAMEFLAG_SHAREWARE)) == res) - { - SkipSubBlock(sc); - return !sc.CheckString("else"); - } - return false; -} - -//============================================================================= -// -// -// -//============================================================================= - -static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) +static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc) { + bool sizeset = false; + bool sizecompatible = true; sc.MustGetStringName("{"); while (!sc.CheckString("}")) { @@ -288,14 +288,6 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) ParseListMenuBody(sc, desc); } } - else if (sc.Compare("ifnotgame")) - { - if (!CheckSkipGameBlock(sc, true)) - { - // recursively parse sub-block - ParseListMenuBody(sc, desc); - } - } else if (sc.Compare("ifoption")) { if (!CheckSkipOptionBlock(sc)) @@ -304,31 +296,27 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) ParseListMenuBody(sc, desc); } } - else if (sc.Compare("ifshareware")) - { - if (!CheckSkipNoSwBlock(sc)) - { - // recursively parse sub-block - ParseListMenuBody(sc, desc); - } - } else if (sc.Compare("Class")) { sc.MustGetString(); - FString s = sc.String; - s.Substitute("$", gi->Name()); - desc->mClass = s; + PClass *cls = PClass::FindClass(sc.String); + if (cls == nullptr || !cls->IsDescendantOf("ListMenu")) + { + sc.ScriptError("Unknown menu class '%s'", sc.String); + } + desc->mClass = cls; + sizecompatible = false; } else if (sc.Compare("Selector")) { sc.MustGetString(); desc->mSelector = GetMenuTexture(sc.String); sc.MustGetStringName(","); - sc.MustGetNumber(); - desc->mSelectOfsX = sc.Number; + sc.MustGetFloat(); + desc->mSelectOfsX = sc.Float; sc.MustGetStringName(","); - sc.MustGetNumber(); - desc->mSelectOfsY = sc.Number; + sc.MustGetFloat(); + desc->mSelectOfsY = sc.Float; } else if (sc.Compare("Linespacing")) { @@ -337,29 +325,15 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) } else if (sc.Compare("Position")) { - sc.MustGetNumber(); - desc->mXpos = sc.Number; + sc.MustGetFloat(); + desc->mXpos = sc.Float; sc.MustGetStringName(","); - sc.MustGetNumber(); - desc->mYpos = sc.Number; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - desc->mYbotton = sc.Number; - } + sc.MustGetFloat(); + desc->mYpos = sc.Float; } else if (sc.Compare("Centermenu")) { - desc->mFlags |= LMF_Centered; - } - else if (sc.Compare("animatedtransition")) - { - desc->mFlags |= LMF_Animate; - } - else if (sc.Compare("Fixedspacing")) - { - sc.MustGetNumber(); - desc->mSpacing = sc.Number; + desc->mCenter = true; } else if (sc.Compare("MouseWindow")) { @@ -368,144 +342,205 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) sc.MustGetStringName(","); sc.MustGetNumber(); desc->mWRight = sc.Number; - } - else if (sc.Compare("StaticPatch") || sc.Compare("StaticPatchCentered")) - { - bool centered = sc.Compare("StaticPatchCentered"); - sc.MustGetNumber(); - int x = sc.Number; - sc.MustGetStringName(","); - sc.MustGetNumber(); - int y = sc.Number; - sc.MustGetStringName(","); - sc.MustGetString(); - auto tex = GetMenuTexture(sc.String); - - FListMenuItem *it = new FListMenuItemStaticPatch(x, y, tex, centered); - desc->mItems.Push(it); - } - else if (sc.Compare("ScriptId")) - { - sc.MustGetNumber(); - desc->mScriptId = sc.Number; - } - else if (sc.Compare("Caption")) - { - sc.MustGetString(); - desc->mCaption = sc.String; - } - else if (sc.Compare("StaticText") || sc.Compare("StaticTextCentered")) - { - bool centered = sc.Compare("StaticTextCentered"); - sc.MustGetNumber(); - int x = sc.Number; - sc.MustGetStringName(","); - sc.MustGetNumber(); - int y = sc.Number; - sc.MustGetStringName(","); - sc.MustGetString(); - FListMenuItem *it = new FListMenuItemStaticText(x, y, sc.String, desc->mFont, desc->mFontColor, centered); - desc->mItems.Push(it); - } - else if (sc.Compare("PatchItem")) - { - sc.MustGetString(); - auto tex = GetMenuTexture(sc.String); - sc.MustGetStringName(","); - sc.MustGetString(); - int hotkey = sc.String[0]; - sc.MustGetStringName(","); - sc.MustGetString(); - FName action = sc.String; - int param = 0; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - param = sc.Number; - } - - FListMenuItem *it = new FListMenuItemPatch(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, tex, action, param); - desc->mItems.Push(it); - desc->mYpos += desc->mLinespacing; - if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; - } - else if (sc.Compare("TextItem")) - { - sc.MustGetString(); - FString text = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - int hotkey = sc.String[0]; - sc.MustGetStringName(","); - sc.MustGetString(); - FName action = sc.String; - int param = 0; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - param = sc.Number; - } - - FListMenuItem *it = new FListMenuItemText(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, text, desc->mFont, desc->mFontColor, desc->mFontColor2, action, param); - desc->mItems.Push(it); - desc->mYpos += desc->mLinespacing; - if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; - - } - else if (sc.Compare("NativeTextItem")) - { - sc.MustGetString(); - FString text = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - int hotkey = sc.String[0]; - sc.MustGetStringName(","); - sc.MustGetString(); - FName action = sc.String; - int param = 0; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - param = sc.Number; - } - - auto it = new FListMenuItemNativeText(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, text, desc->mNativeFontNum, desc->mNativePalNum, desc->mNativeFontScale, action, param); - desc->mItems.Push(it); - desc->mYpos += desc->mLinespacing; - if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size() - 1; - } else if (sc.Compare("Font")) - { - sc.MustGetString(); - FFont* newfont = V_GetFont(sc.String); - if (newfont != NULL) desc->mFont = newfont; - if (sc.CheckString(",")) { sc.MustGetString(); - desc->mFontColor2 = desc->mFontColor = V_FindFontColor((FName)sc.String); + FFont *newfont = V_GetFont(sc.String); + if (newfont != nullptr) desc->mFont = newfont; if (sc.CheckString(",")) { sc.MustGetString(); - desc->mFontColor2 = V_FindFontColor((FName)sc.String); + desc->mFontColor2 = desc->mFontColor = V_FindFontColor((FName)sc.String); + if (sc.CheckString(",")) + { + sc.MustGetString(); + desc->mFontColor2 = V_FindFontColor((FName)sc.String); } } - else - { - desc->mFontColor = OptionSettings.mFontColor; - desc->mFontColor2 = OptionSettings.mFontColorValue; - } + else + { + desc->mFontColor = OptionSettings.mFontColor; + desc->mFontColor2 = OptionSettings.mFontColorValue; + } } else if (sc.Compare("NetgameMessage")) { sc.MustGetString(); desc->mNetgameMessage = sc.String; } + else if (sc.Compare("size")) + { + if (sc.CheckNumber()) + { + desc->mVirtWidth = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mVirtHeight = sc.Number; + } + else + { + sc.MustGetString(); + if (sc.Compare("clean")) + { + desc->mVirtWidth = -1; + } + else if (sc.Compare("optclean")) + { + desc->mVirtWidth = -2; + } + else + { + sc.ScriptError("Invalid value '%s' for 'size'", sc.String); + } + } + } else { - sc.ScriptError("Unknown keyword '%s'", sc.String); + // all item classes from which we know that they support sized scaling. + // If anything else comes through here the option to swich scaling mode is disabled for this menu. + static const char* const compatibles[] = { "StaticPatch", "StaticText", "StaticTextCentered", "TextItem", "PatchItem", "PlayerDisplay", nullptr }; + if (sc.MatchString(compatibles) < 0) sizecompatible = false; + + bool success = false; + FStringf buildname("ListMenuItem%s", sc.String); + PClass *cls = PClass::FindClass(buildname); + if (cls != nullptr && cls->IsDescendantOf("ListMenuItem")) + { + auto func = dyn_cast(cls->FindSymbol("Init", true)); + if (func != nullptr && !(func->Variants[0].Flags & (VARF_Protected | VARF_Private))) // skip internal classes which have a protected init method. + { + auto &args = func->Variants[0].Proto->ArgumentTypes; + TArray params; + int start = 1; + + params.Push(0); + if (args.Size() > 1 && args[1] == NewPointer(PClass::FindClass("ListMenuDescriptor"))) + { + params.Push(desc); + start = 2; + } + auto TypeCVar = NewPointer(NewStruct("CVar", nullptr, true)); + + // Note that this array may not be reallocated so its initial size must be the maximum possible elements. + TArray strings(args.Size()); + for (unsigned i = start; i < args.Size(); i++) + { + sc.MustGetString(); + if (args[i] == TypeString) + { + strings.Push(sc.String); + params.Push(&strings.Last()); + } + else if (args[i] == TypeName) + { + params.Push(FName(sc.String).GetIndex()); + } + else if (args[i] == TypeColor) + { + params.Push(V_GetColor(nullptr, sc)); + } + else if (args[i] == TypeFont) + { + auto f = V_GetFont(sc.String); + if (f == nullptr) + { + sc.ScriptError("Unknown font %s", sc.String); + } + params.Push(f); + } + else if (args[i] == TypeTextureID) + { + auto f = TexMan.CheckForTexture(sc.String, ETextureType::MiscPatch); + if (!f.Exists()) + { + sc.ScriptMessage("Unknown texture %s", sc.String); + } + params.Push(f.GetIndex()); + } + else if (args[i]->isIntCompatible()) + { + char *endp; + int v = (int)strtoll(sc.String, &endp, 0); + if (*endp != 0) + { + // special check for font color ranges. + v = V_FindFontColor(sc.String); + if (v == CR_UNTRANSLATED && !sc.Compare("untranslated")) + { + // todo: check other data types that may get used. + sc.ScriptError("Integer expected, got %s", sc.String); + } + } + if (args[i] == TypeBool) v = !!v; + params.Push(v); + } + else if (args[i]->isFloat()) + { + char *endp; + double v = strtod(sc.String, &endp); + if (*endp != 0) + { + sc.ScriptError("Float expected, got %s", sc.String); + } + params.Push(v); + } + else if (args[i] == TypeCVar) + { + auto cv = FindCVar(sc.String, nullptr); + if (cv == nullptr && *sc.String) + { + sc.ScriptError("Unknown CVar %s", sc.String); + } + params.Push(cv); + } + else + { + sc.ScriptError("Invalid parameter type %s for menu item", args[i]->DescriptiveName()); + } + if (sc.CheckString(",")) + { + if (i == args.Size() - 1) + { + sc.ScriptError("Too many parameters for %s", cls->TypeName.GetChars()); + } + } + else + { + if (i < args.Size() - 1 && !(func->Variants[0].ArgFlags[i + 1] & VARF_Optional)) + { + sc.ScriptError("Insufficient parameters for %s", cls->TypeName.GetChars()); + } + break; + } + } + DMenuItemBase *item = (DMenuItemBase*)cls->CreateNew(); + params[0] = item; + VMCallWithDefaults(func->Variants[0].Implementation, params, nullptr, 0); + desc->mItems.Push((DMenuItemBase*)item); + + if (cls->IsDescendantOf("ListMenuItemSelectable")) + { + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size() - 1; + } + success = true; + } + } + if (!success) + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } } } + if (!sizeset && sizecompatible) // allow unclean scaling on this menu + { + desc->mVirtWidth = -2; + } + for (auto &p : desc->mItems) + { + GC::WriteBarrier(p); + } } //============================================================================= @@ -514,28 +549,112 @@ static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) // //============================================================================= -static bool CheckCompatible(FMenuDescriptor *newd, FMenuDescriptor *oldd) +static bool CheckCompatible(DMenuDescriptor *newd, DMenuDescriptor *oldd) { - /*if (oldd->mClass == NULL)*/ return true; - //return oldd->mClass == newd->mClass; + if (oldd->mClass == nullptr) return true; + return newd->mClass->IsDescendantOf(oldd->mClass); } -static bool ReplaceMenu(FScanner &sc, FMenuDescriptor *desc) +static int GetGroup(DMenuItemBase *desc) { - FMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName); - if (pOld != NULL && *pOld != NULL) + if (desc->IsKindOf(NAME_OptionMenuItemCommand)) return 2; + if (desc->IsKindOf(NAME_OptionMenuItemSubmenu)) return 1; + if (desc->IsKindOf(NAME_OptionMenuItemControlBase)) return 3; + if (desc->IsKindOf(NAME_OptionMenuItemOptionBase)) return 4; + if (desc->IsKindOf(NAME_OptionMenuSliderBase)) return 4; + if (desc->IsKindOf(NAME_OptionMenuFieldBase)) return 4; + if (desc->IsKindOf(NAME_OptionMenuItemColorPicker)) return 4; + if (desc->IsKindOf(NAME_OptionMenuItemStaticText)) return 5; + if (desc->IsKindOf(NAME_OptionMenuItemStaticTextSwitchable)) return 5; + return 0; +} + +static bool FindMatchingItem(DMenuItemBase *desc) +{ + int grp = GetGroup(desc); + if (grp == 0) return false; // no idea what this is. + if (grp == 5) return true; // static texts always match + + FName name = desc->mAction; + + if (grp == 1) { - if (CheckCompatible(desc, *pOld)) + // Check for presence of menu + auto menu = MenuDescriptors.CheckKey(name); + if (menu == nullptr) return false; + } + else if (grp == 4) + { + static const FName CVarBlacklist[] = { + NAME_snd_waterlp, NAME_snd_output, NAME_snd_output_format, NAME_snd_speakermode, NAME_snd_resampler, NAME_AlwaysRun }; + + // Check for presence of CVAR and blacklist + auto cv = FindCVar(name.GetChars(), nullptr); + if (cv == nullptr) return true; + + for (auto bname : CVarBlacklist) { - delete *pOld; + if (name == bname) return true; } - else + } + + MenuDescriptorList::Iterator it(MenuDescriptors); + MenuDescriptorList::Pair *pair; + while (it.NextPair(pair)) + { + for (auto it : pair->Value->mItems) + { + if (it->mAction == name && GetGroup(it) == grp) return true; + } + } + return false; +} + +static bool ReplaceMenu(FScanner &sc, DMenuDescriptor *desc) +{ + DMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName); + if (pOld != nullptr && *pOld != nullptr) + { + if ((*pOld)->mProtected) + { + // If this tries to replace an option menu with an option menu, let's append all new entries to the old menu. + // Otherwise bail out because for list menus it's not that simple. + if (desc->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)) || (*pOld)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) + { + sc.ScriptMessage("Cannot replace protected menu %s.", desc->mMenuName.GetChars()); + return true; + } + for (int i = desc->mItems.Size()-1; i >= 0; i--) + { + if (FindMatchingItem(desc->mItems[i])) + { + desc->mItems.Delete(i); + } + } + if (desc->mItems.Size() > 0) + { + auto sep = CreateOptionMenuItemStaticText(" "); + (*pOld)->mItems.Push(sep); + sep = CreateOptionMenuItemStaticText("---------------", 1); + (*pOld)->mItems.Push(sep); + for (auto it : desc->mItems) + { + (*pOld)->mItems.Push(it); + } + desc->mItems.Clear(); + //sc.ScriptMessage("Merged %d items into %s", desc->mItems.Size(), desc->mMenuName.GetChars()); + } + return true; + } + + if (!CheckCompatible(desc, *pOld)) { sc.ScriptMessage("Tried to replace menu '%s' with a menu of different type", desc->mMenuName.GetChars()); return true; } } MenuDescriptors[desc->mMenuName] = desc; + GC::WriteBarrier(desc); return false; } @@ -549,151 +668,30 @@ static void ParseListMenu(FScanner &sc) { sc.MustGetString(); - FListMenuDescriptor *desc = new FListMenuDescriptor; - desc->mType = MDESC_ListMenu; + DListMenuDescriptor *desc = Create(); + desc->Reset(); desc->mMenuName = sc.String; desc->mSelectedItem = -1; desc->mAutoselect = -1; - desc->mSelectOfsX = DefaultListMenuSettings.mSelectOfsX; - desc->mSelectOfsY = DefaultListMenuSettings.mSelectOfsY; - desc->mSelector = DefaultListMenuSettings.mSelector; - desc->mDisplayTop = DefaultListMenuSettings.mDisplayTop; - desc->mXpos = DefaultListMenuSettings.mXpos; - desc->mYpos = DefaultListMenuSettings.mYpos; - desc->mLinespacing = DefaultListMenuSettings.mLinespacing; - desc->mNetgameMessage = DefaultListMenuSettings.mNetgameMessage; - desc->mFont = DefaultListMenuSettings.mFont; - desc->mFontColor = DefaultListMenuSettings.mFontColor; - desc->mFontColor2 = DefaultListMenuSettings.mFontColor2; - desc->mClass = NULL; - desc->mRedirect = NULL; + desc->mSelectOfsX = DefaultListMenuSettings->mSelectOfsX; + desc->mSelectOfsY = DefaultListMenuSettings->mSelectOfsY; + desc->mSelector = DefaultListMenuSettings->mSelector; + desc->mDisplayTop = DefaultListMenuSettings->mDisplayTop; + desc->mXpos = DefaultListMenuSettings->mXpos; + desc->mYpos = DefaultListMenuSettings->mYpos; + desc->mLinespacing = DefaultListMenuSettings->mLinespacing; + desc->mNetgameMessage = DefaultListMenuSettings->mNetgameMessage; + desc->mFont = DefaultListMenuSettings->mFont; + desc->mFontColor = DefaultListMenuSettings->mFontColor; + desc->mFontColor2 = DefaultListMenuSettings->mFontColor2; + desc->mClass = nullptr; desc->mWLeft = 0; desc->mWRight = 0; + desc->mCenter = false; + desc->mFromEngine = fileSystem.GetFileContainer(sc.LumpNum) == 0; // flags menu if the definition is from the IWAD. ParseListMenuBody(sc, desc); - bool scratch = ReplaceMenu(sc, desc); - if (scratch) delete desc; -} - -//============================================================================= -// -// -// -//============================================================================= - -static void ParseImageScrollerBody(FScanner &sc, FImageScrollerDescriptor *desc) -{ - sc.MustGetStringName("{"); - while (!sc.CheckString("}")) - { - sc.MustGetString(); - if (sc.Compare("else")) - { - SkipSubBlock(sc); - } - else if (sc.Compare("ifgame")) - { - if (!CheckSkipGameBlock(sc)) - { - // recursively parse sub-block - ParseImageScrollerBody(sc, desc); - } - } - else if (sc.Compare("ifnotgame")) - { - if (!CheckSkipGameBlock(sc, true)) - { - // recursively parse sub-block - ParseImageScrollerBody(sc, desc); - } - } - else if (sc.Compare("ifshareware")) - { - if (!CheckSkipNoSwBlock(sc)) - { - // recursively parse sub-block - ParseImageScrollerBody(sc, desc); - } - } - else if (sc.Compare("ifoption")) - { - if (!CheckSkipOptionBlock(sc)) - { - // recursively parse sub-block - ParseImageScrollerBody(sc, desc); - } - } - else if (sc.Compare("Class")) - { - sc.MustGetString(); - FString s = sc.String; - s.Substitute("$", gi->Name()); - desc->mClass = s; - } - else if (sc.Compare("TextItem") || sc.Compare("ImageItem")) - { - FImageScrollerDescriptor::ScrollerItem item; - int type = sc.Compare("TextItem"); - sc.MustGetString(); - item.text = strbin1(sc.String); - if (type) - { - sc.MustGetStringName(","); - sc.MustGetNumber(); - item.type = sc.Number; // y-coordinate - } - else item.type = 0; - item.scriptID = INT_MAX; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - item.scriptID = sc.Number; - } - desc->mItems.Push(item); - } - else if (sc.Compare("qavanimationitem")) - { - if (!(g_gameType & GAMEFLAG_BLOOD)) - { - I_Error("QAV animations not available!"); // these (currently) only exist in Blood. - } - FImageScrollerDescriptor::ScrollerItem item; - sc.GetString(); - item.text = sc.String; - item.type = -1; - item.scriptID = INT_MAX; - desc->mItems.Push(item); - } - else if (sc.Compare("animatedtransition")) - { - desc->mFlags |= LMF_Animate; - } - else - { - sc.ScriptError("Unknown keyword '%s'", sc.String); - } - } -} - - -//============================================================================= -// -// -// -//============================================================================= - -static void ParseImageScroller(FScanner &sc) -{ - sc.MustGetString(); - - FImageScrollerDescriptor *desc = new FImageScrollerDescriptor; - desc->mType = MDESC_ImageScroller; - desc->mMenuName = sc.String; - desc->mClass = NAME_None; - - ParseImageScrollerBody(sc, desc); - bool scratch = ReplaceMenu(sc, desc); - if (scratch) delete desc; + ReplaceMenu(sc, desc); } //============================================================================= @@ -704,11 +702,9 @@ static void ParseImageScroller(FScanner &sc) static void ParseOptionValue(FScanner &sc) { - FName optname; - FOptionValues *val = new FOptionValues; sc.MustGetString(); - optname = sc.String; + FName optname = sc.String; sc.MustGetStringName("{"); while (!sc.CheckString("}")) { @@ -720,7 +716,7 @@ static void ParseOptionValue(FScanner &sc) pair.Text = strbin1(sc.String); } FOptionValues **pOld = OptionValues.CheckKey(optname); - if (pOld != NULL && *pOld != NULL) + if (pOld != nullptr && *pOld != nullptr) { delete *pOld; } @@ -736,11 +732,9 @@ static void ParseOptionValue(FScanner &sc) static void ParseOptionString(FScanner &sc) { - FName optname; - FOptionValues *val = new FOptionValues; sc.MustGetString(); - optname = sc.String; + FName optname = sc.String; sc.MustGetStringName("{"); while (!sc.CheckString("}")) { @@ -753,7 +747,7 @@ static void ParseOptionString(FScanner &sc) pair.Text = strbin1(sc.String); } FOptionValues **pOld = OptionValues.CheckKey(optname); - if (pOld != NULL && *pOld != NULL) + if (pOld != nullptr && *pOld != nullptr) { delete *pOld; } @@ -785,14 +779,6 @@ static void ParseOptionSettings(FScanner &sc) ParseOptionSettings(sc); } } - else if (sc.Compare("ifnotgame")) - { - if (!CheckSkipGameBlock(sc, true)) - { - // recursively parse sub-block - ParseOptionSettings(sc); - } - } else if (sc.Compare("Linespacing")) { sc.MustGetNumber(); @@ -816,7 +802,7 @@ static void ParseOptionSettings(FScanner &sc) // //============================================================================= -static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc) +static void ParseOptionMenuBody(FScanner &sc, DOptionMenuDescriptor *desc) { sc.MustGetStringName("{"); while (!sc.CheckString("}")) @@ -834,22 +820,6 @@ static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc) ParseOptionMenuBody(sc, desc); } } - else if (sc.Compare("ifnotgame")) - { - if (!CheckSkipGameBlock(sc, true)) - { - // recursively parse sub-block - ParseOptionMenuBody(sc, desc); - } - } - else if (sc.Compare("ifshareware")) - { - if (!CheckSkipNoSwBlock(sc)) - { - // recursively parse sub-block - ParseOptionMenuBody(sc, desc); - } - } else if (sc.Compare("ifoption")) { if (!CheckSkipOptionBlock(sc)) @@ -861,11 +831,14 @@ static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc) else if (sc.Compare("Class")) { sc.MustGetString(); - FString s = sc.String; - s.Substitute("$", gi->Name()); - desc->mClass = s; + PClass *cls = PClass::FindClass(sc.String); + if (cls == nullptr || !cls->IsDescendantOf("OptionMenu")) + { + sc.ScriptError("Unknown menu class '%s'", sc.String); + } + desc->mClass = cls; } - else if (sc.Compare("Title") || sc.Compare("Caption")) + else if (sc.Compare("Title")) { sc.MustGetString(); desc->mTitle = sc.String; @@ -890,199 +863,121 @@ static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc) sc.MustGetNumber(); desc->mIndent = sc.Number; } - else if (sc.Compare("Submenu")) - { - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FOptionMenuItem *it = new FOptionMenuItemSubmenu(label, sc.String); - desc->mItems.Push(it); - } - else if (sc.Compare("LabeledSubmenu")) - { - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - auto cvar = FindCVar(sc.String, nullptr); - sc.MustGetStringName(","); - sc.MustGetString(); - FOptionMenuItem* it = new FOptionMenuItemLabeledSubmenu(label, cvar, sc.String); - desc->mItems.Push(it); - } - else if (sc.Compare("Option")) - { - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FString cvar = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FString values = sc.String; - FString check; - int center = 0; - if (sc.CheckString(",")) - { - sc.MustGetString(); - if (*sc.String != 0) check = sc.String; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - center = sc.Number; - } - } - FOptionMenuItem *it = new FOptionMenuItemOption(label, cvar, values, check, center); - desc->mItems.Push(it); - } - else if (sc.Compare("Command")) - { - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FOptionMenuItem *it = new FOptionMenuItemCommand(label, sc.String); - desc->mItems.Push(it); - } - else if (sc.Compare("SafeCommand")) - { - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FOptionMenuItem *it = new FOptionMenuItemSafeCommand(label, sc.String); - desc->mItems.Push(it); - } - else if (sc.Compare("Control") || sc.Compare("MapControl")) - { - bool map = sc.Compare("MapControl"); - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FOptionMenuItem *it = new FOptionMenuItemControl(label, sc.String, map? &AutomapBindings : &Bindings); - desc->mItems.Push(it); - } - else if (sc.Compare("StaticText")) - { - sc.MustGetString(); - FString label = sc.String; - EColorRange cr = OptionSettings.mFontColorHeader; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - cr = sc.Number? OptionSettings.mFontColorHeader : OptionSettings.mFontColor; // fixme! - } - FOptionMenuItem *it = new FOptionMenuItemStaticText(label, cr); - desc->mItems.Push(it); - } - else if (sc.Compare("StaticTextSwitchable")) - { - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FString label2 = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FName action = sc.String; - EColorRange cr = OptionSettings.mFontColorHeader; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - cr = sc.Number ? OptionSettings.mFontColorHeader : OptionSettings.mFontColor; // fixme! - } - FOptionMenuItem *it = new FOptionMenuItemStaticTextSwitchable(label, label2, action, cr); - desc->mItems.Push(it); - } - else if (sc.Compare("Slider")) - { - sc.MustGetString(); - FString text = sc.String; - sc.MustGetStringName(","); - sc.MustGetString(); - FString action = sc.String; - sc.MustGetStringName(","); - sc.MustGetFloat(); - double min = sc.Float; - sc.MustGetStringName(","); - sc.MustGetFloat(); - double max = sc.Float; - sc.MustGetStringName(","); - sc.MustGetFloat(); - double step = sc.Float; - int showvalue = 1; - if (sc.CheckString(",")) - { - sc.MustGetNumber(); - showvalue = sc.Number; - } - FOptionMenuItem *it = new FOptionMenuSliderCVar(text, action, min, max, step, showvalue); - desc->mItems.Push(it); - } - // [TP] -- Text input widget - else if ( sc.Compare( "TextField" )) - { - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName( "," ); - sc.MustGetString(); - FString cvar = sc.String; - FString check; - - if ( sc.CheckString( "," )) - { - sc.MustGetString(); - check = sc.String; - } - - FOptionMenuItem* it = new FOptionMenuTextField( label, cvar, check ); - desc->mItems.Push( it ); - } - // [TP] -- Number input widget - else if ( sc.Compare( "NumberField" )) - { - sc.MustGetString(); - FString label = sc.String; - sc.MustGetStringName( "," ); - sc.MustGetString(); - FString cvar = sc.String; - float minimum = 0.0f; - float maximum = 100.0f; - float step = 1.0f; - FString check; - - if ( sc.CheckString( "," )) - { - sc.MustGetFloat(); - minimum = (float) sc.Float; - sc.MustGetStringName( "," ); - sc.MustGetFloat(); - maximum = (float) sc.Float; - - if ( sc.CheckString( "," )) - { - sc.MustGetFloat(); - step = (float) sc.Float; - - if ( sc.CheckString( "," )) - { - sc.MustGetString(); - check = sc.String; - } - } - } - - FOptionMenuItem* it = new FOptionMenuNumberField( label, cvar, - minimum, maximum, step, check ); - desc->mItems.Push( it ); - } else { - sc.ScriptError("Unknown keyword '%s'", sc.String); + bool success = false; + FStringf buildname("OptionMenuItem%s", sc.String); + // Handle one special case: MapControl maps to Control with one parameter different + PClass *cls = PClass::FindClass(buildname); + if (cls != nullptr && cls->IsDescendantOf("OptionMenuItem")) + { + auto func = dyn_cast(cls->FindSymbol("Init", true)); + if (func != nullptr && !(func->Variants[0].Flags & (VARF_Protected | VARF_Private))) // skip internal classes which have a protexted init method. + { + auto &args = func->Variants[0].Proto->ArgumentTypes; + TArray params; + + params.Push(0); + auto TypeCVar = NewPointer(NewStruct("CVar", nullptr, true)); + + // Note that this array may not be reallocated so its initial size must be the maximum possible elements. + TArray strings(args.Size()); + for (unsigned i = 1; i < args.Size(); i++) + { + sc.MustGetString(); + if (args[i] == TypeString) + { + strings.Push(sc.String); + params.Push(&strings.Last()); + } + else if (args[i] == TypeName) + { + params.Push(FName(sc.String).GetIndex()); + } + else if (args[i] == TypeColor) + { + params.Push(V_GetColor(nullptr, sc)); + } + else if (args[i]->isIntCompatible()) + { + char *endp; + int v = (int)strtoll(sc.String, &endp, 0); + if (*endp != 0) + { + // special check for font color ranges. + v = V_FindFontColor(sc.String); + if (v == CR_UNTRANSLATED && !sc.Compare("untranslated")) + { + // todo: check other data types that may get used. + sc.ScriptError("Integer expected, got %s", sc.String); + } + // Color ranges need to be marked for option menu items to support an older feature where a boolean number could be passed instead. + v |= 0x12340000; + } + if (args[i] == TypeBool) v = !!v; + params.Push(v); + } + else if (args[i]->isFloat()) + { + char *endp; + double v = strtod(sc.String, &endp); + if (*endp != 0) + { + sc.ScriptError("Float expected, got %s", sc.String); + } + params.Push(v); + } + else if (args[i] == TypeCVar) + { + auto cv = FindCVar(sc.String, nullptr); + if (cv == nullptr && *sc.String) + { + if (func->Variants[0].ArgFlags[i] & VARF_Optional) + sc.ScriptMessage("Unknown CVar %s", sc.String); + else + sc.ScriptError("Unknown CVar %s", sc.String); + } + params.Push(cv); + } + else + { + sc.ScriptError("Invalid parameter type %s for menu item", args[i]->DescriptiveName()); + } + if (sc.CheckString(",")) + { + if (i == args.Size() - 1) + { + sc.ScriptError("Too many parameters for %s", cls->TypeName.GetChars()); + } + } + else + { + if (i < args.Size() - 1 && !(func->Variants[0].ArgFlags[i + 1] & VARF_Optional)) + { + sc.ScriptError("Insufficient parameters for %s", cls->TypeName.GetChars()); + } + break; + } + } + + DMenuItemBase *item = (DMenuItemBase*)cls->CreateNew(); + params[0] = item; + VMCallWithDefaults(func->Variants[0].Implementation, params, nullptr, 0); + desc->mItems.Push((DMenuItemBase*)item); + + success = true; + } + } + if (!success) + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } } } + for (auto &p : desc->mItems) + { + GC::WriteBarrier(p); + } } //============================================================================= @@ -1095,20 +990,39 @@ static void ParseOptionMenu(FScanner &sc) { sc.MustGetString(); - FOptionMenuDescriptor *desc = new FOptionMenuDescriptor; - desc->mType = MDESC_OptionsMenu; + DOptionMenuDescriptor *desc = Create(); + desc->mFont = BigUpper; desc->mMenuName = sc.String; desc->mSelectedItem = -1; desc->mScrollPos = 0; - desc->mClass = NULL; - desc->mPosition = DefaultOptionMenuSettings.mPosition; - desc->mScrollTop = DefaultOptionMenuSettings.mScrollTop; - desc->mIndent = DefaultOptionMenuSettings.mIndent; - desc->mDontDim = DefaultOptionMenuSettings.mDontDim; + desc->mClass = nullptr; + desc->mPosition = DefaultOptionMenuSettings->mPosition; + desc->mScrollTop = DefaultOptionMenuSettings->mScrollTop; + desc->mIndent = DefaultOptionMenuSettings->mIndent; + desc->mDontDim = DefaultOptionMenuSettings->mDontDim; + desc->mProtected = sc.CheckString("protected"); ParseOptionMenuBody(sc, desc); - bool scratch = ReplaceMenu(sc, desc); - if (scratch) delete desc; + ReplaceMenu(sc, desc); +} + + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseAddOptionMenu(FScanner &sc) +{ + sc.MustGetString(); + + DMenuDescriptor **pOld = MenuDescriptors.CheckKey(sc.String); + if (pOld == nullptr || *pOld == nullptr || !(*pOld)->IsKindOf(RUNTIME_CLASS(DOptionMenuDescriptor))) + { + sc.ScriptError("%s is not an option menu that can be extended", sc.String); + } + ParseOptionMenuBody(sc, (DOptionMenuDescriptor*)(*pOld)); } @@ -1122,48 +1036,22 @@ void M_ParseMenuDefs() { int lump, lastlump = 0; - //OptionSettings.mTitleColor = CR_RED;// V_FindFontColor(gameinfo.mTitleColor); - OptionSettings.mFontColor = CR_RED; - OptionSettings.mFontColorValue = CR_GRAY; - OptionSettings.mFontColorMore = CR_GRAY; - OptionSettings.mFontColorHeader = CR_GOLD; - OptionSettings.mFontColorHighlight = CR_YELLOW; - OptionSettings.mFontColorSelection = CR_BRICK; +#if 0 + SetDefaultMenuColors(); +#endif + // these are supposed to get GC'd after parsing is complete. + DefaultListMenuSettings = Create(); + DefaultOptionMenuSettings = Create(); + DefaultListMenuSettings->Reset(); + DefaultOptionMenuSettings->Reset(); - if (g_gameType & (GAMEFLAG_NAM | GAMEFLAG_NAPALM | GAMEFLAG_WW2GI)) - { - OptionSettings.mFontColor = CR_DARKGREEN; - OptionSettings.mFontColorHeader = CR_DARKGRAY; - OptionSettings.mFontColorHighlight = CR_WHITE; - OptionSettings.mFontColorSelection = CR_DARKGREEN; - } - else if (g_gameType & GAMEFLAG_BLOOD) - { - OptionSettings.mFontColorHeader = CR_DARKGRAY; - OptionSettings.mFontColorHighlight = CR_WHITE; - OptionSettings.mFontColorSelection = CR_DARKRED; - } - else if (g_gameType & GAMEFLAG_RRALL) - { - OptionSettings.mFontColor = CR_BROWN; - OptionSettings.mFontColorHeader = CR_DARKBROWN; - OptionSettings.mFontColorHighlight = CR_ORANGE; - OptionSettings.mFontColorSelection = CR_TAN; - } - else if (g_gameType & GAMEFLAG_SW) - { - OptionSettings.mFontColorHeader = CR_DARKRED; - OptionSettings.mFontColorHighlight = CR_WHITE; - } + int IWADMenu = fileSystem.CheckNumForName("MENUDEF", ns_global, fileSystem.GetIwadNum()); - DefaultListMenuSettings.Reset(); - DefaultOptionMenuSettings.Reset(); - - M_DeinitMenus(); - while ((lump = fileSystem.FindLumpFullName("engine/menudef.txt", &lastlump)) != -1) + while ((lump = fileSystem.FindLump ("MENUDEF", &lastlump)) != -1) { FScanner sc(lump); + mustPrintErrors = lump >= IWADMenu; sc.SetCMode(true); while (sc.GetString()) { @@ -1171,16 +1059,12 @@ void M_ParseMenuDefs() { ParseListMenu(sc); } - else if (sc.Compare("ImageScroller")) - { - ParseImageScroller(sc); - } else if (sc.Compare("DEFAULTLISTMENU")) { - ParseListMenuBody(sc, &DefaultListMenuSettings); - if (DefaultListMenuSettings.mItems.Size() > 0) + ParseListMenuBody(sc, DefaultListMenuSettings); + if (DefaultListMenuSettings->mItems.Size() > 0) { - I_Error("You cannot add menu items to the menu default settings."); + I_FatalError("You cannot add menu items to the menu default settings."); } } else if (sc.Compare("OPTIONVALUE")) @@ -1199,12 +1083,16 @@ void M_ParseMenuDefs() { ParseOptionMenu(sc); } + else if (sc.Compare("ADDOPTIONMENU")) + { + ParseAddOptionMenu(sc); + } else if (sc.Compare("DEFAULTOPTIONMENU")) { - ParseOptionMenuBody(sc, &DefaultOptionMenuSettings); - if (DefaultOptionMenuSettings.mItems.Size() > 0) + ParseOptionMenuBody(sc, DefaultOptionMenuSettings); + if (DefaultOptionMenuSettings->mItems.Size() > 0) { - I_Error("You cannot add menu items to the menu default settings."); + I_FatalError("You cannot add menu items to the menu default settings."); } } else @@ -1212,240 +1100,15 @@ void M_ParseMenuDefs() sc.ScriptError("Unknown keyword '%s'", sc.String); } } + if (Args->CheckParm("-nocustommenu")) break; } + DefaultListMenuClass = DefaultListMenuSettings->mClass; + DefaultListMenuSettings = nullptr; + DefaultOptionMenuClass = DefaultOptionMenuSettings->mClass; + DefaultOptionMenuSettings = nullptr; } -//============================================================================= -// -// Unlocks a custom menu from a script -// -//============================================================================= - -void M_UnhideCustomMenu(int menu, int iSet) -{ - FName menuname = FName(ENamedName(NAME_CustomSubMenu1 + menu)); - auto desc = MenuDescriptors.CheckKey(menuname); - if (desc != NULL && (*desc)->mType == MDESC_ListMenu) - { - FListMenuDescriptor* ld = static_cast(*desc); - for (unsigned int b = 0; b < MAXMENUGAMEPLAYENTRIES; ++b) - { - if (b >= ld->mItems.Size()) return; - if (iSet & (1u << b)) - { - ld->mItems[b]->mHidden = false; - ld->mItems[b]->mEnabled = true; - } - } - } -} - -//============================================================================= -// -// Creates the episode menu -// -//============================================================================= - -static void BuildEpisodeMenu() -{ - // Build episode menu - int addedVolumes = 0; - FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu); - if (desc != NULL && (*desc)->mType == MDESC_ListMenu) - { - FListMenuDescriptor *ld = static_cast(*desc); - ld->mSelectedItem = gDefaultVolume; - int y = ld->mYpos; - - for (int i = 0; i < MAXVOLUMES; i++) - { - if (gVolumeNames[i].IsNotEmpty() && !(gVolumeFlags[i] & EF_HIDEFROMSP)) - - { - auto it = new FListMenuItemNativeText(ld->mXpos, y, ld->mLinespacing, gVolumeNames[i][0], gVolumeNames[i], NIT_BigFont, NIT_ActiveState, 1, NAME_Skillmenu, i); - if ((g_gameType & GAMEFLAG_DUKE) && (g_gameType & GAMEFLAG_SHAREWARE) && i > 0) - { - it->mEnabled = false; - } - y += ld->mLinespacing; - ld->mItems.Push(it); - addedVolumes++; - if (gVolumeSubtitles[i].IsNotEmpty()) - { - auto it = new FListMenuItemNativeStaticText(ld->mXpos, y, gVolumeSubtitles[i], NIT_SmallFont, NIT_ActiveState, false); - y += ld->mLinespacing * 6 / 10; - ld->mItems.Push(it); - } - } - } -#if 0 // this needs to be backed by a working selection menu, until that gets done it must be disabled. - if (!(g_gameType & GAMEFLAG_SHAREWARE)) - { - //auto it = new FListMenuItemNativeStaticText(ld->mXpos, "", NIT_SmallFont); // empty entry as spacer. - //ld->mItems.Push(it); - - y += ld->mLinespacing / 3; - auto it = new FListMenuItemNativeText(ld->mXpos, y, ld->mLineSpacing, 0, "$MNU_USERMAP", NIT_BigFont, NIT_ActiveState, 1, NAME_UsermapMenu); - ld->mItems.Push(it); - addedVolumes++; - } -#endif - if (addedVolumes == 1) - { - ld->mAutoselect = 0; - } - } - - // Build skill menu - int addedSkills = 0; - desc = MenuDescriptors.CheckKey(NAME_Skillmenu); - if (desc != NULL && (*desc)->mType == MDESC_ListMenu) - { - FListMenuDescriptor* ld = static_cast(*desc); - ld->mSelectedItem = gDefaultSkill; - int y = ld->mYpos; - - for (int i = 0; i < MAXSKILLS; i++) - { - if (gSkillNames[i].IsNotEmpty()) - { - auto it = new FListMenuItemNativeText(ld->mXpos, y, ld->mLinespacing, gSkillNames[i][0], gSkillNames[i], NIT_BigFont, NIT_ActiveState, 1, NAME_Startgame, i); - y += ld->mLinespacing; - ld->mItems.Push(it); - addedSkills++; - } - } - if (addedSkills == 0) - { - // Need to add one item with the default skill so that the menu does not break. - auto it = new FListMenuItemNativeText(ld->mXpos, 0, ld->mLinespacing, 0, "", NIT_BigFont, NIT_ActiveState, 1, NAME_Startgame, gDefaultSkill); - ld->mItems.Push(it); - } - if (addedSkills == 1) - { - ld->mAutoselect = 0; - } - } - - if (g_MenuGameplayEntries[0].entry.isValid()) - { - int e = 0; - FMenuDescriptor** desc = MenuDescriptors.CheckKey("CustomGameMenu"); - if (desc != NULL && (*desc)->mType == MDESC_ListMenu) - { - FListMenuDescriptor* ldo = static_cast(*desc); - - for (MenuGameplayStemEntry const& stem : g_MenuGameplayEntries) - { - MenuGameplayEntry const& entry = stem.entry; - if (!entry.isValid()) - break; - - int s = 0; - FMenuDescriptor** sdesc = MenuDescriptors.CheckKey(FName(FStringf("CustomSubMenu%d", e+1))); - if (sdesc != NULL && (*sdesc)->mType == MDESC_ListMenu) - { - FListMenuDescriptor* ld = static_cast(*sdesc); - ld->mCaption = entry.name; - - for (MenuGameplayEntry const& subentry : stem.subentries) - { - if (!subentry.isValid()) - break; - - auto li = new FListMenuItemNativeText(ld->mXpos, 0, ld->mLinespacing, 0, subentry.name, NIT_BigFont, NIT_ActiveColor, 1.f, subentry.flags & MGE_UserContent ? NAME_UsermapMenu : NAME_Skillmenu); - - if (subentry.flags & MGE_Locked) li->mEnabled = false; - if (subentry.flags & MGE_Hidden) li->mHidden = true; - ld->mItems.Push(li); - ++s; - } - } - FName link = entry.flags & MGE_UserContent ? NAME_UsermapMenu : s == 0 ? NAME_Skillmenu : NAME_CustomSubMenu1; - - auto li = new FListMenuItemNativeText(ldo->mXpos, 0, ldo->mLinespacing, 0, entry.name, NIT_BigFont, NIT_ActiveColor, 1.f, link, e); - if (entry.flags & MGE_Locked) li->mEnabled = false; - if (entry.flags & MGE_Hidden) li->mHidden = true; - ldo->mItems.Push(li); - e++; - } - } - if (e > 0) - { - for (auto name : { NAME_Mainmenu, NAME_IngameMenu }) - { - FMenuDescriptor** desc = MenuDescriptors.CheckKey(name); - if (desc != NULL && (*desc)->mType == MDESC_ListMenu) - { - FListMenuDescriptor* ld = static_cast(*desc); - auto li = ld->mItems[0]; - if (li->GetAction(nullptr) == NAME_Episodemenu) - { - li->SetAction(NAME_CustomGameMenu); - } - } - } - } - } -} - -//============================================================================= -// -// Reads any XHAIRS lumps for the names of crosshairs and -// adds them to the display options menu. -// -//============================================================================= - -static void InitCrosshairsList() -{ - int lastlump, lump; - - lastlump = 0; - - FOptionValues **opt = OptionValues.CheckKey(NAME_Crosshairs); - if (opt == NULL) - { - return; // no crosshair value list present. No need to go on. - } - - FOptionValues::Pair *pair = &(*opt)->mValues[(*opt)->mValues.Reserve(1)]; - pair->Value = 0; - pair->Text = "None"; - - while ((lump = fileSystem.FindLump("XHAIRS", &lastlump)) != -1) - { - FScanner sc(lump); - while (sc.GetNumber()) - { - FOptionValues::Pair value; - value.Value = sc.Number; - sc.MustGetString(); - value.Text = sc.String; - if (value.Value != 0) - { // Check if it already exists. If not, add it. - unsigned int i; - - for (i = 1; i < (*opt)->mValues.Size(); ++i) - { - if ((*opt)->mValues[i].Value == value.Value) - { - break; - } - } - if (i < (*opt)->mValues.Size()) - { - (*opt)->mValues[i].Text = value.Text; - } - else - { - (*opt)->mValues.Push(value); - } - } - } - } -} - //============================================================================= // // Initialize the music configuration submenus @@ -1454,44 +1117,61 @@ static void InitCrosshairsList() extern "C" { extern int adl_getBanksCount(); - extern const char* const* adl_getBankNames(); + extern const char *const *adl_getBankNames(); } static void InitMusicMenus() { - FMenuDescriptor** advmenu = MenuDescriptors.CheckKey(NAME_AdvSoundOptions); + DMenuDescriptor **advmenu = MenuDescriptors.CheckKey("AdvSoundOptions"); auto soundfonts = sfmanager.GetList(); - std::tuple sfmenus[] = { - std::make_tuple("FluidPatchsetMenu", SF_SF2, "fluid_patchset") }; + std::tuple sfmenus[] = { std::make_tuple("GusConfigMenu", SF_SF2 | SF_GUS, "midi_config"), + std::make_tuple("WildMidiConfigMenu", SF_GUS, "wildmidi_config"), + std::make_tuple("TimidityConfigMenu", SF_SF2 | SF_GUS, "timidity_config"), + std::make_tuple("FluidPatchsetMenu", SF_SF2, "fluid_patchset"), + std::make_tuple("ADLMIDICustomBanksMenu", SF_WOPL, "adl_custom_bank"), + std::make_tuple("OPNMIDICustomBanksMenu", SF_WOPN, "opn_custom_bank")}; - for (auto& p : sfmenus) + for (auto &p : sfmenus) { - FMenuDescriptor** menu = MenuDescriptors.CheckKey(std::get<0>(p)); + DMenuDescriptor **menu = MenuDescriptors.CheckKey(std::get<0>(p)); if (menu != nullptr) { if (soundfonts.Size() > 0) { - for (auto& entry : soundfonts) + for (auto &entry : soundfonts) { if (entry.type & std::get<1>(p)) { FString display = entry.mName; display.ReplaceChars("_", ' '); - auto it = new FOptionMenuItemCommand (display, FStringf("%s \"%s\"", std::get<2>(p), entry.mName.GetChars())/*, true*/); - static_cast(*menu)->mItems.Push(it); + auto it = CreateOptionMenuItemCommand(display, FStringf("%s \"%s\"", std::get<2>(p), entry.mName.GetChars()), true); + static_cast(*menu)->mItems.Push(it); } } } else if (advmenu != nullptr) { // Remove the item for this submenu - auto d = static_cast(*advmenu); + auto d = static_cast(*advmenu); auto it = d->GetItem(std::get<0>(p)); if (it != nullptr) d->mItems.Delete(d->mItems.Find(it)); } } } + + DMenuDescriptor **menu = MenuDescriptors.CheckKey("ADLBankMenu"); + + if (menu != nullptr) + { + const char* const* adl_bank_names; + int adl_banks_count = ZMusic_GetADLBanks(&adl_bank_names); + for (int i=0; i < adl_banks_count; i++) + { + auto it = CreateOptionMenuItemCommand(adl_bank_names[i], FStringf("adl_bank %d", i), true); + static_cast(*menu)->mItems.Push(it); + } + } } //============================================================================= @@ -1502,18 +1182,14 @@ static void InitMusicMenus() void M_CreateMenus() { - BuildEpisodeMenu(); - InitCrosshairsList(); InitMusicMenus(); - - FOptionValues** opt = OptionValues.CheckKey(NAME_Mididevices); - if (opt != nullptr) + FOptionValues **opt = OptionValues.CheckKey(NAME_Mididevices); + if (opt != nullptr) { I_BuildMIDIMenuList(*opt); } -#ifndef NO_OPENAL opt = OptionValues.CheckKey(NAME_Aldevices); - if (opt != nullptr) + if (opt != nullptr) { I_BuildALDeviceList(*opt); } @@ -1522,39 +1198,7 @@ void M_CreateMenus() { I_BuildALResamplersList(*opt); } -#endif // !NO_OPENAL } -//============================================================================= -// -// Returns the default skill level. -// -//============================================================================= -int M_GetDefaultSkill() -{ - return gDefaultSkill; -} -void FListMenuDescriptor::Reset() -{ - // Reset the default settings (ignore all other values in the struct) - mSelectOfsX = 0; - mSelectOfsY = 0; - mSelector = nullptr; - mDisplayTop = 0; - mXpos = 0; - mYpos = 0; - mLinespacing = 0; - mNetgameMessage = ""; - mFont = NULL; - mFontColor = CR_UNTRANSLATED; - mFontColor2 = CR_UNTRANSLATED; - mScriptId = -1; - mSecondaryId = 0; - mNativeFontNum = NIT_BigFont; - mNativePalNum = NIT_ActiveColor; - mNativeFontScale = 1.f; - mFlags = 0; - mSpacing = 0; -} diff --git a/source/core/menu/menuinput.cpp b/source/core/menu/menuinput.cpp deleted file mode 100644 index 44a5e9a61..000000000 --- a/source/core/menu/menuinput.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/* -** menuinput.cpp -** The string input code -** -**--------------------------------------------------------------------------- -** Copyright 2001-2010 Randy Heit -** Copyright 2010 Christoph Oelckers -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "menu.h" -#include "c_cvars.h" -#include "d_event.h" -#include "d_gui.h" -#include "v_font.h" -#include "v_text.h" -#include "v_draw.h" -#include "v_video.h" - -#define INPUTGRID_WIDTH 13 -#define INPUTGRID_HEIGHT 5 - -// 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"; - - -CVAR(Bool, m_showinputgrid, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - -//============================================================================= -// -// -// -//============================================================================= - -// [TP] Added allowcolors -DTextEnterMenu::DTextEnterMenu(DMenu *parent, FFont *dpf, FString textbuffer, int maxlen, bool showgrid, bool allowcolors) -: DMenu(parent) -{ - mEnterString = textbuffer; - mEnterSize = maxlen; - mInputGridOkay = (showgrid && (m_showinputgrid == 0)) || (m_showinputgrid >= 1); - if (mEnterString.Len() > 0) - { - InputGridX = INPUTGRID_WIDTH - 1; - InputGridY = INPUTGRID_HEIGHT - 1; - } - else - { - // If we are naming a new save, don't start the cursor on "end". - InputGridX = 0; - InputGridY = 0; - } - AllowColors = allowcolors; // [TP] - displayFont = dpf; - CursorSize = displayFont->StringWidth(displayFont->GetCursor()); -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DTextEnterMenu::TranslateKeyboardEvents() -{ - return mInputGridOkay; -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DTextEnterMenu::Responder(event_t *ev) -{ - if (ev->type == EV_GUI_Event) - { - // Save game and player name string input - if (ev->subtype == EV_GUI_Char) - { - mInputGridOkay = false; - AppendChar(ev->data1); - return true; - } - char ch = (char)ev->data1; - if ((ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) && ch == '\b') - { - if (mEnterString.Len() > 0) - { - mEnterString.DeleteLastCharacter(); - } - } - else if (ev->subtype == EV_GUI_KeyDown) - { - if (ch == GK_ESCAPE) - { - DMenu *parent = mParentMenu; - parent->MenuEvent(MKEY_Abort, false); - Close(); - return true; - } - else if (ch == '\r') - { - if (mEnterString.Len() > 0) - { - // [TP] If we allow color codes, colorize the string now. - //if (AllowColors) - //mEnterString = mEnterString.Filter(); - - DMenu *parent = mParentMenu; - parent->MenuEvent(MKEY_Input, false); - Close(); - return true; - } - } - } - if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) - { - return true; - } - } - return Super::Responder(ev); -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DTextEnterMenu::MouseEvent(int type, int x, int y) -{ - const int cell_width = 18 * CleanXfac_1; - const int cell_height = 16 * CleanYfac_1; - const int screen_y = screen->GetHeight() - INPUTGRID_HEIGHT * cell_height; - const int screen_x = (screen->GetWidth() - INPUTGRID_WIDTH * cell_width) / 2; - - if (x >= screen_x && x < screen_x + INPUTGRID_WIDTH * cell_width && y >= screen_y) - { - InputGridX = (x - screen_x) / cell_width; - InputGridY = (y - screen_y) / cell_height; - if (type == DMenu::MOUSE_Release) - { - if (MenuEvent(MKEY_Enter, true)) - { - //M_MenuSound(CursorSound); - if (m_use_mouse == 2) InputGridX = InputGridY = -1; - } - } - return true; - } - else - { - InputGridX = InputGridY = -1; - } - return Super::MouseEvent(type, x, y); -} - -//============================================================================= -// -// -// -//============================================================================= - -void DTextEnterMenu::AppendChar(int ch) -{ - FStringf newstring("%s%c%c", mEnterString.GetChars(), ch, displayFont->GetCursor()); - if (mEnterSize < 0 || displayFont->StringWidth(newstring) < mEnterSize) - { - mEnterString.AppendCharacter(ch); - } -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DTextEnterMenu::MenuEvent (int key, bool fromcontroller) -{ - if (key == MKEY_Back) - { - mParentMenu->MenuEvent(MKEY_Abort, false); - return Super::MenuEvent(key, fromcontroller); - } - if (fromcontroller) - { - mInputGridOkay = true; - } - - if (mInputGridOkay) - { - int ch; - - if (InputGridX == -1 || InputGridY == -1) - { - InputGridX = InputGridY = 0; - } - switch (key) - { - case MKEY_Down: - InputGridY = (InputGridY + 1) % INPUTGRID_HEIGHT; - return true; - - case MKEY_Up: - InputGridY = (InputGridY + INPUTGRID_HEIGHT - 1) % INPUTGRID_HEIGHT; - return true; - - case MKEY_Right: - InputGridX = (InputGridX + 1) % INPUTGRID_WIDTH; - return true; - - case MKEY_Left: - InputGridX = (InputGridX + INPUTGRID_WIDTH - 1) % INPUTGRID_WIDTH; - return true; - - case MKEY_Clear: - if (mEnterString.Len() > 0) - { - mEnterString.DeleteLastCharacter(); - } - return true; - - case MKEY_Enter: - assert(unsigned(InputGridX) < INPUTGRID_WIDTH && unsigned(InputGridY) < INPUTGRID_HEIGHT); - if (mInputGridOkay) - { - ch = InputGridChars[InputGridX + InputGridY * INPUTGRID_WIDTH]; - if (ch == 0) // end - { - if (mEnterString.Len() > 0) - { - DMenu *parent = mParentMenu; - parent->MenuEvent(MKEY_Input, false); - Close(); - return true; - } - } - else if (ch == '\b') // bs - { - if (mEnterString.Len() > 0) - { - mEnterString.DeleteLastCharacter(); - } - } - else - { - AppendChar(ch); - } - } - return true; - - default: - break; // Keep GCC quiet - } - } - return false; -} - -//============================================================================= -// -// -// -//============================================================================= - -void DTextEnterMenu::Drawer () -{ - mParentMenu->Drawer(); - if (mInputGridOkay) - { - const int cell_width = 18 * CleanXfac; - const int cell_height = 12 * CleanYfac; - const int top_padding = cell_height / 2 - displayFont->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. - twod->AddColorOnlyQuad(0 /*screen->GetWidth()/2 - 13 * cell_width / 2*/, - screen->GetHeight() - INPUTGRID_HEIGHT * cell_height, - screen->GetWidth() /*13 * cell_width*/, - INPUTGRID_HEIGHT * cell_height, 0xc8000000); - - if (InputGridX >= 0 && InputGridY >= 0) - { - // Highlight the background behind the selected character. - twod->AddColorOnlyQuad( - InputGridX * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2, - InputGridY * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(), - cell_width, cell_height, PalEntry(255, 255, 248, 220)); - } - - 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]; - auto pic = displayFont->GetChar(ch, CR_DARKGRAY, &width); - EColorRange color; - int remap; - - // The highlighted character is yellow; the rest are dark gray. - color = (x == InputGridX && y == InputGridY) ? CR_YELLOW : CR_DARKGRAY; - remap = displayFont->GetColorTranslation(color); - - if (pic != NULL) - { - // Draw a normal character. - DrawTexture(twod, pic, xx + cell_width/2 - width*CleanXfac_1/2, yy + top_padding, - DTA_TranslationIndex, remap, - DTA_CleanNoMove_1, 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_1 * 3 / 4; - const int x2 = x1 + width * 3 * CleanXfac_1 / 2; - const int y1 = yy + top_padding; - const int y2 = y1 + displayFont->GetHeight() * CleanYfac_1; - auto palcolor = PalEntry(255, 160, 160, 160); - twod->AddColorOnlyQuad(x1, y1, x2 - x1, CleanYfac_1, palcolor); // top - twod->AddColorOnlyQuad(x1, y2, x2 - x1, CleanYfac_1, palcolor); // bottom - twod->AddColorOnlyQuad(x1, y1+CleanYfac_1, CleanXfac_1, y2 - y1, palcolor); // left - twod->AddColorOnlyQuad(x2-CleanXfac_1, y1+CleanYfac_1, CleanXfac_1, CleanYfac_1, palcolor); // right - } - else if (ch == '\b' || ch == 0) - { - // Draw the backspace and end "characters". - const char *const str = ch == '\b' ? "BS" : "ED"; - DrawText(twod, NewSmallFont, color, - xx + cell_width/2 - displayFont->StringWidth(str)*CleanXfac_1/2, - yy + top_padding, str, DTA_CleanNoMove_1, true, TAG_DONE); - } - } - } - } - Super::Drawer(); -} \ No newline at end of file diff --git a/source/core/menu/messagebox.cpp b/source/core/menu/messagebox.cpp index ddef3f5d9..8b43d8d76 100644 --- a/source/core/menu/messagebox.cpp +++ b/source/core/menu/messagebox.cpp @@ -32,87 +32,26 @@ ** */ +#include #include "menu.h" -#include "d_event.h" -#include "d_gui.h" -#include "v_text.h" -#include "v_draw.h" #include "gstrings.h" +#include "i_video.h" #include "c_dispatch.h" -#include "statistics.h" -#include "v_2ddrawer.h" -#include "v_video.h" -#include "i_time.h" -#include "engineerrors.h" +#include "vm.h" +#include "menustate.h" -extern FSaveGameNode *quickSaveSlot; +void M_StartControlPanel(bool makeSound, bool scaleoverride = false); +FName MessageBoxClass = NAME_MessageBoxMenu; +CVAR(Bool, m_quickexit, false, CVAR_ARCHIVE) -void GameInterface::DrawCenteredTextScreen(const DVector2& origin, const char* text, int position, bool bg) +typedef void(*hfunc)(); +DEFINE_ACTION_FUNCTION(DMessageBoxMenu, CallHandler) { - double scale = SmallFontScale(); - int formatwidth = int(320 / scale); - auto lines = V_BreakLines(SmallFont, formatwidth, text, true); - auto fheight = bg? 10 : SmallFont->GetHeight()* scale; // Fixme: Get spacing for text pages from elsewhere. - if (!bg) - { - auto totaltextheight = lines.Size() * fheight; - position -= totaltextheight / 2; - } - - double y = origin.Y + position; - for (auto& line : lines) - { - double x = origin.X + 160 - line.Width * scale * 0.5; - DrawText(twod, SmallFont, CR_UNTRANSLATED, x, y, line.Text, DTA_FullscreenScale, FSMode_Fit320x200, DTA_ScaleX, scale, DTA_ScaleY, scale, TAG_DONE); - y += fheight; - } -} - -class DMessageBoxMenu : public DMenu -{ - using Super = DMenu; - FString mFullMessage; - TArray mMessage; - int mMessageMode; - int messageSelection; - int mMouseLeft, mMouseRight, mMouseY; - FName mAction; - std::function mActionFunc; - -public: - - DMessageBoxMenu(DMenu *parent = NULL, const char *message = NULL, int messagemode = 0, bool playsound = false, FName action = NAME_None, hFunc handler = nullptr); - void Destroy(); - void Init(DMenu *parent, const char *message, int messagemode, bool playsound = false); - void Drawer(); - bool Responder(event_t *ev); - bool MenuEvent(int mkey, bool fromcontroller); - bool MouseEvent(int type, int x, int y); - void CloseSound(); - virtual void HandleResult(bool res); -}; - - -//============================================================================= -// -// -// -//============================================================================= - -DMessageBoxMenu::DMessageBoxMenu(DMenu *parent, const char *message, int messagemode, bool playsound, FName action, hFunc handler) -: DMenu(parent) -{ - mAction = action; - mActionFunc = handler; - messageSelection = 0; - mMouseLeft = 140; - mMouseY = INT_MIN; - int mr1 = 170 + SmallFont->StringWidth(GStrings["TXT_YES"]); - int mr2 = 170 + SmallFont->StringWidth(GStrings["TXT_NO"]); - mMouseRight = std::max(mr1, mr2); - - Init(parent, message, messagemode, playsound); + PARAM_PROLOGUE; + PARAM_POINTERTYPE(Handler, hfunc); + Handler(); + return 0; } //============================================================================= @@ -121,20 +60,20 @@ DMessageBoxMenu::DMessageBoxMenu(DMenu *parent, const char *message, int message // //============================================================================= -void DMessageBoxMenu::Init(DMenu *parent, const char *message, int messagemode, bool playsound) +DMenu *CreateMessageBoxMenu(DMenu *parent, const char *message, int messagemode, bool playsound, FName action = NAME_None, hfunc handler = nullptr) { - mParentMenu = parent; - if (message != NULL) + auto c = PClass::FindClass(MessageBoxClass); + if (!c->IsDescendantOf(NAME_MessageBoxMenu)) c = PClass::FindClass(NAME_MessageBoxMenu); + auto p = c->CreateNew(); + FString namestr = message; + + IFVIRTUALPTRNAME(p, NAME_MessageBoxMenu, Init) { - mFullMessage = message; - mMessage = V_BreakLines(SmallFont, 300, GStrings.localize(message)); - } - mMessageMode = messagemode; - if (playsound) - { - //S_StopSound (CHAN_VOICE); - //S_Sound (CHAN_VOICE | CHANF_UI, "menu/prompt", snd_menuvolume, ATTN_NONE); + VMValue params[] = { p, parent, &namestr, messagemode, playsound, action.GetIndex(), reinterpret_cast(handler) }; + VMCall(func, params, countof(params), nullptr, 0); + return (DMenu*)p; } + return nullptr; } //============================================================================= @@ -143,326 +82,24 @@ void DMessageBoxMenu::Init(DMenu *parent, const char *message, int messagemode, // //============================================================================= -void DMessageBoxMenu::Destroy() -{ - mMessage.Reset(); -} - -//============================================================================= -// -// -// -//============================================================================= - -void DMessageBoxMenu::CloseSound() -{ - M_MenuSound(CurrentMenu ? BackSound : ::CloseSound); -} - -//============================================================================= -// -// -// -//============================================================================= - -void DMessageBoxMenu::HandleResult(bool res) -{ - if (mMessageMode == 0) - { - if (mActionFunc) - { - if (mActionFunc(res)) Close(); - } - else if (mAction == NAME_None && mParentMenu) - { - mParentMenu->MenuEvent(res ? MKEY_MBYes : MKEY_MBNo, false); - Close(); - } - else - { - Close(); - if (res) M_SetMenu(mAction, -1); - } - CloseSound(); - } -} - -//============================================================================= -// -// -// -//============================================================================= -CVAR(Bool, m_generic_messagebox, false, CVAR_ARCHIVE) - -void DMessageBoxMenu::Drawer() -{ - int y; - PalEntry fade = 0; - - int fontheight = SmallFont->GetHeight(); - //V_SetBorderNeedRefresh(); - //ST_SetNeedRefresh(); - - y = 100; - - if (m_generic_messagebox) - { - if (mMessage.Size()) - { - for (unsigned i = 0; i < mMessage.Size(); i++) - y -= SmallFont->GetHeight() / 2; - - for (unsigned i = 0; i < mMessage.Size(); i++) - { - DrawText(twod, SmallFont, CR_UNTRANSLATED, 160 - mMessage[i].Width / 2, y, mMessage[i].Text, - DTA_Clean, true, TAG_DONE); - y += fontheight; - } - } - - if (mMessageMode == 0) - { - y += fontheight; - mMouseY = y; - DrawText(twod, SmallFont, - messageSelection == 0 ? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, - 160, y, GStrings["TXT_YES"], DTA_Clean, true, TAG_DONE); - DrawText(twod, SmallFont, - messageSelection == 1 ? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, - 160, y + fontheight + 1, GStrings["TXT_NO"], DTA_Clean, true, TAG_DONE); - - if (messageSelection >= 0) - { - auto time = I_msTime() / 30; - if (((time >> 2) % 8) < 6) - { - DrawText(twod, SmallFont, OptionSettings.mFontColorSelection, - (150 - 160) * CleanXfac + screen->GetWidth() / 2, - (y + (fontheight + 1) * messageSelection - 100 + fontheight / 2 - 5) * CleanYfac + screen->GetHeight() / 2, - "\xd", - DTA_CellX, 8 * CleanXfac, - DTA_CellY, 8 * CleanYfac, - TAG_DONE); - } - } - } - } - else - { - twod->ClearScreen(0xa0000000); - gi->DrawCenteredTextScreen(origin, mFullMessage, 100, false); - } -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DMessageBoxMenu::Responder(event_t *ev) -{ - if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown) - { - if (mMessageMode == 0) - { - int ch = tolower(ev->data1); - if (ch == 'n' || ch == ' ') - { - HandleResult(false); - return true; - } - else if (ch == 'y') - { - HandleResult(true); - return true; - } - } - else - { - Close(); - return true; - } - return false; - } - else if (ev->type == EV_KeyDown) - { - Close(); - return true; - } - return Super::Responder(ev); -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DMessageBoxMenu::MenuEvent(int mkey, bool fromcontroller) -{ - if (mMessageMode == 0) - { - if ((mkey == MKEY_Up || mkey == MKEY_Down) && m_generic_messagebox) - { - //S_Sound (CHAN_VOICE | CHANF_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); - messageSelection = !messageSelection; - return true; - } - else if (mkey == MKEY_Enter) - { - // 0 is yes, 1 is no - HandleResult(!messageSelection); - return true; - } - else if (mkey == MKEY_Back) - { - HandleResult(false); - return true; - } - return false; - } - else - { - Close(); - CloseSound(); - return true; - } -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DMessageBoxMenu::MouseEvent(int type, int x, int y) -{ - if (mMessageMode == 1 || m_generic_messagebox) - { - if (type == MOUSE_Click) - { - return MenuEvent(MKEY_Enter, true); - } - return false; - } - else - { - int sel = -1; - int fh = SmallFont->GetHeight() + 1; - - // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture - x = ((x - (screen->GetWidth() / 2)) / CleanXfac) + 160; - y = ((y - (screen->GetHeight() / 2)) / CleanYfac) + 100; - - if (x >= mMouseLeft && x <= mMouseRight && y >= mMouseY && y < mMouseY + 2 * fh) - { - sel = y >= mMouseY + fh; - } - if (sel != -1 && sel != messageSelection) - { - M_MenuSound(CursorSound); - } - messageSelection = sel; - if (type == MOUSE_Release) - { - return MenuEvent(MKEY_Enter, true); - } - return true; - } -} - -//============================================================================= -// -// -// -//============================================================================= - -//============================================================================= -// -// -// -//============================================================================= - -void M_StartMessage(const char *message, int messagemode, int scriptId, FName action) +void M_StartMessage(const char *message, int messagemode, FName action) { if (CurrentMenu == NULL) { // only play a sound if no menu was active before M_StartControlPanel(menuactive == MENU_Off); } - DMenu *newmenu = new DMessageBoxMenu(CurrentMenu, message, messagemode, false, action); + DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, message, messagemode, false, action); newmenu->mParentMenu = CurrentMenu; - newmenu->scriptID = scriptId; M_ActivateMenu(newmenu); } - -//============================================================================= -// -// -// -//============================================================================= - -DMenu* CreateMessageBoxMenu(DMenu* parent, const char* message, int messagemode, int scriptId, bool playsound, FName action, hFunc handler) +DEFINE_ACTION_FUNCTION(DMenu, StartMessage) { - auto newmenu = new DMessageBoxMenu(CurrentMenu, message, messagemode, false, action, handler); - newmenu->scriptID = scriptId; - return newmenu; - + PARAM_PROLOGUE; + PARAM_STRING(msg); + PARAM_INT(mode); + PARAM_NAME(action); + M_StartMessage(msg, mode, action); + return 0; } - - -void ActivateEndGameMenu() -{ -} - -CCMD (menu_endgame) -{ // F7 - if (!gi->CanSave()) - { - return; - } - - M_StartControlPanel (true); - FString tempstring; - tempstring << GStrings("ENDGAME") << "\n\n" << GStrings("PRESSYN"); - DMenu* newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, 501, false, NAME_None, [](bool res) - { - if (res) - { - STAT_Cancel(); - M_ClearMenus(); - gi->QuitToTitle(); - return false; - } - return true; - }); - - M_ActivateMenu(newmenu); -} - -//============================================================================= -// -// -// -//============================================================================= - -CCMD (menu_quit) -{ // F10 - - M_StartControlPanel (true); - - FString EndString; - EndString << GStrings("CONFIRM_QUITMSG") << "\n\n" << GStrings("PRESSYN"); - - DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, EndString, 0, 500, false, NAME_None, [](bool res) - { - if (res) gi->ExitFromMenu(); - return true; - }); - - M_ActivateMenu(newmenu); -} - - diff --git a/source/core/menu/optionmenu.cpp b/source/core/menu/optionmenu.cpp index acadaba78..99069d176 100644 --- a/source/core/menu/optionmenu.cpp +++ b/source/core/menu/optionmenu.cpp @@ -3,7 +3,7 @@ ** Handler class for the option menus and associated items ** **--------------------------------------------------------------------------- -** Copyright 2010 Christoph Oelckers +** Copyright 2010-2017 Christoph Oelckers ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -32,54 +32,10 @@ ** */ -#include "v_font.h" -#include "cmdlib.h" -#include "gstrings.h" -#include "d_gui.h" -#include "d_event.h" -#include "c_dispatch.h" -#include "c_console.h" -#include "c_cvars.h" -#include "c_bind.h" -#include "gameconfigfile.h" -#include "menu.h" -#include "v_draw.h" -#include "v_2ddrawer.h" #include "v_video.h" -#include "i_time.h" +#include "menu.h" +#include "vm.h" -//============================================================================= -// -// Draws a string in the console font, scaled to the 8x8 cells -// used by the default console font. -// -//============================================================================= - -FFont *OptionFont() -{ - return NewSmallFont; -} - -int OptionHeight() -{ - return OptionFont()->GetHeight(); -} - -int OptionWidth(const char * s) -{ - return OptionFont()->StringWidth(s); -} - -void DrawOptionText(int x, int y, int color, const char *text, bool grayed) -{ - PalEntry overlay = grayed? PalEntry(96,48,0,0) : PalEntry(0,0,0); - DrawText (twod, OptionFont(), color, x, y, text, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_END); -} - -int DOptionMenu::GetPosition() -{ - return mDesc->mPosition * screen->GetHeight() * 2 / CleanYfac_1 / 1080; // y position uses a 1920x1080 screen as reference but has to adjust to scaled 320x200 content. -} //============================================================================= // @@ -87,483 +43,26 @@ int DOptionMenu::GetPosition() // //============================================================================= -DOptionMenu::DOptionMenu(DMenu *parent, FOptionMenuDescriptor *desc) -: DMenu(parent) -{ - CanScrollUp = false; - CanScrollDown = false; - VisBottom = 0; - mFocusControl = NULL; - Init(parent, desc); -} - -//============================================================================= -// -// -// -//============================================================================= - -void DOptionMenu::Init(DMenu *parent, FOptionMenuDescriptor *desc) -{ - mParentMenu = parent; - mDesc = desc; - if (mDesc != NULL && mDesc->mSelectedItem == -1) mDesc->mSelectedItem = FirstSelectable(); - -} - -//============================================================================= -// -// -// -//============================================================================= - -int DOptionMenu::FirstSelectable() -{ - if (mDesc != NULL) - { - // Go down to the first selectable item - int i = -1; - do - { - i++; - } - while (i < (int)mDesc->mItems.Size() && !mDesc->mItems[i]->Selectable()); - if (i>=0 && i < (int)mDesc->mItems.Size()) return i; - } - return -1; -} - -//============================================================================= -// -// -// -//============================================================================= - -FOptionMenuItem *DOptionMenu::GetItem(FName name) -{ - for(unsigned i=0;imItems.Size(); i++) - { - FName nm = mDesc->mItems[i]->GetAction(NULL); - if (nm == name) return mDesc->mItems[i]; - } - return NULL; -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DOptionMenu::Responder (event_t *ev) -{ - if (ev->type == EV_GUI_Event) - { - if (ev->subtype == EV_GUI_WheelUp) - { - int scrollamt = std::min(2, mDesc->mScrollPos); - mDesc->mScrollPos -= scrollamt; - return true; - } - else if (ev->subtype == EV_GUI_WheelDown) - { - if (CanScrollDown) - { - if (VisBottom < (int)(mDesc->mItems.Size()-2)) - { - mDesc->mScrollPos += 2; - VisBottom += 2; - } - else - { - mDesc->mScrollPos++; - VisBottom++; - } - } - return true; - } - } - return Super::Responder(ev); -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DOptionMenu::MenuEvent (int mkey, bool fromcontroller) -{ - int startedAt = mDesc->mSelectedItem; - - switch (mkey) - { - case MKEY_Up: - if (mDesc->mSelectedItem == -1) - { - mDesc->mSelectedItem = FirstSelectable(); - break; - } - do - { - --mDesc->mSelectedItem; - - if (mDesc->mScrollPos > 0 && - mDesc->mSelectedItem <= mDesc->mScrollTop + mDesc->mScrollPos) - { - mDesc->mScrollPos = std::max(mDesc->mSelectedItem - mDesc->mScrollTop - 1, 0); - } - - if (mDesc->mSelectedItem < 0) - { - // Figure out how many lines of text fit on the menu - int y = GetPosition(); - - y *= CleanYfac_1; - int rowheight = OptionSettings.mLinespacing * CleanYfac_1; - int maxitems = (screen->GetHeight() - rowheight - y) / rowheight + 1; - - mDesc->mScrollPos = std::max(0, (int)mDesc->mItems.Size() - maxitems + mDesc->mScrollTop); - mDesc->mSelectedItem = mDesc->mItems.Size()-1; - } - } - while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); - break; - - case MKEY_Down: - if (mDesc->mSelectedItem == -1) - { - mDesc->mSelectedItem = FirstSelectable(); - break; - } - do - { - ++mDesc->mSelectedItem; - - if (CanScrollDown && mDesc->mSelectedItem == VisBottom) - { - mDesc->mScrollPos++; - VisBottom++; - } - if (mDesc->mSelectedItem >= (int)mDesc->mItems.Size()) - { - if (startedAt == -1) - { - mDesc->mSelectedItem = -1; - mDesc->mScrollPos = -1; - break; - } - else - { - mDesc->mSelectedItem = 0; - mDesc->mScrollPos = 0; - } - } - } - while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); - break; - - case MKEY_PageUp: - if (mDesc->mScrollPos > 0) - { - mDesc->mScrollPos -= VisBottom - mDesc->mScrollPos - mDesc->mScrollTop; - if (mDesc->mScrollPos < 0) - { - mDesc->mScrollPos = 0; - } - if (mDesc->mSelectedItem != -1) - { - mDesc->mSelectedItem = mDesc->mScrollTop + mDesc->mScrollPos + 1; - while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable()) - { - if (++mDesc->mSelectedItem >= (int)mDesc->mItems.Size()) - { - mDesc->mSelectedItem = 0; - } - } - if (mDesc->mScrollPos > mDesc->mSelectedItem) - { - mDesc->mScrollPos = mDesc->mSelectedItem; - } - } - } - break; - - case MKEY_PageDown: - if (CanScrollDown) - { - int pagesize = VisBottom - mDesc->mScrollPos - mDesc->mScrollTop; - mDesc->mScrollPos += pagesize; - if (mDesc->mScrollPos + mDesc->mScrollTop + pagesize > (int)mDesc->mItems.Size()) - { - mDesc->mScrollPos = mDesc->mItems.Size() - mDesc->mScrollTop - pagesize; - } - if (mDesc->mSelectedItem != -1) - { - mDesc->mSelectedItem = mDesc->mScrollTop + mDesc->mScrollPos; - while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable()) - { - if (++mDesc->mSelectedItem >= (int)mDesc->mItems.Size()) - { - mDesc->mSelectedItem = 0; - } - } - if (mDesc->mScrollPos > mDesc->mSelectedItem) - { - mDesc->mScrollPos = mDesc->mSelectedItem; - } - } - } - break; - - case MKEY_Enter: - if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate(mDesc->mMenuName)) - { - return true; - } - // fall through to default - default: - if (mDesc->mSelectedItem >= 0 && - mDesc->mItems[mDesc->mSelectedItem]->MenuEvent(mkey, fromcontroller)) return true; - return Super::MenuEvent(mkey, fromcontroller); - } - - if (mDesc->mSelectedItem != startedAt) - { - M_MenuSound(CursorSound); - } - return true; -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DOptionMenu::MouseEvent(int type, int x, int y) -{ - y = (y / CleanYfac_1) - mDesc->mDrawTop; - - if (mFocusControl) - { - mFocusControl->MouseEvent(type, x, y); - return true; - } - else - { - int yline = (y / OptionSettings.mLinespacing); - if (yline >= mDesc->mScrollTop) - { - yline += mDesc->mScrollPos; - } - if ((unsigned)yline < mDesc->mItems.Size() && mDesc->mItems[yline]->Selectable()) - { - if (yline != mDesc->mSelectedItem) - { - mDesc->mSelectedItem = yline; - //M_MenuSound(CursorSound); too noisy - - } - mDesc->mItems[yline]->MouseEvent(type, x, y); - return true; - } - } - mDesc->mSelectedItem = -1; - return Super::MouseEvent(type, x, y); -} - -//============================================================================= -// -// -// -//============================================================================= - -void DOptionMenu::Ticker () -{ - Super::Ticker(); - for(unsigned i=0;imItems.Size(); i++) - { - mDesc->mItems[i]->Ticker(); - } -} - -//============================================================================= -// -// -// -//============================================================================= -int DOptionMenu::GetIndent() -{ - int indent = std::max(0, (mDesc->mIndent + 40) - CleanWidth_1 / 2); - return screen->GetWidth() / 2 + indent * CleanXfac_1; -} - -void DOptionMenu::Drawer () -{ - int y = GetPosition(); - - if (mDesc->mTitle.IsNotEmpty()) - { - gi->DrawMenuCaption(origin, GStrings.localize(mDesc->mTitle)); - } - mDesc->mDrawTop = y; - int fontheight = OptionSettings.mLinespacing * CleanYfac_1; - y *= CleanYfac_1; - - int indent = GetIndent(); - - int ytop = y + mDesc->mScrollTop * 8 * CleanYfac_1; - int lastrow = screen->GetHeight() - OptionFont()->GetHeight() * CleanYfac_1; - - unsigned i; - for (i = 0; i < mDesc->mItems.Size() && y <= lastrow; i++, y += fontheight) - { - // Don't scroll the uppermost items - if ((int)i == mDesc->mScrollTop) - { - i += mDesc->mScrollPos; - if (i >= mDesc->mItems.Size()) break; // skipped beyond end of menu - } - bool isSelected = mDesc->mSelectedItem == (int)i; - int cur_indent = mDesc->mItems[i]->Draw(mDesc, y, indent, isSelected); - if (cur_indent >= 0 && isSelected && mDesc->mItems[i]->Selectable()) - { - auto time = I_msTime() / 30; - if ((((time>>2)%8) < 6) || CurrentMenu != this) - { - DrawOptionText(cur_indent + 3 * CleanXfac_1, y, OptionSettings.mFontColorSelection, "â—„"); - //M_DrawConText(OptionSettings.mFontColorSelection, cur_indent + 3 * CleanXfac_1, y+fontheight-9*CleanYfac_1, "\xd"); - } - } - } - - CanScrollUp = (mDesc->mScrollPos > 0); - CanScrollDown = (i < mDesc->mItems.Size()); - VisBottom = i - 1; - - if (CanScrollUp) - { - DrawOptionText(screen->GetWidth() - 11 * CleanXfac_1, ytop, OptionSettings.mFontColorSelection, "â–²"); - //M_DrawConText(CR_ORANGE, 3 * CleanXfac_1, ytop, "\x1a"); - } - if (CanScrollDown) - { - DrawOptionText(screen->GetWidth() - 11 * CleanXfac_1 , y - 8*CleanYfac_1, OptionSettings.mFontColorSelection, "â–¼"); - //M_DrawConText(CR_ORANGE, 3 * CleanXfac_1, y - 8*CleanYfac_1, "\x1b"); - } - Super::Drawer(); -} - - -//============================================================================= -// -// base class for menu items -// -//============================================================================= - -FOptionMenuItem::~FOptionMenuItem() -{ -} - -int FOptionMenuItem::Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) -{ - return indent; -} - -bool FOptionMenuItem::Selectable() -{ - return true; -} - -bool FOptionMenuItem::MouseEvent(int type, int x, int y) -{ - if (Selectable() && type == DMenu::MOUSE_Release) - { - return CurrentMenu->MenuEvent(MKEY_Enter, true); - } - return false; -} - - -int FOptionMenuItem::GetIndent() -{ - if (mCentered) return 0; - if (screen->GetWidth() < 640) return screen->GetWidth() / 2; - return OptionWidth(GStrings.localize(mLabel)); -} - -void FOptionMenuItem::drawText(int x, int y, int color, const char * text, bool grayed) -{ - DrawOptionText(x, y, color, text, grayed); -} - -int FOptionMenuItem::drawLabel(int indent, int y, EColorRange color, bool grayed) -{ - const char *label = GStrings.localize(mLabel); - int x; - int w = OptionWidth(label) * CleanXfac_1; - if (!mCentered) x = indent - w; - else x = (screen->GetWidth() - w) / 2; - DrawOptionText(x, y, color, label, grayed); - return x; -} - -void FOptionMenuItem::drawValue(int indent, int y, int color, const char *text, bool grayed) -{ - DrawOptionText(indent + CursorSpace(), y, color, text, grayed); -} - -int FOptionMenuItem::CursorSpace() -{ - return (14 * CleanXfac_1); -} - -void FOptionMenuDescriptor::CalcIndent() -{ - // calculate the menu indent - int widest = 0, thiswidth; - - for (unsigned i = 0; i < mItems.Size(); i++) - { - thiswidth = mItems[i]->GetIndent(); - if (thiswidth > widest) widest = thiswidth; - } - mIndent = widest + 4; -} - -//============================================================================= -// -// -// -//============================================================================= - -FOptionMenuItem *FOptionMenuDescriptor::GetItem(FName name) +DMenuItemBase *DOptionMenuDescriptor::GetItem(FName name) { for(unsigned i=0;iGetAction(NULL); + FName nm = mItems[i]->mAction; if (nm == name) return mItems[i]; } return NULL; } -class PlayerMenu : public DOptionMenu +void SetCVarDescription(FBaseCVar* cvar, const FString* label) { - using Super = DOptionMenu; - -public: - void Drawer() - { - // Hack: The team item is #3. This part doesn't work properly yet. - gi->DrawPlayerSprite(origin, (mDesc->mSelectedItem == 3)); - Super::Drawer(); - } -}; - -static TMenuClassDescriptor _ppm("NewPlayerMenu"); - -void RegisterOptionMenus() -{ - menuClasses.Push(&_ppm); + cvar->AddDescription(*label); } + +DEFINE_ACTION_FUNCTION_NATIVE(_OptionMenuItemOption, SetCVarDescription, SetCVarDescription) +{ + PARAM_PROLOGUE; + PARAM_POINTER(cv, FBaseCVar); + PARAM_STRING(label); + SetCVarDescription(cv, &label); + return 0; +} \ No newline at end of file diff --git a/source/core/menu/optionmenuitems.h b/source/core/menu/optionmenuitems.h deleted file mode 100644 index 28d922cd3..000000000 --- a/source/core/menu/optionmenuitems.h +++ /dev/null @@ -1,1003 +0,0 @@ -/* -** optionmenuitems.h -** Control items for option menus -** -**--------------------------------------------------------------------------- -** Copyright 2010 Christoph Oelckers -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ -#include "v_text.h" -#include "v_draw.h" -#include "gstrings.h" -#include "v_font.h" -#include "cmdlib.h" -#include "templates.h" - - -void M_DrawConText (int color, int x, int y, const char *str); -void M_SetVideoMode(); - - - -//============================================================================= -// -// opens a submenu, action is a submenu name -// -//============================================================================= - -class FOptionMenuItemSubmenu : public FOptionMenuItem -{ - int mParam; -public: - FOptionMenuItemSubmenu(const char *label, const char *menu, int param = 0) - : FOptionMenuItem(label, menu) - { - mParam = param; - } - - int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override - { - drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColorMore); - return indent; - } - - bool Activate(FName caller) override - { - M_MenuSound(CursorSound); - M_SetMenu(mAction, mParam, static_cast(CurrentMenu)->GetDescriptor()->mMenuName); - return true; - } -}; - - -//============================================================================= -// -// opens a submenu, command is a submenu name -// -//============================================================================= - -class FOptionMenuItemLabeledSubmenu : public FOptionMenuItemSubmenu -{ - FBaseCVar *mLabelCVar; -public: - FOptionMenuItemLabeledSubmenu(const char * label, FBaseCVar *labelcvar, FName command, int param = 0) - : FOptionMenuItemSubmenu(label, command.GetChars(), param) - { - mLabelCVar = labelcvar; - } - - int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override - { - drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor); - - auto text = mLabelCVar? mLabelCVar->GetHumanString() : ""; - if (text[0] == 0) text = GStrings("notset"); - drawValue(indent, y, OptionSettings.mFontColorValue, text); - return indent; - } -}; - - -//============================================================================= -// -// Executes a CCMD, action is a CCMD name -// -//============================================================================= - -class FOptionMenuItemCommand : public FOptionMenuItemSubmenu -{ -public: - FOptionMenuItemCommand(const char *label, const char *menu) - : FOptionMenuItemSubmenu(label, menu) - { - } - - bool Activate(FName caller) override - { - M_MenuSound(AdvanceSound); - C_DoCommand(mAction.GetChars()); - return true; - } - -}; - -//=============================================================================* -// -// Executes a CCMD after confirmation, action is a CCMD name -// -//============================================================================= - -class FOptionMenuItemSafeCommand : public FOptionMenuItemCommand -{ - // action is a CCMD - FString mPrompt; - int mScriptId; // shouldn't be used, but just in case. -public: - FOptionMenuItemSafeCommand(const char *label, const char *menu, const char *prompt = nullptr, int scriptid = INT_MAX) - : FOptionMenuItemCommand(label, menu) - { - mPrompt = prompt; - mScriptId = scriptid; - } - - bool MenuEvent (int mkey, bool fromcontroller) override - { - if (mkey == MKEY_MBYes) - { - C_DoCommand(mAction.GetChars()); - return true; - } - return FOptionMenuItemCommand::MenuEvent(mkey, fromcontroller); - } - - bool Activate(FName caller) override - { - FString msg; - msg << GStrings.localize(mPrompt.IsNotEmpty()? mPrompt.GetChars() : "$SAFEMESSAGE") << "\n\n" << GStrings.localize("$PRESSYN"); - auto actionLabel = GStrings.localize(mLabel.GetChars()); - - //FStringf FullString("%s%s%s\n\n%s", TEXTCOLOR_WHITE, actionLabel, TEXTCOLOR_NORMAL, msg); - FStringf FullString("- %s -\n%s", actionLabel, msg.GetChars()); - M_StartMessage(FullString, 0, mScriptId); - return true; - } -}; - -//============================================================================= -// -// Base class for option lists -// -//============================================================================= - -class FOptionMenuItemOptionBase : public FOptionMenuItem -{ -protected: - // action is a CVAR - FName mValues; // Entry in OptionValues table - FBaseCVar *mGrayCheck; - int mCenter; -public: - - enum - { - OP_VALUES = 0x11001 - }; - - FOptionMenuItemOptionBase(const char *label, const char *menu, const char *values, const char *graycheck, int center) - : FOptionMenuItem(label, menu) - { - mValues = values; - mGrayCheck = (FBoolCVar*)FindCVar(graycheck, NULL); - mCenter = center; - } - - bool SetString(int i, const char *newtext) override - { - if (i == OP_VALUES) - { - FOptionValues **opt = OptionValues.CheckKey(newtext); - mValues = newtext; - if (opt != NULL && *opt != NULL) - { - int s = GetSelection(); - if (s >= (int)(*opt)->mValues.Size()) s = 0; - SetSelection(s); // readjust the CVAR if its value is outside the range now - return true; - } - } - return false; - } - - - - //============================================================================= - virtual int GetSelection() = 0; - virtual void SetSelection(int Selection) = 0; - - //============================================================================= - int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override - { - if (mCenter) - { - indent = (twod->GetWidth() / 2); - } - drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, isGrayed()); - - int Selection = GetSelection(); - const char *text; - FOptionValues **opt = OptionValues.CheckKey(mValues); - if (Selection < 0 || opt == NULL || *opt == NULL) - { - text = "Unknown"; - } - else - { - text = (*opt)->mValues[Selection].Text; - } - if (*text == '$') text = GStrings(text + 1); - - drawValue(indent, y, OptionSettings.mFontColorValue, text, isGrayed()); - return indent; - } - - //============================================================================= - bool MenuEvent (int mkey, bool fromcontroller) override - { - FOptionValues **opt = OptionValues.CheckKey(mValues); - if (opt != NULL && *opt != NULL && (*opt)->mValues.Size() > 0) - { - int Selection = GetSelection(); - if (mkey == MKEY_Left) - { - if (Selection == -1) Selection = 0; - else if (--Selection < 0) Selection = (*opt)->mValues.Size()-1; - } - else if (mkey == MKEY_Right || mkey == MKEY_Enter) - { - if (++Selection >= (int)(*opt)->mValues.Size()) Selection = 0; - } - else - { - return FOptionMenuItem::MenuEvent(mkey, fromcontroller); - } - SetSelection(Selection); - M_MenuSound(ChangeSound); - } - return true; - } - - virtual bool isGrayed() - { - return mGrayCheck != NULL && !(mGrayCheck->GetGenericRep(CVAR_Bool).Bool); - } - - bool Selectable() override - { - return !isGrayed(); - } -}; - -//============================================================================= -// -// Change a CVAR, action is the CVAR name -// -//============================================================================= - -class FOptionMenuItemOption : public FOptionMenuItemOptionBase -{ - // action is a CVAR - FBaseCVar *mCVar; -public: - - FOptionMenuItemOption(const char *label, const char *menu, const char *values, const char *graycheck, int center) - : FOptionMenuItemOptionBase(label, menu, values, graycheck, center) - { - mCVar = FindCVar(mAction.GetChars(), NULL); - } - - //============================================================================= - int GetSelection() override - { - int Selection = -1; - FOptionValues **opt = OptionValues.CheckKey(mValues); - if (opt != NULL && *opt != NULL && mCVar != NULL && (*opt)->mValues.Size() > 0) - { - if ((*opt)->mValues[0].TextValue.IsEmpty()) - { - UCVarValue cv = mCVar->GetGenericRep(CVAR_Float); - for(unsigned i = 0; i < (*opt)->mValues.Size(); i++) - { - if (fabs(cv.Float - (*opt)->mValues[i].Value) < FLT_EPSILON) - { - Selection = i; - break; - } - } - } - else - { - UCVarValue cv = mCVar->GetGenericRep(CVAR_String); - for(unsigned i = 0; i < (*opt)->mValues.Size(); i++) - { - if ((*opt)->mValues[i].TextValue.CompareNoCase(cv.String) == 0) - { - Selection = i; - break; - } - } - } - } - return Selection; - } - - void SetSelection(int Selection) override - { - UCVarValue value; - FOptionValues **opt = OptionValues.CheckKey(mValues); - if (opt != NULL && *opt != NULL && mCVar != NULL && (*opt)->mValues.Size() > 0) - { - if ((*opt)->mValues[0].TextValue.IsEmpty()) - { - value.Float = (float)(*opt)->mValues[Selection].Value; - mCVar->SetGenericRep (value, CVAR_Float); - } - else - { - value.String = (*opt)->mValues[Selection].TextValue.LockBuffer(); - mCVar->SetGenericRep (value, CVAR_String); - (*opt)->mValues[Selection].TextValue.UnlockBuffer(); - } - } - } -}; - -//============================================================================= -// -// This class is used to capture the key to be used as the new key binding -// for a control item -// -//============================================================================= - -class DEnterKey : public DMenu -{ - int *pKey; - -public: - DEnterKey(DMenu *parent, int *keyptr) - : DMenu(parent) - { - pKey = keyptr; - SetMenuMessage(1); - menuactive = MENU_WaitKey; // There should be a better way to disable GUI capture... - } - - bool TranslateKeyboardEvents() override - { - return false; - } - - void SetMenuMessage(int which) - { - DOptionMenu *m = static_cast(mParentMenu); - if (m) - { - FListMenuItem *it = m->GetItem(NAME_Controlmessage); - if (it != NULL) - { - it->SetValue(0, which); - } - } - } - - bool Responder(event_t *ev) override - { - if (ev->type == EV_KeyDown) - { - *pKey = ev->data1; - menuactive = MENU_On; - SetMenuMessage(0); - auto p = mParentMenu; - Close(); - p->MenuEvent((ev->data1 == KEY_ESCAPE)? MKEY_Abort : MKEY_Input, 0); - return true; - } - return false; - } - - void Drawer() override - { - mParentMenu->Drawer(); - } -}; - -//============================================================================= -// -// // Edit a key binding, Action is the CCMD to bind -// -//============================================================================= - -class FOptionMenuItemControl : public FOptionMenuItem -{ - FKeyBindings *mBindings; - int mInput; - bool mWaiting; -public: - - FOptionMenuItemControl(const char *label, const char *menu, FKeyBindings *bindings) - : FOptionMenuItem(label, menu) - { - mBindings = bindings; - mWaiting = false; - } - - - //============================================================================= - int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override - { - drawLabel(indent, y, mWaiting? OptionSettings.mFontColorHighlight: - (selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor)); - - auto binds = mBindings->GetKeysForCommand(mAction.GetChars()); - auto description = C_NameKeys(binds.Data(), binds.Size()); - - if (description.Len() > 0) - { - drawValue(indent, y, CR_WHITE, description); - } - else - { - drawValue(indent, y, CR_BLACK, "---"); - } - return indent; - } - - //============================================================================= - bool MenuEvent(int mkey, bool fromcontroller) override - { - if (mkey == MKEY_Input) - { - mWaiting = false; - mBindings->SetBind(mInput, mAction.GetChars()); - return true; - } - else if (mkey == MKEY_Clear) - { - mBindings->UnbindACommand(mAction.GetChars()); - return true; - } - else if (mkey == MKEY_Abort) - { - mWaiting = false; - return true; - } - return false; - } - - bool Activate(FName caller) override - { - M_MenuSound(AdvanceSound); - mWaiting = true; - DMenu *input = new DEnterKey(CurrentMenu, &mInput); - M_ActivateMenu(input); - return true; - } -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuItemStaticText : public FOptionMenuItem -{ - EColorRange mColor; -public: - FOptionMenuItemStaticText(const char *label, EColorRange color = CR_UNDEFINED) - : FOptionMenuItem(label, NAME_None, true) - { - mColor = color == CR_UNDEFINED? (EColorRange)OptionSettings.mFontColor : color; - } - - int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override - { - drawLabel(indent, y, mColor); - return -1; - } - - bool Selectable() override - { - return false; - } - -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuItemStaticTextSwitchable : public FOptionMenuItem -{ - EColorRange mColor; - FString mAltText; - int mCurrent; - -public: - FOptionMenuItemStaticTextSwitchable(const char *label, const char *label2, FName action, EColorRange color = CR_UNDEFINED) - : FOptionMenuItem(label, action, true) - { - mColor = color == CR_UNDEFINED? (EColorRange)OptionSettings.mFontColor : color; - mAltText = label2; - mCurrent = 0; - } - - int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override - { - const char *txt = GStrings.localize(mCurrent? mAltText.GetChars() : mLabel.GetChars()); - int w = OptionWidth(txt) * CleanXfac_1; - int x = (twod->GetWidth() - w) / 2; - drawText(x, y, mColor, txt); - return -1; - } - - bool SetValue(int i, int val) override - { - if (i == 0) - { - mCurrent = val; - return true; - } - return false; - } - - bool SetString(int i, const char *newtext) override - { - if (i == 0) - { - mAltText = newtext; - return true; - } - return false; - } - - bool Selectable() override - { - return false; - } -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuSliderBase : public FOptionMenuItem -{ - // action is a CVAR - double mMin, mMax, mStep; - int mShowValue; - int mDrawX; - int mSliderShort; - -public: - FOptionMenuSliderBase(const char *label, double min, double max, double step, int showval, FName command = NAME_None) - : FOptionMenuItem(label, command) - { - mMin = min; - mMax = max; - mStep = step; - mShowValue = showval; - mDrawX = 0; - mSliderShort = 0; - } - - virtual double GetSliderValue() = 0; - virtual void SetSliderValue(double val) = 0; - - //============================================================================= - // - // Draw a slider. Set fracdigits negative to not display the current value numerically. - // - //============================================================================= - - void DrawSliderElement (int color, int x, int y, const char * str) - { - DrawText (twod, ConFont, color, x, y, str, DTA_CellX, 16 * CleanXfac_1, DTA_CellY, 16 * CleanYfac_1, TAG_DONE); - } - - void DrawSlider (int x, int y, double min, double max, double cur, int fracdigits, int indent) - { - char textbuf[16]; - double range; - int maxlen = 0; - int right = x + (12*16 + 4) * CleanXfac_1; - int cy = y;// +(OptionSettings.mLinespacing - 8) * CleanYfac_1; - - range = max - min; - double ccur = clamp(cur, min, max) - min; - - if (fracdigits >= 0) - { - snprintf(textbuf, countof(textbuf), "%.*f", fracdigits, max); - maxlen = NewSmallFont->StringWidth(textbuf) * CleanXfac_1; - } - - mSliderShort = right + maxlen > twod->GetWidth(); - - if (!mSliderShort) - { - DrawSliderElement(CR_WHITE, x, cy, "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12"); - DrawSliderElement(CR_ORANGE, x + int((5 + ((ccur * 78) / range)) * 2*CleanXfac_1), cy, "\x13"); - } - else - { - // On 320x200 we need a shorter slider - DrawSliderElement(CR_WHITE, x, cy, "\x10\x11\x11\x11\x11\x11\x12"); - DrawSliderElement(CR_ORANGE, x + int((5 + ((ccur * 38) / range)) * 2*CleanXfac_1), cy, "\x13"); - right -= 5*8*CleanXfac_1; - } - - if (fracdigits >= 0 && right + maxlen <= twod->GetWidth()) - { - snprintf(textbuf, countof(textbuf), "%.*f", fracdigits, cur); - drawText(right, y, CR_DARKGRAY, textbuf); - } - } - - - //============================================================================= - int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) override - { - drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor); - mDrawX = indent + CursorSpace(); - DrawSlider (mDrawX, y, mMin, mMax, GetSliderValue(), mShowValue, indent); - return indent; - } - - //============================================================================= - bool MenuEvent (int mkey, bool fromcontroller) override - { - double value = GetSliderValue(); - - if (mkey == MKEY_Left) - { - value -= mStep; - } - else if (mkey == MKEY_Right) - { - value += mStep; - } - else - { - return FOptionMenuItem::MenuEvent(mkey, fromcontroller); - } - SetSliderValue(clamp(value, mMin, mMax)); - M_MenuSound(ChangeSound); - return true; - } - - bool MouseEvent(int type, int x, int y) override - { - DOptionMenu *lm = static_cast(CurrentMenu); - if (type != DMenu::MOUSE_Click) - { - if (!lm->CheckFocus(this)) return false; - } - if (type == DMenu::MOUSE_Release) - { - lm->ReleaseFocus(); - } - - int slide_left = mDrawX+16*CleanXfac_1; - int slide_right = slide_left + (10*16*CleanXfac_1 >> mSliderShort); // 10 char cells with 8 pixels each. - - if (type == DMenu::MOUSE_Click) - { - if (x < slide_left || x >= slide_right) return true; - } - - x = clamp(x, slide_left, slide_right); - double v = mMin + ((x - slide_left) * (mMax - mMin)) / (slide_right - slide_left); - if (v != GetSliderValue()) - { - SetSliderValue(v); - M_MenuSound(ChangeSound); - } - if (type == DMenu::MOUSE_Click) - { - lm->SetFocus(this); - } - return true; - } - -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuSliderCVar : public FOptionMenuSliderBase -{ - FBaseCVar *mCVar; -public: - FOptionMenuSliderCVar(const char *label, const char *menu, double min, double max, double step, int showval) - : FOptionMenuSliderBase(label, min, max, step, showval) - { - mCVar = FindCVar(menu, NULL); - } - - double GetSliderValue() override - { - if (mCVar != NULL) - { - return mCVar->GetGenericRep(CVAR_Float).Float; - } - else - { - return 0; - } - } - - void SetSliderValue(double val) override - { - if (mCVar != NULL) - { - UCVarValue value; - value.Float = (float)val; - mCVar->SetGenericRep(value, CVAR_Float); - } - } -}; - -//============================================================================= -// -// -// -//============================================================================= - -class FOptionMenuSliderVar : public FOptionMenuSliderBase -{ - float *mPVal; -public: - - FOptionMenuSliderVar(const char *label, float *pVal, double min, double max, double step, int showval) - : FOptionMenuSliderBase(label, min, max, step, showval) - { - mPVal = pVal; - } - - double GetSliderValue() override - { - return *mPVal; - } - - void SetSliderValue(double val) override - { - *mPVal = (float)val; - } -}; - - -//============================================================================= -// -// [TP] FOptionMenuFieldBase -// -// Base class for input fields -// -//============================================================================= - -class FOptionMenuFieldBase : public FOptionMenuItem -{ -public: - FOptionMenuFieldBase ( const char* label, const char* menu, const char* graycheck ) : - FOptionMenuItem ( label, menu ), - mCVar ( FindCVar( mAction.GetChars(), NULL )), - mGrayCheck (( graycheck && strlen( graycheck )) ? FindCVar( graycheck, NULL ) : NULL ) {} - - const char* GetCVarString() - { - if ( mCVar == NULL ) - return ""; - - return mCVar->GetGenericRep( CVAR_String ).String; - } - - virtual FString Represent() - { - return GetCVarString(); - } - - int Draw ( FOptionMenuDescriptor*, int y, int indent, bool selected ) override - { - bool grayed = mGrayCheck != NULL && !( mGrayCheck->GetGenericRep( CVAR_Bool ).Bool ); - drawLabel(indent, y, selected ? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, grayed); - drawValue(indent, y, OptionSettings.mFontColorValue, Represent(), grayed); - return indent; - } - - bool GetString ( int i, char* s, int len ) override - { - if ( i == 0 ) - { - strncpy( s, GetCVarString(), len ); - s[len - 1] = '\0'; - return true; - } - - return false; - } - - bool SetString ( int i, const char* s ) override - { - if ( i == 0 ) - { - if ( mCVar ) - { - UCVarValue vval; - vval.String = s; - mCVar->SetGenericRep( vval, CVAR_String ); - } - - return true; - } - - return false; - } - -protected: - // Action is a CVar in this class and derivatives. - FBaseCVar* mCVar; - FBaseCVar* mGrayCheck; -}; - -//============================================================================= -// -// [TP] FOptionMenuTextField -// -// A text input field widget, for use with string CVars. -// -//============================================================================= - -class FOptionMenuTextField : public FOptionMenuFieldBase -{ -public: - FOptionMenuTextField ( const char *label, const char* menu, const char* graycheck ) : - FOptionMenuFieldBase ( label, menu, graycheck ), - mEntering ( false ) {} - - FString Represent() override - { - if (mEntering) - { - FString text; - text = mInput->GetText(); - text += '_'; - return text; - } - else return FString(GetCVarString()); - } - - int Draw(FOptionMenuDescriptor*desc, int y, int indent, bool selected) override - { - if (mEntering) - { - // reposition the text so that the cursor is visible when in entering mode. - FString text = Represent(); - int tlen = NewSmallFont->StringWidth(text) * CleanXfac_1; - int newindent = twod->GetWidth() - tlen - CursorSpace(); - if (newindent < indent) indent = newindent; - } - return FOptionMenuFieldBase::Draw(desc, y, indent, selected); - } - - bool MenuEvent ( int mkey, bool fromcontroller ) override - { - if ( mkey == MKEY_Enter ) - { - M_MenuSound(AdvanceSound); - mEntering = true; - mInput = new DTextEnterMenu(CurrentMenu, NewSmallFont, GetCVarString(), 256, fromcontroller ); - M_ActivateMenu( mInput ); - return true; - } - else if ( mkey == MKEY_Input ) - { - if ( mCVar ) - { - UCVarValue vval; - vval.String = mInput->GetText(); - mCVar->SetGenericRep( vval, CVAR_String ); - } - - mEntering = false; - mInput = nullptr; - return true; - } - else if ( mkey == MKEY_Abort ) - { - mEntering = false; - mInput = nullptr; - return true; - } - - return FOptionMenuItem::MenuEvent( mkey, fromcontroller ); - } - -private: - bool mEntering; - DTextEnterMenu* mInput; -}; - -//============================================================================= -// -// [TP] FOptionMenuNumberField -// -// A numeric input field widget, for use with number CVars where sliders are inappropriate (i.e. -// where the user is interested in the exact value specifically) -// -//============================================================================= - -class FOptionMenuNumberField : public FOptionMenuFieldBase -{ -public: - FOptionMenuNumberField ( const char *label, const char* menu, float minimum, float maximum, - float step, const char* graycheck ) - : FOptionMenuFieldBase ( label, menu, graycheck ), - mMinimum ( minimum ), - mMaximum ( maximum ), - mStep ( step ) - { - if ( mMaximum <= mMinimum ) - std::swap( mMinimum, mMaximum ); - - if ( mStep <= 0 ) - mStep = 1; - } - - bool MenuEvent ( int mkey, bool fromcontroller ) override - { - if ( mCVar ) - { - float value = mCVar->GetGenericRep( CVAR_Float ).Float; - - if ( mkey == MKEY_Left ) - { - value -= mStep; - - if ( value < mMinimum ) - value = mMaximum; - } - else if ( mkey == MKEY_Right || mkey == MKEY_Enter ) - { - value += mStep; - - if ( value > mMaximum ) - value = mMinimum; - } - else - return FOptionMenuItem::MenuEvent( mkey, fromcontroller ); - - UCVarValue vval; - vval.Float = value; - mCVar->SetGenericRep( vval, CVAR_Float ); - M_MenuSound(ChangeSound); - } - - return true; - } - -private: - float mMinimum; - float mMaximum; - float mStep; -}; diff --git a/source/core/menu/playermenu.cpp b/source/core/menu/playermenu.cpp new file mode 100644 index 000000000..b88bef988 --- /dev/null +++ b/source/core/menu/playermenu.cpp @@ -0,0 +1,257 @@ +/* +** playermenu.cpp +** The player setup menu's setters. These are native for security purposes. +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "menu.h" +#include "c_dispatch.h" +#include "vm.h" + +EXTERN_CVAR(Int, team) +EXTERN_CVAR(Float, autoaim) +EXTERN_CVAR(Bool, neverswitchonpickup) +EXTERN_CVAR(Bool, cl_run) + +//============================================================================= +// +// +// +//============================================================================= +#if 0 +DEFINE_ACTION_FUNCTION(DPlayerMenu, ColorChanged) +{ + PARAM_PROLOGUE; + PARAM_INT(r); + PARAM_INT(g); + PARAM_INT(b); + // only allow if the menu is active to prevent abuse. + if (DMenu::InMenu) + { + char command[24]; + players[consoleplayer].userinfo.ColorChanged(MAKERGB(r, g, b)); + mysnprintf(command, countof(command), "color \"%02x %02x %02x\"", r, g, b); + C_DoCommand(command); + } + return 0; +} +#endif + +//============================================================================= +// +// access to the player config is done natively, so that broader access +// functions do not need to be exported. +// +//============================================================================= + +DEFINE_ACTION_FUNCTION(DPlayerMenu, PlayerNameChanged) +{ +#if 0 + PARAM_PROLOGUE; + PARAM_STRING(s); + const char *pp = s; + FString command("name \""); + + if (DMenu::InMenu) + { + // Escape any backslashes or quotation marks before sending the name to the console. + for (auto p = pp; *p != '\0'; ++p) + { + if (*p == '"' || *p == '\\') + { + command << '\\'; + } + command << *p; + } + command << '"'; + C_DoCommand(command); + } +#endif + return 0; +} + +//============================================================================= +// +// +// +//============================================================================= +#if 0 +DEFINE_ACTION_FUNCTION(DPlayerMenu, ColorSetChanged) +{ + PARAM_PROLOGUE; + PARAM_INT(sel); + if (DMenu::InMenu) + { + players[consoleplayer].userinfo.ColorSetChanged(sel); + char command[24]; + mysnprintf(command, countof(command), "colorset %d", sel); + C_DoCommand(command); + } + return 0; +} +#endif +//============================================================================= +// +// +// +//============================================================================= +#if 0 +DEFINE_ACTION_FUNCTION(DPlayerMenu, ClassChanged) +{ + PARAM_PROLOGUE; + PARAM_INT(sel); + PARAM_POINTER(cls, FPlayerClass); + if (DMenu::InMenu) + { + const char *pclass = sel == -1 ? "Random" : GetPrintableDisplayName(cls->Type).GetChars(); + players[consoleplayer].userinfo.PlayerClassChanged(pclass); + cvar_set("playerclass", pclass); + } + return 0; +} +#endif + +//============================================================================= +// +// +// +//============================================================================= +#if 0 +DEFINE_ACTION_FUNCTION(DPlayerMenu, SkinChanged) +{ + PARAM_PROLOGUE; + PARAM_INT(sel); + if (DMenu::InMenu) + { + players[consoleplayer].userinfo.SkinNumChanged(sel); + cvar_set("skin", Skins[sel].Name); + } + return 0; +} +#endif +//============================================================================= +// +// +// +//============================================================================= +#if 0 +DEFINE_ACTION_FUNCTION(DPlayerMenu, AutoaimChanged) +{ + PARAM_PROLOGUE; + PARAM_FLOAT(val); + // only allow if the menu is active to prevent abuse. + if (DMenu::InMenu) + { + autoaim = float(val); + } + return 0; +} + +#endif +//============================================================================= +// +// +// +//============================================================================= +#if 0 +DEFINE_ACTION_FUNCTION(DPlayerMenu, TeamChanged) +{ + PARAM_PROLOGUE; + PARAM_INT(val); + // only allow if the menu is active to prevent abuse. + if (DMenu::InMenu) + { + team = val == 0 ? TEAM_NONE : val - 1; + } + return 0; +} +#endif +//============================================================================= +// +// +// +//============================================================================= + +DEFINE_ACTION_FUNCTION(DPlayerMenu, GenderChanged) +{ + PARAM_PROLOGUE; + PARAM_INT(v); + // only allow if the menu is active to prevent abuse. + if (DMenu::InMenu) + { + switch(v) + { + case 0: cvar_set("gender", "male"); break; + case 1: cvar_set("gender", "female"); break; + case 2: cvar_set("gender", "neutral"); break; + case 3: cvar_set("gender", "other"); break; + } + } + return 0; +} + +//============================================================================= +// +// +// +//============================================================================= +#if 0 +DEFINE_ACTION_FUNCTION(DPlayerMenu, SwitchOnPickupChanged) +{ + PARAM_PROLOGUE; + PARAM_INT(v); + // only allow if the menu is active to prevent abuse. + if (DMenu::InMenu) + { + neverswitchonpickup = !!v; + } + return 0; +} +#endif +//============================================================================= +// +// +// +//============================================================================= +#if 0 +DEFINE_ACTION_FUNCTION(DPlayerMenu, AlwaysRunChanged) +{ + PARAM_PROLOGUE; + PARAM_INT(v); + // only allow if the menu is active to prevent abuse. + if (DMenu::InMenu) + { + cl_run = !!v; + } + return 0; +} +#endif \ No newline at end of file diff --git a/source/core/menu/razemenu.cpp b/source/core/menu/razemenu.cpp new file mode 100644 index 000000000..7c16a3fcc --- /dev/null +++ b/source/core/menu/razemenu.cpp @@ -0,0 +1,1119 @@ +/* +** menu.cpp +** Menu base class and global interface +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "c_dispatch.h" +#include "d_gui.h" +#include "c_buttons.h" +#include "c_console.h" +#include "c_bind.h" +#include "d_eventbase.h" +#include "g_input.h" +#include "configfile.h" +#include "gstrings.h" +#include "menu.h" +#include "vm.h" +#include "v_video.h" +#include "i_system.h" +#include "types.h" +#include "texturemanager.h" +#include "v_draw.h" +#include "vm.h" +#include "gamestate.h" +#include "i_interface.h" +#include "d_event.h" +#include "st_start.h" +#include "i_system.h" +#include "gameconfigfile.h" + +EXTERN_CVAR(Int, cl_gfxlocalization) +EXTERN_CVAR(Bool, m_quickexit) +EXTERN_CVAR(Bool, saveloadconfirmation) // [mxd] +EXTERN_CVAR(Bool, quicksaverotation) +EXTERN_CVAR(Bool, show_messages) + +typedef void(*hfunc)(); +DMenu* CreateMessageBoxMenu(DMenu* parent, const char* message, int messagemode, bool playsound, FName action = NAME_None, hfunc handler = nullptr); +bool OkForLocalization(FTextureID texnum, const char* substitute); +void D_ToggleHud(); +void I_WaitVBL(int count); + +extern bool hud_toggled; + + +//FNewGameStartup NewGameStartupInfo; + + +bool M_SetSpecialMenu(FName& menu, int param) +{ +#if 0 + // some menus need some special treatment + switch (menu.GetIndex()) + { + case NAME_Mainmenu: + break; + case NAME_Episodemenu: + // sent from the player class menu + break; + + case NAME_Skillmenu: + // sent from the episode menu + break; + + case NAME_StartgameConfirm: + { + // sent from the skill menu for a skill that needs to be confirmed + return false; + } + + case NAME_Startgame: + // sent either from skill menu or confirmation screen. Skill gets only set if sent from skill menu + // Now we can finally start the game. Ugh... + //NewGameStartupInfo.Skill = param; + case NAME_StartgameConfirmed: + + //G_DeferedInitNew (&NewGameStartupInfo); + if (gamestate == GS_FULLCONSOLE) + { + gamestate = GS_HIDECONSOLE; + gameaction = ga_newgame; + } + M_ClearMenus (); + return false; + + case NAME_Savegamemenu: + if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer) || gamestate != GS_LEVEL) + { + // cannot save outside the game. + M_StartMessage (GStrings("SAVEDEAD"), 1); + return false; + } + break; + + case NAME_Quitmenu: + // The separate menu class no longer exists but the name still needs support for existing mods. + C_DoCommand("menu_quit"); + return false; + + case NAME_EndGameMenu: + // The separate menu class no longer exists but the name still needs support for existing mods. + void ActivateEndGameMenu(); + ActivateEndGameMenu(); + return false; + + case NAME_Playermenu: + menu = NAME_NewPlayerMenu; // redirect the old player menu to the new one. + break; + } + + DMenuDescriptor** desc = MenuDescriptors.CheckKey(menu); + if (desc != nullptr) + { + if ((*desc)->mNetgameMessage.IsNotEmpty() && netgame && !demoplayback) + { + M_StartMessage((*desc)->mNetgameMessage, 1); + return false; + } + } + +#endif + // End of special checks + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_StartControlPanel(bool makeSound, bool scaleoverride) +{ +#if 0 + if (hud_toggled) + D_ToggleHud(); + + // intro might call this repeatedly + if (CurrentMenu != nullptr) + return; + + P_CheckTickerPaused(); + + if (makeSound) + { + S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + } + M_DoStartControlPanel(scaleoverride); +#endif +} + + +//========================================================================== +// +// M_Dim +// +// Applies a colored overlay to the entire screen, with the opacity +// determined by the dimamount cvar. +// +//========================================================================== + +CUSTOM_CVAR(Float, dimamount, -1.f, CVAR_ARCHIVE) +{ + if (self < 0.f && self != -1.f) + { + self = -1.f; + } + else if (self > 1.f) + { + self = 1.f; + } +} +CVAR(Color, dimcolor, 0xffd700, CVAR_ARCHIVE) + +void System_M_Dim() +{ +#if 0 + PalEntry dimmer; + float amount; + + if (dimamount >= 0) + { + dimmer = PalEntry(dimcolor); + amount = dimamount; + } + else + { + dimmer = gameinfo.dimcolor; + amount = gameinfo.dimamount; + } + + Dim(twod, dimmer, amount, 0, 0, twod->GetWidth(), twod->GetHeight()); +#endif +} + + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (menu_quit) +{ // F10 +#if 0 + if (m_quickexit) + { + ST_Endoom(); + } + + M_StartControlPanel (true); + + const size_t messageindex = static_cast(gametic) % gameinfo.quitmessages.Size(); + FString EndString; + const char *msg = gameinfo.quitmessages[messageindex]; + if (msg[0] == '$') + { + if (msg[1] == '*') + { + EndString = GStrings(msg + 2); + } + else + { + EndString.Format("%s\n\n%s", GStrings(msg + 1), GStrings("DOSY")); + } + } + else EndString = gameinfo.quitmessages[messageindex]; + + DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, EndString, 0, false, NAME_None, []() + { + if (!netgame) + { + if (gameinfo.quitSound.IsNotEmpty()) + { + S_Sound(CHAN_VOICE, CHANF_UI, gameinfo.quitSound, snd_menuvolume, ATTN_NONE); + I_WaitVBL(105); + } + } + ST_Endoom(); + }); + + + M_ActivateMenu(newmenu); +#endif +} + + + +//============================================================================= +// +// +// +//============================================================================= + +void ActivateEndGameMenu() +{ +#if 0 + FString tempstring = GStrings(netgame ? "NETEND" : "ENDGAME"); + DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []() + { + M_ClearMenus(); + if (!netgame) + { + if (demorecording) + G_CheckDemoStatus(); + D_StartTitle(); + } + }); + + M_ActivateMenu(newmenu); +#endif +} + +CCMD (menu_endgame) +{ // F7 +#if 0 + if (!usergame) + { + S_Sound (CHAN_VOICE, CHANF_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + return; + } + + //M_StartControlPanel (true); + S_Sound (CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + + ActivateEndGameMenu(); +#endif +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (quicksave) +{ // F6 +#if 0 + if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) + { + S_Sound (CHAN_VOICE, CHANF_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + return; + } + + if (gamestate != GS_LEVEL) + return; + + // If the quick save rotation is enabled, it handles the save slot. + if (quicksaverotation) + { + G_DoQuickSave(); + return; + } + + if (savegameManager.quickSaveSlot == NULL || savegameManager.quickSaveSlot == (FSaveGameNode*)1) + { + S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + M_StartControlPanel(false); + M_SetMenu(NAME_Savegamemenu); + return; + } + + // [mxd]. Just save the game, no questions asked. + if (!saveloadconfirmation) + { + G_SaveGame(savegameManager.quickSaveSlot->Filename.GetChars(), savegameManager.quickSaveSlot->SaveTitle.GetChars()); + return; + } + + S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + + FString tempstring = GStrings("QSPROMPT"); + tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars()); + + DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []() + { + G_SaveGame(savegameManager.quickSaveSlot->Filename.GetChars(), savegameManager.quickSaveSlot->SaveTitle.GetChars()); + S_Sound(CHAN_VOICE, CHANF_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + M_ClearMenus(); + }); + + M_ActivateMenu(newmenu); +#endif +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (quickload) +{ // F9 +#if 0 + if (netgame) + { + M_StartControlPanel(true); + M_StartMessage (GStrings("QLOADNET"), 1); + return; + } + + if (savegameManager.quickSaveSlot == NULL || savegameManager.quickSaveSlot == (FSaveGameNode*)1) + { + M_StartControlPanel(true); + // signal that whatever gets loaded should be the new quicksave + savegameManager.quickSaveSlot = (FSaveGameNode *)1; + M_SetMenu(NAME_Loadgamemenu); + return; + } + + // [mxd]. Just load the game, no questions asked. + if (!saveloadconfirmation) + { + G_LoadGame(savegameManager.quickSaveSlot->Filename.GetChars()); + return; + } + FString tempstring = GStrings("QLPROMPT"); + tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars()); + + M_StartControlPanel(true); + + DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []() + { + G_LoadGame(savegameManager.quickSaveSlot->Filename.GetChars()); + S_Sound(CHAN_VOICE, CHANF_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + M_ClearMenus(); + }); + M_ActivateMenu(newmenu); +#endif +} + + + + +EXTERN_CVAR (Int, screenblocks) + +#if 0 +CCMD (sizedown) +{ + screenblocks = screenblocks - 1; + S_Sound (CHAN_VOICE, CHANF_UI, "menu/change", snd_menuvolume, ATTN_NONE); +} + +CCMD (sizeup) +{ + screenblocks = screenblocks + 1; + S_Sound (CHAN_VOICE, CHANF_UI, "menu/change", snd_menuvolume, ATTN_NONE); +} + +CCMD(reset2defaults) +{ + C_SetDefaultBindings (); + C_SetCVarsToDefaults (); + R_SetViewSize (screenblocks); +} + +CCMD(reset2saved) +{ + GameConfig->DoGlobalSetup (); + GameConfig->DoGameSetup (gameinfo.ConfigName); + GameConfig->DoModSetup (gameinfo.ConfigName); + R_SetViewSize (screenblocks); +} + +CCMD(resetb2defaults) +{ + C_SetDefaultBindings (); +} +#endif + +//============================================================================= +// +// Creates the episode menu +// Falls back on an option menu if there's not enough screen space to show all episodes +// +//============================================================================= + +#if 0 +void M_StartupEpisodeMenu(FNewGameStartup *gs) +{ + // Build episode menu + bool success = false; + bool isOld = false; + DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu); + if (desc != nullptr) + { + if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) + { + DListMenuDescriptor *ld = static_cast(*desc); + + // Delete previous contents + for(unsigned i=0; imItems.Size(); i++) + { + FName n = ld->mItems[i]->mAction; + if (n == NAME_Skillmenu) + { + isOld = true; + ld->mItems.Resize(i); + break; + } + } + + + int posx = (int)ld->mXpos; + int posy = (int)ld->mYpos; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = (int)ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + AllEpisodes.Size() * ld->mLinespacing - topy; + + if (totalheight < 190 || AllEpisodes.Size() == 1) + { + int newtop = (200 - totalheight) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + posy += topdelta; + ld->mYpos += topdelta; + } + + if (!isOld) ld->mSelectedItem = ld->mItems.Size(); + + for (unsigned i = 0; i < AllEpisodes.Size(); i++) + { + DMenuItemBase *it = nullptr; + if (AllEpisodes[i].mPicName.IsNotEmpty()) + { + FTextureID tex = GetMenuTexture(AllEpisodes[i].mPicName); + if (AllEpisodes[i].mEpisodeName.IsEmpty() || OkForLocalization(tex, AllEpisodes[i].mEpisodeName)) + continue; // We do not measure patch based entries. They are assumed to fit + } + const char *c = AllEpisodes[i].mEpisodeName; + if (*c == '$') c = GStrings(c + 1); + int textwidth = ld->mFont->StringWidth(c); + int textright = posx + textwidth; + if (posx + textright > 320) posx = std::max(0, 320 - textright); + } + + for(unsigned i = 0; i < AllEpisodes.Size(); i++) + { + DMenuItemBase *it = nullptr; + if (AllEpisodes[i].mPicName.IsNotEmpty()) + { + FTextureID tex = GetMenuTexture(AllEpisodes[i].mPicName); + if (AllEpisodes[i].mEpisodeName.IsEmpty() || OkForLocalization(tex, AllEpisodes[i].mEpisodeName)) + it = CreateListMenuItemPatch(posx, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, tex, NAME_Skillmenu, i); + } + if (it == nullptr) + { + it = CreateListMenuItemText(posx, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, + AllEpisodes[i].mEpisodeName, ld->mFont, ld->mFontColor, ld->mFontColor2, NAME_Skillmenu, i); + } + ld->mItems.Push(it); + posy += ld->mLinespacing; + } + if (AllEpisodes.Size() == 1) + { + ld->mAutoselect = ld->mSelectedItem; + } + success = true; + for (auto &p : ld->mItems) + { + GC::WriteBarrier(*desc, p); + } + } + } + else return; // do not recreate the option menu variant, because it is always text based. + } + if (!success) + { + // Couldn't create the episode menu, either because there's too many episodes or some error occured + // Create an option menu for episode selection instead. + DOptionMenuDescriptor *od = Create(); + MenuDescriptors[NAME_Episodemenu] = od; + od->mMenuName = NAME_Episodemenu; + od->mFont = gameinfo.gametype == GAME_Doom ? BigUpper : BigFont; + od->mTitle = "$MNU_EPISODE"; + od->mSelectedItem = 0; + od->mScrollPos = 0; + od->mClass = nullptr; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + GC::WriteBarrier(od); + for(unsigned i = 0; i < AllEpisodes.Size(); i++) + { + auto it = CreateOptionMenuItemSubmenu(AllEpisodes[i].mEpisodeName, "Skillmenu", i); + od->mItems.Push(it); + GC::WriteBarrier(od, it); + } + } +} +#endif +//============================================================================= +// +// +// +//============================================================================= + +static void BuildPlayerclassMenu() +{ +#if 0 + bool success = false; + + // Build player class menu + DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Playerclassmenu); + if (desc != nullptr) + { + if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) + { + DListMenuDescriptor *ld = static_cast(*desc); + // add player display + + ld->mSelectedItem = ld->mItems.Size(); + + int posy = (int)ld->mYpos; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = (int)ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // Count the number of items this menu will show + int numclassitems = 0; + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type); + if (pname != nullptr) + { + numclassitems++; + } + } + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + (numclassitems+1) * ld->mLinespacing - topy; + + if (numclassitems <= 1) + { + // create a dummy item that auto-chooses the default class. + auto it = CreateListMenuItemText(0, 0, 0, 'p', "player", + ld->mFont,ld->mFontColor, ld->mFontColor2, NAME_Episodemenu, -1000); + ld->mAutoselect = ld->mItems.Push(it); + success = true; + } + else if (totalheight <= 190) + { + int newtop = (200 - totalheight + topy) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + posy -= topdelta; + } + + int n = 0; + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type); + if (pname != nullptr) + { + auto it = CreateListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname, + pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, i); + ld->mItems.Push(it); + ld->mYpos += ld->mLinespacing; + n++; + } + } + } + if (n > 1 && !gameinfo.norandomplayerclass) + { + auto it = CreateListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, 'r', + "$MNU_RANDOM", ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, -1); + ld->mItems.Push(it); + } + if (n == 0) + { + const char *pname = GetPrintableDisplayName(PlayerClasses[0].Type); + if (pname != nullptr) + { + auto it = CreateListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname, + pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, 0); + ld->mItems.Push(it); + } + } + success = true; + for (auto &p : ld->mItems) + { + GC::WriteBarrier(ld, p); + } + } + } + } + if (!success) + { + // Couldn't create the playerclass menu, either because there's too many episodes or some error occured + // Create an option menu for class selection instead. + DOptionMenuDescriptor *od = Create(); + MenuDescriptors[NAME_Playerclassmenu] = od; + od->mMenuName = NAME_Playerclassmenu; + od->mFont = gameinfo.gametype == GAME_Doom ? BigUpper : BigFont; + od->mTitle = "$MNU_CHOOSECLASS"; + od->mSelectedItem = 0; + od->mScrollPos = 0; + od->mClass = nullptr; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + od->mNetgameMessage = "$NEWGAME"; + GC::WriteBarrier(od); + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type); + if (pname != nullptr) + { + auto it = CreateOptionMenuItemSubmenu(pname, "Episodemenu", i); + od->mItems.Push(it); + GC::WriteBarrier(od, it); + } + } + } + auto it = CreateOptionMenuItemSubmenu("Random", "Episodemenu", -1); + od->mItems.Push(it); + GC::WriteBarrier(od, it); + } +#endif +} + +//============================================================================= +// +// Reads any XHAIRS lumps for the names of crosshairs and +// adds them to the display options menu. +// +//============================================================================= + +static void InitCrosshairsList() +{ +#if 0 + int lastlump, lump; + + lastlump = 0; + + FOptionValues **opt = OptionValues.CheckKey(NAME_Crosshairs); + if (opt == nullptr) + { + return; // no crosshair value list present. No need to go on. + } + + FOptionValues::Pair *pair = &(*opt)->mValues[(*opt)->mValues.Reserve(1)]; + pair->Value = 0; + pair->Text = "None"; + + while ((lump = fileSystem.FindLump("XHAIRS", &lastlump)) != -1) + { + FScanner sc(lump); + while (sc.GetNumber()) + { + FOptionValues::Pair value; + value.Value = sc.Number; + sc.MustGetString(); + value.Text = sc.String; + if (value.Value != 0) + { // Check if it already exists. If not, add it. + unsigned int i; + + for (i = 1; i < (*opt)->mValues.Size(); ++i) + { + if ((*opt)->mValues[i].Value == value.Value) + { + break; + } + } + if (i < (*opt)->mValues.Size()) + { + (*opt)->mValues[i].Text = value.Text; + } + else + { + (*opt)->mValues.Push(value); + } + } + } + } +#endif +} + +//============================================================================= +// +// Special menus will be created once all engine data is loaded +// +//============================================================================= + +void M_CreateGameMenus() +{ +#if 0 + BuildPlayerclassMenu(); + InitCrosshairsList(); + + auto opt = OptionValues.CheckKey(NAME_PlayerTeam); + if (opt != nullptr) + { + auto op = *opt; + op->mValues.Resize(Teams.Size() + 1); + op->mValues[0].Value = 0; + op->mValues[0].Text = "$OPTVAL_NONE"; + for (unsigned i = 0; i < Teams.Size(); i++) + { + op->mValues[i+1].Value = i+1; + op->mValues[i+1].Text = Teams[i].GetName(); + } + } + opt = OptionValues.CheckKey(NAME_PlayerClass); + if (opt != nullptr) + { + auto op = *opt; + int o = 0; + if (!gameinfo.norandomplayerclass && PlayerClasses.Size() > 1) + { + op->mValues.Resize(PlayerClasses.Size()+1); + op->mValues[0].Value = -1; + op->mValues[0].Text = "$MNU_RANDOM"; + o = 1; + } + else op->mValues.Resize(PlayerClasses.Size()); + for (unsigned i = 0; i < PlayerClasses.Size(); i++) + { + op->mValues[i+o].Value = i; + op->mValues[i+o].Text = GetPrintableDisplayName(PlayerClasses[i].Type); + } + } +#endif +} + + +//============================================================================= +// +// The skill menu must be refeshed each time it starts up +// +//============================================================================= +extern int restart; + +#if 0 +void M_StartupSkillMenu(FNewGameStartup *gs) +{ + static int done = -1; + bool success = false; + TArray MenuSkills; + TArray SkillIndices; + if (MenuSkills.Size() == 0) + { + for (unsigned ind = 0; ind < AllSkills.Size(); ind++) + { + if (!AllSkills[ind].NoMenu) + { + MenuSkills.Push(&AllSkills[ind]); + SkillIndices.Push(ind); + } + } + } + if (MenuSkills.Size() == 0) I_Error("No valid skills for menu found. At least one must be defined."); + + int defskill = DefaultSkill; + if ((unsigned int)defskill >= MenuSkills.Size()) + { + defskill = SkillIndices[(MenuSkills.Size() - 1) / 2]; + } + if (AllSkills[defskill].NoMenu) + { + for (defskill = 0; defskill < (int)AllSkills.Size(); defskill++) + { + if (!AllSkills[defskill].NoMenu) break; + } + } + int defindex = 0; + for (unsigned i = 0; i < MenuSkills.Size(); i++) + { + if (MenuSkills[i] == &AllSkills[defskill]) + { + defindex = i; + break; + } + } + + DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu); + if (desc != nullptr) + { + if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) + { + DListMenuDescriptor *ld = static_cast(*desc); + int posx = (int)ld->mXpos; + int y = (int)ld->mYpos; + + // Delete previous contents + for(unsigned i=0; imItems.Size(); i++) + { + FName n = ld->mItems[i]->mAction; + if (n == NAME_Startgame || n == NAME_StartgameConfirm) + { + ld->mItems.Resize(i); + break; + } + } + + if (done != restart) + { + done = restart; + ld->mSelectedItem = ld->mItems.Size() + defindex; + + int posy = y; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = (int)ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + MenuSkills.Size() * ld->mLinespacing - topy; + + if (totalheight < 190 || MenuSkills.Size() == 1) + { + int newtop = (200 - totalheight) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + ld->mYpos = y = posy + topdelta; + } + } + else + { + // too large + desc = nullptr; + done = false; + goto fail; + } + } + + for (unsigned int i = 0; i < MenuSkills.Size(); i++) + { + FSkillInfo &skill = *MenuSkills[i]; + DMenuItemBase *li = nullptr; + + FString *pItemText = nullptr; + if (gs->PlayerClass != nullptr) + { + pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); + } + + if (skill.PicName.Len() != 0 && pItemText == nullptr) + { + FTextureID tex = GetMenuTexture(skill.PicName); + if (skill.MenuName.IsEmpty() || OkForLocalization(tex, skill.MenuName)) + continue; + } + const char *c = pItemText ? pItemText->GetChars() : skill.MenuName.GetChars(); + if (*c == '$') c = GStrings(c + 1); + int textwidth = ld->mFont->StringWidth(c); + int textright = posx + textwidth; + if (posx + textright > 320) posx = std::max(0, 320 - textright); + } + + unsigned firstitem = ld->mItems.Size(); + for(unsigned int i = 0; i < MenuSkills.Size(); i++) + { + FSkillInfo &skill = *MenuSkills[i]; + DMenuItemBase *li = nullptr; + // Using a different name for skills that must be confirmed makes handling this easier. + FName action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ? + NAME_StartgameConfirm : NAME_Startgame; + FString *pItemText = nullptr; + if (gs->PlayerClass != nullptr) + { + pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); + } + + EColorRange color = (EColorRange)skill.GetTextColor(); + if (color == CR_UNTRANSLATED) color = ld->mFontColor; + if (skill.PicName.Len() != 0 && pItemText == nullptr) + { + FTextureID tex = GetMenuTexture(skill.PicName); + if (skill.MenuName.IsEmpty() || OkForLocalization(tex, skill.MenuName)) + li = CreateListMenuItemPatch(posx, y, ld->mLinespacing, skill.Shortcut, tex, action, SkillIndices[i]); + } + if (li == nullptr) + { + li = CreateListMenuItemText(posx, y, ld->mLinespacing, skill.Shortcut, + pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, SkillIndices[i]); + } + ld->mItems.Push(li); + GC::WriteBarrier(*desc, li); + y += ld->mLinespacing; + } + if (AllEpisodes[gs->Episode].mNoSkill || MenuSkills.Size() == 1) + { + ld->mAutoselect = firstitem + defindex; + } + else + { + ld->mAutoselect = -1; + } + success = true; + } + } + if (success) return; +fail: + // Option menu fallback for overlong skill lists + DOptionMenuDescriptor *od; + if (desc == nullptr) + { + od = Create(); + MenuDescriptors[NAME_Skillmenu] = od; + od->mMenuName = NAME_Skillmenu; + od->mFont = gameinfo.gametype == GAME_Doom ? BigUpper : BigFont; + od->mTitle = "$MNU_CHOOSESKILL"; + od->mSelectedItem = defindex; + od->mScrollPos = 0; + od->mClass = nullptr; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + GC::WriteBarrier(od); + } + else + { + od = static_cast(*desc); + od->mItems.Clear(); + } + for(unsigned int i = 0; i < MenuSkills.Size(); i++) + { + FSkillInfo &skill = *MenuSkills[i]; + DMenuItemBase *li; + // Using a different name for skills that must be confirmed makes handling this easier. + const char *action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ? + "StartgameConfirm" : "Startgame"; + + FString *pItemText = nullptr; + if (gs->PlayerClass != nullptr) + { + pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); + } + li = CreateOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, SkillIndices[i]); + od->mItems.Push(li); + GC::WriteBarrier(od, li); + if (!done) + { + done = true; + od->mSelectedItem = defindex; + } + } +} +#endif +//========================================================================== +// +// Defines how graphics substitution is handled. +// 0: Never replace a text-containing graphic with a font-based text. +// 1: Always replace, regardless of any missing information. Useful for testing the substitution without providing full data. +// 2: Only replace for non-default texts, i.e. if some language redefines the string's content, use it instead of the graphic. Never replace a localized graphic. +// 3: Only replace if the string is not the default and the graphic comes from the IWAD. Never replace a localized graphic. +// 4: Like 1, but lets localized graphics pass. +// +// The default is 3, which only replaces known content with non-default texts. +// +//========================================================================== + +#if 0 +CUSTOM_CVAR(Int, cl_gfxlocalization, 3, CVAR_ARCHIVE) +{ + if (self < 0 || self > 4) self = 0; +} + +bool OkForLocalization(FTextureID texnum, const char* substitute) +{ + if (!texnum.isValid()) return false; + + // First the unconditional settings, 0='never' and 1='always'. + if (cl_gfxlocalization == 1 || gameinfo.forcetextinmenus) return false; + if (cl_gfxlocalization == 0 || gameinfo.forcenogfxsubstitution) return true; + return TexMan.OkForLocalization(texnum, substitute, cl_gfxlocalization); +} + +bool CheckSkipGameOptionBlock(FScanner &sc) +{ + bool filter = false; + if (sc.Compare("ReadThis")) filter |= gameinfo.drawreadthis; + else if (sc.Compare("Swapmenu")) filter |= gameinfo.swapmenu; + return filter; +} + +void SetDefaultMenuColors() +{ + OptionSettings.mTitleColor = V_FindFontColor(gameinfo.mTitleColor); + OptionSettings.mFontColor = V_FindFontColor(gameinfo.mFontColor); + OptionSettings.mFontColorValue = V_FindFontColor(gameinfo.mFontColorValue); + OptionSettings.mFontColorMore = V_FindFontColor(gameinfo.mFontColorMore); + OptionSettings.mFontColorHeader = V_FindFontColor(gameinfo.mFontColorHeader); + OptionSettings.mFontColorHighlight = V_FindFontColor(gameinfo.mFontColorHighlight); + OptionSettings.mFontColorSelection = V_FindFontColor(gameinfo.mFontColorSelection); +} +#endif diff --git a/source/core/menu/razemenu.h b/source/core/menu/razemenu.h new file mode 100644 index 000000000..c211cc69a --- /dev/null +++ b/source/core/menu/razemenu.h @@ -0,0 +1,69 @@ +#pragma once +#include "menu.h" +#include "gamestruct.h" +#include "c_cvars.h" + +void M_StartControlPanel (bool makeSound, bool scaleoverride = false); + + +extern FNewGameStartup NewGameStartupInfo; +void M_StartupEpisodeMenu(FNewGameStartup *gs); +void M_StartupSkillMenu(FNewGameStartup *gs); +void M_CreateGameMenus(); + +// The savegame manager contains too much code that is game specific. Parts are shareable but need more work first. + +struct FSavegameManager +{ +private: + TArray SaveGames; + FSaveGameNode NewSaveNode; + int LastSaved = -1; + int LastAccessed = -1; + FGameTexture *SavePic = nullptr; + +public: + int WindowSize = 0; + FString SaveCommentString; + FSaveGameNode *quickSaveSlot = nullptr; + ~FSavegameManager(); + +private: + int InsertSaveNode(FSaveGameNode *node); +public: + void NotifyNewSave(const FString &file, const FString &title, bool okForQuicksave, bool forceQuicksave); + void ClearSaveGames(); + + void ReadSaveStrings(); + void UnloadSaveData(); + + int RemoveSaveSlot(int index); + void LoadSavegame(int Selected); + void DoSave(int Selected, const char *savegamestring); + unsigned ExtractSaveData(int index); + void ClearSaveStuff(); + bool DrawSavePic(int x, int y, int w, int h); + void DrawSaveComment(FFont *font, int cr, int x, int y, int scalefactor); + void SetFileInfo(int Selected); + unsigned SavegameCount(); + FSaveGameNode *GetSavegame(int i); + void InsertNewSaveNode(); + bool RemoveNewSaveNode(); + +}; + +extern FSavegameManager savegameManager; + +enum EMenuSounds : int +{ + ActivateSound, + CursorSound, + AdvanceSound, + BackSound, + CloseSound, + PageSound, + ChangeSound, + ChooseSound +}; + +EXTERN_CVAR(Bool, menu_sounds) diff --git a/source/core/menu/savegamemanager.cpp b/source/core/menu/savegamemanager.cpp deleted file mode 100644 index a62be3068..000000000 --- a/source/core/menu/savegamemanager.cpp +++ /dev/null @@ -1,739 +0,0 @@ -/* -** loadsavemenu.cpp -** The load game and save game menus -** -**--------------------------------------------------------------------------- -** Copyright 2001-2010 Randy Heit -** Copyright 2010 Christoph Oelckers -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "menu.h" -#include "version.h" -#include "m_png.h" -#include "filesystem.h" -#include "v_text.h" -#include "d_event.h" -#include "gstrings.h" -#include "d_gui.h" -#include "v_draw.h" -#include "files.h" -#include "resourcefile.h" -#include "cmdlib.h" -#include "files.h" -#include "savegamehelp.h" -#include "i_specialpaths.h" -#include "c_dispatch.h" -#include "build.h" -#include "serializer.h" -#include "findfile.h" -#include "inputstate.h" -#include "gamestate.h" - - -FSavegameManager savegameManager; -FString BackupSaveGame; - -void DoLoadGame(const char* name) -{ - if (OpenSaveGameForRead(name)) - { - if (gi->LoadGame(nullptr)) - { - gameaction = ga_level; - } - else - { - I_Error("%s: Failed to load savegame", name); - } - } - else - { - I_Error("%s: Failed to open savegame", name); - } -} - -void FSavegameManager::LoadGame(FSaveGameNode* node) -{ - inputState.ClearAllInput(); - gi->FreeLevelData(); - DoLoadGame(node->Filename); - BackupSaveGame = node->Filename; -} - -void FSavegameManager::SaveGame(FSaveGameNode* node, bool ok4q, bool forceq) -{ - if (OpenSaveGameForWrite(node->Filename, node->SaveTitle)) - { - if (gi->SaveGame(node) && FinishSavegameWrite()) - { - FString fn = node->Filename; - FString desc = node->SaveTitle; - NotifyNewSave(fn, desc, ok4q, forceq); - Printf(PRINT_NOTIFY, "%s\n", GStrings("GAME SAVED")); - BackupSaveGame = node->Filename; - } - } -} - -//============================================================================= -// -// Save data maintenance -// -//============================================================================= - -void FSavegameManager::ClearSaveGames() -{ - for (unsigned i = 0; ibNoDelete) - delete SaveGames[i]; - } - SaveGames.Clear(); -} - -FSavegameManager::~FSavegameManager() -{ - ClearSaveGames(); -} - -//============================================================================= -// -// Save data maintenance -// -//============================================================================= - -int FSavegameManager::RemoveSaveSlot(int index) -{ - int listindex = SaveGames[0]->bNoDelete ? index - 1 : index; - if (listindex < 0) return index; - - remove(SaveGames[index]->Filename.GetChars()); - UnloadSaveData(); - - FSaveGameNode *file = SaveGames[index]; - - if (quickSaveSlot == SaveGames[index]) - { - quickSaveSlot = nullptr; - } - if (!file->bNoDelete) delete file; - - if (LastSaved == listindex) LastSaved = -1; - else if (LastSaved > listindex) LastSaved--; - if (LastAccessed == listindex) LastAccessed = -1; - else if (LastAccessed > listindex) LastAccessed--; - - SaveGames.Delete(index); - if ((unsigned)index >= SaveGames.Size()) index--; - ExtractSaveData(index); - return index; -} - -//============================================================================= -// -// -// -//============================================================================= - -int FSavegameManager::InsertSaveNode(FSaveGameNode *node) -{ - if (SaveGames.Size() == 0) - { - return SaveGames.Push(node); - } - - if (node->bOldVersion) - { // Add node at bottom of list - return SaveGames.Push(node); - } - else - { // Add node at top of list - unsigned int i = 0; - if (SaveGames[0] == &NewSaveNode) i++; // To not insert above the "new savegame" dummy entry. - for (; i < SaveGames.Size(); i++) - { - if (SaveGames[i]->bOldVersion || node->SaveTitle.CompareNoCase(SaveGames[i]->SaveTitle) <= 0) - { - break; - } - } - SaveGames.Insert(i, node); - return i; - } -} - -//============================================================================= -// -// M_ReadSaveStrings -// -// Find savegames and read their titles -// -//============================================================================= - -void FSavegameManager::ReadSaveStrings() -{ - if (SaveGames.Size() == 0) - { - void *filefirst; - findstate_t c_file; - FString filter; - - LastSaved = LastAccessed = -1; - quickSaveSlot = nullptr; - filter = G_BuildSaveName("*"); - 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)); - - FResourceFile *savegame = FResourceFile::OpenResourceFile(filepath, true, true); - if (savegame != nullptr) - { - FResourceLump *info = savegame->FindLump("info.json"); - if (info == nullptr) - { - // savegame info not found. This is not a savegame so leave it alone. - delete savegame; - continue; - } - auto fr = info->NewReader(); - FString title; - int check = G_ValidateSavegame(fr, &title, true); - fr.Close(); - delete savegame; - if (check != 0) - { - FSaveGameNode *node = new FSaveGameNode; - node->Filename = filepath; - node->bOldVersion = check == -1; - node->bMissingWads = check == -2; - node->SaveTitle = title; - InsertSaveNode(node); - } - } - } while (I_FindNext (filefirst, &c_file) == 0); - I_FindClose (filefirst); - } - } -} - - -//============================================================================= -// -// -// -//============================================================================= - -void FSavegameManager::NotifyNewSave(const FString &file, const FString &title, bool okForQuicksave, bool forceQuicksave) -{ - FSaveGameNode *node; - - if (file.IsEmpty()) - return; - - ReadSaveStrings(); - - // See if the file is already in our list - for (unsigned i = 0; iFilename.Compare(file) == 0) -#else - if (node->Filename.CompareNoCase(file) == 0) -#endif - { - node->SaveTitle = title; - node->bOldVersion = false; - node->bMissingWads = false; - if (okForQuicksave) - { - if (quickSaveSlot == nullptr || quickSaveSlot == (FSaveGameNode*)1 || forceQuicksave) quickSaveSlot = node; - LastAccessed = LastSaved = i - 1; // without item - } - return; - } - } - - node = new FSaveGameNode; - node->SaveTitle = title; - node->Filename = file; - node->bOldVersion = false; - node->bMissingWads = false; - int index = InsertSaveNode(node); - - if (okForQuicksave) - { - if (quickSaveSlot == nullptr || quickSaveSlot == (FSaveGameNode*)1 || forceQuicksave) quickSaveSlot = node; - LastAccessed = LastSaved = index - 1; // without item - } - else - { - LastAccessed = ++LastSaved; - } -} - -//============================================================================= -// -// Loads the savegame -// -//============================================================================= - -void FSavegameManager::LoadSavegame(int Selected) -{ - auto sel = savegameManager.GetSavegame(Selected); - if (sel && !sel->bOldVersion && !sel->bMissingWads) - { - savegameManager.LoadGame(SaveGames[Selected]); - if (quickSaveSlot == (FSaveGameNode*)1) - { - quickSaveSlot = SaveGames[Selected]; - } - M_ClearMenus(); - LastAccessed = Selected; - } -} - - -//============================================================================= -// -// -// -//============================================================================= - -void FSavegameManager::DoSave(int Selected, const char *savegamestring) -{ - if (Selected != 0) - { - auto node = *SaveGames[Selected]; - node.SaveTitle = savegamestring; - savegameManager.SaveGame(&node, true, false); - } - else - { - // Find an unused filename and save as that - FString filename; - int i; - - for (i = 0;; ++i) - { - filename = G_BuildSaveName(FStringf("save%04d", i)); - if (!FileExists(filename)) - { - break; - } - } - FSaveGameNode sg{ savegamestring, filename }; - savegameManager.SaveGame(&sg, true, false); - } - M_ClearMenus(); -} - - -//============================================================================= -// -// -// -//============================================================================= - -unsigned FSavegameManager::ExtractSaveData(int index) -{ - FResourceFile *resf; - FSaveGameNode *node; - - if (index == -1) - { - if (SaveGames.Size() > 0 && SaveGames[0]->bNoDelete) - { - index = LastSaved + 1; - } - else - { - index = LastAccessed < 0? 0 : LastAccessed; - } - } - - UnloadSaveData(); - - if ((unsigned)index < SaveGames.Size() && - (node = SaveGames[index]) && - !node->Filename.IsEmpty() && - !node->bOldVersion && - (resf = FResourceFile::OpenResourceFile(node->Filename.GetChars(), true)) != nullptr) - { - FResourceLump *info = resf->FindLump("info.json"); - if (info == nullptr) - { - // this should not happen because the file has already been verified. - return index; - } - - void* data = info->Lock(); - FSerializer arc; - if (!arc.OpenReader((const char*)data, info->LumpSize)) - { - info->Unlock(); - return index; - } - info->Unlock(); - - FString comment, fcomment, ncomment, mtime; - - arc("Creation Time", comment) - ("Map Label", fcomment) - ("Map Name", ncomment) - ("Map Time", mtime); - - comment.AppendFormat("\n%s - %s\n%s", fcomment.GetChars(), ncomment.GetChars(), mtime.GetChars()); - SaveCommentString = comment; - - FResourceLump *pic = resf->FindLump("savepic.png"); - if (pic != nullptr) - { - FileReader picreader; - - picreader.OpenMemoryArray([=](TArray &array) - { - auto cache = pic->Lock(); - array.Resize(pic->LumpSize); - memcpy(&array[0], cache, pic->LumpSize); - pic->Unlock(); - return true; - }); - PNGHandle *png = M_VerifyPNG(picreader); - if (png != nullptr) - { - SavePic = PNGTexture_CreateFromFile(png, node->Filename); - delete png; - if (SavePic && SavePic->GetDisplayWidth() == 1 && SavePic->GetDisplayHeight() == 1) - { - delete SavePic; - SavePic = nullptr; - SavePicData.Clear(); - } - } - } - delete resf; - } - return index; -} - -//============================================================================= -// -// -// -//============================================================================= - -void FSavegameManager::UnloadSaveData() -{ - if (SavePic != nullptr) - { - delete SavePic; - } - - SaveCommentString = ""; - SavePic = nullptr; - SavePicData.Clear(); -} - -//============================================================================= -// -// -// -//============================================================================= - -void FSavegameManager::ClearSaveStuff() -{ - UnloadSaveData(); - if (quickSaveSlot == (FSaveGameNode*)1) - { - quickSaveSlot = nullptr; - } -} - - -//============================================================================= -// -// -// -//============================================================================= - -bool FSavegameManager::DrawSavePic(int x, int y, int w, int h) -{ - if (SavePic == nullptr) return false; - DrawTexture(twod, SavePic, x, y, DTA_DestWidth, w, DTA_DestHeight, h, DTA_Masked, false, TAG_DONE); - return true; -} - - -//============================================================================= -// -// -// -//============================================================================= - -void FSavegameManager::SetFileInfo(int Selected) -{ - if (!SaveGames[Selected]->Filename.IsEmpty()) - { - SaveCommentString.Format("File on disk:\n%s", SaveGames[Selected]->Filename.GetChars()); - } -} - - -//============================================================================= -// -// -// -//============================================================================= - -unsigned FSavegameManager::SavegameCount() -{ - return SaveGames.Size(); -} - - -//============================================================================= -// -// -// -//============================================================================= - -FSaveGameNode *FSavegameManager::GetSavegame(int i) -{ - return SaveGames[i]; -} - - -//============================================================================= -// -// -// -//============================================================================= - -void FSavegameManager::InsertNewSaveNode() -{ - NewSaveNode.SaveTitle = GStrings("NEWSAVE"); - NewSaveNode.bNoDelete = true; - SaveGames.Insert(0, &NewSaveNode); -} - - -//============================================================================= -// -// -// -//============================================================================= - -bool FSavegameManager::RemoveNewSaveNode() -{ - if (SaveGames[0] == &NewSaveNode) - { - SaveGames.Delete(0); - return true; - } - return false; -} - -//============================================================================= -// -// -// -//============================================================================= - -CVAR(Bool, saveloadconfirmation, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) - -CVAR(Int, autosavenum, 0, CVAR_NOSET | CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -static int nextautosave = -1; -CVAR(Int, disableautosave, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -CUSTOM_CVAR(Int, autosavecount, 4, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -{ - if (self < 1) - self = 1; -} - -CVAR(Int, quicksavenum, 0, CVAR_NOSET | CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -static int nextquicksave = -1; - CUSTOM_CVAR(Int, quicksavecount, 4, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -{ - if (self < 1) - self = 1; -} - -void M_Autosave() -{ - if (disableautosave) return; - if (!gi->CanSave()) return; - FString description; - FString file; - // Keep a rotating sets of autosaves - UCVarValue num; - const char* readableTime; - int count = autosavecount != 0 ? autosavecount : 1; - - if (nextautosave == -1) - { - nextautosave = (autosavenum + 1) % count; - } - - num.Int = nextautosave; - autosavenum.ForceSet(num, CVAR_Int); - - FSaveGameNode sg; - sg.Filename = G_BuildSaveName(FStringf("auto%04d", nextautosave)); - readableTime = myasctime(); - sg.SaveTitle.Format("Autosave %s", readableTime); - nextautosave = (nextautosave + 1) % count; - savegameManager.SaveGame(&sg, false, false); -} - -CCMD(autosave) -{ - gameaction = ga_autosave; -} - -CCMD(rotatingquicksave) -{ - if (!gi->CanSave()) return; - FString description; - FString file; - // Keep a rotating sets of quicksaves - UCVarValue num; - const char* readableTime; - int count = quicksavecount != 0 ? quicksavecount : 1; - - if (nextquicksave == -1) - { - nextquicksave = (quicksavenum + 1) % count; - } - - num.Int = nextquicksave; - quicksavenum.ForceSet(num, CVAR_Int); - - FSaveGameNode sg; - sg.Filename = G_BuildSaveName(FStringf("quick%04d", nextquicksave)); - readableTime = myasctime(); - sg.SaveTitle.Format("Quicksave %s", readableTime); - nextquicksave = (nextquicksave + 1) % count; - savegameManager.SaveGame(&sg, false, false); -} - - -//============================================================================= -// -// -// -//============================================================================= - -CCMD(quicksave) -{ // F6 - if (!gi->CanSave()) return; - - if (savegameManager.quickSaveSlot == NULL || savegameManager.quickSaveSlot == (FSaveGameNode*)1) - { - M_StartControlPanel(true); - M_SetMenu(NAME_Savegamemenu); - return; - } - - auto slot = savegameManager.quickSaveSlot; - - // [mxd]. Just save the game, no questions asked. - if (!saveloadconfirmation) - { - savegameManager.SaveGame(savegameManager.quickSaveSlot, true, true); - return; - } - - FString tempstring = GStrings("QSPROMPT"); - tempstring.Substitute("%s", slot->SaveTitle.GetChars()); - M_StartControlPanel(true); - - DMenu* newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, INT_MAX, false, NAME_None, [](bool res) - { - if (res) - { - savegameManager.SaveGame(savegameManager.quickSaveSlot, true, true); - } - return true; - }); - - M_ActivateMenu(newmenu); -} - -//============================================================================= -// -// -// -//============================================================================= - -CCMD(quickload) -{ // F9 -#if 0 - if (netgame) - { - M_StartControlPanel(true); - M_StartMessage(GStrings("QLOADNET"), 1); - return; - } -#endif - - if (savegameManager.quickSaveSlot == nullptr || savegameManager.quickSaveSlot == (FSaveGameNode*)1) - { - M_StartControlPanel(true); - // signal that whatever gets loaded should be the new quicksave - savegameManager.quickSaveSlot = (FSaveGameNode*)1; - M_SetMenu(NAME_Loadgamemenu); - return; - } - - // [mxd]. Just load the game, no questions asked. - if (!saveloadconfirmation) - { - savegameManager.LoadGame(savegameManager.quickSaveSlot); - return; - } - FString tempstring = GStrings("QLPROMPT"); - tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars()); - - M_StartControlPanel(true); - - DMenu* newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, INT_MAX, false, NAME_None, [](bool res) - { - if (res) - { - savegameManager.LoadGame(savegameManager.quickSaveSlot); - } - return true; - }); - M_ActivateMenu(newmenu); -} diff --git a/source/core/savegamehelp.cpp b/source/core/savegamehelp.cpp index 9a347f7dc..26dad0812 100644 --- a/source/core/savegamehelp.cpp +++ b/source/core/savegamehelp.cpp @@ -53,6 +53,8 @@ #include "gamestruct.h" #include "automap.h" #include "statusbar.h" +#include "gamestate.h" +#include "razemenu.h" static CompositeSavegameWriter savewriter; static FResourceFile *savereader; @@ -564,3 +566,88 @@ void LoadEngineState() fr.Close(); } } + +//============================================================================= +// +// +// +//============================================================================= + +CVAR(Bool, saveloadconfirmation, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CVAR(Int, autosavenum, 0, CVAR_NOSET | CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +static int nextautosave = -1; +CVAR(Int, disableautosave, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Int, autosavecount, 4, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 1) + self = 1; +} + +CVAR(Int, quicksavenum, 0, CVAR_NOSET | CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +static int nextquicksave = -1; + CUSTOM_CVAR(Int, quicksavecount, 4, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 1) + self = 1; +} + +void M_Autosave() +{ + if (disableautosave) return; + if (!gi->CanSave()) return; + FString description; + FString file; + // Keep a rotating sets of autosaves + UCVarValue num; + const char* readableTime; + int count = autosavecount != 0 ? autosavecount : 1; + + if (nextautosave == -1) + { + nextautosave = (autosavenum + 1) % count; + } + + num.Int = nextautosave; + autosavenum.ForceSet(num, CVAR_Int); + + FSaveGameNode sg; + sg.Filename = G_BuildSaveName(FStringf("auto%04d", nextautosave)); + readableTime = myasctime(); + sg.SaveTitle.Format("Autosave %s", readableTime); + nextautosave = (nextautosave + 1) % count; + //savegameManager.SaveGame(&sg, false, false); +} + +CCMD(autosave) +{ + gameaction = ga_autosave; +} + +CCMD(rotatingquicksave) +{ + if (!gi->CanSave()) return; + FString description; + FString file; + // Keep a rotating sets of quicksaves + UCVarValue num; + const char* readableTime; + int count = quicksavecount != 0 ? quicksavecount : 1; + + if (nextquicksave == -1) + { + nextquicksave = (quicksavenum + 1) % count; + } + + num.Int = nextquicksave; + quicksavenum.ForceSet(num, CVAR_Int); + + FSaveGameNode sg; + sg.Filename = G_BuildSaveName(FStringf("quick%04d", nextquicksave)); + readableTime = myasctime(); + sg.SaveTitle.Format("Quicksave %s", readableTime); + nextquicksave = (nextquicksave + 1) % count; + //savegameManager.SaveGame(&sg, false, false); +} + + diff --git a/source/core/savegamehelp.h b/source/core/savegamehelp.h index 2cfbb58fc..23c9a844f 100644 --- a/source/core/savegamehelp.h +++ b/source/core/savegamehelp.h @@ -19,6 +19,7 @@ int G_ValidateSavegame(FileReader &fr, FString *savetitle, bool formenu); void SaveEngineState(); void LoadEngineState(); +void M_Autosave(); #define SAVEGAME_EXT ".dsave" diff --git a/source/core/screenjob.cpp b/source/core/screenjob.cpp index 0df4952b8..ecef96a27 100644 --- a/source/core/screenjob.cpp +++ b/source/core/screenjob.cpp @@ -43,7 +43,7 @@ #include "s_soundinternal.h" #include "animtexture.h" #include "gamestate.h" -#include "menu.h" +#include "razemenu.h" #include "raze_sound.h" #include "SmackerDecoder.h" #include "movie/playmve.h" diff --git a/source/core/statusbar.cpp b/source/core/statusbar.cpp index 306dc3838..fba769692 100644 --- a/source/core/statusbar.cpp +++ b/source/core/statusbar.cpp @@ -59,7 +59,7 @@ #include "m_fixed.h" #include "gamecontrol.h" #include "gamestruct.h" -#include "menu.h" +#include "razemenu.h" #include "mapinfo.h" #include "../version.h" diff --git a/source/exhumed/src/d_menu.cpp b/source/exhumed/src/d_menu.cpp index e8250ffdf..d03242d38 100644 --- a/source/exhumed/src/d_menu.cpp +++ b/source/exhumed/src/d_menu.cpp @@ -30,9 +30,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "gamestate.h" #include "mapinfo.h" #include "gamecontrol.h" +#include "v_draw.h" -#include "menu/menu.h" // to override the local menu.h +#include "razemenu.h" // to override the local menu.h #include "../../glbackend/glbackend.h" @@ -49,6 +50,7 @@ BEGIN_PS_NS void menu_DoPlasma(); double zoomsize = 0; +#if 0 class PSMainMenu : public DListMenu { @@ -81,6 +83,7 @@ class PSMainMenu : public DListMenu } } }; +#endif //---------------------------------------------------------------------------- @@ -91,6 +94,7 @@ class PSMainMenu : public DListMenu void GameInterface::DrawNativeMenuText(int fontnum, int state, double xpos, double ypos, float fontscale, const char* text, int flags) { +#if 0 int tilenum = (int)strtoll(text, nullptr, 0); double y = ypos - tilesiz[tilenum].y / 2; @@ -122,6 +126,7 @@ void GameInterface::DrawNativeMenuText(int fontnum, int state, double xpos, doub DrawTexture(twod, tex, 62, ypos - 12, DTA_FullscreenScale, FSMode_Fit320x200, DTA_TopLeft, true, TAG_DONE); DrawTexture(twod, tex, 207, ypos - 12, DTA_FullscreenScale, FSMode_Fit320x200, DTA_TopLeft, true, DTA_FlipX, true, TAG_DONE); } +#endif } @@ -183,17 +188,3 @@ void GameInterface::DrawMenuCaption(const DVector2& origin, const char* text) END_PS_NS - -//---------------------------------------------------------------------------- -// -// Class registration -// -//---------------------------------------------------------------------------- - - -static TMenuClassDescriptor _mm("Exhumed.MainMenu"); - -void RegisterPSMenus() -{ - menuClasses.Push(&_mm); -} diff --git a/source/exhumed/src/exhumed.cpp b/source/exhumed/src/exhumed.cpp index 8cd209610..1872f3601 100644 --- a/source/exhumed/src/exhumed.cpp +++ b/source/exhumed/src/exhumed.cpp @@ -46,7 +46,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "cheathandler.h" #include "inputstate.h" #include "d_protocol.h" -#include "core/menu/menu.h" +#include "razemenu.h" BEGIN_PS_NS @@ -481,7 +481,9 @@ void GameInterface::app_init() //int esi = 1; //int edi = esi; +#if 0 help_disabled = true; +#endif // Create the global level table. Parts of the engine need it, even though the game itself does not. for (int i = 0; i <= 32; i++) { diff --git a/source/exhumed/src/gameloop.cpp b/source/exhumed/src/gameloop.cpp index 8d6c8d08c..2c1d0a959 100644 --- a/source/exhumed/src/gameloop.cpp +++ b/source/exhumed/src/gameloop.cpp @@ -44,10 +44,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "cheathandler.h" #include "statistics.h" #include "g_input.h" -#include "core/menu/menu.h" +#include "razemenu.h" #include "d_net.h" #include "automap.h" #include "raze_music.h" +#include "v_draw.h" BEGIN_PS_NS diff --git a/source/exhumed/src/input.cpp b/source/exhumed/src/input.cpp index 64b8d513c..ef8662fbe 100644 --- a/source/exhumed/src/input.cpp +++ b/source/exhumed/src/input.cpp @@ -21,7 +21,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "player.h" #include "status.h" #include "view.h" -#include "menu.h" +#include "razemenu.h" BEGIN_PS_NS diff --git a/source/games/duke/src/d_menu.cpp b/source/games/duke/src/d_menu.cpp index 6eecfd132..39a4ecf75 100644 --- a/source/games/duke/src/d_menu.cpp +++ b/source/games/duke/src/d_menu.cpp @@ -35,7 +35,7 @@ Prepared for public release: 03/21/2003 - Charlie Wiederhold, 3D Realms #include "gamecvars.h" #include "gamecontrol.h" #include "c_bind.h" -#include "menu/menu.h" +#include "razemenu.h" #include "gstrings.h" #include "version.h" #include "names.h" @@ -83,6 +83,7 @@ static void Menu_DrawCursor(double x, double y, double scale, bool right) // //---------------------------------------------------------------------------- +#if 0 class DukeListMenu : public DListMenu { using Super = DListMenu; @@ -163,7 +164,7 @@ class DukeMainMenu : public DukeListMenu } }; - +#endif //---------------------------------------------------------------------------- // @@ -173,6 +174,7 @@ class DukeMainMenu : public DukeListMenu void GameInterface::DrawNativeMenuText(int fontnum, int state, double oxpos, double ypos, float fontscale, const char* text, int flags) { +#if 0 double xpos = oxpos; int trans; PalEntry pe; @@ -214,7 +216,7 @@ void GameInterface::DrawNativeMenuText(int fontnum, int state, double oxpos, dou else Menu_DrawCursor(oxpos - cursorOffset, ymid, cursorScale, false); } - +#endif } void GameInterface::MenuOpened() @@ -270,7 +272,7 @@ bool GameInterface::StartGame(FNewGameStartup& gs) { if (g_gameType & GAMEFLAG_SHAREWARE) { - M_StartMessage(GStrings("BUYDUKE"), 1, -1); + M_StartMessage(GStrings("BUYDUKE"), 1, NAME_None); return false; } } @@ -283,6 +285,7 @@ bool GameInterface::StartGame(FNewGameStartup& gs) static const short sounds_r[] = { 427, 428, 196, 195, 197 }; if (gs.Skill >=0 && gs.Skill <= 5) skillsound = isRR()? sounds_r[gs.Skill] : sounds_d[gs.Skill]; +#if 0 if (menu_sounds && skillsound >= 0 && SoundEnabled() && !netgame) { S_PlaySound(skillsound, CHAN_AUTO, CHANF_UI); @@ -295,6 +298,7 @@ bool GameInterface::StartGame(FNewGameStartup& gs) } Net_ClearFifo(); } +#endif auto map = FindMapByLevelNum(levelnum(gs.Episode, gs.Level)); if (map) { @@ -520,22 +524,3 @@ bool GameInterface::DrawSpecialScreen(const DVector2& origin, int tilenum) } END_DUKE_NS - -//---------------------------------------------------------------------------- -// -// Class registration -// -//---------------------------------------------------------------------------- - - -static TMenuClassDescriptor _mm("Duke.MainMenu"); -static TMenuClassDescriptor _lm("Duke.ListMenu"); - -static TMenuClassDescriptor _ism("Duke.ImageScrollerMenu"); // does not implement a new class, we only need the descriptor. - -void RegisterDuke3dMenus() -{ - menuClasses.Push(&_mm); - menuClasses.Push(&_lm); - menuClasses.Push(&_ism); -} diff --git a/source/games/duke/src/duke3d.h b/source/games/duke/src/duke3d.h index b918d7823..ed1638026 100644 --- a/source/games/duke/src/duke3d.h +++ b/source/games/duke/src/duke3d.h @@ -9,7 +9,7 @@ #include "polymost.h" #include "gamecvars.h" -#include "menu/menu.h" +#include "razemenu.h" #include "funct.h" #include "gamecontrol.h" #include "gamevar.h" @@ -20,6 +20,8 @@ #include "sounds.h" #include "soundefs.h" #include "binaryangle.h" +#include "gamestruct.h" +#include "v_draw.h" BEGIN_DUKE_NS diff --git a/source/games/duke/src/gamedef.cpp b/source/games/duke/src/gamedef.cpp index d6d78b3ff..d4b9aeb4e 100644 --- a/source/games/duke/src/gamedef.cpp +++ b/source/games/duke/src/gamedef.cpp @@ -40,7 +40,7 @@ Modifications for JonoF's port by Jonathon Fowler (jf@jonof.id.au) #include "printf.h" #include "filesystem.h" #include "mapinfo.h" -#include "menu.h" +#include "razemenu.h" #include "global.h" #include "m_argv.h" #include "sounds.h" diff --git a/source/sw/src/d_menu.cpp b/source/sw/src/d_menu.cpp index cacf40f9a..8f555bc8e 100644 --- a/source/sw/src/d_menu.cpp +++ b/source/sw/src/d_menu.cpp @@ -43,11 +43,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "network.h" #include "misc.h" -#include "menu.h" +#include "razemenu.h" #include "raze_sound.h" #include "sounds.h" #include "gamestate.h" #include "raze_music.h" +#include "v_draw.h" #include "../../glbackend/glbackend.h" @@ -62,6 +63,7 @@ BEGIN_SW_NS // //---------------------------------------------------------------------------- +#if 0 class SWMainMenu : public DListMenu { void Ticker() override @@ -102,6 +104,7 @@ public: } } }; +#endif //---------------------------------------------------------------------------- // @@ -111,6 +114,7 @@ public: void GameInterface::DrawNativeMenuText(int fontnum, int state, double xpos, double ypos, float fontscale, const char* text, int flags) { +#if 0 switch (fontnum) { case NIT_BigFont: @@ -144,6 +148,7 @@ void GameInterface::DrawNativeMenuText(int fontnum, int state, double xpos, doub DrawTexture(twod, tileGetTexture(pic_yinyang, true), x, y, DTA_FullscreenScale, FSMode_Fit320x200, DTA_CenterOffset, true, DTA_Color, 0xfff0f0f0, DTA_ScaleX, scale / 65536., DTA_ScaleY, scale / 65536., TAG_DONE); } +#endif } void GameInterface::QuitToTitle() @@ -200,7 +205,7 @@ bool GameInterface::StartGame(FNewGameStartup& gs) { if (g_gameType & GAMEFLAG_SHAREWARE) { - M_StartMessage(GStrings("BUYSW"), 1, -1); + M_StartMessage(GStrings("BUYSW"), 1, NAME_None); return false; } map = FindMapByLevelNum(5); @@ -251,19 +256,3 @@ void GameInterface::DrawMenuCaption(const DVector2& origin, const char* text) END_SW_NS - -//---------------------------------------------------------------------------- -// -// Class registration -// -//---------------------------------------------------------------------------- - - -static TMenuClassDescriptor _mm("ShadowWarrior.MainMenu"); -static TMenuClassDescriptor _so("ShadowWarrior.OrderMenu"); - -void RegisterSWMenus() -{ - menuClasses.Push(&_mm); - menuClasses.Push(&_so); -} diff --git a/source/sw/src/draw.cpp b/source/sw/src/draw.cpp index 56c526dff..0693b2444 100644 --- a/source/sw/src/draw.cpp +++ b/source/sw/src/draw.cpp @@ -51,9 +51,10 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "interp.h" #include "interpso.h" #include "sector.h" -#include "menu.h" +#include "razemenu.h" #include "v_2ddrawer.h" #include "v_video.h" +#include "v_draw.h" #include "glbackend/glbackend.h" BEGIN_SW_NS diff --git a/source/sw/src/game.cpp b/source/sw/src/game.cpp index 195912890..34f8406a9 100644 --- a/source/sw/src/game.cpp +++ b/source/sw/src/game.cpp @@ -64,7 +64,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "printf.h" #include "m_argv.h" #include "debugbreak.h" -#include "menu.h" +#include "razemenu.h" #include "raze_music.h" #include "statistics.h" #include "gstrings.h" @@ -77,6 +77,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "inputstate.h" #include "gamestate.h" #include "d_net.h" +#include "v_draw.h" //#include "crc32.h" diff --git a/source/sw/src/input.cpp b/source/sw/src/input.cpp index de3cef46a..fdd8d2e7f 100644 --- a/source/sw/src/input.cpp +++ b/source/sw/src/input.cpp @@ -29,7 +29,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "network.h" #include "gamecontrol.h" #include "player.h" -#include "menu.h" +#include "razemenu.h" BEGIN_SW_NS diff --git a/source/sw/src/panel.cpp b/source/sw/src/panel.cpp index aa80a0419..3b7582bcd 100644 --- a/source/sw/src/panel.cpp +++ b/source/sw/src/panel.cpp @@ -39,7 +39,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "v_2ddrawer.h" #include "weapon.h" -#include "menu.h" +#include "razemenu.h" #include "raze_sound.h" #include "glbackend/glbackend.h" diff --git a/source/sw/src/player.cpp b/source/sw/src/player.cpp index 7a768d971..ef7ad3528 100644 --- a/source/sw/src/player.cpp +++ b/source/sw/src/player.cpp @@ -50,9 +50,10 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "misc.h" #include "interp.h" #include "interpso.h" -#include "menu.h" +#include "razemenu.h" #include "gstrings.h" #include "raze_music.h" +#include "v_draw.h" #include "gamestate.h" BEGIN_SW_NS diff --git a/source/sw/src/scrip2.cpp b/source/sw/src/scrip2.cpp index b0eb8533c..d008ea556 100644 --- a/source/sw/src/scrip2.cpp +++ b/source/sw/src/scrip2.cpp @@ -37,7 +37,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "jsector.h" #include "parent.h" #include "sc_man.h" -#include "menu.h" +#include "razemenu.h" #include "quotemgr.h" #include "mapinfo.h" diff --git a/source/sw/src/sounds.cpp b/source/sw/src/sounds.cpp index 934bdbf41..1477c83fe 100644 --- a/source/sw/src/sounds.cpp +++ b/source/sw/src/sounds.cpp @@ -41,7 +41,7 @@ Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms #include "misc.h" #include "rts.h" #include "menus.h" -#include "menu.h" +#include "razemenu.h" #include "raze_music.h" #include "raze_sound.h" #include "filesystem.h"