summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mm/2s2h/BenGui/BenGui.cpp13
-rw-r--r--mm/2s2h/DeveloperTools/EventLog.cpp52
-rw-r--r--mm/2s2h/DeveloperTools/SaveEditor.cpp6
-rw-r--r--mm/2s2h/Rando/ActorBehavior/EnOsn.cpp9
-rw-r--r--mm/2s2h/Rando/CheckTracker/CheckTracker.cpp59
-rw-r--r--mm/2s2h/Rando/CheckTracker/CheckTracker.h33
-rw-r--r--mm/2s2h/Rando/Menu.cpp15
-rw-r--r--mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp173
-rw-r--r--mm/2s2h/Rando/Rando.h1
-rw-r--r--mm/2s2h/Rando/RemoveItem.cpp129
-rw-r--r--mm/2s2h/Rando/StaticData/Checks.cpp10
-rw-r--r--mm/2s2h/Rando/StaticData/Regions.cpp147
-rw-r--r--mm/2s2h/Rando/StaticData/StaticData.h10
-rw-r--r--mm/2s2h/Rando/Types.h30
-rw-r--r--mm/2s2h/ShipUtils.cpp22
-rw-r--r--mm/2s2h/ShipUtils.h1
-rw-r--r--mm/include/functions.h1
-rw-r--r--mm/src/code/z_actor.c8
-rw-r--r--mm/src/code/z_parameter.c7
19 files changed, 677 insertions, 49 deletions
diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp
index 4a9fa2967..eed6d0b09 100644
--- a/mm/2s2h/BenGui/BenGui.cpp
+++ b/mm/2s2h/BenGui/BenGui.cpp
@@ -9,6 +9,7 @@
#include "UIWidgets.hpp"
#include "HudEditor.h"
#include "Notification.h"
+#include "2s2h/Rando/CheckTracker/CheckTracker.h"
#ifdef __APPLE__
#include "graphic/Fast3D/gfx_metal.h"
@@ -38,6 +39,8 @@ std::shared_ptr<EventLogWindow> mEventLogWindow;
std::shared_ptr<BenMenu> mBenMenu;
std::shared_ptr<BenInputEditorWindow> mBenInputEditorWindow;
std::shared_ptr<Notification::Window> mNotificationWindow;
+std::shared_ptr<Rando::CheckTracker::Window> mRandoCheckTrackerWindow;
+std::shared_ptr<Rando::CheckTracker::SettingsWindow> mRandoCheckTrackerSettingsWindow;
void SetupGuiElements() {
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
@@ -99,6 +102,14 @@ void SetupGuiElements() {
mNotificationWindow = std::make_shared<Notification::Window>("gWindows.Notifications", "Notifications Window");
gui->AddGuiWindow(mNotificationWindow);
mNotificationWindow->Show();
+
+ mRandoCheckTrackerWindow =
+ std::make_shared<Rando::CheckTracker::Window>("gWindows.CheckTracker", "Check Tracker", ImVec2(375, 460));
+ gui->AddGuiWindow(mRandoCheckTrackerWindow);
+
+ mRandoCheckTrackerSettingsWindow = std::make_shared<Rando::CheckTracker::SettingsWindow>(
+ "gWindows.CheckTrackerSettings", "Check Tracker Settings");
+ gui->AddGuiWindow(mRandoCheckTrackerSettingsWindow);
}
void Destroy() {
@@ -111,6 +122,8 @@ void Destroy() {
mCollisionViewerWindow = nullptr;
mEventLogWindow = nullptr;
mNotificationWindow = nullptr;
+ mRandoCheckTrackerWindow = nullptr;
+ mRandoCheckTrackerSettingsWindow = nullptr;
mSaveEditorWindow = nullptr;
mHudEditorWindow = nullptr;
diff --git a/mm/2s2h/DeveloperTools/EventLog.cpp b/mm/2s2h/DeveloperTools/EventLog.cpp
index e73857e75..72de4286b 100644
--- a/mm/2s2h/DeveloperTools/EventLog.cpp
+++ b/mm/2s2h/DeveloperTools/EventLog.cpp
@@ -7,6 +7,7 @@
#include "2s2h/BenGui/UIWidgets.hpp"
#include "2s2h/GameInteractor/GameInteractor.h"
+#include "2s2h/ShipUtils.h"
extern "C" {
#include <z64.h>
@@ -56,19 +57,6 @@ std::unordered_map<s16, const char*> actorNames = {
#undef DEFINE_ACTOR_INTERNAL
#undef DEFINE_ACTOR_UNSET
-// 2S2H Added columns to scene table: entranceSceneId, betterMapSelectIndex, humanName
-#define DEFINE_SCENE(_name, enumValue, _textId, _drawConfig, _restrictionFlags, _persistentCycleFlags, \
- _entranceSceneId, _betterMapSelectIndex, humanName) \
- { enumValue, humanName },
-#define DEFINE_SCENE_UNSET(_enumValue)
-
-std::unordered_map<s16, const char*> sceneNames = {
-#include "tables/scene_table.h"
-};
-
-#undef DEFINE_SCENE
-#undef DEFINE_SCENE_UNSET
-
static HOOK_ID onFlagSetHookId = 0;
static HOOK_ID onFlagUnsetHookId = 0;
static HOOK_ID onSceneFlagSetHookId = 0;
@@ -219,25 +207,27 @@ void RegisterEventLogHooks() {
TrimEventLog();
});
- onSceneInitHookId = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](s16 sceneId,
- s8 spawnNum) {
- eventLogEntries.insert(eventLogEntries.begin(), {
- .timestamp = CurrentTime(),
- .type = EVENT_LOG_ENTRY_TYPE_SCENE_INIT,
- .meta = fmt::format("{} {}", sceneNames[sceneId], spawnNum),
- });
- TrimEventLog();
- });
+ onSceneInitHookId =
+ GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([](s16 sceneId, s8 spawnNum) {
+ eventLogEntries.insert(eventLogEntries.begin(),
+ {
+ .timestamp = CurrentTime(),
+ .type = EVENT_LOG_ENTRY_TYPE_SCENE_INIT,
+ .meta = fmt::format("{} {}", Ship_GetSceneName(sceneId), spawnNum),
+ });
+ TrimEventLog();
+ });
- onRoomInitHookId = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnRoomInit>([](s16 sceneId,
- s8 roomId) {
- eventLogEntries.insert(eventLogEntries.begin(), {
- .timestamp = CurrentTime(),
- .type = EVENT_LOG_ENTRY_TYPE_ROOM_INIT,
- .meta = fmt::format("{} {}", sceneNames[sceneId], roomId),
- });
- TrimEventLog();
- });
+ onRoomInitHookId =
+ GameInteractor::Instance->RegisterGameHook<GameInteractor::OnRoomInit>([](s16 sceneId, s8 roomId) {
+ eventLogEntries.insert(eventLogEntries.begin(),
+ {
+ .timestamp = CurrentTime(),
+ .type = EVENT_LOG_ENTRY_TYPE_ROOM_INIT,
+ .meta = fmt::format("{} {}", Ship_GetSceneName(sceneId), roomId),
+ });
+ TrimEventLog();
+ });
onOpenTextHookId = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnOpenText>(
[](u16* textId, bool* loadFromMessageTable) {
diff --git a/mm/2s2h/DeveloperTools/SaveEditor.cpp b/mm/2s2h/DeveloperTools/SaveEditor.cpp
index 7cc3fed22..18c5b0fc8 100644
--- a/mm/2s2h/DeveloperTools/SaveEditor.cpp
+++ b/mm/2s2h/DeveloperTools/SaveEditor.cpp
@@ -1187,9 +1187,13 @@ void DrawQuestStatusTab() {
ImGui::SeparatorText("Randomizer Check Status");
for (auto& [_, randoStaticCheck] : Rando::StaticData::Checks) {
+ RandoSaveCheck& randoSaveCheck = RANDO_SAVE_CHECKS[randoStaticCheck.randoCheckId];
+ if (!randoSaveCheck.shuffled) {
+ continue;
+ }
+
std::string hiddenName = "##";
hiddenName += randoStaticCheck.name;
- RandoSaveCheck& randoSaveCheck = RANDO_SAVE_CHECKS[randoStaticCheck.randoCheckId];
UIWidgets::Checkbox((hiddenName + "eligible").c_str(), &randoSaveCheck.eligible);
ImGui::SetItemTooltip("Eligible");
ImGui::SameLine();
diff --git a/mm/2s2h/Rando/ActorBehavior/EnOsn.cpp b/mm/2s2h/Rando/ActorBehavior/EnOsn.cpp
index 972c2dd6b..d918453c0 100644
--- a/mm/2s2h/Rando/ActorBehavior/EnOsn.cpp
+++ b/mm/2s2h/Rando/ActorBehavior/EnOsn.cpp
@@ -8,10 +8,15 @@ extern "C" {
// This interaction is skipped by the SkipLearningSongOfHealing and forced on for rando for now, this file simply
// handles queuing up the checks to be given.
void Rando::ActorBehavior::InitEnOsnBehavior() {
+ COND_VB_SHOULD(VB_OSN_CONSIDER_ELIGIBLE_FOR_SONG_OF_HEALING, IS_RANDO, {
+ *should = INV_CONTENT(ITEM_OCARINA_OF_TIME) == ITEM_OCARINA_OF_TIME &&
+ !RANDO_SAVE_CHECKS[RC_CLOCK_TOWER_INTERIOR_SONG_OF_HEALING].obtained;
+ });
+
COND_VB_SHOULD(VB_GIVE_ITEM_FROM_OSN, IS_RANDO, {
*should = false;
- RANDO_SAVE_CHECKS[RC_CLOCK_TOWN_DEKU_MASK].eligible = true;
- RANDO_SAVE_CHECKS[RC_CLOCK_TOWN_SONG_OF_HEALING].eligible = true;
+ RANDO_SAVE_CHECKS[RC_CLOCK_TOWER_INTERIOR_DEKU_MASK].eligible = true;
+ RANDO_SAVE_CHECKS[RC_CLOCK_TOWER_INTERIOR_SONG_OF_HEALING].eligible = true;
});
}
diff --git a/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp
new file mode 100644
index 000000000..fcfcc318d
--- /dev/null
+++ b/mm/2s2h/Rando/CheckTracker/CheckTracker.cpp
@@ -0,0 +1,59 @@
+
+#include "CheckTracker.h"
+#include "2s2h/ShipUtils.h"
+
+namespace Rando {
+
+namespace CheckTracker {
+
+void FindReachableRegions(RandoRegionId currentRegion, std::set<RandoRegionId>& reachableRegions) {
+ auto& randoStaticRegion = Rando::StaticData::Regions[currentRegion];
+
+ for (auto& [neighborRegionId, accessLogicFunc] : randoStaticRegion.regions) {
+ // Check if the region is accessible and hasn’t been visited yet
+ if (reachableRegions.count(neighborRegionId) == 0 && accessLogicFunc()) {
+ reachableRegions.insert(neighborRegionId); // Mark region as visited
+ FindReachableRegions(neighborRegionId, reachableRegions); // Recursively visit neighbors
+ }
+ }
+}
+
+void Window::DrawElement() {
+ std::set<RandoRegionId> reachableRegions = { RR_CLOCK_TOWN_SOUTH };
+ FindReachableRegions(RR_CLOCK_TOWN_SOUTH, reachableRegions);
+
+ for (RandoRegionId regionId : reachableRegions) {
+ auto& randoStaticRegion = Rando::StaticData::Regions[regionId];
+ std::vector<RandoCheckId> availableChecks;
+
+ for (auto& [randoCheckId, accessLogicFunc] : randoStaticRegion.checks) {
+ auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId];
+ auto& randoSaveCheck = RANDO_SAVE_CHECKS[randoCheckId];
+ if (randoSaveCheck.shuffled && !randoSaveCheck.obtained && accessLogicFunc()) {
+ availableChecks.push_back(randoCheckId);
+ }
+ }
+
+ if (availableChecks.size() > 0) {
+ std::string regionName = Ship_GetSceneName(randoStaticRegion.sceneId);
+ if (randoStaticRegion.name != "") {
+ regionName += " - ";
+ regionName += randoStaticRegion.name;
+ }
+ ImGui::SeparatorText(regionName.c_str());
+
+ for (RandoCheckId checkId : availableChecks) {
+ auto& randoStaticCheck = Rando::StaticData::Checks[checkId];
+ ImGui::Text("%s", randoStaticCheck.name);
+ }
+ }
+ }
+}
+
+void SettingsWindow::DrawElement() {
+ ImGui::Text("Check Tracker Settings");
+}
+
+} // namespace CheckTracker
+
+} // namespace Rando
diff --git a/mm/2s2h/Rando/CheckTracker/CheckTracker.h b/mm/2s2h/Rando/CheckTracker/CheckTracker.h
new file mode 100644
index 000000000..84a7ff956
--- /dev/null
+++ b/mm/2s2h/Rando/CheckTracker/CheckTracker.h
@@ -0,0 +1,33 @@
+#ifndef RANDO_CHECK_TRACKER_H
+#define RANDO_CHECK_TRACKER_H
+
+#include "Rando/Rando.h"
+#include <libultraship/libultraship.h>
+
+namespace Rando {
+
+namespace CheckTracker {
+
+class Window : public Ship::GuiWindow {
+ public:
+ using GuiWindow::GuiWindow;
+
+ void InitElement() override{};
+ void DrawElement() override;
+ void UpdateElement() override{};
+};
+
+class SettingsWindow : public Ship::GuiWindow {
+ public:
+ using GuiWindow::GuiWindow;
+
+ void InitElement() override{};
+ void DrawElement() override;
+ void UpdateElement() override{};
+};
+
+} // namespace CheckTracker
+
+} // namespace Rando
+
+#endif // RANDO_CHECK_TRACKER_H
diff --git a/mm/2s2h/Rando/Menu.cpp b/mm/2s2h/Rando/Menu.cpp
index b96406b1c..2ab3af991 100644
--- a/mm/2s2h/Rando/Menu.cpp
+++ b/mm/2s2h/Rando/Menu.cpp
@@ -2,10 +2,16 @@
#include "Rando/Spoiler/Spoiler.h"
#include <libultraship/libultraship.h>
#include "2s2h/BenGui/UIWidgets.hpp"
+#include "Rando/CheckTracker/CheckTracker.h"
#include "BenPort.h"
// TODO: This block should come from elsewhere, tied to data in Rando::StaticData::Options
-std::vector<std::string> logicOptions = { "No Logic", "Vanilla" };
+std::vector<std::string> logicOptions = { "No Logic", "Vanilla", "Glitchless" };
+
+namespace BenGui {
+extern std::shared_ptr<Rando::CheckTracker::Window> mRandoCheckTrackerWindow;
+extern std::shared_ptr<Rando::CheckTracker::SettingsWindow> mRandoCheckTrackerSettingsWindow;
+} // namespace BenGui
void Rando::DrawMenu() {
if (UIWidgets::BeginMenu("Rando", UIWidgets::Colors::Green)) {
@@ -48,6 +54,13 @@ void Rando::DrawMenu() {
}
}
+ UIWidgets::WindowButton("Check Tracker", "gWindows.CheckTracker", BenGui::mRandoCheckTrackerWindow);
+ // TODO: Implement Check Tracker Settings
+ // ImGui::SameLine();
+ // UIWidgets::WindowButton(ICON_FA_COG, "gWindows.CheckTrackerSettings",
+ // BenGui::mRandoCheckTrackerSettingsWindow,
+ // { .size = UIWidgets::Sizes::Inline });
+
ImGui::EndMenu();
}
}
diff --git a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
index 9d7c95b60..09e4daa1f 100644
--- a/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
+++ b/mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
@@ -10,6 +10,27 @@ extern "C" {
#include "ShipUtils.h"
}
+void FindReachableRegions(RandoRegionId currentRegion, std::set<RandoRegionId>& reachableRegions) {
+ auto& randoStaticRegion = Rando::StaticData::Regions[currentRegion];
+
+ for (auto& [neighborRegionId, accessLogicFunc] : randoStaticRegion.regions) {
+ // Check if the region is accessible and hasn’t been visited yet
+ if (reachableRegions.count(neighborRegionId) == 0 && accessLogicFunc()) {
+ reachableRegions.insert(neighborRegionId); // Mark region as visited
+ FindReachableRegions(neighborRegionId, reachableRegions); // Recursively visit neighbors
+ }
+ }
+}
+
+struct RandoPoolEntry {
+ bool shuffled;
+ RandoItemId vanillaItemId;
+ RandoItemId placedItemId;
+ bool itemPlaced;
+ bool checkFilled;
+ bool inPool;
+};
+
// 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) {
@@ -81,6 +102,158 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) {
RANDO_SAVE_CHECKS[checkPool[i]].shuffled = true;
RANDO_SAVE_CHECKS[checkPool[i]].randoItemId = itemPool[i];
}
+ } else if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_GLITCHLESS) {
+ std::unordered_map<RandoCheckId, RandoPoolEntry> randoCheckPool;
+ SaveContext copiedSaveContext;
+ memcpy(&copiedSaveContext, &gSaveContext, sizeof(SaveContext));
+
+ // As far as logic is concerned, we start in Deku form
+ gSaveContext.save.playerForm = PLAYER_FORM_DEKU;
+
+ // First loop through all regions and add checks/items to the pool
+ for (auto& [randoRegionId, randoStaticRegion] : Rando::StaticData::Regions) {
+ for (auto& [randoCheckId, _] : randoStaticRegion.checks) {
+ auto& randoStaticCheck = Rando::StaticData::Checks[randoCheckId];
+
+ if (randoStaticCheck.randoCheckType == RCTYPE_SKULL_TOKEN &&
+ RANDO_SAVE_OPTIONS[RO_SHUFFLE_GOLD_SKULLTULAS] == RO_GENERIC_NO) {
+ continue;
+ }
+
+ if (randoStaticCheck.randoCheckType == RCTYPE_POT &&
+ RANDO_SAVE_OPTIONS[RO_SHUFFLE_POTS] == RO_GENERIC_NO) {
+ continue;
+ }
+
+ if (randoStaticCheck.randoCheckType == RCTYPE_SHOP) {
+ if (RANDO_SAVE_OPTIONS[RO_SHUFFLE_SHOPS] == RO_GENERIC_NO) {
+ continue;
+ } else {
+ RANDO_SAVE_CHECKS[randoCheckId].price = Ship_Random(0, 200);
+ }
+ }
+
+ randoCheckPool[randoCheckId] = { true, randoStaticCheck.randoItemId,
+ RI_UNKNOWN, false,
+ false, false };
+ }
+ }
+
+ // Recursive lambda for backtracking placement
+ std::function<bool(std::set<RandoRegionId>)> PlaceItems =
+ [&](std::set<RandoRegionId> reachableRegions) -> bool {
+ // Crawl through all reachable regions and add any new reachable regions
+ std::set<RandoRegionId> currentReachableRegions = reachableRegions;
+ for (RandoRegionId regionId : reachableRegions) {
+ FindReachableRegions(regionId, currentReachableRegions);
+ }
+
+ // Mark newly accessible checks
+ std::vector<RandoCheckId> newChecksInPool;
+ for (RandoRegionId regionId : currentReachableRegions) {
+ auto& randoStaticRegion = Rando::StaticData::Regions[regionId];
+ for (auto& [randoCheckId, accessLogicFunc] : randoStaticRegion.checks) {
+ if (
+ // Check is shuffled
+ randoCheckPool[randoCheckId].shuffled &&
+ // Check is not already in the pool
+ randoCheckPool[randoCheckId].inPool == false) {
+ // Check is accessible
+ if (accessLogicFunc()) {
+ randoCheckPool[randoCheckId].inPool = true;
+ newChecksInPool.push_back(randoCheckId);
+ } else {
+ SPDLOG_INFO("Check {} is not accessible",
+ Rando::StaticData::Checks[randoCheckId].name);
+ }
+ }
+ }
+ }
+
+ int checksLeft = 0;
+ std::vector<RandoCheckId> currentCheckPool;
+ std::vector<std::pair<RandoItemId, RandoCheckId>> currentItemPool;
+ for (auto& [randoCheckId, randoPoolEntry] : randoCheckPool) {
+ if (randoPoolEntry.inPool) {
+ if (!randoPoolEntry.itemPlaced) {
+ currentItemPool.push_back({ randoPoolEntry.vanillaItemId, randoCheckId });
+ }
+ if (!randoPoolEntry.checkFilled) {
+ currentCheckPool.push_back(randoCheckId);
+ }
+ }
+ if (randoPoolEntry.shuffled && !randoPoolEntry.checkFilled) {
+ checksLeft++;
+ }
+ }
+ if (checksLeft == 0) {
+ return true; // All items placed
+ }
+
+ if (currentItemPool.size() == 0) {
+ for (RandoCheckId randoCheckId : newChecksInPool) {
+ randoCheckPool[randoCheckId].inPool = false;
+ }
+ return false; // No more items to place
+ }
+
+ // Shuffle the pool
+ if (currentItemPool.size() > 1) {
+ for (size_t i = 0; i < currentItemPool.size(); i++) {
+ std::swap(currentItemPool[i],
+ currentItemPool[Ship_Random(0, currentItemPool.size() - 1)]);
+ }
+ }
+
+ for (size_t i = 0; i < currentItemPool.size(); i++) {
+ // Place the item in the check
+ RandoCheckId randoCheckId = currentCheckPool[i];
+ auto [randoItemId, randoCheckIdFromItem] = currentItemPool[i];
+ SPDLOG_INFO("Placing item {} in check {}",
+ Rando::StaticData::Items[randoItemId].spoilerName,
+ Rando::StaticData::Checks[randoCheckId].name);
+ randoCheckPool[randoCheckId].checkFilled = true;
+ randoCheckPool[randoCheckId].placedItemId = randoItemId;
+ randoCheckPool[randoCheckIdFromItem].itemPlaced = true;
+ RandoItemId convertedItemId = ConvertItem(randoItemId);
+ GiveItem(convertedItemId);
+
+ // Recurse to place the next item
+ if (PlaceItems(currentReachableRegions)) {
+ return true; // Found a solution
+ }
+
+ SPDLOG_INFO("Failed to place item {} in check {}",
+ Rando::StaticData::Items[randoItemId].spoilerName,
+ Rando::StaticData::Checks[randoCheckId].name);
+ // Backtrack: remove the item and try another check
+ randoCheckPool[randoCheckId].checkFilled = false;
+ randoCheckPool[randoCheckId].placedItemId = RI_UNKNOWN;
+ randoCheckPool[randoCheckIdFromItem].itemPlaced = false;
+ RemoveItem(convertedItemId);
+ }
+
+ for (RandoCheckId randoCheckId : newChecksInPool) {
+ randoCheckPool[randoCheckId].inPool = false;
+ }
+
+ return false; // No valid placements for this item
+ };
+
+ if (!PlaceItems({ RR_CLOCK_TOWN_SOUTH })) {
+ memcpy(&gSaveContext, &copiedSaveContext, sizeof(SaveContext));
+ throw std::runtime_error("Failed to place all items in glitchless logic, input seed: " +
+ inputSeed);
+ } else {
+ memcpy(&gSaveContext, &copiedSaveContext, sizeof(SaveContext));
+
+ for (auto& [randoCheckId, randoPoolEntry] : randoCheckPool) {
+ RANDO_SAVE_CHECKS[randoCheckId].randoItemId = randoPoolEntry.placedItemId;
+ RANDO_SAVE_CHECKS[randoCheckId].shuffled = true;
+ }
+
+ SPDLOG_INFO("Successfully placed all items in glitchless logic, input seed: {}", inputSeed);
+ }
}
if (CVarGetInteger("gRando.GenerateSpoiler", 0)) {
diff --git a/mm/2s2h/Rando/Rando.h b/mm/2s2h/Rando/Rando.h
index 8733237fc..f6b0b3b4f 100644
--- a/mm/2s2h/Rando/Rando.h
+++ b/mm/2s2h/Rando/Rando.h
@@ -13,6 +13,7 @@ namespace Rando {
void Init();
void DrawItem(RandoItemId randoItemId);
void GiveItem(RandoItemId randoItemId);
+void RemoveItem(RandoItemId randoItemId);
RandoItemId ConvertItem(RandoItemId randoItemId, RandoCheckId randoCheckId = RC_UNKNOWN);
void DrawMenu();
diff --git a/mm/2s2h/Rando/RemoveItem.cpp b/mm/2s2h/Rando/RemoveItem.cpp
new file mode 100644
index 000000000..1de95e2ad
--- /dev/null
+++ b/mm/2s2h/Rando/RemoveItem.cpp
@@ -0,0 +1,129 @@
+#include "Rando/Rando.h"
+#include <libultraship/libultraship.h>
+
+extern "C" {
+#include "variables.h"
+#include "functions.h"
+}
+
+void Rando::RemoveItem(RandoItemId randoItemId) {
+ switch (randoItemId) {
+ case RI_CLOCK_TOWN_STRAY_FAIRY:
+ CLEAR_WEEKEVENTREG(WEEKEVENTREG_08_80);
+ break;
+ case RI_WOODFALL_STRAY_FAIRY:
+ gSaveContext.save.saveInfo.inventory.strayFairies[DUNGEON_INDEX_WOODFALL_TEMPLE]--;
+ break;
+ case RI_SNOWHEAD_STRAY_FAIRY:
+ gSaveContext.save.saveInfo.inventory.strayFairies[DUNGEON_INDEX_SNOWHEAD_TEMPLE]--;
+ break;
+ case RI_GREAT_BAY_STRAY_FAIRY:
+ gSaveContext.save.saveInfo.inventory.strayFairies[DUNGEON_INDEX_GREAT_BAY_TEMPLE]--;
+ break;
+ case RI_STONE_TOWER_STRAY_FAIRY:
+ gSaveContext.save.saveInfo.inventory.strayFairies[DUNGEON_INDEX_STONE_TOWER_TEMPLE]--;
+ break;
+ case RI_GREAT_SPIN_ATTACK:
+ CLEAR_WEEKEVENTREG(WEEKEVENTREG_OBTAINED_GREAT_SPIN_ATTACK);
+ break;
+ case RI_DOUBLE_DEFENSE:
+ gSaveContext.save.saveInfo.playerData.doubleDefense = false;
+ gSaveContext.save.saveInfo.inventory.defenseHearts = 0;
+ break;
+ case RI_SINGLE_MAGIC:
+ gSaveContext.save.saveInfo.playerData.isMagicAcquired = false;
+ break;
+ case RI_DOUBLE_MAGIC:
+ gSaveContext.save.saveInfo.playerData.isDoubleMagicAcquired = false;
+ break;
+ case RI_WOODFALL_BOSS_KEY:
+ case RI_WOODFALL_MAP:
+ case RI_WOODFALL_COMPASS:
+ REMOVE_DUNGEON_ITEM(Rando::StaticData::Items[randoItemId].itemId - ITEM_KEY_BOSS,
+ DUNGEON_INDEX_WOODFALL_TEMPLE);
+ break;
+ case RI_WOODFALL_SMALL_KEY:
+ DUNGEON_KEY_COUNT(DUNGEON_INDEX_WOODFALL_TEMPLE)--;
+ gSaveContext.save.shipSaveInfo.rando.foundDungeonKeys[DUNGEON_INDEX_WOODFALL_TEMPLE]--;
+ break;
+ case RI_SNOWHEAD_BOSS_KEY:
+ case RI_SNOWHEAD_MAP:
+ case RI_SNOWHEAD_COMPASS:
+ REMOVE_DUNGEON_ITEM(Rando::StaticData::Items[randoItemId].itemId - ITEM_KEY_BOSS,
+ DUNGEON_INDEX_SNOWHEAD_TEMPLE);
+ break;
+ case RI_SNOWHEAD_SMALL_KEY:
+ DUNGEON_KEY_COUNT(DUNGEON_INDEX_SNOWHEAD_TEMPLE)--;
+ gSaveContext.save.shipSaveInfo.rando.foundDungeonKeys[DUNGEON_INDEX_WOODFALL_TEMPLE]--;
+ break;
+ case RI_GREAT_BAY_BOSS_KEY:
+ case RI_GREAT_BAY_MAP:
+ case RI_GREAT_BAY_COMPASS:
+ REMOVE_DUNGEON_ITEM(Rando::StaticData::Items[randoItemId].itemId - ITEM_KEY_BOSS,
+ DUNGEON_INDEX_GREAT_BAY_TEMPLE);
+ break;
+ case RI_GREAT_BAY_SMALL_KEY:
+ DUNGEON_KEY_COUNT(DUNGEON_INDEX_GREAT_BAY_TEMPLE)--;
+ gSaveContext.save.shipSaveInfo.rando.foundDungeonKeys[DUNGEON_INDEX_WOODFALL_TEMPLE]--;
+ break;
+ case RI_STONE_TOWER_BOSS_KEY:
+ case RI_STONE_TOWER_MAP:
+ case RI_STONE_TOWER_COMPASS:
+ REMOVE_DUNGEON_ITEM(Rando::StaticData::Items[randoItemId].itemId - ITEM_KEY_BOSS,
+ DUNGEON_INDEX_STONE_TOWER_TEMPLE);
+ break;
+ case RI_STONE_TOWER_SMALL_KEY:
+ DUNGEON_KEY_COUNT(DUNGEON_INDEX_STONE_TOWER_TEMPLE)--;
+ gSaveContext.save.shipSaveInfo.rando.foundDungeonKeys[DUNGEON_INDEX_WOODFALL_TEMPLE]--;
+ break;
+ case RI_PROGRESSIVE_MAGIC:
+ case RI_PROGRESSIVE_BOW:
+ case RI_PROGRESSIVE_BOMB_BAG:
+ // TODO
+ break;
+ case RI_GS_TOKEN_SWAMP: {
+ int skullTokenCount = Inventory_GetSkullTokenCount(SCENE_KINSTA1);
+ gSaveContext.save.saveInfo.skullTokenCount =
+ ((int)(skullTokenCount & 0xFFFF) << 0x10) | (gSaveContext.save.saveInfo.skullTokenCount & 0xFFFF);
+ break;
+ }
+ case RI_GS_TOKEN_OCEAN: {
+ int skullTokenCount = Inventory_GetSkullTokenCount(SCENE_KINDAN2);
+ gSaveContext.save.saveInfo.skullTokenCount =
+ (gSaveContext.save.saveInfo.skullTokenCount & 0xFFFF0000) | (skullTokenCount & 0xFFFF);
+ break;
+ }
+ case RI_MOONS_TEAR:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR);
+ break;
+ case RI_DEED_LAND:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_DEED_LAND);
+ break;
+ case RI_DEED_SWAMP:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_DEED_SWAMP);
+ break;
+ case RI_DEED_MOUNTAIN:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_DEED_MOUNTAIN);
+ break;
+ case RI_DEED_OCEAN:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_DEED_OCEAN);
+ break;
+ case RI_ROOM_KEY:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_ROOM_KEY);
+ break;
+ case RI_LETTER_TO_MAMA:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_LETTER_TO_MAMA);
+ break;
+ case RI_LETTER_TO_KAFEI:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_LETTER_TO_KAFEI);
+ break;
+ case RI_PENDANT_OF_MEMORIES:
+ Flags_ClearRandoInf(RANDO_INF_OBTAINED_PENDANT_OF_MEMORIES);
+ break;
+ default:
+ // TODO: Handle equipment
+ Inventory_DeleteItem(Rando::StaticData::Items[randoItemId].itemId,
+ SLOT(Rando::StaticData::Items[randoItemId].itemId));
+ break;
+ }
+}
diff --git a/mm/2s2h/Rando/StaticData/Checks.cpp b/mm/2s2h/Rando/StaticData/Checks.cpp
index 25cc40997..41eb50024 100644
--- a/mm/2s2h/Rando/StaticData/Checks.cpp
+++ b/mm/2s2h/Rando/StaticData/Checks.cpp
@@ -29,17 +29,17 @@ std::map<RandoCheckId, RandoStaticCheck> Checks = {
RC(RC_BENEATH_THE_WELL_KEESE_CHEST, RCTYPE_CHEST, SCENE_REDEAD, FLAG_CYCL_SCENE_CHEST, 0x02, RI_RUPEE_PURPLE),
RC(RC_BENEATH_THE_WELL_MIRROR_SHIELD, RCTYPE_CHEST, SCENE_REDEAD, FLAG_CYCL_SCENE_CHEST, 0x1b, RI_SHIELD_MIRROR),
RC(RC_BENEATH_THE_WELL_SKULLTULLA_CHEST, RCTYPE_CHEST, SCENE_REDEAD, FLAG_CYCL_SCENE_CHEST, 0x01, RI_RUPEE_PURPLE),
+ RC(RC_CLOCK_TOWER_INTERIOR_DEKU_MASK, RCTYPE_NPC, SCENE_INSIDETOWER, FLAG_NONE, 0x00, RI_MASK_DEKU),
+ RC(RC_CLOCK_TOWER_INTERIOR_SONG_OF_HEALING, RCTYPE_NPC, SCENE_INSIDETOWER, FLAG_NONE, 0x00, RI_SONG_OF_HEALING),
RC(RC_CLOCK_TOWER_ROOF_OCARINA, RCTYPE_NPC, SCENE_OKUJOU, FLAG_NONE, 0x00, RI_OCARINA),
RC(RC_CLOCK_TOWER_ROOF_SONG_OF_TIME, RCTYPE_SONG, SCENE_OKUJOU, FLAG_NONE, 0x00, RI_SONG_OF_TIME),
- RC(RC_CLOCK_TOWN_DEKU_MASK, RCTYPE_NPC, SCENE_INSIDETOWER, FLAG_NONE, 0x00, RI_MASK_DEKU),
RC(RC_CLOCK_TOWN_EAST_UPPER_CHEST, RCTYPE_CHEST, SCENE_TOWN, FLAG_CYCL_SCENE_CHEST, 0x0A, RI_RUPEE_SILVER),
RC(RC_CLOCK_TOWN_GREAT_FAIRY_ALT, RCTYPE_NPC, SCENE_YOUSEI_IZUMI, FLAG_NONE, 0x00, RI_MASK_GREAT_FAIRY),
RC(RC_CLOCK_TOWN_GREAT_FAIRY, RCTYPE_NPC, SCENE_YOUSEI_IZUMI, FLAG_NONE, 0x00, RI_PROGRESSIVE_MAGIC),
RC(RC_CLOCK_TOWN_LAUNDRY_GURU_GURU, RCTYPE_NPC, SCENE_ALLEY, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_38_40, RI_MASK_BREMEN),
- RC(RC_CLOCK_TOWN_NORTH_TREE_HP, RCTYPE_FREESTANDING, SCENE_BACKTOWN, FLAG_CYCL_SCENE_COLLECTIBLE, 0x0A, RI_HEART_PIECE),
RC(RC_CLOCK_TOWN_NORTH_BOMB_LADY, RCTYPE_NPC, SCENE_BACKTOWN, FLAG_CYCL_SCENE_COLLECTIBLE, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_BLAST_MASK, RI_MASK_BLAST),
+ RC(RC_CLOCK_TOWN_NORTH_TREE_HP, RCTYPE_FREESTANDING, SCENE_BACKTOWN, FLAG_CYCL_SCENE_COLLECTIBLE, 0x0A, RI_HEART_PIECE),
RC(RC_CLOCK_TOWN_SCRUB_DEED, RCTYPE_NPC, SCENE_CLOCKTOWER, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_17_80, RI_DEED_LAND),
- RC(RC_CLOCK_TOWN_SONG_OF_HEALING, RCTYPE_NPC, SCENE_INSIDETOWER, FLAG_NONE, 0x00, RI_SONG_OF_HEALING),
RC(RC_CLOCK_TOWN_SOUTH_CHEST_LOWER, RCTYPE_CHEST, SCENE_CLOCKTOWER, FLAG_CYCL_SCENE_CHEST, 0x00, RI_RUPEE_RED),
RC(RC_CLOCK_TOWN_SOUTH_CHEST_UPPER, RCTYPE_CHEST, SCENE_CLOCKTOWER, FLAG_CYCL_SCENE_CHEST, 0x01, RI_RUPEE_PURPLE),
RC(RC_CLOCK_TOWN_SOUTH_PLATFORM_HP, RCTYPE_FREESTANDING, SCENE_CLOCKTOWER, FLAG_CYCL_SCENE_COLLECTIBLE, 0x0A, RI_HEART_PIECE),
@@ -86,8 +86,8 @@ std::map<RandoCheckId, RandoStaticCheck> Checks = {
RC(RC_LONE_PEAK_SHRINE_BOULDER_CHEST, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x06, RI_RUPEE_RED),
RC(RC_LONE_PEAK_SHRINE_INVISIBLE_CHEST, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x03, RI_RUPEE_PURPLE),
RC(RC_LONE_PEAK_SHRINE_LENS_CHEST, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x01, RI_LENS),
- RC(RC_MAYORS_OFFICE_KAFEIS_MASK, RCTYPE_NPC, SCENE_SONCHONOIE, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_KAFEIS_MASK, RI_MASK_KAFEIS_MASK),
RC(RC_MAYORS_OFFICE_HP, RCTYPE_NPC, SCENE_SONCHONOIE, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_MAYOR_HP, RI_HEART_PIECE),
+ RC(RC_MAYORS_OFFICE_KAFEIS_MASK, RCTYPE_NPC, SCENE_SONCHONOIE, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_KAFEIS_MASK, RI_MASK_KAFEIS_MASK),
RC(RC_MOON_TRIAL_DEKU_HP, RCTYPE_FREESTANDING, SCENE_LAST_DEKU, FLAG_CYCL_SCENE_COLLECTIBLE, 0x01, RI_HEART_PIECE),
RC(RC_MOON_TRIAL_GORON_HP, RCTYPE_FREESTANDING, SCENE_LAST_GORON, FLAG_CYCL_SCENE_COLLECTIBLE, 0x01, RI_HEART_PIECE),
RC(RC_MOON_TRIAL_LINK_GARO_MASTER_CHEST, RCTYPE_CHEST, SCENE_LAST_LINK, FLAG_CYCL_SCENE_CHEST, 0x01, RI_ARROWS_30),
@@ -252,13 +252,13 @@ std::map<RandoCheckId, RandoStaticCheck> Checks = {
RC(RC_SWORDSMAN_SCHOOL_HP, RCTYPE_NPC, SCENE_DOUJOU, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_63_20, RI_HEART_PIECE),
RC(RC_TERMINA_FIELD_BIO_BABA_GROTTO, RCTYPE_FREESTANDING, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_COLLECTIBLE, 0x02, RI_HEART_PIECE),
RC(RC_TERMINA_FIELD_DODONGO_GROTTO, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x00, RI_HEART_PIECE),
+ RC(RC_TERMINA_FIELD_KAMARO, RCTYPE_NPC, SCENE_00KEIKOKU, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_KAMAROS_MASK, RI_MASK_KAMARO),
RC(RC_TERMINA_FIELD_PEAHAT_GROTTO, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x04, RI_HEART_PIECE),
RC(RC_TERMINA_FIELD_PILLAR_GROTTO, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x1a, RI_BOMBCHU),
RC(RC_TERMINA_FIELD_TALL_GRASS_CHEST, RCTYPE_CHEST, SCENE_00KEIKOKU, FLAG_CYCL_SCENE_CHEST, 0x01, RI_RUPEE_RED),
RC(RC_TERMINA_FIELD_TALL_GRASS_GROTTO, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x1f, RI_RUPEE_RED),
RC(RC_TERMINA_FIELD_TREE_STUMP_CHEST, RCTYPE_CHEST, SCENE_00KEIKOKU, FLAG_CYCL_SCENE_CHEST, 0x02, RI_RUPEE_RED),
RC(RC_TERMINA_FIELD_WATER_CHEST, RCTYPE_CHEST, SCENE_00KEIKOKU, FLAG_CYCL_SCENE_CHEST, 0x00, RI_RUPEE_RED),
- RC(RC_TERMINA_FIELD_KAMARO, RCTYPE_NPC, SCENE_00KEIKOKU, FLAG_WEEK_EVENT_REG, WEEKEVENTREG_BOMBERS_NOTEBOOK_EVENT_RECEIVED_KAMAROS_MASK, RI_MASK_KAMARO),
RC(RC_TWIN_ISLANDS_FROZEN_GROTTO_CHEST, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x02, RI_RUPEE_RED),
RC(RC_TWIN_ISLANDS_RAMP_GROTTO_CHEST, RCTYPE_CHEST, SCENE_KAKUSIANA, FLAG_CYCL_SCENE_CHEST, 0x19, RI_BOMBCHU),
RC(RC_TWIN_ISLANDS_UNDERWATER_CHEST_1, RCTYPE_CHEST, SCENE_17SETUGEN, FLAG_CYCL_SCENE_CHEST, 0x06, RI_HEART_PIECE),
diff --git a/mm/2s2h/Rando/StaticData/Regions.cpp b/mm/2s2h/Rando/StaticData/Regions.cpp
new file mode 100644
index 000000000..06980f921
--- /dev/null
+++ b/mm/2s2h/Rando/StaticData/Regions.cpp
@@ -0,0 +1,147 @@
+#include "StaticData.h"
+
+extern "C" {
+s32 Flags_GetRandoInf(s32 flag);
+}
+
+namespace Rando {
+
+namespace StaticData {
+
+#define IS_DEKU (GET_PLAYER_FORM == PLAYER_FORM_DEKU)
+#define IS_ZORA (GET_PLAYER_FORM == PLAYER_FORM_ZORA)
+#define IS_DEITY (GET_PLAYER_FORM == PLAYER_FORM_FIERCE_DEITY)
+#define IS_GORON (GET_PLAYER_FORM == PLAYER_FORM_GORON)
+#define IS_HUMAN (GET_PLAYER_FORM == PLAYER_FORM_HUMAN)
+#define HAS_ITEM(item) (INV_CONTENT(item) == item)
+#define CAN_BE_DEKU (IS_DEKU || HAS_ITEM(ITEM_MASK_DEKU))
+#define CAN_BE_ZORA (IS_ZORA || HAS_ITEM(ITEM_MASK_ZORA))
+#define CAN_BE_DIETY (IS_DEITY || HAS_ITEM(ITEM_MASK_FIERCE_DEITY))
+#define CAN_BE_GORON (IS_GORON || HAS_ITEM(ITEM_MASK_GORON))
+#define CAN_BE_HUMAN \
+ (IS_HUMAN || (IS_DEITY && HAS_ITEM(ITEM_MASK_FIERCE_DEITY)) || (IS_ZORA && HAS_ITEM(ITEM_MASK_ZORA)) || \
+ (IS_DEKU && HAS_ITEM(ITEM_MASK_DEKU)) || (IS_GORON && HAS_ITEM(ITEM_MASK_GORON)))
+#define HAS_MAGIC (gSaveContext.save.saveInfo.playerData.isMagicAcquired)
+#define CAN_USE_EXPLOSIVE (HAS_ITEM(ITEM_BOMB) || HAS_ITEM(ITEM_BOMBCHU) || (HAS_ITEM(ITEM_MASK_BLAST) && CAN_BE_HUMAN))
+
+// clang-format off
+std::map<RandoRegionId, RandoStaticRegion> Regions = {
+ { RR_CLOCK_TOWN_SOUTH, { RR_CLOCK_TOWN_SOUTH, "", SCENE_CLOCKTOWER,
+ { // Checks
+ { RC_CLOCK_TOWN_SCRUB_DEED, []() { return Flags_GetRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR); } },
+ { RC_CLOCK_TOWN_SOUTH_CHEST_UPPER, []() { return CAN_BE_DEKU && Flags_GetRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR); } },
+ { RC_CLOCK_TOWN_SOUTH_CHEST_LOWER, []() { return CAN_BE_DEKU && Flags_GetRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR) && (CAN_BE_HUMAN || CAN_BE_ZORA || CAN_BE_DIETY); } },
+ },
+ { // Regions
+ { RR_CLOCK_TOWN_SOUTH_PLATFORM, []() { return CAN_BE_HUMAN || CAN_BE_ZORA || CAN_BE_DIETY || (CAN_BE_DEKU && Flags_GetRandoInf(RANDO_INF_OBTAINED_MOONS_TEAR)); } },
+ { RR_CLOCK_TOWN_LAUNDRY, []() { return true; } },
+ { RR_CLOCK_TOWN_NORTH, []() { return true; } },
+ { RR_CLOCK_TOWN_EAST, []() { return true; } },
+ { RR_CLOCK_TOWER_INTERIOR, []() { return true; } },
+ { RR_CATCH_ALL, []() { return true; } }, // TODO: We'll remove this eventually
+ },
+ } },
+ { RR_CLOCK_TOWER_INTERIOR, { RR_CLOCK_TOWER_INTERIOR, "", SCENE_INSIDETOWER,
+ { // Checks
+ { RC_CLOCK_TOWER_INTERIOR_SONG_OF_HEALING, []() { return HAS_ITEM(ITEM_OCARINA_OF_TIME); } },
+ { RC_CLOCK_TOWER_INTERIOR_DEKU_MASK, []() { return HAS_ITEM(ITEM_OCARINA_OF_TIME); } },
+ },
+ { // Regions
+ { RR_CLOCK_TOWN_SOUTH, []() { return true; } },
+ },
+ } },
+ { RR_CLOCK_TOWN_SOUTH_PLATFORM, { RR_CLOCK_TOWN_SOUTH_PLATFORM, "Clock Tower Platform", SCENE_CLOCKTOWER,
+ { // Checks
+ { RC_CLOCK_TOWN_SOUTH_PLATFORM_HP, []() { return true; } },
+ },
+ { // Regions
+ { RR_CLOCK_TOWN_SOUTH, []() { return true; } },
+ { RR_CLOCK_TOWER_ROOF, []() { return true; } },
+ },
+ } },
+ { RR_CLOCK_TOWER_ROOF, { RR_CLOCK_TOWER_ROOF, "", SCENE_OKUJOU,
+ { // Checks
+ { RC_CLOCK_TOWER_ROOF_SONG_OF_TIME, []() { return HAS_MAGIC && CAN_BE_DEKU; } },
+ { RC_CLOCK_TOWER_ROOF_OCARINA, []() { return HAS_MAGIC && CAN_BE_DEKU; } },
+ },
+ { // Regions
+ // TODO: Moon?
+ },
+ } },
+ { RR_CLOCK_TOWN_LAUNDRY, { RR_CLOCK_TOWN_LAUNDRY, "", SCENE_ALLEY,
+ { // Checks
+ { RC_CLOCK_TOWN_STRAY_FAIRY, []() { return true; } },
+ { RC_CLOCK_TOWN_LAUNDRY_GURU_GURU, []() { return CAN_BE_DIETY || CAN_BE_HUMAN || CAN_BE_ZORA || CAN_BE_GORON; } },
+ },
+ { // Regions
+ { RR_CLOCK_TOWN_SOUTH, []() { return true; } },
+ },
+ } },
+ { RR_CLOCK_TOWN_NORTH, { RR_CLOCK_TOWN_NORTH, "", SCENE_BACKTOWN,
+ { // Checks
+ { RC_CLOCK_TOWN_NORTH_TREE_HP, []() { return CAN_BE_DIETY || CAN_BE_HUMAN || CAN_BE_ZORA; } },
+ { RC_CLOCK_TOWN_NORTH_BOMB_LADY, []() { return CAN_BE_DIETY || CAN_BE_HUMAN || CAN_BE_ZORA || CAN_BE_GORON; } },
+ },
+ { // Regions
+ { RR_CLOCK_TOWN_SOUTH, []() { return true; } },
+ { RR_CLOCK_TOWN_EAST, []() { return true; } },
+ { RR_CLOCK_TOWN_GREAT_FAIRY_FOUNTAIN, []() { return true; } },
+ },
+ } },
+ { RR_CLOCK_TOWN_GREAT_FAIRY_FOUNTAIN, { RR_CLOCK_TOWN_GREAT_FAIRY_FOUNTAIN, "Clock Town", SCENE_YOUSEI_IZUMI,
+ { // Checks
+ { RC_CLOCK_TOWN_GREAT_FAIRY, []() { return CHECK_WEEKEVENTREG(WEEKEVENTREG_08_80); } },
+ { RC_CLOCK_TOWN_GREAT_FAIRY_ALT, []() { return CHECK_WEEKEVENTREG(WEEKEVENTREG_08_80) && CAN_BE_HUMAN; } },
+ },
+ { // Regions
+ { RR_CLOCK_TOWN_NORTH, []() { return true; } },
+ },
+ } },
+ { RR_CLOCK_TOWN_EAST, { RR_CLOCK_TOWN_EAST, "", SCENE_TOWN,
+ { // Checks
+ { RC_CLOCK_TOWN_STRAY_FAIRY, []() { return CAN_BE_DEKU; } }, // Same check in two places, is this okay?
+ },
+ { // Regions
+ { RR_CLOCK_TOWN_NORTH, []() { return true; } },
+ { RR_ASTRAL_OBSERVATORY_PASSAGE, []() { return true; } }, // TODO: Bombers Code req
+ },
+ } },
+ { RR_ASTRAL_OBSERVATORY_PASSAGE, { RR_ASTRAL_OBSERVATORY_PASSAGE, "Passage", SCENE_TENMON_DAI,
+ { // Checks
+ { RC_ASTRAL_OBSERVATORY_PASSAGE_CHEST, []() { return (CAN_BE_HUMAN || CAN_BE_DIETY || CAN_BE_ZORA) && CAN_USE_EXPLOSIVE; } },
+ },
+ { // Regions
+ { RR_CLOCK_TOWN_EAST, []() { return true; } },
+ { RR_ASTRAL_OBSERVATORY, []() { return CAN_BE_DEKU && HAS_MAGIC; } },
+ },
+ } },
+ { RR_ASTRAL_OBSERVATORY, { RR_ASTRAL_OBSERVATORY, "", SCENE_TENMON_DAI,
+ { // Checks
+ },
+ { // Regions
+ { RR_ASTRAL_OBSERVATORY_PASSAGE, []() { return CAN_BE_DEKU && HAS_MAGIC; } }, // TODO: Trick to backflip over balloon
+ { RR_ASTRAL_OBSERVATORY_OUTSIDE, []() { return true; } },
+ },
+ } },
+ { RR_ASTRAL_OBSERVATORY_OUTSIDE, { RR_ASTRAL_OBSERVATORY_OUTSIDE, "Outside Astral Observatory", SCENE_00KEIKOKU,
+ { // Checks
+ { RC_ASTRAL_OBSERVATORY_MOON_TEAR, []() { return true; } },
+ },
+ { // Regions
+ { RR_ASTRAL_OBSERVATORY, []() { return true; } },
+ },
+ } },
+ // TODO: We'll remove this eventually
+ { RR_CATCH_ALL, { RR_CATCH_ALL, "", SCENE_MAX,
+ { // Checks
+ // { RC_CLOCK_TOWER_INTERIOR_DEKU_MASK, []() { return true; } },
+ },
+ { // Regions
+ },
+ } },
+};
+// clang-format on
+
+} // namespace StaticData
+
+} // namespace Rando
diff --git a/mm/2s2h/Rando/StaticData/StaticData.h b/mm/2s2h/Rando/StaticData/StaticData.h
index 8611847c4..720f56a2d 100644
--- a/mm/2s2h/Rando/StaticData/StaticData.h
+++ b/mm/2s2h/Rando/StaticData/StaticData.h
@@ -52,6 +52,16 @@ extern std::map<RandoOptionId, RandoStaticOption> Options;
RandoOptionId GetOptionIdFromName(const char* name);
+struct RandoStaticRegion {
+ RandoRegionId randoRegionId;
+ const char* name;
+ SceneId sceneId;
+ std::unordered_map<RandoCheckId, std::function<bool()>> checks;
+ std::unordered_map<RandoRegionId, std::function<bool()>> regions;
+};
+
+extern std::map<RandoRegionId, RandoStaticRegion> Regions;
+
} // namespace StaticData
} // namespace Rando
diff --git a/mm/2s2h/Rando/Types.h b/mm/2s2h/Rando/Types.h
index 6d64db90c..ee6d76dc1 100644
--- a/mm/2s2h/Rando/Types.h
+++ b/mm/2s2h/Rando/Types.h
@@ -27,17 +27,17 @@ typedef enum {
RC_BENEATH_THE_WELL_KEESE_CHEST,
RC_BENEATH_THE_WELL_MIRROR_SHIELD,
RC_BENEATH_THE_WELL_SKULLTULLA_CHEST,
+ RC_CLOCK_TOWER_INTERIOR_DEKU_MASK,
+ RC_CLOCK_TOWER_INTERIOR_SONG_OF_HEALING,
RC_CLOCK_TOWER_ROOF_OCARINA,
RC_CLOCK_TOWER_ROOF_SONG_OF_TIME,
- RC_CLOCK_TOWN_DEKU_MASK,
RC_CLOCK_TOWN_EAST_UPPER_CHEST,
RC_CLOCK_TOWN_GREAT_FAIRY_ALT,
RC_CLOCK_TOWN_GREAT_FAIRY,
RC_CLOCK_TOWN_LAUNDRY_GURU_GURU,
- RC_CLOCK_TOWN_NORTH_TREE_HP,
RC_CLOCK_TOWN_NORTH_BOMB_LADY,
+ RC_CLOCK_TOWN_NORTH_TREE_HP,
RC_CLOCK_TOWN_SCRUB_DEED,
- RC_CLOCK_TOWN_SONG_OF_HEALING,
RC_CLOCK_TOWN_SOUTH_CHEST_LOWER,
RC_CLOCK_TOWN_SOUTH_CHEST_UPPER,
RC_CLOCK_TOWN_SOUTH_PLATFORM_HP,
@@ -84,8 +84,8 @@ typedef enum {
RC_LONE_PEAK_SHRINE_BOULDER_CHEST,
RC_LONE_PEAK_SHRINE_INVISIBLE_CHEST,
RC_LONE_PEAK_SHRINE_LENS_CHEST,
- RC_MAYORS_OFFICE_KAFEIS_MASK,
RC_MAYORS_OFFICE_HP,
+ RC_MAYORS_OFFICE_KAFEIS_MASK,
RC_MOON_TRIAL_DEKU_HP,
RC_MOON_TRIAL_GORON_HP,
RC_MOON_TRIAL_LINK_GARO_MASTER_CHEST,
@@ -250,13 +250,13 @@ typedef enum {
RC_SWORDSMAN_SCHOOL_HP,
RC_TERMINA_FIELD_BIO_BABA_GROTTO,
RC_TERMINA_FIELD_DODONGO_GROTTO,
+ RC_TERMINA_FIELD_KAMARO,
RC_TERMINA_FIELD_PEAHAT_GROTTO,
RC_TERMINA_FIELD_PILLAR_GROTTO,
RC_TERMINA_FIELD_TALL_GRASS_CHEST,
RC_TERMINA_FIELD_TALL_GRASS_GROTTO,
RC_TERMINA_FIELD_TREE_STUMP_CHEST,
RC_TERMINA_FIELD_WATER_CHEST,
- RC_TERMINA_FIELD_KAMARO,
RC_TWIN_ISLANDS_FROZEN_GROTTO_CHEST,
RC_TWIN_ISLANDS_RAMP_GROTTO_CHEST,
RC_TWIN_ISLANDS_UNDERWATER_CHEST_1,
@@ -895,6 +895,7 @@ typedef enum {
RI_MAGIC_BEAN,
RI_MAGIC_JAR_BIG,
RI_MAGIC_JAR_SMALL,
+ RI_MASK_BLAST,
RI_MASK_BREMEN,
RI_MASK_BUNNY,
RI_MASK_CAPTAIN,
@@ -902,9 +903,8 @@ typedef enum {
RI_MASK_GIANT,
RI_MASK_GREAT_FAIRY,
RI_MASK_KAFEIS_MASK,
- RI_MASK_ROMANI,
- RI_MASK_BLAST,
RI_MASK_KAMARO,
+ RI_MASK_ROMANI,
RI_MILK_REFILL,
RI_MOONS_TEAR,
RI_NONE,
@@ -949,6 +949,22 @@ typedef enum {
} RandoItemId;
typedef enum {
+ RR_CLOCK_TOWN_SOUTH,
+ RR_CLOCK_TOWN_SOUTH_PLATFORM,
+ RR_CLOCK_TOWER_ROOF,
+ RR_CLOCK_TOWN_LAUNDRY,
+ RR_CLOCK_TOWN_NORTH,
+ RR_CLOCK_TOWN_GREAT_FAIRY_FOUNTAIN,
+ RR_CLOCK_TOWN_EAST,
+ RR_CLOCK_TOWER_INTERIOR,
+ RR_ASTRAL_OBSERVATORY,
+ RR_ASTRAL_OBSERVATORY_PASSAGE,
+ RR_ASTRAL_OBSERVATORY_OUTSIDE,
+ RR_CATCH_ALL,
+ RR_MAX,
+} RandoRegionId;
+
+typedef enum {
RO_LOGIC,
RO_SHUFFLE_GOLD_SKULLTULAS,
RO_SHUFFLE_POTS,
diff --git a/mm/2s2h/ShipUtils.cpp b/mm/2s2h/ShipUtils.cpp
index e605de725..8ab330921 100644
--- a/mm/2s2h/ShipUtils.cpp
+++ b/mm/2s2h/ShipUtils.cpp
@@ -10,6 +10,7 @@
extern "C" {
#include "macros.h"
+#include "z64.h"
extern f32 sNESFontWidths[160];
extern const char* fontTbl[156];
@@ -18,6 +19,27 @@ extern TexturePtr gQuestIcons[14];
extern TexturePtr gBombersNotebookPhotos[24];
}
+// 2S2H Added columns to scene table: entranceSceneId, betterMapSelectIndex, humanName
+#define DEFINE_SCENE(_name, enumValue, _textId, _drawConfig, _restrictionFlags, _persistentCycleFlags, \
+ _entranceSceneId, _betterMapSelectIndex, humanName) \
+ { enumValue, humanName },
+#define DEFINE_SCENE_UNSET(_enumValue)
+
+std::unordered_map<s16, const char*> sceneNames = {
+#include "tables/scene_table.h"
+};
+
+#undef DEFINE_SCENE
+#undef DEFINE_SCENE_UNSET
+
+extern "C" const char* Ship_GetSceneName(s16 sceneId) {
+ if (sceneNames.contains(sceneId)) {
+ return sceneNames[sceneId];
+ }
+
+ return "Unknown";
+}
+
// Build vertex coordinates for a quad command
// In order of top left, top right, bottom left, then bottom right
// Supports flipping the texture horizontally
diff --git a/mm/2s2h/ShipUtils.h b/mm/2s2h/ShipUtils.h
index 61582dffe..b2ffe54ab 100644
--- a/mm/2s2h/ShipUtils.h
+++ b/mm/2s2h/ShipUtils.h
@@ -11,6 +11,7 @@ void LoadGuiTextures();
extern "C" {
#endif
+const char* Ship_GetSceneName(s16 sceneId);
void Ship_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH);
f32 Ship_GetCharFontWidthNES(u8 character);
TexturePtr Ship_GetCharFontTextureNES(u8 character);
diff --git a/mm/include/functions.h b/mm/include/functions.h
index 8ce6ede41..358eb15e6 100644
--- a/mm/include/functions.h
+++ b/mm/include/functions.h
@@ -1346,6 +1346,7 @@ void Flags_ClearEventInf(s32 flag);
// #region 2S2H [Rando]
s32 Flags_GetRandoInf(s32 flag);
void Flags_SetRandoInf(s32 flag);
+void Flags_ClearRandoInf(s32 flag);
// #endregion
// #region 2S2H [Port] Stubbed methods
void osSetThreadPri(OSThread* thread, OSPri p);
diff --git a/mm/src/code/z_actor.c b/mm/src/code/z_actor.c
index 7e1bfb38e..7e7281822 100644
--- a/mm/src/code/z_actor.c
+++ b/mm/src/code/z_actor.c
@@ -927,6 +927,14 @@ void Flags_SetRandoInf(s32 flag) {
GameInteractor_ExecuteOnFlagSet(FLAG_RANDO_INF, flag);
}
}
+
+void Flags_ClearRandoInf(s32 flag) {
+ u8 previouslyOn = Flags_GetRandoInf(flag);
+ gSaveContext.save.shipSaveInfo.rando.randoInf[(flag) >> 4] &= (u8) ~(1 << ((flag)&0xF));
+ if (previouslyOn) {
+ GameInteractor_ExecuteOnFlagUnset(FLAG_RANDO_INF, flag);
+ }
+}
// #endregion
/* End of Flags section */
diff --git a/mm/src/code/z_parameter.c b/mm/src/code/z_parameter.c
index 13c5e2901..dc6a46866 100644
--- a/mm/src/code/z_parameter.c
+++ b/mm/src/code/z_parameter.c
@@ -3551,7 +3551,6 @@ s16 sRupeeRefillCounts[] = { 1, 5, 10, 20, 50, 100, 200 };
// 2S2H [Enhancements] This was originally Item_Give, we wrapped it for hooking purposes
u8 Item_GiveImpl(PlayState* play, u8 item) {
- Player* player = GET_PLAYER(play);
u8 i;
u8 temp;
u8 slot;
@@ -3605,7 +3604,11 @@ u8 Item_GiveImpl(PlayState* play, u8 item) {
} else if ((item >= ITEM_SHIELD_HERO) && (item <= ITEM_SHIELD_MIRROR)) {
if (GET_CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) != (u16)(item - ITEM_SHIELD_HERO + EQUIP_VALUE_SHIELD_HERO)) {
SET_EQUIP_VALUE(EQUIP_TYPE_SHIELD, item - ITEM_SHIELD_HERO + EQUIP_VALUE_SHIELD_HERO);
- Player_SetEquipmentData(play, player);
+ // 2S2H [Randomizer] Added a nullptr check so that we can call this function outside of gameplay for logic
+ if (gPlayState != NULL) {
+ Player* player = GET_PLAYER(play);
+ Player_SetEquipmentData(play, player);
+ }
return ITEM_NONE;
}
return item;