diff options
| author | Garrett Cox <garrettjcox@gmail.com> | 2026-01-10 18:44:17 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-10 18:44:17 -0600 |
| commit | 458faa34e497d558761594177db2ac952bb20a19 (patch) | |
| tree | 4096f2c5e80403238c5dbd614a5d41b7da46c3a9 | |
| parent | 72f3dcdf8c6e6c8cfae176acb8e458c84f574954 (diff) | |
Tweaks to how starting items works and is stored and interacted with (#1448)
* Tweaks to how starting items works and is stored and interacted with
* More tweaks to clock shuffle initialization
| -rw-r--r-- | mm/2s2h/BenJsonConversions.hpp | 6 | ||||
| -rw-r--r-- | mm/2s2h/DeveloperTools/WarpPoint.cpp | 1 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Logic/GeneratePools.cpp | 21 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Menu.cpp | 90 | ||||
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp | 97 | ||||
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/ClockShuffle.h | 1 | ||||
| -rw-r--r-- | mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp | 62 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Rando.h | 14 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Spoiler/Apply.cpp | 4 | ||||
| -rw-r--r-- | mm/2s2h/Rando/Spoiler/Generate.cpp | 3 | ||||
| -rw-r--r-- | mm/2s2h/Rando/StartingItems.cpp | 169 | ||||
| -rw-r--r-- | mm/2s2h/Rando/StaticData/Items.cpp | 10 | ||||
| -rw-r--r-- | mm/2s2h/Rando/StaticData/StaticData.h | 3 | ||||
| -rw-r--r-- | mm/2s2h/ShipUtils.cpp | 33 | ||||
| -rw-r--r-- | mm/2s2h/ShipUtils.h | 2 | ||||
| -rw-r--r-- | mm/include/z64save.h | 2 |
16 files changed, 265 insertions, 253 deletions
diff --git a/mm/2s2h/BenJsonConversions.hpp b/mm/2s2h/BenJsonConversions.hpp index 1ba1f3bee..a530adc6e 100644 --- a/mm/2s2h/BenJsonConversions.hpp +++ b/mm/2s2h/BenJsonConversions.hpp @@ -67,11 +67,7 @@ void from_json(const json& j, RandoSaveInfo& rando) { j.at("randoSaveChecks").get_to(rando.randoSaveChecks); j.at("finalSeed").get_to(rando.finalSeed); j.at("randoSaveOptions").get_to(rando.randoSaveOptions); - - // The value of Starting Items is a string in code but is stored as a char in the RandoSaveInfo - std::string startingItemsStr = j.value("randoStartingItems", ""); - strncpy(rando.randoStartingItems, startingItemsStr.c_str(), startingItemsStr.size() + 1); - + j.at("randoStartingItems").get_to(rando.randoStartingItems); j.at("foundDungeonKeys").get_to(rando.foundDungeonKeys); j.at("foundTriforcePieces").get_to(rando.foundTriforcePieces); } diff --git a/mm/2s2h/DeveloperTools/WarpPoint.cpp b/mm/2s2h/DeveloperTools/WarpPoint.cpp index c4f317bd3..ad63c0804 100644 --- a/mm/2s2h/DeveloperTools/WarpPoint.cpp +++ b/mm/2s2h/DeveloperTools/WarpPoint.cpp @@ -113,7 +113,6 @@ void RenderWarpPointSection() { "Boot to Warp Point on Launch", WARP_POINT_CVAR "BootToWarpPoint", UIWidgets::CheckboxOptions({ { .disabled = skipToFileSelect, .disabledTooltip = "Incompatible with Skip to File Select enhancement" } }) - .DefaultValue(true) .Color(THEME_COLOR) .Tooltip( "If enabled, the game will boot directly to the saved warp point with the debug save when launching " diff --git a/mm/2s2h/Rando/Logic/GeneratePools.cpp b/mm/2s2h/Rando/Logic/GeneratePools.cpp index c2fcd85b3..f8847446f 100644 --- a/mm/2s2h/Rando/Logic/GeneratePools.cpp +++ b/mm/2s2h/Rando/Logic/GeneratePools.cpp @@ -13,7 +13,7 @@ namespace Rando { namespace Logic { void GeneratePools(RandoSaveInfo& saveInfo, std::vector<RandoCheckId>& checkPool, std::vector<RandoItemId>& itemPool) { - std::vector<RandoItemId> startingItems = convertStartingItemsToRandoItemId(saveInfo.randoStartingItems, ","); + std::vector<RandoItemId> startingItems = Rando::GetStartingItemsFromSave(saveInfo); if (saveInfo.randoSaveOptions[RO_STARTING_MAPS_AND_COMPASSES]) { std::vector<RandoItemId> MapsAndCompasses = { @@ -183,8 +183,23 @@ void GeneratePools(RandoSaveInfo& saveInfo, std::vector<RandoCheckId>& checkPool } } - // Initialize shuffle time settings and item pool - ClockShuffle::InitializeFileClocks(saveInfo, itemPool); + // Shuffle Time + if (saveInfo.randoSaveOptions[RO_CLOCK_SHUFFLE] == RO_GENERIC_YES) { + auto clockShuffleMode = saveInfo.randoSaveOptions[RO_CLOCK_SHUFFLE_PROGRESSIVE]; + + if (clockShuffleMode == RO_CLOCK_SHUFFLE_RANDOM) { + itemPool.push_back(RI_TIME_DAY_1); + itemPool.push_back(RI_TIME_NIGHT_1); + itemPool.push_back(RI_TIME_DAY_2); + itemPool.push_back(RI_TIME_NIGHT_2); + itemPool.push_back(RI_TIME_DAY_3); + itemPool.push_back(RI_TIME_NIGHT_3); + } else { + for (int i = 0; i < ClockItems::HALF_COUNT; ++i) { + itemPool.push_back(RI_TIME_PROGRESSIVE); + } + } + } // Abilities if (saveInfo.randoSaveOptions[RO_SHUFFLE_SWIM] == RO_GENERIC_YES) { diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp index d057b4980..0c5d1918b 100644 --- a/mm/2s2h/Rando/Menu.cpp +++ b/mm/2s2h/Rando/Menu.cpp @@ -87,7 +87,7 @@ static void ApplyClockItemRendering(RandoItemId item, ImVec4& tintColor, std::st } // Grey out and add tooltip if progressive mode is active - if (isProgressiveMode) { + if (item != RI_TIME_PROGRESSIVE && isProgressiveMode) { tintColor.w *= DISABLED_ITEM_ALPHA; tooltipText += CLOCK_PROGRESSIVE_TOOLTIP; } @@ -157,18 +157,20 @@ static int checksInPool = 0; static int itemsInPool = 0; static int junkInPool = 0; static bool ableToBalance = true; +static std::set<RandoItemId> setOfItemsInPool; void RefreshMetrics() { + setOfItemsInPool.clear(); RandoSaveInfo randoSaveInfo; std::vector<RandoCheckId> checkPool; std::vector<RandoItemId> itemPool; - // Load options into CVars + // Load options from CVars for (auto& [randoOptionId, randoStaticOption] : Rando::StaticData::Options) { randoSaveInfo.randoSaveOptions[randoOptionId] = (uint32_t)CVarGetInteger(randoStaticOption.cvar, randoStaticOption.defaultValue); } - std::string startingItemsString = CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT); - strncpy(randoSaveInfo.randoStartingItems, startingItemsString.c_str(), startingItemsString.size() + 1); + auto startingItems = Rando::GetStartingItemsFromConfig(); + Rando::SetStartingItemsInSave(randoSaveInfo, startingItems); Rando::Logic::GeneratePools(randoSaveInfo, checkPool, itemPool); @@ -176,10 +178,14 @@ void RefreshMetrics() { itemsInPool = itemPool.size(); junkInPool = 0; for (auto& item : itemPool) { + setOfItemsInPool.insert(item); if (Rando::StaticData::Items[item].randoItemType == RITYPE_JUNK) { junkInPool++; } } + for (auto& item : startingItems) { + setOfItemsInPool.insert(item); + } ableToBalance = checksInPool >= (itemsInPool - junkInPool); } @@ -192,6 +198,7 @@ static RegisterShipInitFunc refreshMetricsInit(RefreshMetrics, { "gRando.Options.RO_ACCESS_MOON_REMAINS_COUNT", "gRando.Options.RO_ACCESS_TRIALS", "gRando.Options.RO_CLOCK_SHUFFLE", + "gRando.Options.RO_CLOCK_SHUFFLE_PROGRESSIVE", "gRando.Options.RO_HINTS_BOSS_REMAINS", "gRando.Options.RO_HINTS_GOSSIP_STONES", "gRando.Options.RO_HINTS_HOOKSHOT", @@ -473,15 +480,13 @@ static void DrawItemsTab() { { RO_CLOCK_SHUFFLE_DESCENDING, "Progressive: Descending" }, }; { - int32_t value = - CVarGetInteger(Rando::StaticData::Options[RO_CLOCK_SHUFFLE_PROGRESSIVE].cvar, RO_CLOCK_SHUFFLE_RANDOM); - if (UIWidgets::Combobox<int32_t>("Time Progression Mode", &value, &clockModeOptions)) { - CVarSetInteger(Rando::StaticData::Options[RO_CLOCK_SHUFFLE_PROGRESSIVE].cvar, value); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); - } - UIWidgets::Tooltip("Random: All 6 half-days shuffled randomly. Player starts with one random half-day.\n\n" - "Progressive Ascending: Unlocks half-days in order (D1, N1, D2, N2, D3, N3).\n\n" - "Progressive Descending: Unlocks half-days in reverse order (N3, D3, N2, D2, N1, D1)."); + UIWidgets::CVarCombobox( + "Time Progression Mode", Rando::StaticData::Options[RO_CLOCK_SHUFFLE_PROGRESSIVE].cvar, + &clockModeOptions, + UIWidgets::ComboboxOptions().Tooltip( + "Random: All 6 half-days shuffled randomly. Player starts with one random half-day.\n\n" + "Progressive Ascending: Unlocks half-days in order (D1, N1, D2, N2, D3, N3).\n\n" + "Progressive Descending: Unlocks half-days in reverse order (N3, D3, N2, D2, N1, D1).")); } // Terminal time slider (Final Hours start time) { @@ -492,15 +497,17 @@ static void DrawItemsTab() { ImGui::Spacing(); ImGui::Text("Final Hours Start Time: %02d:%02d", hours, minutes); ImGui::Spacing(); - UIWidgets::CVarSliderInt("Final Hours Start Time", Rando::StaticData::Options[RO_CLOCK_TERMINAL_TIME].cvar, - UIWidgets::IntSliderOptions().Min(0).Max(359).DefaultValue(0).LabelPosition( - UIWidgets::LabelPosition::None)); - ImGui::Spacing(); - - UIWidgets::Tooltip("Controls when the final hours countdown begins (00:00 to 05:59). " - "When you run out of owned half-days, this allows the player control over how much " - "time is left before the moon crash.\n\n" - "This setting is baked into the seed and cannot be changed after generation."); + UIWidgets::CVarSliderInt( + "Final Hours Start Time", Rando::StaticData::Options[RO_CLOCK_TERMINAL_TIME].cvar, + UIWidgets::IntSliderOptions() + .Min(0) + .Max(359) + .DefaultValue(0) + .LabelPosition(UIWidgets::LabelPosition::None) + .Tooltip("Controls when the final hours countdown begins (00:00 to 05:59). " + "When you run out of owned half-days, this allows the player control over how much " + "time is left before the moon crash.\n\n" + "This setting is baked into the seed and cannot be changed after generation.")); } } @@ -589,7 +596,7 @@ static void DrawStartingItemsTab() { f32 columnWidth = ImGui::GetContentRegionAvail().x / 2 - (ImGui::GetStyle().ItemSpacing.x * 2); f32 quarterHeight = ImGui::GetContentRegionAvail().y / 4 - (ImGui::GetStyle().ItemSpacing.y * 4); int tableColumns = 0; - ImGui::BeginChild("randoStartingOptions", ImVec2(0, quarterHeight)); + ImGui::BeginChild("randoStartingOptions", ImVec2(0, 120.0f)); ImGui::SeparatorText("Starting Options"); if (ImGui::BeginTable("Starting Options", 3)) { ImGui::TableNextColumn(); @@ -631,8 +638,8 @@ static void DrawStartingItemsTab() { ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); - std::vector<RandoItemId> setStartingItemsList = - convertStartingItemsToRandoItemId(CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT), ","); + auto setStartingItemsList = Rando::GetStartingItemsFromConfig(); + uint32_t listIndex = 0; for (auto& startingItem : setStartingItemsList) { ImGui::PushID(listIndex); @@ -654,14 +661,9 @@ static void DrawStartingItemsTab() { if (ImGui::ImageButton(std::to_string(listIndex).c_str(), textureId, imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), tintColor)) { - for (size_t i = 0; i < setStartingItemsList.size(); i++) { - if (setStartingItemsList[i] == startingItem) { - setStartingItemsList.erase(setStartingItemsList.begin() + i); - break; - } - } - CVarSetString("gRando.StartingItems", CreateStartingItemsToCvar(setStartingItemsList).c_str()); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + setStartingItemsList.erase(setStartingItemsList.begin() + listIndex); + Rando::SetStartingItemsInConfig(setStartingItemsList); + RefreshMetrics(); } UIWidgets::Tooltip(tooltipText.c_str()); listIndex++; @@ -701,6 +703,11 @@ static void DrawStartingItemsTab() { ImGui::TableSetupColumn("item", ImGuiTableColumnFlags_WidthFixed, 50.0f); } for (auto& item : category.second) { + if (setOfItemsInPool.count(item) == 0) { + // Skip items that are not in the item pool + continue; + } + ImVec2 imageSize = ImVec2(42.0f, 42.0f); if ((item >= RI_SONG_ELEGY && item <= RI_SONG_TIME) || item == RI_PROGRESSIVE_LULLABY) { imageSize.x /= 1.5f; @@ -727,14 +734,15 @@ static void DrawStartingItemsTab() { if (ImGui::ImageButton(std::to_string(item).c_str(), textureId, imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), tintColor)) { - std::string currentStartingItems = - CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT); - if (currentStartingItems.length() != 0) { - currentStartingItems += ","; + if (std::count(setStartingItemsList.begin(), setStartingItemsList.end(), item) < + (Rando::StaticData::MaxStartingItemsMap.count(item) + ? Rando::StaticData::MaxStartingItemsMap[item] + : 1)) { + + setStartingItemsList.push_back(item); + Rando::SetStartingItemsInConfig(setStartingItemsList); + RefreshMetrics(); } - currentStartingItems += std::to_string(item); - CVarSetString("gRando.StartingItems", currentStartingItems.c_str()); - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } UIWidgets::Tooltip(tooltipText.c_str()); } @@ -941,7 +949,7 @@ static void DrawCheckFilterTab() { static void DrawHintsTab() { f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2); f32 halfHeight = ImGui::GetContentRegionAvail().y / 2 - (ImGui::GetStyle().ItemSpacing.y * 2); - ImGui::BeginChild("randoHintsColumn1", ImVec2(columnWidth, halfHeight)); + ImGui::BeginChild("randoHintsColumn1", ImVec2(columnWidth, 0)); CVarCheckbox( "Spider House", Rando::StaticData::Options[RO_HINTS_SPIDER_HOUSES].cvar, CheckboxOptions( diff --git a/mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp b/mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp index 932a7cf31..fbfecc1d0 100644 --- a/mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp +++ b/mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp @@ -121,19 +121,6 @@ namespace ClockShuffle { // INTERNAL TYPES AND DATA // ============================================================================ -// Set a rando inf flag on the provided save info -static void SetRandoInfFlag(RandoSaveInfo& saveInfo, RandoInf flag) { - if (&saveInfo == &gSaveContext.save.shipSaveInfo.rando) { - // Use the helper function when setting flags on the active save context - // This ensures GameInteractor hooks are triggered and all runtime state is updated - Flags_SetRandoInf(flag); - } else { - // Directly manipulate the flag array when called during seed generation - // where saveInfo is a temporary structure not yet written to the active save - saveInfo.randoInf[flag >> 4] |= static_cast<u16>(1 << (flag & 0xF)); - } -} - // Configuration for each half-day's timing struct HalfDayTimeConfig { u8 dayNumber; // Which day (1, 2, or 3) @@ -695,89 +682,5 @@ void OnFileLoad() { }); } -// Initialize clock settings and item pool for file creation -void InitializeFileClocks(RandoSaveInfo& saveInfo, std::vector<RandoItemId>& itemPool) { - if (!saveInfo.randoSaveOptions[RO_CLOCK_SHUFFLE]) { - return; // Skip if clocks not enabled - } - - const int clockMode = saveInfo.randoSaveOptions[RO_CLOCK_SHUFFLE_PROGRESSIVE]; - - // Check if player has selected any starting time items - std::vector<RandoItemId> startingItems = convertStartingItemsToRandoItemId(saveInfo.randoStartingItems, ","); - std::vector<int> startingClockHalves; - - auto grantClockHalf = [&](int halfDayIndex) { - SetRandoInfFlag(saveInfo, static_cast<RandoInf>(RANDO_INF_OBTAINED_CLOCK_DAY_1 + halfDayIndex)); - }; - - for (RandoItemId item : startingItems) { - if (!ClockItems::IsClockItem(item)) { - continue; - } - - int halfDayIndex = ClockItems::GetHalfDayIndexFromClockItem(item); - if (halfDayIndex != ClockItems::INVALID) { - startingClockHalves.push_back(halfDayIndex); - } - } - - // If player selected starting clocks, use those instead of random/progressive logic - if (!startingClockHalves.empty()) { - // Grant all selected starting time - for (int halfDayIndex : startingClockHalves) { - grantClockHalf(halfDayIndex); - } - - // Add remaining (non-starting) time items to pool - // Progressive mode items are added in the else block of this conditional - if (clockMode == RO_CLOCK_SHUFFLE_RANDOM) { - for (int i = 0; i < ClockItems::HALF_COUNT; ++i) { - // Skip if this clock was a starting item - if (std::find(startingClockHalves.begin(), startingClockHalves.end(), i) != startingClockHalves.end()) { - continue; - } - - RandoItemId clockItem = ClockItems::GetClockItemFromHalfDayIndex(i); - if (clockItem != RI_UNKNOWN) { - itemPool.push_back(clockItem); - } - } - } - } else { - // No starting time selected - use default logic - int initialClockHalf = 0; - - if (clockMode == RO_CLOCK_SHUFFLE_RANDOM) { - // Grant one random half-day - initialClockHalf = Ship_Random(0, ClockItems::HALF_COUNT); // 0..5 map to D1..N3 - } else { - // Progressive modes: grant first half-day in sequence - initialClockHalf = (clockMode == RO_CLOCK_SHUFFLE_ASCENDING) ? 0 : ClockItems::HALF_COUNT - 1; - } - - // Own the selected half - grantClockHalf(initialClockHalf); - - if (clockMode == RO_CLOCK_SHUFFLE_RANDOM) { - // Add remaining 5 individual time items to pool - for (int i = 0; i < ClockItems::HALF_COUNT; ++i) { - if (i == initialClockHalf) { - continue; - } - RandoItemId clockItem = ClockItems::GetClockItemFromHalfDayIndex(i); - if (clockItem != RI_UNKNOWN) { - itemPool.push_back(clockItem); - } - } - } else { - // Add 5 progressive time items to pool (6 total - 1 granted = 5 remaining) - for (int i = 0; i < ClockItems::HALF_COUNT - 1; ++i) { - itemPool.push_back(RI_TIME_PROGRESSIVE); - } - } - } -} - } // namespace ClockShuffle } // namespace Rando diff --git a/mm/2s2h/Rando/MiscBehavior/ClockShuffle.h b/mm/2s2h/Rando/MiscBehavior/ClockShuffle.h index 357ec6938..9dcb6f34c 100644 --- a/mm/2s2h/Rando/MiscBehavior/ClockShuffle.h +++ b/mm/2s2h/Rando/MiscBehavior/ClockShuffle.h @@ -37,7 +37,6 @@ bool IsDayClock(RandoItemId itemId); namespace ClockShuffle { -void InitializeFileClocks(RandoSaveInfo& saveInfo, std::vector<RandoItemId>& itemPool); void OnFileLoad(); void SetTimeToHalfDayStart(int halfDayIndex); diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp index a0f9332d9..86188a056 100644 --- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp +++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp @@ -1,4 +1,5 @@ #include "MiscBehavior.h" +#include "Rando/Rando.h" #include "Rando/Spoiler/Spoiler.h" #include "Rando/Logic/Logic.h" #include "2s2h/ShipUtils.h" @@ -13,59 +14,6 @@ extern "C" { #include "overlays/actors/ovl_En_Sth/z_en_sth.h" } -void GrantStarters() { - std::vector<RandoItemId> startingItems = convertStartingItemsToRandoItemId(RANDO_STARTING_ITEMS, ","); - - if (RANDO_SAVE_OPTIONS[RO_STARTING_MAPS_AND_COMPASSES]) { - std::vector<RandoItemId> MapsAndCompasses = { - RI_GREAT_BAY_COMPASS, RI_GREAT_BAY_MAP, RI_SNOWHEAD_COMPASS, RI_SNOWHEAD_MAP, - RI_STONE_TOWER_COMPASS, RI_STONE_TOWER_MAP, RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_GREAT_BAY, - RI_TINGLE_MAP_ROMANI_RANCH, RI_TINGLE_MAP_SNOWHEAD, RI_TINGLE_MAP_STONE_TOWER, RI_TINGLE_MAP_WOODFALL, - RI_WOODFALL_COMPASS, RI_WOODFALL_MAP, - }; - - for (RandoItemId itemId : MapsAndCompasses) { - startingItems.push_back(itemId); - } - } - - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SWIM] != RO_GENERIC_YES) { - startingItems.push_back(RI_ABILITY_SWIM); - } - - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_SOULS] != RO_GENERIC_YES) { - for (int i = RI_SOUL_ENEMY_ALIEN; i <= RI_SOUL_ENEMY_WOLFOS; i++) { - startingItems.push_back((RandoItemId)i); - } - } - - if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_OCARINA_BUTTONS] != RO_GENERIC_YES) { - for (int i = RI_OCARINA_BUTTON_A; i <= RI_OCARINA_BUTTON_C_UP; i++) { - startingItems.push_back((RandoItemId)i); - } - } - - for (RandoItemId startingItem : startingItems) { - Rando::GiveItem(Rando::ConvertItem(startingItem)); - } - - if (RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] != 3) { - gSaveContext.save.saveInfo.playerData.healthCapacity = gSaveContext.save.saveInfo.playerData.health = - RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] * 0x10; - } - - if (RANDO_SAVE_OPTIONS[RO_STARTING_CONSUMABLES]) { - Rando::GiveItem(RI_DEKU_STICK); - Rando::GiveItem(RI_DEKU_NUT); - AMMO(ITEM_DEKU_STICK) = CUR_CAPACITY(UPG_DEKU_STICKS); - AMMO(ITEM_DEKU_NUT) = CUR_CAPACITY(UPG_DEKU_NUTS); - } - - if (RANDO_SAVE_OPTIONS[RO_STARTING_RUPEES]) { - gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET); - } -} - // Very primitive randomizer implementation, when a save is created, if rando is enabled // we set the save type to rando and shuffle all checks and persist the results to the save void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { @@ -120,8 +68,8 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { } // Persist StartingItems to the save - std::string startingItemsString = CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT); - strncpy(RANDO_STARTING_ITEMS, startingItemsString.c_str(), startingItemsString.size() + 1); + auto startingItems = Rando::GetStartingItemsFromConfig(); + Rando::SetStartingItemsInSave(gSaveContext.save.shipSaveInfo.rando, startingItems); std::vector<RandoCheckId> checkPool; std::vector<RandoItemId> itemPool; @@ -183,7 +131,7 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { } // Grant the starting stuff - GrantStarters(); + Rando::GrantStartingItems(); if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_VANILLA) { GiveItem(RI_SWORD_KOKIRI); @@ -226,7 +174,7 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) { Rando::Spoiler::ApplyToSaveContext(spoiler); // Grant the starting stuff - GrantStarters(); + Rando::GrantStartingItems(); Audio_PlaySfx(NA_SE_SY_ATTENTION_SOUND); } diff --git a/mm/2s2h/Rando/Rando.h b/mm/2s2h/Rando/Rando.h index 7d824e5a9..97882fedd 100644 --- a/mm/2s2h/Rando/Rando.h +++ b/mm/2s2h/Rando/Rando.h @@ -9,12 +9,6 @@ #define RANDO_SAVE_CHECKS gSaveContext.save.shipSaveInfo.rando.randoSaveChecks #define RANDO_SAVE_OPTIONS gSaveContext.save.shipSaveInfo.rando.randoSaveOptions #define RANDO_EVENTS gSaveContext.save.shipSaveInfo.rando.randoEvents -#define RANDO_STARTING_ITEMS gSaveContext.save.shipSaveInfo.rando.randoStartingItems - -#define RANDO_STARTING_ITEMS_DEFAULT \ - (std::to_string(RI_PROGRESSIVE_SWORD) + "," + std::to_string(RI_SHIELD_HERO) + "," + std::to_string(RI_OCARINA) + \ - "," + std::to_string(RI_SONG_TIME)) \ - .c_str() namespace Rando { @@ -28,6 +22,14 @@ RandoItemId ConvertItem(RandoItemId randoItemId, RandoCheckId randoCheckId = RC_ RandoCheckId FindItemPlacement(RandoItemId randoItemId); void RegisterMenu(); +void GrantStartingItems(); +std::vector<RandoItemId> GetStartingItemsFromSpoiler(nlohmann::json& spoiler); +void SetStartingItemsInSpoiler(nlohmann::json& spoiler, std::vector<RandoItemId>& startingItems); +std::vector<RandoItemId> GetStartingItemsFromSave(RandoSaveInfo& randoSaveInfo); +void SetStartingItemsInSave(RandoSaveInfo& randoSaveInfo, std::vector<RandoItemId>& startingItems); +std::vector<RandoItemId> GetStartingItemsFromConfig(); +void SetStartingItemsInConfig(std::vector<RandoItemId>& startingItems); + } // namespace Rando #endif diff --git a/mm/2s2h/Rando/Spoiler/Apply.cpp b/mm/2s2h/Rando/Spoiler/Apply.cpp index 4f3d6baae..cb9cb43c9 100644 --- a/mm/2s2h/Rando/Spoiler/Apply.cpp +++ b/mm/2s2h/Rando/Spoiler/Apply.cpp @@ -22,8 +22,8 @@ void ApplyToSaveContext(nlohmann::json spoiler) { RANDO_SAVE_OPTIONS[RO_MINIMUM_SKULLTULA_TOKENS] = SPIDER_HOUSE_TOKENS_REQUIRED; } - std::string startingItemsSave = spoiler["startingItems"].get<std::string>(); - strncpy(RANDO_STARTING_ITEMS, startingItemsSave.c_str(), startingItemsSave.size() + 1); + auto startingItems = Rando::GetStartingItemsFromSpoiler(spoiler); + Rando::SetStartingItemsInSave(gSaveContext.save.shipSaveInfo.rando, startingItems); for (auto& [randoCheckId, randoStaticCheck] : Rando::StaticData::Checks) { if (randoStaticCheck.randoCheckId == RC_UNKNOWN) { diff --git a/mm/2s2h/Rando/Spoiler/Generate.cpp b/mm/2s2h/Rando/Spoiler/Generate.cpp index 23fc19713..45c8d828c 100644 --- a/mm/2s2h/Rando/Spoiler/Generate.cpp +++ b/mm/2s2h/Rando/Spoiler/Generate.cpp @@ -16,7 +16,8 @@ nlohmann::json GenerateFromSaveContext() { spoiler["options"][randoStaticOption.name] = RANDO_SAVE_OPTIONS[randoOptionId]; } - spoiler["startingItems"] = RANDO_STARTING_ITEMS; + auto startingItems = Rando::GetStartingItemsFromSave(gSaveContext.save.shipSaveInfo.rando); + Rando::SetStartingItemsInSpoiler(spoiler, startingItems); spoiler["checks"] = nlohmann::json::object(); for (auto& [randoCheckId, randoStaticCheck] : Rando::StaticData::Checks) { diff --git a/mm/2s2h/Rando/StartingItems.cpp b/mm/2s2h/Rando/StartingItems.cpp new file mode 100644 index 000000000..bbe850100 --- /dev/null +++ b/mm/2s2h/Rando/StartingItems.cpp @@ -0,0 +1,169 @@ +#include "Rando.h" +#include "2s2h/Rando/StaticData/StaticData.h" +#include "2s2h/ShipUtils.h" +#include <libultraship/libultraship.h> +#include <libultraship/bridge/consolevariablebridge.h> + +// Starting items is a dynamically sized list of strings, so we can't store it with the other options because it can't +// fit in a CVar. We have to store it in various places +// - In the spoiler file, under startingItems as an array of strings ["RI_OCARINA"...] +// - In the save file, in randoSaveInfo.randoStartingItems as an array of u16 [RI_OCARINA...] +// - In the config file, under CVars.gRando.StartingItems as an array of strings ["RI_OCARINA"...] + +namespace Rando { + +void GrantStartingItems() { + std::vector<RandoItemId> startingItems = Rando::GetStartingItemsFromSave(gSaveContext.save.shipSaveInfo.rando); + + if (RANDO_SAVE_OPTIONS[RO_STARTING_MAPS_AND_COMPASSES]) { + std::vector<RandoItemId> MapsAndCompasses = { + RI_GREAT_BAY_COMPASS, RI_GREAT_BAY_MAP, RI_SNOWHEAD_COMPASS, RI_SNOWHEAD_MAP, + RI_STONE_TOWER_COMPASS, RI_STONE_TOWER_MAP, RI_TINGLE_MAP_CLOCK_TOWN, RI_TINGLE_MAP_GREAT_BAY, + RI_TINGLE_MAP_ROMANI_RANCH, RI_TINGLE_MAP_SNOWHEAD, RI_TINGLE_MAP_STONE_TOWER, RI_TINGLE_MAP_WOODFALL, + RI_WOODFALL_COMPASS, RI_WOODFALL_MAP, + }; + + for (RandoItemId itemId : MapsAndCompasses) { + startingItems.push_back(itemId); + } + } + + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SWIM] != RO_GENERIC_YES) { + startingItems.push_back(RI_ABILITY_SWIM); + } + + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENEMY_SOULS] != RO_GENERIC_YES) { + for (int i = RI_SOUL_ENEMY_ALIEN; i <= RI_SOUL_ENEMY_WOLFOS; i++) { + startingItems.push_back((RandoItemId)i); + } + } + + if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_OCARINA_BUTTONS] != RO_GENERIC_YES) { + for (int i = RI_OCARINA_BUTTON_A; i <= RI_OCARINA_BUTTON_C_UP; i++) { + startingItems.push_back((RandoItemId)i); + } + } + + // When shuffling time, if the player did not choose any starting time items, we need to give them at least one. + if (RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE] == RO_GENERIC_YES) { + bool hasTimeItem = false; + for (RandoItemId randoItemId : startingItems) { + if (randoItemId >= RI_TIME_DAY_1 && randoItemId <= RI_TIME_PROGRESSIVE) { + hasTimeItem = true; + break; + } + } + if (!hasTimeItem) { + if (RANDO_SAVE_OPTIONS[RO_CLOCK_SHUFFLE_PROGRESSIVE] == RO_CLOCK_SHUFFLE_RANDOM) { + Ship_Random_Seed(gSaveContext.save.shipSaveInfo.rando.finalSeed); + startingItems.push_back((RandoItemId)(RI_TIME_DAY_1 + Ship_Random(0, 5))); + } else { + startingItems.push_back(RI_TIME_PROGRESSIVE); + } + } + } + + for (RandoItemId startingItem : startingItems) { + Rando::GiveItem(Rando::ConvertItem(startingItem)); + } + + if (RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] != 3) { + gSaveContext.save.saveInfo.playerData.healthCapacity = gSaveContext.save.saveInfo.playerData.health = + RANDO_SAVE_OPTIONS[RO_STARTING_HEALTH] * 0x10; + } + + if (RANDO_SAVE_OPTIONS[RO_STARTING_CONSUMABLES]) { + Rando::GiveItem(RI_DEKU_STICK); + Rando::GiveItem(RI_DEKU_NUT); + AMMO(ITEM_DEKU_STICK) = CUR_CAPACITY(UPG_DEKU_STICKS); + AMMO(ITEM_DEKU_NUT) = CUR_CAPACITY(UPG_DEKU_NUTS); + } + + if (RANDO_SAVE_OPTIONS[RO_STARTING_RUPEES]) { + gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET); + } +} + +std::vector<RandoItemId> GetStartingItemsFromSpoiler(nlohmann::json& spoiler) { + auto startingItemsStrings = spoiler["startingItems"].get<std::vector<std::string>>(); + std::vector<RandoItemId> startingItems; + + for (auto& itemName : startingItemsStrings) { + auto randoItemId = Rando::StaticData::GetItemIdFromName(itemName.c_str()); + if (randoItemId > RI_UNKNOWN && randoItemId < RI_MAX) { + startingItems.push_back(randoItemId); + } + } + + return startingItems; +} + +void SetStartingItemsInSpoiler(nlohmann::json& spoiler, std::vector<RandoItemId>& startingItems) { + std::vector<std::string> startingItemsJson; + for (auto& randoItemId : startingItems) { + if (randoItemId > RI_UNKNOWN && randoItemId < RI_MAX) { + startingItemsJson.push_back(Rando::StaticData::Items[randoItemId].spoilerName); + } + } + spoiler["startingItems"] = startingItemsJson; +} + +std::vector<RandoItemId> GetStartingItemsFromSave(RandoSaveInfo& randoSaveInfo) { + std::vector<RandoItemId> startingItems; + + for (int i = 0; i < ARRAY_COUNT(randoSaveInfo.randoStartingItems); i++) { + if (randoSaveInfo.randoStartingItems[i] > RI_UNKNOWN && randoSaveInfo.randoStartingItems[i] < RI_MAX) { + startingItems.push_back((RandoItemId)randoSaveInfo.randoStartingItems[i]); + } + } + + return startingItems; +} + +void SetStartingItemsInSave(RandoSaveInfo& randoSaveInfo, std::vector<RandoItemId>& startingItems) { + memset(&randoSaveInfo.randoStartingItems, 0, sizeof(randoSaveInfo.randoStartingItems)); + + size_t index = 0; + for (auto& randoItemId : startingItems) { + if (index >= ARRAY_COUNT(randoSaveInfo.randoStartingItems)) { + break; + } + randoSaveInfo.randoStartingItems[index++] = randoItemId; + } +} + +std::vector<RandoItemId> GetStartingItemsFromConfig() { + auto allConfig = Ship::Context::GetInstance()->GetConfig()->GetNestedJson(); + std::vector<RandoItemId> startingItems = { RI_PROGRESSIVE_SWORD, RI_SHIELD_HERO, RI_OCARINA, RI_SONG_TIME }; + + // Verify that the config has CVars.gRando.StartingItems and its an array + if (allConfig.find("CVars") != allConfig.end() && allConfig["CVars"].is_object() && + allConfig["CVars"].find("gRando") != allConfig["CVars"].end() && allConfig["CVars"]["gRando"].is_object() && + allConfig["CVars"]["gRando"].find("StartingItems") != allConfig["CVars"]["gRando"].end() && + allConfig["CVars"]["gRando"]["StartingItems"].is_array()) { + startingItems.clear(); + + auto startingItemsStrings = allConfig["CVars"]["gRando"]["StartingItems"].get<std::vector<std::string>>(); + for (auto& itemName : startingItemsStrings) { + auto randoItemId = Rando::StaticData::GetItemIdFromName(itemName.c_str()); + if (randoItemId > RI_UNKNOWN && randoItemId < RI_MAX) { + startingItems.push_back(randoItemId); + } + } + } + + return startingItems; +} + +void SetStartingItemsInConfig(std::vector<RandoItemId>& startingItems) { + auto startingItemsJson = nlohmann::json::array(); + for (auto& randoItemId : startingItems) { + if (randoItemId > RI_UNKNOWN && randoItemId < RI_MAX) { + startingItemsJson.push_back(Rando::StaticData::Items[randoItemId].spoilerName); + } + } + Ship::Context::GetInstance()->GetConfig()->SetBlock("CVars.gRando.StartingItems", startingItemsJson); + Ship::Context::GetInstance()->GetConfig()->Save(); +} + +} // namespace Rando diff --git a/mm/2s2h/Rando/StaticData/Items.cpp b/mm/2s2h/Rando/StaticData/Items.cpp index 9345290ba..6fb2a2614 100644 --- a/mm/2s2h/Rando/StaticData/Items.cpp +++ b/mm/2s2h/Rando/StaticData/Items.cpp @@ -261,7 +261,7 @@ std::map<RandoItemId, RandoStaticItem> Items = { RI(RI_WOODFALL_STRAY_FAIRY, "a", "Woodfall Stray Fairy", RITYPE_STRAY_FAIRY, ITEM_STRAY_FAIRIES, GI_STRAY_FAIRY, GID_NONE), }; -std::unordered_map<StartingItemCategory, std::vector<RandoItemId>> StartingItemsMap = { +std::map<StartingItemCategory, std::vector<RandoItemId>> StartingItemsMap = { { STARTING_ITEMS_INVENTORY, { RI_OCARINA, RI_PROGRESSIVE_BOW, RI_ARROW_FIRE, RI_ARROW_ICE, RI_ARROW_LIGHT, RI_PROGRESSIVE_BOMB_BAG, RI_BOMBCHU, RI_DEKU_STICK, RI_DEKU_NUT, RI_MAGIC_BEAN, @@ -288,9 +288,15 @@ std::unordered_map<StartingItemCategory, std::vector<RandoItemId>> StartingItems { STARTING_ITEMS_MISC, { RI_SOUL_BOSS_GOHT, RI_SOUL_BOSS_GYORG, RI_SOUL_BOSS_MAJORA, RI_SOUL_BOSS_ODOLWA, RI_SOUL_BOSS_TWINMOLD, RI_FROG_BLUE, RI_FROG_CYAN, RI_FROG_PINK, RI_FROG_WHITE, - RI_TIME_DAY_1, RI_TIME_DAY_2, RI_TIME_DAY_3, RI_TIME_NIGHT_1, RI_TIME_NIGHT_2, RI_TIME_NIGHT_3 + RI_TIME_DAY_1, RI_TIME_DAY_2, RI_TIME_DAY_3, RI_TIME_NIGHT_1, RI_TIME_NIGHT_2, RI_TIME_NIGHT_3, RI_TIME_PROGRESSIVE, } }, }; + +std::map<RandoItemId, u8> MaxStartingItemsMap = { + { RI_PROGRESSIVE_SWORD, 3 }, { RI_PROGRESSIVE_BOMB_BAG, 3 }, { RI_PROGRESSIVE_WALLET, 2 }, + { RI_PROGRESSIVE_BOW, 3 }, { RI_PROGRESSIVE_LULLABY, 2 }, { RI_PROGRESSIVE_MAGIC, 2 }, + { RI_TIME_PROGRESSIVE, 6 }, +}; // clang-format on RandoItemId GetItemIdFromName(const char* name) { diff --git a/mm/2s2h/Rando/StaticData/StaticData.h b/mm/2s2h/Rando/StaticData/StaticData.h index 858c43716..6b5971dd7 100644 --- a/mm/2s2h/Rando/StaticData/StaticData.h +++ b/mm/2s2h/Rando/StaticData/StaticData.h @@ -46,7 +46,8 @@ struct RandoStaticItem { }; extern std::map<RandoItemId, RandoStaticItem> Items; -extern std::unordered_map<StartingItemCategory, std::vector<RandoItemId>> StartingItemsMap; +extern std::map<StartingItemCategory, std::vector<RandoItemId>> StartingItemsMap; +extern std::map<RandoItemId, u8> MaxStartingItemsMap; RandoItemId GetItemIdFromName(const char* name); u8 GetIconForZMessage(RandoItemId itemId); diff --git a/mm/2s2h/ShipUtils.cpp b/mm/2s2h/ShipUtils.cpp index aac3b5fa5..a5b90c46c 100644 --- a/mm/2s2h/ShipUtils.cpp +++ b/mm/2s2h/ShipUtils.cpp @@ -448,39 +448,6 @@ void LoadGuiTextures() { } } -std::string CreateStartingItemsToCvar(std::vector<RandoItemId> startingItemList) { - std::string startingItemsStr = ""; - for (auto& item : startingItemList) { - if (startingItemsStr != "") { - startingItemsStr += ","; - } - startingItemsStr += std::to_string(item).c_str(); - } - - return startingItemsStr; -} - -std::vector<RandoItemId> convertStartingItemsToRandoItemId(const std::string& input, const std::string& delimiter) { - std::vector<RandoItemId> result; - size_t start = 0; - size_t end = input.find(delimiter); - - while (end != std::string::npos) { - std::string item = input.substr(start, end - start); - if (!item.empty()) { - result.push_back(static_cast<RandoItemId>(std::stoul(item))); - } - start = end + delimiter.length(); - end = input.find(delimiter, start); - } - - if (!input.substr(start).empty()) { - result.push_back(static_cast<RandoItemId>(std::stoul(input.substr(start)))); - } - - return result; -} - std::string convertEnumToReadableName(const std::string& input) { std::string result; std::string content = input; diff --git a/mm/2s2h/ShipUtils.h b/mm/2s2h/ShipUtils.h index a1eaab4ab..532efdb46 100644 --- a/mm/2s2h/ShipUtils.h +++ b/mm/2s2h/ShipUtils.h @@ -21,8 +21,6 @@ #include "Rando/Rando.h" void LoadGuiTextures(); std::string convertEnumToReadableName(const std::string& input); -std::vector<RandoItemId> convertStartingItemsToRandoItemId(const std::string& input, const std::string& delimiter); -std::string CreateStartingItemsToCvar(std::vector<RandoItemId> startingItemList); std::string Ship_RemoveSpecialCharacters(const std::string& str); extern u16 sOwlWarpEntrancesForMods[]; extern std::array<const char*, 11> digitList; diff --git a/mm/include/z64save.h b/mm/include/z64save.h index 29a2c899a..58b984266 100644 --- a/mm/include/z64save.h +++ b/mm/include/z64save.h @@ -385,7 +385,7 @@ typedef struct RandoSaveInfo { RandoSaveCheck randoSaveChecks[RC_MAX]; u32 finalSeed; u32 randoSaveOptions[RO_MAX]; // Type here may change in the future - char randoStartingItems[512]; + u16 randoStartingItems[256]; // Max 256 starting items, using u16 in case we add more than 255 items s8 foundDungeonKeys[9]; // Tracks the number of dungeon keys found, opposed to the number of keys in the inventory u16 foundTriforcePieces; } RandoSaveInfo; |
