Ported BtnSelector from 2ship (#6158)

Allows the rebinding for Resetting the game, Debug Map Select, and Debug No-Clip.
Also allows for Speed Modifiers to use the new button combos, instead of only the modifier buttons.
This commit is contained in:
ItsHeckinPat
2026-01-18 11:01:39 -05:00
committed by GitHub
parent eab279c0be
commit 04c5d50b3e
11 changed files with 249 additions and 17 deletions

View File

@@ -0,0 +1,37 @@
#include <libultraship/bridge.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/ShipInit.hpp"
#include "functions.h"
#include "soh/OTRGlobals.h"
extern "C" {
#include "z64.h"
#include "overlays/gamestates/ovl_file_choose/file_choose.h"
}
static constexpr int32_t CVAR_RESET_BTN_MASK_DEFAULT = BTN_CUSTOM_MODIFIER2;
#define CVAR_RESET_BTN_MASK_NAME "gSettings.ResetBtn"
#define CVAR_RESET_BTN_MASK_VALUE CVarGetInteger(CVAR_RESET_BTN_MASK_NAME, CVAR_RESET_BTN_MASK_DEFAULT)
static void OnGameStateMainStartResetHotkey() {
const int32_t packed = CVarGetInteger("gSettings.ResetBtn", BTN_CUSTOM_MODIFIER2);
const uint16_t mask = static_cast<uint16_t>(packed & 0xFFFF);
if (mask != 0 && CHECK_BTN_ANY(gGameState->input[0].press.button, mask) &&
CHECK_BTN_ALL(gGameState->input[0].cur.button, mask)) {
auto consoleWin = std::reinterpret_pointer_cast<Ship::ConsoleWindow>(
Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console"));
if (consoleWin) {
consoleWin->Dispatch("reset");
}
}
}
static void RegisterResetHotkey() {
COND_HOOK(OnGameStateMainStart, true, OnGameStateMainStartResetHotkey);
}
static RegisterShipInitFunc initFuncResetHotkey(RegisterResetHotkey, { CVAR_RESET_BTN_MASK_NAME });

View File

@@ -1609,6 +1609,25 @@ void SohInputEditorWindow::DrawLinkTab() {
.Color(THEME_COLOR)
.Tooltip("Hold the assigned button to change the maximum walking or swimming speed"));
if (CVarGetInteger(CVAR_SETTING("WalkModifier.Enabled"), 0)) {
CVarBtnSelector(
"Speed Modifier 1 Button Combo", CVAR_SETTING("WalkModifier.Mod1Btn"),
BtnSelectorOptions()
.DefaultValue(BTN_CUSTOM_MODIFIER1)
.Color(THEME_COLOR)
.Tooltip(
"Buttons that activate Speed Modifier 1.\n\n"
"If \"Toggle modifier instead of holding\" is off, hold this combo to apply the modifier.\n"
"If it is on, tap this combo to toggle the modifier on/off."));
CVarBtnSelector(
"Speed Modifier 2 Button Combo", CVAR_SETTING("WalkModifier.Mod2Btn"),
BtnSelectorOptions()
.DefaultValue(BTN_CUSTOM_MODIFIER2)
.Color(THEME_COLOR)
.Tooltip(
"Buttons that activate Speed Modifier 2.\n\n"
"If \"Toggle modifier instead of holding\" is off, hold this combo to apply the modifier.\n"
"If it is on, tap this combo to toggle the modifier on/off."));
UIWidgets::Spacer(5);
Ship::GuiWindow::BeginGroupPanel("Speed Modifier", ImGui::GetContentRegionAvail());
CVarCheckbox("Toggle modifier instead of holding", CVAR_SETTING("WalkModifier.SpeedToggle"),

View File

@@ -460,6 +460,15 @@ void Menu::MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors me
}
}
} break;
case WIDGET_CVAR_BTN_SELECTOR: {
auto options = std::static_pointer_cast<UIWidgets::BtnSelectorOptions>(widget.options);
options->color = menuThemeIndex;
if (UIWidgets::CVarBtnSelector(widget.name.c_str(), widget.cVar, *options)) {
if (widget.callback != nullptr) {
widget.callback(widget);
}
}
} break;
case WIDGET_BUTTON: {
auto options = std::static_pointer_cast<UIWidgets::ButtonOptions>(widget.options);
options->color = menuThemeIndex;

View File

@@ -37,6 +37,7 @@ typedef enum {
WIDGET_CVAR_COMBOBOX,
WIDGET_CVAR_SLIDER_INT,
WIDGET_CVAR_SLIDER_FLOAT,
WIDGET_CVAR_BTN_SELECTOR,
WIDGET_BUTTON,
WIDGET_INPUT,
WIDGET_CVAR_INPUT,
@@ -71,10 +72,10 @@ typedef enum {
// holds the widget values for a widget, contains all CVar types available from LUS. int32_t is used for boolean
// evaluation
using CVarVariant = std::variant<int32_t, const char*, float, Color_RGBA8, Color_RGB8>;
using OptionsVariant =
std::variant<UIWidgets::ButtonOptions, UIWidgets::CheckboxOptions, UIWidgets::ComboboxOptions,
UIWidgets::FloatSliderOptions, UIWidgets::IntSliderOptions, UIWidgets::TextOptions,
UIWidgets::WidgetOptions, UIWidgets::WindowButtonOptions, UIWidgets::ColorPickerOptions>;
using OptionsVariant = std::variant<UIWidgets::ButtonOptions, UIWidgets::CheckboxOptions, UIWidgets::ComboboxOptions,
UIWidgets::FloatSliderOptions, UIWidgets::IntSliderOptions, UIWidgets::TextOptions,
UIWidgets::WidgetOptions, UIWidgets::WindowButtonOptions,
UIWidgets::ColorPickerOptions, UIWidgets::BtnSelectorOptions>;
// All the info needed for display and search of all widgets in the menu.
// `name` is the label displayed,
@@ -135,6 +136,10 @@ struct WidgetInfo {
options =
std::make_shared<UIWidgets::FloatSliderOptions>(std::get<UIWidgets::FloatSliderOptions>(options_));
break;
case WIDGET_CVAR_BTN_SELECTOR:
options =
std::make_shared<UIWidgets::BtnSelectorOptions>(std::get<UIWidgets::BtnSelectorOptions>(options_));
break;
case WIDGET_SLIDER_INT:
case WIDGET_CVAR_SLIDER_INT:
options =

View File

@@ -45,6 +45,9 @@ WidgetInfo& SohMenu::AddWidget(WidgetPath& pathInfo, std::string widgetName, Wid
case WIDGET_CVAR_SLIDER_FLOAT:
widget.options = std::make_shared<FloatSliderOptions>();
break;
case WIDGET_CVAR_BTN_SELECTOR:
widget.options = std::make_shared<BtnSelectorOptions>();
break;
case WIDGET_SLIDER_INT:
case WIDGET_CVAR_SLIDER_INT:
widget.options = std::make_shared<IntSliderOptions>();

View File

@@ -46,6 +46,14 @@ void SohMenu::AddMenuDevTools() {
.Options(
CheckboxOptions().Tooltip("Enables Debug Mode, allowing you to select maps with L + R + Z, noclip "
"with L + D-pad Right, and open the debug menu with L on the pause screen."));
AddWidget(path, "Map Select Button Combination:", WIDGET_CVAR_BTN_SELECTOR)
.CVar("gDeveloperTools.MapSelectBtn")
.Options(BtnSelectorOptions().DefaultValue(BTN_R | BTN_L | BTN_Z))
.PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); });
AddWidget(path, "No Clip Button Combination:", WIDGET_CVAR_BTN_SELECTOR)
.CVar("gDeveloperTools.NoClipBtn")
.PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); })
.Options(BtnSelectorOptions().DefaultValue(BTN_L | BTN_DRIGHT));
AddWidget(path, "OoT Registry Editor", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_DEVELOPER_TOOLS("RegEditEnabled"))
.PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); })

