From 64a112e358299a45879e53e5eec6bf63d4a1eac2 Mon Sep 17 00:00:00 2001
From: James R <justsomejames2@gmail.com>
Date: Fri, 25 Aug 2023 17:13:13 -0700
Subject: [PATCH] Pause menu: add cheats menu

Automatically populated with every cheat cvar.
---
 src/command.c                        |   2 +-
 src/command.h                        |   2 +
 src/k_menu.h                         |   1 +
 src/menus/transient/CMakeLists.txt   |   1 +
 src/menus/transient/pause-cheats.cpp | 161 +++++++++++++++++++++++++++
 src/menus/transient/pause-game.c     |   9 ++
 6 files changed, 175 insertions(+), 1 deletion(-)
 create mode 100644 src/menus/transient/pause-cheats.cpp

diff --git a/src/command.c b/src/command.c
index daf0865b99..fd5044e8b7 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 9c3e8277e1..953af40b1d 100644
--- a/src/command.h
+++ b/src/command.h
@@ -208,6 +208,8 @@ struct CVarList;
 { __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/k_menu.h b/src/k_menu.h
index 3c39dd8769..875da6535f 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 aa13975951..ea38b85056 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 0000000000..92d9a1a1bb
--- /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 <algorithm>
+#include <cstddef>
+#include <cstring>
+#include <vector>
+
+#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<menuitem_t> g_menu;
+std::vector<INT32> 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 7cc349a051..3f69e46556 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();
 }
 
-- 
GitLab