mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-09-02 02:32:34 -04:00
a7cd6c8113
Fixes both starting file while generating, & starting generation in file loading transition Remove RandoGenerating cvar
356 lines
16 KiB
C++
356 lines
16 KiB
C++
#include <vector>
|
|
|
|
#include <fast/Fast3dGui.h>
|
|
|
|
#include "soh/SohGui/SohGui.hpp"
|
|
#include "soh/SohGui/UIWidgets.hpp"
|
|
#include "soh/SohGui/ImGuiUtils.h"
|
|
#include "soh/OTRGlobals.h"
|
|
#include "soh/cvar_prefixes.h"
|
|
#include "soh/Enhancements/randomizer/randomizer.h"
|
|
#include "soh/Enhancements/randomizer/settings.h"
|
|
|
|
namespace SohGui {
|
|
|
|
using namespace UIWidgets;
|
|
|
|
static constexpr float kIconSize = 48.0f;
|
|
static const ImVec2 kSongSize = ImVec2(32.0f, 48.0f);
|
|
|
|
static std::shared_ptr<Fast::Fast3dGui> GetFast3dGui() {
|
|
return std::dynamic_pointer_cast<Fast::Fast3dGui>(Ship::Context::GetRawInstance()->GetWindow()->GetGui());
|
|
}
|
|
|
|
static void SaveStartingItemCVars() {
|
|
Ship::Context::GetRawInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
|
}
|
|
|
|
// Toggleable item icon (lit when the starting CVar is set, faded when not), mirroring
|
|
// DrawQuestItemButton in the save editor. The CVar name and tooltip come from the Option itself.
|
|
static void StartingItemToggle(RandomizerSettingKey rsk, uint32_t itemId, bool forceFaded = false) {
|
|
Rando::Option& option = Rando::Settings::GetInstance()->GetOption(rsk);
|
|
const char* cvar = option.GetCVarName().c_str();
|
|
const ItemMapEntry& entry = itemMapping[itemId];
|
|
bool on = !forceFaded && CVarGetInteger(cvar, 0) != 0;
|
|
|
|
ImGui::PushID(static_cast<int32_t>(rsk));
|
|
PushStyleButton(Colors::DarkGray);
|
|
if (ImGui::ImageButton(entry.name.c_str(), GetFast3dGui()->GetTextureByName(on ? entry.name : entry.nameFaded),
|
|
ImVec2(kIconSize, kIconSize), ImVec2(0, 0), ImVec2(1, 1))) {
|
|
CVarSetInteger(cvar, on ? 0 : 1);
|
|
SaveStartingItemCVars();
|
|
}
|
|
PopStyleButton();
|
|
Tooltip(option.GetName().c_str());
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Tiered item icon with a popup picker (None + one icon per tier), mirroring DrawUpgradeIcon.
|
|
// `itemIds[i]` is the icon shown for CVar value `i + 1`; value 0 shows itemIds[0] faded.
|
|
static void StartingItemTiered(RandomizerSettingKey rsk, const std::vector<uint32_t>& itemIds) {
|
|
Rando::Option& option = Rando::Settings::GetInstance()->GetOption(rsk);
|
|
const char* cvar = option.GetCVarName().c_str();
|
|
int32_t value = CVarGetInteger(cvar, 0);
|
|
|
|
ImGui::PushID(static_cast<int32_t>(rsk));
|
|
const ItemMapEntry& displayEntry = itemMapping[itemIds[value > 0 ? value - 1 : 0]];
|
|
bool lit = value > 0;
|
|
PushStyleButton(Colors::DarkGray);
|
|
if (ImGui::ImageButton(displayEntry.name.c_str(),
|
|
GetFast3dGui()->GetTextureByName(lit ? displayEntry.name : displayEntry.nameFaded),
|
|
ImVec2(kIconSize, kIconSize), ImVec2(0, 0), ImVec2(1, 1))) {
|
|
ImGui::OpenPopup("tieredPicker");
|
|
}
|
|
PopStyleButton();
|
|
Tooltip(option.GetName().c_str());
|
|
|
|
if (ImGui::BeginPopup("tieredPicker")) {
|
|
PushStyleButton(Colors::DarkGray);
|
|
if (ImGui::Button("##none", ImVec2(kIconSize, kIconSize) + ImGui::GetStyle().FramePadding * 2)) {
|
|
CVarSetInteger(cvar, 0);
|
|
SaveStartingItemCVars();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
PopStyleButton();
|
|
Tooltip("None");
|
|
for (size_t i = 0; i < itemIds.size(); i++) {
|
|
ImGui::SameLine();
|
|
const ItemMapEntry& entry = itemMapping[itemIds[i]];
|
|
PushStyleButton(Colors::DarkGray);
|
|
if (ImGui::ImageButton(entry.name.c_str(), GetFast3dGui()->GetTextureByName(entry.name),
|
|
ImVec2(kIconSize, kIconSize), ImVec2(0, 0), ImVec2(1, 1))) {
|
|
CVarSetInteger(cvar, static_cast<int32_t>(i) + 1);
|
|
SaveStartingItemCVars();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
PopStyleButton();
|
|
Tooltip(option.GetOptionText(i + 1).c_str());
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Toggleable 32x48 song icon, mirroring the songMapping loop in DrawQuestStatusTab.
|
|
static void StartingSongToggle(RandomizerSettingKey rsk, QuestItem song) {
|
|
Rando::Option& option = Rando::Settings::GetInstance()->GetOption(rsk);
|
|
const char* cvar = option.GetCVarName().c_str();
|
|
const SongMapEntry& entry = songMapping[song];
|
|
bool on = CVarGetInteger(cvar, 0) != 0;
|
|
|
|
ImGui::PushID(static_cast<int32_t>(rsk));
|
|
PushStyleButton(Colors::DarkGray);
|
|
if (ImGui::ImageButton(entry.name.c_str(), GetFast3dGui()->GetTextureByName(on ? entry.name : entry.nameFaded),
|
|
kSongSize, ImVec2(0, 0), ImVec2(1, 1))) {
|
|
CVarSetInteger(cvar, on ? 0 : 1);
|
|
SaveStartingItemCVars();
|
|
}
|
|
PopStyleButton();
|
|
Tooltip(option.GetName().c_str());
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Item icon followed by a count slider, like the ammo rows in DrawInventoryTab. The slider
|
|
// mirrors the Option's own semantics: the CVar stores the option index and the displayed
|
|
// number comes from the option text (matching how WIDGET_CVAR_SLIDER_INT renders these).
|
|
static void StartingItemCount(RandomizerSettingKey rsk, uint32_t itemId) {
|
|
Rando::Option& option = Rando::Settings::GetInstance()->GetOption(rsk);
|
|
const char* cvar = option.GetCVarName().c_str();
|
|
const ItemMapEntry& entry = itemMapping[itemId];
|
|
|
|
ImGui::PushID(static_cast<int32_t>(rsk));
|
|
ImGui::Image(GetFast3dGui()->GetTextureByName(entry.name), ImVec2(kIconSize, kIconSize));
|
|
ImGui::SameLine();
|
|
int32_t value = CVarGetInteger(cvar, option.GetMenuOptionDefault());
|
|
CVarSliderInt(option.GetName().c_str(), cvar,
|
|
IntSliderOptions()
|
|
.Color(THEME_COLOR)
|
|
.Min(0)
|
|
.Max(static_cast<int32_t>(option.GetOptionCount()) - 1)
|
|
.DefaultValue(option.GetMenuOptionDefault())
|
|
.Format(option.GetOptionText(value).c_str())
|
|
.LabelPosition(LabelPositions::Near));
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// Plain combobox bound to an Option's CVar, with option text pulled from the Option.
|
|
static void StartingItemCombobox(RandomizerSettingKey rsk) {
|
|
Rando::Option& option = Rando::Settings::GetInstance()->GetOption(rsk);
|
|
std::vector<const char*> entries;
|
|
for (size_t i = 0; i < option.GetOptionCount(); i++) {
|
|
entries.push_back(option.GetOptionText(i).c_str());
|
|
}
|
|
CVarCombobox(option.GetName().c_str(), option.GetCVarName().c_str(), entries,
|
|
ComboboxOptions().Color(THEME_COLOR).LabelPosition(LabelPositions::Above));
|
|
}
|
|
|
|
void DrawStartingItemsMenu(WidgetInfo& info) {
|
|
bool generating = IsRandoGenerating();
|
|
bool disableEditingRandoSettings = generating || CVarGetInteger(CVAR_GENERAL("OnFileSelectNameEntry"), 0);
|
|
ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0) || disableEditingRandoSettings);
|
|
|
|
ImGui::SeparatorText("Equipment");
|
|
StartingItemToggle(RSK_STARTING_KOKIRI_SWORD, ITEM_SWORD_KOKIRI);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_MASTER_SWORD, ITEM_SWORD_MASTER);
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_BIGGORON_SWORD, { ITEM_SWORD_KNIFE, ITEM_SWORD_BGS });
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_DEKU_SHIELD, ITEM_SHIELD_DEKU);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_HYLIAN_SHIELD, ITEM_SHIELD_HYLIAN);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_MIRROR_SHIELD, ITEM_SHIELD_MIRROR);
|
|
|
|
StartingItemToggle(RSK_STARTING_GORON_TUNIC, ITEM_TUNIC_GORON);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_ZORA_TUNIC, ITEM_TUNIC_ZORA);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_IRON_BOOTS, ITEM_BOOTS_IRON);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_HOVER_BOOTS, ITEM_BOOTS_HOVER);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_STONE_OF_AGONY, ITEM_STONE_OF_AGONY);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_GERUDO_CARD, ITEM_GERUDO_CARD);
|
|
|
|
// Starting Strength/Scale/Wallet have no effect when Grab/Swim/Child's Wallet are shuffled; the generator
|
|
// force-disables them (settings.cpp), so gray them out to match.
|
|
bool grabShuffled =
|
|
CVarGetInteger(Rando::Settings::GetInstance()->GetOption(RSK_SHUFFLE_GRAB).GetCVarName().c_str(), 0) != 0;
|
|
ImGui::BeginDisabled(grabShuffled);
|
|
StartingItemTiered(RSK_STARTING_STRENGTH, { ITEM_BRACELET, ITEM_GAUNTLETS_SILVER, ITEM_GAUNTLETS_GOLD });
|
|
ImGui::EndDisabled();
|
|
if (grabShuffled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
|
ImGui::SetTooltip("Disabled because Shuffle Grab is on.");
|
|
}
|
|
ImGui::SameLine();
|
|
bool swimShuffled =
|
|
CVarGetInteger(Rando::Settings::GetInstance()->GetOption(RSK_SHUFFLE_SWIM).GetCVarName().c_str(), 0) != 0;
|
|
ImGui::BeginDisabled(swimShuffled);
|
|
StartingItemTiered(RSK_STARTING_SCALE, { ITEM_SCALE_SILVER, ITEM_SCALE_GOLDEN });
|
|
ImGui::EndDisabled();
|
|
if (swimShuffled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
|
ImGui::SetTooltip("Disabled because Shuffle Swim is on.");
|
|
}
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_MAGIC_METER, { ITEM_MAGIC_SMALL, ITEM_MAGIC_LARGE });
|
|
ImGui::SameLine();
|
|
bool childsWalletShuffled =
|
|
CVarGetInteger(Rando::Settings::GetInstance()->GetOption(RSK_SHUFFLE_CHILD_WALLET).GetCVarName().c_str(), 0) !=
|
|
0;
|
|
ImGui::BeginDisabled(childsWalletShuffled);
|
|
StartingItemTiered(RSK_STARTING_WALLET, { ITEM_WALLET_ADULT, ITEM_WALLET_GIANT });
|
|
ImGui::EndDisabled();
|
|
if (childsWalletShuffled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
|
ImGui::SetTooltip("Disabled because Shuffle Child's Wallet is on.");
|
|
}
|
|
|
|
ImGui::SeparatorText("Items");
|
|
bool stickBagShuffled =
|
|
CVarGetInteger(Rando::Settings::GetInstance()->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG).GetCVarName().c_str(),
|
|
0) != 0;
|
|
ImGui::BeginDisabled(stickBagShuffled);
|
|
StartingItemToggle(RSK_STARTING_STICKS, ITEM_STICK, stickBagShuffled);
|
|
ImGui::EndDisabled();
|
|
if (stickBagShuffled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
|
ImGui::SetTooltip("Disabled because the Deku Stick Bag is being shuffled.");
|
|
}
|
|
ImGui::SameLine();
|
|
bool nutBagShuffled =
|
|
CVarGetInteger(Rando::Settings::GetInstance()->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG).GetCVarName().c_str(), 0) !=
|
|
0;
|
|
ImGui::BeginDisabled(nutBagShuffled);
|
|
StartingItemToggle(RSK_STARTING_NUTS, ITEM_NUT, nutBagShuffled);
|
|
ImGui::EndDisabled();
|
|
if (nutBagShuffled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
|
ImGui::SetTooltip("Disabled because the Deku Nut Bag is being shuffled.");
|
|
}
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_BOMB_BAG, { ITEM_BOMB_BAG_20, ITEM_BOMB_BAG_30, ITEM_BOMB_BAG_40 });
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_BOW, { ITEM_QUIVER_30, ITEM_QUIVER_40, ITEM_QUIVER_50 });
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_FIRE_ARROWS, ITEM_ARROW_FIRE);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_DINS_FIRE, ITEM_DINS_FIRE);
|
|
|
|
StartingItemTiered(RSK_STARTING_SLINGSHOT, { ITEM_BULLET_BAG_30, ITEM_BULLET_BAG_40, ITEM_BULLET_BAG_50 });
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_OCARINA, { ITEM_OCARINA_FAIRY, ITEM_OCARINA_TIME });
|
|
ImGui::SameLine();
|
|
// The bombchu bag start option only makes sense when the bombchu bag setting enables bombchu bags;
|
|
// single-bag mode is a simple toggle, progressive mode is tiered, and None disables it entirely.
|
|
{
|
|
Rando::Option& bombchuOption = Rando::Settings::GetInstance()->GetOption(RSK_BOMBCHU_BAG);
|
|
int32_t bombchuMode = CVarGetInteger(bombchuOption.GetCVarName().c_str(), RO_BOMBCHU_BAG_NONE);
|
|
if (bombchuMode == RO_BOMBCHU_BAG_PROGRESSIVE) {
|
|
StartingItemTiered(RSK_STARTING_BOMBCHU_BAG, { ITEM_BOMBCHU, ITEM_BOMBCHU, ITEM_BOMBCHU });
|
|
} else if (bombchuMode == RO_BOMBCHU_BAG_SINGLE) {
|
|
StartingItemToggle(RSK_STARTING_BOMBCHU_BAG, ITEM_BOMBCHU);
|
|
} else {
|
|
ImGui::BeginDisabled();
|
|
StartingItemToggle(RSK_STARTING_BOMBCHU_BAG, ITEM_BOMBCHU, true);
|
|
ImGui::EndDisabled();
|
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
|
ImGui::SetTooltip("Enable the Bombchu Bag setting to start with bombchus.");
|
|
}
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_HOOKSHOT, { ITEM_HOOKSHOT, ITEM_LONGSHOT });
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_ICE_ARROWS, ITEM_ARROW_ICE);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_FARORES_WIND, ITEM_FARORES_WIND);
|
|
|
|
StartingItemToggle(RSK_STARTING_BOOMERANG, ITEM_BOOMERANG);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_LENS_OF_TRUTH, ITEM_LENS);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_BEANS, ITEM_BEAN);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_MEGATON_HAMMER, ITEM_HAMMER);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_LIGHT_ARROWS, ITEM_ARROW_LIGHT);
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_NAYRUS_LOVE, ITEM_NAYRUS_LOVE);
|
|
|
|
StartingItemTiered(RSK_STARTING_BOTTLE_1, { ITEM_BOTTLE, ITEM_BIG_POE, ITEM_LETTER_RUTO });
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_BOTTLE_2, { ITEM_BOTTLE, ITEM_BIG_POE });
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_BOTTLE_3, { ITEM_BOTTLE, ITEM_BIG_POE });
|
|
ImGui::SameLine();
|
|
StartingItemTiered(RSK_STARTING_BOTTLE_4, { ITEM_BOTTLE, ITEM_BIG_POE });
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_CLAIM_CHECK, ITEM_CLAIM_CHECK);
|
|
ImGui::SameLine();
|
|
bool weirdEggShuffled =
|
|
CVarGetInteger(Rando::Settings::GetInstance()->GetOption(RSK_SHUFFLE_WEIRD_EGG).GetCVarName().c_str(),
|
|
RO_WEIRD_EGG_VANILLA) == RO_WEIRD_EGG_SHUFFLED;
|
|
ImGui::BeginDisabled(!weirdEggShuffled);
|
|
StartingItemToggle(RSK_STARTING_WEIRD_EGG, ITEM_WEIRD_EGG, !weirdEggShuffled);
|
|
ImGui::EndDisabled();
|
|
if (!weirdEggShuffled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
|
ImGui::SetTooltip("Enable Shuffle Weird Egg to start with the Weird Egg.");
|
|
}
|
|
ImGui::SameLine();
|
|
bool zeldasLetterShuffled =
|
|
CVarGetInteger(Rando::Settings::GetInstance()->GetOption(RSK_SHUFFLE_ZELDAS_LETTER).GetCVarName().c_str(), 0) !=
|
|
0;
|
|
StartingItemToggle(RSK_STARTING_ZELDAS_LETTER, ITEM_LETTER_ZELDA);
|
|
if (ImGui::IsItemHovered()) {
|
|
if (zeldasLetterShuffled) {
|
|
ImGui::SetTooltip("Start with Zelda's Letter\n\n"
|
|
"The Kakariko gate starts open.");
|
|
} else {
|
|
ImGui::SetTooltip("Start with Zelda's Letter\n\n"
|
|
"The Kakariko gate starts open.\n"
|
|
"While the letter isn't shuffled this also skips child Zelda:\n"
|
|
"you get the item Impa would give and skip everything up to meeting Zelda.");
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
StartingItemToggle(RSK_STARTING_BUNNY_HOOD, ITEM_MASK_BUNNY);
|
|
|
|
ImGui::SeparatorText("Songs");
|
|
StartingSongToggle(RSK_STARTING_ZELDAS_LULLABY, QUEST_SONG_LULLABY);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_EPONAS_SONG, QUEST_SONG_EPONA);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_SARIAS_SONG, QUEST_SONG_SARIA);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_SUNS_SONG, QUEST_SONG_SUN);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_SONG_OF_TIME, QUEST_SONG_TIME);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_SONG_OF_STORMS, QUEST_SONG_STORMS);
|
|
|
|
StartingSongToggle(RSK_STARTING_MINUET_OF_FOREST, QUEST_SONG_MINUET);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_BOLERO_OF_FIRE, QUEST_SONG_BOLERO);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_SERENADE_OF_WATER, QUEST_SONG_SERENADE);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_REQUIEM_OF_SPIRIT, QUEST_SONG_REQUIEM);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_NOCTURNE_OF_SHADOW, QUEST_SONG_NOCTURNE);
|
|
ImGui::SameLine();
|
|
StartingSongToggle(RSK_STARTING_PRELUDE_OF_LIGHT, QUEST_SONG_PRELUDE);
|
|
|
|
ImGui::SeparatorText("Other");
|
|
StartingItemCombobox(RSK_LINKS_POCKET);
|
|
// The reward type only applies when Link's Pocket grants a dungeon reward.
|
|
if (CVarGetInteger(Rando::Settings::GetInstance()->GetOption(RSK_LINKS_POCKET).GetCVarName().c_str(),
|
|
RO_LINKS_POCKET_DUNGEON_REWARD) == RO_LINKS_POCKET_DUNGEON_REWARD) {
|
|
StartingItemCombobox(RSK_LINKS_POCKET_REWARD);
|
|
}
|
|
StartingItemCount(RSK_STARTING_SKULLTULA_TOKEN, ITEM_SKULL_TOKEN);
|
|
StartingItemCount(RSK_STARTING_HEARTS, ITEM_HEART_CONTAINER);
|
|
|
|
ImGui::EndDisabled();
|
|
}
|
|
|
|
} // namespace SohGui
|