diff --git a/src/command.h b/src/command.h index 61d211987..1f54341ce 100644 --- a/src/command.h +++ b/src/command.h @@ -29,6 +29,7 @@ enum { COM_ADMIN = 0x01, COM_LOCAL = 0x02, + COM_NOSHOWHELP = 0x04, COM_PLAYER2 = 0x10, COM_PLAYER3 = 0x20, COM_PLAYER4 = 0x30, diff --git a/src/deh_tables.c b/src/deh_tables.c index 4dbe6436a..cbdc39fc2 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4956,6 +4956,7 @@ struct int_const_s const INT_CONST[] = { // Lua command registration flags {"COM_ADMIN",COM_ADMIN}, {"COM_LOCAL",COM_LOCAL}, + {"COM_NOSHOWHELP",COM_NOSHOWHELP}, {"COM_PLAYER2",COM_PLAYER2}, {"COM_PLAYER3",COM_PLAYER3}, {"COM_PLAYER4",COM_PLAYER4}, diff --git a/src/k_menu.h b/src/k_menu.h index ff21a8ee2..81c85eb03 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -123,8 +123,10 @@ void M_NextMenuGametype(UINT32 forbidden); void M_PrevMenuGametype(UINT32 forbidden); void M_HandleHostMenuGametype(INT32 choice); void M_HandlePauseMenuGametype(INT32 choice); +void M_HandlePauseMenuAddons(INT32 choice); extern UINT32 menucallvote; // not midVoteType_e to prevent #include k_zvote +extern UINT32 menuaddonoptions; void M_HandlePauseMenuCallVote(INT32 choice); // @@ -483,6 +485,7 @@ extern menu_t PAUSE_MainDef; extern menu_t PAUSE_KickHandlerDef; extern menu_t PAUSE_CheatsDef; +extern menu_t PAUSE_AddonOptionsDef; // EXTRAS extern menuitem_t MISC_Manual[]; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 800510601..ab327a9cb 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5821,6 +5821,11 @@ void M_DrawPause(void) selectableheadertext = currentMenu->menuitems[itemOn].text; selectabletext = gametypes[menugametype]->name; } + else if (itemOn == mpause_addons) + { + selectableheadertext = "ADDONS"; + selectabletext = menuaddonoptions ? "LOAD..." : "SETTINGS"; + } else if (itemOn == mpause_callvote) { selectableheadertext = currentMenu->menuitems[itemOn].text; diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c index 8587fdfa8..ea9d96e22 100644 --- a/src/lua_consolelib.c +++ b/src/lua_consolelib.c @@ -422,6 +422,10 @@ static int lib_cvRegisterVar(lua_State *L) lua_rawset(L, 5); lua_pop(L, 1); cvar->func = Lua_OnChange; + } else if (i == 6 || (k && fasticmp(k, "description"))) { + if (!lua_isstring(L, 4)) + TYPEERROR("description", LUA_TSTRING) + cvar->description = Z_StrDup(lua_tostring(L, 4)); } lua_pop(L, 1); } diff --git a/src/menus/transient/CMakeLists.txt b/src/menus/transient/CMakeLists.txt index ea38b8505..1b5bb7f24 100644 --- a/src/menus/transient/CMakeLists.txt +++ b/src/menus/transient/CMakeLists.txt @@ -12,4 +12,5 @@ target_sources(SRB2SDL2 PRIVATE pause-replay.c virtual-keyboard.c pause-cheats.cpp + pause-addonoptions.cpp ) diff --git a/src/menus/transient/pause-addonoptions.cpp b/src/menus/transient/pause-addonoptions.cpp new file mode 100644 index 000000000..58ba1fc4e --- /dev/null +++ b/src/menus/transient/pause-addonoptions.cpp @@ -0,0 +1,294 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// Copyright (C) 2024 by AJ "Tyron" Martinez +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file menus/transient/pause-addonoptions.c +/// \brief Addon options + +#include +#include +#include +#include + +#include "../../v_draw.hpp" +#include "../../v_video.h" + +#include "../../command.h" +#include "../../doomtype.h" +#include "../../k_hud.h" +#include "../../k_menu.h" +#include "../../screen.h" + +#include "../../z_zone.h" + +extern "C" { +#include "../../lua_script.h" +#include "../../lua_libs.h" +#include "../../lua_hook.h" +}; + +extern "C" void COM_Lua_f(void); + +using srb2::Draw; + +namespace +{ + +enum Mode +{ + kUser, + kAdmin, + kNumModes +}; + +const char* mode_strings[kNumModes] = { + "User Settings", + "Host Settings", +}; + +std::vector g_menu; +std::vector g_menu_offsets; +int g_menu_cursors[kNumModes]; + +int menu_mode() +{ + return PAUSE_AddonOptionsDef.extra1; +} + +boolean admin_mode() +{ + return !!(menu_mode() == kAdmin); +} + +void menu_mode(int mode) +{ + g_menu_cursors[menu_mode()] = itemOn; + + PAUSE_AddonOptionsDef.extra1 = mode; + + itemOn = g_menu_cursors[menu_mode()]; +} + +void list_cvars() +{ + for (consvar_t* var = consvar_vars; var; var = var->next) + { + if (!(var->flags & CV_ADDEDBYLUA)) + continue; + + if (var->flags & CV_NOSHOWHELP) + continue; + + if (!admin_mode() != !(var->flags & CV_NETVAR)) // LOL. + continue; + + UINT16 status = IT_STRING | IT_CVAR; + INT32 height = 8; + + if (!var->PossibleValue && !(var->flags & CV_FLOAT)) + { + status |= IT_CV_STRING; + height += 16; + } + + g_menu.push_back(menuitem_t {status, var->name, var->description, nullptr, {.cvar = var}, 0, height}); + } +} + +void list_commands() +{ + static const auto call = [](INT32 idx) { COM_ImmedExecute(currentMenu->menuitems[idx].text); }; + UINT16 flags; + + for (xcommand_t* cmd = com_commands; cmd; cmd = cmd->next) + { + if (!(cmd->function == COM_Lua_f)) + continue; + + // Ha Ha What The Fuck + // Taken from COM_Lua_f with only a vague idea of what I am doing. Sorry! + char* buf; + + I_Assert(gL != NULL); + + lua_settop(gL, 0); // Just in case... + lua_pushcfunction(gL, LUA_GetErrorMessage); + + lua_getfield(gL, LUA_REGISTRYINDEX, "COM_Command"); // push COM_Command + I_Assert(lua_istable(gL, -1)); + + // use buf temporarily -- must use lowercased string + buf = Z_StrDup(cmd->name); + strlwr(buf); + lua_getfield(gL, -1, buf); // push command info table + I_Assert(lua_istable(gL, -1)); + lua_remove(gL, -2); // pop COM_Command + Z_Free(buf); + + lua_rawgeti(gL, -1, 2); // push flags from command info table + if (lua_isboolean(gL, -1)) + flags = (lua_toboolean(gL, -1) ? COM_ADMIN : 0); + else + flags = (UINT16)lua_tointeger(gL, -1); + lua_pop(gL, 1); // pop flags + + lua_pop(gL, 1); // pop command info table + + if (!admin_mode() != !(flags & COM_ADMIN)) + continue; + + if (flags & COM_NOSHOWHELP) + continue; + + g_menu.push_back(menuitem_t {IT_STRING | IT_CALL, cmd->name, "Press \xAA to execute this command", nullptr, {.routine = call}, 0, 8}); + } +} + +void group_menu() +{ + int old_key = '\0'; + + // Can't use range-for because iterators are invalidated + // by std::vector::insert. + for (std::size_t i = 0; i < g_menu.size(); ++i) + { + int new_key = g_menu[i].text[0]; + + if (new_key == old_key) + { + // Group cvars starting with the same letter + // together. + continue; + } + + old_key = new_key; + + if (i == 0) + { + continue; + } + + constexpr int spacer = 8; + + g_menu.insert( + g_menu.begin() + i, + menuitem_t {IT_SPACE | IT_DYLITLSPACE, nullptr, nullptr, nullptr, {}, spacer, spacer} + ); + + i++; // skip the inserted element + } +} + +void menu_open() +{ + g_menu = {}; + g_menu_offsets = {}; + + list_cvars(); + list_commands(); + + std::sort(g_menu.begin(), g_menu.end(), + [](menuitem_t& a, menuitem_t& b) { return std::strcmp(a.text, b.text) < 0; }); + + if (g_menu.size() == 0) + { + g_menu.insert( + g_menu.begin(), + menuitem_t {IT_DISABLED, "No addon options!", nullptr, nullptr, {}, 0, 0} + ); + } + + group_menu(); + + INT32 y = 0; + + for (menuitem_t& item : g_menu) + { + g_menu_offsets.push_back(y); + y += item.mvar2; + } + + PAUSE_AddonOptionsDef.menuitems = g_menu.data(); + PAUSE_AddonOptionsDef.numitems = g_menu.size(); +} + +boolean menu_close() +{ + PAUSE_AddonOptionsDef.menuitems = nullptr; + PAUSE_AddonOptionsDef.numitems = 0; + + g_menu = {}; + g_menu_offsets = {}; + + return true; +} + +boolean menu_input(INT32) +{ + // C button: cycle through modes + if (M_MenuButtonPressed(0, MBT_Y)) + { + menu_mode((menu_mode() + 1) % kNumModes); + + if (!server && !IsPlayerAdmin(consoleplayer)) + menu_mode(kUser); + + // reload menu + menu_open(); + } + + return false; +} + +void draw_menu() +{ + constexpr int kMargin = 4; + + Draw draw = Draw(0, 0).align(Draw::Align::kCenter); + + draw.patch("MENUHINT"); + + const menuitem_t& item = currentMenu->menuitems[itemOn]; + + if (item.tooltip) + { + draw.xy(BASEVIDWIDTH/2, 12).font(Draw::Font::kThin).text(item.tooltip); + } + draw = draw.y(27 + kMargin); + + draw.x(BASEVIDWIDTH/2).font(Draw::Font::kGamemode).text(mode_strings[menu_mode()]); + if (server || IsPlayerAdmin(consoleplayer)) + K_drawButton((draw.x() + 8) * FRACUNIT, (draw.y() + 8) * FRACUNIT, 0, kp_button_y[0], M_MenuButtonHeld(0, MBT_Y)); + draw = draw.y(32 + kMargin); + + currentMenu->y = std::min(static_cast(draw.y()), (BASEVIDHEIGHT/2) - g_menu_offsets[itemOn]); + + V_SetClipRect(0, draw.y() * FRACUNIT, BASEVIDWIDTH * FRACUNIT, (BASEVIDHEIGHT - draw.y() - kMargin) * FRACUNIT, 0); + M_DrawGenericMenu(); + V_ClearClipRect(); +} + +}; // namespace + +menu_t PAUSE_AddonOptionsDef = { + 0, + &PAUSE_MainDef, + 0, + nullptr, + 48, 0, + kUser, 0, + MBF_SOUNDLESS, + nullptr, + 0, 0, + draw_menu, + nullptr, + nullptr, + menu_open, + menu_close, + menu_input, +}; diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 16f5478f8..dcd2cd3e8 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -19,8 +19,8 @@ menuitem_t PAUSE_Main[] = { - {IT_STRING | IT_CALL, "ADDONS", "M_ICOADD", - NULL, {.routine = M_Addons}, 0, 0}, + {IT_STRING | IT_ARROWS, "ADDONS", "M_ICOADD", + NULL, {.routine = M_HandlePauseMenuAddons}, 0, 0}, {IT_STRING | IT_CALL, "STEREO MODE", "M_ICOSTM", NULL, {.routine = M_SoundTest}, 0, 0}, @@ -158,7 +158,7 @@ void M_OpenPauseMenu(void) if (M_SecretUnlocked(SECRET_ADDONS, true)) { - PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL; + PAUSE_Main[mpause_addons].status = IT_STRING | IT_ARROWS; } if (netgame) @@ -308,6 +308,23 @@ boolean M_PauseInputs(INT32 ch) return false; } +UINT32 menuaddonoptions = 0; +void M_HandlePauseMenuAddons(INT32 choice) +{ + if (choice == 2) + { + if (menuaddonoptions) + M_Addons(0); + else + M_SetupNextMenu(&PAUSE_AddonOptionsDef, false); + + return; + } + + menuaddonoptions = menuaddonoptions ? 0 : 1; + S_StartSound(NULL, sfx_s3k5b); +} + // Change gametype void M_HandlePauseMenuGametype(INT32 choice) {