View File

@@ -178,6 +178,9 @@ void SohMenu::AddMenuSettings() {
.RaceDisable(false)
.Options(CheckboxOptions().Tooltip(
"Search input box gets autofocus when visible. Does not affect using other widgets."));
AddWidget(path, "Reset Button Combination:", WIDGET_CVAR_BTN_SELECTOR)
.CVar("gSettings.ResetBtn")
.Options(BtnSelectorOptions().DefaultValue(BTN_CUSTOM_MODIFIER2));
AddWidget(path, "Open App Files Folder", WIDGET_BUTTON)
.RaceDisable(false)
.Callback([](WidgetInfo& info) {

View File

@@ -7,6 +7,7 @@
#include <unordered_map>
#include <libultraship/libultra/types.h>
#include <spdlog/fmt/fmt.h>
#include "soh/OTRGlobals.h"
namespace UIWidgets {
@@ -1149,6 +1150,104 @@ void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color) {
}
ImGui::PopID();
}
std::map<std::string, int32_t> buttonMap = {
{ "A", BTN_A },
{ "B", BTN_B },
{ "Z", BTN_Z },
{ "START", BTN_START },
{ "D-Up", BTN_DUP },
{ "D-Down", BTN_DDOWN },
{ "D-Left", BTN_DLEFT },
{ "D-Right", BTN_DRIGHT },
{ "L", BTN_L },
{ "R", BTN_R },
{ "C-Up", BTN_CUP },
{ "C-Down", BTN_CDOWN },
{ "C-Left", BTN_CLEFT },
{ "C-Right", BTN_CRIGHT },
{ "Modifier 1", BTN_CUSTOM_MODIFIER1 },
{ "Modifier 2", BTN_CUSTOM_MODIFIER2 },
};
bool BtnSelector(const char* label, int32_t* value, const BtnSelectorOptions& options) {
bool dirty = false;
ImGui::PushID(label);
ImGui::BeginGroup();
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", label);
ImGui::BeginDisabled(false);
PushStyleCombobox(options.color);
ImGui::BeginChild("ButtonCombo", ImVec2(0, ImGui::GetFrameHeightWithSpacing() + 14.0f), ImGuiChildFlags_None,
ImGuiWindowFlags_HorizontalScrollbar);
int32_t currentValue = *value;
int index = 0;
for (const auto& [buttonName, buttonMask] : buttonMap) {
if (currentValue & buttonMask) {
ImGui::PushID(buttonName.c_str());
if (index++ > 0) {
ImGui::Text("+");
ImGui::SameLine();
}
if (UIWidgets::Button(buttonName.c_str(), UIWidgets::ButtonOptions()
.Tooltip("Remove this button from the combination")
.Color(UIWidgets::Colors::Gray)
.Size(UIWidgets::Sizes::Inline))) {
currentValue &= ~buttonMask;
dirty = true;
}
ImGui::PopID();
ImGui::SameLine();
}
}
if (UIWidgets::Button("+", UIWidgets::ButtonOptions({ { .tooltip = "Add a button to the combination" } })
.Size(UIWidgets::Sizes::Inline)
.Color(options.color))) {
ImGui::OpenPopup("Add Button");
}
if (ImGui::BeginPopup("Add Button")) {
UIWidgets::PushStyleMenuItem();
for (const auto& [buttonName, buttonMask] : buttonMap) {
if (!(currentValue & buttonMask)) {
if (ImGui::MenuItem(buttonName.c_str())) {
currentValue |= buttonMask;
dirty = true;
}
}
}
UIWidgets::PopStyleMenuItem();
ImGui::EndPopup();
}
ImGui::SameLine();
if (UIWidgets::Button(ICON_FA_UNDO,
UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(options.color))) {
currentValue = options.defaultValue;
dirty = true;
}
ImGui::EndChild();
PopStyleCombobox();
ImGui::EndDisabled();
ImGui::EndGroup();
ImGui::PopID();
if (dirty) {
*value = currentValue;
}
return dirty;
}
bool CVarBtnSelector(const char* label, const char* cvarName, const BtnSelectorOptions& options) {
bool dirty = false;
int32_t value = CVarGetInteger(cvarName, options.defaultValue);
if (BtnSelector(label, &value, options)) {
CVarSetInteger(cvarName, value);
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
ShipInit::Init(cvarName);
dirty = true;
}
return dirty;
}
} // namespace UIWidgets
ImVec4 GetRandomValue() {

View File

@@ -502,6 +502,38 @@ struct FloatSliderOptions : WidgetOptions {
}
};
struct BtnSelectorOptions : WidgetOptions {
s32 defaultValue = 0;
ComponentAlignments alignment = ComponentAlignments::Left;
LabelPositions labelPosition = LabelPositions::Above;
Colors color = Colors::Gray;
BtnSelectorOptions& DefaultValue(int32_t defaultValue_) {
defaultValue = defaultValue_;
return *this;
}
BtnSelectorOptions& ComponentAlignment(ComponentAlignments alignment_) {
alignment = alignment_;
return *this;
}
BtnSelectorOptions& LabelPosition(LabelPositions labelPosition_) {
labelPosition = labelPosition_;
return *this;
}
BtnSelectorOptions& Tooltip(const char* tooltip_) {
WidgetOptions::tooltip = tooltip_;
return *this;
}
BtnSelectorOptions& Color(Colors color_) {
color = color_;
return *this;
}
};
struct RadioButtonsOptions : WidgetOptions {
std::map<int32_t, const char*> buttonMap;
int32_t defaultIndex = 0;
@@ -1046,6 +1078,8 @@ void DrawFlagArray32(const std::string& name, uint32_t& flags, Colors color = Co
void DrawFlagArray16(const std::string& name, uint16_t& flags, Colors color = Colors::LightBlue);
void DrawFlagArray8(const std::string& name, uint8_t& flags, Colors color = Colors::LightBlue);
void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color = Colors::LightBlue);
bool BtnSelector(const char* label, int32_t* value, const BtnSelectorOptions& options);
bool CVarBtnSelector(const char* label, const char* cvarName, const BtnSelectorOptions& options);
void InsertHelpHoverText(const std::string& text);
void InsertHelpHoverText(const char* text);

View File

@@ -408,9 +408,11 @@ void Graph_Update(GraphicsContext* gfxCtx, GameState* gameState) {
sGraphUpdateTime = time;
}
s32 mask = CVarGetInteger("gDeveloperTools.MapSelectBtn", BTN_Z | BTN_L | BTN_R);
if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0)) {
if (CHECK_BTN_ALL(gameState->input[0].press.button, BTN_Z) &&
CHECK_BTN_ALL(gameState->input[0].cur.button, BTN_L | BTN_R)) {
if (CHECK_BTN_ANY(gameState->input[0].press.button, mask) &&
CHECK_BTN_ALL(gameState->input[0].cur.button, mask)) {
gSaveContext.gameMode = GAMEMODE_NORMAL;
SET_NEXT_GAMESTATE(gameState, Select_Init, SelectContext);
gameState->running = false;

View File

@@ -7146,9 +7146,12 @@ void func_8083DFE0(Player* this, f32* arg1, s16* arg2) {
maxSpeed *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping2"), 1.0f);
}
} else {
if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER1)) {
const s32 mod1Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod1Btn"), BTN_CUSTOM_MODIFIER1);
const s32 mod2Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod2Btn"), BTN_CUSTOM_MODIFIER2);
if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) {
maxSpeed *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping1"), 1.0f);
} else if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER2)) {
} else if (mod2Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod2Mask)) {
maxSpeed *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping2"), 1.0f);
}
}
@@ -8892,9 +8895,12 @@ void Player_Action_80842180(Player* this, PlayState* play) {
sp2C *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping2"), 1.0f);
}
} else {
if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER1)) {
const s32 mod1Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod1Btn"), BTN_CUSTOM_MODIFIER1);
const s32 mod2Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod2Btn"), BTN_CUSTOM_MODIFIER2);
if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) {
sp2C *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping1"), 1.0f);
} else if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER2)) {
} else if (mod2Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod2Mask)) {
sp2C *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping2"), 1.0f);
}
}
@@ -12411,10 +12417,15 @@ void Player_Update(Actor* thisx, PlayState* play) {
if (CVarGetInteger(CVAR_SETTING("WalkModifier.Enabled"), 0) &&
CVarGetInteger(CVAR_SETTING("WalkModifier.SpeedToggle"), 0)) {
if (CHECK_BTN_ALL(sControlInput->press.button, BTN_CUSTOM_MODIFIER1)) {
const s32 mod1Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod1Btn"), BTN_CUSTOM_MODIFIER1);
const s32 mod2Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod2Btn"), BTN_CUSTOM_MODIFIER2);
if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask) &&
CHECK_BTN_ANY(sControlInput->press.button, mod1Mask)) {
gWalkSpeedToggle1 = !gWalkSpeedToggle1;
}
if (CHECK_BTN_ALL(sControlInput->press.button, BTN_CUSTOM_MODIFIER2)) {
if (mod2Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod2Mask) &&
CHECK_BTN_ANY(sControlInput->press.button, mod2Mask)) {
gWalkSpeedToggle2 = !gWalkSpeedToggle2;
}
}
@@ -12876,9 +12887,12 @@ void func_8084AEEC(Player* this, f32* arg1, f32 arg2, s16 arg3) {
// sControlInput is NULL to prevent inputs while surfacing after obtaining an underwater item so we want to
// ignore it for that case
} else if (sControlInput != NULL) {
if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER1)) {
const s32 mod1Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod1Btn"), BTN_CUSTOM_MODIFIER1);
const s32 mod2Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod2Btn"), BTN_CUSTOM_MODIFIER2);
if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) {
swimMod *= CVarGetFloat(CVAR_SETTING("WalkModifier.SwimMapping1"), 1.0f);
} else if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER2)) {
} else if (mod2Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod2Mask)) {
swimMod *= CVarGetFloat(CVAR_SETTING("WalkModifier.SwimMapping2"), 1.0f);
}
}
@@ -15104,11 +15118,10 @@ void Player_Action_8084FBF4(Player* this, PlayState* play) {
*/
s32 Player_UpdateNoclip(Player* this, PlayState* play) {
sControlInput = &play->state.input[0];
s32 mask = CVarGetInteger("gDeveloperTools.NoClipBtn", BTN_L | BTN_DRIGHT);
if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0) &&
((CHECK_BTN_ALL(sControlInput->cur.button, BTN_A | BTN_L | BTN_R) &&
CHECK_BTN_ALL(sControlInput->press.button, BTN_B)) ||
(CHECK_BTN_ALL(sControlInput->cur.button, BTN_L) && CHECK_BTN_ALL(sControlInput->press.button, BTN_DRIGHT)))) {
(CHECK_BTN_ALL(sControlInput->cur.button, mask) && CHECK_BTN_ANY(sControlInput->press.button, mask))) {
sNoclipEnabled ^= 1;