diff --git a/src/command.c b/src/command.c index daf0865b9..fd5044e8b 100644 --- a/src/command.c +++ b/src/command.c @@ -66,7 +66,7 @@ static boolean CV_Command(void); consvar_t *CV_FindVar(const char *name); static const char *CV_StringValue(const char *var_name); -static consvar_t *consvar_vars; // list of registered console variables +consvar_t *consvar_vars; // list of registered console variables static UINT16 consvar_number_of_netids = 0; static char com_token[1024]; diff --git a/src/command.h b/src/command.h index 718a60abd..953af40b1 100644 --- a/src/command.h +++ b/src/command.h @@ -144,6 +144,7 @@ struct consvar_t //NULL, NULL, 0, NULL, NULL |, 0, NULL, NULL, 0, 0, NULL INT32 flags; // flags see cvflags_t above CV_PossibleValue_t *PossibleValue; // table of possible values void (*func)(void); // called on change, if CV_CALL set + const char *description; INT32 value; // for INT32 and fixed_t const char *string; // value in string char *zstring; // Either NULL or same as string. @@ -204,9 +205,11 @@ struct CVarList; #define CVAR_INIT consvar_t #else #define CVAR_INIT( ... ) \ -{ __VA_ARGS__, 0, NULL, NULL, {0, {NULL}}, 0U, (char)0, NULL } +{ __VA_ARGS__, NULL, 0, NULL, NULL, {0, {NULL}}, 0U, (char)0, NULL } #endif +extern consvar_t *consvar_vars; // list of registered console variables + extern CV_PossibleValue_t CV_OnOff[]; extern CV_PossibleValue_t CV_YesNo[]; extern CV_PossibleValue_t CV_Unsigned[]; diff --git a/src/cvars.cpp b/src/cvars.cpp index d9e5e67ba..88d9c07ee 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -86,6 +86,12 @@ struct consvar_t::Builder return builder; } + Builder& description(const char* description) + { + var_.description = description; + return *this; + } + Builder& save() { var_.flags |= CV_SAVE; @@ -757,43 +763,43 @@ consvar_t cv_votetime = UnsavedNetVar("votetime", "20").min_max(10, 3600); // Cheats don't save... // -consvar_t cv_battletest = OnlineCheat("battletest", "Off").on_off(); +consvar_t cv_battletest = OnlineCheat("battletest", "Off").on_off().description("Free Play goes to Battle instead of Prisons"); #ifdef DEVELOP - consvar_t cv_botcontrol = OnlineCheat("botcontrol", "On").on_off(); + consvar_t cv_botcontrol = OnlineCheat("botcontrol", "On").on_off().description("Toggle bot AI movement"); #endif extern CV_PossibleValue_t capsuletest_cons_t[]; void CapsuleTest_OnChange(void); -consvar_t cv_capsuletest = OnlineCheat("capsuletest", "Off").values(capsuletest_cons_t).onchange(CapsuleTest_OnChange); +consvar_t cv_capsuletest = OnlineCheat("capsuletest", "Off").values(capsuletest_cons_t).onchange(CapsuleTest_OnChange).description("Force item capsule spawning rules"); -consvar_t cv_debugencorevote = OnlineCheat("debugencorevote", "Off").on_off(); +consvar_t cv_debugencorevote = OnlineCheat("debugencorevote", "Off").on_off().description("Force encore choice to appear on vote screen"); void ForceSkin_OnChange(void); -consvar_t cv_forceskin = OnlineCheat("forcecharacter", "None").onchange(ForceSkin_OnChange); +consvar_t cv_forceskin = OnlineCheat("forcecharacter", "None").onchange(ForceSkin_OnChange).description("Force all players to use one character"); -consvar_t cv_kartdebugamount = OnlineCheat("debugitemamount", "1").min_max(1, 255); -consvar_t cv_kartdebugbots = OnlineCheat("debugbots", "Off").on_off(); -consvar_t cv_kartdebugdistribution = OnlineCheat("debugitemodds", "Off").on_off(); -consvar_t cv_kartdebughuddrop = OnlineCheat("debugitemdrop", "Off").on_off(); +consvar_t cv_kartdebugamount = OnlineCheat("debugitemamount", "1").min_max(1, 255).description("If debugitem, give multiple copies of an item"); +consvar_t cv_kartdebugbots = OnlineCheat("debugbots", "Off").on_off().description("Bot AI debugger"); +consvar_t cv_kartdebugdistribution = OnlineCheat("debugitemodds", "Off").on_off().description("Show items that the roulette can roll"); +consvar_t cv_kartdebughuddrop = OnlineCheat("debugitemdrop", "Off").on_off().description("Players drop paper items when damaged in any way"); extern CV_PossibleValue_t kartdebugitem_cons_t[]; -consvar_t cv_kartdebugitem = OnlineCheat("debugitem", "None").values(kartdebugitem_cons_t); +consvar_t cv_kartdebugitem = OnlineCheat("debugitem", "None").values(kartdebugitem_cons_t).description("Force item boxes to only roll one kind of item"); -consvar_t cv_kartdebugstart = OnlineCheat("debugstart", "Off").on_off(); -consvar_t cv_kartdebugwaypoints = OnlineCheat("debugwaypoints", "Off").values({{0, "Off"}, {1, "Forwards"}, {2, "Backwards"}}); +consvar_t cv_kartdebugstart = OnlineCheat("debugstart", "Off").on_off().description("Skip POSITION"); +consvar_t cv_kartdebugwaypoints = OnlineCheat("debugwaypoints", "Off").values({{0, "Off"}, {1, "Forwards"}, {2, "Backwards"}}).description("Make waypoints visible"); #ifdef DEVELOP - consvar_t cv_kartencoremap = OnlineCheat("encoremap", "On").on_off(); + consvar_t cv_kartencoremap = OnlineCheat("encoremap", "On").on_off().description("Toggle Encore colormap"); #endif extern CV_PossibleValue_t numlaps_cons_t[]; void NumLaps_OnChange(void); -consvar_t cv_numlaps = OnlineCheat("numlaps", "Map default").values(numlaps_cons_t).onchange(NumLaps_OnChange).save(); +consvar_t cv_numlaps = OnlineCheat("numlaps", "Map default").values(numlaps_cons_t).onchange(NumLaps_OnChange).save().description("Race maps always have the same number of laps"); -consvar_t cv_restrictskinchange = OnlineCheat("restrictskinchange", "Yes").yes_no(); -consvar_t cv_spbtest = OnlineCheat("spbtest", "Off").on_off(); -consvar_t cv_timescale = OnlineCheat(cvlist_timer)("timescale", "1.0").floating_point().min_max(FRACUNIT/20, 20*FRACUNIT); +consvar_t cv_restrictskinchange = OnlineCheat("restrictskinchange", "Yes").yes_no().description("Don't let players change their skin in the middle of gameplay"); +consvar_t cv_spbtest = OnlineCheat("spbtest", "Off").on_off().description("SPB can never target a player"); +consvar_t cv_timescale = OnlineCheat(cvlist_timer)("timescale", "1.0").floating_point().min_max(FRACUNIT/20, 20*FRACUNIT).description("Overclock or slow down the game"); // @@ -802,14 +808,14 @@ consvar_t cv_timescale = OnlineCheat(cvlist_timer)("timescale", "1.0").floating_ // consvar_t cv_botscanvote = ServerCheat("botscanvote", "No").yes_no(); -consvar_t cv_debugrank = ServerCheat("debugrank", "Off").on_off(); +consvar_t cv_debugrank = ServerCheat("debugrank", "Off").on_off().description("Show GP rank state on the HUD"); void Gravity_OnChange(void); -consvar_t cv_gravity = ServerCheat("gravity", "0.8").floating_point().onchange(Gravity_OnChange); // change DEFAULT_GRAVITY if you change this +consvar_t cv_gravity = ServerCheat("gravity", "0.8").floating_point().onchange(Gravity_OnChange).description("Change the default gravity"); // change DEFAULT_GRAVITY if you change this -consvar_t cv_kartdebugcolorize = ServerCheat("debugcolorize", "Off").on_off(); -consvar_t cv_kartdebugdirector = ServerCheat("debugdirector", "Off").on_off(); -consvar_t cv_kartdebugnodes = ServerCheat("debugnodes", "Off").on_off(); +consvar_t cv_kartdebugcolorize = ServerCheat("debugcolorize", "Off").on_off().description("Show all colorized options on the HUD"); +consvar_t cv_kartdebugdirector = ServerCheat("debugdirector", "Off").on_off().description("Show director AI on the HUD"); +consvar_t cv_kartdebugnodes = ServerCheat("debugnodes", "Off").on_off().description("Show player node and latency on the HUD"); // @@ -818,19 +824,19 @@ consvar_t cv_kartdebugnodes = ServerCheat("debugnodes", "Off").on_off(); // Not saved... // -consvar_t cv_debugfinishline = PlayerCheat("debugfinishline", "Off").on_off(); -consvar_t cv_debugrender_contrast = PlayerCheat("debugrender_contrast", "0.0").floating_point().min_max(-FRACUNIT, FRACUNIT); -consvar_t cv_debugrender_portal = PlayerCheat("debugrender_portal", "Off").on_off(); -consvar_t cv_debugrender_spriteclip = PlayerCheat("debugrender_spriteclip", "Off").on_off(); -consvar_t cv_devmode_screen = PlayerCheat("devmode_screen", "1").min_max(1, 4); -consvar_t cv_drawpickups = PlayerCheat("drawpickups", "Yes").yes_no(); +consvar_t cv_debugfinishline = PlayerCheat("debugfinishline", "Off").on_off().description("Highlight finish lines and respawn lines with high contrast colors"); +consvar_t cv_debugrender_contrast = PlayerCheat("debugrender_contrast", "0.0").floating_point().min_max(-FRACUNIT, FRACUNIT).description("Change level lighting"); +consvar_t cv_debugrender_portal = PlayerCheat("debugrender_portal", "Off").on_off().description("Highlight visual portals in red"); +consvar_t cv_debugrender_spriteclip = PlayerCheat("debugrender_spriteclip", "Off").on_off().description("Let sprites draw through walls"); +consvar_t cv_devmode_screen = PlayerCheat("devmode_screen", "1").min_max(1, 4).description("Choose which splitscreen player devmode applies to"); +consvar_t cv_drawpickups = PlayerCheat("drawpickups", "Yes").yes_no().description("Hide rings, spheres, item capsules, prison capsules (visual only)"); void CV_palette_OnChange(void); -consvar_t cv_palette = PlayerCheat("palette", "").onchange_noinit(CV_palette_OnChange); -consvar_t cv_palettenum = PlayerCheat("palettenum", "0").values(CV_Unsigned).onchange_noinit(CV_palette_OnChange); +consvar_t cv_palette = PlayerCheat("palette", "").onchange_noinit(CV_palette_OnChange).description("Force palette to a different lump"); +consvar_t cv_palettenum = PlayerCheat("palettenum", "0").values(CV_Unsigned).onchange_noinit(CV_palette_OnChange).description("Use a different sub-palette by default"); extern CV_PossibleValue_t renderhitbox_cons_t[]; -consvar_t cv_renderhitbox = PlayerCheat("renderhitbox", "Off").values(renderhitbox_cons_t); +consvar_t cv_renderhitbox = PlayerCheat("renderhitbox", "Off").values(renderhitbox_cons_t).description("Show hitboxes around objects"); // diff --git a/src/k_menu.h b/src/k_menu.h index 3c39dd876..875da6535 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -462,6 +462,7 @@ typedef enum mpause_canceljoin, mpause_spectatemenu, mpause_psetup, + mpause_cheats, mpause_options, mpause_title, diff --git a/src/menus/transient/CMakeLists.txt b/src/menus/transient/CMakeLists.txt index aa1397595..ea38b8505 100644 --- a/src/menus/transient/CMakeLists.txt +++ b/src/menus/transient/CMakeLists.txt @@ -11,4 +11,5 @@ target_sources(SRB2SDL2 PRIVATE pause-kick.c pause-replay.c virtual-keyboard.c + pause-cheats.cpp ) diff --git a/src/menus/transient/pause-cheats.cpp b/src/menus/transient/pause-cheats.cpp new file mode 100644 index 000000000..92d9a1a1b --- /dev/null +++ b/src/menus/transient/pause-cheats.cpp @@ -0,0 +1,161 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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-cheats.c +/// \brief Cheats directory, for developers + +#include +#include +#include +#include + +#include "../../v_draw.hpp" +#include "../../v_video.h" + +#include "../../command.h" +#include "../../doomtype.h" +#include "../../k_menu.h" +#include "../../screen.h" + +using srb2::Draw; + +namespace +{ + +std::vector g_menu; +std::vector g_menu_offsets; + +void sort_menu() +{ + std::sort(g_menu.begin(), g_menu.end(), + [](menuitem_t& a, menuitem_t& b) { return std::strcmp(a.text, b.text) < 0; }); + + 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 = {}; + + for (consvar_t* var = consvar_vars; var; var = var->next) + { + if (!(var->flags & CV_CHEAT)) + { + 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, nullptr, nullptr, {.cvar = var}, 0, height}); + } + + sort_menu(); + + INT32 y = 0; + + for (menuitem_t& item : g_menu) + { + g_menu_offsets.push_back(y); + y += item.mvar2; + } + + PAUSE_CheatsDef.menuitems = g_menu.data(); + PAUSE_CheatsDef.numitems = g_menu.size(); +} + +boolean menu_close() +{ + PAUSE_CheatsDef.menuitems = nullptr; + PAUSE_CheatsDef.numitems = 0; + + g_menu = {}; + g_menu_offsets = {}; + + return true; +} + +void draw_menu() +{ + auto tooltip = Draw(0, 0); + + tooltip.patch("MENUHINT"); + + const menuitem_t& item = currentMenu->menuitems[itemOn]; + + if (const consvar_t* cvar = item.itemaction.cvar; cvar && cvar->description) + { + tooltip.xy(BASEVIDWIDTH/2, 12).font(Draw::Font::kThin).align(Draw::Align::kCenter).text(cvar->description); + } + + constexpr int kTooltipHeight = 27; + constexpr int kPad = 4; + int y = tooltip.y() + kTooltipHeight + kPad; + + currentMenu->y = std::min(y, (BASEVIDHEIGHT/2) - g_menu_offsets[itemOn]); + + V_SetClipRect(0, y * FRACUNIT, BASEVIDWIDTH * FRACUNIT, (BASEVIDHEIGHT - y - kPad) * FRACUNIT, 0); + M_DrawGenericMenu(); + V_ClearClipRect(); +} + +}; // namespace + +menu_t PAUSE_CheatsDef = { + 0, + &PAUSE_MainDef, + 0, + nullptr, + 48, 0, + 0, 0, + MBF_SOUNDLESS, + nullptr, + 0, 0, + draw_menu, + nullptr, + menu_open, + menu_close, + nullptr, +}; diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 7cc349a05..3f69e4655 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -64,6 +64,9 @@ menuitem_t PAUSE_Main[] = {IT_STRING | IT_CALL, "PLAYER SETUP", "M_ICOCHR", NULL, {.routine = M_CharacterSelect}, 0, 0}, + {IT_STRING | IT_SUBMENU, "CHEATS", "M_ICOCHT", + NULL, {.submenu = &PAUSE_CheatsDef}, 0, 0}, + {IT_STRING | IT_CALL, "OPTIONS", "M_ICOOPT", NULL, {.routine = M_InitOptions}, 0, 0}, @@ -135,6 +138,7 @@ void M_OpenPauseMenu(void) PAUSE_Main[mpause_canceljoin].status = IT_DISABLED; PAUSE_Main[mpause_spectatemenu].status = IT_DISABLED; PAUSE_Main[mpause_psetup].status = IT_DISABLED; + PAUSE_Main[mpause_cheats].status = IT_DISABLED; Dummymenuplayer_OnChange(); // Make sure the consvar is within bounds of the amount of splitscreen players we have. @@ -217,6 +221,11 @@ void M_OpenPauseMenu(void) } } + if (CV_CheatsEnabled()) + { + PAUSE_Main[mpause_cheats].status = IT_STRING | IT_SUBMENU; + } + G_ResetAllDeviceRumbles(); } diff --git a/src/v_video.cpp b/src/v_video.cpp index e700005ab..9c6583916 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -990,6 +990,8 @@ void V_DrawBlock(INT32 x, INT32 y, INT32 scrn, INT32 width, INT32 height, const // void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) { + const cliprect_t *clip = V_GetClipRect(); + if (rendermode == render_none) return; @@ -1051,6 +1053,21 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) UINT8 g = (color.rgba & 0xFF00) >> 8; UINT8 b = (color.rgba & 0xFF0000) >> 16; + if (clip && clip->enabled) + { + int x2 = std::min(x + w, clip->right); + int y2 = std::min(y + h, clip->bottom); + + if (x < clip->left) + x = clip->left; + + if (y < clip->top) + y = clip->top; + + w = std::max(0, x2 - x); + h = std::max(0, y2 - y); + } + g_2d.begin_quad() .patch(nullptr) .color(r / 255.f, g / 255.f, b / 255.f, 1.f)