diff --git a/source/blood/src/blood.cpp b/source/blood/src/blood.cpp index f900b7a80..d48682ef0 100644 --- a/source/blood/src/blood.cpp +++ b/source/blood/src/blood.cpp @@ -1726,14 +1726,14 @@ RESTART: } netUpdate(); MUSIC_Update(); - CONTROL_BindsEnabled = gInputMode == INPUT_MODE_0; + CONTROL_BindsEnabled = gInputMode == kInputGame; switch (gInputMode) { - case INPUT_MODE_1: + case kInputMenu: if (gGameMenuMgr.m_bActive) gGameMenuMgr.Process(); break; - case INPUT_MODE_0: + case kInputGame: LocalKeys(); break; default: @@ -1811,15 +1811,15 @@ RESTART: { switch (gInputMode) { - case INPUT_MODE_1: + case kInputMenu: if (gGameMenuMgr.m_bActive) gGameMenuMgr.Draw(); break; - case INPUT_MODE_2: + case kInputMessage: gPlayerMsg.ProcessKeys(); gPlayerMsg.Draw(); break; - case INPUT_MODE_3: + case kInputEndGame: gEndGameMgr.ProcessKeys(); gEndGameMgr.Draw(); break; diff --git a/source/blood/src/blood.h b/source/blood/src/blood.h index 400ff52af..d0a906abd 100644 --- a/source/blood/src/blood.h +++ b/source/blood/src/blood.h @@ -45,10 +45,10 @@ extern INICHAIN *pINIChain; extern INICHAIN const*pINISelected; enum INPUT_MODE { - INPUT_MODE_0 = 0, - INPUT_MODE_1, - INPUT_MODE_2, - INPUT_MODE_3, + kInputGame = 0, + kInputMenu, + kInputMessage, + kInputEndGame, }; extern INPUT_MODE gInputMode; diff --git a/source/blood/src/common_game.h b/source/blood/src/common_game.h index 5e59d0de8..74e5b41f0 100644 --- a/source/blood/src/common_game.h +++ b/source/blood/src/common_game.h @@ -470,6 +470,10 @@ enum searchpathtypes_t { SEARCHPATH_REMOVE = 1<<0, }; +#define NEG_ALPHA_TO_BLEND(alpha, blend, orientation) do { \ + if (alpha < 0) { blend = -alpha; alpha = 0; orientation |= RS_TRANS1; } \ +} while (0) + extern char *g_grpNamePtr; extern int loaddefinitions_game(const char *fn, int32_t preload); @@ -778,6 +782,14 @@ inline int approxDist(int dx, int dy) return dx+dy; } +// the point of this is to prevent re-running a function or calculation passed to potentialValue +// without making a new variable under each individual circumstance +inline void SetIfGreater(int32_t* variable, int32_t potentialValue) +{ + if (potentialValue > * variable) + * variable = potentialValue; +} + class Rect { public: int x0, y0, x1, y1; diff --git a/source/blood/src/controls.cpp b/source/blood/src/controls.cpp index 1876d58c6..ef9f6de08 100644 --- a/source/blood/src/controls.cpp +++ b/source/blood/src/controls.cpp @@ -154,7 +154,7 @@ void ctrlGetInput(void) fix16_t turn = 0; memset(&gInput, 0, sizeof(gInput)); - if (!gGameStarted || gInputMode != INPUT_MODE_0) + if (!gGameStarted || gInputMode != kInputGame) { CONTROL_GetInput(&info); return; @@ -215,12 +215,12 @@ void ctrlGetInput(void) if (gQuitRequest) gInput.keyFlags.quit = 1; - if (gGameStarted && gInputMode != INPUT_MODE_2 && gInputMode != INPUT_MODE_1 + if (gGameStarted && gInputMode != kInputMessage && gInputMode != kInputMenu && BUTTON(gamefunc_Send_Message)) { CONTROL_ClearButton(gamefunc_Send_Message); keyFlushScans(); - gInputMode = INPUT_MODE_2; + gInputMode = kInputMessage; } if (BUTTON(gamefunc_AutoRun)) diff --git a/source/blood/src/demo.cpp b/source/blood/src/demo.cpp index 9792781ef..c294dbb2e 100644 --- a/source/blood/src/demo.cpp +++ b/source/blood/src/demo.cpp @@ -285,13 +285,13 @@ void CDemo::ProcessKeys(void) { switch (gInputMode) { - case INPUT_MODE_1: + case kInputMenu: gGameMenuMgr.Process(); break; - case INPUT_MODE_2: + case kInputMessage: gPlayerMsg.ProcessKeys(); break; - case INPUT_MODE_0: + case kInputGame: { char nKey; while ((nKey = keyGetScan()) != 0) @@ -317,7 +317,7 @@ void CDemo::ProcessKeys(void) } break; default: - gInputMode = INPUT_MODE_0; + gInputMode = kInputGame; break; } } @@ -414,7 +414,7 @@ _DEMOPLAYBACK: if (viewFPSLimit()) { viewDrawScreen(); - if (gInputMode == INPUT_MODE_1 && CGameMenuMgr::m_bActive) + if (gInputMode == kInputMenu && CGameMenuMgr::m_bActive) gGameMenuMgr.Draw(); videoNextPage(); } diff --git a/source/blood/src/endgame.cpp b/source/blood/src/endgame.cpp index b3fc6c01d..608beba69 100644 --- a/source/blood/src/endgame.cpp +++ b/source/blood/src/endgame.cpp @@ -99,7 +99,7 @@ extern void EndLevel(void); void CEndGameMgr::Setup(void) { at1 = gInputMode; - gInputMode = INPUT_MODE_3; + gInputMode = kInputEndGame; at0 = 1; EndLevel(); sndStartSample(268, 128, -1, 1); diff --git a/source/blood/src/gamemenu.cpp b/source/blood/src/gamemenu.cpp index 84dfdf80a..e5cf066e2 100644 --- a/source/blood/src/gamemenu.cpp +++ b/source/blood/src/gamemenu.cpp @@ -122,7 +122,7 @@ bool CGameMenuMgr::Push(CGameMenu *pMenu, int nItem) if (nItem >= 0) pMenu->SetFocusItem(nItem); m_bActive = true; - gInputMode = INPUT_MODE_1; + gInputMode = kInputMenu; InitializeMenu(); m_menuchange_watchpoint = 1; m_mousecaught = 1; @@ -297,7 +297,7 @@ void CGameMenuMgr::Deactivate(void) m_bActive = false; mouseLockToWindow(1); - gInputMode = INPUT_MODE_0; + gInputMode = kInputGame; } bool CGameMenuMgr::MouseOutsideBounds(vec2_t const * const pos, const int32_t x, const int32_t y, const int32_t width, const int32_t height) diff --git a/source/blood/src/input.cpp b/source/blood/src/input.cpp new file mode 100644 index 000000000..05739c1a9 --- /dev/null +++ b/source/blood/src/input.cpp @@ -0,0 +1,403 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +//#include "global.h" +//#include "game.h" +#include "function.h" +#include "keyboard.h" +#include "mouse.h" +#include "joystick.h" +#include "control.h" +#include "input.h" +#include "menus.h" + +int32_t I_CheckAllInput(void) +{ + return + KB_KeyWaiting() + || MOUSE_GetButtons() + || JOYSTICK_GetButtons() +#if defined EDUKE32_IOS + || g_mouseClickState == MOUSE_PRESSED +#endif + ; +} +void I_ClearAllInput(void) +{ + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); + MOUSE_ClearAllButtons(); + JOYSTICK_ClearAllButtons(); + CONTROL_ClearAllButtons(); +#if defined EDUKE32_IOS + mouseAdvanceClickState(); +#endif +} + + +int32_t I_TextSubmit(void) +{ + return + KB_KeyPressed(sc_Enter) + || KB_KeyPressed(sc_kpad_Enter) +#if !defined EDUKE32_TOUCH_DEVICES + || MOUSEINACTIVECONDITIONAL(MOUSE_GetButtons()&LEFT_MOUSE) +#endif + || (JOYSTICK_GetGameControllerButtons()&(1< 0) + { + inputloc--; + *(t+inputloc) = 0; + } + } + else + { + if (ch == asc_Enter) + { + I_AdvanceTriggerClear(); + return 1; + } + else if (ch == asc_Escape) + { + I_ReturnTriggerClear(); + return -1; + } + else if (ch >= 32 && inputloc < maxlength && ch < 127) + { + if (!(flags & INPUT_NUMERIC) || (ch >= '0' && ch <= '9')) + { + // JBF 20040508: so we can have numeric only if we want + *(t+inputloc) = ch; + *(t+inputloc+1) = 0; + inputloc++; + } + } + } + } + + if (I_TextSubmit()) + { + I_TextSubmitClear(); + return 1; + } + if (I_ReturnTrigger()) + { + I_ReturnTriggerClear(); + return -1; + } + + return 0; +} diff --git a/source/blood/src/input.h b/source/blood/src/input.h new file mode 100644 index 000000000..777d8efdd --- /dev/null +++ b/source/blood/src/input.h @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef input_h_ +#define input_h_ + +#define WIN_IS_PRESSED ( KB_KeyPressed( sc_RightWin ) || KB_KeyPressed( sc_LeftWin ) ) +#define ALT_IS_PRESSED ( KB_KeyPressed( sc_RightAlt ) || KB_KeyPressed( sc_LeftAlt ) ) +#define SHIFTS_IS_PRESSED ( KB_KeyPressed( sc_RightShift ) || KB_KeyPressed( sc_LeftShift ) ) + +extern int32_t I_CheckAllInput(void); +extern void I_ClearAllInput(void); + +// Advance = Selecting a menu option || Saying "Yes" || Going forward in Help/Credits +// Return = Closing a sub-menu || Saying "No" +// General = Advance + Return = Skipping screens +// Escape = Opening the menu in-game (should not be any gamefuncs) + +extern int32_t I_AdvanceTrigger(void); +extern void I_AdvanceTriggerClear(void); +extern int32_t I_ReturnTrigger(void); +extern void I_ReturnTriggerClear(void); +extern int32_t I_GeneralTrigger(void); +extern void I_GeneralTriggerClear(void); +extern int32_t I_EscapeTrigger(void); +extern void I_EscapeTriggerClear(void); + +extern int32_t I_MenuUp(void); +extern void I_MenuUpClear(void); +extern int32_t I_MenuDown(void); +extern void I_MenuDownClear(void); +extern int32_t I_MenuLeft(void); +extern void I_MenuLeftClear(void); +extern int32_t I_MenuRight(void); +extern void I_MenuRightClear(void); + +extern int32_t I_PanelUp(void); +extern void I_PanelUpClear(void); +extern int32_t I_PanelDown(void); +extern void I_PanelDownClear(void); + +extern int32_t I_SliderLeft(void); +extern void I_SliderLeftClear(void); +extern int32_t I_SliderRight(void); +extern void I_SliderRightClear(void); + + +enum EnterTextFlags_t { + INPUT_NUMERIC = 0x00000001, +}; + +extern int32_t I_EnterText(char *t, int32_t maxlength, int32_t flags); + +#endif diff --git a/source/blood/src/menus.cpp b/source/blood/src/menus.cpp new file mode 100644 index 000000000..6f419f8a2 --- /dev/null +++ b/source/blood/src/menus.cpp @@ -0,0 +1,7353 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "cheats.h" +#include "communityapi.h" +#include "compat.h" +#include "demo.h" +#include "duke3d.h" +#include "input.h" +#include "in_android.h" +#include "menus.h" +#include "osdcmds.h" +#include "savegame.h" +#include "xxhash.h" + +#ifndef __ANDROID__ +droidinput_t droidinput; +#endif + +// common positions +#define MENU_MARGIN_REGULAR 40 +#define MENU_MARGIN_WIDE 32 +#define MENU_MARGIN_CENTER 160 +#define MENU_HEIGHT_CENTER 100 + +#define USERMAPENTRYLENGTH 25 + +static FORCE_INLINE void Menu_StartTextInput() +{ + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); +#if defined EDUKE32_TOUCH_DEVICES && defined SDL_MAJOR_VERSION && SDL_MAJOR_VERSION > 1 +# if defined __ANDROID__ + AndroidShowKeyboard(1); +# else + SDL_StartTextInput(); +# endif +#endif +} + +static FORCE_INLINE void Menu_StopTextInput() +{ +#if defined EDUKE32_TOUCH_DEVICES && defined SDL_MAJOR_VERSION && SDL_MAJOR_VERSION > 1 +# if defined __ANDROID__ + AndroidShowKeyboard(0); +# else + SDL_StopTextInput(); +# endif +#endif +} + +static FORCE_INLINE void rotatesprite_ybounds(int32_t sx, int32_t sy, int32_t z, int16_t a, int16_t picnum, int8_t dashade, char dapalnum, int32_t dastat, int32_t ydim_upper, int32_t ydim_lower) +{ + rotatesprite_(sx, sy, z, a, picnum, dashade, dapalnum, dastat, 0, 0, 0, ydim_upper, xdim-1, ydim_lower); +} + +static void mgametext(int32_t x, int32_t y, char const * t) +{ + G_ScreenText(MF_Bluefont.tilenum, x, y, MF_Bluefont.zoom, 0, 0, t, 0, MF_Bluefont.pal, 2|8|16|ROTATESPRITE_FULL16, 0, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags, 0, 0, xdim-1, ydim-1); +} + +static vec2_t mgametextcenterat(int32_t x, int32_t y, char const * t, int32_t f = 0) +{ + return G_ScreenText(MF_Bluefont.tilenum, x, y, MF_Bluefont.zoom, 0, 0, t, 0, MF_Bluefont.pal, 2|8|16|ROTATESPRITE_FULL16, 0, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags|f|TEXT_XCENTER, 0, 0, xdim-1, ydim-1); +} +static vec2_t mgametextcenter(int32_t x, int32_t y, char const * t, int32_t f = 0) +{ + return mgametextcenterat((MENU_MARGIN_CENTER<<16) + x, y, t, f); +} + +#define mminitext(x,y,t,p) minitext_(x, y, t, 0, p, 2|8|16|ROTATESPRITE_FULL16) +#define mmenutext menutext + +#ifndef EDUKE32_STANDALONE +static void shadowminitext(int32_t x, int32_t y, const char *t, int32_t p) +{ + int32_t f = 0; + + if (!minitext_lowercase) + f |= TEXT_UPPERCASE; + + G_ScreenTextShadow(1, 1, MINIFONT, x, y, 65536, 0, 0, t, 0, p, 2|8|16|ROTATESPRITE_FULL16, 0, 4<<16, 8<<16, 1<<16, 0, f, 0, 0, xdim-1, ydim-1); +} +#endif +static void creditsminitext(int32_t x, int32_t y, const char *t, int32_t p) +{ + int32_t f = TEXT_XCENTER; + + if (!minitext_lowercase) + f |= TEXT_UPPERCASE; + + G_ScreenTextShadow(1, 1, MINIFONT, x, y, 65536, 0, 0, t, 0, p, 2|8|16|ROTATESPRITE_FULL16, 0, 4<<16, 8<<16, 1<<16, 0, f, 0, 0, xdim-1, ydim-1); +} + +#pragma pack(push,1) +static savehead_t savehead; +#pragma pack(pop) + +static void Menu_DrawBackground(const vec2_t origin) +{ + rotatesprite_fs(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (100<<16), 65536L,0,MENUSCREEN,16,0,10+64); +} + +static void Menu_DrawTopBar(const vec2_t origin) +{ + if ((G_GetLogoFlags() & LOGO_NOTITLEBAR) == 0) + rotatesprite_fs(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (19<<16), MF_Redfont.cursorScale, 0,MENUBAR,16,0,10); +} + +static void Menu_DrawTopBarCaption(const char *caption, const vec2_t origin) +{ + static char t[64]; + size_t const srclen = strlen(caption); + size_t const dstlen = min(srclen, ARRAY_SIZE(t)-1); + memcpy(t, caption, dstlen); + t[dstlen] = '\0'; + char *p = &t[dstlen-1]; + if (*p == ':') + *p = '\0'; + captionmenutext(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (24<<16) + ((15>>1)<<16), t); +} + +static FORCE_INLINE int32_t Menu_CursorShade(void) +{ + return VM_OnEventWithReturn(EVENT_MENUCURSORSHADE, -1, myconnectindex, 4-(sintable[((int32_t) totalclock<<4)&2047]>>11)); +} +static void Menu_DrawCursorCommon(int32_t x, int32_t y, int32_t z, int32_t picnum, int32_t ydim_upper = 0, int32_t ydim_lower = ydim-1) +{ + rotatesprite_(x, y, z, 0, picnum, Menu_CursorShade(), 0, 2|8, 0, 0, 0, ydim_upper, xdim-1, ydim_lower); +} +static void Menu_DrawCursorLeft(int32_t x, int32_t y, int32_t z) +{ + if (FURY) return; + Menu_DrawCursorCommon(x, y, z, VM_OnEventWithReturn(EVENT_MENUCURSORLEFT, -1, myconnectindex, SPINNINGNUKEICON+(((int32_t) totalclock>>3)%7))); +} +static void Menu_DrawCursorRight(int32_t x, int32_t y, int32_t z) +{ + if (FURY) return; + Menu_DrawCursorCommon(x, y, z, VM_OnEventWithReturn(EVENT_MENUCURSORRIGHT, -1, myconnectindex, SPINNINGNUKEICON+6-((6+((int32_t) totalclock>>3))%7))); +} +static void Menu_DrawCursorTextTile(int32_t x, int32_t y, int32_t h, int32_t picnum, vec2_16_t const & siz, int32_t ydim_upper = 0, int32_t ydim_lower = ydim-1) +{ + vec2_t const adjsiz = { (siz.x>>1)<<16, siz.y<<16 }; + Menu_DrawCursorCommon(x + scale(adjsiz.x, h, adjsiz.y), y, divscale16(h, adjsiz.y), picnum, ydim_upper, ydim_lower); +} +static void Menu_DrawCursorText(int32_t x, int32_t y, int32_t h, int32_t ydim_upper = 0, int32_t ydim_lower = ydim-1) +{ + vec2_16_t const & siz = tilesiz[SPINNINGNUKEICON]; + + if (FURY || siz.x == 0) + { + Menu_DrawCursorTextTile(x, y, h, SMALLFNTCURSOR, tilesiz[SMALLFNTCURSOR], ydim_upper, ydim_lower); + return; + } + + Menu_DrawCursorTextTile(x, y, h, SPINNINGNUKEICON+(((int32_t) totalclock>>3)%7), siz, ydim_upper, ydim_lower); +} + + +static uint16_t g_oldSaveCnt; + + + + +/* +All MAKE_* macros are generally for the purpose of keeping state initialization +separate from actual data. Alternatively, they can serve to factor out repetitive +stuff and keep the important bits from getting lost to our eyes. + +They serve as a stand-in for C++ default value constructors, since this was written +when the codebase still used C89. + +Note that I prefer to include a space on the inside of the macro parentheses, since +they effectively stand in for curly braces as struct initializers. +*/ + + +MenuGameplayStemEntry g_MenuGameplayEntries[MAXMENUGAMEPLAYENTRIES]; + +// common font types +// tilenums are set after namesdyn runs + +// emptychar x,y between x,y zoom cursorLeft cursorCenter cursorScale textflags +// tilenum shade_deselected shade_disabled pal pal_selected pal_deselected pal_disabled +MenuFont_t MF_Redfont = { { 5<<16, 15<<16 }, { 0, 0 }, 65536, 20<<16, 110<<16, 65536, TEXT_BIGALPHANUM | TEXT_UPPERCASE, + -1, 10, 0, 0, 0, 0, 1, + 0, 0, 1 }; +MenuFont_t MF_Bluefont = { { 5<<16, 7<<16 }, { 0, 0 }, 65536, 10<<16, 110<<16, 32768, 0, + -1, 10, 0, 0, 10, 10, 16, + 0, 0, 16 }; +MenuFont_t MF_Minifont = { { 4<<16, 5<<16 }, { 1<<16, 1<<16 }, 65536, 10<<16, 110<<16, 32768, 0, + -1, 10, 0, 0, 2, 2, 0, + 0, 0, 16 }; + + +static MenuMenuFormat_t MMF_Top_Main = { { MENU_MARGIN_CENTER<<16, 55<<16, }, -(170<<16) }; +static MenuMenuFormat_t MMF_Top_Episode = { { MENU_MARGIN_CENTER<<16, 48<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_Top_NewGameCustom = { { MENU_MARGIN_CENTER<<16, 48<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_Top_NewGameCustomSub = { { MENU_MARGIN_CENTER<<16, 48<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_Top_Skill = { { MENU_MARGIN_CENTER<<16, 58<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_Top_Options = { { MENU_MARGIN_CENTER<<16, 38<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_Top_Joystick_Network = { { MENU_MARGIN_CENTER<<16, 70<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_BigOptions = { { MENU_MARGIN_WIDE<<16, 38<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_SmallOptions = { { MENU_MARGIN_WIDE<<16, 37<<16, }, 170<<16 }; +static MenuMenuFormat_t MMF_Macros = { { 26<<16, 40<<16, }, 160<<16 }; +static MenuMenuFormat_t MMF_SmallOptionsNarrow = { { MENU_MARGIN_REGULAR<<16, 38<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_KeyboardSetupFuncs = { { 50<<16, 34<<16, }, 151<<16 }; +static MenuMenuFormat_t MMF_MouseJoySetupBtns = { { 76<<16, 34<<16, }, 143<<16 }; +static MenuMenuFormat_t MMF_FuncList = { { 100<<16, 51<<16, }, 152<<16 }; +static MenuMenuFormat_t MMF_ColorCorrect = { { MENU_MARGIN_REGULAR<<16, 86<<16, }, 190<<16 }; +static MenuMenuFormat_t MMF_BigSliders = { { MENU_MARGIN_WIDE<<16, 37<<16, }, 190<<16 }; +static MenuMenuFormat_t MMF_LoadSave = { { 200<<16, 49<<16, }, 145<<16 }; +static MenuMenuFormat_t MMF_NetSetup = { { 36<<16, 38<<16, }, 190<<16 }; +static MenuMenuFormat_t MMF_FileSelectLeft = { { 40<<16, 45<<16, }, 162<<16 }; +static MenuMenuFormat_t MMF_FileSelectRight = { { 164<<16, 45<<16, }, 162<<16 }; + +static MenuEntryFormat_t MEF_Null = { 0, 0, 0 }; +static MenuEntryFormat_t MEF_MainMenu = { 4<<16, 0, 0 }; +static MenuEntryFormat_t MEF_OptionsMenu = { 7<<16, 0, 0 }; +static MenuEntryFormat_t MEF_LeftMenu = { 7<<16, 0, 120<<16 }; +static MenuEntryFormat_t MEF_CenterMenu = { 7<<16, 0, 0 }; +static MenuEntryFormat_t MEF_BigOptions_Apply = { 4<<16, 16<<16, -(260<<16) }; +static MenuEntryFormat_t MEF_BigOptionsRt = { 4<<16, 0, -(260<<16) }; +static MenuEntryFormat_t MEF_BigOptionsRtSections = { 3<<16, 0, -(260<<16) }; +#if defined USE_OPENGL || !defined EDUKE32_ANDROID_MENU +static MenuEntryFormat_t MEF_SmallOptions = { 1<<16, 0, -(260<<16) }; +#endif +static MenuEntryFormat_t MEF_BigCheats = { 3<<16, 0, -(260<<16) }; +static MenuEntryFormat_t MEF_Cheats = { 2<<16, 0, -(260<<16) }; +static MenuEntryFormat_t MEF_PlayerNarrow = { 1<<16, 0, 90<<16 }; +static MenuEntryFormat_t MEF_Macros = { 2<<16, -1, 268<<16 }; +static MenuEntryFormat_t MEF_VideoSetup = { 4<<16, 0, 168<<16 }; +static MenuEntryFormat_t MEF_VideoSetup_Apply = { 4<<16, 16<<16, 168<<16 }; +static MenuEntryFormat_t MEF_KBFuncList = { 3<<16, 0, -(225<<16) }; +static MenuEntryFormat_t MEF_FuncList = { 3<<16, 0, -(170<<16) }; +static MenuEntryFormat_t MEF_ColorCorrect = { 2<<16, 0, -(240<<16) }; +static MenuEntryFormat_t MEF_BigSliders = { 2<<16, 0, -(260<<16) }; +static MenuEntryFormat_t MEF_LoadSave = { 2<<16, -1, 78<<16 }; +static MenuEntryFormat_t MEF_NetSetup = { 4<<16, 0, 112<<16 }; +static MenuEntryFormat_t MEF_NetSetup_Confirm = { 4<<16, 16<<16, 112<<16 }; + +// common menu option sets +#define MAKE_MENUOPTIONSET(optionNames, optionValues, features) { optionNames, optionValues, &MMF_FuncList, &MEF_FuncList, &MF_Minifont, ARRAY_SIZE(optionNames), -1, 0, features } +#define MAKE_MENUOPTIONSETDYN(optionNames, optionValues, numOptions, features) { optionNames, optionValues, &MMF_FuncList, &MEF_FuncList, &MF_Minifont, numOptions, -1, 0, features } +#define MAKE_MENUOPTIONSETNULL { NULL, NULL, &MMF_FuncList, &MEF_FuncList, &MF_Minifont, 0, -1, 0, 0 } + +static char const *MEOSN_OffOn[] = { "Off", "On", }; +static MenuOptionSet_t MEOS_OffOn = MAKE_MENUOPTIONSET( MEOSN_OffOn, NULL, 0x3 ); +static char const *MEOSN_OnOff[] = { "On", "Off", }; +static MenuOptionSet_t MEOS_OnOff = MAKE_MENUOPTIONSET( MEOSN_OnOff, NULL, 0x3 ); +static char const *MEOSN_NoYes[] = { "No", "Yes", }; +static MenuOptionSet_t MEOS_NoYes = MAKE_MENUOPTIONSET( MEOSN_NoYes, NULL, 0x3 ); +static char const *MEOSN_YesNo[] = { "Yes", "No", }; +static MenuOptionSet_t MEOS_YesNo = MAKE_MENUOPTIONSET( MEOSN_YesNo, NULL, 0x3 ); + + +static char MenuGameFuncs[NUMGAMEFUNCTIONS][MAXGAMEFUNCLEN]; +static char const *MenuGameFuncNone = " -None-"; +static char const *MEOSN_Gamefuncs[NUMGAMEFUNCTIONS+1]; +static int32_t MEOSV_Gamefuncs[NUMGAMEFUNCTIONS+1]; +static MenuOptionSet_t MEOS_Gamefuncs = MAKE_MENUOPTIONSET( MEOSN_Gamefuncs, MEOSV_Gamefuncs, 0x1 ); + + + +/* +MenuEntry_t is passed in arrays of pointers so that the callback function +that is called when an entry is modified or activated can test equality of the current +entry pointer directly against the known ones, instead of relying on an ID number. + +That way, individual menu entries can be ifdef'd out painlessly. +*/ + +static MenuLink_t MEO_NULL = { MENU_NULL, MA_None, }; +static const char* MenuCustom = "Custom"; + +#define MAKE_MENUSTRING(...) { NULL, __VA_ARGS__, } +#define MAKE_MENUOPTION(...) { __VA_ARGS__, -1, } +#define MAKE_MENURANGE(...) { __VA_ARGS__, } +#define MAKE_MENUENTRY(...) { __VA_ARGS__, 0, 0, 0, } + + +#define MAKE_SPACER( EntryName, Height ) \ +static MenuSpacer_t MEO_ ## EntryName = { Height }; + +MAKE_SPACER( Space2, 2<<16 ); // bigoptions +MAKE_SPACER( Space4, 4<<16 ); // usermap, smalloptions, anything else non-top +MAKE_SPACER( Space6, 6<<16 ); // videosetup +MAKE_SPACER( Space8, 8<<16 ); // colcorr, redslide + +static MenuEntry_t ME_Space2_Redfont = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_Null, &MEO_Space2, Spacer ); +static MenuEntry_t ME_Space4_Bluefont = MAKE_MENUENTRY( NULL, &MF_Bluefont, &MEF_Null, &MEO_Space4, Spacer ); +static MenuEntry_t ME_Space4_Redfont = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_Null, &MEO_Space4, Spacer ); +#ifndef EDUKE32_SIMPLE_MENU +static MenuEntry_t ME_Space8_Bluefont = MAKE_MENUENTRY( NULL, &MF_Bluefont, &MEF_Null, &MEO_Space8, Spacer ); +#endif +static MenuEntry_t ME_Space6_Redfont = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_Null, &MEO_Space6, Spacer ); +static MenuEntry_t ME_Space8_Redfont = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_Null, &MEO_Space8, Spacer ); + +#define MAKE_MENU_TOP_ENTRYLINK( Title, Format, EntryName, LinkID ) \ +static MenuLink_t MEO_ ## EntryName = { LinkID, MA_Advance, };\ +static MenuEntry_t ME_ ## EntryName = MAKE_MENUENTRY( Title, &MF_Redfont, &Format, &MEO_ ## EntryName, Link ) + +static char const s_NewGame[] = "New Game"; +static char const s_SaveGame[] = "Save Game"; +static char const s_LoadGame[] = "Load Game"; +static char const s_Continue[] = "Continue"; +static char const s_Options[] = "Options"; +static char const s_Credits[] = "Credits"; + +MAKE_MENU_TOP_ENTRYLINK( s_NewGame, MEF_MainMenu, MAIN_NEWGAME, MENU_EPISODE ); +#ifdef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK( "Resume Game", MEF_MainMenu, MAIN_RESUMEGAME, MENU_CLOSE ); +#endif +MAKE_MENU_TOP_ENTRYLINK( s_NewGame, MEF_MainMenu, MAIN_NEWGAME_INGAME, MENU_NEWVERIFY ); +static MenuLink_t MEO_MAIN_NEWGAME_NETWORK = { MENU_NETWORK, MA_Advance, }; +MAKE_MENU_TOP_ENTRYLINK( s_SaveGame, MEF_MainMenu, MAIN_SAVEGAME, MENU_SAVE ); +MAKE_MENU_TOP_ENTRYLINK( s_LoadGame, MEF_MainMenu, MAIN_LOADGAME, MENU_LOAD ); +MAKE_MENU_TOP_ENTRYLINK( s_Options, MEF_MainMenu, MAIN_OPTIONS, MENU_OPTIONS ); +#ifdef EDUKE32_STANDALONE +MAKE_MENU_TOP_ENTRYLINK( "Read me!", MEF_MainMenu, MAIN_HELP, MENU_STORY ); +#else +MAKE_MENU_TOP_ENTRYLINK("Help", MEF_MainMenu, MAIN_HELP, MENU_STORY); +#endif +#ifndef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK( s_Credits, MEF_MainMenu, MAIN_CREDITS, MENU_CREDITS ); +#endif +MAKE_MENU_TOP_ENTRYLINK( "End Game", MEF_MainMenu, MAIN_QUITTOTITLE, MENU_QUITTOTITLE ); +MAKE_MENU_TOP_ENTRYLINK( "Quit", MEF_MainMenu, MAIN_QUIT, MENU_QUIT ); +#ifndef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK( "Quit Game", MEF_MainMenu, MAIN_QUITGAME, MENU_QUIT ); +#endif + +static MenuEntry_t *MEL_MAIN[] = { + &ME_MAIN_NEWGAME, + &ME_MAIN_LOADGAME, + &ME_MAIN_OPTIONS, + &ME_MAIN_HELP, +#ifndef EDUKE32_SIMPLE_MENU + &ME_MAIN_CREDITS, +#endif + &ME_MAIN_QUIT, +}; + +static MenuEntry_t *MEL_MAIN_INGAME[] = { +#ifdef EDUKE32_SIMPLE_MENU + &ME_MAIN_RESUMEGAME, +#else + &ME_MAIN_NEWGAME_INGAME, +#endif + &ME_MAIN_SAVEGAME, + &ME_MAIN_LOADGAME, + &ME_MAIN_OPTIONS, + &ME_MAIN_HELP, + &ME_MAIN_QUITTOTITLE, +#ifndef EDUKE32_SIMPLE_MENU + &ME_MAIN_QUITGAME, +#endif +}; + +// Episode and Skill will be dynamically generated after CONs are parsed +static MenuLink_t MEO_EPISODE = { MENU_SKILL, MA_Advance, }; +static MenuLink_t MEO_EPISODE_SHAREWARE = { MENU_BUYDUKE, MA_Advance, }; +static MenuEntry_t ME_EPISODE_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_CenterMenu, &MEO_EPISODE, Link ); +static MenuEntry_t ME_EPISODE[MAXVOLUMES]; +static MenuLink_t MEO_EPISODE_USERMAP = { MENU_USERMAP, MA_Advance, }; +static MenuEntry_t ME_EPISODE_USERMAP = MAKE_MENUENTRY( "User Map", &MF_Redfont, &MEF_CenterMenu, &MEO_EPISODE_USERMAP, Link ); +static MenuEntry_t *MEL_EPISODE[MAXVOLUMES+2]; // +2 for spacer and User Map + +static MenuLink_t MEO_NEWGAMECUSTOM_TEMPLATE = { MENU_NEWGAMECUSTOMSUB, MA_Advance, }; +static MenuLink_t MEO_NEWGAMECUSTOM[MAXMENUGAMEPLAYENTRIES]; +static MenuLink_t MEO_NEWGAMECUSTOMSUB_TEMPLATE = { MENU_SKILL, MA_Advance, }; +static MenuLink_t MEO_NEWGAMECUSTOMSUB[MAXMENUGAMEPLAYENTRIES][MAXMENUGAMEPLAYENTRIES]; +MenuEntry_t ME_NEWGAMECUSTOMENTRIES[MAXMENUGAMEPLAYENTRIES]; +MenuEntry_t ME_NEWGAMECUSTOMSUBENTRIES[MAXMENUGAMEPLAYENTRIES][MAXMENUGAMEPLAYENTRIES]; +static MenuEntry_t *MEL_NEWGAMECUSTOM[MAXMENUGAMEPLAYENTRIES]; +static MenuEntry_t *MEL_NEWGAMECUSTOMSUB[MAXMENUGAMEPLAYENTRIES]; + +static MenuEntry_t ME_SKILL_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_CenterMenu, &MEO_NULL, Link ); +static MenuEntry_t ME_SKILL[MAXSKILLS]; +static MenuEntry_t *MEL_SKILL[MAXSKILLS]; + +#ifdef EDUKE32_SIMPLE_MENU +static MenuLink_t MEO_GAMESETUP_SAVESETUP = { MENU_SAVESETUP, MA_Advance, }; +static MenuEntry_t ME_GAMESETUP_SAVESETUP = MAKE_MENUENTRY( "Save setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_SAVESETUP, Link ); +#endif + +#if defined STARTUP_SETUP_WINDOW && !defined EDUKE32_SIMPLE_MENU +static MenuOption_t MEO_GAMESETUP_STARTWIN = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.setup.forcesetup ); +static MenuEntry_t ME_GAMESETUP_STARTWIN = MAKE_MENUENTRY( "Startup window:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_STARTWIN, Option ); +#endif + +static char const *MEOSN_GAMESETUP_AIM_AUTO[] = { "Never", "Always", "Hitscan only", +#ifdef EDUKE32_ANDROID_MENU +"Extra wide" +#endif +}; +static int32_t MEOSV_GAMESETUP_AIM_AUTO[] = { 0, 1, 2, +#ifdef EDUKE32_ANDROID_MENU +3, +#endif +}; + +static MenuOptionSet_t MEOS_GAMESETUP_AIM_AUTO = MAKE_MENUOPTIONSET( MEOSN_GAMESETUP_AIM_AUTO, MEOSV_GAMESETUP_AIM_AUTO, 0x2 ); +static MenuOption_t MEO_GAMESETUP_AIM_AUTO = MAKE_MENUOPTION( &MF_Redfont, &MEOS_GAMESETUP_AIM_AUTO, &ud.config.AutoAim ); +static MenuEntry_t ME_GAMESETUP_AIM_AUTO = MAKE_MENUENTRY( "Auto aim:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_AIM_AUTO, Option ); + +static MenuOption_t MEO_GAMESETUP_ALWAYS_RUN = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &ud.auto_run); +static MenuEntry_t ME_GAMESETUP_ALWAYS_RUN = MAKE_MENUENTRY( "Always run:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_ALWAYS_RUN, Option ); + +static char const *MEOSN_GAMESETUP_WEAPSWITCH_PICKUP[] = { "Never", "If new", /*"If favored",*/ }; +static MenuOptionSet_t MEOS_GAMESETUP_WEAPSWITCH_PICKUP = MAKE_MENUOPTIONSET( MEOSN_GAMESETUP_WEAPSWITCH_PICKUP, NULL, 0x2 ); +static MenuOption_t MEO_GAMESETUP_WEAPSWITCH_PICKUP = MAKE_MENUOPTION( &MF_Redfont, &MEOS_GAMESETUP_WEAPSWITCH_PICKUP, NULL ); +static MenuEntry_t ME_GAMESETUP_WEAPSWITCH_PICKUP = MAKE_MENUENTRY( "Equip pickups:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_WEAPSWITCH_PICKUP, Option ); + +static char const *MEOSN_DemoRec[] = { "Off", "Running", }; +static MenuOptionSet_t MEOS_DemoRec = MAKE_MENUOPTIONSET( MEOSN_DemoRec, NULL, 0x3 ); +static MenuOption_t MEO_GAMESETUP_DEMOREC = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.m_recstat ); +static MenuEntry_t ME_GAMESETUP_DEMOREC = MAKE_MENUENTRY( "Record demo:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_DEMOREC, Option ); + +#ifdef _WIN32 +static MenuOption_t MEO_GAMESETUP_UPDATES = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &ud.config.CheckForUpdates ); +static MenuEntry_t ME_GAMESETUP_UPDATES = MAKE_MENUENTRY( "Online updates:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_UPDATES, Option ); +#endif + +static MenuOption_t MEO_ADULTMODE = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &ud.lockout); +static MenuEntry_t ME_ADULTMODE = MAKE_MENUENTRY( "Parental lock:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_ADULTMODE, Option ); +// static MenuLink_t MEO_ADULTMODE_PASSWORD = { MENU_ADULTPASSWORD, MA_None, }; +// static MenuEntry_t ME_ADULTMODE_PASSWORD = MAKE_MENUENTRY( "Enter Password", &MF_Redfont, &, &MEO_ADULTMODE_PASSWORD, Link ); + +#ifdef EDUKE32_ANDROID_MENU +static MenuOption_t MEO_GAMESETUP_CROUCHLOCK = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &droidinput.toggleCrouch); +static MenuEntry_t ME_GAMESETUP_CROUCHLOCK = MAKE_MENUENTRY("Crouch lock:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_CROUCHLOCK, Option); + +static MenuOption_t MEO_GAMESETUP_QUICKSWITCH = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &droidinput.quickSelectWeapon); +static MenuEntry_t ME_GAMESETUP_QUICKSWITCH = MAKE_MENUENTRY("Quick weapon switch:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_QUICKSWITCH, Option); +#endif + +#if defined(EDUKE32_ANDROID_MENU) || !defined(EDUKE32_SIMPLE_MENU) +static MenuLink_t MEO_GAMESETUP_CHEATS = { MENU_CHEATS, MA_Advance, }; +static MenuEntry_t ME_GAMESETUP_CHEATS = MAKE_MENUENTRY( "Cheats", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_CHEATS, Link ); + +static MenuEntry_t *MEL_GAMESETUP[] = { + &ME_ADULTMODE, +#if defined STARTUP_SETUP_WINDOW && !defined EDUKE32_SIMPLE_MENU + &ME_GAMESETUP_STARTWIN, +#endif +#ifndef EDUKE32_ANDROID_MENU + &ME_GAMESETUP_DEMOREC, +#ifdef _WIN32 + &ME_GAMESETUP_UPDATES, +#endif +#endif + &ME_GAMESETUP_CHEATS, +}; +#endif + +#ifndef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK( "Game Setup", MEF_OptionsMenu, OPTIONS_GAMESETUP, MENU_GAMESETUP ); +#endif +MAKE_MENU_TOP_ENTRYLINK( "Sound Setup", MEF_OptionsMenu, OPTIONS_SOUNDSETUP, MENU_SOUND ); +MAKE_MENU_TOP_ENTRYLINK( "Display Setup", MEF_OptionsMenu, OPTIONS_DISPLAYSETUP, MENU_DISPLAYSETUP ); +MAKE_MENU_TOP_ENTRYLINK( "Player Setup", MEF_OptionsMenu, OPTIONS_PLAYERSETUP, MENU_PLAYER ); +#ifndef EDUKE32_ANDROID_MENU +MAKE_MENU_TOP_ENTRYLINK( "Control Setup", MEF_OptionsMenu, OPTIONS_CONTROLS, MENU_CONTROLS ); + +MAKE_MENU_TOP_ENTRYLINK( "Keyboard Setup", MEF_BigOptionsRtSections, OPTIONS_KEYBOARDSETUP, MENU_KEYBOARDSETUP ); +MAKE_MENU_TOP_ENTRYLINK( "Mouse Setup", MEF_BigOptionsRtSections, OPTIONS_MOUSESETUP, MENU_MOUSESETUP ); +#endif +MAKE_MENU_TOP_ENTRYLINK( "Gamepad Setup", MEF_BigOptionsRtSections, OPTIONS_JOYSTICKSETUP, MENU_JOYSTICKSETUP ); +#ifdef EDUKE32_ANDROID_MENU +MAKE_MENU_TOP_ENTRYLINK( "Touch Setup", MEF_BigOptionsRtSections, OPTIONS_TOUCHSETUP, MENU_TOUCHSETUP ); +#endif +#ifdef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK("Cheats", MEF_OptionsMenu, OPTIONS_CHEATS, MENU_CHEATS); +#endif + +static int32_t newresolution, newrendermode, newfullscreen, newvsync, newborderless; + +enum resflags_t { + RES_FS = 0x1, + RES_WIN = 0x2, +}; + +#define MAXRESOLUTIONSTRINGLENGTH 19 + +typedef struct resolution_t { + int32_t xdim, ydim; + int32_t flags; + int32_t bppmax; + char name[MAXRESOLUTIONSTRINGLENGTH]; +} resolution_t; + +resolution_t resolution[MAXVALIDMODES]; + +static char const *MEOSN_VIDEOSETUP_RESOLUTION[MAXVALIDMODES]; +static MenuOptionSet_t MEOS_VIDEOSETUP_RESOLUTION = MAKE_MENUOPTIONSETDYN( MEOSN_VIDEOSETUP_RESOLUTION, NULL, 0, 0x0 ); +static MenuOption_t MEO_VIDEOSETUP_RESOLUTION = MAKE_MENUOPTION( &MF_Redfont, &MEOS_VIDEOSETUP_RESOLUTION, &newresolution ); +static MenuEntry_t ME_VIDEOSETUP_RESOLUTION = MAKE_MENUENTRY( "Resolution:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_RESOLUTION, Option ); + +#ifdef USE_OPENGL +#ifdef POLYMER +static char const *MEOSN_VIDEOSETUP_RENDERER[] = { "Classic", "Polymost", "Polymer", }; +static int32_t MEOSV_VIDEOSETUP_RENDERER[] = { REND_CLASSIC, REND_POLYMOST, REND_POLYMER, }; +#else +static char const *MEOSN_VIDEOSETUP_RENDERER[] = { "Classic", "OpenGL", }; +static int32_t MEOSV_VIDEOSETUP_RENDERER[] = { REND_CLASSIC, REND_POLYMOST, }; +#endif + +static MenuOptionSet_t MEOS_VIDEOSETUP_RENDERER = MAKE_MENUOPTIONSET( MEOSN_VIDEOSETUP_RENDERER, MEOSV_VIDEOSETUP_RENDERER, 0x2 ); + +static MenuOption_t MEO_VIDEOSETUP_RENDERER = MAKE_MENUOPTION( &MF_Redfont, &MEOS_VIDEOSETUP_RENDERER, &newrendermode ); +static MenuEntry_t ME_VIDEOSETUP_RENDERER = MAKE_MENUENTRY( "Renderer:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_RENDERER, Option ); +#endif + +static MenuOption_t MEO_VIDEOSETUP_FULLSCREEN = MAKE_MENUOPTION( &MF_Redfont, &MEOS_YesNo, &newfullscreen ); +static MenuEntry_t ME_VIDEOSETUP_FULLSCREEN = MAKE_MENUENTRY( "Windowed:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_FULLSCREEN, Option ); + +static char const *MEOSN_VIDEOSETUP_BORDERLESS [] = { "No", "Yes", "Auto", }; +static int32_t MEOSV_VIDEOSETUP_BORDERLESS [] = { 0, 1, 2, }; +static MenuOptionSet_t MEOS_VIDEOSETUP_BORDERLESS = MAKE_MENUOPTIONSET(MEOSN_VIDEOSETUP_BORDERLESS, MEOSV_VIDEOSETUP_BORDERLESS, 0x2); +static MenuOption_t MEO_VIDEOSETUP_BORDERLESS = MAKE_MENUOPTION(&MF_Redfont, &MEOS_VIDEOSETUP_BORDERLESS, &newborderless); +static MenuEntry_t ME_VIDEOSETUP_BORDERLESS = MAKE_MENUENTRY("Borderless:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_BORDERLESS, Option); + +static char const *MEOSN_VIDEOSETUP_VSYNC [] = { "Adaptive", "Off", "On", }; +static int32_t MEOSV_VIDEOSETUP_VSYNC [] = { -1, 0, 1, }; +static MenuOptionSet_t MEOS_VIDEOSETUP_VSYNC = MAKE_MENUOPTIONSET(MEOSN_VIDEOSETUP_VSYNC, MEOSV_VIDEOSETUP_VSYNC, 0x2); +static MenuOption_t MEO_VIDEOSETUP_VSYNC = MAKE_MENUOPTION(&MF_Redfont, &MEOS_VIDEOSETUP_VSYNC, &newvsync); +static MenuEntry_t ME_VIDEOSETUP_VSYNC = MAKE_MENUENTRY("VSync:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_VSYNC, Option); + + + +static char const *MEOSN_VIDEOSETUP_FRAMELIMIT [] = { "None", "30 fps", "60 fps", "75 fps", "100 fps", "120 fps", "144 fps", "165 fps", "240 fps" }; +static int32_t MEOSV_VIDEOSETUP_FRAMELIMIT [] = { 0, 30, 60, 75, 100, 120, 144, 165, 240 }; +static MenuOptionSet_t MEOS_VIDEOSETUP_FRAMELIMIT = MAKE_MENUOPTIONSET(MEOSN_VIDEOSETUP_FRAMELIMIT, MEOSV_VIDEOSETUP_FRAMELIMIT, 0x0); +static MenuOption_t MEO_VIDEOSETUP_FRAMELIMIT= MAKE_MENUOPTION(&MF_Redfont, &MEOS_VIDEOSETUP_FRAMELIMIT, &r_maxfps); +static MenuEntry_t ME_VIDEOSETUP_FRAMELIMIT = MAKE_MENUENTRY("Framerate limit:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_FRAMELIMIT, Option); + +static MenuRangeInt32_t MEO_VIDEOSETUP_FRAMELIMITOFFSET = MAKE_MENURANGE( &r_maxfpsoffset, &MF_Redfont, -10, 10, 0, 21, 1 ); +static MenuEntry_t ME_VIDEOSETUP_FRAMELIMITOFFSET = MAKE_MENUENTRY( "FPS offset:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_FRAMELIMITOFFSET, RangeInt32 ); + +static MenuEntry_t ME_VIDEOSETUP_APPLY = MAKE_MENUENTRY( "Apply Changes", &MF_Redfont, &MEF_BigOptions_Apply, &MEO_NULL, Link ); + + +static MenuLink_t MEO_DISPLAYSETUP_COLORCORR = { MENU_COLCORR, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_COLORCORR = MAKE_MENUENTRY( "Color Correction", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_COLORCORR, Link ); + + +static char const *MEOSN_DISPLAYSETUP_UPSCALING[] = { "None", "2x" }; +static int32_t MEOSV_DISPLAYSETUP_UPSCALING[] = { 1, 2 }; +static MenuOptionSet_t MEOS_DISPLAYSETUP_UPSCALING = MAKE_MENUOPTIONSET( MEOSN_DISPLAYSETUP_UPSCALING, MEOSV_DISPLAYSETUP_UPSCALING, 0x0 ); +static MenuOption_t MEO_DISPLAYSETUP_UPSCALING = MAKE_MENUOPTION( &MF_Redfont, &MEOS_DISPLAYSETUP_UPSCALING, &ud.detail ); +static MenuEntry_t ME_DISPLAYSETUP_UPSCALING = MAKE_MENUENTRY( "Upscaling:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_UPSCALING, Option ); + + +#ifndef EDUKE32_ANDROID_MENU +static MenuOption_t MEO_DISPLAYSETUP_ASPECTRATIO = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &r_usenewaspect); +static MenuEntry_t ME_DISPLAYSETUP_ASPECTRATIO = MAKE_MENUENTRY( "Widescreen:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_ASPECTRATIO, Option ); +#endif + +static MenuOption_t MEO_DISPLAYSETUP_VOXELS = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &usevoxels); +static MenuEntry_t ME_DISPLAYSETUP_VOXELS = MAKE_MENUENTRY( "Voxels:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_VOXELS, Option ); + +static MenuRangeInt32_t MEO_DISPLAYSETUP_FOV = MAKE_MENURANGE( &ud.fov, &MF_Redfont, 70, 120, 0, 11, 1 ); +static MenuEntry_t ME_DISPLAYSETUP_FOV = MAKE_MENUENTRY( "FOV:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_FOV, RangeInt32 ); + + +#ifdef USE_OPENGL +# if !(defined EDUKE32_STANDALONE) || defined POLYMER +//POGOTODO: allow filtering again in standalone once indexed colour textures support filtering +#ifdef TEXFILTER_MENU_OPTIONS +static char const *MEOSN_DISPLAYSETUP_TEXFILTER[] = { "Classic", "Filtered" }; +static int32_t MEOSV_DISPLAYSETUP_TEXFILTER[] = { TEXFILTER_OFF, TEXFILTER_ON }; +static MenuOptionSet_t MEOS_DISPLAYSETUP_TEXFILTER = MAKE_MENUOPTIONSET( MEOSN_DISPLAYSETUP_TEXFILTER, MEOSV_DISPLAYSETUP_TEXFILTER, 0x2 ); +static MenuOption_t MEO_DISPLAYSETUP_TEXFILTER = MAKE_MENUOPTION( &MF_Redfont, &MEOS_DISPLAYSETUP_TEXFILTER, &gltexfiltermode ); +static MenuEntry_t ME_DISPLAYSETUP_TEXFILTER = MAKE_MENUENTRY( "Texture Mode:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_TEXFILTER, Option ); + +static char const *MEOSN_DISPLAYSETUP_ANISOTROPY[] = { "Max", "None", "2x", "4x", "8x", "16x", }; +static int32_t MEOSV_DISPLAYSETUP_ANISOTROPY[] = { 0, 1, 2, 4, 8, 16, }; +static MenuOptionSet_t MEOS_DISPLAYSETUP_ANISOTROPY = MAKE_MENUOPTIONSET( MEOSN_DISPLAYSETUP_ANISOTROPY, MEOSV_DISPLAYSETUP_ANISOTROPY, 0x0 ); +static MenuOption_t MEO_DISPLAYSETUP_ANISOTROPY = MAKE_MENUOPTION(&MF_Redfont, &MEOS_DISPLAYSETUP_ANISOTROPY, &glanisotropy); +static MenuEntry_t ME_DISPLAYSETUP_ANISOTROPY = MAKE_MENUENTRY( "Anisotropy:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_ANISOTROPY, Option ); +#endif +# endif + +# ifdef EDUKE32_ANDROID_MENU +static MenuOption_t MEO_DISPLAYSETUP_HIDEDPAD = MAKE_MENUOPTION(&MF_Redfont, &MEOS_NoYes, &droidinput.hideStick); +static MenuEntry_t ME_DISPLAYSETUP_HIDEDPAD = MAKE_MENUENTRY("Hide touch D-pad:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_HIDEDPAD, Option); + +static MenuRangeFloat_t MEO_DISPLAYSETUP_TOUCHALPHA = MAKE_MENURANGE(&droidinput.gameControlsAlpha, &MF_Redfont, 0, 1, 0, 16, 2); +static MenuEntry_t ME_DISPLAYSETUP_TOUCHALPHA = MAKE_MENUENTRY("UI opacity:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_TOUCHALPHA, RangeFloat); +# endif + +#endif + +static char const s_Scale[] = "Scale:"; + +static MenuOption_t MEO_SCREENSETUP_CROSSHAIR = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &ud.crosshair); +static MenuEntry_t ME_SCREENSETUP_CROSSHAIR = MAKE_MENUENTRY( "Crosshair:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_CROSSHAIR, Option ); +static MenuRangeInt32_t MEO_SCREENSETUP_CROSSHAIRSIZE = MAKE_MENURANGE( &ud.crosshairscale, &MF_Redfont, 25, 100, 0, 16, 2 ); +static MenuEntry_t ME_SCREENSETUP_CROSSHAIRSIZE = MAKE_MENUENTRY( s_Scale, &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SCREENSETUP_CROSSHAIRSIZE, RangeInt32 ); + +static int32_t vpsize; +static MenuRangeInt32_t MEO_SCREENSETUP_SCREENSIZE = MAKE_MENURANGE( &vpsize, &MF_Redfont, 0, 0, 0, 1, EnforceIntervals ); +static MenuOption_t MEO_SCREENSETUP_SCREENSIZE_TWO = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &vpsize ); +static MenuEntry_t ME_SCREENSETUP_SCREENSIZE = MAKE_MENUENTRY( "Status bar:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_SCREENSIZE, RangeInt32 ); +static MenuRangeInt32_t MEO_SCREENSETUP_TEXTSIZE = MAKE_MENURANGE( &ud.textscale, &MF_Redfont, 100, 400, 0, 16, 2 ); +static MenuEntry_t ME_SCREENSETUP_TEXTSIZE = MAKE_MENUENTRY( s_Scale, &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SCREENSETUP_TEXTSIZE, RangeInt32 ); +static MenuOption_t MEO_SCREENSETUP_LEVELSTATS = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &ud.levelstats); +static MenuEntry_t ME_SCREENSETUP_LEVELSTATS = MAKE_MENUENTRY( "Level stats:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_LEVELSTATS, Option ); + + +static MenuOption_t MEO_SCREENSETUP_SHOWPICKUPMESSAGES = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &ud.fta_on); +static MenuEntry_t ME_SCREENSETUP_SHOWPICKUPMESSAGES = MAKE_MENUENTRY( "Game messages:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_SHOWPICKUPMESSAGES, Option ); + + + +#ifdef EDUKE32_ANDROID_MENU +static char const *MEOSN_SCREENSETUP_STATUSBARONTOP[] = { "Bottom", "Top" }; +static int32_t MEOSV_SCREENSETUP_STATUSBARONTOP[] = { 0, 1 }; +static MenuOptionSet_t MEOS_SCREENSETUP_STATUSBARONTOP = MAKE_MENUOPTIONSET( MEOSN_SCREENSETUP_STATUSBARONTOP, MEOSV_SCREENSETUP_STATUSBARONTOP, 0x2 ); +static MenuOption_t MEO_SCREENSETUP_STATUSBARONTOP = MAKE_MENUOPTION(&MF_Redfont, &MEOS_SCREENSETUP_STATUSBARONTOP, &ud.hudontop); +static MenuEntry_t ME_SCREENSETUP_STATUSBARONTOP = MAKE_MENUENTRY( "Status bar:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_STATUSBARONTOP, Option ); +#endif + +static MenuRangeInt32_t MEO_SCREENSETUP_SBARSIZE = MAKE_MENURANGE( &ud.statusbarscale, &MF_Redfont, 50, 100, 0, 10, 2 ); +static MenuEntry_t ME_SCREENSETUP_SBARSIZE = MAKE_MENUENTRY( s_Scale, &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SCREENSETUP_SBARSIZE, RangeInt32 ); + + +static MenuLink_t MEO_DISPLAYSETUP_SCREENSETUP = { MENU_SCREENSETUP, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_SCREENSETUP = MAKE_MENUENTRY( "HUD setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_SCREENSETUP, Link ); + + +#ifndef EDUKE32_SIMPLE_MENU +#ifdef USE_OPENGL +static MenuLink_t MEO_DISPLAYSETUP_ADVANCED_GL_POLYMOST = { MENU_POLYMOST, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_ADVANCED_GL_POLYMOST = MAKE_MENUENTRY( "Polymost setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_ADVANCED_GL_POLYMOST, Link ); + +#ifdef POLYMER +static MenuLink_t MEO_DISPLAYSETUP_ADVANCED_GL_POLYMER = { MENU_POLYMER, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_ADVANCED_GL_POLYMER = MAKE_MENUENTRY("Polymer setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_ADVANCED_GL_POLYMER, Link); +#endif +#endif +#endif + +#ifndef EDUKE32_ANDROID_MENU +static MenuLink_t MEO_DISPLAYSETUP_VIDEOSETUP = { MENU_VIDEOSETUP, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_VIDEOSETUP = MAKE_MENUENTRY( "Video mode", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_VIDEOSETUP, Link ); +#endif + + +static MenuLink_t MEO_ENTERCHEAT = { MENU_CHEATENTRY, MA_None, }; +static MenuEntry_t ME_ENTERCHEAT = MAKE_MENUENTRY( "Enter Cheat Code", &MF_Redfont, &MEF_BigCheats, &MEO_ENTERCHEAT, Link ); + +static MenuLink_t MEO_CHEAT_WARP = { MENU_CHEAT_WARP, MA_None, }; +static MenuLink_t MEO_CHEAT_SKILL = { MENU_CHEAT_SKILL, MA_None, }; +// KEEPINSYNC cheats.h: enum CheatCodeFunctions +// KEEPINSYNC cheats.cpp: uint8_t CheatFunctionIDs[] +#define MAKE_MENUCHEAT( Name ) MAKE_MENUENTRY( Name, &MF_Bluefont, &MEF_Cheats, &MEO_NULL, Link ) +static MenuEntry_t ME_CheatCodes[] = { + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_CASHMAN] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_CORNHOLIO] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_STUFF] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_WEAPONS] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_ITEMS] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_INVENTORY] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_KEYS] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_HYPER] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_VIEW] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_SHOWMAP] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_UNLOCK] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_CLIP] ), + MAKE_MENUENTRY( CheatDescriptions[CHEAT_SCOTTY], &MF_Bluefont, &MEF_Cheats, &MEO_CHEAT_WARP, Link ), + MAKE_MENUENTRY( CheatDescriptions[CHEAT_SKILL], &MF_Bluefont, &MEF_Cheats, &MEO_CHEAT_SKILL, Link ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_MONSTERS] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_RATE] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_BETA] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_TODD] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_ALLEN] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_COORDS] ), + MAKE_MENUCHEAT( CheatDescriptions[CHEAT_DEBUG] ), +}; + +static MenuEntry_t *MEL_OPTIONS[] = { +#ifndef EDUKE32_SIMPLE_MENU + &ME_OPTIONS_GAMESETUP, +#endif + &ME_OPTIONS_DISPLAYSETUP, + &ME_OPTIONS_SOUNDSETUP, +#ifndef EDUKE32_ANDROID_MENU +#ifndef EDUKE32_SIMPLE_MENU + &ME_OPTIONS_PLAYERSETUP, +#endif + &ME_OPTIONS_CONTROLS, +#else + &ME_OPTIONS_TOUCHSETUP, +#endif +#ifdef EDUKE32_SIMPLE_MENU + &ME_GAMESETUP_SAVESETUP, + &ME_OPTIONS_CHEATS +#endif +}; + +static MenuEntry_t *MEL_CONTROLS[] = { +#ifndef EDUKE32_ANDROID_MENU + &ME_OPTIONS_KEYBOARDSETUP, + &ME_OPTIONS_MOUSESETUP, + &ME_OPTIONS_JOYSTICKSETUP, +#else + &ME_OPTIONS_TOUCHSETUP, +#endif + &ME_Space6_Redfont, + &ME_GAMESETUP_AIM_AUTO, + &ME_GAMESETUP_ALWAYS_RUN, + &ME_GAMESETUP_WEAPSWITCH_PICKUP, +#ifdef EDUKE32_ANDROID_MENU + &ME_GAMESETUP_QUICKSWITCH, + &ME_GAMESETUP_CROUCHLOCK, +#endif +}; + +static MenuEntry_t *MEL_CHEATS[ARRAY_SIZE(ME_CheatCodes)+1] = { + &ME_ENTERCHEAT, +}; + +static MenuEntry_t *MEL_VIDEOSETUP[] = { + &ME_VIDEOSETUP_RESOLUTION, +#ifdef USE_OPENGL + &ME_VIDEOSETUP_RENDERER, +#endif + &ME_VIDEOSETUP_FULLSCREEN, + &ME_VIDEOSETUP_BORDERLESS, + &ME_VIDEOSETUP_VSYNC, + &ME_VIDEOSETUP_FRAMELIMIT, + &ME_VIDEOSETUP_FRAMELIMITOFFSET, + &ME_Space4_Redfont, + &ME_VIDEOSETUP_APPLY, +}; +static MenuEntry_t *MEL_DISPLAYSETUP[] = { + &ME_DISPLAYSETUP_SCREENSETUP, + &ME_DISPLAYSETUP_COLORCORR, +#ifndef EDUKE32_ANDROID_MENU + &ME_DISPLAYSETUP_VIDEOSETUP, + &ME_DISPLAYSETUP_ASPECTRATIO, + &ME_DISPLAYSETUP_VOXELS, + &ME_DISPLAYSETUP_FOV, +#endif + &ME_DISPLAYSETUP_UPSCALING, +}; + +#ifdef USE_OPENGL +static MenuEntry_t *MEL_DISPLAYSETUP_GL[] = { + &ME_DISPLAYSETUP_SCREENSETUP, + &ME_DISPLAYSETUP_COLORCORR, +#ifndef EDUKE32_ANDROID_MENU + &ME_DISPLAYSETUP_VIDEOSETUP, + &ME_DISPLAYSETUP_ASPECTRATIO, + &ME_DISPLAYSETUP_VOXELS, + &ME_DISPLAYSETUP_FOV, +#endif +#ifndef EDUKE32_STANDALONE +# ifdef TEXFILTER_MENU_OPTIONS + &ME_DISPLAYSETUP_TEXFILTER, +# endif +#endif +#ifdef EDUKE32_ANDROID_MENU + &ME_DISPLAYSETUP_HIDEDPAD, + &ME_DISPLAYSETUP_TOUCHALPHA, +#else +# ifndef EDUKE32_STANDALONE +# ifdef TEXFILTER_MENU_OPTIONS + &ME_DISPLAYSETUP_ANISOTROPY, +# endif +# endif +# ifndef EDUKE32_SIMPLE_MENU + &ME_DISPLAYSETUP_ADVANCED_GL_POLYMOST, +# endif +#endif +}; + +#ifdef POLYMER +static MenuEntry_t *MEL_DISPLAYSETUP_GL_POLYMER[] = { + &ME_DISPLAYSETUP_SCREENSETUP, + &ME_DISPLAYSETUP_COLORCORR, +#ifndef EDUKE32_ANDROID_MENU + &ME_DISPLAYSETUP_VIDEOSETUP, + &ME_DISPLAYSETUP_FOV, + &ME_DISPLAYSETUP_VOXELS, +#endif +#ifdef TEXFILTER_MENU_OPTIONS + &ME_DISPLAYSETUP_TEXFILTER, + &ME_DISPLAYSETUP_ANISOTROPY, +#endif +#ifndef EDUKE32_SIMPLE_MENU + &ME_DISPLAYSETUP_ADVANCED_GL_POLYMER, +#endif +}; + +#endif +#endif + + + +static char const *MenuKeyNone = " -"; +static char const *MEOSN_Keys[NUMKEYS]; + +static MenuCustom2Col_t MEO_KEYBOARDSETUPFUNCS_TEMPLATE = { { NULL, NULL, }, MEOSN_Keys, &MF_Minifont, NUMKEYS, 54<<16, 0 }; +static MenuCustom2Col_t MEO_KEYBOARDSETUPFUNCS[NUMGAMEFUNCTIONS]; +static MenuEntry_t ME_KEYBOARDSETUPFUNCS_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Minifont, &MEF_KBFuncList, &MEO_KEYBOARDSETUPFUNCS_TEMPLATE, Custom2Col ); +static MenuEntry_t ME_KEYBOARDSETUPFUNCS[NUMGAMEFUNCTIONS]; +static MenuEntry_t *MEL_KEYBOARDSETUPFUNCS[NUMGAMEFUNCTIONS]; + +static MenuLink_t MEO_KEYBOARDSETUP_KEYS = { MENU_KEYBOARDKEYS, MA_Advance, }; +static MenuEntry_t ME_KEYBOARDSETUP_KEYS = MAKE_MENUENTRY( "Configure Keys", &MF_Redfont, &MEF_CenterMenu, &MEO_KEYBOARDSETUP_KEYS, Link ); +static MenuLink_t MEO_KEYBOARDSETUP_RESET = { MENU_KEYSRESETVERIFY, MA_None, }; +static MenuEntry_t ME_KEYBOARDSETUP_RESET = MAKE_MENUENTRY( "Reset To Defaults", &MF_Redfont, &MEF_CenterMenu, &MEO_KEYBOARDSETUP_RESET, Link ); +static MenuLink_t MEO_KEYBOARDSETUP_RESETCLASSIC = { MENU_KEYSCLASSICVERIFY, MA_None, }; +static MenuEntry_t ME_KEYBOARDSETUP_RESETCLASSIC = MAKE_MENUENTRY( "Reset To Classic", &MF_Redfont, &MEF_CenterMenu, &MEO_KEYBOARDSETUP_RESETCLASSIC, Link ); + +static MenuEntry_t *MEL_KEYBOARDSETUP[] = { + &ME_KEYBOARDSETUP_KEYS, + &ME_KEYBOARDSETUP_RESET, + &ME_KEYBOARDSETUP_RESETCLASSIC, +}; + + +// There is no better way to do this than manually. + +#define MENUMOUSEFUNCTIONS 12 + +static char const *MenuMouseNames[MENUMOUSEFUNCTIONS] = { + "Button 1", + "Double Button 1", + "Button 2", + "Double Button 2", + "Button 3", + "Double Button 3", + + "Wheel Up", + "Wheel Down", + + "Button 4", + "Double Button 4", + "Button 5", + "Double Button 5", +}; +static int32_t MenuMouseDataIndex[MENUMOUSEFUNCTIONS][2] = { + { 0, 0, }, + { 0, 1, }, + { 1, 0, }, + { 1, 1, }, + { 2, 0, }, + { 2, 1, }, + + // note the mouse wheel + { 4, 0, }, + { 5, 0, }, + + { 3, 0, }, + { 3, 1, }, + { 6, 0, }, + { 6, 1, }, +}; + +static MenuOption_t MEO_MOUSEJOYSETUPBTNS_TEMPLATE = MAKE_MENUOPTION( &MF_Minifont, &MEOS_Gamefuncs, NULL ); +static MenuOption_t MEO_MOUSESETUPBTNS[MENUMOUSEFUNCTIONS]; +static MenuEntry_t ME_MOUSEJOYSETUPBTNS_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Minifont, &MEF_FuncList, NULL, Option ); +static MenuEntry_t ME_MOUSESETUPBTNS[MENUMOUSEFUNCTIONS]; +static MenuEntry_t *MEL_MOUSESETUPBTNS[MENUMOUSEFUNCTIONS]; + +static MenuLink_t MEO_MOUSESETUP_BTNS = { MENU_MOUSEBTNS, MA_Advance, }; +static MenuEntry_t ME_MOUSESETUP_BTNS = MAKE_MENUENTRY( "Button assignment", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_BTNS, Link ); +static MenuRangeFloat_t MEO_MOUSESETUP_SENSITIVITY = MAKE_MENURANGE( &CONTROL_MouseSensitivity, &MF_Redfont, .5f, 16.f, 0.f, 32, 1 ); +static MenuEntry_t ME_MOUSESETUP_SENSITIVITY = MAKE_MENUENTRY( "Sensitivity:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_SENSITIVITY, RangeFloat ); + +#ifndef EDUKE32_SIMPLE_MENU +static char const *MEOSN_MOUSESETUP_AIM_TYPE [] = { "Toggle", "Hold" }; +static MenuOptionSet_t MEOS_MOUSESETUP_AIM_TYPE = MAKE_MENUOPTIONSET(MEOSN_MOUSESETUP_AIM_TYPE, NULL, 0x2); +static MenuOption_t MEO_MOUSESETUP_MOUSEAIMINGTYPE = MAKE_MENUOPTION(&MF_Redfont, &MEOS_MOUSESETUP_AIM_TYPE, &ud.mouseaiming); +static MenuEntry_t ME_MOUSESETUP_MOUSEAIMINGTYPE = MAKE_MENUENTRY("Aiming type:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_MOUSEAIMINGTYPE, Option); +static MenuOption_t MEO_MOUSESETUP_MOUSEAIMING = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &g_myAimMode ); +static MenuEntry_t ME_MOUSESETUP_MOUSEAIMING = MAKE_MENUENTRY( "Vertical aiming:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_MOUSEAIMING, Option ); +#endif +static MenuOption_t MEO_MOUSESETUP_INVERT = MAKE_MENUOPTION( &MF_Redfont, &MEOS_YesNo, &ud.mouseflip ); +static MenuEntry_t ME_MOUSESETUP_INVERT = MAKE_MENUENTRY( "Invert aiming:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_INVERT, Option ); +static MenuOption_t MEO_MOUSESETUP_SMOOTH = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &ud.config.SmoothInput ); +static MenuEntry_t ME_MOUSESETUP_SMOOTH = MAKE_MENUENTRY( "Filter input:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_SMOOTH, Option ); +#ifndef EDUKE32_SIMPLE_MENU +static MenuLink_t MEO_MOUSESETUP_ADVANCED = { MENU_MOUSEADVANCED, MA_Advance, }; +static MenuEntry_t ME_MOUSESETUP_ADVANCED = MAKE_MENUENTRY( "Advanced setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_ADVANCED, Link ); +#endif +static MenuRangeInt32_t MEO_MOUSEADVANCED_SCALEX = MAKE_MENURANGE(&ud.config.MouseAnalogueScale[0], &MF_Redfont, -262144, 262144, 65536, 161, 3); +static MenuEntry_t ME_MOUSEADVANCED_SCALEX = MAKE_MENUENTRY("X-Scale:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSEADVANCED_SCALEX, RangeInt32); +static MenuRangeInt32_t MEO_MOUSEADVANCED_SCALEY = MAKE_MENURANGE(&ud.config.MouseAnalogueScale[1], &MF_Redfont, -262144, 262144, 65536, 161, 3); +static MenuEntry_t ME_MOUSEADVANCED_SCALEY = MAKE_MENUENTRY("Y-Scale:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSEADVANCED_SCALEY, RangeInt32); + +static MenuEntry_t *MEL_MOUSESETUP[] = { + &ME_MOUSESETUP_BTNS, + &ME_MOUSESETUP_SENSITIVITY, +#ifdef EDUKE32_SIMPLE_MENU + &ME_MOUSEADVANCED_SCALEX, + &ME_MOUSEADVANCED_SCALEY, +#endif + &ME_Space2_Redfont, + &ME_MOUSESETUP_INVERT, + &ME_MOUSESETUP_SMOOTH, +#ifndef EDUKE32_SIMPLE_MENU + &ME_MOUSESETUP_MOUSEAIMINGTYPE, + &ME_MOUSESETUP_MOUSEAIMING, + &ME_MOUSESETUP_ADVANCED, +#endif +}; + +#ifdef EDUKE32_ANDROID_MENU +static MenuRangeFloat_t MEO_TOUCHSETUP_SENSITIVITY_MOVE = MAKE_MENURANGE(&droidinput.forward_sens, &MF_Redfont, 1.f, 9.f, 0.f, 17, 1 + EnforceIntervals); +static MenuEntry_t ME_TOUCHSETUP_SENSITIVITY_MOVE = MAKE_MENUENTRY("Running:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_SENSITIVITY_MOVE, RangeFloat); + +static MenuRangeFloat_t MEO_TOUCHSETUP_SENSITIVITY_STRAFE = MAKE_MENURANGE(&droidinput.strafe_sens, &MF_Redfont, 1.f, 9.f, 0.f, 17, 1 + EnforceIntervals); +static MenuEntry_t ME_TOUCHSETUP_SENSITIVITY_STRAFE = MAKE_MENUENTRY("Strafing:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_SENSITIVITY_STRAFE, RangeFloat); + +static MenuRangeFloat_t MEO_TOUCHSETUP_SENSITIVITY_LOOK = MAKE_MENURANGE(&droidinput.pitch_sens, &MF_Redfont, 1.f, 9.f, 0.f, 17, 1 + EnforceIntervals); +static MenuEntry_t ME_TOUCHSETUP_SENSITIVITY_LOOK = MAKE_MENUENTRY("Looking:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_SENSITIVITY_LOOK, RangeFloat); + +static MenuRangeFloat_t MEO_TOUCHSETUP_SENSITIVITY_TURN = MAKE_MENURANGE(&droidinput.yaw_sens, &MF_Redfont, 1.f, 9.f, 0.f, 17, 1 + EnforceIntervals); +static MenuEntry_t ME_TOUCHSETUP_SENSITIVITY_TURN = MAKE_MENUENTRY("Turning:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_SENSITIVITY_TURN, RangeFloat); + +static MenuOption_t MEO_TOUCHSETUP_INVERT = MAKE_MENUOPTION(&MF_Redfont, &MEOS_NoYes, &droidinput.invertLook); +static MenuEntry_t ME_TOUCHSETUP_INVERT = MAKE_MENUENTRY("Invert look:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_INVERT, Option); + +MAKE_MENU_TOP_ENTRYLINK("Sensitivity", MEF_CenterMenu, TOUCHSENS, MENU_TOUCHSENS); +MAKE_MENU_TOP_ENTRYLINK("Button Setup", MEF_CenterMenu, TOUCHBUTTONS, MENU_TOUCHBUTTONS); + +static MenuEntry_t *MEL_TOUCHSETUP [] = { + &ME_TOUCHSENS, + &ME_TOUCHBUTTONS, +}; + +static MenuEntry_t *MEL_TOUCHSENS [] = { + &ME_TOUCHSETUP_SENSITIVITY_MOVE, + &ME_TOUCHSETUP_SENSITIVITY_STRAFE, + &ME_TOUCHSETUP_SENSITIVITY_LOOK, + &ME_TOUCHSETUP_SENSITIVITY_TURN, + &ME_Space2_Redfont, + &ME_TOUCHSETUP_INVERT, +}; +#endif + +static MenuOption_t MEO_JOYSTICK_ENABLE = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.setup.usejoystick ); +static MenuEntry_t ME_JOYSTICK_ENABLE = MAKE_MENUENTRY( "Enable Gamepad:", &MF_Redfont, &MEF_BigOptionsRtSections, &MEO_JOYSTICK_ENABLE, Option ); + +MAKE_MENU_TOP_ENTRYLINK( "Edit Buttons", MEF_BigOptionsRtSections, JOYSTICK_EDITBUTTONS, MENU_JOYSTICKBTNS ); +MAKE_MENU_TOP_ENTRYLINK( "Edit Axes", MEF_BigOptionsRtSections, JOYSTICK_EDITAXES, MENU_JOYSTICKAXES ); + +static MenuLink_t MEO_JOYSTICK_DEFAULTS_STANDARD = { MENU_JOYSTANDARDVERIFY, MA_None, }; +static MenuEntry_t ME_JOYSTICK_DEFAULTS_STANDARD = MAKE_MENUENTRY( "Use Standard Layout", &MF_Redfont, &MEF_BigOptionsRtSections, &MEO_JOYSTICK_DEFAULTS_STANDARD, Link ); +static MenuLink_t MEO_JOYSTICK_DEFAULTS_PRO = { MENU_JOYPROVERIFY, MA_None, }; +static MenuEntry_t ME_JOYSTICK_DEFAULTS_PRO = MAKE_MENUENTRY( "Use Pro Layout", &MF_Redfont, &MEF_BigOptionsRtSections, &MEO_JOYSTICK_DEFAULTS_PRO, Link ); +static MenuLink_t MEO_JOYSTICK_DEFAULTS_CLEAR = { MENU_JOYCLEARVERIFY, MA_None, }; +static MenuEntry_t ME_JOYSTICK_DEFAULTS_CLEAR = MAKE_MENUENTRY( "Clear All Settings", &MF_Redfont, &MEF_BigOptionsRtSections, &MEO_JOYSTICK_DEFAULTS_CLEAR, Link ); + +static MenuEntry_t *MEL_JOYSTICKSETUP[] = { + &ME_JOYSTICK_ENABLE, + &ME_Space6_Redfont, + &ME_JOYSTICK_EDITBUTTONS, + &ME_JOYSTICK_EDITAXES, + &ME_Space6_Redfont, + &ME_JOYSTICK_DEFAULTS_STANDARD, + &ME_JOYSTICK_DEFAULTS_PRO, + &ME_JOYSTICK_DEFAULTS_CLEAR, +}; + +#define MAXJOYBUTTONSTRINGLENGTH 32 + +static char MenuJoystickNames[MAXJOYBUTTONSANDHATS*2][MAXJOYBUTTONSTRINGLENGTH]; + +static MenuOption_t MEO_JOYSTICKBTNS[MAXJOYBUTTONSANDHATS*2]; +static MenuEntry_t ME_JOYSTICKBTNS[MAXJOYBUTTONSANDHATS*2]; +static MenuEntry_t *MEL_JOYSTICKBTNS[MAXJOYBUTTONSANDHATS*2]; + +static MenuLink_t MEO_JOYSTICKAXES = { MENU_JOYSTICKAXIS, MA_Advance, }; +static MenuEntry_t ME_JOYSTICKAXES_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXES, Link ); +static MenuEntry_t ME_JOYSTICKAXES[MAXJOYAXES]; +static char MenuJoystickAxes[MAXJOYAXES][MAXJOYBUTTONSTRINGLENGTH]; + +static MenuEntry_t *MEL_JOYSTICKAXES[MAXJOYAXES]; + +static MenuEntry_t *MEL_MOUSEADVANCED[] = { + &ME_MOUSEADVANCED_SCALEX, + &ME_MOUSEADVANCED_SCALEY, +}; + +static const char *MenuJoystickHatDirections[] = { "Up", "Right", "Down", "Left", }; + +static char const *MEOSN_JOYSTICKAXIS_ANALOG[] = { " -None-", "Turning", "Strafing", "Looking", "Moving", }; +static int32_t MEOSV_JOYSTICKAXIS_ANALOG[] = { -1, analog_turning, analog_strafing, analog_lookingupanddown, analog_moving, }; +static MenuOptionSet_t MEOS_JOYSTICKAXIS_ANALOG = MAKE_MENUOPTIONSET( MEOSN_JOYSTICKAXIS_ANALOG, MEOSV_JOYSTICKAXIS_ANALOG, 0x0 ); +static MenuOption_t MEO_JOYSTICKAXIS_ANALOG = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_JOYSTICKAXIS_ANALOG, NULL ); +static MenuEntry_t ME_JOYSTICKAXIS_ANALOG = MAKE_MENUENTRY( "Analog", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_ANALOG, Option ); +static MenuRangeInt32_t MEO_JOYSTICKAXIS_SCALE = MAKE_MENURANGE( NULL, &MF_Bluefont, -262144, 262144, 65536, 161, 3 ); +static MenuEntry_t ME_JOYSTICKAXIS_SCALE = MAKE_MENUENTRY( "Scale", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_SCALE, RangeInt32 ); +static MenuOption_t MEO_JOYSTICKAXIS_INVERT = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, NULL ); +static MenuEntry_t ME_JOYSTICKAXIS_INVERT = MAKE_MENUENTRY( "Invert", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_INVERT, Option ); +static MenuRangeInt32_t MEO_JOYSTICKAXIS_DEAD = MAKE_MENURANGE( NULL, &MF_Bluefont, 0, 10000, 0, 101, 2 ); +static MenuEntry_t ME_JOYSTICKAXIS_DEAD = MAKE_MENUENTRY( "Dead Zone", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_DEAD, RangeInt32 ); +static MenuRangeInt32_t MEO_JOYSTICKAXIS_SATU = MAKE_MENURANGE( NULL, &MF_Bluefont, 0, 10000, 0, 101, 2 ); +static MenuEntry_t ME_JOYSTICKAXIS_SATU = MAKE_MENUENTRY( "Saturation", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_SATU, RangeInt32 ); + +static MenuOption_t MEO_JOYSTICKAXIS_DIGITALNEGATIVE = MAKE_MENUOPTION( &MF_Minifont, &MEOS_Gamefuncs, NULL ); +static MenuEntry_t ME_JOYSTICKAXIS_DIGITALNEGATIVE = MAKE_MENUENTRY( "Digital -", &MF_Bluefont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_DIGITALNEGATIVE, Option ); +static MenuOption_t MEO_JOYSTICKAXIS_DIGITALPOSITIVE = MAKE_MENUOPTION( &MF_Minifont, &MEOS_Gamefuncs, NULL ); +static MenuEntry_t ME_JOYSTICKAXIS_DIGITALPOSITIVE = MAKE_MENUENTRY( "Digital +", &MF_Bluefont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_DIGITALPOSITIVE, Option ); + +static MenuEntry_t *MEL_JOYSTICKAXIS[] = { + &ME_JOYSTICKAXIS_ANALOG, + &ME_JOYSTICKAXIS_SCALE, + &ME_JOYSTICKAXIS_INVERT, + &ME_JOYSTICKAXIS_DEAD, + &ME_JOYSTICKAXIS_SATU, + &ME_Space8_Redfont, + &ME_JOYSTICKAXIS_DIGITALNEGATIVE, + &ME_JOYSTICKAXIS_DIGITALPOSITIVE, +}; + +static MenuEntry_t *MEL_INTERNAL_JOYSTICKAXIS_DIGITAL[] = { + &ME_JOYSTICKAXIS_DIGITALNEGATIVE, + &ME_JOYSTICKAXIS_DIGITALPOSITIVE, +}; + +#ifdef USE_OPENGL +static MenuOption_t MEO_RENDERERSETUP_HIGHTILE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NoYes, &usehightile ); +static MenuEntry_t ME_RENDERERSETUP_HIGHTILE = MAKE_MENUENTRY( "True color textures:", &MF_Bluefont, &MEF_SmallOptions, &MEO_RENDERERSETUP_HIGHTILE, Option ); + +static char const *MEOSN_RENDERERSETUP_TEXQUALITY [] = { "Full", "Half", "Barf", }; +static MenuOptionSet_t MEOS_RENDERERSETUP_TEXQUALITY = MAKE_MENUOPTIONSET(MEOSN_RENDERERSETUP_TEXQUALITY, NULL, 0x2); +static MenuOption_t MEO_RENDERERSETUP_TEXQUALITY = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_RENDERERSETUP_TEXQUALITY, &r_downsize); +static MenuEntry_t ME_RENDERERSETUP_TEXQUALITY = MAKE_MENUENTRY("GL texture quality:", &MF_Bluefont, &MEF_SmallOptions, &MEO_RENDERERSETUP_TEXQUALITY, Option); + + +static MenuOption_t MEO_RENDERERSETUP_PRECACHE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OffOn, &ud.config.useprecache ); +static MenuEntry_t ME_RENDERERSETUP_PRECACHE = MAKE_MENUENTRY( "Pre-load map textures:", &MF_Bluefont, &MEF_SmallOptions, &MEO_RENDERERSETUP_PRECACHE, Option ); +# ifndef EDUKE32_GLES +static char const *MEOSN_RENDERERSETUP_TEXCACHE[] = { "Off", "On", "Compr.", }; +static MenuOptionSet_t MEOS_RENDERERSETUP_TEXCACHE = MAKE_MENUOPTIONSET( MEOSN_RENDERERSETUP_TEXCACHE, NULL, 0x2 ); +static MenuOption_t MEO_RENDERERSETUP_TEXCACHE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_RENDERERSETUP_TEXCACHE, &glusetexcache ); +static MenuEntry_t ME_RENDERERSETUP_TEXCACHE = MAKE_MENUENTRY( "On-disk texture cache:", &MF_Bluefont, &MEF_SmallOptions, &MEO_RENDERERSETUP_TEXCACHE, Option ); +# endif +# ifdef USE_GLEXT +static MenuOption_t MEO_RENDERERSETUP_DETAILTEX = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NoYes, &r_detailmapping ); +static MenuEntry_t ME_RENDERERSETUP_DETAILTEX = MAKE_MENUENTRY( "Detail textures:", &MF_Bluefont, &MEF_SmallOptions, &MEO_RENDERERSETUP_DETAILTEX, Option ); +static MenuOption_t MEO_RENDERERSETUP_GLOWTEX = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_NoYes, &r_glowmapping); +static MenuEntry_t ME_RENDERERSETUP_GLOWTEX = MAKE_MENUENTRY("Glow textures:", &MF_Bluefont, &MEF_SmallOptions, &MEO_RENDERERSETUP_GLOWTEX, Option); +# endif +static MenuOption_t MEO_RENDERERSETUP_MODELS = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NoYes, &usemodels ); +static MenuEntry_t ME_RENDERERSETUP_MODELS = MAKE_MENUENTRY( "3D models:", &MF_Bluefont, &MEF_SmallOptions, &MEO_RENDERERSETUP_MODELS, Option ); +#endif + +#ifdef POLYMER +static char const *MEOSN_POLYMER_LIGHTS [] = { "Off", "Full", "Map only", }; +static MenuOptionSet_t MEOS_POLYMER_LIGHTS = MAKE_MENUOPTIONSET(MEOSN_POLYMER_LIGHTS, NULL, 0x2); +static MenuOption_t MEO_POLYMER_LIGHTS = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_POLYMER_LIGHTS, &pr_lighting); +static MenuEntry_t ME_POLYMER_LIGHTS = MAKE_MENUENTRY("Dynamic lights:", &MF_Bluefont, &MEF_SmallOptions, &MEO_POLYMER_LIGHTS, Option); + +static MenuRangeInt32_t MEO_POLYMER_LIGHTPASSES = MAKE_MENURANGE(&r_pr_maxlightpasses, &MF_Bluefont, 1, 10, 1, 10, 1); +static MenuEntry_t ME_POLYMER_LIGHTPASSES = MAKE_MENUENTRY("Lights per surface:", &MF_Bluefont, &MEF_SmallOptions, &MEO_POLYMER_LIGHTPASSES, RangeInt32); + +static MenuOption_t MEO_POLYMER_SHADOWS = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_OffOn, &pr_shadows); +static MenuEntry_t ME_POLYMER_SHADOWS = MAKE_MENUENTRY("Dynamic shadows:", &MF_Bluefont, &MEF_SmallOptions, &MEO_POLYMER_SHADOWS, Option); + +static MenuRangeInt32_t MEO_POLYMER_SHADOWCOUNT = MAKE_MENURANGE(&pr_shadowcount, &MF_Bluefont, 1, 10, 1, 10, 1); +static MenuEntry_t ME_POLYMER_SHADOWCOUNT = MAKE_MENUENTRY("Shadows per surface:", &MF_Bluefont, &MEF_SmallOptions, &MEO_POLYMER_SHADOWCOUNT, RangeInt32); + +#endif + +#ifdef USE_OPENGL +static MenuEntry_t *MEL_RENDERERSETUP_POLYMOST[] = { + &ME_RENDERERSETUP_HIGHTILE, + &ME_RENDERERSETUP_TEXQUALITY, + &ME_RENDERERSETUP_PRECACHE, +# ifndef EDUKE32_GLES + &ME_RENDERERSETUP_TEXCACHE, +# endif +# ifdef USE_GLEXT + &ME_RENDERERSETUP_DETAILTEX, + &ME_RENDERERSETUP_GLOWTEX, +# endif + &ME_Space4_Bluefont, + &ME_RENDERERSETUP_MODELS, +}; + +#ifdef POLYMER +static MenuEntry_t *MEL_RENDERERSETUP_POLYMER [] = { + &ME_RENDERERSETUP_HIGHTILE, + &ME_RENDERERSETUP_TEXQUALITY, + &ME_RENDERERSETUP_PRECACHE, +# ifndef EDUKE32_GLES + &ME_RENDERERSETUP_TEXCACHE, +# endif +# ifdef USE_GLEXT + &ME_RENDERERSETUP_DETAILTEX, + &ME_RENDERERSETUP_GLOWTEX, +# endif + &ME_Space4_Bluefont, + &ME_RENDERERSETUP_MODELS, + &ME_Space4_Bluefont, + &ME_POLYMER_LIGHTS, + &ME_POLYMER_LIGHTPASSES, + &ME_POLYMER_SHADOWS, + &ME_POLYMER_SHADOWCOUNT, +}; +#endif +#endif + +#ifdef EDUKE32_ANDROID_MENU +static MenuRangeFloat_t MEO_COLCORR_GAMMA = MAKE_MENURANGE( &g_videoGamma, &MF_Bluefont, 1.f, 2.5f, 0.f, 39, 1 ); +#else +static MenuRangeFloat_t MEO_COLCORR_GAMMA = MAKE_MENURANGE( &g_videoGamma, &MF_Bluefont, 0.3f, 4.f, 0.f, 75, 1 ); +#endif +static MenuEntry_t ME_COLCORR_GAMMA = MAKE_MENUENTRY( "Gamma:", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_GAMMA, RangeFloat ); +static MenuRangeFloat_t MEO_COLCORR_CONTRAST = MAKE_MENURANGE( &g_videoContrast, &MF_Bluefont, 0.1f, 2.7f, 0.f, 53, 1 ); +static MenuEntry_t ME_COLCORR_CONTRAST = MAKE_MENUENTRY( "Contrast:", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_CONTRAST, RangeFloat ); +static MenuRangeFloat_t MEO_COLCORR_BRIGHTNESS = MAKE_MENURANGE( &g_videoBrightness, &MF_Bluefont, -0.8f, 0.8f, 0.f, 33, 1 ); +static MenuEntry_t ME_COLCORR_BRIGHTNESS = MAKE_MENUENTRY( "Brightness:", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_BRIGHTNESS, RangeFloat ); +static MenuLink_t MEO_COLCORR_RESET = { MENU_COLCORRRESETVERIFY, MA_None, }; +static MenuEntry_t ME_COLCORR_RESET = MAKE_MENUENTRY( "Reset To Defaults", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_RESET, Link ); +#ifdef EDUKE32_ANDROID_MENU +#define MINVIS 1.f +#else +#define MINVIS 0.125f +#endif +#ifndef EDUKE32_SIMPLE_MENU +static MenuRangeFloat_t MEO_COLCORR_AMBIENT = MAKE_MENURANGE( &r_ambientlight, &MF_Bluefont, MINVIS, 4.f, 0.f, 32, 1 ); +static MenuEntry_t ME_COLCORR_AMBIENT = MAKE_MENUENTRY( "Visibility:", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_AMBIENT, RangeFloat ); +#endif +static MenuEntry_t *MEL_COLCORR[] = { + &ME_COLCORR_GAMMA, +#ifndef EDUKE32_ANDROID_MENU + &ME_COLCORR_CONTRAST, + &ME_COLCORR_BRIGHTNESS, +#endif +#ifndef EDUKE32_SIMPLE_MENU + &ME_COLCORR_AMBIENT, +#endif + &ME_Space8_Redfont, + &ME_COLCORR_RESET, +}; + +static MenuEntry_t *MEL_SCREENSETUP[] = { +#ifdef EDUKE32_ANDROID_MENU + &ME_SCREENSETUP_STATUSBARONTOP, +#endif + &ME_SCREENSETUP_SCREENSIZE, + &ME_SCREENSETUP_SBARSIZE, + + &ME_SCREENSETUP_CROSSHAIR, + &ME_SCREENSETUP_CROSSHAIRSIZE, + + &ME_SCREENSETUP_LEVELSTATS, + &ME_SCREENSETUP_TEXTSIZE, + + &ME_SCREENSETUP_SHOWPICKUPMESSAGES, +}; + +// Save and load will be filled in before every viewing of the save/load screen. +static MenuLink_t MEO_LOAD = { MENU_LOADVERIFY, MA_None, }; +static MenuEntry_t ME_LOAD_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Minifont, &MEF_LoadSave, &MEO_LOAD, Link ); +static MenuEntry_t ME_LOAD_EMPTY = MAKE_MENUENTRY( NULL, &MF_Minifont, &MEF_LoadSave, nullptr, Dummy ); +static MenuEntry_t *ME_LOAD; +static MenuEntry_t **MEL_LOAD; + +static char const s_NewSaveGame[] = "(New Save Game)"; +static MenuString_t MEO_SAVE_TEMPLATE = MAKE_MENUSTRING( NULL, &MF_Minifont, MAXSAVEGAMENAME, 0 ); +static MenuString_t MEO_SAVE_NEW = MAKE_MENUSTRING( NULL, &MF_Minifont, MAXSAVEGAMENAME, 0 ); +static MenuString_t *MEO_SAVE; +static MenuEntry_t ME_SAVE_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Minifont, &MEF_LoadSave, &MEO_SAVE_TEMPLATE, String ); +static MenuEntry_t ME_SAVE_NEW = MAKE_MENUENTRY( s_NewSaveGame, &MF_Minifont, &MEF_LoadSave, &MEO_SAVE_NEW, String ); +static MenuEntry_t *ME_SAVE; +static MenuEntry_t **MEL_SAVE; + +static int32_t soundrate, soundvoices; +static MenuOption_t MEO_SOUND = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.config.SoundToggle ); +static MenuEntry_t ME_SOUND = MAKE_MENUENTRY( "Sound:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND, Option ); + +static MenuOption_t MEO_SOUND_MUSIC = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.config.MusicToggle ); +static MenuEntry_t ME_SOUND_MUSIC = MAKE_MENUENTRY( "Music:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_MUSIC, Option ); + +static char const s_Volume[] = "Volume:"; + +static MenuRangeInt32_t MEO_SOUND_VOLUME_FX = MAKE_MENURANGE( &ud.config.FXVolume, &MF_Redfont, 0, 255, 0, 33, 2 ); +static MenuEntry_t ME_SOUND_VOLUME_FX = MAKE_MENUENTRY( s_Volume, &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SOUND_VOLUME_FX, RangeInt32 ); + +static MenuRangeInt32_t MEO_SOUND_VOLUME_MUSIC = MAKE_MENURANGE( &ud.config.MusicVolume, &MF_Redfont, 0, 255, 0, 33, 2 ); +static MenuEntry_t ME_SOUND_VOLUME_MUSIC = MAKE_MENUENTRY( s_Volume, &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SOUND_VOLUME_MUSIC, RangeInt32 ); + +#ifndef EDUKE32_STANDALONE +static MenuOption_t MEO_SOUND_DUKETALK = MAKE_MENUOPTION(&MF_Redfont, &MEOS_NoYes, NULL); +static MenuEntry_t ME_SOUND_DUKETALK = MAKE_MENUENTRY( "Duke talk:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_DUKETALK, Option ); +#else +static MenuOption_t MEO_SOUND_DUKETALK = MAKE_MENUOPTION(&MF_Redfont, &MEOS_YesNo, NULL); +static MenuEntry_t ME_SOUND_DUKETALK = MAKE_MENUENTRY("Silent protagonist:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_DUKETALK, Option); +#endif + +static char const *MEOSN_SOUND_SAMPLINGRATE[] = { "22050Hz", "44100Hz", "48000Hz", }; +static int32_t MEOSV_SOUND_SAMPLINGRATE[] = { 22050, 44100, 48000, }; +static MenuOptionSet_t MEOS_SOUND_SAMPLINGRATE = MAKE_MENUOPTIONSET( MEOSN_SOUND_SAMPLINGRATE, MEOSV_SOUND_SAMPLINGRATE, 0x3 ); +static MenuOption_t MEO_SOUND_SAMPLINGRATE = MAKE_MENUOPTION( &MF_Redfont, &MEOS_SOUND_SAMPLINGRATE, &soundrate ); +static MenuEntry_t ME_SOUND_SAMPLINGRATE = MAKE_MENUENTRY( "Sample rate:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_SAMPLINGRATE, Option ); + +#ifndef EDUKE32_SIMPLE_MENU +static MenuRangeInt32_t MEO_SOUND_NUMVOICES = MAKE_MENURANGE( &soundvoices, &MF_Redfont, 16, 256, 0, 16, 1 ); +static MenuEntry_t ME_SOUND_NUMVOICES = MAKE_MENUENTRY( "Voices:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_NUMVOICES, RangeInt32 ); +#endif + +static MenuEntry_t ME_SOUND_RESTART = MAKE_MENUENTRY( "Apply Changes", &MF_Redfont, &MEF_BigOptions_Apply, &MEO_NULL, Link ); + +#ifndef EDUKE32_SIMPLE_MENU +static MenuLink_t MEO_ADVSOUND = { MENU_ADVSOUND, MA_Advance, }; +static MenuEntry_t ME_SOUND_ADVSOUND = MAKE_MENUENTRY( "Advanced", &MF_Redfont, &MEF_BigOptionsRt, &MEO_ADVSOUND, Link ); +#endif + +static MenuEntry_t *MEL_SOUND[] = { + &ME_SOUND, + &ME_SOUND_VOLUME_FX, + &ME_SOUND_MUSIC, + &ME_SOUND_VOLUME_MUSIC, + &ME_SOUND_DUKETALK, +#ifndef EDUKE32_SIMPLE_MENU + &ME_SOUND_ADVSOUND, +#endif +}; + +static MenuEntry_t *MEL_ADVSOUND[] = { + &ME_SOUND_SAMPLINGRATE, + &ME_Space2_Redfont, +#ifndef EDUKE32_SIMPLE_MENU + &ME_SOUND_NUMVOICES, + &ME_Space2_Redfont, +#endif + &ME_SOUND_RESTART, +}; + + +static MenuOption_t MEO_SAVESETUP_AUTOSAVE = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.autosave ); +static MenuEntry_t ME_SAVESETUP_AUTOSAVE = MAKE_MENUENTRY( "Checkpoints:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SAVESETUP_AUTOSAVE, Option ); + +static MenuOption_t MEO_SAVESETUP_AUTOSAVEDELETION = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &ud.autosavedeletion ); +static MenuEntry_t ME_SAVESETUP_AUTOSAVEDELETION = MAKE_MENUENTRY( "Auto-Delete:", &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SAVESETUP_AUTOSAVEDELETION, Option ); +static MenuRangeInt32_t MEO_SAVESETUP_MAXAUTOSAVES = MAKE_MENURANGE( &ud.maxautosaves, &MF_Redfont, 1, 10, 0, 10, 1 ); +static MenuEntry_t ME_SAVESETUP_MAXAUTOSAVES = MAKE_MENUENTRY( "Limit:", &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SAVESETUP_MAXAUTOSAVES, RangeInt32 ); + +static MenuEntry_t ME_SAVESETUP_CLEANUP = MAKE_MENUENTRY( "Clean Up Saves", &MF_Redfont, &MEF_BigOptionsRt, &MEO_NULL, Link ); + +#ifdef EDUKE32_STANDALONE +static MenuEntry_t ME_SAVESETUP_RESETSTATS = MAKE_MENUENTRY( "Reset Stats/Achievements", &MF_Redfont, &MEF_BigOptionsRt, &MEO_NULL, Link ); +#endif + +static MenuEntry_t *MEL_SAVESETUP[] = { + &ME_SAVESETUP_AUTOSAVE, + &ME_SAVESETUP_AUTOSAVEDELETION, + &ME_SAVESETUP_MAXAUTOSAVES, + &ME_SAVESETUP_CLEANUP, +#ifdef EDUKE32_STANDALONE + &ME_SAVESETUP_RESETSTATS, +#endif +}; + + +MAKE_MENU_TOP_ENTRYLINK( "Player Setup", MEF_CenterMenu, NETWORK_PLAYERSETUP, MENU_PLAYER ); +MAKE_MENU_TOP_ENTRYLINK( "Join Game", MEF_CenterMenu, NETWORK_JOINGAME, MENU_NETJOIN ); +MAKE_MENU_TOP_ENTRYLINK( "Host Game", MEF_CenterMenu, NETWORK_HOSTGAME, MENU_NETHOST ); + +static MenuEntry_t *MEL_NETWORK[] = { + &ME_NETWORK_PLAYERSETUP, + &ME_NETWORK_JOINGAME, + &ME_NETWORK_HOSTGAME, +}; + +static MenuString_t MEO_PLAYER_NAME = MAKE_MENUSTRING( szPlayerName, &MF_Bluefont, MAXPLAYERNAME, 0 ); +static MenuEntry_t ME_PLAYER_NAME = MAKE_MENUENTRY( "Name", &MF_Bluefont, &MEF_PlayerNarrow, &MEO_PLAYER_NAME, String ); +static char const *MEOSN_PLAYER_COLOR[] = { "Auto", "Blue", "Red", "Green", "Gray", "Dark gray", "Dark green", "Brown", "Dark blue", "Bright red", "Yellow", }; +static int32_t MEOSV_PLAYER_COLOR[] = { 0, 9, 10, 11, 12, 13, 14, 15, 16, 21, 23, }; +static MenuOptionSet_t MEOS_PLAYER_COLOR = MAKE_MENUOPTIONSET( MEOSN_PLAYER_COLOR, MEOSV_PLAYER_COLOR, 0x2 ); +static MenuOption_t MEO_PLAYER_COLOR = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_PLAYER_COLOR, &ud.color ); +static MenuEntry_t ME_PLAYER_COLOR = MAKE_MENUENTRY( "Color", &MF_Bluefont, &MEF_PlayerNarrow, &MEO_PLAYER_COLOR, Option ); +static char const *MEOSN_PLAYER_TEAM[] = { "Blue", "Red", "Green", "Gray", }; +static MenuOptionSet_t MEOS_PLAYER_TEAM = MAKE_MENUOPTIONSET( MEOSN_PLAYER_TEAM, NULL, 0x2 ); +static MenuOption_t MEO_PLAYER_TEAM = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_PLAYER_TEAM, &ud.team ); +static MenuEntry_t ME_PLAYER_TEAM = MAKE_MENUENTRY( "Team", &MF_Bluefont, &MEF_PlayerNarrow, &MEO_PLAYER_TEAM, Option ); +#ifndef EDUKE32_SIMPLE_MENU +static MenuLink_t MEO_PLAYER_MACROS = { MENU_MACROS, MA_Advance, }; +static MenuEntry_t ME_PLAYER_MACROS = MAKE_MENUENTRY( "Multiplayer macros", &MF_Bluefont, &MEF_SmallOptions, &MEO_PLAYER_MACROS, Link ); +#endif + +static MenuEntry_t *MEL_PLAYER[] = { + &ME_PLAYER_NAME, + &ME_Space4_Bluefont, + &ME_PLAYER_COLOR, + &ME_Space4_Bluefont, + &ME_PLAYER_TEAM, +#ifndef EDUKE32_SIMPLE_MENU + &ME_Space8_Bluefont, + &ME_PLAYER_MACROS, +#endif +}; + +static MenuString_t MEO_MACROS_TEMPLATE = MAKE_MENUSTRING( NULL, &MF_Bluefont, MAXRIDECULELENGTH, 0 ); +static MenuString_t MEO_MACROS[10]; +static MenuEntry_t ME_MACROS_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Bluefont, &MEF_Macros, &MEO_MACROS_TEMPLATE, String ); +static MenuEntry_t ME_MACROS[MAXRIDECULE]; +static MenuEntry_t *MEL_MACROS[MAXRIDECULE]; + +#ifndef EDUKE32_SIMPLE_MENU +static char const *MenuUserMap = "User Map"; +#endif +static char const *MenuSkillNone = "None"; + +static char const *MEOSN_NetGametypes[MAXGAMETYPES]; +static char const *MEOSN_NetEpisodes[MAXVOLUMES+1]; +static int32_t MEOSV_NetEpisodes[MAXVOLUMES+1]; +static char const *MEOSN_NetLevels[MAXVOLUMES][MAXLEVELS]; +static char const *MEOSN_NetSkills[MAXSKILLS+1]; + +static MenuLink_t MEO_NETHOST_OPTIONS = { MENU_NETOPTIONS, MA_Advance, }; +static MenuEntry_t ME_NETHOST_OPTIONS = MAKE_MENUENTRY( "Game Options", &MF_Redfont, &MEF_VideoSetup, &MEO_NETHOST_OPTIONS, Link ); +static MenuEntry_t ME_NETHOST_LAUNCH = MAKE_MENUENTRY( "Launch Game", &MF_Redfont, &MEF_VideoSetup, &MEO_NULL, Link ); + +static MenuEntry_t *MEL_NETHOST[] = { + &ME_NETHOST_OPTIONS, + &ME_NETHOST_LAUNCH, +}; + +static MenuOptionSet_t MEOS_NETOPTIONS_GAMETYPE = MAKE_MENUOPTIONSET( MEOSN_NetGametypes, NULL, 0x0 ); +static MenuOption_t MEO_NETOPTIONS_GAMETYPE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NETOPTIONS_GAMETYPE, &ud.m_coop ); +static MenuEntry_t ME_NETOPTIONS_GAMETYPE = MAKE_MENUENTRY( "Game Type", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_GAMETYPE, Option ); +static MenuOptionSet_t MEOS_NETOPTIONS_EPISODE = MAKE_MENUOPTIONSET( MEOSN_NetEpisodes, MEOSV_NetEpisodes, 0x0 ); +static int32_t NetEpisode; +static MenuOption_t MEO_NETOPTIONS_EPISODE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NETOPTIONS_EPISODE, &NetEpisode ); +static MenuEntry_t ME_NETOPTIONS_EPISODE = MAKE_MENUENTRY( "Episode", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_EPISODE, Option ); +static MenuOptionSet_t MEOS_NETOPTIONS_LEVEL_TEMPLATE = MAKE_MENUOPTIONSETNULL; +static MenuOptionSet_t MEOS_NETOPTIONS_LEVEL[MAXVOLUMES]; +static MenuOption_t MEO_NETOPTIONS_LEVEL = MAKE_MENUOPTION( &MF_Bluefont, NULL, &ud.m_level_number ); +static MenuEntry_t ME_NETOPTIONS_LEVEL = MAKE_MENUENTRY( "Level", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_LEVEL, Option ); +static MenuLink_t MEO_NETOPTIONS_USERMAP = { MENU_NETUSERMAP, MA_Advance, }; +static MenuEntry_t ME_NETOPTIONS_USERMAP = MAKE_MENUENTRY( "User Map", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_USERMAP, Link ); +static MenuOptionSet_t MEOS_NETOPTIONS_MONSTERS = MAKE_MENUOPTIONSET( MEOSN_NetSkills, NULL, 0x0 ); +static MenuOption_t MEO_NETOPTIONS_MONSTERS = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NETOPTIONS_MONSTERS, NULL ); +static MenuEntry_t ME_NETOPTIONS_MONSTERS = MAKE_MENUENTRY( "Monsters", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_MONSTERS, Option ); +static MenuOption_t MEO_NETOPTIONS_MARKERS = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OffOn, &ud.m_marker ); +static MenuEntry_t ME_NETOPTIONS_MARKERS = MAKE_MENUENTRY( "Markers", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_MARKERS, Option ); +static MenuOption_t MEO_NETOPTIONS_MAPEXITS = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OnOff, &ud.m_noexits ); +static MenuEntry_t ME_NETOPTIONS_MAPEXITS = MAKE_MENUENTRY( "Map Exits", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_MAPEXITS, Option ); +static MenuOption_t MEO_NETOPTIONS_FRFIRE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OffOn, &ud.m_ffire ); +static MenuEntry_t ME_NETOPTIONS_FRFIRE = MAKE_MENUENTRY( "Fr. Fire", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_FRFIRE, Option ); +static MenuEntry_t ME_NETOPTIONS_ACCEPT = MAKE_MENUENTRY( "Accept", &MF_Redfont, &MEF_NetSetup_Confirm, &MEO_NETWORK_HOSTGAME, Link ); + +static MenuEntry_t *MEL_NETOPTIONS[] = { + &ME_NETOPTIONS_GAMETYPE, + &ME_NETOPTIONS_EPISODE, + &ME_NETOPTIONS_LEVEL, + &ME_NETOPTIONS_MONSTERS, + &ME_NETOPTIONS_MARKERS, + &ME_NETOPTIONS_MAPEXITS, + &ME_NETOPTIONS_ACCEPT, +}; + +static char MenuServer[BMAX_PATH] = "localhost"; +static MenuString_t MEO_NETJOIN_SERVER = MAKE_MENUSTRING( MenuServer, &MF_Bluefont, BMAX_PATH, 0 ); +static MenuEntry_t ME_NETJOIN_SERVER = MAKE_MENUENTRY( "Server", &MF_Redfont, &MEF_VideoSetup, &MEO_NETJOIN_SERVER, String ); +#define MAXPORTSTRINGLENGTH 6 // unsigned 16-bit integer +static char MenuPort[MAXPORTSTRINGLENGTH] = "19014"; +static MenuString_t MEO_NETJOIN_PORT = MAKE_MENUSTRING( MenuPort, &MF_Bluefont, MAXPORTSTRINGLENGTH, INPUT_NUMERIC ); +static MenuEntry_t ME_NETJOIN_PORT = MAKE_MENUENTRY( "Port", &MF_Redfont, &MEF_VideoSetup, &MEO_NETJOIN_PORT, String ); +static MenuEntry_t ME_NETJOIN_CONNECT = MAKE_MENUENTRY( "Connect", &MF_Redfont, &MEF_VideoSetup_Apply, &MEO_NULL, Link ); + +static MenuEntry_t *MEL_NETJOIN[] = { + &ME_NETJOIN_SERVER, + &ME_NETJOIN_PORT, + &ME_NETJOIN_CONNECT, +}; + + +#define NoTitle NULL + +#define MAKE_MENUMENU(Title, Format, Entries) { Title, Format, Entries, ARRAY_SIZE(Entries), 0, 0, 0 } +#define MAKE_MENUMENU_CUSTOMSIZE(Title, Format, Entries) { Title, Format, Entries, 0, 0, 0, 0 } + +static MenuMenu_t M_MAIN = MAKE_MENUMENU( NoTitle, &MMF_Top_Main, MEL_MAIN ); +static MenuMenu_t M_MAIN_INGAME = MAKE_MENUMENU( NoTitle, &MMF_Top_Main, MEL_MAIN_INGAME ); +static MenuMenu_t M_EPISODE = MAKE_MENUMENU( "Select An Episode", &MMF_Top_Episode, MEL_EPISODE ); +static MenuMenu_t M_SKILL = MAKE_MENUMENU( "Select Skill", &MMF_Top_Skill, MEL_SKILL ); +static MenuMenu_t M_NEWGAMECUSTOM = MAKE_MENUMENU( s_NewGame, &MMF_Top_NewGameCustom, MEL_NEWGAMECUSTOM ); +static MenuMenu_t M_NEWGAMECUSTOMSUB = MAKE_MENUMENU( s_NewGame, &MMF_Top_NewGameCustomSub, MEL_NEWGAMECUSTOMSUB ); +#ifndef EDUKE32_SIMPLE_MENU +static MenuMenu_t M_GAMESETUP = MAKE_MENUMENU( "Game Setup", &MMF_BigOptions, MEL_GAMESETUP ); +#endif +static MenuMenu_t M_OPTIONS = MAKE_MENUMENU( s_Options, &MMF_Top_Options, MEL_OPTIONS ); +static MenuMenu_t M_VIDEOSETUP = MAKE_MENUMENU( "Video Mode", &MMF_BigOptions, MEL_VIDEOSETUP ); +static MenuMenu_t M_KEYBOARDSETUP = MAKE_MENUMENU( "Keyboard Setup", &MMF_Top_Options, MEL_KEYBOARDSETUP ); +static MenuMenu_t M_CONTROLS = MAKE_MENUMENU( "Control Setup", &MMF_BigOptions, MEL_CONTROLS ); +static MenuMenu_t M_CHEATS = MAKE_MENUMENU( "Cheats", &MMF_SmallOptions, MEL_CHEATS ); +static MenuMenu_t M_MOUSESETUP = MAKE_MENUMENU( "Mouse Setup", &MMF_BigOptions, MEL_MOUSESETUP ); +#ifdef EDUKE32_ANDROID_MENU +static MenuMenu_t M_TOUCHSETUP = MAKE_MENUMENU( "Touch Setup", &MMF_Top_Options, MEL_TOUCHSETUP ); +static MenuMenu_t M_TOUCHSENS = MAKE_MENUMENU( "Sensitivity", &MMF_BigOptions, MEL_TOUCHSENS); +static MenuPanel_t M_TOUCHBUTTONS = { "Button Setup", MENU_TOUCHSETUP, MA_Return, MENU_TOUCHSETUP, MA_Advance, }; +#endif +static MenuMenu_t M_JOYSTICKSETUP = MAKE_MENUMENU( "Gamepad Setup", &MMF_BigOptions, MEL_JOYSTICKSETUP ); +static MenuMenu_t M_JOYSTICKBTNS = MAKE_MENUMENU( "Gamepad Buttons", &MMF_MouseJoySetupBtns, MEL_JOYSTICKBTNS ); +static MenuMenu_t M_JOYSTICKAXES = MAKE_MENUMENU( "Gamepad Axes", &MMF_BigSliders, MEL_JOYSTICKAXES ); +static MenuMenu_t M_KEYBOARDKEYS = MAKE_MENUMENU( "Key Configuration", &MMF_KeyboardSetupFuncs, MEL_KEYBOARDSETUPFUNCS ); +static MenuMenu_t M_MOUSEBTNS = MAKE_MENUMENU( "Mouse Buttons", &MMF_MouseJoySetupBtns, MEL_MOUSESETUPBTNS ); +static MenuMenu_t M_MOUSEADVANCED = MAKE_MENUMENU( "Advanced Mouse", &MMF_BigSliders, MEL_MOUSEADVANCED ); +static MenuMenu_t M_JOYSTICKAXIS = MAKE_MENUMENU( NULL, &MMF_BigSliders, MEL_JOYSTICKAXIS ); +#ifdef USE_OPENGL +static MenuMenu_t M_RENDERERSETUP_POLYMOST = MAKE_MENUMENU( "Polymost Setup", &MMF_SmallOptions, MEL_RENDERERSETUP_POLYMOST ); +# ifdef POLYMER +static MenuMenu_t M_RENDERERSETUP_POLYMER = MAKE_MENUMENU("Polymer Setup", &MMF_SmallOptions, MEL_RENDERERSETUP_POLYMER ); +# endif +#endif +static MenuMenu_t M_COLCORR = MAKE_MENUMENU( "Color Correction", &MMF_ColorCorrect, MEL_COLCORR ); +static MenuMenu_t M_SCREENSETUP = MAKE_MENUMENU( "HUD Setup", &MMF_BigOptions, MEL_SCREENSETUP ); +static MenuMenu_t M_DISPLAYSETUP = MAKE_MENUMENU( "Display Setup", &MMF_BigOptions, MEL_DISPLAYSETUP ); +static MenuMenu_t M_LOAD = MAKE_MENUMENU_CUSTOMSIZE( s_LoadGame, &MMF_LoadSave, MEL_LOAD ); +static MenuMenu_t M_SAVE = MAKE_MENUMENU_CUSTOMSIZE( s_SaveGame, &MMF_LoadSave, MEL_SAVE ); +static MenuMenu_t M_SOUND = MAKE_MENUMENU( "Sound Setup", &MMF_BigOptions, MEL_SOUND ); +static MenuMenu_t M_ADVSOUND = MAKE_MENUMENU( "Advanced Sound", &MMF_BigOptions, MEL_ADVSOUND ); +static MenuMenu_t M_SAVESETUP = MAKE_MENUMENU( "Save Setup", &MMF_BigOptions, MEL_SAVESETUP ); +static MenuMenu_t M_NETWORK = MAKE_MENUMENU( "Network Game", &MMF_Top_Joystick_Network, MEL_NETWORK ); +static MenuMenu_t M_PLAYER = MAKE_MENUMENU( "Player Setup", &MMF_SmallOptions, MEL_PLAYER ); +static MenuMenu_t M_MACROS = MAKE_MENUMENU( "Multiplayer Macros", &MMF_Macros, MEL_MACROS ); +static MenuMenu_t M_NETHOST = MAKE_MENUMENU( "Host Network Game", &MMF_SmallOptionsNarrow, MEL_NETHOST ); +static MenuMenu_t M_NETOPTIONS = MAKE_MENUMENU( "Net Game Options", &MMF_NetSetup, MEL_NETOPTIONS ); +static MenuMenu_t M_NETJOIN = MAKE_MENUMENU( "Join Network Game", &MMF_SmallOptionsNarrow, MEL_NETJOIN ); + +#ifdef EDUKE32_SIMPLE_MENU +static MenuPanel_t M_STORY = { NoTitle, MENU_STORY, MA_Return, MENU_STORY, MA_Advance, }; +#else +static MenuPanel_t M_STORY = { NoTitle, MENU_F1HELP, MA_Return, MENU_F1HELP, MA_Advance, }; +#endif + +static MenuPanel_t M_F1HELP = { NoTitle, MENU_STORY, MA_Return, MENU_STORY, MA_Advance, }; +static MenuPanel_t M_CREDITS = { NoTitle, MENU_CREDITS5, MA_Return, MENU_CREDITS2, MA_Advance, }; +static MenuPanel_t M_CREDITS2 = { NoTitle, MENU_CREDITS, MA_Return, MENU_CREDITS3, MA_Advance, }; +static MenuPanel_t M_CREDITS3 = { NoTitle, MENU_CREDITS2, MA_Return, MENU_CREDITS4, MA_Advance, }; +static MenuPanel_t M_CREDITS4 = { "About " APPNAME, MENU_CREDITS3, MA_Return, MENU_CREDITS5, MA_Advance, }; +static MenuPanel_t M_CREDITS5 = { "About " APPNAME, MENU_CREDITS4, MA_Return, MENU_CREDITS, MA_Advance, }; + +#define CURSOR_CENTER_2LINE { MENU_MARGIN_CENTER<<16, 120<<16, } +#define CURSOR_CENTER_3LINE { MENU_MARGIN_CENTER<<16, 129<<16, } +#define CURSOR_BOTTOMRIGHT { 304<<16, 186<<16, } + +static MenuVerify_t M_SAVECLEANVERIFY = { CURSOR_CENTER_3LINE, MENU_SAVESETUP, MA_None, }; +static MenuVerify_t M_RESETSTATSVERIFY = { CURSOR_CENTER_3LINE, MENU_SAVESETUP, MA_None, }; +static MenuVerify_t M_QUIT = { CURSOR_CENTER_2LINE, MENU_CLOSE, MA_None, }; +static MenuVerify_t M_QUITTOTITLE = { CURSOR_CENTER_2LINE, MENU_CLOSE, MA_None, }; +static MenuVerify_t M_LOADVERIFY = { CURSOR_CENTER_3LINE, MENU_CLOSE, MA_None, }; +static MenuVerify_t M_LOADDELVERIFY = { CURSOR_CENTER_3LINE, MENU_LOAD, MA_None, }; +static MenuVerify_t M_NEWVERIFY = { CURSOR_CENTER_2LINE, MENU_EPISODE, MA_Advance, }; +static MenuVerify_t M_SAVEVERIFY = { CURSOR_CENTER_2LINE, MENU_SAVE, MA_None, }; +static MenuVerify_t M_SAVEDELVERIFY = { CURSOR_CENTER_3LINE, MENU_SAVE, MA_None, }; +static MenuVerify_t M_RESETPLAYER = { CURSOR_CENTER_3LINE, MENU_CLOSE, MA_None, }; + +static MenuVerify_t M_COLCORRRESETVERIFY = { CURSOR_CENTER_2LINE, MENU_COLCORR, MA_None, }; +static MenuVerify_t M_KEYSRESETVERIFY = { CURSOR_CENTER_2LINE, MENU_KEYBOARDSETUP, MA_None, }; +static MenuVerify_t M_KEYSCLASSICVERIFY = { CURSOR_CENTER_2LINE, MENU_KEYBOARDSETUP, MA_None, }; +static MenuVerify_t M_JOYSTANDARDVERIFY = { CURSOR_CENTER_2LINE, MENU_JOYSTICKSETUP, MA_None, }; +static MenuVerify_t M_JOYPROVERIFY = { CURSOR_CENTER_2LINE, MENU_JOYSTICKSETUP, MA_None, }; +static MenuVerify_t M_JOYCLEARVERIFY = { CURSOR_CENTER_2LINE, MENU_JOYSTICKSETUP, MA_None, }; + +static MenuMessage_t M_NETWAITMASTER = { CURSOR_BOTTOMRIGHT, MENU_NULL, MA_None, }; +static MenuMessage_t M_NETWAITVOTES = { CURSOR_BOTTOMRIGHT, MENU_NULL, MA_None, }; +static MenuMessage_t M_BUYDUKE = { CURSOR_BOTTOMRIGHT, MENU_EPISODE, MA_Return, }; + +static MenuTextForm_t M_ADULTPASSWORD = { NULL, "Enter Password:", MAXPWLOCKOUT, MTF_Password }; +static MenuTextForm_t M_CHEATENTRY = { NULL, "Enter Cheat Code:", MAXCHEATLEN, 0 }; +static MenuTextForm_t M_CHEAT_WARP = { NULL, "Enter Warp #:", 3, 0 }; +static MenuTextForm_t M_CHEAT_SKILL = { NULL, "Enter Skill #:", 1, 0 }; + +#define MAKE_MENUFILESELECT(a, dir, b, c) { a, { &MMF_FileSelectLeft, &MMF_FileSelectRight }, { &MF_Minifont, &MF_Minifont }, dir, b, c, { NULL, NULL }, { 0, 0 }, { 3<<16, 3<<16 }, FNLIST_INITIALIZER, 0 } + +static MenuFileSelect_t M_USERMAP = MAKE_MENUFILESELECT( "Select A User Map", "./usermaps/", "*.map", boardfilename ); + +// MUST be in ascending order of MenuID enum values due to binary search +static Menu_t Menus[] = { + { &M_MAIN, MENU_MAIN, MENU_CLOSE, MA_None, Menu }, + { &M_MAIN_INGAME, MENU_MAIN_INGAME, MENU_CLOSE, MA_None, Menu }, + { &M_EPISODE, MENU_EPISODE, MENU_MAIN, MA_Return, Menu }, + { &M_USERMAP, MENU_USERMAP, MENU_PREVIOUS, MA_Return, FileSelect }, + { &M_NEWGAMECUSTOM, MENU_NEWGAMECUSTOM, MENU_MAIN, MA_Return, Menu }, + { &M_NEWGAMECUSTOMSUB, MENU_NEWGAMECUSTOMSUB, MENU_NEWGAMECUSTOM, MA_Return, Menu }, + { &M_SKILL, MENU_SKILL, MENU_PREVIOUS, MA_Return, Menu }, +#ifndef EDUKE32_SIMPLE_MENU + { &M_GAMESETUP, MENU_GAMESETUP, MENU_OPTIONS, MA_Return, Menu }, +#endif + { &M_OPTIONS, MENU_OPTIONS, MENU_MAIN, MA_Return, Menu }, + { &M_VIDEOSETUP, MENU_VIDEOSETUP, MENU_DISPLAYSETUP, MA_Return, Menu }, + { &M_KEYBOARDSETUP, MENU_KEYBOARDSETUP, MENU_CONTROLS, MA_Return, Menu }, + { &M_MOUSESETUP, MENU_MOUSESETUP, MENU_CONTROLS, MA_Return, Menu }, + { &M_JOYSTICKSETUP, MENU_JOYSTICKSETUP, MENU_CONTROLS, MA_Return, Menu }, + { &M_JOYSTICKBTNS, MENU_JOYSTICKBTNS, MENU_JOYSTICKSETUP, MA_Return, Menu }, + { &M_JOYSTICKAXES, MENU_JOYSTICKAXES, MENU_JOYSTICKSETUP, MA_Return, Menu }, + { &M_KEYBOARDKEYS, MENU_KEYBOARDKEYS, MENU_KEYBOARDSETUP, MA_Return, Menu }, + { &M_MOUSEBTNS, MENU_MOUSEBTNS, MENU_MOUSESETUP, MA_Return, Menu }, + { &M_MOUSEADVANCED, MENU_MOUSEADVANCED, MENU_MOUSESETUP, MA_Return, Menu }, + { &M_JOYSTICKAXIS, MENU_JOYSTICKAXIS, MENU_JOYSTICKAXES, MA_Return, Menu }, +#ifdef EDUKE32_ANDROID_MENU + { &M_TOUCHSETUP, MENU_TOUCHSETUP, MENU_OPTIONS, MA_Return, Menu }, + { &M_TOUCHSENS, MENU_TOUCHSENS, MENU_TOUCHSETUP, MA_Return, Menu }, + { &M_TOUCHBUTTONS, MENU_TOUCHBUTTONS, MENU_TOUCHSETUP, MA_Return, Panel }, +#endif + { &M_CONTROLS, MENU_CONTROLS, MENU_OPTIONS, MA_Return, Menu }, +#ifdef USE_OPENGL + { &M_RENDERERSETUP_POLYMOST, MENU_POLYMOST, MENU_DISPLAYSETUP, MA_Return, Menu }, +#endif + { &M_COLCORR, MENU_COLCORR, MENU_DISPLAYSETUP, MA_Return, Menu }, + { &M_COLCORR, MENU_COLCORR_INGAME, MENU_CLOSE, MA_Return, Menu }, + { &M_SCREENSETUP, MENU_SCREENSETUP, MENU_DISPLAYSETUP, MA_Return, Menu }, + { &M_DISPLAYSETUP, MENU_DISPLAYSETUP, MENU_OPTIONS, MA_Return, Menu }, +#ifdef POLYMER + { &M_RENDERERSETUP_POLYMER, MENU_POLYMER, MENU_DISPLAYSETUP, MA_Return, Menu }, +#endif + { &M_LOAD, MENU_LOAD, MENU_MAIN, MA_Return, Menu }, + { &M_SAVE, MENU_SAVE, MENU_MAIN, MA_Return, Menu }, + { &M_STORY, MENU_STORY, MENU_MAIN, MA_Return, Panel }, + { &M_F1HELP, MENU_F1HELP, MENU_MAIN, MA_Return, Panel }, + { &M_QUIT, MENU_QUIT, MENU_PREVIOUS, MA_Return, Verify }, + { &M_QUITTOTITLE, MENU_QUITTOTITLE, MENU_PREVIOUS, MA_Return, Verify }, + { &M_QUIT, MENU_QUIT_INGAME, MENU_CLOSE, MA_None, Verify }, + { &M_NETHOST, MENU_NETSETUP, MENU_MAIN, MA_Return, Menu }, + { &M_NETWAITMASTER, MENU_NETWAITMASTER, MENU_MAIN, MA_Return, Message }, + { &M_NETWAITVOTES, MENU_NETWAITVOTES, MENU_MAIN, MA_Return, Message }, + { &M_SOUND, MENU_SOUND, MENU_OPTIONS, MA_Return, Menu }, + { &M_SOUND, MENU_SOUND_INGAME, MENU_CLOSE, MA_Return, Menu }, + { &M_ADVSOUND, MENU_ADVSOUND, MENU_SOUND, MA_Return, Menu }, + { &M_SAVESETUP, MENU_SAVESETUP, MENU_OPTIONS, MA_Return, Menu }, + { &M_SAVECLEANVERIFY, MENU_SAVECLEANVERIFY, MENU_SAVESETUP, MA_None, Verify }, + { &M_RESETSTATSVERIFY, MENU_RESETSTATSVERIFY, MENU_SAVESETUP, MA_None, Verify }, +#ifdef EDUKE32_SIMPLE_MENU + { &M_CHEATS, MENU_CHEATS, MENU_OPTIONS, MA_Return, Menu }, +#else + { &M_CHEATS, MENU_CHEATS, MENU_GAMESETUP, MA_Return, Menu }, +#endif + { &M_CHEATENTRY, MENU_CHEATENTRY, MENU_CHEATS, MA_None, TextForm }, + { &M_CHEAT_WARP, MENU_CHEAT_WARP, MENU_CHEATS, MA_None, TextForm }, + { &M_CHEAT_SKILL, MENU_CHEAT_SKILL, MENU_CHEATS, MA_None, TextForm }, + { &M_CREDITS, MENU_CREDITS, MENU_MAIN, MA_Return, Panel }, + { &M_CREDITS2, MENU_CREDITS2, MENU_MAIN, MA_Return, Panel }, + { &M_CREDITS3, MENU_CREDITS3, MENU_MAIN, MA_Return, Panel }, + { &M_CREDITS4, MENU_CREDITS4, MENU_MAIN, MA_Return, Panel }, + { &M_CREDITS5, MENU_CREDITS5, MENU_MAIN, MA_Return, Panel }, + { &M_LOADVERIFY, MENU_LOADVERIFY, MENU_LOAD, MA_None, Verify }, + { &M_LOADDELVERIFY, MENU_LOADDELVERIFY, MENU_LOAD, MA_None, Verify }, + { &M_NEWVERIFY, MENU_NEWVERIFY, MENU_PREVIOUS, MA_Return, Verify }, + { &M_SAVEVERIFY, MENU_SAVEVERIFY, MENU_SAVE, MA_None, Verify }, + { &M_SAVEDELVERIFY, MENU_SAVEDELVERIFY, MENU_SAVE, MA_None, Verify }, + { &M_COLCORRRESETVERIFY, MENU_COLCORRRESETVERIFY, MENU_COLCORR, MA_None, Verify }, + { &M_KEYSRESETVERIFY, MENU_KEYSRESETVERIFY, MENU_KEYBOARDSETUP, MA_None, Verify }, + { &M_KEYSCLASSICVERIFY, MENU_KEYSCLASSICVERIFY, MENU_KEYBOARDSETUP, MA_None, Verify }, + { &M_JOYSTANDARDVERIFY, MENU_JOYSTANDARDVERIFY, MENU_JOYSTICKSETUP, MA_None, Verify }, + { &M_JOYPROVERIFY, MENU_JOYPROVERIFY, MENU_JOYSTICKSETUP, MA_None, Verify }, + { &M_JOYCLEARVERIFY, MENU_JOYCLEARVERIFY, MENU_JOYSTICKSETUP, MA_None, Verify }, + { &M_ADULTPASSWORD, MENU_ADULTPASSWORD, MENU_GAMESETUP, MA_None, TextForm }, + { &M_RESETPLAYER, MENU_RESETPLAYER, MENU_CLOSE, MA_None, Verify }, + { &M_BUYDUKE, MENU_BUYDUKE, MENU_EPISODE, MA_Return, Message }, + { &M_NETWORK, MENU_NETWORK, MENU_MAIN, MA_Return, Menu }, + { &M_PLAYER, MENU_PLAYER, MENU_OPTIONS, MA_Return, Menu }, + { &M_MACROS, MENU_MACROS, MENU_PLAYER, MA_Return, Menu }, + { &M_NETHOST, MENU_NETHOST, MENU_NETWORK, MA_Return, Menu }, + { &M_NETOPTIONS, MENU_NETOPTIONS, MENU_NETWORK, MA_Return, Menu }, + { &M_USERMAP, MENU_NETUSERMAP, MENU_NETOPTIONS, MA_Return, FileSelect }, + { &M_NETJOIN, MENU_NETJOIN, MENU_NETWORK, MA_Return, Menu }, +}; + +static CONSTEXPR const uint16_t numMenus = ARRAY_SIZE(Menus); + +Menu_t *m_currentMenu = &Menus[0]; +static Menu_t *m_previousMenu = &Menus[0]; +static Menu_t * m_parentMenu; + +MenuID_t g_currentMenu; +static MenuID_t g_previousMenu; + +#define MenuMenu_ChangeEntryList(m, el)\ + do {\ + m.entrylist = el;\ + m.numEntries = ARRAY_SIZE(el);\ + } while (0) + +static void MenuEntry_DisableOnCondition(MenuEntry_t * const entry, const int32_t condition) +{ + if (condition) + entry->flags |= MEF_Disabled; + else + entry->flags &= ~MEF_Disabled; +} +static void MenuEntry_LookDisabledOnCondition(MenuEntry_t * const entry, const int32_t condition) +{ + if (condition) + entry->flags |= MEF_LookDisabled; + else + entry->flags &= ~MEF_LookDisabled; +} +static void MenuEntry_HideOnCondition(MenuEntry_t * const entry, const int32_t condition) +{ + if (condition) + entry->flags |= MEF_Hidden; + else + entry->flags &= ~MEF_Hidden; +} + +static int32_t M_RunMenu_Menu(Menu_t *cm, MenuMenu_t *menu, MenuEntry_t *currentry, int32_t state, vec2_t origin, bool actually_draw = true); +static void Menu_EntryFocus(/*MenuEntry_t *entry*/); + +static MenuEntry_t *Menu_AdjustForCurrentEntryAssignment(MenuMenu_t *menu) +{ + MenuEntry_t *currentry = menu->entrylist[menu->currentEntry]; + + Menu_EntryFocus(/*currentry*/); + + if (currentry->ybottom - menu->scrollPos > klabs(menu->format->bottomcutoff)) + menu->scrollPos = currentry->ybottom - klabs(menu->format->bottomcutoff); + else if (currentry->ytop - menu->scrollPos < menu->format->pos.y) + menu->scrollPos = currentry->ytop - menu->format->pos.y; + + return currentry; +} + +static MenuEntry_t *Menu_AdjustForCurrentEntryAssignmentBlind(MenuMenu_t *menu) +{ + M_RunMenu_Menu(nullptr, menu, nullptr, 0, { 0, 0 }, false); + return Menu_AdjustForCurrentEntryAssignment(menu); +} + +static int32_t SELECTDIR_z = 65536; + +void Menu_PopulateNewGameCustom(void) +{ + M_NEWGAMECUSTOM.title = s_NewGame; + + int e = 0; + for (MenuGameplayStemEntry const & stem : g_MenuGameplayEntries) + { + MenuGameplayEntry const & entry = stem.entry; + if (!entry.isValid()) + break; + + MEL_NEWGAMECUSTOM[e] = &ME_NEWGAMECUSTOMENTRIES[e]; + + ++e; + } + M_NEWGAMECUSTOM.numEntries = e; + MMF_Top_NewGameCustom.pos.y = (58 + (3-e)*6)<<16; +} + +void Menu_PopulateNewGameCustomSub(int e) +{ + if ((unsigned)e >= MAXMENUGAMEPLAYENTRIES) + return; + + MenuGameplayStemEntry const & stem = g_MenuGameplayEntries[e]; + MenuGameplayEntry const & entry = stem.entry; + if (!entry.isValid()) + return; + + M_NEWGAMECUSTOMSUB.title = entry.name; + + int s = 0; + for (MenuGameplayEntry const & subentry : stem.subentries) + { + if (!subentry.isValid()) + break; + + MEL_NEWGAMECUSTOMSUB[s] = &ME_NEWGAMECUSTOMSUBENTRIES[e][s]; + + ++s; + } + M_NEWGAMECUSTOMSUB.numEntries = s; + MMF_Top_NewGameCustomSub.pos.y = (58 + (3-s)*6)<<16; +} + +/* +This function prepares data after ART and CON have been processed. +It also initializes some data in loops rather than statically at compile time. +*/ +void Menu_Init(void) +{ + int32_t i, j, k; + + if (FURY) + { + MMF_Top_Skill.pos.x = (320<<15); + ME_SKILL_TEMPLATE.format = &MEF_LeftMenu; + } + + // prepare menu fonts + // check if tilenum is -1 in case it was set in EVENT_SETDEFAULTS + if ((unsigned)MF_Redfont.tilenum >= MAXTILES) MF_Redfont.tilenum = BIGALPHANUM; + if ((unsigned)MF_Bluefont.tilenum >= MAXTILES) MF_Bluefont.tilenum = STARTALPHANUM; + if ((unsigned)MF_Minifont.tilenum >= MAXTILES) MF_Minifont.tilenum = MINIFONT; + MF_Redfont.emptychar.y = tilesiz[MF_Redfont.tilenum].y<<16; + MF_Bluefont.emptychar.y = tilesiz[MF_Bluefont.tilenum].y<<16; + MF_Minifont.emptychar.y = tilesiz[MF_Minifont.tilenum].y<<16; + if (!minitext_lowercase) + MF_Minifont.textflags |= TEXT_UPPERCASE; + + // prepare gamefuncs and keys + MEOSN_Gamefuncs[0] = MenuGameFuncNone; + MEOSV_Gamefuncs[0] = -1; + k = 1; + for (i = 0; i < NUMGAMEFUNCTIONS; ++i) + { + Bstrcpy(MenuGameFuncs[i], gamefunctions[i]); + + for (j = 0; j < MAXGAMEFUNCLEN; ++j) + if (MenuGameFuncs[i][j] == '_') + MenuGameFuncs[i][j] = ' '; + + if (gamefunctions[i][0] != '\0') + { + MEOSN_Gamefuncs[k] = MenuGameFuncs[i]; + MEOSV_Gamefuncs[k] = i; + ++k; + } + } + MEOS_Gamefuncs.numOptions = k; + + for (i = 0; i < NUMKEYS; ++i) + MEOSN_Keys[i] = g_keyNameTable[i]; + MEOSN_Keys[NUMKEYS-1] = MenuKeyNone; + + + // prepare episodes + k = 0; + for (i = 0; i < g_volumeCnt; ++i) + { + if (g_volumeNames[i][0]) + { + if (!(g_volumeFlags[i] & EF_HIDEFROMSP)) + { + MEL_EPISODE[i] = &ME_EPISODE[i]; + ME_EPISODE[i] = ME_EPISODE_TEMPLATE; + ME_EPISODE[i].name = g_volumeNames[i]; + } + + // if (!(EpisodeFlags[i] & EF_HIDEFROMMP)) + { + MEOSN_NetEpisodes[k] = g_volumeNames[i]; + MEOSV_NetEpisodes[k] = i; + + k++; + } + } + + // prepare levels + MEOS_NETOPTIONS_LEVEL[i] = MEOS_NETOPTIONS_LEVEL_TEMPLATE; + for (j = 0; j < MAXLEVELS; ++j) + { + MEOSN_NetLevels[i][j] = g_mapInfo[MAXLEVELS*i+j].name; + if (g_mapInfo[i*MAXLEVELS+j].filename != NULL) + MEOS_NETOPTIONS_LEVEL[i].numOptions = j+1; + } + MEOS_NETOPTIONS_LEVEL[i].optionNames = MEOSN_NetLevels[i]; + } + M_EPISODE.numEntries = g_volumeCnt+2; +#ifndef EDUKE32_SIMPLE_MENU + MEL_EPISODE[g_volumeCnt] = &ME_Space4_Redfont; + MEL_EPISODE[g_volumeCnt+1] = &ME_EPISODE_USERMAP; + MEOSN_NetEpisodes[k] = MenuUserMap; + MEOSV_NetEpisodes[k] = MAXVOLUMES; +#else + M_EPISODE.numEntries = g_volumeCnt; + k--; +#endif + MEOS_NETOPTIONS_EPISODE.numOptions = k + 1; + NetEpisode = MEOSV_NetEpisodes[0]; + MMF_Top_Episode.pos.y = (58 + (3-k)*6)<<16; + if (g_skillCnt == 0) + MEO_EPISODE.linkID = MENU_NULL; + M_EPISODE.currentEntry = ud.default_volume; + + // prepare new game custom :O + if (g_MenuGameplayEntries[0].entry.isValid()) + { + MEO_MAIN_NEWGAME.linkID = M_NEWVERIFY.linkID = MENU_NEWGAMECUSTOM; + + int e = 0; + for (MenuGameplayStemEntry const & stem : g_MenuGameplayEntries) + { + MenuGameplayEntry const & entry = stem.entry; + if (!entry.isValid()) + break; + + MenuEntry_t & e_me = ME_NEWGAMECUSTOMENTRIES[e]; + e_me = ME_EPISODE_TEMPLATE; + MenuLink_t & e_meo = MEO_NEWGAMECUSTOM[e]; + e_meo = MEO_NEWGAMECUSTOM_TEMPLATE; + e_me.entry = &e_meo; + + e_me.name = entry.name; + if (entry.flags & MGE_Locked) + e_me.flags |= MEF_Disabled; + if (entry.flags & MGE_Hidden) + e_me.flags |= MEF_Hidden; + + int s = 0; + for (MenuGameplayEntry const & subentry : stem.subentries) + { + if (!subentry.isValid()) + break; + + MenuEntry_t & s_me = ME_NEWGAMECUSTOMSUBENTRIES[e][s]; + s_me = ME_EPISODE_TEMPLATE; + MenuLink_t & s_meo = MEO_NEWGAMECUSTOMSUB[e][s]; + s_meo = MEO_NEWGAMECUSTOMSUB_TEMPLATE; + s_me.entry = &s_meo; + + s_me.name = subentry.name; + if (subentry.flags & MGE_Locked) + s_me.flags |= MEF_Disabled; + if (subentry.flags & MGE_Hidden) + s_me.flags |= MEF_Hidden; + + ++s; + } + + if (entry.flags & MGE_UserContent) + e_meo.linkID = MENU_USERMAP; + else if (s == 0) + e_meo.linkID = MENU_SKILL; + + ++e; + } + + Menu_PopulateNewGameCustom(); + } + + // prepare skills + k = -1; + for (i = 0; i < g_skillCnt && g_skillNames[i][0]; ++i) + { + MEL_SKILL[i] = &ME_SKILL[i]; + ME_SKILL[i] = ME_SKILL_TEMPLATE; + ME_SKILL[i].name = g_skillNames[i]; + + MEOSN_NetSkills[i] = g_skillNames[i]; + + k = i; + } + ++k; + M_SKILL.numEntries = g_skillCnt; // k; + MEOS_NETOPTIONS_MONSTERS.numOptions = g_skillCnt + 1; // k+1; + MEOSN_NetSkills[g_skillCnt] = MenuSkillNone; + MMF_Top_Skill.pos.y = (58 + (4-g_skillCnt)*6)<<16; + M_SKILL.currentEntry = ud.default_skill; + Menu_AdjustForCurrentEntryAssignmentBlind(&M_SKILL); + + // prepare multiplayer gametypes + k = -1; + for (i = 0; i < MAXGAMETYPES; ++i) + if (g_gametypeNames[i][0]) + { + MEOSN_NetGametypes[i] = g_gametypeNames[i]; + k = i; + } + ++k; + MEOS_NETOPTIONS_GAMETYPE.numOptions = k; + if (NAM_WW2GI) + ME_NETOPTIONS_MONSTERS.name = "Enemies"; + + // prepare cheats + for (i = 0; i < NUMCHEATFUNCS; ++i) + MEL_CHEATS[i+1] = &ME_CheatCodes[i]; + + // prepare text chat macros + for (i = 0; i < MAXRIDECULE; ++i) + { + MEL_MACROS[i] = &ME_MACROS[i]; + ME_MACROS[i] = ME_MACROS_TEMPLATE; + ME_MACROS[i].entry = &MEO_MACROS[i]; + MEO_MACROS[i] = MEO_MACROS_TEMPLATE; + + MEO_MACROS[i].variable = ud.ridecule[i]; + } + + // prepare input + for (i = 0; i < NUMGAMEFUNCTIONS; ++i) + { + if (MenuGameFuncs[i][0] == '\0') + { + MEL_KEYBOARDSETUPFUNCS[i] = NULL; + continue; + } + + MEL_KEYBOARDSETUPFUNCS[i] = &ME_KEYBOARDSETUPFUNCS[i]; + ME_KEYBOARDSETUPFUNCS[i] = ME_KEYBOARDSETUPFUNCS_TEMPLATE; + ME_KEYBOARDSETUPFUNCS[i].name = MenuGameFuncs[i]; + ME_KEYBOARDSETUPFUNCS[i].entry = &MEO_KEYBOARDSETUPFUNCS[i]; + MEO_KEYBOARDSETUPFUNCS[i] = MEO_KEYBOARDSETUPFUNCS_TEMPLATE; + MEO_KEYBOARDSETUPFUNCS[i].column[0] = &ud.config.KeyboardKeys[i][0]; + MEO_KEYBOARDSETUPFUNCS[i].column[1] = &ud.config.KeyboardKeys[i][1]; + } + M_KEYBOARDKEYS.numEntries = NUMGAMEFUNCTIONS; + for (i = 0; i < MENUMOUSEFUNCTIONS; ++i) + { + MEL_MOUSESETUPBTNS[i] = &ME_MOUSESETUPBTNS[i]; + ME_MOUSESETUPBTNS[i] = ME_MOUSEJOYSETUPBTNS_TEMPLATE; + ME_MOUSESETUPBTNS[i].name = MenuMouseNames[i]; + ME_MOUSESETUPBTNS[i].entry = &MEO_MOUSESETUPBTNS[i]; + MEO_MOUSESETUPBTNS[i] = MEO_MOUSEJOYSETUPBTNS_TEMPLATE; + MEO_MOUSESETUPBTNS[i].data = &ud.config.MouseFunctions[MenuMouseDataIndex[i][0]][MenuMouseDataIndex[i][1]]; + } + for (i = 0; i < 2*joystick.numButtons + 8*joystick.numHats; ++i) + { + if (i < 2*joystick.numButtons) + { + if (i & 1) + Bsnprintf(MenuJoystickNames[i], MAXJOYBUTTONSTRINGLENGTH, "Double %s", joyGetName(1, i>>1)); + else + Bstrncpy(MenuJoystickNames[i], joyGetName(1, i>>1), MAXJOYBUTTONSTRINGLENGTH); + } + else + { + Bsnprintf(MenuJoystickNames[i], MAXJOYBUTTONSTRINGLENGTH, (i & 1) ? "Double Hat %d %s" : "Hat %d %s", ((i - 2*joystick.numButtons)>>3), MenuJoystickHatDirections[((i - 2*joystick.numButtons)>>1) % 4]); + } + + MEL_JOYSTICKBTNS[i] = &ME_JOYSTICKBTNS[i]; + ME_JOYSTICKBTNS[i] = ME_MOUSEJOYSETUPBTNS_TEMPLATE; + ME_JOYSTICKBTNS[i].name = MenuJoystickNames[i]; + ME_JOYSTICKBTNS[i].entry = &MEO_JOYSTICKBTNS[i]; + MEO_JOYSTICKBTNS[i] = MEO_MOUSEJOYSETUPBTNS_TEMPLATE; + MEO_JOYSTICKBTNS[i].data = &ud.config.JoystickFunctions[i>>1][i&1]; + } + M_JOYSTICKBTNS.numEntries = 2*joystick.numButtons + 8*joystick.numHats; + for (i = 0; i < joystick.numAxes; ++i) + { + ME_JOYSTICKAXES[i] = ME_JOYSTICKAXES_TEMPLATE; + Bstrncpy(MenuJoystickAxes[i], joyGetName(0, i), MAXJOYBUTTONSTRINGLENGTH); + ME_JOYSTICKAXES[i].name = MenuJoystickAxes[i]; + MEL_JOYSTICKAXES[i] = &ME_JOYSTICKAXES[i]; + } + M_JOYSTICKAXES.numEntries = joystick.numAxes; + + // prepare sound setup +#ifndef EDUKE32_STANDALONE + if (WW2GI) + ME_SOUND_DUKETALK.name = "GI talk:"; + else if (NAM) + ME_SOUND_DUKETALK.name = "Grunt talk:"; +#endif + + if (FURY) + { + MF_Redfont.between.x = 2<<16; + MF_Redfont.cursorScale = 32768; + MF_Redfont.zoom = 16384; + MF_Bluefont.zoom = 16384; + + // hack; should swap out pointers + MF_Minifont = MF_Bluefont; + + MMF_Top_Main.pos.x = 40<<16; + MMF_Top_Main.pos.y = 130<<16; + MMF_Top_Main.bottomcutoff = 190<<16; + M_OPTIONS.format = &MMF_Top_Main; + + MEF_MainMenu.width = MEF_OptionsMenu.width = -(160<<16); + MEF_MainMenu.marginBottom = 7<<16; + + M_OPTIONS.title = NoTitle; + + SELECTDIR_z = 16384; + } + + // prepare shareware + if (VOLUMEONE) + { + // blue out episodes beyond the first + for (i = 1; i < g_volumeCnt; ++i) + { + if (MEL_EPISODE[i]) + { + ME_EPISODE[i].entry = &MEO_EPISODE_SHAREWARE; + ME_EPISODE[i].flags |= MEF_LookDisabled; + } + } + M_EPISODE.numEntries = g_volumeCnt; // remove User Map (and spacer) + MEOS_NETOPTIONS_EPISODE.numOptions = 1; + MenuEntry_DisableOnCondition(&ME_NETOPTIONS_EPISODE, 1); + } + + // prepare pre-Atomic + if (!VOLUMEALL || !PLUTOPAK) + { + // prepare credits + M_CREDITS.title = M_CREDITS2.title = M_CREDITS3.title = s_Credits; + } + + MenuEntry_HideOnCondition(&ME_MAIN_HELP, G_GetLogoFlags() & LOGO_NOHELP); +#ifndef EDUKE32_SIMPLE_MENU + MenuEntry_HideOnCondition(&ME_MAIN_CREDITS, G_GetLogoFlags() & LOGO_NOCREDITS); +#endif +} + +static void Menu_Run(Menu_t *cm, vec2_t origin); + + +static void Menu_BlackRectangle(int32_t x, int32_t y, int32_t width, int32_t height, int32_t orientation); + +/* +At present, no true difference is planned between Menu_Pre() and Menu_PreDraw(). +They are separate for purposes of organization. +*/ + +static void Menu_Pre(MenuID_t cm) +{ + int32_t i; + auto ps = g_player[myconnectindex].ps; + + switch (cm) + { + case MENU_MAIN_INGAME: + MenuEntry_DisableOnCondition(&ME_MAIN_SAVEGAME, ud.recstat == 2); + MenuEntry_DisableOnCondition(&ME_MAIN_QUITTOTITLE, g_netServer || numplayers > 1); + fallthrough__; + case MENU_MAIN: + if ((g_netServer || ud.multimode > 1) && ud.recstat != 2) + { + ME_MAIN_NEWGAME.entry = &MEO_MAIN_NEWGAME_NETWORK; + ME_MAIN_NEWGAME_INGAME.entry = &MEO_MAIN_NEWGAME_NETWORK; + } + else + { + ME_MAIN_NEWGAME.entry = &MEO_MAIN_NEWGAME; + ME_MAIN_NEWGAME_INGAME.entry = &MEO_MAIN_NEWGAME_INGAME; + } + break; + + case MENU_GAMESETUP: + MEO_GAMESETUP_DEMOREC.options = (ps->gm&MODE_GAME) ? &MEOS_DemoRec : &MEOS_OffOn; + MenuEntry_DisableOnCondition(&ME_GAMESETUP_DEMOREC, (ps->gm&MODE_GAME) && ud.m_recstat != 1); + break; + + case MENU_DISPLAYSETUP: + MenuEntry_HideOnCondition(&ME_DISPLAYSETUP_VOXELS, !g_haveVoxels); +#ifdef USE_OPENGL + if (videoGetRenderMode() == REND_CLASSIC) + MenuMenu_ChangeEntryList(M_DISPLAYSETUP, MEL_DISPLAYSETUP); +#ifdef POLYMER + else if (videoGetRenderMode() == REND_POLYMER) + MenuMenu_ChangeEntryList(M_DISPLAYSETUP, MEL_DISPLAYSETUP_GL_POLYMER); +#endif + else + MenuMenu_ChangeEntryList(M_DISPLAYSETUP, MEL_DISPLAYSETUP_GL); + + MEO_SCREENSETUP_SCREENSIZE.steps = !(ud.statusbarflags & STATUSBAR_NONONE) + + !(ud.statusbarflags & STATUSBAR_NOMODERN) + + !(ud.statusbarflags & STATUSBAR_NOMINI) * (ud.statusbarrange + 1) + + !(ud.statusbarflags & STATUSBAR_NOOVERLAY) + + !(ud.statusbarflags & STATUSBAR_NOFULL) + + !(ud.statusbarflags & STATUSBAR_NOSHRINK) * 14; + MEO_SCREENSETUP_SCREENSIZE.max = MEO_SCREENSETUP_SCREENSIZE.steps - 1; + if (MEO_SCREENSETUP_SCREENSIZE.steps <= 2 && !(ud.statusbarflags & STATUSBAR_NONONE)) + { + ME_SCREENSETUP_SCREENSIZE.entry = &MEO_SCREENSETUP_SCREENSIZE_TWO; + ME_SCREENSETUP_SCREENSIZE.type = Option; + } + else + { + ME_SCREENSETUP_SCREENSIZE.entry = &MEO_SCREENSETUP_SCREENSIZE; + ME_SCREENSETUP_SCREENSIZE.type = RangeInt32; + } + MenuEntry_HideOnCondition(&ME_SCREENSETUP_SCREENSIZE, (MEO_SCREENSETUP_SCREENSIZE.steps < 2)); + + vpsize = !(ud.statusbarflags & STATUSBAR_NONONE) + + (ud.screen_size >= 4 && !(ud.statusbarflags & STATUSBAR_NOMODERN)) + + (ud.screen_size >= 4 && ud.althud == 0 && !(ud.statusbarflags & STATUSBAR_NOMINI)) * (ud.statusbarcustom + 1) + + (ud.screen_size >= 8 && !(ud.statusbarflags & STATUSBAR_NOOVERLAY)) + + (ud.screen_size >= 8 && ud.statusbarmode == 0 && !(ud.statusbarflags & STATUSBAR_NOFULL)) + + (ud.screen_size > 8 && !(ud.statusbarflags & STATUSBAR_NOSHRINK)) * ((ud.screen_size - 8) >> 2) + -1; + +#ifndef EDUKE32_STANDALONE +#ifdef TEXFILTER_MENU_OPTIONS + if (videoGetRenderMode() != REND_CLASSIC) + { + //POGOTODO: allow setting anisotropy again while r_useindexedcolortextures is set when support is added down the line + // don't allow setting anisotropy while in POLYMOST and r_useindexedcolortextures is enabled + MenuEntry_DisableOnCondition(&ME_DISPLAYSETUP_ANISOTROPY, videoGetRenderMode() == REND_POLYMOST && r_useindexedcolortextures); + + for (i = (int32_t) ARRAY_SIZE(MEOSV_DISPLAYSETUP_ANISOTROPY) - 1; i >= 0; --i) + { + if (MEOSV_DISPLAYSETUP_ANISOTROPY[i] <= glinfo.maxanisotropy) + { + MEOS_DISPLAYSETUP_ANISOTROPY.numOptions = i + 1; + break; + } + } + } +#endif +#endif + break; + + case MENU_POLYMER: + case MENU_POLYMOST: + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_TEXQUALITY, !usehightile); + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_PRECACHE, !usehightile); +# ifndef EDUKE32_GLES + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_TEXCACHE, !(glusetexcompr && usehightile)); +# endif +# ifdef USE_GLEXT + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_DETAILTEX, !usehightile); + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_GLOWTEX, !usehightile); +# endif +#endif + break; + + case MENU_VIDEOSETUP: + { + Bmemset(resolution, 0, sizeof(resolution)); + MEOS_VIDEOSETUP_RESOLUTION.numOptions = 0; + + // prepare video setup + for (int i = 0; i < validmodecnt; ++i) + { + int j; + + for (j = 0; j < MEOS_VIDEOSETUP_RESOLUTION.numOptions; ++j) + { + if (validmode[i].xdim == resolution[j].xdim && validmode[i].ydim == resolution[j].ydim) + { + resolution[j].flags |= validmode[i].fs ? RES_FS : RES_WIN; + Bsnprintf(resolution[j].name, MAXRESOLUTIONSTRINGLENGTH, "%d x %d%s", resolution[j].xdim, resolution[j].ydim, (resolution[j].flags & RES_FS) ? "" : "Win"); + MEOSN_VIDEOSETUP_RESOLUTION[j] = resolution[j].name; + if (validmode[i].bpp > resolution[j].bppmax) + resolution[j].bppmax = validmode[i].bpp; + break; + } + } + + if (j == MEOS_VIDEOSETUP_RESOLUTION.numOptions) // no match found + { + resolution[j].xdim = validmode[i].xdim; + resolution[j].ydim = validmode[i].ydim; + resolution[j].bppmax = validmode[i].bpp; + resolution[j].flags = validmode[i].fs ? RES_FS : RES_WIN; + Bsnprintf(resolution[j].name, MAXRESOLUTIONSTRINGLENGTH, "%d x %d%s", resolution[j].xdim, resolution[j].ydim, (resolution[j].flags & RES_FS) ? "" : "Win"); + MEOSN_VIDEOSETUP_RESOLUTION[j] = resolution[j].name; + ++MEOS_VIDEOSETUP_RESOLUTION.numOptions; + } + } + + const int32_t nr = newresolution; + + // don't allow setting fullscreen mode if it's not supported by the resolution + MenuEntry_DisableOnCondition(&ME_VIDEOSETUP_FULLSCREEN, !(resolution[nr].flags & RES_FS)); + + MenuEntry_DisableOnCondition(&ME_VIDEOSETUP_APPLY, + (xres == resolution[nr].xdim && yres == resolution[nr].ydim && + videoGetRenderMode() == newrendermode && fullscreen == newfullscreen + && vsync == newvsync && r_borderless == newborderless + ) + || (newrendermode != REND_CLASSIC && resolution[nr].bppmax <= 8)); + MenuEntry_DisableOnCondition(&ME_VIDEOSETUP_BORDERLESS, newfullscreen); + MenuEntry_DisableOnCondition(&ME_VIDEOSETUP_FRAMELIMITOFFSET, !r_maxfps); + break; + } + + case MENU_SOUND: + case MENU_SOUND_INGAME: + case MENU_ADVSOUND: + MenuEntry_DisableOnCondition(&ME_SOUND_VOLUME_FX, !ud.config.SoundToggle); + MenuEntry_DisableOnCondition(&ME_SOUND_VOLUME_MUSIC, !ud.config.MusicToggle); + MenuEntry_DisableOnCondition(&ME_SOUND_DUKETALK, !ud.config.SoundToggle); + MenuEntry_DisableOnCondition(&ME_SOUND_SAMPLINGRATE, !ud.config.SoundToggle && !ud.config.MusicToggle); +#ifndef EDUKE32_SIMPLE_MENU + MenuEntry_DisableOnCondition(&ME_SOUND_NUMVOICES, !ud.config.SoundToggle); +#endif + MenuEntry_DisableOnCondition(&ME_SOUND_RESTART, soundrate == ud.config.MixRate && + soundvoices == ud.config.NumVoices); + break; + + case MENU_SAVESETUP: + MenuEntry_DisableOnCondition(&ME_SAVESETUP_MAXAUTOSAVES, !ud.autosavedeletion); +#ifdef EDUKE32_STANDALONE + MenuEntry_DisableOnCondition(&ME_SAVESETUP_RESETSTATS, !communityapiEnabled()); +#endif + break; + + case MENU_JOYSTICKSETUP: + MenuEntry_DisableOnCondition(&ME_JOYSTICK_EDITBUTTONS, !CONTROL_JoyPresent || (joystick.numButtons == 0 && joystick.numHats == 0)); + MenuEntry_DisableOnCondition(&ME_JOYSTICK_EDITAXES, !CONTROL_JoyPresent || joystick.numAxes == 0); + MenuEntry_DisableOnCondition(&ME_JOYSTICK_DEFAULTS_STANDARD, !joystick.isGameController); + MenuEntry_DisableOnCondition(&ME_JOYSTICK_DEFAULTS_PRO, !joystick.isGameController); + break; + +#ifndef EDUKE32_SIMPLE_MENU + case MENU_MOUSESETUP: + MenuEntry_DisableOnCondition(&ME_MOUSESETUP_MOUSEAIMING, ud.mouseaiming); + break; +#endif + case MENU_NETOPTIONS: + if (MEOSV_NetEpisodes[MEO_NETOPTIONS_EPISODE.currentOption] == MAXVOLUMES) + MEL_NETOPTIONS[2] = &ME_NETOPTIONS_USERMAP; + else + { + MEL_NETOPTIONS[2] = &ME_NETOPTIONS_LEVEL; + MEO_NETOPTIONS_LEVEL.options = &MEOS_NETOPTIONS_LEVEL[MEOSV_NetEpisodes[MEO_NETOPTIONS_EPISODE.currentOption]]; + } + if (!(g_gametypeFlags[ud.m_coop] & GAMETYPE_MARKEROPTION)) + { + ME_NETOPTIONS_MARKERS.type = Dummy; + ME_NETOPTIONS_MARKERS.flags |= MEF_Disabled; + } + else + { + ME_NETOPTIONS_MARKERS.type = Option; + ME_NETOPTIONS_MARKERS.flags &= ~MEF_Disabled; + } + MEL_NETOPTIONS[5] = (g_gametypeFlags[ud.m_coop] & (GAMETYPE_PLAYERSFRIENDLY|GAMETYPE_TDM)) ? &ME_NETOPTIONS_FRFIRE : &ME_NETOPTIONS_MAPEXITS; + break; + + case MENU_OPTIONS: + MenuEntry_DisableOnCondition(&ME_OPTIONS_PLAYERSETUP, ud.recstat == 1); + break; + + case MENU_COLCORR: + case MENU_COLCORR_INGAME: + MenuEntry_DisableOnCondition(&ME_COLCORR_CONTRAST, !gammabrightness); + MenuEntry_DisableOnCondition(&ME_COLCORR_BRIGHTNESS, !gammabrightness); + break; + + case MENU_CHEATS: + case MENU_CHEATENTRY: + case MENU_CHEAT_WARP: + case MENU_CHEAT_SKILL: + { + const int32_t menucheatsdisabled = numplayers != 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME); + + // refresh display names of quote cheats + if (!DUKEBETA) + { + ME_CheatCodes[CHEATFUNC_QUOTEBETA].name = apStrings[QUOTE_CHEAT_BETA]; + if (!NAM) + ME_CheatCodes[CHEATFUNC_QUOTETODD].name = apStrings[QUOTE_CHEAT_TODD]; + ME_CheatCodes[CHEATFUNC_QUOTEALLEN].name = apStrings[QUOTE_CHEAT_ALLEN]; + } + + for (i = 0; i < NUMCHEATFUNCS; i++) + { + uint32_t cheatmask = cl_cheatmask & (1<>11),0,2+8); + } + break; + + case MENU_PLAYER: + rotatesprite_fs(origin.x + (260<<16), origin.y + ((24+(tilesiz[APLAYER].y>>1))<<16), 49152L,0,1441-((((4-((int32_t) totalclock>>4)))&3)*5),0,entry == &ME_PLAYER_TEAM ? G_GetTeamPalette(ud.team) : ud.color,10); + break; + + case MENU_MACROS: + mgametextcenter(origin.x, origin.y + (144<<16), "Activate in-game with Shift-F#"); + break; + + case MENU_COLCORR: + case MENU_COLCORR_INGAME: + // center panel + rotatesprite_fs(origin.x + (120<<16), origin.y + (32<<16), 16384, 0, 3290, 0, 0, 2|8|16); + rotatesprite_fs(origin.x + (160<<16) - (tilesiz[BOTTOMSTATUSBAR].x<<13), origin.y + (82<<16) - (tilesiz[BOTTOMSTATUSBAR].y<<14), 16384, 0, BOTTOMSTATUSBAR, 0, 0, 2|8|16); + + // left panel + rotatesprite_fs(origin.x + (40<<16), origin.y + (32<<16), 16384, 0, BONUSSCREEN, 0, 0, 2|8|16); + + // right panel + rotatesprite_fs(origin.x + (200<<16), origin.y + (32<<16), 16384, 0, LOADSCREEN, 0, 0, 2|8|16); + break; + + case MENU_NETSETUP: + case MENU_NETHOST: + mminitext(origin.x + (90<<16), origin.y + (90<<16), "Game Type", MF_Minifont.pal_deselected); + mminitext(origin.x + (90<<16), origin.y + ((90+8)<<16), "Episode", MF_Minifont.pal_deselected); + mminitext(origin.x + (90<<16), origin.y + ((90+8+8)<<16), "Level", MF_Minifont.pal_deselected); + mminitext(origin.x + (90<<16), origin.y + ((90+8+8+8)<<16), ME_NETOPTIONS_MONSTERS.name, MF_Minifont.pal_deselected); + if (ud.m_coop == 0) + mminitext(origin.x + (90<<16), origin.y + ((90+8+8+8+8)<<16), "Markers", MF_Minifont.pal_deselected); + else if (ud.m_coop == 1) + mminitext(origin.x + (90<<16), origin.y + ((90+8+8+8+8)<<16), "Friendly Fire", MF_Minifont.pal_deselected); + mminitext(origin.x + (90<<16), origin.y + ((90+8+8+8+8+8)<<16), "User Map", MF_Minifont.pal_deselected); + + mminitext(origin.x + ((90+60)<<16), origin.y + (90<<16), g_gametypeNames[ud.m_coop], MF_Minifont.pal_deselected_right); + + mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8)<<16), g_volumeNames[ud.m_volume_number], MF_Minifont.pal_deselected_right); + mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8)<<16), g_mapInfo[MAXLEVELS*ud.m_volume_number+ud.m_level_number].name, MF_Minifont.pal_deselected_right); + if (ud.m_monsters_off == 0 || ud.m_player_skill > 0) + mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8)<<16), g_skillNames[ud.m_player_skill], MF_Minifont.pal_deselected_right); + else mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8)<<16), "None", MF_Minifont.pal_deselected_right); + if (ud.m_coop == 0) + { + if (ud.m_marker) mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8+8)<<16), "On", MF_Minifont.pal_deselected_right); + else mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8+8)<<16), "Off", MF_Minifont.pal_deselected_right); + } + else if (ud.m_coop == 1) + { + if (ud.m_ffire) mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8+8)<<16), "On", MF_Minifont.pal_deselected_right); + else mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8+8)<<16), "Off", MF_Minifont.pal_deselected_right); + } + break; + + case MENU_VIDEOSETUP: + if (entry == &ME_VIDEOSETUP_VSYNC && *MEO_VIDEOSETUP_VSYNC.data) + mgametextcenter(origin.x, origin.y + (175<<16), "Try VSync in your graphics driver's\n" + "control panel before this option."); + break; + + case MENU_RESETPLAYER: + videoFadeToBlack(1); + Bsprintf(tempbuf, "Load last game:\n\"%s\"", g_quickload->name); + Menu_DrawVerifyPrompt(origin.x, origin.y, tempbuf, 2); + break; + + case MENU_LOAD: + { +#if 0 + for (i = 0; i <= 108; i += 12) + rotatesprite_fs(origin.x + ((160+64+91-64)<<16), origin.y + ((i+56)<<16), 65536L,0,TEXTBOX,24,0,10); +#endif + Menu_BlackRectangle(origin.x + (198<<16), origin.y + (47<<16), 102<<16, 100<<16, 1|32); + + rotatesprite_fs(origin.x + (22<<16), origin.y + (97<<16), 65536L,0,WINDOWBORDER2,24,0,10); + rotatesprite_fs(origin.x + (180<<16), origin.y + (97<<16), 65536L,1024,WINDOWBORDER2,24,0,10); + rotatesprite_fs(origin.x + (99<<16), origin.y + (50<<16), 65536L,512,WINDOWBORDER1,24,0,10); + rotatesprite_fs(origin.x + (103<<16), origin.y + (144<<16), 65536L,1024+512,WINDOWBORDER1,24,0,10); + + if (M_LOAD.currentEntry >= (int32_t)g_nummenusaves) + { + menutext_centeralign(origin.x + (101<<16), origin.y + (97<<16), "Empty"); + break; + } + + menusave_t & msv = g_menusaves[M_LOAD.currentEntry]; + + if (msv.brief.isValid()) + { + if (waloff[TILE_LOADSHOT]) + rotatesprite_fs(origin.x + (101<<16), origin.y + (97<<16), 65536>>1,512,TILE_LOADSHOT, msv.isOldVer?16:-32, 0,4+10+64); + + if (msv.isOldVer) + { + mgametextcenterat(origin.x + (101<<16), origin.y + (50<<16), + msv.brief.isExt ? "Previous Version,\nSequence Point Available" : "Previous Version,\nUnable to Load"); + +#ifndef EDUKE32_SIMPLE_MENU + Bsprintf(tempbuf,"Saved: %d.%d.%d.%u %d-bit", savehead.majorver, savehead.minorver, + savehead.bytever, savehead.userbytever, 8*savehead.getPtrSize()); + mgametext(origin.x + (25<<16), origin.y + (124<<16), tempbuf); + Bsprintf(tempbuf,"Our: %d.%d.%d.%u %d-bit", SV_MAJOR_VER, SV_MINOR_VER, BYTEVERSION, + ud.userbytever, (int32_t)(8*sizeof(intptr_t))); + mgametext(origin.x + ((25+16)<<16), origin.y + (134<<16), tempbuf); +#endif + + if (msv.isUnreadable) + break; + } + + if (savehead.numplayers > 1) + { + Bsprintf(tempbuf, "Players: %-2d ", savehead.numplayers); + mgametextcenter(origin.x, origin.y + (156<<16), tempbuf); + } + + { + const char *name = g_mapInfo[(savehead.volnum*MAXLEVELS) + savehead.levnum].name; + Bsprintf(tempbuf, "%s / %s", name ? name : "^10unnamed^0", g_skillNames[savehead.skill-1]); + } + + mgametextcenter(origin.x, origin.y + (168<<16), tempbuf); + if (savehead.volnum == 0 && savehead.levnum == 7) + mgametextcenter(origin.x, origin.y + (180<<16), savehead.boardfn); + } + break; + } + + case MENU_SAVE: + { +#if 0 + for (i = 0; i <= 108; i += 12) + rotatesprite_fs(origin.x + ((160+64+91-64)<<16), origin.y + ((i+56)<<16), 65536L,0,TEXTBOX,24,0,10); +#endif + Menu_BlackRectangle(origin.x + (198<<16), origin.y + (47<<16), 102<<16, 100<<16, 1|32); + + rotatesprite_fs(origin.x + (22<<16), origin.y + (97<<16), 65536L,0,WINDOWBORDER2,24,0,10); + rotatesprite_fs(origin.x + (180<<16), origin.y + (97<<16), 65536L,1024,WINDOWBORDER2,24,0,10); + rotatesprite_fs(origin.x + (99<<16), origin.y + (50<<16), 65536L,512,WINDOWBORDER1,24,0,10); + rotatesprite_fs(origin.x + (103<<16), origin.y + (144<<16), 65536L,1024+512,WINDOWBORDER1,24,0,10); + + j = 0; + for (int k = 0; k < g_nummenusaves+1; ++k) + if (((MenuString_t*)M_SAVE.entrylist[k]->entry)->editfield) + j |= 1; + + if (j) + rotatesprite_fs(origin.x + (101<<16), origin.y + (97<<16), 65536L>>1,512,TILE_SAVESHOT,-32,0,4+10+64); + else if (0 < M_SAVE.currentEntry && M_SAVE.currentEntry <= (int32_t)g_nummenusaves) + { + menusave_t & msv = g_menusaves[M_SAVE.currentEntry-1]; + + if (msv.brief.isValid()) + { + if (waloff[TILE_LOADSHOT]) + rotatesprite_fs(origin.x + (101<<16), origin.y + (97<<16), 65536>>1,512,TILE_LOADSHOT, msv.isOldVer?16:-32, 0,4+10+64); + + if (msv.isOldVer) + { + mgametextcenterat(origin.x + (101<<16), origin.y + (50<<16), + msv.brief.isExt ? "Previous Version,\nSequence Point Available" : "Previous Version,\nUnable to Load"); + +#ifndef EDUKE32_SIMPLE_MENU + Bsprintf(tempbuf,"Saved: %d.%d.%d.%u %d-bit", savehead.majorver, savehead.minorver, + savehead.bytever, savehead.userbytever, 8*savehead.getPtrSize()); + mgametext(origin.x + (25<<16), origin.y + (124<<16), tempbuf); + Bsprintf(tempbuf,"Our: %d.%d.%d.%u %d-bit", SV_MAJOR_VER, SV_MINOR_VER, BYTEVERSION, + ud.userbytever, (int32_t)(8*sizeof(intptr_t))); + mgametext(origin.x + ((25+16)<<16), origin.y + (134<<16), tempbuf); +#endif + } + } + } + else + menutext_centeralign(origin.x + (101<<16), origin.y + (97<<16), "New"); + + if (ud.multimode > 1) + { + Bsprintf(tempbuf, "Players: %-2d ", ud.multimode); + mgametextcenter(origin.x, origin.y + (156<<16), tempbuf); + } + + Bsprintf(tempbuf,"%s / %s",g_mapInfo[(ud.volume_number*MAXLEVELS) + ud.level_number].name, g_skillNames[ud.player_skill-1]); + mgametextcenter(origin.x, origin.y + (168<<16), tempbuf); + if (ud.volume_number == 0 && ud.level_number == 7) + mgametextcenter(origin.x, origin.y + (180<<16), currentboardfilename); + break; + } + +#ifdef EDUKE32_ANDROID_MENU + case MENU_SKILL: + { + static const char *s[] = { "EASY - Few enemies, and lots of stuff.", "MEDIUM - Normal difficulty.", "HARD - For experienced players.", "EXPERTS - Lots of enemies, plus they respawn!" }; + if (M_SKILL.currentEntry < ARRAY_SSIZE(s)) + mgametextcenter(origin.x, origin.y + (168<<16), s[M_SKILL.currentEntry]); + } + break; +#endif + + case MENU_SAVECLEANVERIFY: + videoFadeToBlack(1); + + if (g_oldSaveCnt) + { + Bsprintf(tempbuf, "Delete %d obsolete saves?\nThis action cannot be undone.", g_oldSaveCnt); + Menu_DrawVerifyPrompt(origin.x, origin.y, tempbuf, 2); + } + else + mgametextcenter(origin.x, origin.y + (90<<16), "No obsolete saves found!"); + + break; + + case MENU_RESETSTATSVERIFY: + videoFadeToBlack(1); + + if (communityapiEnabled()) + { + Bsprintf(tempbuf, "Delete %s stats and achievement data?\nThis action cannot be undone!", communityApiGetPlatformName()); + Menu_DrawVerifyPrompt(origin.x, origin.y, tempbuf, 2); + } + else + mgametextcenter(origin.x, origin.y + (90<<16), "No data found!"); + + break; + + case MENU_LOADVERIFY: + { + videoFadeToBlack(1); + menusave_t & msv = g_menusaves[M_LOAD.currentEntry]; + if (msv.isOldVer && msv.brief.isExt) + { + Bsprintf(tempbuf, "Resume game from sequence point:\n\"%s\"", msv.brief.name); + Menu_DrawVerifyPrompt(origin.x, origin.y, tempbuf, 2); + } + else if (msv.isOldVer) + { +#if 1 + mgametextcenter(origin.x, origin.y + (90<<16), "You're not supposed to be here."); +#else + Bsprintf(tempbuf, "Start new game:\n%s / %s" + , g_mapInfo[(ud.volume_number*MAXLEVELS) + ud.level_number].name, g_skillNames[ud.player_skill-1]); + Menu_DrawVerifyPrompt(origin.x, origin.y, tempbuf, 2); +#endif + } + else + { + Bsprintf(tempbuf, "Load game:\n\"%s\"", msv.brief.name); + Menu_DrawVerifyPrompt(origin.x, origin.y, tempbuf, 2); + } + break; + } + + case MENU_SAVEVERIFY: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Overwrite previous saved game?"); + break; + + case MENU_LOADDELVERIFY: + case MENU_SAVEDELVERIFY: + { + videoFadeToBlack(1); + menusave_t & msv = cm == MENU_LOADDELVERIFY ? g_menusaves[M_LOAD.currentEntry] : g_menusaves[M_SAVE.currentEntry-1]; + Bsprintf(tempbuf, "Delete saved game:\n\"%s\"?", msv.brief.name); + Menu_DrawVerifyPrompt(origin.x, origin.y, tempbuf, 2); + break; + } + + case MENU_NEWVERIFY: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Abort this game?"); + break; + + case MENU_COLCORRRESETVERIFY: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Reset color correction to defaults?"); + break; + case MENU_KEYSRESETVERIFY: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Reset keys to defaults?"); + break; + case MENU_KEYSCLASSICVERIFY: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Reset keys to classic defaults?"); + break; + case MENU_JOYSTANDARDVERIFY: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Reset gamepad to standard layout?"); + break; + case MENU_JOYPROVERIFY: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Reset gamepad to pro layout?"); + break; + case MENU_JOYCLEARVERIFY: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Clear all gamepad settings?"); + break; + + case MENU_QUIT: + case MENU_QUIT_INGAME: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "Are you sure you want to quit?"); + break; + + case MENU_QUITTOTITLE: + videoFadeToBlack(1); + Menu_DrawVerifyPrompt(origin.x, origin.y, "End game and return to title screen?"); + break; + + case MENU_NETWAITMASTER: + G_DrawFrags(); + mgametextcenter(origin.x, origin.y + (50<<16), "Waiting for master\n" + "to select level"); + break; + + case MENU_NETWAITVOTES: + G_DrawFrags(); + mgametextcenter(origin.x, origin.y + (90<<16), "Waiting for votes"); + break; + +#ifndef EDUKE32_STANDALONE + case MENU_BUYDUKE: + mgametextcenter(origin.x, origin.y + (33<<16), "You are playing the shareware\n" + "version of Duke Nukem 3D. While\n" + "this version is really cool, you\n" + "are missing over 75% of the total\n" + "game, along with other great extras\n" + "which you'll get when you order\n" + "the complete version and get\n" + "the final three episodes."); + + mgametextcenter(origin.x, origin.y + ((148+16)<<16), "Press any key or button..."); + break; +#endif + case MENU_CREDITS: + case MENU_CREDITS2: + case MENU_CREDITS3: +#ifndef EDUKE32_STANDALONE + if (!VOLUMEALL || !PLUTOPAK) + { + int32_t m; + switch (cm) + { + case MENU_CREDITS: + m = origin.x + (20<<16); + l = origin.y + (33<<16); + + shadowminitext(m, l, "Original Concept", 12); l += 7<<16; + shadowminitext(m, l, "Todd Replogle and Allen H. Blum III", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Produced & Directed By", 12); l += 7<<16; + shadowminitext(m, l, "Greg Malone", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Executive Producer", 12); l += 7<<16; + shadowminitext(m, l, "George Broussard", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "BUILD Engine", 12); l += 7<<16; + shadowminitext(m, l, "Ken Silverman", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Game Programming", 12); l += 7<<16; + shadowminitext(m, l, "Todd Replogle", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "3D Engine/Tools/Net", 12); l += 7<<16; + shadowminitext(m, l, "Ken Silverman", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Network Layer/Setup Program", 12); l += 7<<16; + shadowminitext(m, l, "Mark Dochtermann", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Map Design", 12); l += 7<<16; + shadowminitext(m, l, "Allen H. Blum III", 12); l += 7<<16; + shadowminitext(m, l, "Richard Gray", 12); l += 7<<16; + + m = origin.x + (180<<16); + l = origin.y + (33<<16); + + shadowminitext(m, l, "3D Modeling", 12); l += 7<<16; + shadowminitext(m, l, "Chuck Jones", 12); l += 7<<16; + shadowminitext(m, l, "Sapphire Corporation", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Artwork", 12); l += 7<<16; + shadowminitext(m, l, "Dirk Jones, Stephen Hornback", 12); l += 7<<16; + shadowminitext(m, l, "James Storey, David Demaret", 12); l += 7<<16; + shadowminitext(m, l, "Douglas R. Wood", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Sound Engine", 12); l += 7<<16; + shadowminitext(m, l, "Jim Dose", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Sound & Music Development", 12); l += 7<<16; + shadowminitext(m, l, "Robert Prince", 12); l += 7<<16; + shadowminitext(m, l, "Lee Jackson", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Voice Talent", 12); l += 7<<16; + shadowminitext(m, l, "Lani Minella - Voice Producer", 12); l += 7<<16; + shadowminitext(m, l, "Jon St. John as \"Duke Nukem\"", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Graphic Design", 12); l += 7<<16; + shadowminitext(m, l, "Packaging, Manual, Ads", 12); l += 7<<16; + shadowminitext(m, l, "Robert M. Atkins", 12); l += 7<<16; + shadowminitext(m, l, "Michael Hadwin", 12); l += 7<<16; + break; + + case MENU_CREDITS2: + m = origin.x + (20<<16); + l = origin.y + (33<<16); + + shadowminitext(m, l, "Special Thanks To", 12); l += 7<<16; + shadowminitext(m, l, "Steven Blackburn, Tom Hall", 12); l += 7<<16; + shadowminitext(m, l, "Scott Miller, Joe Siegler", 12); l += 7<<16; + shadowminitext(m, l, "Terry Nagy, Colleen Compton", 12); l += 7<<16; + shadowminitext(m, l, "HASH, Inc., FormGen, Inc.", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "The 3D Realms Beta Testers", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Nathan Anderson, Wayne Benner", 12); l += 7<<16; + shadowminitext(m, l, "Glenn Brensinger, Rob Brown", 12); l += 7<<16; + shadowminitext(m, l, "Erik Harris, Ken Heckbert", 12); l += 7<<16; + shadowminitext(m, l, "Terry Herrin, Greg Hively", 12); l += 7<<16; + shadowminitext(m, l, "Hank Leukart, Eric Baker", 12); l += 7<<16; + shadowminitext(m, l, "Jeff Rausch, Kelly Rogers", 12); l += 7<<16; + shadowminitext(m, l, "Mike Duncan, Doug Howell", 12); l += 7<<16; + shadowminitext(m, l, "Bill Blair", 12); l += 7<<16; + + m = origin.x + (160<<16); + l = origin.y + (33<<16); + + shadowminitext(m, l, "Company Product Support", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "The following companies were cool", 12); l += 7<<16; + shadowminitext(m, l, "enough to give us lots of stuff", 12); l += 7<<16; + shadowminitext(m, l, "during the making of Duke Nukem 3D.", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Altec Lansing Multimedia", 12); l += 7<<16; + shadowminitext(m, l, "for tons of speakers and the", 12); l += 7<<16; + shadowminitext(m, l, "THX-licensed sound system.", 12); l += 7<<16; + shadowminitext(m, l, "For info call 1-800-548-0620", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Creative Labs, Inc.", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Thanks for the hardware, guys.", 12); l += 7<<16; + break; + + case MENU_CREDITS3: + mgametextcenter(origin.x, origin.y + (50<<16), "Duke Nukem 3D is a trademark of\n" + "3D Realms Entertainment" + "\n" + "Duke Nukem 3D\n" + "(C) 1996 3D Realms Entertainment"); + +#if !defined(EDUKE32_ANDROID_MENU) && !defined(EDUKE32_STANDALONE) + if (VOLUMEONE) + { + mgametextcenter(origin.x, origin.y + (106<<16), "Please read LICENSE.DOC for shareware\n" + "distribution grants and restrictions."); + } +#endif + mgametextcenter(origin.x, origin.y + ((VOLUMEONE?134:115)<<16), "Made in Dallas, Texas USA"); + break; + } + } + break; +#endif + case MENU_CREDITS4: // JBF 20031220 + { +#define MENU_YOFFSET 40 +#define MENU_INCREMENT(x) (oy += ((x) << 16)) // maybe this should have been MENU_EXCREMENT instead + + int32_t oy = origin.y; + + mgametextcenter(origin.x, MENU_INCREMENT(MENU_YOFFSET), "Developers"); + creditsminitext(origin.x + (160 << 16), MENU_INCREMENT(11), "Richard \"TerminX\" Gobeille", 8); + creditsminitext(origin.x + (160 << 16), MENU_INCREMENT(7), "Evan \"Hendricks266\" Ramos", 8); + creditsminitext(origin.x + (160 << 16), MENU_INCREMENT(7), "Alex \"pogokeen\" Dawson", 8); + + mgametextcenter(origin.x, MENU_INCREMENT(11), "Retired developers"); + creditsminitext(origin.x + (160 << 16), MENU_INCREMENT(11), "Pierre-Loup \"Plagman\" Griffais", 8); + creditsminitext(origin.x + (160 << 16), MENU_INCREMENT(7), "Philipp \"Helixhorned\" Kutin", 8); + + mgametextcenter(origin.x, MENU_INCREMENT(11), "Special thanks to"); + creditsminitext(origin.x + (160 << 16), MENU_INCREMENT(11), "Jonathon \"JonoF\" Fowler", 8); + + mgametextcenter(origin.x, MENU_INCREMENT(11), "Uses BUILD Engine technology by"); + creditsminitext(origin.x + (160 << 16), MENU_INCREMENT(11), "Ken \"Awesoken\" Silverman", 8); + +#undef MENU_INCREMENT +#undef MENU_YOFFSET + } + break; + + case MENU_CREDITS5: + l = 7; + + mgametextcenter(origin.x, origin.y + ((38-l)<<16), "License and Other Contributors"); + { + static const char *header[] = + { + "This program is distributed under the terms of the", + "GNU General Public License version 2 as published by the", + "Free Software Foundation. See gpl-2.0.txt for details.", + "BUILD engine technology available under license. See buildlic.txt.", + nullptr, + "The EDuke32 team thanks the following people for their contributions:", + nullptr, + }; + static const char *body[] = + { + "Alexey Skrybykin", // Nuke.YKT - Polymost fixes + "Bioman", // GTK work, APT repository and package upkeep + "Brandon Bergren", // "Bdragon" - tiles.cfg + "Charlie Honig", // "CONAN" - showview command + "Dan Gaskill", // "DeeperThought" - testing + "David Koenig", // "Bargle" - Merged a couple of things from duke3d_w32 + "Ed Coolidge", // Mapster32 improvements + "Emile Belanger", // original Android work + "Fox", // various patches + "Hunter_rus", // tons of stuff + "James Bentler", // Mapster32 improvements + "Jasper Foreman", // netcode contributions + "Javier Martinez", // "Malone3D" - EDuke 2.1.1 components + "Jeff Hart", // website graphics + "Jonathan Strander", // "Mblackwell" - testing and feature speccing + "Jordon Moss", // "Striker" - various patches, OldMP work + "Jose del Castillo", // "Renegado" - EDuke 2.1.1 components + "Lachlan McDonald", // official EDuke32 icon + "LSDNinja", // OS X help and testing + "Marcus Herbert", // "rhoenie" - OS X compatibility work + "Matthew Palmer", // "Usurper" - testing and eduke32.com domain + "Matt Saettler", // original DOS EDuke/WW2GI enhancements + "NY00123", // Linux / SDL usability patches + "Ozkan Sezer", // SDL/GTK version checking improvements + "Peter Green", // "Plugwash" - dynamic remapping, custom gametypes + "Peter Veenstra", // "Qbix" - port to 64-bit + "Robin Green", // CON array support + "Ryan Gordon", // "icculus" - icculus.org Duke3D port sound code + "Stephen Anthony", // early 64-bit porting work + "tueidj", // Wii port + }; + EDUKE32_STATIC_ASSERT(ARRAY_SIZE(body) % 3 == 0); + static const char *footer[] = + { + nullptr, + "Visit eduke32.com for news and updates", + }; + + static constexpr int header_numlines = ARRAY_SIZE(header); + static constexpr int body_numlines = ARRAY_SIZE(body); + static constexpr int footer_numlines = ARRAY_SIZE(footer); + + static constexpr int CCOLUMNS = 3; + static constexpr int CCOLXBUF = 20; + + int c; + i = 0; + for (c = 0; c < header_numlines; c++) + if (header[c]) + creditsminitext(origin.x + (160<<16), origin.y + ((17+10+10+8+4+(c*7)-l)<<16), header[c], 8); + i += c; + for (c = 0; c < body_numlines; c++) + if (body[c]) + creditsminitext(origin.x + ((CCOLXBUF+((320-CCOLXBUF*2)/(CCOLUMNS*2)) +((320-CCOLXBUF*2)/CCOLUMNS)*(c/(body_numlines/CCOLUMNS)))<<16), origin.y + ((17+10+10+8+4+((c%(body_numlines/CCOLUMNS))*7)+(i*7)-l)<<16), body[c], 8); + i += c/CCOLUMNS; + for (c = 0; c < footer_numlines; c++) + if (footer[c]) + creditsminitext(origin.x + (160<<16), origin.y + ((17+10+10+8+4+(c*7)+(i*7)-l)<<16), footer[c], 8); + } + + break; + + default: + break; + } +} + + +static void Menu_ReadSaveGameHeaders(); + +static void Menu_LoadReadHeaders() +{ + Menu_ReadSaveGameHeaders(); + + for (int i = 0; i < g_nummenusaves; ++i) + { + menusave_t const & msv = g_menusaves[i]; + // MenuEntry_LookDisabledOnCondition(&ME_LOAD[i], msv.isOldVer && msv.brief.isExt); + MenuEntry_DisableOnCondition(&ME_LOAD[i], msv.isOldVer && !msv.brief.isExt); + } +} + +static void Menu_SaveReadHeaders() +{ + Menu_ReadSaveGameHeaders(); + + for (int i = 0; i < g_nummenusaves; ++i) + { + menusave_t const & msv = g_menusaves[i]; + MenuEntry_LookDisabledOnCondition(&ME_SAVE[i], msv.isOldVer && !msv.brief.isExt); + } +} + +static void Menu_PreInput(MenuEntry_t *entry) +{ + switch (g_currentMenu) + { + + case MENU_KEYBOARDKEYS: + if (KB_KeyPressed(sc_Delete)) + { + auto column = (MenuCustom2Col_t*)entry->entry; + char key[2]; + key[0] = ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][0]; + key[1] = ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][1]; + *column->column[M_KEYBOARDKEYS.currentColumn] = 0xff; + CONFIG_MapKey(M_KEYBOARDKEYS.currentEntry, ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][0], key[0], ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][1], key[1]); + S_PlaySound(KICK_HIT); + KB_ClearKeyDown(sc_Delete); + } + break; + + case MENU_LOAD: + if (KB_KeyPressed(sc_Delete)) + { + KB_ClearKeyDown(sc_Delete); + if (M_LOAD.currentEntry < g_nummenusaves) + Menu_Change(MENU_LOADDELVERIFY); + } + break; + case MENU_SAVE: + if (KB_KeyPressed(sc_Delete)) + { + KB_ClearKeyDown(sc_Delete); + if (0 < M_SAVE.currentEntry && M_SAVE.currentEntry <= (int32_t)g_nummenusaves) + Menu_Change(MENU_SAVEDELVERIFY); + } + break; + + default: + break; + } +} + +static void Menu_PreOptionListDraw(MenuEntry_t *entry, const vec2_t origin) +{ + switch (g_currentMenu) + { + case MENU_MOUSEBTNS: + case MENU_MOUSEADVANCED: + case MENU_JOYSTICKBTNS: + case MENU_JOYSTICKAXIS: + mgametextcenter(origin.x, origin.y + (31<<16), "Select a function to assign"); + + Bsprintf(tempbuf, "to %s", entry->name); + + mgametextcenter(origin.x, origin.y + ((31+9)<<16), tempbuf); + + mgametextcenter(origin.x, origin.y + (161<<16), "Press \"Escape\" To Cancel"); + break; + } +} + +static int32_t Menu_PreCustom2ColScreen(MenuEntry_t *entry) +{ + if (g_currentMenu == MENU_KEYBOARDKEYS) + { + auto column = (MenuCustom2Col_t*)entry->entry; + + int32_t sc = KB_GetLastScanCode(); + if (sc != sc_None) + { + char key[2]; + key[0] = ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][0]; + key[1] = ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][1]; + + S_PlaySound(PISTOL_BODYHIT); + + *column->column[M_KEYBOARDKEYS.currentColumn] = KB_GetLastScanCode(); + + CONFIG_MapKey(M_KEYBOARDKEYS.currentEntry, ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][0], key[0], ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][1], key[1]); + + KB_ClearKeyDown(sc); + + return -1; + } + } + + return 0; +} + +static void Menu_PreCustom2ColScreenDraw(MenuEntry_t *entry, const vec2_t origin) +{ + if (g_currentMenu == MENU_KEYBOARDKEYS) + { + Bsprintf(tempbuf, "Press the key to assign as\n" + "%s for \"%s\"\n" + "\n" + "Press \"Escape\" To Cancel" + , M_KEYBOARDKEYS.currentColumn?"secondary":"primary", entry->name); + mgametextcenter(origin.x, origin.y + (90<<16), tempbuf); + } +} + +static void Menu_EntryFocus(/*MenuEntry_t *entry*/) +{ + switch (g_currentMenu) + { + case MENU_LOAD: + if (M_LOAD.currentEntry < (int32_t)g_nummenusaves) + { + savebrief_t & sv = g_menusaves[M_LOAD.currentEntry].brief; + if (sv.isValid()) + G_LoadSaveHeaderNew(sv.path, &savehead); + } + break; + + case MENU_SAVE: + if (0 < M_SAVE.currentEntry && M_SAVE.currentEntry <= (int32_t)g_nummenusaves) + { + savebrief_t & sv = g_menusaves[M_SAVE.currentEntry-1].brief; + if (sv.isValid()) + G_LoadSaveHeaderNew(sv.path, &savehead); + } + break; + + default: + break; + } +} + +static void Menu_StartGameWithoutSkill(void) +{ + ud.m_player_skill = M_SKILL.currentEntry+1; + + ud.skill_voice = S_PlaySound(PISTOL_BODYHIT); + + ud.m_respawn_monsters = 0; + + ud.m_monsters_off = ud.monsters_off = 0; + + ud.m_respawn_items = 0; + ud.m_respawn_inventory = 0; + + ud.multimode = 1; + + G_NewGame_EnterLevel(); +} + +static void Menu_DoCheat(int32_t cheatID) +{ + if (numplayers != 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME)) + return; + + osdcmd_cheatsinfo_stat.cheatnum = cheatID; +} + +static int32_t Menu_Cheat_Warp(char const * const numbers) +{ + if (numplayers != 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME)) + return 0; + + if (numbers == NULL || !numbers[0] || !numbers[1] || (VOLUMEALL && !numbers[2])) + return 1; + + if (VOLUMEALL) + { + osdcmd_cheatsinfo_stat.volume = numbers[0] - '0'; + osdcmd_cheatsinfo_stat.level = (numbers[1] - '0')*10+(numbers[2]-'0'); + } + else + { + osdcmd_cheatsinfo_stat.volume = numbers[0] - '0'; + osdcmd_cheatsinfo_stat.level = numbers[1] - '0'; + } + + osdcmd_cheatsinfo_stat.volume--; + osdcmd_cheatsinfo_stat.level--; + + if ((VOLUMEONE && osdcmd_cheatsinfo_stat.volume > 0) || osdcmd_cheatsinfo_stat.volume > g_volumeCnt-1 || + osdcmd_cheatsinfo_stat.level >= MAXLEVELS || g_mapInfo[osdcmd_cheatsinfo_stat.volume *MAXLEVELS+osdcmd_cheatsinfo_stat.level].filename == NULL) + return 1; + + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_SCOTTY; + + return 0; +} + +static int32_t Menu_Cheat_Skill(char const * const number) +{ + if (numplayers != 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME)) + return 0; + + if (number == NULL || !number[0]) + return 1; + + osdcmd_cheatsinfo_stat.volume = number[0] - '1'; + + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_SKILL; + + return 0; +} + +/* +Functions where a "newValue" or similar is passed are run *before* the linked variable is actually changed. +That way you can compare the new and old values and potentially block the change. +*/ +static void Menu_EntryLinkActivate(MenuEntry_t *entry) +{ + switch (g_currentMenu) + { + case MENU_EPISODE: + if (entry != &ME_EPISODE_USERMAP) + { + ud.m_volume_number = M_EPISODE.currentEntry; + ud.m_level_number = 0; + + if (g_skillCnt == 0) + Menu_StartGameWithoutSkill(); + } + break; + + case MENU_NEWGAMECUSTOM: + ud.returnvar[0] = -1; + VM_OnEventWithReturn(EVENT_NEWGAMECUSTOM, -1, myconnectindex, M_NEWGAMECUSTOM.currentEntry); + break; + + case MENU_NEWGAMECUSTOMSUB: + ud.returnvar[0] = M_NEWGAMECUSTOMSUB.currentEntry; + ud.returnvar[1] = -1; + VM_OnEventWithReturn(EVENT_NEWGAMECUSTOM, -1, myconnectindex, M_NEWGAMECUSTOM.currentEntry); + break; + + case MENU_SKILL: + { + int32_t skillsound = PISTOL_BODYHIT; + + switch (M_SKILL.currentEntry) + { + case 0: + skillsound = JIBBED_ACTOR6; + break; + case 1: + skillsound = BONUS_SPEECH1; + break; + case 2: + skillsound = DUKE_GETWEAPON2; + break; + case 3: + skillsound = JIBBED_ACTOR5; + break; + } + + ud.m_player_skill = M_SKILL.currentEntry+1; + + ud.skill_voice = S_PlaySound(skillsound); + + if (M_SKILL.currentEntry == 3) ud.m_respawn_monsters = 1; + else ud.m_respawn_monsters = 0; + + ud.m_monsters_off = ud.monsters_off = 0; + + ud.m_respawn_items = 0; + ud.m_respawn_inventory = 0; + + ud.multimode = 1; + + G_NewGame_EnterLevel(); + break; + } + + case MENU_JOYSTICKAXES: + M_JOYSTICKAXIS.title = joyGetName(0, M_JOYSTICKAXES.currentEntry); + MEO_JOYSTICKAXIS_ANALOG.data = &ud.config.JoystickAnalogueAxes[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_SCALE.variable = &ud.config.JoystickAnalogueScale[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_INVERT.data = &ud.config.JoystickAnalogueInvert[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_DEAD.variable = &ud.config.JoystickAnalogueDead[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_SATU.variable = &ud.config.JoystickAnalogueSaturate[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_DIGITALNEGATIVE.data = &ud.config.JoystickDigitalFunctions[M_JOYSTICKAXES.currentEntry][0]; + MEO_JOYSTICKAXIS_DIGITALPOSITIVE.data = &ud.config.JoystickDigitalFunctions[M_JOYSTICKAXES.currentEntry][1]; + break; + + case MENU_CHEATS: + { + const int32_t cheatFuncID = M_CHEATS.currentEntry - 1; + switch (cheatFuncID) + { + case -1: + case CHEATFUNC_WARP: + case CHEATFUNC_SKILL: + break; + default: + Menu_DoCheat(CheatFunctionIDs[cheatFuncID]); + break; + } + break; + } + + default: + break; + } + + if (entry == &ME_VIDEOSETUP_APPLY) + { + resolution_t p = { xres, yres, fullscreen, bpp, 0 }; + int32_t prend = videoGetRenderMode(); + int32_t pvsync = vsync; + int pborderless = r_borderless; + + resolution_t n = { resolution[newresolution].xdim, resolution[newresolution].ydim, + (resolution[newresolution].flags & RES_FS) ? newfullscreen : 0, + (newrendermode == REND_CLASSIC) ? 8 : resolution[newresolution].bppmax, 0 }; + + if (r_borderless != newborderless) + videoResetMode(); + + r_borderless = newborderless; + + if (videoSetGameMode(n.flags, n.xdim, n.ydim, n.bppmax, upscalefactor) < 0) + { + r_borderless = pborderless; + + if (videoSetGameMode(p.flags, p.xdim, p.ydim, p.bppmax, upscalefactor) < 0) + { + videoSetRenderMode(prend); + G_GameExit("Failed restoring old video mode."); + } + else + { + onvideomodechange(p.bppmax > 8); + vsync = videoSetVsync(pvsync); + } + } + else + { + videoSetRenderMode(newrendermode); + vsync = videoSetVsync(newvsync); + onvideomodechange(n.bppmax > 8); + } + + g_restorePalette = -1; + G_UpdateScreenArea(); + ud.setup.fullscreen = fullscreen; + ud.setup.xdim = xres; + ud.setup.ydim = yres; + ud.setup.bpp = bpp; + } + else if (entry == &ME_SOUND_RESTART) + { + ud.config.MixRate = soundrate; + ud.config.NumVoices = soundvoices; + + S_SoundShutdown(); + S_MusicShutdown(); + + S_MusicStartup(); + S_SoundStartup(); + + FX_StopAllSounds(); + S_ClearSoundLocks(); + + if (ud.config.MusicToggle) + S_RestartMusic(); + } + else if (entry == &ME_SAVESETUP_CLEANUP) + { + g_oldSaveCnt = G_CountOldSaves(); + Menu_Change(MENU_SAVECLEANVERIFY); + } +#ifdef EDUKE32_STANDALONE + else if (entry == &ME_SAVESETUP_RESETSTATS) + Menu_Change(MENU_RESETSTATSVERIFY); +#endif + else if (entry == &ME_NETHOST_LAUNCH) + { + // master does whatever it wants + if (g_netServer) + { + Net_FillNewGame(&pendingnewgame, 1); + Net_StartNewGame(); + Net_SendNewGame(1, NULL); + } + else if (voting == -1) + { + Net_SendMapVoteInitiate(); + Menu_Change(MENU_NETWAITVOTES); + } + } +} + +static int32_t Menu_EntryOptionModify(MenuEntry_t *entry, int32_t newOption) +{ + int32_t x; + auto ps = g_player[myconnectindex].ps; + + if (entry == &ME_GAMESETUP_DEMOREC) + { + if ((ps->gm&MODE_GAME)) + G_CloseDemoWrite(); + } +#ifdef _WIN32 + else if (entry == &ME_GAMESETUP_UPDATES) + ud.config.LastUpdateCheck = 0; +#endif + else if (entry == &ME_GAMESETUP_WEAPSWITCH_PICKUP) + { + ud.weaponswitch &= ~(1|4); + switch (newOption) + { + case 2: + ud.weaponswitch |= 4; + fallthrough__; + case 1: + ud.weaponswitch |= 1; + break; + default: + break; + } + } + else if (entry == &ME_SCREENSETUP_SCREENSIZE) + G_SetViewportShrink((newOption - vpsize) * 4); + else if (entry == &ME_SOUND) + { + if (newOption == 0) + { + FX_StopAllSounds(); + S_ClearSoundLocks(); + } + } + else if (entry == &ME_SOUND_MUSIC) + { + ud.config.MusicToggle = newOption; + + if (newOption == 0) + S_PauseMusic(true); + else + { + S_RestartMusic(); + S_PauseMusic(false); + } + } + else if (entry == &ME_SOUND_DUKETALK) + ud.config.VoiceToggle = (ud.config.VoiceToggle&~1) | newOption; + else if (entry == &ME_MOUSESETUP_SMOOTH) + CONTROL_SmoothMouse = ud.config.SmoothInput; + else if (entry == &ME_JOYSTICK_ENABLE) + { + if (newOption) + CONTROL_ScanForControllers(); + CONTROL_JoystickEnabled = (newOption && CONTROL_JoyPresent); + } + else if (entry == &ME_JOYSTICKAXIS_ANALOG) + CONTROL_MapAnalogAxis(M_JOYSTICKAXES.currentEntry, newOption, controldevice_joystick); + else if (entry == &ME_JOYSTICKAXIS_INVERT) + CONTROL_SetAnalogAxisInvert(M_JOYSTICKAXES.currentEntry, newOption, controldevice_joystick); + else if (entry == &ME_NETOPTIONS_EPISODE) + { + if (newOption < g_volumeCnt) + ud.m_volume_number = newOption; + } + else if (entry == &ME_NETOPTIONS_MONSTERS) + { + ud.m_monsters_off = (newOption == g_skillCnt); + if (newOption < g_skillCnt) + ud.m_player_skill = newOption; + } + else if (entry == &ME_ADULTMODE) + { + if (newOption) + { + for (x=0; x= 0) + wall[animwall[x].wallnum].picnum = wall[animwall[x].wallnum].extra; +#endif + } + else + { + Menu_Change(MENU_ADULTPASSWORD); + return -1; + } + } + } + else if (entry == &ME_VIDEOSETUP_FRAMELIMIT) + g_frameDelay = calcFrameDelay(newOption + r_maxfpsoffset); + + switch (g_currentMenu) + { + case MENU_MOUSEBTNS: + CONTROL_MapButton(newOption, MenuMouseDataIndex[M_MOUSEBTNS.currentEntry][0], MenuMouseDataIndex[M_MOUSEBTNS.currentEntry][1], controldevice_mouse); + CONTROL_FreeMouseBind(MenuMouseDataIndex[M_MOUSEBTNS.currentEntry][0]); + break; + case MENU_JOYSTICKBTNS: + CONTROL_MapButton(newOption, M_JOYSTICKBTNS.currentEntry>>1, M_JOYSTICKBTNS.currentEntry&1, controldevice_joystick); + break; + case MENU_JOYSTICKAXIS: + { + for (int i = 0; i < ARRAY_SSIZE(MEL_INTERNAL_JOYSTICKAXIS_DIGITAL); i++) + if (entry == MEL_INTERNAL_JOYSTICKAXIS_DIGITAL[i]) + CONTROL_MapDigitalAxis(M_JOYSTICKAXES.currentEntry, newOption, i&1, controldevice_joystick); + } + break; + } + + return 0; +} + +static void Menu_EntryOptionDidModify(MenuEntry_t *entry) +{ +#ifdef USE_OPENGL + int domodechange = 0; +#endif + + if (entry == &ME_GAMESETUP_AIM_AUTO || + entry == &ME_GAMESETUP_WEAPSWITCH_PICKUP || + entry == &ME_PLAYER_NAME || + entry == &ME_PLAYER_COLOR || + entry == &ME_PLAYER_TEAM) + G_UpdatePlayerFromMenu(); + else if (entry == &ME_DISPLAYSETUP_UPSCALING) + { + if (in3dmode()) + { + videoSetGameMode(fullscreen, xres, yres, bpp, ud.detail); + } + } +#ifdef USE_OPENGL +#ifndef EDUKE32_STANDALONE +#ifdef TEXFILTER_MENU_OPTIONS + else if (entry == &ME_DISPLAYSETUP_ANISOTROPY || entry == &ME_DISPLAYSETUP_TEXFILTER) + gltexapplyprops(); +#endif +#endif + else if (entry == &ME_RENDERERSETUP_TEXQUALITY) + { + texcache_invalidate(); + r_downsizevar = r_downsize; + domodechange = 1; + } +#ifdef POLYMER + else if (entry == &ME_POLYMER_LIGHTS || + entry == &ME_POLYMER_LIGHTPASSES || + entry == &ME_POLYMER_SHADOWCOUNT) + domodechange = 1; +#endif + + if (domodechange) + { + videoResetMode(); + if (videoSetGameMode(fullscreen, xres, yres, bpp, upscalefactor)) + OSD_Printf("restartvid: Reset failed...\n"); + onvideomodechange(ud.setup.bpp>8); + G_RefreshLights(); + } +#endif +} + +static void Menu_Custom2ColScreen(/*MenuEntry_t *entry*/) +{ + if (g_currentMenu == MENU_KEYBOARDKEYS) + { + KB_FlushKeyboardQueue(); + KB_ClearLastScanCode(); + } +} + +static int32_t Menu_EntryRangeInt32Modify(MenuEntry_t *entry, int32_t newValue) +{ + if (entry == &ME_SCREENSETUP_SCREENSIZE) + G_SetViewportShrink((newValue - vpsize) * 4); + else if (entry == &ME_SCREENSETUP_SBARSIZE) + G_SetStatusBarScale(newValue); + else if (entry == &ME_SOUND_VOLUME_FX) + FX_SetVolume(newValue); + else if (entry == &ME_SOUND_VOLUME_MUSIC) + S_MusicVolume(newValue); + else if (entry == &ME_MOUSEADVANCED_SCALEX) + CONTROL_SetAnalogAxisScale(0, newValue, controldevice_mouse); + else if (entry == &ME_MOUSEADVANCED_SCALEY) + CONTROL_SetAnalogAxisScale(1, newValue, controldevice_mouse); + else if (entry == &ME_JOYSTICKAXIS_SCALE) + CONTROL_SetAnalogAxisScale(M_JOYSTICKAXES.currentEntry, newValue, controldevice_joystick); + else if (entry == &ME_JOYSTICKAXIS_DEAD) + joySetDeadZone(M_JOYSTICKAXES.currentEntry, newValue, *MEO_JOYSTICKAXIS_SATU.variable); + else if (entry == &ME_JOYSTICKAXIS_SATU) + joySetDeadZone(M_JOYSTICKAXES.currentEntry, *MEO_JOYSTICKAXIS_DEAD.variable, newValue); + else if (entry == &ME_VIDEOSETUP_FRAMELIMITOFFSET) + g_frameDelay = calcFrameDelay(r_maxfps + newValue); + + return 0; +} + +static int32_t Menu_EntryRangeFloatModify(MenuEntry_t *entry, float newValue) +{ +#ifndef EDUKE32_SIMPLE_MENU + if (entry == &ME_COLCORR_AMBIENT) + r_ambientlightrecip = 1.f/newValue; +#else + UNREFERENCED_PARAMETER(entry); + UNREFERENCED_PARAMETER(newValue); +#endif + + return 0; +} + +static int32_t Menu_EntryRangeFloatDidModify(MenuEntry_t *entry) +{ + if (entry == &ME_COLCORR_GAMMA) + { + ud.brightness = GAMMA_CALC<<2; + videoSetPalette(ud.brightness>>2, g_player[myconnectindex].ps->palette, 0); + } + else if (entry == &ME_COLCORR_CONTRAST || entry == &ME_COLCORR_BRIGHTNESS) + { + videoSetPalette(ud.brightness>>2, g_player[myconnectindex].ps->palette, 0); + } + + return 0; +} + +#ifdef MENU_ENABLE_RANGEDOUBLE +static int32_t Menu_EntryRangeDoubleModify(void /*MenuEntry_t *entry, double newValue*/) +{ + + return 0; +} +#endif + +static uint32_t save_xxh = 0; + +static void Menu_EntryStringActivate(/*MenuEntry_t *entry*/) +{ + switch (g_currentMenu) + { + case MENU_SAVE: + if (M_SAVE.currentEntry > 0) + { + savebrief_t & sv = g_menusaves[M_SAVE.currentEntry-1].brief; + if (!save_xxh) + save_xxh = XXH32((uint8_t *)sv.name, MAXSAVEGAMENAME, 0xDEADBEEF); + if (sv.isValid()) + Menu_Change(MENU_SAVEVERIFY); + } + else + { + ME_SAVE_NEW.name = nullptr; + save_xxh = 0; + } + break; + + default: + break; + } +} + +static int32_t Menu_EntryStringSubmit(/*MenuEntry_t *entry, */char *input) +{ + int32_t returnvar = 0; + + switch (g_currentMenu) + { + case MENU_SAVE: + { + savebrief_t & sv = g_lastusersave = M_SAVE.currentEntry == 0 ? savebrief_t{input} : g_menusaves[M_SAVE.currentEntry-1].brief; + + // dirty hack... char 127 in last position indicates an auto-filled name +#ifdef __ANDROID__ + if (1) +#else + if (input[0] == 0 || (sv.name[MAXSAVEGAMENAME] == 127 && + strncmp(sv.name, input, MAXSAVEGAMENAME) == 0 && + save_xxh == XXH32((uint8_t *)sv.name, MAXSAVEGAMENAME, 0xDEADBEEF))) +#endif + { + strncpy(sv.name, g_mapInfo[ud.volume_number * MAXLEVELS + ud.level_number].name, MAXSAVEGAMENAME); + sv.name[MAXSAVEGAMENAME] = 127; + returnvar = -1; + } + else + { + strncpy(sv.name, input, MAXSAVEGAMENAME); + sv.name[MAXSAVEGAMENAME] = 0; + } + + G_SavePlayerMaybeMulti(sv); + + g_quickload = &sv; + g_player[myconnectindex].ps->gm = MODE_GAME; + + Menu_Change(MENU_CLOSE); + save_xxh = 0; + break; + } + + default: + break; + } + + return returnvar; +} + +static void Menu_EntryStringCancel(/*MenuEntry_t *entry*/) +{ + switch (g_currentMenu) + { + case MENU_SAVE: + save_xxh = 0; + ME_SAVE_NEW.name = s_NewSaveGame; + break; + + default: + break; + } +} + +/* +This is polled when the menu code is populating the screen but for some reason doesn't have the data. +*/ +static int32_t Menu_EntryOptionSource(MenuEntry_t *entry, int32_t currentValue) +{ + if (entry == &ME_GAMESETUP_WEAPSWITCH_PICKUP) + return (ud.weaponswitch & 1) ? ((ud.weaponswitch & 4) ? 2 : 1) : 0; + else if (entry == &ME_SOUND_DUKETALK) + return ud.config.VoiceToggle & 1; + else if (entry == &ME_NETOPTIONS_MONSTERS) + return (ud.m_monsters_off ? g_skillCnt : ud.m_player_skill); + + return currentValue; +} + +static void Menu_Verify(int32_t input) +{ + switch (g_currentMenu) + { + case MENU_SAVECLEANVERIFY: + if (input) + { + G_DeleteOldSaves(); + } + break; + +#ifdef EDUKE32_STANDALONE + case MENU_RESETSTATSVERIFY: + if (input) + { + communityapiResetStats(); + VM_OnEvent(EVENT_CAPIR); + } + break; +#endif + + case MENU_RESETPLAYER: + switch (input) + { + default: + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); + FX_StopAllSounds(); + + if (G_LoadPlayerMaybeMulti(*g_quickload) == 0) + break; + + // error state, consider as a no instead of yes + g_quickload->reset(); + + fallthrough__; + case 0: + if (sprite[g_player[myconnectindex].ps->i].extra <= 0) + { + if (G_EnterLevel(MODE_GAME)) G_BackToMenu(); + return; + } + + Menu_Change(MENU_CLOSE); + break; + } + break; + + case MENU_LOADVERIFY: + if (input) + { + menusave_t & msv = g_menusaves[M_LOAD.currentEntry]; + savebrief_t & sv = msv.brief; + + if (strcmp(sv.path, g_lastusersave.path) != 0) + { + g_freshload = sv; + g_lastusersave.reset(); + g_lastautosave.reset(); + g_quickload = &g_freshload; + } + else + { + g_quickload = &g_lastusersave; + } + + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); + + if (G_LoadPlayerMaybeMulti(sv)) + Menu_Change(MENU_PREVIOUS); + else + Menu_Change(MENU_CLOSE); + } + break; + + case MENU_SAVEVERIFY: + if (!input) + { + save_xxh = 0; + + ((MenuString_t*)M_SAVE.entrylist[M_SAVE.currentEntry]->entry)->editfield = NULL; + } + break; + + case MENU_LOADDELVERIFY: + if (input) + { + G_DeleteSave(g_menusaves[M_LOAD.currentEntry].brief); + Menu_LoadReadHeaders(); + M_LOAD.currentEntry = clamp(M_LOAD.currentEntry, 0, (int32_t)g_nummenusaves-1); + } + break; + case MENU_SAVEDELVERIFY: + if (input) + { + G_DeleteSave(g_menusaves[M_SAVE.currentEntry-1].brief); + Menu_SaveReadHeaders(); + M_SAVE.currentEntry = clamp(M_SAVE.currentEntry, 0, (int32_t)g_nummenusaves); + } + break; + + case MENU_COLCORRRESETVERIFY: + if (input) + { + g_videoGamma = DEFAULT_GAMMA; + g_videoContrast = DEFAULT_CONTRAST; + g_videoBrightness = DEFAULT_BRIGHTNESS; + ud.brightness = 0; + r_ambientlight = r_ambientlightrecip = 1.f; + videoSetPalette(ud.brightness>>2,g_player[myconnectindex].ps->palette,0); + } + break; + + case MENU_KEYSRESETVERIFY: + if (input) + CONFIG_SetDefaultKeys(keydefaults); + break; + case MENU_KEYSCLASSICVERIFY: + if (input) + CONFIG_SetDefaultKeys(oldkeydefaults); + break; + case MENU_JOYSTANDARDVERIFY: + if (input) + CONFIG_SetGameControllerDefaultsStandard(); + break; + case MENU_JOYPROVERIFY: + if (input) + CONFIG_SetGameControllerDefaultsPro(); + break; + case MENU_JOYCLEARVERIFY: + if (input) + CONFIG_SetGameControllerDefaultsClear(); + break; + + case MENU_QUIT: + case MENU_QUIT_INGAME: + if (input) + G_GameQuit(); + else + g_quitDeadline = 0; + break; + + case MENU_QUITTOTITLE: + if (input) + { + g_player[myconnectindex].ps->gm = MODE_DEMO; + if (ud.recstat == 1) + G_CloseDemoWrite(); + artClearMapArt(); + } + break; + + case MENU_NETWAITVOTES: + if (!input) + Net_SendMapVoteCancel(0); + break; + + default: + break; + } +} + +static int Menu_CheatStringMatch(char const * input, char const * cheat) +{ + while (*cheat || *input) + { + if (*cheat != *input) + { + if (!(*cheat == '#' && Bisdigit(*input))) + return 0; + } + + ++cheat; + ++input; + } + + return 1; +} + +static void Menu_TextFormSubmit(char *input) +{ + switch (g_currentMenu) + { + case MENU_ADULTPASSWORD: + if (Bstrlen(input) && (ud.pwlockout[0] == 0 || ud.lockout == 0)) + Bstrcpy(&ud.pwlockout[0], input); + else if (Bstrcmp(input, &ud.pwlockout[0]) == 0) + { + for (int x=0; x= 0) + wall[animwall[x].wallnum].picnum = wall[animwall[x].wallnum].extra; + ud.lockout = 0; + } + + S_PlaySound(PISTOL_BODYHIT); + Menu_Change(MENU_GAMESETUP); + break; + + case MENU_CHEATENTRY: + { + const size_t inputlength = Bstrlen(input); + Bstrcpy(tempbuf, input); + for (size_t i = 0; i < inputlength; i++) + tempbuf[i] = Btolower(tempbuf[i]); + + int8_t cheatID = -1; + + if (inputlength > 2 && tempbuf[0] == g_keyAsciiTable[CheatKeys[0]] && tempbuf[1] == g_keyAsciiTable[CheatKeys[1]]) + { + for (int i = 0; i < NUMCHEATS; i++) + if (Menu_CheatStringMatch(tempbuf+2, CheatStrings[i])) + { + cheatID = i; + break; + } + } + + switch (cheatID) + { + case -1: + S_PlaySound(KICK_HIT); + break; + case CHEAT_SCOTTY: + { + char const * const numberpos = Bstrchr(CheatStrings[CHEAT_SCOTTY], '#'); + if (numberpos == NULL) + { + S_PlaySound(KICK_HIT); + break; + } + + Menu_Cheat_Warp(input + (numberpos - CheatStrings[CHEAT_SCOTTY]) + 2); + if (g_player[myconnectindex].ps->gm&MODE_MENU) + S_PlaySound(DUKE_GET); + break; + } + case CHEAT_SKILL: + { + char const * const numberpos = Bstrchr(CheatStrings[CHEAT_SKILL], '#'); + if (numberpos == NULL) + { + S_PlaySound(KICK_HIT); + break; + } + + Menu_Cheat_Skill(input + (numberpos - CheatStrings[CHEAT_SKILL]) + 2); + if (g_player[myconnectindex].ps->gm&MODE_MENU) + S_PlaySound(DUKE_GET); + break; + } + default: + Menu_DoCheat(cheatID); + S_PlaySound(DUKE_GET); + break; + } + + if (cheatID >= 0) + cl_cheatmask |= CheatFunctionFlags[cheatID]; + + if ((NAM_WW2GI && (cl_cheatmask & (1<prev) + start = start->prev; + + while (end->next) + end = end->next; + + int i = 0; + + for (auto n = start; n; n = n->next) + { + n->type = i; // overload this... + n->usera = start; + n->userb = end; + i++; + } +} + +static void Menu_FileSelectInit(MenuFileSelect_t *object) +{ + fnlist_clearnames(&object->fnlist); + + if (object->destination[0] == 0) + { + BDIR * usermaps = Bopendir(object->startdir); + if (usermaps) + { + Bclosedir(usermaps); + Bstrcpy(object->destination, object->startdir); + } + else + Bstrcpy(object->destination, "./"); + } + Bcorrectfilename(object->destination, 1); + + fnlist_getnames(&object->fnlist, object->destination, object->pattern, 0, 0); + object->findhigh[0] = object->fnlist.finddirs; + object->findhigh[1] = object->fnlist.findfiles; + + for (int i = 0; i < 2; ++i) + { + object->scrollPos[i] = 0; + klistbookends(object->findhigh[i]); + } + + object->currentList = 0; + if (object->findhigh[1]) + object->currentList = 1; + + KB_FlushKeyboardQueue(); +} + +static void Menu_FileSelect(int32_t input) +{ + switch (g_currentMenu) + { + case MENU_NETUSERMAP: + if ((g_netServer || ud.multimode > 1)) + Net_SendUserMapName(); + fallthrough__; + case MENU_USERMAP: + if (input) + { + ud.m_volume_number = 0; + ud.m_level_number = 7; + + if (g_skillCnt > 0) + Menu_AnimateChange(MENU_SKILL, MA_Advance); + else + Menu_StartGameWithoutSkill(); + } + break; + + default: + break; + } +} + + + + + +static Menu_t* Menu_BinarySearch(MenuID_t query, uint16_t searchstart, uint16_t searchend) +{ + const uint16_t thissearch = (searchstart + searchend) / 2; + const MenuID_t difference = query - Menus[thissearch].menuID; + + if (difference == 0) + return &Menus[thissearch]; + else if (searchstart == searchend) + return NULL; + else if (difference > 0) + { + if (thissearch == searchend) + return NULL; + searchstart = thissearch + 1; + } + else if (difference < 0) + { + if (thissearch == searchstart) + return NULL; + searchend = thissearch - 1; + } + + return Menu_BinarySearch(query, searchstart, searchend); +} + +static Menu_t* Menu_Find(MenuID_t query) +{ + if ((unsigned) query > (unsigned) Menus[numMenus-1].menuID) + return NULL; + + return Menu_BinarySearch(query, 0, numMenus-1); +} + +static Menu_t* Menu_FindFiltered(MenuID_t query) +{ + if ((g_player[myconnectindex].ps->gm&MODE_GAME) && query == MENU_MAIN) + query = MENU_MAIN_INGAME; + + return Menu_Find(query); +} + +MenuAnimation_t m_animation; + +int32_t Menu_Anim_SinOutRight(MenuAnimation_t *animdata) +{ + return sintable[divscale10((int32_t) totalclock - animdata->start, animdata->length) + 512] - 16384; +} +int32_t Menu_Anim_SinInRight(MenuAnimation_t *animdata) +{ + return sintable[divscale10((int32_t) totalclock - animdata->start, animdata->length) + 512] + 16384; +} +int32_t Menu_Anim_SinOutLeft(MenuAnimation_t *animdata) +{ + return -sintable[divscale10((int32_t) totalclock - animdata->start, animdata->length) + 512] + 16384; +} +int32_t Menu_Anim_SinInLeft(MenuAnimation_t *animdata) +{ + return -sintable[divscale10((int32_t) totalclock - animdata->start, animdata->length) + 512] - 16384; +} + +void Menu_AnimateChange(int32_t cm, MenuAnimationType_t animtype) +{ + if (FURY) + { + m_animation.start = 0; + m_animation.length = 0; + Menu_Change(cm); + return; + } + + switch (animtype) + { + case MA_Advance: + { + Menu_t * const previousMenu = m_currentMenu; + + if (!Menu_Change(cm)) + { + m_animation.out = Menu_Anim_SinOutRight; + m_animation.in = Menu_Anim_SinInRight; + m_animation.start = (int32_t) totalclock; + m_animation.length = 30; + + m_animation.previous = previousMenu; + m_animation.current = m_currentMenu; + } + + break; + } + case MA_Return: + { + Menu_t * const previousMenu = m_currentMenu; + + if (!Menu_Change(cm)) + { + m_animation.out = Menu_Anim_SinOutLeft; + m_animation.in = Menu_Anim_SinInLeft; + m_animation.start = (int32_t) totalclock; + m_animation.length = 30; + + m_animation.previous = previousMenu; + m_animation.current = m_currentMenu; + } + + break; + } + default: + m_animation.start = 0; + m_animation.length = 0; + Menu_Change(cm); + break; + } +} + +static void Menu_MaybeSetSelectionToChild(Menu_t * m, MenuID_t id) +{ + if (m->type == Menu) + { + auto menu = (MenuMenu_t *)m->object; + + if (menu->currentEntry < menu->numEntries) + { + MenuEntry_t const * currentEntry = menu->entrylist[menu->currentEntry]; + if (currentEntry != NULL && currentEntry->type == Link) + { + auto const * link = (MenuLink_t const *)currentEntry->entry; + if (link->linkID == id) + return; // already good to go + } + } + + for (int i = 0, i_end = menu->numEntries; i < i_end; ++i) + { + MenuEntry_t const * entry = menu->entrylist[i]; + if (entry != NULL && entry->type == Link && !(entry->flags & MEF_Hidden)) + { + auto const * link = (MenuLink_t const *)entry->entry; + if (link->linkID == id) + { + menu->currentEntry = i; + Menu_AdjustForCurrentEntryAssignmentBlind(menu); + break; + } + } + } + } +} + +static void Menu_ReadSaveGameHeaders() +{ + ReadSaveGameHeaders(); + + int const numloaditems = max(g_nummenusaves, 1), numsaveitems = g_nummenusaves+1; + ME_LOAD = (MenuEntry_t *)Xrealloc(ME_LOAD, g_nummenusaves * sizeof(MenuEntry_t)); + MEL_LOAD = (MenuEntry_t **)Xrealloc(MEL_LOAD, numloaditems * sizeof(MenuEntry_t *)); + MEO_SAVE = (MenuString_t *)Xrealloc(MEO_SAVE, g_nummenusaves * sizeof(MenuString_t)); + ME_SAVE = (MenuEntry_t *)Xrealloc(ME_SAVE, g_nummenusaves * sizeof(MenuEntry_t)); + MEL_SAVE = (MenuEntry_t **)Xrealloc(MEL_SAVE, numsaveitems * sizeof(MenuEntry_t *)); + + MEL_SAVE[0] = &ME_SAVE_NEW; + ME_SAVE_NEW.name = s_NewSaveGame; + for (int i = 0; i < g_nummenusaves; ++i) + { + MEL_LOAD[i] = &ME_LOAD[i]; + MEL_SAVE[i+1] = &ME_SAVE[i]; + ME_LOAD[i] = ME_LOAD_TEMPLATE; + ME_SAVE[i] = ME_SAVE_TEMPLATE; + ME_SAVE[i].entry = &MEO_SAVE[i]; + MEO_SAVE[i] = MEO_SAVE_TEMPLATE; + + ME_LOAD[i].name = g_menusaves[i].brief.name; + MEO_SAVE[i].variable = g_menusaves[i].brief.name; + } + + if (g_nummenusaves == 0) + MEL_LOAD[0] = &ME_LOAD_EMPTY; + + M_LOAD.entrylist = MEL_LOAD; + M_LOAD.numEntries = numloaditems; + M_SAVE.entrylist = MEL_SAVE; + M_SAVE.numEntries = numsaveitems; + + // lexicographical sorting? +} + +static void Menu_AboutToStartDisplaying(Menu_t * m) +{ + switch (m->menuID) + { + case MENU_MAIN: + if (FURY) + ME_MAIN_LOADGAME.name = s_Continue; + break; + + case MENU_MAIN_INGAME: + if (FURY) + ME_MAIN_LOADGAME.name = s_LoadGame; + break; + + case MENU_NEWGAMECUSTOMSUB: + Menu_PopulateNewGameCustomSub(M_NEWGAMECUSTOM.currentEntry); + break; + + case MENU_LOAD: + if (FURY) + M_LOAD.title = (g_player[myconnectindex].ps->gm & MODE_GAME) ? s_LoadGame : s_Continue; + + Menu_LoadReadHeaders(); + + if (g_quickload && g_quickload->isValid()) + { + for (int i = 0; i < g_nummenusaves; ++i) + { + if (strcmp(g_menusaves[i].brief.path, g_quickload->path) == 0) + { + M_LOAD.currentEntry = i; + Menu_AdjustForCurrentEntryAssignmentBlind(&M_LOAD); + break; + } + } + } + break; + + case MENU_SAVE: + if (g_previousMenu == MENU_SAVEVERIFY || g_previousMenu == MENU_SAVEDELVERIFY) + break; + + Menu_SaveReadHeaders(); + + if (g_lastusersave.isValid()) + { + for (int i = 0; i < g_nummenusaves; ++i) + { + if (strcmp(g_menusaves[i].brief.path, g_lastusersave.path) == 0) + { + M_SAVE.currentEntry = i+1; + Menu_AdjustForCurrentEntryAssignmentBlind(&M_SAVE); + break; + } + } + } + + if (g_player[myconnectindex].ps->gm&MODE_GAME) + { + g_screenCapture = 1; + G_DrawRooms(myconnectindex,65536); + g_screenCapture = 0; + } + break; + + case MENU_VIDEOSETUP: + newresolution = 0; + for (int i = 0; i < MAXVALIDMODES; ++i) + { + if (resolution[i].xdim == xres && resolution[i].ydim == yres) + { + newresolution = i; + break; + } + } + newrendermode = videoGetRenderMode(); + newfullscreen = fullscreen; + newvsync = vsync; + newborderless = r_borderless; + break; + + case MENU_ADVSOUND: + soundrate = ud.config.MixRate; + soundvoices = ud.config.NumVoices; + break; + + default: + break; + } + + switch (m->type) + { + case TextForm: + typebuf[0] = 0; + ((MenuTextForm_t*)m->object)->input = typebuf; + break; + case FileSelect: + Menu_FileSelectInit((MenuFileSelect_t*)m->object); + break; + case Menu: + { + auto menu = (MenuMenu_t*)m->object; + // MenuEntry_t* currentry = menu->entrylist[menu->currentEntry]; + + // need this for MENU_SKILL + if (menu->currentEntry >= menu->numEntries) + menu->currentEntry = 0; + + int32_t i = menu->currentEntry; + while (!menu->entrylist[menu->currentEntry] || + (((MenuEntry_t*)menu->entrylist[menu->currentEntry])->flags & MEF_Hidden) || + ((MenuEntry_t*)menu->entrylist[menu->currentEntry])->type == Spacer) + { + menu->currentEntry++; + if (menu->currentEntry >= menu->numEntries) + menu->currentEntry = 0; + if (menu->currentEntry == i) + G_GameExit("Menu_Change: Attempted to show a menu with no entries."); + } + + Menu_EntryFocus(/*currentry*/); + break; + } + default: + break; + } +} + +static void Menu_ChangingTo(Menu_t * m) +{ +#ifdef __ANDROID__ + if (m->menuID == MENU_TOUCHBUTTONS) + AndroidToggleButtonEditor(); +#endif + + switch (m->type) + { + case TextForm: + Menu_StartTextInput(); + break; + default: + break; + } +} + +int Menu_Change(MenuID_t cm) +{ + Menu_t * beginMenu = m_currentMenu; + + cm = VM_OnEventWithReturn(EVENT_CHANGEMENU, g_player[screenpeek].ps->i, screenpeek, cm); + + if (cm == MENU_PREVIOUS) + { + m_currentMenu = m_previousMenu; + g_currentMenu = g_previousMenu; + } + else if (cm == MENU_CLOSE) + Menu_Close(myconnectindex); + else if (cm >= 0) + { + Menu_t * search = Menu_FindFiltered(cm); + + if (search == NULL) + return 0; // intentional, so that users don't use any random value as "don't change" + + // security + if (search->type == Verify && + search->parentID != MENU_PREVIOUS && + search->parentID != MENU_CLOSE && + search->parentID != g_currentMenu) + return 1; + + m_previousMenu = m_currentMenu; + g_previousMenu = g_currentMenu; + m_currentMenu = search; + g_currentMenu = search->menuID; + } + else + return 1; + + if (FURY) + { + Menu_t * parent = m_currentMenu, * result = NULL; + + while (parent != NULL && parent->menuID != MENU_OPTIONS && parent->menuID != MENU_MAIN && parent->menuID != MENU_MAIN_INGAME) + { + result = parent = Menu_FindFiltered(parent->parentID); + } + + m_parentMenu = result; + + if (result) + { + Menu_MaybeSetSelectionToChild(result, m_currentMenu->menuID); + Menu_AboutToStartDisplaying(result); + } + } + + Menu_MaybeSetSelectionToChild(m_currentMenu, beginMenu->menuID); + Menu_AboutToStartDisplaying(m_currentMenu); + Menu_ChangingTo(m_currentMenu); + +#if !defined EDUKE32_TOUCH_DEVICES + m_menuchange_watchpoint = 1; +#endif + + return 0; +} + + + + + + + + + + +int G_CheckPlayerColor(int color) +{ + for (int i : MEOSV_PLAYER_COLOR) + if (i == color) + return color; + + return -1; +} + + +int32_t Menu_DetermineSpecialState(MenuEntry_t *entry) +{ + if (entry == NULL) + return 0; + + if (entry->type == String) + { + if (((MenuString_t*)entry->entry)->editfield) + return 1; + } + else if (entry->type == Option) + { + if (((MenuOption_t*)entry->entry)->options->currentEntry >= 0) + return 2; + } + else if (entry->type == Custom2Col) + { + if (((MenuCustom2Col_t*)entry->entry)->screenOpen) + return 2; + } + + return 0; +} + +int32_t Menu_IsTextInput(Menu_t *cm) +{ + switch (m_currentMenu->type) + { + case Verify: + case TextForm: + case FileSelect: + case Message: + return 1; + break; + case Panel: + return 0; + break; + case Menu: + { + auto menu = (MenuMenu_t *)cm->object; + auto entry = menu->entrylist[menu->currentEntry]; + return Menu_DetermineSpecialState(entry); + } + break; + } + + return 0; +} + +static inline int32_t Menu_BlackTranslucentBackgroundOK(MenuID_t cm) +{ + switch (cm) + { + case MENU_COLCORR: + case MENU_COLCORR_INGAME: + return 0; + break; + default: + return 1; + break; + } + + return 1; +} + +static inline int32_t Menu_UpdateScreenOK(MenuID_t cm) +{ + switch (cm) + { + case MENU_LOAD: + case MENU_SAVE: + case MENU_LOADVERIFY: + case MENU_LOADDELVERIFY: + case MENU_SAVEVERIFY: + case MENU_SAVEDELVERIFY: + return 0; + break; + default: + return 1; + break; + } + + return 1; +} + + +/* + Code below this point is entirely general, + so if you want to change or add a menu, + chances are you should scroll up. +*/ + +int32_t m_mouselastactivity; +#if !defined EDUKE32_TOUCH_DEVICES +int32_t m_mousewake_watchpoint, m_menuchange_watchpoint; +#endif +int32_t m_mousecaught; +static vec2_t m_prevmousepos, m_mousepos, m_mousedownpos; + +void Menu_Open(uint8_t playerID) +{ + g_player[playerID].ps->gm |= MODE_MENU; + + mouseReadAbs(&m_prevmousepos, &g_mouseAbs); + m_mouselastactivity = -M_MOUSETIMEOUT; + +#if !defined EDUKE32_TOUCH_DEVICES + m_mousewake_watchpoint = 0; +#endif + + mouseLockToWindow(0); +} + +void Menu_Close(uint8_t playerID) +{ + auto & gm = g_player[playerID].ps->gm; + if (gm & MODE_GAME) + { + if (gm & MODE_MENU) + I_ClearAllInput(); + + // The following lines are here so that you cannot close the menu when no game is running. + gm &= ~MODE_MENU; + mouseLockToWindow(1); + + if ((!g_netServer && ud.multimode < 2) && ud.recstat != 2) + { + ready2send = 1; + totalclock = ototalclock; + CAMERACLOCK = (int32_t) totalclock; + CAMERADIST = 65536; + m_animation.start = 0; + m_animation.length = 0; + + // Reset next-viewscreen-redraw counter. + // XXX: are there any other cases like that in need of handling? + if (g_curViewscreen >= 0) + actor[g_curViewscreen].t_data[0] = (int32_t) totalclock; + } + + walock[TILE_SAVESHOT] = 1; + G_UpdateScreenArea(); + S_PauseSounds(false); + } +} + +static int32_t x_widescreen_left(void) +{ + return (320<<15) - scale(240<<15, xdim, ydim); +} + +static int32_t xdim_from_320_16(int32_t x) +{ + const int32_t screenwidth = scale(240<<16, xdim, ydim); + return scale(x + (screenwidth>>1) - (160<<16), xdim, screenwidth); +} +static int32_t ydim_from_200_16(int32_t y) +{ + y = mulscale16(y + rotatesprite_y_offset - (200<<15), rotatesprite_yxaspect) + (200<<15); + return scale(y, ydim, 200<<16); +} + +static void Menu_BlackRectangle(int32_t x, int32_t y, int32_t width, int32_t height, int32_t orientation) +{ + const int32_t xscale = divscale16(width, tilesiz[0].x<<16), yscale = divscale16(height, tilesiz[0].y<<16); + + rotatesprite_(x, y, max(xscale, yscale), 0, 0, 127, ud.shadow_pal, (orientation&(1|32))|2|8|16, 0, 0, xdim_from_320_16(x), ydim_from_200_16(y), xdim_from_320_16(x + width), ydim_from_200_16(y + height)); +} + +enum MenuTextFlags_t +{ + MT_Selected = 1<<0, + MT_Disabled = 1<<1, + MT_XCenter = 1<<2, + MT_XRight = 1<<3, + MT_YCenter = 1<<4, + MT_Literal = 1<<5, + MT_RightSide = 1<<6, +}; + +static void Menu_GetFmt(const MenuFont_t *font, uint8_t const status, int32_t *s, int32_t *z) +{ + if (status & MT_Selected) + *s = VM_OnEventWithReturn(EVENT_MENUSHADESELECTED, -1, myconnectindex, sintable[((int32_t) totalclock<<5)&2047]>>12); + else + *s = font->shade_deselected; + // sum shade values + if (status & MT_Disabled) + *s += font->shade_disabled; + + if (FURY && status & MT_Selected) + *z += (*z >> 4); +} + +static vec2_t Menu_Text(int32_t x, int32_t y, const MenuFont_t *font, const char *t, uint8_t status, int32_t ydim_upper, int32_t ydim_lower) +{ + int32_t s, p, ybetween = font->between.y; + int32_t f = font->textflags; + if (status & MT_XCenter) + f |= TEXT_XCENTER; + if (status & MT_XRight) + f |= TEXT_XRIGHT; + if (status & MT_YCenter) + { + f |= TEXT_YCENTER | TEXT_YOFFSETZERO; + ybetween = font->emptychar.y; // <^ the battle against 'Q' + } + if (status & MT_Literal) + f |= TEXT_LITERALESCAPE; + + int32_t z = font->zoom; + + if (status & MT_Disabled) + p = (status & MT_RightSide) ? font->pal_disabled_right : font->pal_disabled; + else if (status & MT_Selected) + p = (status & MT_RightSide) ? font->pal_selected_right : font->pal_selected; + else + p = (status & MT_RightSide) ? font->pal_deselected_right : font->pal_deselected; + + Menu_GetFmt(font, status, &s, &z); + + return G_ScreenText(font->tilenum, x, y, z, 0, 0, t, s, p, 2|8|16|ROTATESPRITE_FULL16, 0, font->emptychar.x, font->emptychar.y, font->between.x, ybetween, f, 0, ydim_upper, xdim-1, ydim_lower); +} + +#if 0 +static vec2_t Menu_TextSize(int32_t x, int32_t y, const MenuFont_t *font, const char *t, uint8_t status) +{ + int32_t f = font->textflags; + if (status & MT_Literal) + f |= TEXT_LITERALESCAPE; + + return G_ScreenTextSize(font->tilenum, x, y, font->zoom, 0, t, 2|8|16|ROTATESPRITE_FULL16, font->emptychar.x, font->emptychar.y, font->between.x, font->between.y, f, 0, 0, xdim-1, ydim-1); +} +#endif + +static int32_t Menu_FindOptionBinarySearch(MenuOption_t *object, const int32_t query, uint16_t searchstart, uint16_t searchend) +{ + const uint16_t thissearch = (searchstart + searchend) / 2; + const bool isIdentityMap = object->options->optionValues == NULL; + const int32_t destination = isIdentityMap ? (int32_t)thissearch : object->options->optionValues[thissearch]; + const int32_t difference = query - destination; + + Bassert(!isIdentityMap || query >= 0); + + if (difference == 0) + return thissearch; + else if (searchstart == searchend) + return -1; + else if (difference > 0) + { + if (thissearch == searchend) + return -1; + searchstart = thissearch + 1; + } + else if (difference < 0) + { + if (thissearch == searchstart) + return -1; + searchend = thissearch - 1; + } + + return Menu_FindOptionBinarySearch(object, query, searchstart, searchend); +} + +static int32_t Menu_MouseOutsideBounds(vec2_t const * const pos, const int32_t x, const int32_t y, const int32_t width, const int32_t height) +{ + return pos->x < x || pos->x >= x + width || pos->y < y || pos->y >= y + height; +} + +static void Menu_RunScrollbar(Menu_t *cm, MenuMenuFormat_t const * const format, const int32_t totalextent, int32_t * const scrollPos, const int32_t rightedge, const vec2_t origin) +{ + if (totalextent > klabs(format->bottomcutoff)) + { + int32_t scrollTile = (ud.menu_scrollbartilenum >= 0) ? ud.menu_scrollbartilenum : -1; + int32_t scrollTileTop = (ud.menu_scrollbartilenum >= 0) ? ud.menu_scrollbartilenum + 1 : -1; + int32_t scrollTileBottom = (ud.menu_scrollbartilenum >= 0) ? ud.menu_scrollbartilenum + 2 : -1; + int32_t scrollTileCursor = (ud.menu_scrollbartilenum >= 0) ? ud.menu_scrollbartilenum + 3 : SELECTDIR; + + const int32_t scrollwidth = (scrollTile >= 0) ? tilesiz[scrollTile].x*ud.menu_scrollbarz : tilesiz[scrollTileCursor].x*ud.menu_scrollcursorz; + const int32_t scrollx = origin.x + rightedge - scrollwidth, scrolly = origin.y + format->pos.y; + const int32_t scrollheight = klabs(format->bottomcutoff) - format->pos.y; + int32_t scrollregionstart = scrolly; + int32_t scrollregionend = scrolly + scrollheight; + if (ud.menu_scrollbartilenum >= 0) + { + scrollregionstart += tilesiz[scrollTileTop].y*ud.menu_scrollbarz; + scrollregionend -= tilesiz[scrollTileBottom].y*ud.menu_scrollbarz; + } + const int32_t scrollregionheight = scrollregionend - scrollregionstart - (tilesiz[scrollTileCursor].y*ud.menu_scrollcursorz); + const int32_t scrollPosMax = totalextent - klabs(format->bottomcutoff); + + if (scrollTile >= 0) + { + // draw the scrollbar (minus the top tile) twice to fill the gaps between tiles + if (tilesiz[scrollTile].y > 0) + { + for (int32_t y = scrollregionstart + ((tilesiz[scrollTileTop].y == 0)*tilesiz[scrollTile].y*ud.menu_scrollbarz); y < scrollregionend; y += tilesiz[scrollTile].y*ud.menu_scrollbarz) + rotatesprite(scrollx, y - (ud.menu_scrollbarz>>1), ud.menu_scrollbarz, 0, scrollTile, 0, 0, 26, 0, 0, xdim-1, mulscale16(scrollregionend, ydim*200)-1); + } + rotatesprite_fs(scrollx, scrollregionend - (ud.menu_scrollbarz>>1), ud.menu_scrollbarz, 0, scrollTileBottom, 0, 0, 26); + + if (tilesiz[scrollTile].y > 0) + { + for (int32_t y = scrollregionstart; y < scrollregionend; y += tilesiz[scrollTile].y*ud.menu_scrollbarz) + rotatesprite(scrollx, y, ud.menu_scrollbarz, 0, scrollTile, 0, 0, 26, 0, 0, xdim-1, mulscale16(scrollregionend, ydim*200)-1); + } + rotatesprite_fs(scrollx, scrolly, ud.menu_scrollbarz, 0, scrollTileTop, 0, 0, 26); + rotatesprite_fs(scrollx, scrollregionend, ud.menu_scrollbarz, 0, scrollTileBottom, 0, 0, 26); + } + else + Menu_BlackRectangle(scrollx, scrolly, scrollwidth, scrollheight, 1|32); + + rotatesprite_fs(scrollx + ((scrollwidth>>17)<<16) - ((tilesiz[scrollTileCursor].x>>1)*ud.menu_scrollcursorz), scrollregionstart + scale(scrollregionheight, *scrollPos, scrollPosMax), ud.menu_scrollcursorz, 0, scrollTileCursor, 0, 0, 26); + + if (cm == m_currentMenu && !m_mousecaught && MOUSEACTIVECONDITIONAL(g_mouseClickState == MOUSE_PRESSED || g_mouseClickState == MOUSE_HELD)) + { + const int32_t scrolltilehalfheight = (tilesiz[scrollTileCursor].y>>1)*ud.menu_scrollcursorz; + const int32_t scrollregiony = scrollregionstart + scrolltilehalfheight; + + // region between the y-midline of the arrow at the extremes scrolls proportionally + if (!Menu_MouseOutsideBounds(&m_mousepos, scrollx, scrollregiony, scrollwidth, scrollregionheight)) + { + *scrollPos = scale(m_mousepos.y - scrollregiony, scrollPosMax, scrollregionheight); + + m_mousecaught = 1; + } + // region outside the y-midlines clamps to the extremes + else if (!Menu_MouseOutsideBounds(&m_mousepos, scrollx, scrolly, scrollwidth, scrollheight)) + { + if (m_mousepos.y > scrolly + ((scrollheight>>17)<<16)) + *scrollPos = scrollPosMax; + else + *scrollPos = 0; + + m_mousecaught = 1; + } + } + } +} + +typedef enum MenuMovement_t +{ + MM_Up = 1, + MM_End = 3, + MM_Down = 4, + MM_Home = 12, + MM_Left = 16, + MM_AllTheWayLeft = 48, + MM_Right = 64, + MM_AllTheWayRight = 192, + MM_Swap = 80, +} MenuMovement_t; + +static MenuEntry_t *Menu_RunInput_Menu_MovementVerify(MenuMenu_t *menu); +static MenuEntry_t *Menu_RunInput_Menu_Movement(MenuMenu_t *menu, MenuMovement_t direction); +static void Menu_RunInput_EntryLink_Activate(MenuEntry_t *entry); +static void Menu_RunInput_EntryOptionList_MovementVerify(MenuOption_t *object); +static void Menu_RunInput_EntryOptionList_Movement(MenuOption_t *object, MenuMovement_t direction); +static int32_t Menu_RunInput_EntryOption_Modify(MenuEntry_t *entry, MenuOption_t *object, int32_t newValueIndex); +static int32_t Menu_RunInput_EntryOption_Movement(MenuEntry_t *entry, MenuOption_t *object, MenuMovement_t direction); +static int32_t Menu_RunInput_EntryOption_Activate(MenuEntry_t *entry, MenuOption_t *object); +static int32_t Menu_RunInput_EntryOptionList_Activate(MenuEntry_t *entry, MenuOption_t *object); +static void Menu_RunInput_EntryCustom2Col_Activate(MenuEntry_t *entry); +static void Menu_RunInput_EntryRangeInt32_MovementVerify(MenuEntry_t *entry, MenuRangeInt32_t *object, int32_t newValue); +static void Menu_RunInput_EntryRangeInt32_MovementArbitrary(MenuEntry_t *entry, MenuRangeInt32_t *object, int32_t newValue); +static void Menu_RunInput_EntryRangeInt32_Movement(MenuEntry_t *entry, MenuRangeInt32_t *object, MenuMovement_t direction); +static void Menu_RunInput_EntryRangeFloat_MovementVerify(MenuEntry_t *entry, MenuRangeFloat_t *object, float newValue); +static void Menu_RunInput_EntryRangeFloat_MovementArbitrary(MenuEntry_t *entry, MenuRangeFloat_t *object, float newValue); +static void Menu_RunInput_EntryRangeFloat_Movement(MenuEntry_t *entry, MenuRangeFloat_t *object, MenuMovement_t direction); +#ifdef MENU_ENABLE_RANGEDOUBLE +static void Menu_RunInput_EntryRangeDouble_MovementVerify(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, double newValue); +static void Menu_RunInput_EntryRangeDouble_MovementArbitrary(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, double newValue); +static void Menu_RunInput_EntryRangeDouble_Movement(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, MenuMovement_t direction); +#endif +static void Menu_RunInput_EntryString_Activate(MenuEntry_t *entry); +static void Menu_RunInput_EntryString_Submit(/*MenuEntry_t *entry, */MenuString_t *object); +static void Menu_RunInput_EntryString_Cancel(/*MenuEntry_t *entry, */MenuString_t *object); +static void Menu_RunInput_FileSelect_MovementVerify(MenuFileSelect_t *object); +static void Menu_RunInput_FileSelect_Movement(MenuFileSelect_t *object, MenuMovement_t direction); +static void Menu_RunInput_FileSelect_Select(MenuFileSelect_t *object); + +static int32_t M_RunMenu_Menu(Menu_t *cm, MenuMenu_t *menu, MenuEntry_t *currentry, int32_t state, const vec2_t origin, bool actually_draw) +{ + int32_t totalHeight = 0; + + // RIP MenuGroup_t b. 2014-03-?? d. 2014-11-29 + { + int32_t e; + const int32_t y_upper = menu->format->pos.y; + const int32_t y_lower = klabs(menu->format->bottomcutoff); + int32_t y = 0; + int32_t calculatedentryspacing = 0; + + if (menu->format->bottomcutoff < 0) + { + int32_t totalheight = 0, numvalidentries = 0; + + for (e = 0; e < menu->numEntries; ++e) + { + MenuEntry_t *entry = menu->entrylist[e]; + + if (entry == NULL || (entry->flags & MEF_Hidden)) + continue; + + ++numvalidentries; + + // assumes height == font->get_yline()! + totalheight += entry->getHeight(); + } + + calculatedentryspacing = (klabs(menu->format->bottomcutoff) - menu->format->pos.y - totalheight) / (numvalidentries > 1 ? numvalidentries - 1 : 1); + } + + // totalHeight calculating pass + for (e = 0; e < menu->numEntries; ++e) + { + MenuEntry_t *entry = menu->entrylist[e]; + + if (entry == NULL || (entry->flags & MEF_Hidden)) + continue; + + int32_t const height = entry->getHeight(); + + y += height; + totalHeight = y; + y += (!calculatedentryspacing || calculatedentryspacing > entry->getMarginBottom()) ? entry->getMarginBottom() : calculatedentryspacing; + } + y = 0; + + int32_t ydim_upper, ydim_lower; + if (y_upper + totalHeight > y_lower) + { + ydim_upper = ydim_from_200_16(origin.y + y_upper); + ydim_lower = ydim_from_200_16(origin.y + y_lower); + } + else + { + ydim_upper = 0; + ydim_lower = ydim-1; + } + + for (e = 0; e < menu->numEntries; ++e) + { + MenuEntry_t *entry = menu->entrylist[e]; + + if (entry == NULL || (entry->flags & MEF_Hidden)) + continue; + + int32_t const indent = entry->getIndent(); + int32_t x = menu->format->pos.x; + + uint8_t status = 0; + if (e == menu->currentEntry) + status |= MT_Selected; + if (entry->flags & (MEF_Disabled|MEF_LookDisabled)) + status |= MT_Disabled; + if (entry->format->width == 0) + status |= MT_XCenter; + + bool const dodraw = entry->type != Spacer && actually_draw && + 0 <= y - menu->scrollPos + entry->font->get_yline() && + y - menu->scrollPos <= klabs(menu->format->bottomcutoff) - menu->format->pos.y; + + int32_t const height = entry->getHeight(); // max(textsize.y, entry->font->get_yline()); // bluefont Q ruins this + status |= MT_YCenter; + int32_t const y_internal = origin.y + y_upper + y + ((height>>17)<<16) - menu->scrollPos; + + vec2_t textsize; + if (dodraw) + textsize = Menu_Text(origin.x + x + indent, y_internal, entry->font, entry->name, status, ydim_upper, ydim_lower); + + if (entry->format->width < 0) + status |= MT_XRight; + + if (dodraw && (status & MT_Selected) && state != 1) + { + if (status & MT_XCenter) + { + Menu_DrawCursorLeft(origin.x + (MENU_MARGIN_CENTER<<16) + entry->font->cursorCenterPosition, y_internal, entry->font->cursorScale); + Menu_DrawCursorRight(origin.x + (MENU_MARGIN_CENTER<<16) - entry->font->cursorCenterPosition, y_internal, entry->font->cursorScale); + } + else + Menu_DrawCursorLeft(origin.x + x + indent - entry->font->cursorLeftPosition, y_internal, entry->font->cursorScale); + } + + if (entry->name != nullptr && entry->name[0] != '\0') + status |= MT_RightSide; + + // need this up here to avoid race conditions + entry->ybottom = (entry->ytop = y_upper + y) + height; + + if (dodraw) + { + const int32_t mousex = origin.x + indent + entry->format->width == 0 ? x - ((textsize.x>>17)<<16) : x; + const int32_t mousey = origin.y + y_upper + y - menu->scrollPos; + int32_t mousewidth = entry->format->width == 0 ? textsize.x : klabs(entry->format->width); + + if (entry->name) + x += klabs(entry->format->width); + + switch (entry->type) + { + case Spacer: + break; + case Dummy: + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + } + break; + case Link: + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, mousewidth, entry->font->get_yline())) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryLink_Activate(entry); + + if (g_player[myconnectindex].ps->gm&MODE_MENU) // for skill selection + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + break; + case Option: + { + auto object = (MenuOption_t*)entry->entry; + int32_t currentOption = Menu_FindOptionBinarySearch(object, object->data == NULL ? Menu_EntryOptionSource(entry, object->currentOption) : *object->data, 0, object->options->numOptions); + + if (currentOption >= 0) + object->currentOption = currentOption; + + int32_t optiontextx = origin.x + x; + const int32_t optiontexty = origin.y + y_upper + y - menu->scrollPos; + + const vec2_t optiontextsize = Menu_Text(optiontextx, optiontexty + ((height>>17)<<16), object->font, + currentOption < 0 ? MenuCustom : currentOption < object->options->numOptions ? object->options->optionNames[currentOption] : NULL, + status, ydim_upper, ydim_lower); + + if (entry->format->width > 0) + mousewidth += optiontextsize.x; + else + optiontextx -= optiontextsize.x; + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, mousewidth, entry->font->get_yline())) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryOption_Activate(entry, object); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + + break; + } + case Custom2Col: + { + auto object = (MenuCustom2Col_t*)entry->entry; + int32_t columnx[2] = { origin.x + x - ((status & MT_XRight) ? object->columnWidth : 0), origin.x + x + ((status & MT_XRight) ? 0 : object->columnWidth) }; + const int32_t columny = origin.y + y_upper + y - menu->scrollPos; + + const vec2_t column0textsize = Menu_Text(columnx[0], columny + ((height>>17)<<16), object->font, object->key[*object->column[0]], menu->currentColumn == 0 ? status : (status & ~MT_Selected), ydim_upper, ydim_lower); + const vec2_t column1textsize = Menu_Text(columnx[1], columny + ((height>>17)<<16), object->font, object->key[*object->column[1]], menu->currentColumn == 1 ? status : (status & ~MT_Selected), ydim_upper, ydim_lower); + + if (entry->format->width > 0) + mousewidth += object->columnWidth + column1textsize.x; + else + { + columnx[0] -= column0textsize.x; + columnx[1] -= column0textsize.x; + } + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!Menu_MouseOutsideBounds(&m_mousepos, columnx[1], mousey, column1textsize.x, object->font->get_yline())) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, columnx[1], mousey, column1textsize.x, object->font->get_yline()))) + { + menu->currentColumn = 1; + } + + if (!m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !Menu_MouseOutsideBounds(&m_mousedownpos, columnx[1], mousey, column1textsize.x, object->font->get_yline())) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + menu->currentColumn = 1; + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryCustom2Col_Activate(entry); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + else if (!Menu_MouseOutsideBounds(&m_mousepos, columnx[0], mousey, column0textsize.x, object->font->get_yline())) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, columnx[0], mousey, column0textsize.x, object->font->get_yline()))) + { + menu->currentColumn = 0; + } + + if (!m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !Menu_MouseOutsideBounds(&m_mousedownpos, columnx[0], mousey, column0textsize.x, object->font->get_yline())) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + menu->currentColumn = 0; + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryCustom2Col_Activate(entry); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + } + break; + } + case RangeInt32: + { + auto object = (MenuRangeInt32_t*)entry->entry; + + int32_t s, p; + int32_t z = entry->font->cursorScale; + Menu_GetFmt(object->font, status|MT_RightSide, &s, &z); + + if (status & MT_Disabled) + p = ud.slidebar_paldisabled; + else if (status & MT_Selected) + p = ud.slidebar_palselected; + else + p = 0; + + const int32_t slidebarwidth = mulscale16(tilesiz[SLIDEBAR].x * ud.menu_slidebarz, z); + const int32_t slidebarheight = mulscale16(tilesiz[SLIDEBAR].y * ud.menu_slidebarz, z); + + if (status & MT_XRight) + x -= slidebarwidth; + else + mousewidth += slidebarwidth; + + const int32_t slidebarx = origin.x + x; + const int32_t slidebary = origin.y + y_upper + y + (((height - slidebarheight)>>17)<<16) - menu->scrollPos; + + rotatesprite_ybounds(slidebarx, slidebary, mulscale16(ud.menu_slidebarz, z), 0, SLIDEBAR, s, p, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + const int32_t slideregionwidth = mulscale16((tilesiz[SLIDEBAR].x * ud.menu_slidebarz) - (ud.menu_slidebarmargin<<1) - (tilesiz[SLIDEBAR+1].x * ud.menu_slidecursorz), z); + const int32_t slidepointx = slidebarx + mulscale16(ud.menu_slidebarmargin, z) + scale(slideregionwidth, *object->variable - object->min, object->max - object->min); + const int32_t slidepointy = slidebary + mulscale16((((tilesiz[SLIDEBAR].y>>1) * ud.menu_slidebarz) - ((tilesiz[SLIDEBAR+1].y>>1) * ud.menu_slidecursorz)), z); + + rotatesprite_ybounds(slidepointx, slidepointy, mulscale16(ud.menu_slidecursorz, z), 0, SLIDEBAR+1, s, p, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + if (object->flags & DisplayTypeMask) + { + status |= MT_XRight; + + int32_t onehundredpercent = object->onehundredpercent; + if (onehundredpercent == 0) + onehundredpercent = object->max; + + switch (object->flags & DisplayTypeMask) + { + case DisplayTypeInteger: + Bsprintf(tempbuf, "%d", *object->variable); + break; + case DisplayTypePercent: + Bsprintf(tempbuf, "%d%%", roundscale(*object->variable, 100, onehundredpercent)); + break; + case DisplayTypeNormalizedDecimal: + Bsprintf(tempbuf, "%.2f", (double) *object->variable / (double) onehundredpercent); + break; + } + + Menu_Text(origin.x + x - (4<<16), origin.y + y_upper + y + ((height>>17)<<16) - menu->scrollPos, object->font, tempbuf, status, ydim_upper, ydim_lower); + } + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && (g_mouseClickState == MOUSE_PRESSED || g_mouseClickState == MOUSE_HELD)) + { + const int32_t slidepointhalfwidth = mulscale16((((tilesiz[SLIDEBAR+1].x)*ud.menu_slidecursorz)>>2) + ud.menu_slidebarmargin, z); + const int32_t slideregionx = slidebarx + slidepointhalfwidth; + + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + // region between the x-midline of the slidepoint at the extremes slides proportionally + if (!Menu_MouseOutsideBounds(&m_mousepos, slideregionx, mousey, slideregionwidth, height)) + { + Menu_RunInput_EntryRangeInt32_MovementArbitrary(entry, object, roundscale(object->max - object->min, m_mousepos.x - slideregionx, slideregionwidth) + object->min); + + m_mousecaught = 1; + } + // region outside the x-midlines clamps to the extremes + else if (!Menu_MouseOutsideBounds(&m_mousepos, slidebarx, mousey, slidebarwidth, height)) + { + if (m_mousepos.x > slideregionx + ((slideregionwidth>>17)<<16)) + Menu_RunInput_EntryRangeInt32_MovementVerify(entry, object, object->max); + else + Menu_RunInput_EntryRangeInt32_MovementVerify(entry, object, object->min); + + m_mousecaught = 1; + } + } + } + + break; + } + case RangeFloat: + { + auto object = (MenuRangeFloat_t*)entry->entry; + + int32_t s, p; + int32_t z = entry->font->cursorScale; + Menu_GetFmt(object->font, status|MT_RightSide, &s, &z); + + if (status & MT_Disabled) + p = ud.slidebar_paldisabled; + else if (status & MT_Selected) + p = ud.slidebar_palselected; + else + p = 0; + + const int32_t slidebarwidth = mulscale16(tilesiz[SLIDEBAR].x * ud.menu_slidebarz, z); + const int32_t slidebarheight = mulscale16(tilesiz[SLIDEBAR].y * ud.menu_slidebarz, z); + + if (status & MT_XRight) + x -= slidebarwidth; + else + mousewidth += slidebarwidth; + + const int32_t slidebarx = origin.x + x; + const int32_t slidebary = origin.y + y_upper + y + (((height - slidebarheight)>>17)<<16) - menu->scrollPos; + + rotatesprite_ybounds(slidebarx, slidebary, mulscale16(ud.menu_slidebarz, z), 0, SLIDEBAR, s, p, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + const int32_t slideregionwidth = mulscale16((tilesiz[SLIDEBAR].x * ud.menu_slidebarz) - (ud.menu_slidebarmargin<<1) - (tilesiz[SLIDEBAR+1].x * ud.menu_slidecursorz), z); + const int32_t slidepointx = slidebarx + mulscale16(ud.menu_slidebarmargin, z) + Blrintf((float) slideregionwidth * (*object->variable - object->min) / (object->max - object->min)); + const int32_t slidepointy = slidebary + mulscale16(((tilesiz[SLIDEBAR].y>>1) * ud.menu_slidebarz) - ((tilesiz[SLIDEBAR+1].y>>1) * ud.menu_slidecursorz), z); + + rotatesprite_ybounds(slidepointx, slidepointy, mulscale16(ud.menu_slidecursorz, z), 0, SLIDEBAR+1, s, p, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + if (object->flags & DisplayTypeMask) + { + status |= MT_XRight; + + float onehundredpercent = object->onehundredpercent; + if (onehundredpercent == 0.f) + onehundredpercent = 1.f; + + switch (object->flags & DisplayTypeMask) + { + case DisplayTypeInteger: + Bsprintf(tempbuf, "%.2f", *object->variable); + break; + case DisplayTypePercent: + Bsprintf(tempbuf, "%ld%%", lrintf(*object->variable * 100.f / onehundredpercent)); + break; + case DisplayTypeNormalizedDecimal: + Bsprintf(tempbuf, "%.2f", *object->variable / onehundredpercent); + break; + } + + Menu_Text(origin.x + x - (4<<16), origin.y + y_upper + y + ((height>>17)<<16) - menu->scrollPos, object->font, tempbuf, status, ydim_upper, ydim_lower); + } + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && (g_mouseClickState == MOUSE_PRESSED || g_mouseClickState == MOUSE_HELD)) + { + const int32_t slidepointhalfwidth = mulscale16((2+tilesiz[SLIDEBAR+1].x)<<15, z); + const int32_t slideregionx = slidebarx + slidepointhalfwidth; + + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + // region between the x-midline of the slidepoint at the extremes slides proportionally + if (!Menu_MouseOutsideBounds(&m_mousepos, slideregionx, mousey, slideregionwidth, height)) + { + Menu_RunInput_EntryRangeFloat_MovementArbitrary(entry, object, (object->max - object->min) * (m_mousepos.x - slideregionx) / slideregionwidth + object->min); + + m_mousecaught = 1; + } + // region outside the x-midlines clamps to the extremes + else if (!Menu_MouseOutsideBounds(&m_mousepos, slidebarx, mousey, slidebarwidth, height)) + { + if (m_mousepos.x > slideregionx + (slideregionwidth>>17<<16)) + Menu_RunInput_EntryRangeFloat_MovementVerify(entry, object, object->max); + else + Menu_RunInput_EntryRangeFloat_MovementVerify(entry, object, object->min); + + m_mousecaught = 1; + } + } + } + + break; + } +#ifdef MENU_ENABLE_RANGEDOUBLE + case RangeDouble: + { + MenuRangeDouble_t *object = (MenuRangeDouble_t*)entry->entry; + + int32_t s, p; + int32_t z = entry->font->cursorScale; + Menu_GetFmt(object->font, status|MT_RightSide, &s, &z); + + if (status & MT_Disabled) + p = ud.slidebar_paldisabled; + else if (status & MT_Selected) + p = ud.slidebar_palselected; + else + p = 0; + + const int32_t slidebarwidth = mulscale16(tilesiz[SLIDEBAR].x * ud.menu_slidebarz, z); + const int32_t slidebarheight = mulscale16(tilesiz[SLIDEBAR].y * ud.menu_slidebarz, z); + + if (status & MT_XRight) + x -= slidebarwidth; + else + mousewidth += slidebarwidth; + + const int32_t slidebarx = origin.x + x; + const int32_t slidebary = origin.y + y_upper + y + (((height - slidebarheight)>>17)<<16) - menu->scrollPos; + + rotatesprite_ybounds(slidebarx, slidebary, mulscale16(ud.menu_slidebarz, z), 0, SLIDEBAR, s, p, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + const int32_t slideregionwidth = mulscale16((tilesiz[SLIDEBAR].x * ud.menu_slidebarz) - (ud.menu_slidebarmargin<<1) - (tilesiz[SLIDEBAR+1].x * ud.menu_slidecursorz), z); + const int32_t slidepointx = slidebarx + mulscale16(ud.menu_slidebarmargin, z) + lrint((double) slideregionwidth * (*object->variable - object->min) / (object->max - object->min)); + const int32_t slidepointy = slidebary + mulscale16(((tilesiz[SLIDEBAR].y)>>1 * ud.menu_slidebarz) - ((tilesiz[SLIDEBAR+1].y)>>1 * ud.menu_slidecursorz), z); + + rotatesprite_ybounds(slidepointx, slidepointy, mulscale16(ud.menu_slidecursorz, z), 0, SLIDEBAR+1, s, p, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + if (object->flags & DisplayTypeMask) + { + status |= MT_XRight; + + double onehundredpercent = object->onehundredpercent; + if (onehundredpercent == 0.) + onehundredpercent = 1.; + + switch (object->flags & DisplayTypeMask) + { + case DisplayTypeInteger: + Bsprintf(tempbuf, "%.2f", *object->variable); + break; + case DisplayTypePercent: + Bsprintf(tempbuf, "%ld%%", lrint(*object->variable * 100. / onehundredpercent)); + break; + case DisplayTypeNormalizedDecimal: + Bsprintf(tempbuf, "%.2f", *object->variable / onehundredpercent); + break; + } + + Menu_Text(origin.x + x - (4<<16), origin.y + y_upper + y + ((height>>17)<<16) - menu->scrollPos, object->font, tempbuf, status, ydim_upper, ydim_lower); + } + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && (g_mouseClickState == MOUSE_PRESSED || g_mouseClickState == MOUSE_HELD)) + { + const int32_t slidepointhalfwidth = mulscale16((2+tilesiz[SLIDEBAR+1].x)<<15, z); + const int32_t slideregionx = slidebarx + slidepointhalfwidth; + + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + // region between the x-midline of the slidepoint at the extremes slides proportionally + if (!Menu_MouseOutsideBounds(&m_mousepos, slideregionx, mousey, slideregionwidth, height)) + { + Menu_RunInput_EntryRangeDouble_MovementArbitrary(/*entry, */object, (object->max - object->min) * (m_mousepos.x - slideregionx) / slideregionwidth + object->min); + + m_mousecaught = 1; + } + // region outside the x-midlines clamps to the extremes + else if (!Menu_MouseOutsideBounds(&m_mousepos, slidebarx, mousey, slidebarwidth, height)) + { + if (m_mousepos.x > slideregionx + ((slideregionwidth>>17)<<16)) + Menu_RunInput_EntryRangeDouble_MovementVerify(/*entry, */object, object->max); + else + Menu_RunInput_EntryRangeDouble_MovementVerify(/*entry, */object, object->min); + + m_mousecaught = 1; + } + } + } + + break; + } +#endif + case String: + { + auto object = (MenuString_t*)entry->entry; + + vec2_t dim; + int32_t stringx = x; + const int32_t stringy = origin.y + y_upper + y + ((height>>17)<<16) - menu->scrollPos; + int32_t h; + + if (entry == currentry && object->editfield != NULL) + { + dim = Menu_Text(origin.x + stringx, stringy, object->font, object->editfield, (status & ~MT_Disabled) | MT_Literal, ydim_upper, ydim_lower); + h = max(dim.y, entry->font->get_yline()); + + Menu_DrawCursorText(origin.x + x + dim.x + (1<<16), stringy, h, ydim_upper, ydim_lower); + } + else + { + dim = Menu_Text(origin.x + stringx, stringy, object->font, object->variable, status, ydim_upper, ydim_lower); + h = max(dim.y, entry->font->get_yline()); + } + + if (entry->format->width > 0) + { + if (entry->name) + mousewidth += dim.x; + } + else + stringx -= dim.x; + + if (MOUSEACTIVECONDITIONAL(cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, h))) + { + if (state != 1 && Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, h)) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + #ifndef EDUKE32_TOUCH_DEVICES + if (!m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, h) && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, mousewidth, h)) + #endif + { + if (entry == currentry && object->editfield != NULL) + { + Menu_RunInput_EntryString_Submit(/*entry, */object); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + else if (state != 1) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryString_Activate(entry); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + } + + break; + } + } + } + + // prepare for the next line + y += height; + y += (!calculatedentryspacing || calculatedentryspacing > entry->getMarginBottom()) ? entry->getMarginBottom() : calculatedentryspacing; + } + + // draw indicators if applicable + if (actually_draw) + Menu_RunScrollbar(cm, menu->format, y_upper + totalHeight, &menu->scrollPos, 320<<16, origin); + } + + return totalHeight; +} + +static void Menu_RunOptionList(Menu_t *cm, MenuEntry_t *entry, MenuOption_t *object, const vec2_t origin) +{ + int32_t e, y = 0; + const int32_t y_upper = object->options->menuFormat->pos.y; + const int32_t y_lower = object->options->menuFormat->bottomcutoff; + int32_t calculatedentryspacing = object->options->getMarginBottom(); + + // assumes height == font->get_yline()! + if (calculatedentryspacing < 0) + calculatedentryspacing = (-calculatedentryspacing - object->options->font->get_yline()) / (object->options->numOptions - 1) - object->options->font->get_yline(); + + int32_t totalHeight = 0; + for (e = 0; e < object->options->numOptions; ++e) + { + int32_t const height = object->options->font->get_yline(); + + y += height; + totalHeight = y; + y += calculatedentryspacing; + } + y = 0; + + int32_t ydim_upper, ydim_lower; + if (y_upper + totalHeight > y_lower) + { + ydim_upper = ydim_from_200_16(origin.y + y_upper); + ydim_lower = ydim_from_200_16(origin.y + y_lower); + } + else + { + ydim_upper = 0; + ydim_lower = ydim-1; + } + + for (e = 0; e < object->options->numOptions; ++e) + { + int32_t const x = object->options->menuFormat->pos.x; + + uint8_t status = 0; + if (e == object->options->currentEntry) + status |= MT_Selected; + if (object->options->entryFormat->width == 0) + status |= MT_XCenter; + + bool const dodraw = 0 <= y - object->options->scrollPos + object->options->font->get_yline() && + y - object->options->scrollPos <= object->options->menuFormat->bottomcutoff - object->options->menuFormat->pos.y; + + int32_t const height = object->options->font->get_yline(); // max(textsize.y, object->options->font->get_yline()); + status |= MT_YCenter; + int32_t const y_internal = origin.y + y_upper + y + ((height>>17)<<16) - object->options->scrollPos; + + vec2_t textsize; + if (dodraw) + textsize = Menu_Text(origin.x + x, y_internal, object->options->font, object->options->optionNames[e], status, ydim_upper, ydim_lower); + + if (object->options->entryFormat->width < 0) + status |= MT_XRight; + + if (dodraw && (status & MT_Selected)) + { + if (status & MT_XCenter) + { + Menu_DrawCursorLeft(origin.x + (MENU_MARGIN_CENTER<<16) + object->options->font->cursorCenterPosition, y_internal, object->options->font->cursorScale); + Menu_DrawCursorRight(origin.x + (MENU_MARGIN_CENTER<<16) - object->options->font->cursorCenterPosition, y_internal, object->options->font->cursorScale); + } + else + Menu_DrawCursorLeft(origin.x + x - object->options->font->cursorLeftPosition, y_internal, object->options->font->cursorScale); + } + + if (dodraw) + { + const int32_t mousex = origin.x + object->options->entryFormat->width == 0 ? x - ((textsize.x>>17)<<16) : x; + const int32_t mousey = origin.y + y_upper + y - object->options->scrollPos; + const int32_t mousewidth = object->options->entryFormat->width == 0 ? textsize.x : klabs(object->options->entryFormat->width); + + if (MOUSEACTIVECONDITIONAL(cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, object->options->font->get_yline()))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, object->options->font->get_yline()))) + { + object->options->currentEntry = e; + } + + if (!m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, mousewidth, object->options->font->get_yline())) + { + object->options->currentEntry = e; + + if (!Menu_RunInput_EntryOptionList_Activate(entry, object)) + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + } + + // prepare for the next line + y += height; + y += calculatedentryspacing; + } + + // draw indicators if applicable + Menu_RunScrollbar(cm, object->options->menuFormat, y_upper + totalHeight, &object->options->scrollPos, 320<<16, origin); +} + +static int32_t Menu_RunInput_MouseAdvance(void) +{ + return MOUSEACTIVECONDITIONAL(!m_mousecaught && g_mouseClickState == MOUSE_RELEASED); +} + +static int32_t Menu_RunInput_MouseReturn_status; + +#if !defined EDUKE32_TOUCH_DEVICES +static void Menu_Run_MouseReturn(Menu_t *cm, const vec2_t origin) +{ + if (!MOUSEACTIVECONDITION) + return; + + if (cm->menuID == MENU_MAIN) + return; + + uint32_t const posx = tilesiz[SELECTDIR].y * SELECTDIR_z; + + rotatesprite_(origin.x + posx, 0, SELECTDIR_z, 512, SELECTDIR, + Menu_RunInput_MouseReturn_status ? 4 - (sintable[((int32_t) totalclock << 4) & 2047] >> 11) : 6, 0, + 2 | 8 | 16 | RS_ALIGN_L, MOUSEALPHA, 0, xdim_from_320_16(origin.x + x_widescreen_left()), 0, + xdim_from_320_16(origin.x + x_widescreen_left() + ((posx>>17)<<16)), ydim - 1); +} +#endif + +static int32_t Menu_RunInput_MouseReturn(void) +{ +#if !defined EDUKE32_TOUCH_DEVICES + if (!MOUSEACTIVECONDITION) + { + Menu_RunInput_MouseReturn_status = 0; + return 0; + } +#endif + + if (g_currentMenu == MENU_MAIN) + return 0; + + const int32_t MouseReturnRegionX = x_widescreen_left(); + + vec2_t backbuttonbound = { ((tilesiz[SELECTDIR].y * SELECTDIR_z)>>17)<<16, tilesiz[SELECTDIR].x * SELECTDIR_z }; + + if (!Menu_MouseOutsideBounds(&m_mousepos, MouseReturnRegionX, 0, backbuttonbound.x, backbuttonbound.y)) + { +#if !defined EDUKE32_TOUCH_DEVICES + Menu_RunInput_MouseReturn_status = 1; +#else + Menu_RunInput_MouseReturn_status = (g_mouseClickState == MOUSE_PRESSED || g_mouseClickState == MOUSE_HELD); +#endif + + return !m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !Menu_MouseOutsideBounds(&m_mousedownpos, MouseReturnRegionX, 0, backbuttonbound.x, backbuttonbound.y); + } + + Menu_RunInput_MouseReturn_status = 0; + + return 0; +} + +static void Menu_Run_AbbreviateNameIntoBuffer(const char* name, int32_t entrylength) +{ + int32_t len = Bstrlen(name); + Bstrncpy(tempbuf, name, ARRAY_SIZE(tempbuf)); + if (len > entrylength) + { + len = entrylength-3; + tempbuf[len] = 0; + while (len < entrylength) + tempbuf[len++] = '.'; + } + tempbuf[len] = 0; +} + +static void Menu_Recurse(MenuID_t cm, const vec2_t origin) +{ + switch (cm) + { + case MENU_SAVECLEANVERIFY: + case MENU_RESETSTATSVERIFY: + case MENU_LOADVERIFY: + case MENU_LOADDELVERIFY: + case MENU_SAVEVERIFY: + case MENU_SAVEDELVERIFY: + case MENU_COLCORRRESETVERIFY: + case MENU_KEYSRESETVERIFY: + case MENU_KEYSCLASSICVERIFY: + case MENU_JOYSTANDARDVERIFY: + case MENU_JOYPROVERIFY: + case MENU_JOYCLEARVERIFY: + case MENU_ADULTPASSWORD: + case MENU_CHEATENTRY: + case MENU_CHEAT_WARP: + case MENU_CHEAT_SKILL: + Menu_Run(m_previousMenu, origin); + break; + default: + break; + } +} + +static void Menu_Run(Menu_t *cm, const vec2_t origin) +{ + Menu_Recurse(cm->menuID, origin); + + switch (cm->type) + { + case Verify: + { + auto object = (MenuVerify_t*)cm->object; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + Menu_PreDraw(cm->menuID, NULL, origin); + + Menu_DrawCursorLeft(origin.x + object->cursorpos.x, origin.y + object->cursorpos.y, 65536); + + break; + } + + case Message: + { + auto object = (MenuMessage_t*)cm->object; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + Menu_PreDraw(cm->menuID, NULL, origin); + + Menu_DrawCursorLeft(origin.x + object->cursorpos.x, origin.y + object->cursorpos.y, 65536); + + break; + } + + case TextForm: + { + auto object = (MenuTextForm_t*)cm->object; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + Menu_BlackRectangle(origin.x + (60<<16), origin.y + (86<<16), 200<<16, 28<<16, 0); + + mgametextcenter(origin.x, origin.y + (98<<16), object->instructions, TEXT_YBOTTOM); + + const char *displaytext = object->input; + + if (object->flags & MTF_Password) + { + size_t x; + for (x = 0; x < Bstrlen(object->input); ++x) + tempbuf[x] = '*'; + tempbuf[x] = 0; + + displaytext = tempbuf; + } + + const vec2_t textreturn = mgametextcenter(origin.x, origin.y + (102<<16), displaytext); + + Menu_PreDraw(cm->menuID, NULL, origin); + + int32_t const h = MF_Bluefont.get_yline(); + + Menu_DrawCursorText(origin.x + (MENU_MARGIN_CENTER<<16) + ((textreturn.x>>17)<<16) + (1<<16), origin.y + (102<<16) + ((h>>17)<<16), h); + + break; + } + + case FileSelect: + { + auto object = (MenuFileSelect_t*)cm->object; + const int32_t MenuFileSelect_scrollbar_rightedge[2] = { 160<<16, 284<<16 }; + int32_t i, selected = 0; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + if (object->title != NoTitle) + Menu_DrawTopBar(origin); + + + // black translucent background underneath file lists + Menu_BlackRectangle(origin.x + (36<<16), origin.y + (42<<16), 248<<16, 123<<16, 1|32); + + // path + Bsnprintf(tempbuf, sizeof(tempbuf), "Path: %s", object->destination); + mgametext(origin.x + object->format[0]->pos.x, origin.y + (32<<16), tempbuf); + + uint8_t column_status[2] = { 0, MT_RightSide }; + + for (i = 0; i < 2; ++i) + { + if (object->findhigh[i]) + { + CACHE1D_FIND_REC *dir; + int32_t y = 0; + const int32_t y_upper = object->format[i]->pos.y; + const int32_t y_lower = klabs(object->format[i]->bottomcutoff); + + int32_t totalHeight = 0; + for (dir = object->findhigh[i]->usera; dir; dir = dir->next) + { + y += object->font[i]->get_yline(); + totalHeight = y; + y += object->getMarginBottom(i); + } + y = 0; + + int32_t ydim_upper, ydim_lower; + if (y_upper + totalHeight > y_lower) + { + ydim_upper = ydim_from_200_16(origin.y + y_upper); + ydim_lower = ydim_from_200_16(origin.y + y_lower); + } + else + { + ydim_upper = 0; + ydim_lower = ydim-1; + } + + for (dir = object->findhigh[i]->usera; dir; dir = dir->next) + { + uint8_t status = column_status[i]; + if (dir == object->findhigh[i] && object->currentList == i) + status |= MT_Selected; + + // pal = dir->source==CACHE1D_SOURCE_ZIP ? 8 : 2 + + Menu_Run_AbbreviateNameIntoBuffer(dir->name, USERMAPENTRYLENGTH); + + const int32_t thisx = object->format[i]->pos.x; + const int32_t thisy = y - object->scrollPos[i]; + + int32_t const height = object->font[i]->get_yline(); + + if (0 <= thisy + height && thisy <= klabs(object->format[i]->bottomcutoff) - object->format[i]->pos.y) + { + status |= MT_YCenter; + + const int32_t mousex = origin.x + thisx; + const int32_t mousey = origin.y + y_upper + thisy + ((height>>17)<<16); + + vec2_t textdim = Menu_Text(mousex, mousey, object->font[i], tempbuf, status, ydim_upper, ydim_lower); + + if (MOUSEACTIVECONDITIONAL(cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, textdim.x, object->font[i]->get_yline()))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, textdim.x, object->font[i]->get_yline()))) + { + object->findhigh[i] = dir; + object->currentList = i; + + Menu_RunInput_FileSelect_MovementVerify(object); + } + + if (!m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, textdim.x, object->font[i]->get_yline())) + { + object->findhigh[i] = dir; + object->currentList = i; + + Menu_RunInput_FileSelect_MovementVerify(object); + + m_mousecaught = 1; + selected = 1; + } + } + } + + y += object->font[i]->get_yline() + object->getMarginBottom(i); + } + + Menu_RunScrollbar(cm, object->format[i], y_upper + totalHeight, &object->scrollPos[i], MenuFileSelect_scrollbar_rightedge[i], origin); + } + } + + Menu_PreDraw(cm->menuID, NULL, origin); + + if (object->title != NoTitle) + Menu_DrawTopBarCaption(object->title, origin); + + if (selected) + { + Menu_RunInput_FileSelect_Select(object); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + + break; + } + + case Panel: + { + auto object = (MenuPanel_t*)cm->object; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + if (object->title != NoTitle) + Menu_DrawTopBar(origin); + + Menu_PreDraw(cm->menuID, NULL, origin); + + if (object->title != NoTitle) + Menu_DrawTopBarCaption(object->title, origin); + + break; + } + + case Menu: + { + int32_t state; + + auto menu = (MenuMenu_t*)cm->object; + MenuEntry_t *currentry = menu->entrylist[menu->currentEntry]; + + state = Menu_DetermineSpecialState(currentry); + + if (state != 2) + { + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + if (menu->title != NoTitle) + Menu_DrawTopBar(origin); + + Menu_PreDraw(cm->menuID, currentry, origin); + + M_RunMenu_Menu(cm, menu, currentry, state, origin); + } + else + { + Menu_PreDrawBackground(cm->menuID, origin); + + if (menu->title != NoTitle) + Menu_DrawTopBar(origin); + + if (currentry->type == Option) + { + if (currentry->name) + Menu_DrawTopBarCaption(currentry->name, origin); + + Menu_PreOptionListDraw(currentry, origin); + + Menu_RunOptionList(cm, currentry, (MenuOption_t*)currentry->entry, origin); + } + else if (currentry->type == Custom2Col) + { + Menu_PreCustom2ColScreenDraw(currentry, origin); + } + } + + if ((currentry->type != Option || state != 2) && menu->title != NoTitle) + Menu_DrawTopBarCaption(menu->title, origin); + + break; + } + } + +#if !defined EDUKE32_TOUCH_DEVICES + Menu_Run_MouseReturn(cm, origin); +#endif +} + +/* +Note: When menus are exposed to scripting, care will need to be taken so that +a user cannot define an empty MenuEntryList, or one containing only spacers, +or else this function will recurse infinitely. +*/ +static MenuEntry_t *Menu_RunInput_Menu_MovementVerify(MenuMenu_t *menu) +{ + return Menu_AdjustForCurrentEntryAssignment(menu); +} + +static MenuEntry_t *Menu_RunInput_Menu_Movement(MenuMenu_t *menu, MenuMovement_t direction) +{ + if (menu->numEntries == 1) + return menu->entrylist[menu->currentEntry]; + + switch (direction) + { + case MM_End: + menu->currentEntry = menu->numEntries; + fallthrough__; + case MM_Up: + do + { + --menu->currentEntry; + if (menu->currentEntry < 0) + return Menu_RunInput_Menu_Movement(menu, MM_End); + } + while (!menu->entrylist[menu->currentEntry] || + (menu->entrylist[menu->currentEntry]->flags & MEF_Hidden) || + menu->entrylist[menu->currentEntry]->type == Spacer); + break; + + case MM_Home: + menu->currentEntry = -1; + fallthrough__; + case MM_Down: + do + { + ++menu->currentEntry; + if (menu->currentEntry >= menu->numEntries) + return Menu_RunInput_Menu_Movement(menu, MM_Home); + } + while (!menu->entrylist[menu->currentEntry] || + (menu->entrylist[menu->currentEntry]->flags & MEF_Hidden) || + menu->entrylist[menu->currentEntry]->type == Spacer); + break; + + case MM_Swap: + menu->currentColumn = !menu->currentColumn; + break; + + default: + break; + } + + return Menu_RunInput_Menu_MovementVerify(menu); +} + +static void Menu_RunInput_EntryLink_Activate(MenuEntry_t *entry) +{ + auto link = (MenuLink_t*)entry->entry; + + Menu_EntryLinkActivate(entry); + + Menu_AnimateChange(link->linkID, link->animation); +} + +static void Menu_RunInput_EntryOptionList_MovementVerify(MenuOption_t *object) +{ + const int32_t listytop = object->options->menuFormat->pos.y; + // assumes height == font->get_yline()! + const int32_t unitheight = object->options->getMarginBottom() < 0 ? (-object->options->getMarginBottom() - object->options->font->get_yline()) / object->options->numOptions : (object->options->font->get_yline() + object->options->getMarginBottom()); + const int32_t ytop = listytop + object->options->currentEntry * unitheight; + const int32_t ybottom = ytop + object->options->font->get_yline(); + + if (ybottom - object->options->scrollPos > object->options->menuFormat->bottomcutoff) + object->options->scrollPos = ybottom - object->options->menuFormat->bottomcutoff; + else if (ytop - object->options->scrollPos < listytop) + object->options->scrollPos = ytop - listytop; +} + +static void Menu_RunInput_EntryOptionList_Movement(MenuOption_t *object, MenuMovement_t direction) +{ + switch (direction) + { + case MM_Up: + --object->options->currentEntry; + if (object->options->currentEntry >= 0) + break; + fallthrough__; + case MM_End: + object->options->currentEntry = object->options->numOptions-1; + break; + + case MM_Down: + ++object->options->currentEntry; + if (object->options->currentEntry < object->options->numOptions) + break; + fallthrough__; + case MM_Home: + object->options->currentEntry = 0; + break; + + default: + break; + } + + Menu_RunInput_EntryOptionList_MovementVerify(object); +} + +static int32_t Menu_RunInput_EntryOption_Modify(MenuEntry_t *entry, MenuOption_t *object, int32_t newValueIndex) +{ + int32_t newValue = (object->options->optionValues == NULL) ? newValueIndex : object->options->optionValues[newValueIndex]; + if (!Menu_EntryOptionModify(entry, newValue)) + { + object->currentOption = newValueIndex; + + if ((int32_t*)object->data != NULL) // NULL implies the functions will handle it + *((int32_t*)object->data) = newValue; + + Menu_EntryOptionDidModify(entry); + + return 0; + } + + return -1; +} + +static int32_t Menu_RunInput_EntryOption_Movement(MenuEntry_t *entry, MenuOption_t *object, MenuMovement_t direction) +{ + int32_t newValueIndex = object->currentOption; + + switch (direction) + { + case MM_Left: + --newValueIndex; + if (newValueIndex >= 0) + break; + fallthrough__; + case MM_AllTheWayRight: + newValueIndex = object->options->numOptions-1; + break; + + case MM_Right: + ++newValueIndex; + if (newValueIndex < object->options->numOptions) + break; + fallthrough__; + case MM_AllTheWayLeft: + newValueIndex = 0; + break; + + default: + break; + } + + return Menu_RunInput_EntryOption_Modify(entry, object, newValueIndex); +} + +static int32_t Menu_RunInput_EntryOption_Activate(MenuEntry_t *entry, MenuOption_t *object) +{ + if (object->options->features & 2) + return Menu_RunInput_EntryOption_Movement(entry, object, MM_Right); + else + { + object->options->currentEntry = object->currentOption >= 0 ? object->currentOption : 0; + Menu_RunInput_EntryOptionList_MovementVerify(object); + + return 0; + } +} + +static int32_t Menu_RunInput_EntryOptionList_Activate(MenuEntry_t *entry, MenuOption_t *object) +{ + if (!Menu_RunInput_EntryOption_Modify(entry, object, object->options->currentEntry)) + { + object->options->currentEntry = -1; + + return 0; + } + + return -1; +} + +static void Menu_RunInput_EntryCustom2Col_Activate(MenuEntry_t *entry) +{ + auto object = (MenuCustom2Col_t*)entry->entry; + + Menu_Custom2ColScreen(/*entry*/); + + object->screenOpen = 1; +} + +static void Menu_RunInput_EntryRangeInt32_MovementVerify(MenuEntry_t *entry, MenuRangeInt32_t *object, int32_t newValue) +{ + if (!Menu_EntryRangeInt32Modify(entry, newValue)) + *object->variable = newValue; +} + +static void Menu_RunInput_EntryRangeInt32_MovementArbitrary(MenuEntry_t *entry, MenuRangeInt32_t *object, int32_t newValue) +{ + if (object->flags & EnforceIntervals) + { + int32_t const range = object->max - object->min; + int32_t const maxInterval = object->steps - 1; + int32_t const newValueIndex = roundscale(newValue - object->min, maxInterval, range); + newValue = newValueIndex * range / maxInterval + object->min; + } + + Menu_RunInput_EntryRangeInt32_MovementVerify(entry, object, newValue); +} + +static void Menu_RunInput_EntryRangeInt32_Movement(MenuEntry_t *entry, MenuRangeInt32_t *object, MenuMovement_t direction) +{ + int32_t const oldValue = *object->variable; + int32_t const range = object->max - object->min; + int32_t const maxInterval = object->steps - 1; + int32_t const oldValueIndex = roundscale(oldValue - object->min, maxInterval, range); + int32_t const oldValueQuantized = oldValueIndex * range / maxInterval + object->min; + int32_t newValueIndex = oldValueIndex; + + switch (direction) + { + case MM_Left: + if (oldValueQuantized >= oldValue) + --newValueIndex; + if (newValueIndex >= 0) + break; + fallthrough__; + case MM_AllTheWayLeft: + newValueIndex = 0; + break; + + case MM_Right: + if (oldValueQuantized <= oldValue) + ++newValueIndex; + if (newValueIndex <= maxInterval) + break; + fallthrough__; + case MM_AllTheWayRight: + newValueIndex = maxInterval; + break; + + default: + break; + } + + int32_t const newValue = newValueIndex * range / maxInterval + object->min; + Menu_RunInput_EntryRangeInt32_MovementVerify(entry, object, newValue); +} + +static void Menu_RunInput_EntryRangeFloat_MovementVerify(MenuEntry_t *entry, MenuRangeFloat_t *object, float newValue) +{ + if (!Menu_EntryRangeFloatModify(entry, newValue)) + { + *object->variable = newValue; + Menu_EntryRangeFloatDidModify(entry); + } +} + +static void Menu_RunInput_EntryRangeFloat_MovementArbitrary(MenuEntry_t *entry, MenuRangeFloat_t *object, float newValue) +{ + if (object->flags & EnforceIntervals) + { + float const range = object->max - object->min; + float const maxInterval = (float)(object->steps - 1); + float const newValueIndex = rintf((newValue - object->min) * maxInterval / range); + newValue = newValueIndex * range / maxInterval + object->min; + } + + Menu_RunInput_EntryRangeFloat_MovementVerify(entry, object, newValue); +} + +static void Menu_RunInput_EntryRangeFloat_Movement(MenuEntry_t *entry, MenuRangeFloat_t *object, MenuMovement_t direction) +{ + float const oldValue = *object->variable; + float const range = object->max - object->min; + float const maxInterval = (float)(object->steps - 1); + float const oldValueIndexUnrounded = (oldValue - object->min) * maxInterval / range; + float const oldValueIndex = rintf(oldValueIndexUnrounded); + float const oldValueQuantized = oldValueIndex * range / maxInterval + object->min; + float newValueIndex = oldValueIndex; + + switch (direction) + { + case MM_Left: + if (oldValueQuantized >= oldValue) + newValueIndex -= 1.f; + if (newValueIndex >= 0.f) + break; + fallthrough__; + case MM_AllTheWayLeft: + newValueIndex = 0.f; + break; + + case MM_Right: + if (oldValueQuantized <= oldValue) + newValueIndex += 1.f; + if (newValueIndex <= maxInterval) + break; + fallthrough__; + case MM_AllTheWayRight: + newValueIndex = maxInterval; + break; + + default: + break; + } + + float const newValue = newValueIndex * range / maxInterval + object->min; + Menu_RunInput_EntryRangeFloat_MovementVerify(entry, object, newValue); +} + +#ifdef MENU_ENABLE_RANGEDOUBLE +static void Menu_RunInput_EntryRangeDouble_MovementVerify(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, double newValue) +{ + if (!Menu_EntryRangeDoubleModify(/*entry, newValue*/)) + *object->variable = newValue; +} + +static void Menu_RunInput_EntryRangeDouble_MovementArbitrary(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, double newValue) +{ + if (object->flags & EnforceIntervals) + { + double const range = object->max - object->min; + double const maxInterval = object->steps - 1; + double const newValueIndex = rint((newValue - object->min) * maxInterval / range); + newValue = newValueIndex * range / maxInterval + object->min; + } + + Menu_RunInput_EntryRangeDouble_MovementVerify(/*entry, */object, newValue); +} + +static void Menu_RunInput_EntryRangeDouble_Movement(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, MenuMovement_t direction) +{ + double const oldValue = *object->variable; + double const range = object->max - object->min; + double const maxInterval = object->steps - 1; + double const oldValueIndex = rint((oldValue - object->min) * maxInterval / range); + double const oldValueQuantized = oldValueIndex * range / maxInterval + object->min; + double newValueIndex = oldValueIndex; + + switch (direction) + { + case MM_Left: + if (oldValueQuantized >= oldValue) + newValueIndex -= 1.; + if (newValueIndex >= 0.) + break; + fallthrough__; + case MM_AllTheWayLeft: + newValueIndex = 0.; + break; + + case MM_Right: + if (oldValueQuantized <= oldValue) + newValueIndex += 1.; + if (newValueIndex <= maxInterval) + break; + fallthrough__; + case MM_AllTheWayRight: + newValueIndex = maxInterval; + break; + + default: + break; + } + + double const newValue = newValueIndex * range / maxInterval + object->min; + Menu_RunInput_EntryRangeDouble_MovementVerify(/*entry, */object, newValue); +} +#endif + +static void Menu_RunInput_EntryString_Activate(MenuEntry_t *entry) +{ + auto object = (MenuString_t*)entry->entry; + + if (object->variable) + strncpy(typebuf, object->variable, TYPEBUFSIZE); + else + typebuf[0] = '\0'; + object->editfield = typebuf; + + // this limitation is an arbitrary implementation detail + if (object->bufsize > TYPEBUFSIZE) + object->bufsize = TYPEBUFSIZE; + + Menu_EntryStringActivate(/*entry*/); + Menu_StartTextInput(); +} + +static void Menu_RunInput_EntryString_Submit(/*MenuEntry_t *entry, */MenuString_t *object) +{ + if (!Menu_EntryStringSubmit(/*entry, */object->editfield)) + { + if (object->variable) + strncpy(object->variable, object->editfield, object->bufsize); + } + + object->editfield = NULL; + Menu_StopTextInput(); +} + +static void Menu_RunInput_EntryString_Cancel(/*MenuEntry_t *entry, */MenuString_t *object) +{ + Menu_EntryStringCancel(/*entry*/); + + object->editfield = NULL; + Menu_StopTextInput(); +} + +static void Menu_RunInput_FileSelect_MovementVerify(MenuFileSelect_t *object) +{ + const int32_t listytop = object->format[object->currentList]->pos.y; + const int32_t listybottom = klabs(object->format[object->currentList]->bottomcutoff); + const int32_t ytop = listytop + object->findhigh[object->currentList]->type * (object->font[object->currentList]->get_yline() + object->getMarginBottom(object->currentList)); + const int32_t ybottom = ytop + object->font[object->currentList]->get_yline(); + + if (ybottom - object->scrollPos[object->currentList] > listybottom) + object->scrollPos[object->currentList] = ybottom - listybottom; + else if (ytop - object->scrollPos[object->currentList] < listytop) + object->scrollPos[object->currentList] = ytop - listytop; +} + +static void Menu_RunInput_FileSelect_Movement(MenuFileSelect_t *object, MenuMovement_t direction) +{ + switch (direction) + { + case MM_Up: + if (!object->findhigh[object->currentList]) + break; + if (object->findhigh[object->currentList]->prev) + { + object->findhigh[object->currentList] = object->findhigh[object->currentList]->prev; + break; + } + fallthrough__; + case MM_End: + object->findhigh[object->currentList] = object->findhigh[object->currentList]->userb; + break; + + case MM_Down: + if (!object->findhigh[object->currentList]) + break; + if (object->findhigh[object->currentList]->next) + { + object->findhigh[object->currentList] = object->findhigh[object->currentList]->next; + break; + } + fallthrough__; + case MM_Home: + object->findhigh[object->currentList] = object->findhigh[object->currentList]->usera; + break; + + case MM_Swap: + object->currentList = !object->currentList; + break; + + default: + break; + } + + Menu_RunInput_FileSelect_MovementVerify(object); +} + +static void Menu_RunInput_FileSelect_Select(MenuFileSelect_t *object) +{ + if (!object->findhigh[object->currentList]) + return; + + Bstrcat(object->destination, object->findhigh[object->currentList]->name); + + if (object->currentList == 0) + { + Bstrcat(object->destination, "/"); + Bcorrectfilename(object->destination, 1); + + Menu_FileSelectInit(object); + } + else + { + Menu_FileSelect(1); + } +} + +static void Menu_RunInput(Menu_t *cm) +{ + switch (cm->type) + { + case Panel: + { + auto panel = (MenuPanel_t*)cm->object; + + if (I_ReturnTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + } + else if (I_PanelUp()) + { + I_PanelUpClear(); + + S_PlaySound(KICK_HIT); + Menu_AnimateChange(panel->previousID, panel->previousAnimation); + } + else if (I_PanelDown() || Menu_RunInput_MouseAdvance()) + { + I_PanelDownClear(); + m_mousecaught = 1; + + S_PlaySound(KICK_HIT); + Menu_AnimateChange(panel->nextID, panel->nextAnimation); + } + break; + } + + case TextForm: + { + auto object = (MenuTextForm_t*)cm->object; + int32_t hitstate = I_EnterText(object->input, object->bufsize-1, 0); + + if (hitstate == -1 || Menu_RunInput_MouseReturn()) + { + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + object->input = NULL; + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + Menu_StopTextInput(); + } + else if (hitstate == 1 || Menu_RunInput_MouseAdvance()) + { + m_mousecaught = 1; + + Menu_TextFormSubmit(object->input); + + object->input = NULL; + Menu_StopTextInput(); + } + break; + } + + case FileSelect: + { + auto object = (MenuFileSelect_t*)cm->object; + + if (I_ReturnTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + object->destination[0] = 0; + + Menu_FileSelect(0); + + Menu_AnimateChange(cm->parentID, MA_Return); + } + else if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_FileSelect_Select(object); + + S_PlaySound(PISTOL_BODYHIT); + } + else if (KB_KeyPressed(sc_Home)) + { + KB_ClearKeyDown(sc_Home); + + Menu_RunInput_FileSelect_Movement(object, MM_Home); + + S_PlaySound(KICK_HIT); + } + else if (KB_KeyPressed(sc_End)) + { + KB_ClearKeyDown(sc_End); + + Menu_RunInput_FileSelect_Movement(object, MM_End); + + S_PlaySound(KICK_HIT); + } + else if (KB_KeyPressed(sc_PgUp)) + { + int32_t i; + + CACHE1D_FIND_REC *seeker = object->findhigh[object->currentList]; + + KB_ClearKeyDown(sc_PgUp); + + for (i = 0; i < 6; ++i) + { + if (seeker && seeker->prev) + seeker = seeker->prev; + } + + if (seeker) + { + object->findhigh[object->currentList] = seeker; + + Menu_RunInput_FileSelect_MovementVerify(object); + + S_PlaySound(KICK_HIT); + } + } + else if (KB_KeyPressed(sc_PgDn)) + { + int32_t i; + + CACHE1D_FIND_REC *seeker = object->findhigh[object->currentList]; + + KB_ClearKeyDown(sc_PgDn); + + for (i = 0; i < 6; ++i) + { + if (seeker && seeker->next) + seeker = seeker->next; + } + + if (seeker) + { + object->findhigh[object->currentList] = seeker; + + Menu_RunInput_FileSelect_MovementVerify(object); + + S_PlaySound(KICK_HIT); + } + } + else if (I_MenuLeft() || I_MenuRight()) + { + I_MenuLeftClear(); + I_MenuRightClear(); + + if ((object->currentList ? object->fnlist.numdirs : object->fnlist.numfiles) > 0) + { + Menu_RunInput_FileSelect_Movement(object, MM_Swap); + + S_PlaySound(KICK_HIT); + } + } + else if (I_MenuUp()) + { + I_MenuUpClear(); + + Menu_RunInput_FileSelect_Movement(object, MM_Up); + + S_PlaySound(KICK_HIT); + } + else if (I_MenuDown()) + { + I_MenuDownClear(); + + Menu_RunInput_FileSelect_Movement(object, MM_Down); + + S_PlaySound(KICK_HIT); + } + else + { + // JBF 20040208: seek to first name matching pressed character + char ch2, ch; + ch = KB_GetCh(); + if (ch > 0 && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'))) + { + CACHE1D_FIND_REC *seeker = object->findhigh[object->currentList]->usera; + if (ch >= 'a') + ch -= ('a'-'A'); + while (seeker) + { + ch2 = seeker->name[0]; + if (ch2 >= 'a' && ch2 <= 'z') + ch2 -= ('a'-'A'); + if (ch2 == ch) + break; + seeker = seeker->next; + } + if (seeker) + { + object->findhigh[object->currentList] = seeker; + + Menu_RunInput_FileSelect_MovementVerify(object); + + S_PlaySound(KICK_HIT); + } + } + } + + Menu_PreInput(NULL); + break; + } + + case Message: + if (I_ReturnTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + } + + if (I_CheckAllInput()) + { + auto message = (MenuMessage_t*)cm->object; + + I_ClearAllInput(); + + S_PlaySound(EXITMENUSOUND); + + Menu_AnimateChange(message->linkID, message->animation); + } + + Menu_PreInput(NULL); + break; + + case Verify: + if (I_ReturnTrigger() || KB_KeyPressed(sc_N) || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + KB_ClearKeyDown(sc_N); + m_mousecaught = 1; + + Menu_Verify(0); + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + + S_PlaySound(EXITMENUSOUND); + } + + if (I_AdvanceTrigger() || KB_KeyPressed(sc_Y) || Menu_RunInput_MouseAdvance()) + { + auto verify = (MenuVerify_t*)cm->object; + + I_AdvanceTriggerClear(); + KB_ClearKeyDown(sc_Y); + m_mousecaught = 1; + + Menu_Verify(1); + + Menu_AnimateChange(verify->linkID, verify->animation); + + S_PlaySound(PISTOL_BODYHIT); + } + + Menu_PreInput(NULL); + break; + + case Menu: + { + int32_t state; + + auto menu = (MenuMenu_t*)cm->object; + MenuEntry_t *currentry = menu->entrylist[menu->currentEntry]; + + state = Menu_DetermineSpecialState(currentry); + + if (state == 0) + { + if (currentry != NULL) + switch (currentry->type) + { + case Dummy: + case Spacer: + break; + case Link: + if (currentry->flags & MEF_Disabled) + break; + if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_EntryLink_Activate(currentry); + + if (g_player[myconnectindex].ps->gm&MODE_MENU) // for skill selection + S_PlaySound(PISTOL_BODYHIT); + } + break; + case Option: + { + auto object = (MenuOption_t*)currentry->entry; + + if (currentry->flags & MEF_Disabled) + break; + + if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_EntryOption_Activate(currentry, object); + + S_PlaySound(PISTOL_BODYHIT); + } + else if (I_MenuRight()) + { + I_MenuRightClear(); + + Menu_RunInput_EntryOption_Movement(currentry, object, MM_Right); + + S_PlaySound(PISTOL_BODYHIT); + } + else if (I_MenuLeft()) + { + I_MenuLeftClear(); + + Menu_RunInput_EntryOption_Movement(currentry, object, MM_Left); + + S_PlaySound(PISTOL_BODYHIT); + } + } + break; + case Custom2Col: + if (I_MenuLeft() || I_MenuRight()) + { + I_MenuLeftClear(); + I_MenuRightClear(); + + Menu_RunInput_Menu_Movement(menu, MM_Swap); + + S_PlaySound(KICK_HIT); + } + + if (currentry->flags & MEF_Disabled) + break; + + if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_EntryCustom2Col_Activate(currentry); + + S_PlaySound(PISTOL_BODYHIT); + } + break; + case RangeInt32: + { + auto object = (MenuRangeInt32_t*)currentry->entry; + + if (currentry->flags & MEF_Disabled) + break; + + if (I_SliderLeft()) + { + I_SliderLeftClear(); + + Menu_RunInput_EntryRangeInt32_Movement(currentry, object, MM_Left); + + S_PlaySound(KICK_HIT); + } + else if (I_SliderRight()) + { + I_SliderRightClear(); + + Menu_RunInput_EntryRangeInt32_Movement(currentry, object, MM_Right); + + S_PlaySound(KICK_HIT); + } + break; + } + case RangeFloat: + { + auto object = (MenuRangeFloat_t*)currentry->entry; + + if (currentry->flags & MEF_Disabled) + break; + + if (I_SliderLeft()) + { + I_SliderLeftClear(); + + Menu_RunInput_EntryRangeFloat_Movement(currentry, object, MM_Left); + + S_PlaySound(KICK_HIT); + } + else if (I_SliderRight()) + { + I_SliderRightClear(); + + Menu_RunInput_EntryRangeFloat_Movement(currentry, object, MM_Right); + + S_PlaySound(KICK_HIT); + } + break; + } +#ifdef MENU_ENABLE_RANGEDOUBLE + case RangeDouble: + { + MenuRangeDouble_t *object = (MenuRangeDouble_t*)currentry->entry; + + if (currentry->flags & MEF_Disabled) + break; + + if (I_SliderLeft()) + { + I_SliderLeftClear(); + + Menu_RunInput_EntryRangeDouble_Movement(/*currentry, */object, MM_Left); + + S_PlaySound(KICK_HIT); + } + else if (I_SliderRight()) + { + I_SliderRightClear(); + + Menu_RunInput_EntryRangeDouble_Movement(/*currentry, */object, MM_Right); + + S_PlaySound(KICK_HIT); + } + break; + } +#endif + + case String: + { + if (currentry->flags & MEF_Disabled) + break; + + if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_EntryString_Activate(currentry); + + S_PlaySound(PISTOL_BODYHIT); + } + + break; + } + } + + if (I_ReturnTrigger() || I_EscapeTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + I_EscapeTriggerClear(); + m_mousecaught = 1; + + if (cm->parentID != MENU_CLOSE || (g_player[myconnectindex].ps->gm & MODE_GAME)) + S_PlaySound(EXITMENUSOUND); + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + } + else if (KB_KeyPressed(sc_Home)) + { + KB_ClearKeyDown(sc_Home); + + S_PlaySound(KICK_HIT); + + currentry = Menu_RunInput_Menu_Movement(menu, MM_Home); + } + else if (KB_KeyPressed(sc_End)) + { + KB_ClearKeyDown(sc_End); + + S_PlaySound(KICK_HIT); + + currentry = Menu_RunInput_Menu_Movement(menu, MM_End); + } + else if (I_MenuUp()) + { + I_MenuUpClear(); + + S_PlaySound(KICK_HIT); + + currentry = Menu_RunInput_Menu_Movement(menu, MM_Up); + } + else if (I_MenuDown()) + { + I_MenuDownClear(); + + S_PlaySound(KICK_HIT); + + currentry = Menu_RunInput_Menu_Movement(menu, MM_Down); + } + + if (currentry != NULL) + Menu_PreInput(currentry); + } + else if (state == 1) + { + if (currentry->type == String) + { + auto object = (MenuString_t*)currentry->entry; + + int32_t hitstate = I_EnterText(object->editfield, object->bufsize-1, object->flags); + + if (hitstate == -1 || Menu_RunInput_MouseReturn()) + { + m_mousecaught = 1; + + Menu_RunInput_EntryString_Cancel(/*currentry, */object); + + S_PlaySound(EXITMENUSOUND); + } + else if (hitstate == 1) + { + Menu_RunInput_EntryString_Submit(/*currentry, */object); + + S_PlaySound(PISTOL_BODYHIT); + } + } + } + else if (state == 2) + { + if (currentry->type == Option) + { + auto object = (MenuOption_t*)currentry->entry; + + if (I_ReturnTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + object->options->currentEntry = -1; + } + else if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + if (!Menu_RunInput_EntryOptionList_Activate(currentry, object)) + S_PlaySound(PISTOL_BODYHIT); + } + else if (KB_KeyPressed(sc_Home)) + { + KB_ClearKeyDown(sc_Home); + + S_PlaySound(KICK_HIT); + + Menu_RunInput_EntryOptionList_Movement(object, MM_Home); + } + else if (KB_KeyPressed(sc_End)) + { + KB_ClearKeyDown(sc_End); + + S_PlaySound(KICK_HIT); + + Menu_RunInput_EntryOptionList_Movement(object, MM_End); + } + else if (I_MenuUp()) + { + I_MenuUpClear(); + + S_PlaySound(KICK_HIT); + + Menu_RunInput_EntryOptionList_Movement(object, MM_Up); + } + else if (I_MenuDown()) + { + I_MenuDownClear(); + + S_PlaySound(KICK_HIT); + + Menu_RunInput_EntryOptionList_Movement(object, MM_Down); + } + } + else if (currentry->type == Custom2Col) + { + if (I_EscapeTrigger() || Menu_RunInput_MouseReturn()) + { + I_EscapeTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + ((MenuCustom2Col_t*)currentry->entry)->screenOpen = 0; + } + else if (Menu_PreCustom2ColScreen(currentry)) + ((MenuCustom2Col_t*)currentry->entry)->screenOpen = 0; + } + } + + break; + } + } +} + +// This function MUST NOT RECURSE. That is why Menu_Run is separate. +void M_DisplayMenus(void) +{ + vec2_t origin = { 0, 0 }, previousOrigin = { 0, 0 }; + + Net_GetPackets(); + + if ((g_player[myconnectindex].ps->gm&MODE_MENU) == 0) + { + walock[TILE_LOADSHOT] = 1; + return; + } + + if (!Menu_IsTextInput(m_currentMenu) && KB_KeyPressed(sc_Q)) + Menu_AnimateChange(MENU_QUIT, MA_Advance); + + int32_t mousestatus = mouseReadAbs(&m_mousepos, &g_mouseAbs); + if (mousestatus && g_mouseClickState == MOUSE_PRESSED) + m_mousedownpos = m_mousepos; + + Menu_RunInput(m_currentMenu); + + g_player[myconnectindex].ps->gm &= (0xff-MODE_TYPE); + // g_player[myconnectindex].ps->fta = 0; + + int32_t const backgroundOK = ud.menubackground && Menu_BlackTranslucentBackgroundOK(g_currentMenu); + + // need EVENT_DISPLAYMENUBACKGROUND here + + if (!FURY && ((g_player[myconnectindex].ps->gm&MODE_GAME) || ud.recstat==2) && backgroundOK) + videoFadeToBlack(1); + + if (Menu_UpdateScreenOK(g_currentMenu)) + G_UpdateScreenArea(); + +#if !defined EDUKE32_TOUCH_DEVICES + if (m_menuchange_watchpoint > 0) + m_menuchange_watchpoint++; +#endif + + if (m_parentMenu) + { + ud.returnvar[0] = origin.x; + ud.returnvar[1] = origin.y; + if (m_parentMenu->type == Menu) + { + ud.returnvar[2] = ((MenuMenu_t *)m_parentMenu->object)->currentEntry; + if (m_parentMenu->menuID == MENU_NEWGAMECUSTOMSUB) + ud.returnvar[3] = M_NEWGAMECUSTOM.currentEntry; + } + VM_OnEventWithReturn(EVENT_DISPLAYINACTIVEMENU, g_player[screenpeek].ps->i, screenpeek, m_parentMenu->menuID); + origin.x = ud.returnvar[0]; + origin.y = ud.returnvar[1]; + } + + // Determine animation values. + if (totalclock < m_animation.start + m_animation.length) + { + const int32_t screenwidth = scale(240<<16, xdim, ydim); + + origin.x = mulscale15(screenwidth, m_animation.in(&m_animation)); + previousOrigin.x = mulscale15(screenwidth, m_animation.out(&m_animation)); + + ud.returnvar[0] = previousOrigin.x; + ud.returnvar[1] = previousOrigin.y; + if (m_animation.previous->type == Menu) + { + ud.returnvar[2] = ((MenuMenu_t *)m_animation.previous->object)->currentEntry; + if (m_animation.previous->menuID == MENU_NEWGAMECUSTOMSUB) + ud.returnvar[3] = M_NEWGAMECUSTOM.currentEntry; + } + VM_OnEventWithReturn(EVENT_DISPLAYINACTIVEMENU, g_player[screenpeek].ps->i, screenpeek, m_animation.previous->menuID); + previousOrigin.x = ud.returnvar[0]; + previousOrigin.y = ud.returnvar[1]; + } + + ud.returnvar[0] = origin.x; + ud.returnvar[1] = origin.y; + if (m_currentMenu->type == Menu) + { + ud.returnvar[2] = ((MenuMenu_t *)m_currentMenu->object)->currentEntry; + if (g_currentMenu == MENU_NEWGAMECUSTOMSUB) + ud.returnvar[3] = M_NEWGAMECUSTOM.currentEntry; + } + VM_OnEventWithReturn(EVENT_DISPLAYMENU, g_player[screenpeek].ps->i, screenpeek, g_currentMenu); + origin.x = ud.returnvar[0]; + origin.y = ud.returnvar[1]; + + if (m_parentMenu && backgroundOK) + { + Menu_Run(m_parentMenu, origin); + } + + // hack; need EVENT_DISPLAYMENUBACKGROUND above + if (FURY && ((g_player[myconnectindex].ps->gm&MODE_GAME) || ud.recstat==2 || m_parentMenu != NULL) && backgroundOK) + videoFadeToBlack(1); + + // Display the menu, with a transition animation if applicable. + if (totalclock < m_animation.start + m_animation.length) + { + Menu_Run(m_animation.previous, previousOrigin); + Menu_Run(m_animation.current, origin); + } + else + Menu_Run(m_currentMenu, origin); + +#if !defined EDUKE32_TOUCH_DEVICES + if (m_menuchange_watchpoint >= 3) + m_menuchange_watchpoint = 0; +#endif + + if (m_parentMenu) + { + ud.returnvar[0] = origin.x; + ud.returnvar[1] = origin.y; + if (m_parentMenu->type == Menu) + { + ud.returnvar[2] = ((MenuMenu_t *)m_parentMenu->object)->currentEntry; + if (m_parentMenu->menuID == MENU_NEWGAMECUSTOMSUB) + ud.returnvar[3] = M_NEWGAMECUSTOM.currentEntry; + } + VM_OnEventWithReturn(EVENT_DISPLAYINACTIVEMENUREST, g_player[screenpeek].ps->i, screenpeek, m_parentMenu->menuID); + } + + if (totalclock < m_animation.start + m_animation.length) + { + ud.returnvar[0] = previousOrigin.x; + ud.returnvar[1] = previousOrigin.y; + if (m_animation.previous->type == Menu) + { + ud.returnvar[2] = ((MenuMenu_t *)m_animation.previous->object)->currentEntry; + if (m_animation.previous->menuID == MENU_NEWGAMECUSTOMSUB) + ud.returnvar[3] = M_NEWGAMECUSTOM.currentEntry; + } + VM_OnEventWithReturn(EVENT_DISPLAYINACTIVEMENUREST, g_player[screenpeek].ps->i, screenpeek, m_animation.previous->menuID); + } + + ud.returnvar[0] = origin.x; + ud.returnvar[1] = origin.y; + if (m_currentMenu->type == Menu) + { + ud.returnvar[2] = ((MenuMenu_t *)m_currentMenu->object)->currentEntry; + if (g_currentMenu == MENU_NEWGAMECUSTOMSUB) + ud.returnvar[3] = M_NEWGAMECUSTOM.currentEntry; + } + VM_OnEventWithReturn(EVENT_DISPLAYMENUREST, g_player[screenpeek].ps->i, screenpeek, g_currentMenu); + +#if !defined EDUKE32_TOUCH_DEVICES + if (tilesiz[CROSSHAIR].x > 0 && mousestatus) +#else + if (mousestatus) +#endif + { +#if !defined EDUKE32_TOUCH_DEVICES + if (!MOUSEACTIVECONDITION) + m_mousewake_watchpoint = 1; +#endif + + if (MOUSEACTIVECONDITIONAL(mouseAdvanceClickState()) || m_mousepos.x != m_prevmousepos.x || m_mousepos.y != m_prevmousepos.y) + { + m_prevmousepos = m_mousepos; + m_mouselastactivity = (int32_t) totalclock; + } +#if !defined EDUKE32_TOUCH_DEVICES + else + m_mousewake_watchpoint = 0; +#endif + + m_mousecaught = 0; + } + else + { + m_mouselastactivity = -M_MOUSETIMEOUT; + +#if !defined EDUKE32_TOUCH_DEVICES + m_mousewake_watchpoint = 0; +#endif + } + +#ifndef EDUKE32_TOUCH_DEVICES + // Display the mouse cursor, except on touch devices. + if (MOUSEACTIVECONDITION) + { + if (VM_HaveEvent(EVENT_DISPLAYCURSOR)) + { + ud.returnvar[0] = m_mousepos.x; + ud.returnvar[1] = m_mousepos.y; + ud.returnvar[2] = CURSORALPHA; + } + int32_t a = VM_OnEventWithReturn(EVENT_DISPLAYCURSOR, g_player[screenpeek].ps->i, screenpeek, CROSSHAIR); + + if ((unsigned) a < MAXTILES) + { + vec2_t cursorpos = m_mousepos; + int32_t z = 65536; + uint8_t p = CROSSHAIR_PAL; + uint32_t o = 2|8; + + auto const oyxaspect = yxaspect; + int32_t alpha; + if (FURY) + { + renderSetAspect(viewingrange, 65536); + cursorpos.x = scale(cursorpos.x - (320<<15), ydim << 2, xdim * 3) + (320<<15); + cursorpos.y = scale(cursorpos.y - (200<<15), (ydim << 2) * 6, (xdim * 3) * 5) + (200<<15); + z = scale(32768, ydim << 2, xdim * 3); + p = 0; + o |= 1024; + alpha = MOUSEALPHA; + } + else + { + alpha = CURSORALPHA; + } + + rotatesprite_fs_alpha(cursorpos.x, cursorpos.y, z, 0, a, 0, p, o, alpha); + + if (FURY) + renderSetAspect(viewingrange, oyxaspect); + } + } + else + g_mouseClickState = MOUSE_IDLE; +#endif + + if ((g_player[myconnectindex].ps->gm&MODE_MENU) != MODE_MENU) + { + G_UpdateScreenArea(); + CAMERACLOCK = (int32_t) totalclock; + CAMERADIST = 65536; + } +} diff --git a/source/blood/src/menus.h b/source/blood/src/menus.h new file mode 100644 index 000000000..fc3a2fd4b --- /dev/null +++ b/source/blood/src/menus.h @@ -0,0 +1,542 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef menus_h_ +#define menus_h_ + +#include "compat.h" +#include "cache1d.h" +#include "pragmas.h" +#include "common.h" +#include "blood.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined EDUKE32_TOUCH_DEVICES +# define EDUKE32_SIMPLE_MENU +# define EDUKE32_ANDROID_MENU +#endif + +// #define EDUKE32_SIMPLE_MENU + +enum MenuIndex_t { + MENU_NULL = INT32_MIN, // sentinel for "do nothing" + MENU_CLOSE = -2, // sentinel for "close the menu"/"no menu" + MENU_PREVIOUS = -1, // sentinel for "go to previous menu" + MENU_MAIN = 0, + MENU_MAIN_INGAME = 50, + MENU_EPISODE = 100, + MENU_USERMAP = 101, + MENU_NEWGAMECUSTOM = 102, + MENU_NEWGAMECUSTOMSUB = 103, + MENU_SKILL = 110, + MENU_GAMESETUP = 200, + MENU_OPTIONS = 202, + MENU_VIDEOSETUP = 203, + MENU_KEYBOARDSETUP = 204, + MENU_MOUSESETUP = 205, + MENU_JOYSTICKSETUP = 206, + MENU_JOYSTICKBTNS = 207, + MENU_JOYSTICKAXES = 208, + MENU_KEYBOARDKEYS = 209, + MENU_MOUSEBTNS = 210, + MENU_MOUSEADVANCED = 212, + MENU_JOYSTICKAXIS = 213, + MENU_TOUCHSETUP = 214, + MENU_TOUCHSENS = 215, + MENU_TOUCHBUTTONS = 216, + MENU_CONTROLS = 220, + MENU_POLYMOST = 230, + MENU_COLCORR = 231, + MENU_COLCORR_INGAME = 232, + MENU_SCREENSETUP = 233, + MENU_DISPLAYSETUP = 234, + MENU_POLYMER = 240, + MENU_LOAD = 300, + MENU_SAVE = 350, + MENU_STORY = 400, + MENU_F1HELP = 401, + MENU_QUIT = 500, + MENU_QUITTOTITLE = 501, + MENU_QUIT_INGAME = 502, + MENU_NETSETUP = 600, + MENU_NETWAITMASTER = 601, + MENU_NETWAITVOTES = 603, + MENU_SOUND = 700, + MENU_SOUND_INGAME = 701, + MENU_ADVSOUND = 702, + MENU_SAVESETUP = 750, + MENU_SAVECLEANVERIFY = 751, + MENU_RESETSTATSVERIFY = 752, + MENU_CHEATS = 800, + MENU_CHEATENTRY = 801, + MENU_CHEAT_WARP = 802, + MENU_CHEAT_SKILL = 803, + MENU_CREDITS = 990, + MENU_CREDITS2 = 991, + MENU_CREDITS3 = 992, + MENU_CREDITS4 = 993, + MENU_CREDITS5 = 994, + MENU_LOADVERIFY = 1000, + MENU_LOADDELVERIFY = 1100, + MENU_NEWVERIFY = 1500, + MENU_SAVEVERIFY = 2000, + MENU_SAVEDELVERIFY = 2100, + MENU_COLCORRRESETVERIFY = 2200, + MENU_KEYSRESETVERIFY = 2201, + MENU_KEYSCLASSICVERIFY = 2202, + MENU_JOYSTANDARDVERIFY = 2203, + MENU_JOYPROVERIFY = 2204, + MENU_JOYCLEARVERIFY = 2205, + MENU_ADULTPASSWORD = 10001, + MENU_RESETPLAYER = 15000, + MENU_BUYDUKE = 20000, + MENU_NETWORK = 20001, + MENU_PLAYER = 20002, + MENU_MACROS = 20004, + MENU_NETHOST = 20010, + MENU_NETOPTIONS = 20011, + MENU_NETUSERMAP = 20012, + MENU_NETJOIN = 20020, +}; + + + +typedef int32_t MenuID_t; + + +typedef enum MenuAnimationType_t +{ // Note: This enum is for logical categories, not visual types. + MA_None, + MA_Return, + MA_Advance, +} MenuAnimationType_t; + +// a subset of screentext parameters, restricted because menus require accessibility +typedef struct MenuFont_t +{ +// int32_t xspace, yline; + vec2_t emptychar, between; + int32_t zoom; + int32_t cursorLeftPosition, cursorCenterPosition, cursorScale; + int32_t textflags; + int16_t tilenum; + // selected shade glows, deselected shade is used by Blood, disabled shade is used by SW + int8_t shade_deselected, shade_disabled; + uint8_t pal; + uint8_t pal_selected, pal_deselected, pal_disabled; + uint8_t pal_selected_right, pal_deselected_right, pal_disabled_right; + + int32_t get_yline() const { return mulscale16(emptychar.y, zoom); } +} MenuFont_t; + + + +typedef enum MenuEntryType_t +{ + Dummy, + Link, + Option, + Custom2Col, + RangeInt32, + RangeFloat, +#ifdef MENU_ENABLE_RANGEDOUBLE + RangeDouble, +#endif + String, + Spacer, +} MenuEntryType_t; + +typedef struct MenuEntryFormat_t +{ + int32_t marginBottom; + int32_t indent; + int32_t width; // 0: center, >0: width of the label column (left-aligned options), <0: -width of everything (right-aligned) +} MenuEntryFormat_t; + + +typedef struct MenuMenuFormat_t +{ + vec2_t pos; + int32_t bottomcutoff; // >0: the bottom edge of the menu before automatic scrolling kicks in, <0: -total height for vertical justification +} MenuMenuFormat_t; + +typedef struct MenuLink_t +{ + // traits + MenuID_t linkID; + MenuAnimationType_t animation; +} MenuLink_t; +typedef struct MenuOptionSet_t +{ + // traits + char const **optionNames; + int32_t *optionValues; // If NULL, the identity of currentOption is assumed. + + // pop-up list appearance + MenuMenuFormat_t *menuFormat; + MenuEntryFormat_t *entryFormat; + MenuFont_t *font; + + // traits + int32_t numOptions; + + // pop-up list state + int32_t currentEntry; + int32_t scrollPos; + + // appearance + uint8_t features; // bit 1 = disable left/right arrows, bit 2 = disable list + + int32_t getMarginBottom() const { return mulscale16(entryFormat->marginBottom, font->zoom); } + int32_t getIndent() const { return mulscale16(entryFormat->indent, font->zoom); } +} MenuOptionSet_t; +typedef struct MenuOption_t +{ + // appearance + MenuFont_t *font; + + // traits + MenuOptionSet_t *options; // so that common sets such as Yes/No, On/Off can be reused + + // effect + int32_t *data; + + // state + int32_t currentOption; +} MenuOption_t; +typedef struct MenuCustom2Col_t +{ + // effect + uint8_t *column[2]; + char const **key; + + // appearance + MenuFont_t *font; + + // effect + size_t numvalid; + + // appearance + int32_t columnWidth; + + // state + int8_t screenOpen; +} MenuCustom2Col_t; + +enum MenuRangeFlags_t +{ + DisplayTypeInteger = 1, + DisplayTypePercent = 2, + DisplayTypeNormalizedDecimal = 3, + DisplayTypeMask = (1<<0)|(1<<1), + + EnforceIntervals = 1<<7, +}; +typedef struct MenuRangeInt32_t +{ + // effect + int32_t *variable; + + // appearance + MenuFont_t *font; + + // traits + int32_t min; + int32_t max; + int32_t onehundredpercent; // 0 implies max + int32_t steps; + + uint8_t flags; +} MenuRangeInt32_t; +typedef struct MenuRangeFloat_t +{ + // effect + float *variable; + + // appearance + MenuFont_t *font; + + // traits + float min; + float max; + float onehundredpercent; // 0 implies 1.0 + int32_t steps; + + uint8_t flags; +} MenuRangeFloat_t; +#ifdef MENU_ENABLE_RANGEDOUBLE +typedef struct MenuRangeDouble_t +{ + // effect + double *variable; + + // appearance + MenuFont_t *font; + + // traits + double min; + double max; + double onehundredpercent; // 0 implies 1.0 + int32_t steps; + + uint8_t flags; +} MenuRangeDouble_t; +#endif +typedef struct MenuString_t +{ + // state + char* editfield; + + // effect + char* variable; + + // appearance + MenuFont_t *font; + + // effect + int32_t bufsize; + int32_t flags; +} MenuString_t; +typedef struct MenuSpacer_t +{ + int32_t height; +} MenuSpacer_t; + +// For internal use only. +enum MenuEntryFlags_t +{ + MEF_Disabled = 1<<0, + MEF_LookDisabled = 1<<1, + MEF_Hidden = 1<<2, +}; + +typedef struct MenuEntry_t +{ + // traits + const char *name; + + // appearance + MenuFont_t *font; + MenuEntryFormat_t *format; + + void *entry; + MenuEntryType_t type; + + // state + int32_t flags; + int32_t ytop, ybottom; + + int32_t getMarginBottom() const { return mulscale16(format->marginBottom, font->zoom); } + int32_t getIndent() const { return mulscale16(format->indent, font->zoom); } + int32_t getHeight() const + { + return type == Spacer ? mulscale16(((MenuSpacer_t *)entry)->height, font->zoom) : font->get_yline(); + } +} MenuEntry_t; + + +typedef enum MenuType_t +{ + Menu, + Panel, + Verify, + Message, + TextForm, + FileSelect, +} MenuType_t; + +typedef struct MenuMenu_t +{ + const char *title; + + MenuMenuFormat_t *format; + + MenuEntry_t **entrylist; + int32_t numEntries; + + // state + int32_t currentEntry, currentColumn; + int32_t scrollPos; +} MenuMenu_t; +typedef struct MenuPanel_t +{ + const char *title; + + MenuID_t previousID; + MenuAnimationType_t previousAnimation; + MenuID_t nextID; + MenuAnimationType_t nextAnimation; +} MenuPanel_t; +typedef struct MenuVerify_t +{ + vec2_t cursorpos; + + MenuID_t linkID; + MenuAnimationType_t animation; +} MenuVerify_t; +typedef struct MenuMessage_t +{ + vec2_t cursorpos; + + MenuID_t linkID; + MenuAnimationType_t animation; +} MenuMessage_t; +enum MenuTextFormFlags_t +{ + MTF_Password = 1<<0, +}; +typedef struct MenuTextForm_t +{ + // state + char *input; + + // traits + const char *instructions; + int32_t bufsize; + uint8_t flags; +} MenuTextForm_t; +typedef struct MenuFileSelect_t +{ + const char *title; + + // appearance + MenuMenuFormat_t *format[2]; + MenuFont_t *font[2]; + + // traits + const char * startdir; + const char *pattern; + char *destination; + + // state + CACHE1D_FIND_REC *findhigh[2]; + int32_t scrollPos[2]; + + // appearance + int32_t marginBottom[2]; + + // state + fnlist_t fnlist; + int32_t currentList; + + int32_t getMarginBottom(size_t index) const { return mulscale16(marginBottom[index], font[index]->zoom); } +} MenuFileSelect_t; + +typedef struct Menu_t +{ + void *object; + MenuID_t menuID; + MenuID_t parentID; + MenuAnimationType_t parentAnimation; + MenuType_t type; +} Menu_t; + +typedef struct MenuAnimation_t +{ + int32_t(*out)(struct MenuAnimation_t *); + int32_t(*in)(struct MenuAnimation_t *); + + Menu_t *previous; + Menu_t *current; + + int32_t start; + int32_t length; +} MenuAnimation_t; + +extern MenuAnimation_t m_animation; + +extern MenuID_t g_currentMenu; +extern Menu_t *m_currentMenu; + +extern int32_t g_quitDeadline; +extern int32_t voting; +int Menu_Change(MenuID_t cm); +void Menu_AnimateChange(int32_t cm, MenuAnimationType_t animtype); +int32_t Menu_IsTextInput(Menu_t *cm); +int G_CheckPlayerColor(int color); +void Menu_Init(void); +void Menu_Open(uint8_t playerID); +void Menu_Close(uint8_t playerID); +void M_DisplayMenus(void); + +extern MenuFont_t MF_Redfont, MF_Bluefont, MF_Minifont; + +#define M_MOUSETIMEOUT 210 +extern int32_t m_mouselastactivity; + +#if defined EDUKE32_TOUCH_DEVICES +# define MOUSEALPHA 0 +# define CURSORALPHA (255/3) +# define MOUSEACTIVECONDITIONAL(condition) (condition) +# define MOUSEWATCHPOINTCONDITIONAL(condition) (condition) +#else +extern int32_t m_mousewake_watchpoint, m_menuchange_watchpoint; +// alpha increments of 3 --> 255 / 3 = 85 --> round up to power of 2 --> 128 --> divide by 2 --> 64 alphatabs required +// use 16 anyway :P +# define MOUSEUSEALPHA (videoGetRenderMode() != REND_CLASSIC || numalphatabs >= 15) +# define MOUSEALPHA (MOUSEUSEALPHA ? clamp(((int32_t) totalclock - m_mouselastactivity - 90)*3, 0, 255) : 0) +# define CURSORALPHA (MOUSEUSEALPHA ? clamp(((int32_t) totalclock - m_mouselastactivity - 90)*2 + (255/3), (255/3), 255) : 255/3) +# define MOUSEACTIVECONDITION (totalclock - m_mouselastactivity < M_MOUSETIMEOUT) +# define MOUSEACTIVECONDITIONAL(condition) (MOUSEACTIVECONDITION && (condition)) +# define MOUSEINACTIVECONDITIONAL(condition) ((gInputMode != kInputMenu || !MOUSEACTIVECONDITION) && (condition)) +# define MOUSEWATCHPOINTCONDITIONAL(condition) ((condition) || m_mousewake_watchpoint || m_menuchange_watchpoint == 3) +#endif + +#define MAXMENUGAMEPLAYENTRIES 7 + +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]; + +extern MenuEntry_t ME_NEWGAMECUSTOMENTRIES[MAXMENUGAMEPLAYENTRIES]; +extern MenuEntry_t ME_NEWGAMECUSTOMSUBENTRIES[MAXMENUGAMEPLAYENTRIES][MAXMENUGAMEPLAYENTRIES]; + +#define TYPEBUFSIZE 141 +extern char typebuf[TYPEBUFSIZE]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/blood/src/messages.cpp b/source/blood/src/messages.cpp index d3ca8a8b1..47706f498 100644 --- a/source/blood/src/messages.cpp +++ b/source/blood/src/messages.cpp @@ -366,7 +366,7 @@ void CGameMessageMgr::Display(void) { if (VanillaMode()) { - if (numberOfDisplayedMessages && this->state && gInputMode != INPUT_MODE_2) + if (numberOfDisplayedMessages && this->state && gInputMode != kInputMessage) { int initialNrOfDisplayedMsgs = numberOfDisplayedMessages; int initialMessagesIndex = messagesIndex; @@ -397,7 +397,7 @@ void CGameMessageMgr::Display(void) } else { - if (this->state && gInputMode != INPUT_MODE_2) + if (this->state && gInputMode != kInputMessage) { messageStruct* currentMessages[kMessageLogSize]; int currentMessagesCount = 0; @@ -529,7 +529,7 @@ void CPlayerMsg::Clear(void) void CPlayerMsg::Term(void) { Clear(); - gInputMode = INPUT_MODE_0; + gInputMode = kInputGame; } void CPlayerMsg::Draw(void) diff --git a/source/blood/src/osdcmd.cpp b/source/blood/src/osdcmd.cpp index 914120aef..40437b4ba 100644 --- a/source/blood/src/osdcmd.cpp +++ b/source/blood/src/osdcmd.cpp @@ -483,7 +483,7 @@ static int osdcmd_button(osdcmdptr_t parm) char const *p = parm->name + strlen_gamefunc_; // if (g_player[myconnectindex].ps->gm == MODE_GAME) // only trigger these if in game - if (gInputMode == INPUT_MODE_0) + if (gInputMode == kInputGame) CONTROL_ButtonFlags[CONFIG_FunctionNameToNum(p)] = 1; // FIXME return OSDCMD_OK; diff --git a/source/blood/src/screentext.cpp b/source/blood/src/screentext.cpp new file mode 100644 index 000000000..f31b80227 --- /dev/null +++ b/source/blood/src/screentext.cpp @@ -0,0 +1,1109 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "compat.h" +#include "blood.h" +#include "common_game.h" +#include "screentext.h" +#include "menus.h" + +// get the string length until the next '\n' +int32_t G_GetStringLineLength(const char *text, const char *end, const int32_t iter) +{ + int32_t length = 0; + + while (text != end && *text != '\n') + { + ++length; + + text += iter; + } + + return length; +} + +int32_t G_GetStringNumLines(const char *text, const char *end, const int32_t iter) +{ + int32_t count = 1; + + while (text != end) + { + if (*text == '\n') + ++count; + text += iter; + } + + return count; +} +// Note: Neither of these care about TEXT_LINEWRAP. This is intended. + +// This function requires you to Xfree() the returned char*. +char* G_GetSubString(const char *text, const char *end, const int32_t iter, const int32_t length) +{ + char *line = (char*) Xmalloc((length+1) * sizeof(char)); + int32_t counter = 0; + + while (counter < length && text != end) + { + line[counter] = *text; + + text += iter; + ++counter; + } + + line[counter] = '\0'; + + return line; +} + +// assign the character's tilenum +int32_t G_GetStringTile(int32_t font, char *t, int32_t f) +{ + if (f & TEXT_DIGITALNUMBER) + return *t - '0' + font; // copied from digitalnumber + else + return *t - ' ' + font; // uses ASCII order +} + +#define NUMHACKACTIVE ((f & TEXT_GAMETEXTNUMHACK) && t >= '0' && t <= '9') + +// qstrdim +vec2_t G_ScreenTextSize(const int32_t font, + int32_t x, int32_t y, const int32_t z, const int32_t blockangle, + const char *str, const int32_t o, + int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, + const int32_t f, + int32_t x1, int32_t y1, int32_t x2, int32_t y2) +{ + vec2_t size = { 0, 0, }; // eventually the return value + vec2_t pos = { 0, 0, }; // holds the coordinate position as we draw each character tile of the string + vec2_t extent = { 0, 0, }; // holds the x-width of each character and the greatest y-height of each line + vec2_t offset = { 0, 0, }; // temporary; holds the last movement made in both directions + + int32_t tile; + char t; + + // set the start and end points depending on direction + int32_t iter = (f & TEXT_BACKWARDS) ? -1 : 1; // iteration direction + + const char *end; + const char *text; + + if (str == NULL) + return size; + + end = (f & TEXT_BACKWARDS) ? str-1 : Bstrchr(str, '\0'); + text = (f & TEXT_BACKWARDS) ? Bstrchr(str, '\0')-1 : str; + + // optimization: justification in both directions + if ((f & TEXT_XJUSTIFY) && (f & TEXT_YJUSTIFY)) + { + size.x = xbetween; + size.y = ybetween; + return size; + } + + // for best results, we promote 320x200 coordinates to full precision before any math + if (!(o & ROTATESPRITE_FULL16)) + { + x <<= 16; + y <<= 16; + xspace <<= 16; + yline <<= 16; + xbetween <<= 16; + ybetween <<= 16; + } + // coordinate values should be shifted left by 16 + + // handle zooming where applicable + xspace = mulscale16(xspace, z); + yline = mulscale16(yline, z); + xbetween = mulscale16(xbetween, z); + ybetween = mulscale16(ybetween, z); + // size/width/height/spacing/offset values should be multiplied or scaled by $z, zoom (since 100% is 65536, the same as 1<<16) + + // loop through the string + while (text != end && (t = *text)) + { + // handle escape sequences + if (t == '^' && Bisdigit(*(text+iter)) && !(f & TEXT_LITERALESCAPE)) + { + text += iter + iter; + if (Bisdigit(*text)) + text += iter; + continue; + } + + // handle case bits + if (f & TEXT_UPPERCASE) + { + if (f & TEXT_INVERTCASE) // optimization...? + { // v^ important that these two ifs remain separate due to the else below + if (Bisupper(t)) + t = Btolower(t); + } + else if (Bislower(t)) + t = Btoupper(t); + } + else if (f & TEXT_INVERTCASE) + { + if (Bisupper(t)) + t = Btolower(t); + else if (Bislower(t)) + t = Btoupper(t); + } + + // translate the character to a tilenum + tile = G_GetStringTile(font, &t, f); + + // reset this here because we haven't printed anything yet this loop + extent.x = 0; + + // reset this here because the act of printing something on this line means that we include the margin above in the total size + offset.y = 0; + + // handle each character itself in the context of screen drawing + switch (t) + { + case '\t': + case ' ': + // width + extent.x = xspace; + + if (f & (TEXT_INTERNALSPACE|TEXT_TILESPACE)) + { + char space = ' '; // this is subject to change as an implementation detail + if (f & TEXT_TILESPACE) + space = '\x7F'; // tile after '~' + tile = G_GetStringTile(font, &space, f); + + extent.x += (tilesiz[tile].x * z); + } + + // prepare the height // near-CODEDUP the other two near-CODEDUPs for this section + { + int32_t tempyextent = yline; + + if (f & (TEXT_INTERNALLINE|TEXT_TILELINE)) + { + char line = 'A'; // this is subject to change as an implementation detail + if (f & TEXT_TILELINE) + line = '\x7F'; // tile after '~' + tile = G_GetStringTile(font, &line, f); + + tempyextent += tilesiz[tile].y * z; + } + + SetIfGreater(&extent.y, tempyextent); + } + + if (t == '\t') + extent.x <<= 2; // *= 4 + + break; + + case '\n': // near-CODEDUP "if (wrap)" + extent.x = 0; + + // save the position + if (!(f & TEXT_XOFFSETZERO)) // we want the entire offset to count as the character width + pos.x -= offset.x; + SetIfGreater(&size.x, pos.x); + + // reset the position + pos.x = 0; + + // prepare the height + { + int32_t tempyextent = yline; + + if (f & (TEXT_INTERNALLINE|TEXT_TILELINE)) + { + char line = 'A'; // this is subject to change as an implementation detail + if (f & TEXT_TILELINE) + line = '\x7F'; // tile after '~' + tile = G_GetStringTile(font, &line, f); + + tempyextent += tilesiz[tile].y * z; + } + + SetIfGreater(&extent.y, tempyextent); + } + + // move down the line height + if (!(f & TEXT_YOFFSETZERO)) + pos.y += extent.y; + + // reset the current height + extent.y = 0; + + // line spacing + offset.y = (f & TEXT_YJUSTIFY) ? 0 : ybetween; // ternary to prevent overflow + pos.y += offset.y; + + break; + + default: + // width + extent.x = tilesiz[tile].x * z; + + // obnoxious hardcoded functionality from gametext + if (NUMHACKACTIVE) + { + char numeral = '0'; // this is subject to change as an implementation detail + extent.x = (tilesiz[G_GetStringTile(font, &numeral, f)].x-1) * z; + } + + // height + SetIfGreater(&extent.y, (tilesiz[tile].y * z)); + + break; + } + + // incrementing the coordinate counters + offset.x = 0; + + // advance the x coordinate + if (!(f & TEXT_XOFFSETZERO) || NUMHACKACTIVE) + offset.x += extent.x; + + // account for text spacing + if (!NUMHACKACTIVE // this "if" line ONLY == replicating hardcoded stuff + && t != '\n' + && !(f & TEXT_XJUSTIFY)) // to prevent overflow + offset.x += xbetween; + + // line wrapping + if ((f & TEXT_LINEWRAP) && !(f & TEXT_XRIGHT) && !(f & TEXT_XCENTER) && blockangle % 512 == 0) + { + int32_t wrap = 0; + const int32_t ang = blockangle % 2048; + + // this is the only place in qstrdim where angle actually affects direction, but only in the wrapping measurement + switch (ang) + { + case 0: + wrap = (x + (pos.x + offset.x) > ((o & 2) ? (320<<16) : ((x2 - USERQUOTE_RIGHTOFFSET)<<16))); + break; + case 512: + wrap = (y + (pos.x + offset.x) > ((o & 2) ? (200<<16) : ((y2 - USERQUOTE_RIGHTOFFSET)<<16))); + break; + case 1024: + wrap = (x - (pos.x + offset.x) < ((o & 2) ? 0 : ((x1 + USERQUOTE_RIGHTOFFSET)<<16))); + break; + case 1536: + wrap = (y - (pos.x + offset.x) < ((o & 2) ? 0 : ((y1 + USERQUOTE_RIGHTOFFSET)<<16))); + break; + } + if (wrap) // near-CODEDUP "case '\n':" + { + // save the position + SetIfGreater(&size.x, pos.x); + + // reset the position + pos.x = 0; + + // prepare the height + { + int32_t tempyextent = yline; + + if (f & (TEXT_INTERNALLINE|TEXT_TILELINE)) + { + char line = 'A'; // this is subject to change as an implementation detail + if (f & TEXT_TILELINE) + line = '\x7F'; // tile after '~' + tile = G_GetStringTile(font, &line, f); + + tempyextent += tilesiz[tile].y * z; + } + + SetIfGreater(&extent.y, tempyextent); + } + + // move down the line height + if (!(f & TEXT_YOFFSETZERO)) + pos.y += extent.y; + + // reset the current height + extent.y = 0; + + // line spacing + offset.y = (f & TEXT_YJUSTIFY) ? 0 : ybetween; // ternary to prevent overflow + pos.y += offset.y; + } + else + pos.x += offset.x; + } + else + pos.x += offset.x; + + // save some trouble with calculation in case the line breaks + if (!(f & TEXT_XOFFSETZERO) || NUMHACKACTIVE) + offset.x -= extent.x; + + // iterate to the next character in the string + text += iter; + } + + // calculate final size + if (!(f & TEXT_XOFFSETZERO)) + pos.x -= offset.x; + + if (!(f & TEXT_YOFFSETZERO)) + { + pos.y -= offset.y; + pos.y += extent.y; + } + else + pos.y += ybetween; + + SetIfGreater(&size.x, pos.x); + SetIfGreater(&size.y, pos.y); + + // justification where only one of the two directions is set, so we have to iterate + if (f & TEXT_XJUSTIFY) + size.x = xbetween; + if (f & TEXT_YJUSTIFY) + size.y = ybetween; + + // return values in the same manner we receive them + if (!(o & ROTATESPRITE_FULL16)) + { + size.x >>= 16; + size.y >>= 16; + } + + return size; +} + +void G_AddCoordsFromRotation(vec2_t *coords, const vec2_t *unitDirection, const int32_t magnitude) +{ + coords->x += mulscale14(magnitude, unitDirection->x); + coords->y += mulscale14(magnitude, unitDirection->y); +} + +// screentext +vec2_t G_ScreenText(const int32_t font, + int32_t x, int32_t y, const int32_t z, const int32_t blockangle, const int32_t charangle, + const char *str, const int32_t shade, int32_t pal, int32_t o, int32_t alpha, + int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, const int32_t f, + const int32_t x1, const int32_t y1, const int32_t x2, const int32_t y2) +{ + vec2_t size = { 0, 0, }; // eventually the return value + vec2_t origin = { 0, 0, }; // where to start, depending on the alignment + vec2_t pos = { 0, 0, }; // holds the coordinate position as we draw each character tile of the string + vec2_t extent = { 0, 0, }; // holds the x-width of each character and the greatest y-height of each line + const vec2_t Xdirection = { sintable[(blockangle+512)&2047], sintable[blockangle&2047], }; + const vec2_t Ydirection = { sintable[(blockangle+1024)&2047], sintable[(blockangle+512)&2047], }; + + int32_t blendidx=0, tile; + char t; + + // set the start and end points depending on direction + int32_t iter = (f & TEXT_BACKWARDS) ? -1 : 1; // iteration direction + + const char *end; + const char *text; + + if (str == NULL) + return size; + + NEG_ALPHA_TO_BLEND(alpha, blendidx, o); + + end = (f & TEXT_BACKWARDS) ? str-1 : Bstrchr(str, '\0'); + text = (f & TEXT_BACKWARDS) ? Bstrchr(str, '\0')-1 : str; + + // for best results, we promote 320x200 coordinates to full precision before any math + if (!(o & ROTATESPRITE_FULL16)) + { + x <<= 16; + y <<= 16; + xspace <<= 16; + yline <<= 16; + xbetween <<= 16; + ybetween <<= 16; + } + // coordinate values should be shifted left by 16 + + // eliminate conflicts, necessary here to get the correct size value + // especially given justification's special handling in G_ScreenTextSize() + if ((f & TEXT_XRIGHT) || (f & TEXT_XCENTER) || (f & TEXT_XJUSTIFY) || (f & TEXT_YJUSTIFY) || blockangle % 512 != 0) + o &= ~TEXT_LINEWRAP; + + // size is the return value, and we need it for alignment + size = G_ScreenTextSize(font, x, y, z, blockangle, str, o | ROTATESPRITE_FULL16, xspace, yline, (f & TEXT_XJUSTIFY) ? 0 : xbetween, (f & TEXT_YJUSTIFY) ? 0 : ybetween, f & ~(TEXT_XJUSTIFY|TEXT_YJUSTIFY), x1, y1, x2, y2); + + int32_t const xspace_orig = xspace; + int32_t const yline_orig = yline; + int32_t const xbetween_orig = xbetween; + int32_t const ybetween_orig = ybetween; + // handle zooming where applicable + xspace = mulscale16(xspace, z); + yline = mulscale16(yline, z); + xbetween = mulscale16(xbetween, z); + ybetween = mulscale16(ybetween, z); + // size/width/height/spacing/offset values should be multiplied or scaled by $z, zoom (since 100% is 65536, the same as 1<<16) + + // alignment + // near-CODEDUP "case '\n':" + { + int32_t lines = G_GetStringNumLines(text, end, iter); + + if ((f & TEXT_XJUSTIFY) || (f & TEXT_XRIGHT) || (f & TEXT_XCENTER)) + { + const int32_t length = G_GetStringLineLength(text, end, iter); + + int32_t linewidth = size.x; + + if (lines != 1) + { + char *line = G_GetSubString(text, end, iter, length); + + linewidth = G_ScreenTextSize(font, x, y, z, blockangle, line, o | ROTATESPRITE_FULL16, xspace_orig, yline_orig, (f & TEXT_XJUSTIFY) ? 0 : xbetween_orig, (f & TEXT_YJUSTIFY) ? 0 : ybetween_orig, f & ~(TEXT_XJUSTIFY|TEXT_YJUSTIFY|TEXT_BACKWARDS), x1, y1, x2, y2).x; + + Xfree(line); + } + + if (f & TEXT_XJUSTIFY) + { + size.x = xbetween; + + xbetween = (length == 1) ? 0 : tabledivide32_noinline((xbetween - linewidth), (length - 1)); + + linewidth = size.x; + } + + if (f & TEXT_XRIGHT) + origin.x = -(linewidth/z*z); + else if (f & TEXT_XCENTER) + origin.x = -(linewidth/2/z*z); + } + + if (f & TEXT_YJUSTIFY) + { + const int32_t tempswap = ybetween; + ybetween = (lines == 1) ? 0 : tabledivide32_noinline(ybetween - size.y, lines - 1); + size.y = tempswap; + } + + if (f & TEXT_YBOTTOM) + origin.y = -(size.y/z*z); + else if (f & TEXT_YCENTER) + origin.y = -(size.y/2/z*z); + } + + // loop through the string + while (text != end && (t = *text)) + { + int32_t orientation = o; + int32_t angle = blockangle + charangle; + + // handle escape sequences + if (t == '^' && Bisdigit(*(text+iter)) && !(f & TEXT_LITERALESCAPE)) + { + char smallbuf[4]; + + text += iter; + smallbuf[0] = *text; + + text += iter; + if (Bisdigit(*text)) + { + smallbuf[1] = *text; + smallbuf[2] = '\0'; + text += iter; + } + else + smallbuf[1] = '\0'; + + if (!(f & TEXT_IGNOREESCAPE)) + pal = Batoi(smallbuf); + + continue; + } + + // handle case bits + if (f & TEXT_UPPERCASE) + { + if (f & TEXT_INVERTCASE) // optimization...? + { // v^ important that these two ifs remain separate due to the else below + if (Bisupper(t)) + t = Btolower(t); + } + else if (Bislower(t)) + t = Btoupper(t); + } + else if (f & TEXT_INVERTCASE) + { + if (Bisupper(t)) + t = Btolower(t); + else if (Bislower(t)) + t = Btoupper(t); + } + + // translate the character to a tilenum + tile = G_GetStringTile(font, &t, f); + + switch (t) + { + case '\t': + case ' ': + case '\n': + case '\x7F': + break; + + default: + { + vec2_t location = { x, y, }; + + G_AddCoordsFromRotation(&location, &Xdirection, origin.x); + G_AddCoordsFromRotation(&location, &Ydirection, origin.y); + + G_AddCoordsFromRotation(&location, &Xdirection, pos.x); + G_AddCoordsFromRotation(&location, &Ydirection, pos.y); + + rotatesprite_(location.x, location.y, z, angle, tile, shade, pal, orientation, alpha, blendidx, x1, y1, x2, y2); + + break; + } + } + + // reset this here because we haven't printed anything yet this loop + extent.x = 0; + + // handle each character itself in the context of screen drawing + switch (t) + { + case '\t': + case ' ': + // width + extent.x = xspace; + + if (f & (TEXT_INTERNALSPACE|TEXT_TILESPACE)) + { + char space = ' '; // this is subject to change as an implementation detail + if (f & TEXT_TILESPACE) + space = '\x7F'; // tile after '~' + tile = G_GetStringTile(font, &space, f); + + extent.x += (tilesiz[tile].x * z); + } + + // prepare the height // near-CODEDUP the other two near-CODEDUPs for this section + { + int32_t tempyextent = yline; + + if (f & (TEXT_INTERNALLINE|TEXT_TILELINE)) + { + char line = 'A'; // this is subject to change as an implementation detail + if (f & TEXT_TILELINE) + line = '\x7F'; // tile after '~' + tile = G_GetStringTile(font, &line, f); + + tempyextent += tilesiz[tile].y * z; + } + + SetIfGreater(&extent.y, tempyextent); + } + + if (t == '\t') + extent.x <<= 2; // *= 4 + + break; + + case '\n': // near-CODEDUP "if (wrap)" + extent.x = 0; + + // reset the position + pos.x = 0; + + // prepare the height + { + int32_t tempyextent = yline; + + if (f & (TEXT_INTERNALLINE|TEXT_TILELINE)) + { + char line = 'A'; // this is subject to change as an implementation detail + if (f & TEXT_TILELINE) + line = '\x7F'; // tile after '~' + tile = G_GetStringTile(font, &line, f); + + tempyextent += tilesiz[tile].y * z; + } + + SetIfGreater(&extent.y, tempyextent); + } + + // move down the line height + if (!(f & TEXT_YOFFSETZERO)) + pos.y += extent.y; + + // reset the current height + extent.y = 0; + + // line spacing + pos.y += ybetween; + + // near-CODEDUP "alignments" + if ((f & TEXT_XJUSTIFY) || (f & TEXT_XRIGHT) || (f & TEXT_XCENTER)) + { + const int32_t length = G_GetStringLineLength(text+1, end, iter); + + char *line = G_GetSubString(text+1, end, iter, length); + + int32_t linewidth = G_ScreenTextSize(font, x, y, z, blockangle, line, o | ROTATESPRITE_FULL16, xspace_orig, yline_orig, (f & TEXT_XJUSTIFY) ? 0 : xbetween_orig, (f & TEXT_YJUSTIFY) ? 0 : ybetween_orig, f & ~(TEXT_XJUSTIFY|TEXT_YJUSTIFY|TEXT_BACKWARDS), x1, y1, x2, y2).x; + + Xfree(line); + + if (f & TEXT_XJUSTIFY) + { + xbetween = (length == 1) ? 0 : tabledivide32_noinline(xbetween - linewidth, length - 1); + + linewidth = size.x; + } + + if (f & TEXT_XRIGHT) + origin.x = -linewidth; + else if (f & TEXT_XCENTER) + origin.x = -(linewidth / 2); + } + + break; + + default: + // width + extent.x = tilesiz[tile].x * z; + + // obnoxious hardcoded functionality from gametext + if (NUMHACKACTIVE) + { + char numeral = '0'; // this is subject to change as an implementation detail + extent.x = (tilesiz[G_GetStringTile(font, &numeral, f)].x-1) * z; + } + + // height + SetIfGreater(&extent.y, (tilesiz[tile].y * z)); + + break; + } + + // incrementing the coordinate counters + { + int32_t xoffset = 0; + + // advance the x coordinate + if (!(f & TEXT_XOFFSETZERO) || NUMHACKACTIVE) + xoffset += extent.x; + + // account for text spacing + if (!NUMHACKACTIVE // this "if" line ONLY == replicating hardcoded stuff + && t != '\n') + xoffset += xbetween; + + // line wrapping + if (f & TEXT_LINEWRAP) + { + int32_t wrap = 0; + const int32_t ang = blockangle % 2048; + + // it's safe to make some assumptions and not go through G_AddCoordsFromRotation() since we limit to four directions + switch (ang) + { + case 0: + wrap = (x + (pos.x + xoffset) > ((orientation & 2) ? (320<<16) : ((x2 - USERQUOTE_RIGHTOFFSET)<<16))); + break; + case 512: + wrap = (y + (pos.x + xoffset) > ((orientation & 2) ? (200<<16) : ((y2 - USERQUOTE_RIGHTOFFSET)<<16))); + break; + case 1024: + wrap = (x - (pos.x + xoffset) < ((orientation & 2) ? 0 : ((x1 + USERQUOTE_RIGHTOFFSET)<<16))); + break; + case 1536: + wrap = (y - (pos.x + xoffset) < ((orientation & 2) ? 0 : ((y1 + USERQUOTE_RIGHTOFFSET)<<16))); + break; + } + if (wrap) // near-CODEDUP "case '\n':" + { + // reset the position + pos.x = 0; + + // prepare the height + { + int32_t tempyextent = yline; + + if (f & (TEXT_INTERNALLINE|TEXT_TILELINE)) + { + char line = 'A'; // this is subject to change as an implementation detail + if (f & TEXT_TILELINE) + line = '\x7F'; // tile after '~' + tile = G_GetStringTile(font, &line, f); + + tempyextent += tilesiz[tile].y * z; + } + + SetIfGreater(&extent.y, tempyextent); + } + + // move down the line height + if (!(f & TEXT_YOFFSETZERO)) + pos.y += extent.y; + + // reset the current height + extent.y = 0; + + // line spacing + pos.y += ybetween; + } + else + pos.x += xoffset; + } + else + pos.x += xoffset; + } + + // iterate to the next character in the string + text += iter; + } + + // return values in the same manner we receive them + if (!(o & ROTATESPRITE_FULL16)) + { + size.x >>= 16; + size.y >>= 16; + } + + return size; +} + +#if 0 +vec2_t G_ScreenTextShadow(int32_t sx, int32_t sy, + const int32_t font, + int32_t x, int32_t y, const int32_t z, const int32_t blockangle, const int32_t charangle, + const char *str, const int32_t shade, int32_t pal, int32_t o, const int32_t alpha, + int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, const int32_t f, + const int32_t x1, const int32_t y1, const int32_t x2, const int32_t y2) +{ + vec2_t size = { 0, 0, }; // eventually the return value + + if (!(o & ROTATESPRITE_FULL16)) + { + sx <<= 16; + sy <<= 16; + x <<= 16; + y <<= 16; + xspace <<= 16; + yline <<= 16; + xbetween <<= 16; + ybetween <<= 16; + } + + G_ScreenText(font, x + mulscale16(sx, z), y + mulscale16(sy, z), z, blockangle, charangle, str, 127, 4, o|ROTATESPRITE_FULL16, alpha, xspace, yline, xbetween, ybetween, f, x1, y1, x2, y2); + + size = G_ScreenText(font, x, y, z, blockangle, charangle, str, shade, pal, o|ROTATESPRITE_FULL16, alpha, xspace, yline, xbetween, ybetween, f, x1, y1, x2, y2); + + // return values in the same manner we receive them + if (!(o & ROTATESPRITE_FULL16)) + { + size.x >>= 16; + size.y >>= 16; + } + + return size; +} + +void G_PrintGameText(int32_t tile, int32_t x, int32_t y, const char *t, + int32_t s, int32_t p, int32_t o, + int32_t x1, int32_t y1, int32_t x2, int32_t y2, + int32_t z, int32_t a) +{ + int32_t f = TEXT_GAMETEXTNUMHACK; + + if (t == NULL) + return; + + if (!(o & ROTATESPRITE_FULL16)) + { + x <<= 16; + y <<= 16; + } + + if (x == (160<<16)) + f |= TEXT_XCENTER; + + G_ScreenText(tile, x, y, z, 0, 0, t, s, p, 2|o|ROTATESPRITE_FULL16, a, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags|f, x1, y1, x2, y2); +} + +vec2_t gametext_(int32_t x, int32_t y, const char *t, int32_t s, int32_t p, int32_t o, int32_t a, int32_t f) +{ + return G_ScreenText(MF_Bluefont.tilenum, x, y, MF_Bluefont.zoom, 0, 0, t, s, p, o|2|8|16|ROTATESPRITE_FULL16, a, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags|f, 0, 0, xdim-1, ydim-1); +} +void gametext_simple(int32_t x, int32_t y, const char *t) +{ + G_ScreenText(MF_Bluefont.tilenum, x, y, MF_Bluefont.zoom, 0, 0, t, 0, MF_Bluefont.pal, 2|8|16|ROTATESPRITE_FULL16, 0, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags, 0, 0, xdim-1, ydim-1); +} +vec2_t mpgametext(int32_t x, int32_t y, const char *t, int32_t s, int32_t o, int32_t a, int32_t f) +{ + return G_ScreenText(MF_Bluefont.tilenum, x, y, textsc(MF_Bluefont.zoom), 0, 0, t, s, MF_Bluefont.pal, o|2|8|16|ROTATESPRITE_FULL16, a, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags|f, 0, 0, xdim-1, ydim-1); +} +vec2_t mpgametextsize(const char *t, int32_t f) +{ + return G_ScreenTextSize(MF_Bluefont.tilenum, 0, 0, textsc(MF_Bluefont.zoom), 0, t, 2|8|16|ROTATESPRITE_FULL16, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags|f, 0, 0, xdim-1, ydim-1); +} + +// minitext_yofs: in hud_scale-independent, (<<16)-scaled, 0-200-normalized y coords, +// (sb&ROTATESPRITE_MAX) only. +int32_t minitext_yofs = 0; +int32_t minitext_lowercase = 0; +int32_t minitext_(int32_t x, int32_t y, const char *t, int32_t s, int32_t p, int32_t sb) +{ + vec2_t dim; + int32_t z = MF_Minifont.zoom; + + if (t == NULL) + { + OSD_Printf("minitext: NULL text!\n"); + return 0; + } + + if (!(sb & ROTATESPRITE_FULL16)) + { + x<<=16; + y<<=16; + } + + if (sb & ROTATESPRITE_MAX) + { + x = sbarx16(x); + y = minitext_yofs+sbary16(y); + z = sbarsc(z); + } + + sb &= (ROTATESPRITE_MAX-1)|RS_CENTERORIGIN; + + dim = G_ScreenText(MF_Minifont.tilenum, x, y, z, 0, 0, t, s, p, sb|ROTATESPRITE_FULL16, 0, MF_Minifont.emptychar.x, MF_Minifont.emptychar.y, MF_Minifont.between.x, MF_Minifont.between.y, MF_Minifont.textflags, 0, 0, xdim-1, ydim-1); + + x += dim.x; + + if (!(sb & ROTATESPRITE_FULL16)) + x >>= 16; + + return x; +} + +void menutext_(int32_t x, int32_t y, int32_t s, char const *t, int32_t o, int32_t f) +{ + G_ScreenText(MF_Redfont.tilenum, x, y - (12<<16), MF_Redfont.zoom, 0, 0, t, s, MF_Redfont.pal, o|ROTATESPRITE_FULL16, 0, MF_Redfont.emptychar.x, MF_Redfont.emptychar.y, MF_Redfont.between.x, MF_Redfont.between.y, f|MF_Redfont.textflags|TEXT_LITERALESCAPE, 0, 0, xdim-1, ydim-1); +} + +void captionmenutext(int32_t x, int32_t y, char const *t) +{ + G_ScreenText(MF_Redfont.tilenum, x, y - (12<<16), MF_Redfont.zoom, 0, 0, t, 0, ud.menutitle_pal, 2|8|16|ROTATESPRITE_FULL16, 0, MF_Redfont.emptychar.x, MF_Redfont.emptychar.y, MF_Redfont.between.x, MF_Redfont.between.y, MF_Redfont.textflags|TEXT_LITERALESCAPE|TEXT_XCENTER|TEXT_YCENTER, 0, 0, xdim-1, ydim-1); +} + + +int32_t user_quote_time[MAXUSERQUOTES]; +static char user_quote[MAXUSERQUOTES][178]; + +void G_AddUserQuote(const char *daquote) +{ + int32_t i; + + for (i=MAXUSERQUOTES-1; i>0; i--) + { + Bstrcpy(user_quote[i], user_quote[i-1]); + user_quote_time[i] = user_quote_time[i-1]; + } + Bstrcpy(user_quote[0], daquote); + OSD_Printf("%s\n", daquote); + + user_quote_time[0] = ud.msgdisptime; + pub = NUMPAGES; +} + +int32_t textsc(int32_t sc) +{ + return scale(sc, ud.textscale, 400); +} + +int32_t hud_glowingquotes = 1; + +#define FTAOPAQUETIME 30 + +// alpha increments of 8 --> 256 / 8 = 32 --> round up to power of 2 --> 32 --> divide by 2 --> 16 alphatabs required +static inline int32_t textsh(uint32_t t) +{ + return (hud_glowingquotes && ((videoGetRenderMode() == REND_CLASSIC && numalphatabs < 15) || t >= FTAOPAQUETIME)) + ? sintable[(t << 7) & 2047] >> 11 + : (sintable[(FTAOPAQUETIME << 7) & 2047] >> 11); +} + +// orientation flags depending on time that a quote has still to be displayed +static inline int32_t texto(int32_t t) +{ + if (videoGetRenderMode() != REND_CLASSIC || numalphatabs >= 15 || t > 4) + return 0; + + if (t > 2) + return 1; + + return 1|32; +} + +static inline int32_t texta(int32_t t) +{ + if (videoGetRenderMode() == REND_CLASSIC && numalphatabs < 15) + return 0; + + return 255 - clamp(t<<3, 0, 255); +} + +static FORCE_INLINE int32_t text_ypos(void) +{ + if (ud.hudontop == 1 && ud.screen_size == 4 && ud.althud == 1) + return 32<<16; + +#ifdef GEKKO + return 16<<16; +#elif defined EDUKE32_TOUCH_DEVICES + return 24<<16; +#else + return 1<<16; +#endif +} + +// this handles both multiplayer and item pickup message type text +// both are passed on to gametext +void G_PrintGameQuotes(int32_t snum) +{ + auto const ps = g_player[snum].ps; + const int32_t reserved_quote = (ps->ftq >= QUOTE_RESERVED && ps->ftq <= QUOTE_RESERVED3); + // NOTE: QUOTE_RESERVED4 is not included. + + int32_t const ybase = (fragbarheight()<<16) + text_ypos(); + int32_t height = 0; + int32_t k = ps->fta; + + + // primary quote + + do + { + if (k <= 1) + break; + + if (EDUKE32_PREDICT_FALSE(apStrings[ps->ftq] == NULL)) + { + OSD_Printf(OSD_ERROR "%s %d null quote %d\n", "screentext:", __LINE__, ps->ftq); + break; + } + + int32_t y = ybase; + if (reserved_quote) + { +#ifdef SPLITSCREEN_MOD_HACKS + if (!g_fakeMultiMode) + y = 140<<16; + else + y = 70<<16; +#else + y = 140<<16; +#endif + } + + int32_t pal = 0; + int32_t x = 160<<16; + +#ifdef SPLITSCREEN_MOD_HACKS + if (g_fakeMultiMode) + { + pal = g_player[snum].pcolor; + const int32_t sidebyside = ud.screen_size != 0; + + if (sidebyside) + x = snum == 1 ? 240<<16 : 80<<16; + else if (snum == 1) + y += 100<<16; + } +#endif + + height = gametext_(x, y, apStrings[ps->ftq], textsh(k), pal, texto(k), texta(k), TEXT_XCENTER).y + (1<<16); + } + while (0); + + + // userquotes + + int32_t y = ybase; + + if (k > 1 && !reserved_quote) + y += k <= 8 ? (height * (k-1))>>3 : height; + + for (int i = 0; i < MAXUSERQUOTES; i++) + { + k = user_quote_time[i]; + + if (k <= 0) + continue; + + // int32_t const sh = hud_glowingquotes ? sintable[((totalclock+(i<<2))<<5)&2047]>>11 : 0; + + height = mpgametext(mpgametext_x, y, user_quote[i], textsh(k), texto(k), texta(k), TEXT_LINEWRAP).y + textsc(1<<16); + y += k <= 4 ? (height * (k-1))>>2 : height; + } +} + +void P_DoQuote(int32_t q, DukePlayer_t *p) +{ + int32_t cq = 0; + + if (ud.fta_on == 0 || q < 0 || !(p->gm & MODE_GAME)) + return; + + if (q & MAXQUOTES) + { + cq = 1; + q &= ~MAXQUOTES; + } + + if (EDUKE32_PREDICT_FALSE(apStrings[q] == NULL)) + { + OSD_Printf(OSD_ERROR "%s %d null quote %d\n", "screentext:", __LINE__, q); + return; + } + + if (p->fta > 0 && q != QUOTE_RESERVED && q != QUOTE_RESERVED2) + if (p->ftq == QUOTE_RESERVED || p->ftq == QUOTE_RESERVED2) return; + + p->fta = 100; + + if (p->ftq != q) + { + if (p == g_player[screenpeek].ps && apStrings[q][0] != '\0') + OSD_Printf(cq ? OSDTEXT_DEFAULT "%s\n" : "%s\n", apStrings[q]); + + p->ftq = q; + } + + pub = NUMPAGES; + pus = NUMPAGES; +} +#endif diff --git a/source/blood/src/screentext.h b/source/blood/src/screentext.h new file mode 100644 index 000000000..cceb29508 --- /dev/null +++ b/source/blood/src/screentext.h @@ -0,0 +1,102 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#pragma once + +#include "menus.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define USERQUOTE_LEFTOFFSET 5 +#define USERQUOTE_RIGHTOFFSET 14 + +extern int32_t minitext_lowercase; +extern int32_t minitext_yofs; + +enum ScreenTextFlags_t { + TEXT_XRIGHT = 0x00000001, + TEXT_XCENTER = 0x00000002, + TEXT_YBOTTOM = 0x00000004, + TEXT_YCENTER = 0x00000008, + TEXT_INTERNALSPACE = 0x00000010, + TEXT_TILESPACE = 0x00000020, + TEXT_INTERNALLINE = 0x00000040, + TEXT_TILELINE = 0x00000080, + TEXT_XOFFSETZERO = 0x00000100, + TEXT_XJUSTIFY = 0x00000200, + TEXT_YOFFSETZERO = 0x00000400, + TEXT_YJUSTIFY = 0x00000800, + TEXT_LINEWRAP = 0x00001000, + TEXT_UPPERCASE = 0x00002000, + TEXT_INVERTCASE = 0x00004000, + TEXT_IGNOREESCAPE = 0x00008000, + TEXT_LITERALESCAPE = 0x00010000, + TEXT_BACKWARDS = 0x00020000, + TEXT_GAMETEXTNUMHACK = 0x00040000, + TEXT_DIGITALNUMBER = 0x00080000, + // TEXT_BIGALPHANUM = 0x00100000, + // TEXT_GRAYFONT = 0x00200000, +}; + +extern int32_t minitext_(int32_t x, int32_t y, const char *t, int32_t s, int32_t p, int32_t sb); +extern void menutext_(int32_t x, int32_t y, int32_t s, char const *t, int32_t o, int32_t f); +extern void captionmenutext(int32_t x, int32_t y, char const *t); +extern vec2_t gametext_(int32_t x, int32_t y, const char *t, int32_t s, int32_t p, int32_t o, int32_t a, int32_t f); +extern void gametext_simple(int32_t x, int32_t y, const char *t); +#define mpgametext_x (5<<16) +extern vec2_t mpgametext(int32_t x, int32_t y, char const * t, int32_t s, int32_t o, int32_t a, int32_t f); +extern vec2_t mpgametextsize(char const * t, int32_t f); +extern int32_t textsc(int32_t sc); + +#define minitextshade(x, y, t, s, p, sb) minitext_(x,y,t,s,p,sb) +#define minitext(x, y, t, p, sb) minitext_(x,y,t,0,p,sb) +#define menutext(x, y, t) menutext_((x), (y), 0, (t), 10|16, 0) +#define menutext_centeralign(x, y, t) menutext_((x), (y), 0, (t), 10|16, TEXT_XCENTER|TEXT_YCENTER) +#define menutext_center(y, t) menutext_(160<<16, (y)<<16, 0, (t), 10|16, TEXT_XCENTER) +#define gametext(x, y, t) gametext_simple((x)<<16, (y)<<16, (t)) +#define gametext_widenumber(x, y, t) gametext_((x)<<16, (y)<<16, (t), 0, MF_Bluefont.pal, 1024, 0, TEXT_GAMETEXTNUMHACK) +#define gametext_number(x, y, t) gametext_((x)<<16, (y)<<16, (t), 0, MF_Bluefont.pal, 0, 0, TEXT_GAMETEXTNUMHACK) +#define gametext_pal(x, y, t, p) gametext_((x)<<16, (y)<<16, (t), 0, (p), 0, 0, 0) +#define gametext_center(y, t) gametext_(160<<16, (y)<<16, (t), 0, MF_Bluefont.pal, 0, 0, TEXT_XCENTER) +#define gametext_center_number(y, t) gametext_(160<<16, (y)<<16, (t), 0, MF_Bluefont.pal, 0, 0, TEXT_XCENTER|TEXT_GAMETEXTNUMHACK) +#define gametext_center_shade(y, t, s) gametext_(160<<16, (y)<<16, (t), (s), MF_Bluefont.pal, 0, 0, TEXT_XCENTER) +#define gametext_center_shade_pal(y, t, s, p) gametext_(160<<16, (y)<<16, (t), (s), (p), 0, 0, TEXT_XCENTER) + +extern void G_PrintGameText(int32_t tile, int32_t x, int32_t y, const char *t, + int32_t s, int32_t p, int32_t o, + int32_t x1, int32_t y1, int32_t x2, int32_t y2, + int32_t z, int32_t a); + +extern int32_t G_GetStringLineLength(const char *text, const char *end, int32_t iter); +extern int32_t G_GetStringNumLines(const char *text, const char *end, int32_t iter); +extern char* G_GetSubString(const char *text, const char *end, int32_t iter, int32_t length); +extern int32_t G_GetStringTile(int32_t font, char *t, int32_t f); +extern vec2_t G_ScreenTextSize(int32_t font, int32_t x, int32_t y, int32_t z, int32_t blockangle, const char *str, int32_t o, int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, int32_t f, int32_t x1, int32_t y1, int32_t x2, int32_t y2); +extern void G_AddCoordsFromRotation(vec2_t *coords, const vec2_t *unitDirection, int32_t magnitude); +extern vec2_t G_ScreenText(int32_t font, int32_t x, int32_t y, int32_t z, int32_t blockangle, int32_t charangle, const char *str, int32_t shade, int32_t pal, int32_t o, int32_t alpha, int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, int32_t f, int32_t x1, int32_t y1, int32_t x2, int32_t y2); +extern vec2_t G_ScreenTextShadow(int32_t sx, int32_t sy, int32_t font, int32_t x, int32_t y, int32_t z, int32_t blockangle, int32_t charangle, const char *str, int32_t shade, int32_t pal, int32_t o, int32_t alpha, int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, int32_t f, int32_t x1, int32_t y1, int32_t x2, int32_t y2); + +#ifdef __cplusplus +} +#endif