add config menu and hook it up into current config system

currently any options that requires additional settings, like "Hyrule Barrier Requirements", wont actually have any additional options yet until i figure out a clean way to handle that
This commit is contained in:
CraftyBoss
2026-05-10 23:13:02 -07:00
parent 83480110fd
commit 04d9640eff
13 changed files with 431 additions and 102 deletions
+270
View File
@@ -0,0 +1,270 @@
#include "rando_config.hpp"
#include <mutex>
#include "bool_button.hpp"
#include "pane.hpp"
#include "dusk/config.hpp"
#include "dusk/logging.h"
namespace dusk::ui {
struct ConfigBoolProps {
Rml::String key;
Rml::String icon;
Rml::String helpText;
std::function<void(bool)> onChange;
std::function<bool()> isDisabled;
};
static bool generatingSeed = false;
static std::string generationStatusMsg{};
static std::mutex generationStatusMsgMutex{};
static void StartSeedGeneration() {
if (generatingSeed) {
return;
}
generatingSeed = true;
std::lock_guard lock(generationStatusMsgMutex);
GenerateAndWriteSeed(generationStatusMsg);
generatingSeed = false;
DuskLog.debug("{}", generationStatusMsg);
}
// ripped straight from settings window
SelectButton& config_bool_select(
Pane& leftPane, Pane& rightPane, ConfigVar<bool>& var, ConfigBoolProps props) {
auto& button = leftPane.add_child<BoolButton>(BoolButton::Props{
.key = std::move(props.key),
.icon = std::move(props.icon),
.getValue = [&var] { return var.getValue(); },
.setValue =
[&var, callback = std::move(props.onChange)](bool value) {
if (value == var.getValue()) {
return;
}
var.setValue(value);
config::Save();
if (callback) {
callback(value);
}
},
.isDisabled = std::move(props.isDisabled),
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
});
leftPane.register_control(
button, rightPane, [helpText = std::move(props.helpText)](Pane& pane) {
pane.clear();
pane.add_rml(helpText);
});
return button;
}
auto& FindSetting(const std::string& key) {
// TODO: handle multi-world selection
auto& settings = g_RandomizerGenerator.GetConfig().GetSettingsList().front();
return settings.GetMap().at(key);
}
auto* FindSettingPtr(const std::string& key) {
// TODO: handle multi-world selection
auto& settings = g_RandomizerGenerator.GetConfig().GetSettingsList().front();
return &settings.GetMap().at(key);
}
void SaveConfig() {
g_RandomizerGenerator.GetConfig().WriteSettingsToFile(g_RandomizerGenerator.GetConfigPath());
}
void rando_config_group(Pane& leftPane, Pane& rightPane, std::string settingKey) {
auto randoSettings = randomizer::seedgen::settings::GetAllSettingsInfo();
auto& settingData = randoSettings->at(settingKey);
if (settingData == nullptr) {
return;
}
auto& curSetting = FindSetting(settingKey);
leftPane.register_control(
leftPane.add_select_button({
.key = settingKey,
.getValue =
[settingKey = std::move(settingKey)] { return Rml::String{FindSetting(settingKey).GetCurrentOption()}; },
}),
rightPane, [&curSetting, settingKey](Pane& pane) {
auto curSelIdx = curSetting.GetCurrentOptionIndex();
auto settingInfo = curSetting.GetInfo();
Rml::Element* text_elem = pane.add_rml(settingInfo->GetDescriptions().at(curSelIdx));
for (int i = 0; i < settingInfo->GetOptions().size(); ++i) {
pane.add_button(
{
.text = settingInfo->GetOptions()[i],
.isSelected = [settingKey = std::move(settingKey), i] { return FindSetting(settingKey).GetCurrentOptionIndex() == i; },
})
.on_pressed([i, text_elem, settingKey = std::move(settingKey)] {
auto& curSetting = FindSetting(settingKey);
auto settingInfo = curSetting.GetInfo();
mDoAud_seStartMenu(kSoundItemChange);
curSetting.SetCurrentOption(i);
text_elem->SetInnerRML(settingInfo->GetDescriptions().at(i));
SaveConfig();
});
}
});
}
SelectButton& rando_config_toggle(
Pane& leftPane, Pane& rightPane, std::string settingKey) {
auto& button = leftPane.add_child<BoolButton>(BoolButton::Props{
.key = settingKey,
.getValue = [settingKey] { return FindSetting(settingKey).GetCurrentOptionIndex() != 0; },
.setValue =
[settingKey](bool value) {
auto& setting = FindSetting(settingKey);
auto idx = setting.GetCurrentOptionIndex();
if (idx == value) {
return;
}
setting.SetCurrentOption(value);
SaveConfig();
},
});
auto& comp = leftPane.register_control(
button, rightPane, [settingKey](Pane& pane) {
pane.clear();
auto& setting = FindSetting(settingKey);
auto info = setting.GetInfo();
pane.add_rml(info->GetDescriptions()[setting.GetCurrentOptionIndex()]);
});
comp.listen(comp.root(), Rml::EventId::Click, [&rightPane, settingKey](Rml::Event&) {
rightPane.clear();
auto& setting = FindSetting(settingKey);
auto info = setting.GetInfo();
rightPane.add_rml(info->GetDescriptions()[setting.GetCurrentOptionIndex()]);
});
return button;
}
RandomizerWindow::RandomizerWindow() {
add_tab("Seed Management", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Controlled);
config_bool_select(leftPane, rightPane, getSettings().game.randomizerEnabled, {
.key = "Randomizer Enabled",
.helpText = "Determines if a new Save will load as a regular save or randomized save. (Doesn't do anything currently)"
});
leftPane.add_button("Generate Seed").on_pressed([this] {
std::thread randoGenerationThread(StartSeedGeneration);
randoGenerationThread.detach();
m_showRandoGeneration = true;
});
});
add_tab("Seed Options", [this](Rml::Element* content) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
leftPane.add_section("Logic Settings");
rando_config_group(leftPane, rightPane, "Logic Rules");
leftPane.add_section("Access Options");
rando_config_group(leftPane, rightPane, "Hyrule Barrier Requirements");
rando_config_group(leftPane, rightPane, "Palace of Twilight Requirements");
rando_config_group(leftPane, rightPane, "Faron Woods Logic");
leftPane.add_section("World (TODO)");
leftPane.add_section("Item Pool");
rando_config_toggle(leftPane, rightPane, "Golden Bugs");
rando_config_toggle(leftPane, rightPane, "Sky Characters");
rando_config_toggle(leftPane, rightPane, "Gifts From NPCs");
rando_config_toggle(leftPane, rightPane, "Shop Items");
rando_config_toggle(leftPane, rightPane, "Hidden Skills");
rando_config_toggle(leftPane, rightPane, "Hidden Rupees");
rando_config_toggle(leftPane, rightPane, "Freestanding Rupees");
rando_config_group(leftPane, rightPane, "Poe Souls");
rando_config_group(leftPane, rightPane, "Ilia Memory Quest");
rando_config_group(leftPane, rightPane, "Item Scarcity");
leftPane.add_section("Dungeon Items");
rando_config_group(leftPane, rightPane, "Small Keys");
rando_config_group(leftPane, rightPane, "Big Keys");
rando_config_group(leftPane, rightPane, "Maps and Compasses");
rando_config_group(leftPane, rightPane, "Hyrule Castle Big Key Requirements");
// TODO: figure out a way to add conditional options depending on selection above
rando_config_toggle(leftPane, rightPane, "Dungeon Rewards Can Be Anywhere");
rando_config_toggle(leftPane, rightPane, "No Small Keys on Bosses");
rando_config_toggle(leftPane, rightPane, "Unrequired Dungeons Are Barren");
leftPane.add_section("Timesavers");
rando_config_toggle(leftPane, rightPane, "Skip Prologue");
rando_config_toggle(leftPane, rightPane, "Faron Twilight Cleared");
rando_config_toggle(leftPane, rightPane, "Eldin Twilight Cleared");
rando_config_toggle(leftPane, rightPane, "Lanayru Twilight Cleared");
rando_config_toggle(leftPane, rightPane, "Skip Midna's Desparate Hour");
rando_config_toggle(leftPane, rightPane, "Skip Minor Cutscenes");
rando_config_toggle(leftPane, rightPane, "Skip Major Cutscenes");
rando_config_toggle(leftPane, rightPane, "Unlock Map Regions");
rando_config_toggle(leftPane, rightPane, "Open Door of Time");
rando_config_toggle(leftPane, rightPane, "Active Goron Mines Magnets");
rando_config_toggle(leftPane, rightPane, "Lower Hyrule Castle Chandelier");
rando_config_toggle(leftPane, rightPane, "Skip Bridge Donation");
leftPane.add_section("Additional Settings");
rando_config_group(leftPane, rightPane, "Starting Form");
rando_config_toggle(leftPane, rightPane, "Increase Wallet Capacity");
rando_config_toggle(leftPane, rightPane, "Shops Display The Replaced Item");
rando_config_toggle(leftPane, rightPane, "Bonks Do Damage");
rando_config_group(leftPane, rightPane, "Trap Item Frequency");
rando_config_toggle(leftPane, rightPane, "Starting Time of Day");
leftPane.add_section("Dungeon Entrance Settings");
rando_config_toggle(leftPane, rightPane, "Lakebed Does Not Require Water Bombs");
rando_config_toggle(leftPane, rightPane, "Arbiters Does Not Require Bulblin Camp");
rando_config_toggle(leftPane, rightPane, "Snowpeak Does Not Require Reekfish Scent");
rando_config_toggle(leftPane, rightPane, "Sacred Grove Does Not Require Skull Kid");
rando_config_toggle(leftPane, rightPane, "City Does Not Require Filled Skybook");
rando_config_group(leftPane, rightPane, "Goron Mines Entrance");
rando_config_group(leftPane, rightPane, "Temple of Time Sword Requirement");
rando_config_toggle(leftPane, rightPane, "Randomize Starting Spawn");
rando_config_group(leftPane, rightPane, "Randomize Dungeon Entrances");
rando_config_toggle(leftPane, rightPane, "Randomize Boss Entrances");
rando_config_toggle(leftPane, rightPane, "Randomize Grotto Entrances");
rando_config_toggle(leftPane, rightPane, "Randomize Cave Entrances");
rando_config_toggle(leftPane, rightPane, "Randomize Interior Entrances");
rando_config_toggle(leftPane, rightPane, "Randomize Overworld Entrances");
rando_config_toggle(leftPane, rightPane, "Decouple Double Door Entrances");
rando_config_toggle(leftPane, rightPane, "Decouple Entrances");
leftPane.add_section("Tricks");
rando_config_toggle(leftPane, rightPane, "Back Slice as Sword");
rando_config_toggle(leftPane, rightPane, "Ball and Chain Webs");
rando_config_toggle(leftPane, rightPane, "Logic Transform Anywhere");
});
}
}