summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett Cox <garrettjcox@gmail.com>2026-01-10 18:44:17 -0600
committerGitHub <noreply@github.com>2026-01-10 18:44:17 -0600
commit458faa34e497d558761594177db2ac952bb20a19 (patch)
tree4096f2c5e80403238c5dbd614a5d41b7da46c3a9
parent72f3dcdf8c6e6c8cfae176acb8e458c84f574954 (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.hpp6
-rw-r--r--mm/2s2h/DeveloperTools/WarpPoint.cpp1
-rw-r--r--mm/2s2h/Rando/Logic/GeneratePools.cpp21
-rw-r--r--mm/2s2h/Rando/Menu.cpp90
-rw-r--r--mm/2s2h/Rando/MiscBehavior/ClockShuffle.cpp97
-rw-r--r--mm/2s2h/Rando/MiscBehavior/ClockShuffle.h1
-rw-r--r--mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp62
-rw-r--r--mm/2s2h/Rando/Rando.h14
-rw-r--r--mm/2s2h/Rando/Spoiler/Apply.cpp4
-rw-r--r--mm/2s2h/Rando/Spoiler/Generate.cpp3
-rw-r--r--mm/2s2h/Rando/StartingItems.cpp169
-rw-r--r--mm/2s2h/Rando/StaticData/Items.cpp10
-rw-r--r--mm/2s2h/Rando/StaticData/StaticData.h3
-rw-r--r--mm/2s2h/ShipUtils.cpp33
-rw-r--r--mm/2s2h/ShipUtils.h2
-rw-r--r--mm/include/z64save.h2
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;