summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPepper0ni <93387759+Pepper0ni@users.noreply.github.com>2026-06-22 06:20:33 +0100
committerGitHub <noreply@github.com>2026-06-22 05:20:33 +0000
commitec2a3d7aa2c2b7e39be9e83279ebc4eda078d98f (patch)
treea0528aa8f84b6cf83638a8801ad265f7743ac574
parentcb215ede878fa8c135a3f2bcf202f59e8893957e (diff)
Fix numerous Small Key Tracking bugs, excess items now more reliably turn into Blupees (#6750)
m---------libultraship0
-rw-r--r--soh/include/macros.h10
-rw-r--r--soh/soh/Enhancements/gameplaystats.cpp29
-rw-r--r--soh/soh/Enhancements/randomizer/Traps.cpp2
-rw-r--r--soh/soh/Enhancements/randomizer/dungeon.cpp133
-rw-r--r--soh/soh/Enhancements/randomizer/dungeon.h25
-rw-r--r--soh/soh/Enhancements/randomizer/location_access.cpp2
-rw-r--r--soh/soh/Enhancements/randomizer/logic.cpp151
-rw-r--r--soh/soh/Enhancements/randomizer/logic.h10
-rw-r--r--soh/soh/Enhancements/randomizer/randomizer.cpp114
-rw-r--r--soh/soh/Enhancements/randomizer/randomizer.h19
-rw-r--r--soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp93
-rw-r--r--soh/soh/Enhancements/randomizer/savefile.cpp6
-rw-r--r--soh/soh/Enhancements/randomizer/static_data.h1
14 files changed, 306 insertions, 289 deletions
diff --git a/libultraship b/libultraship
-Subproject 2c63e7edef577cd2a3726ce24dfedd7f088f12a
+Subproject 1d9829140444268827a1369095bcc93518ff3b4
diff --git a/soh/include/macros.h b/soh/include/macros.h
index dd51786f1..eb377ffa0 100644
--- a/soh/include/macros.h
+++ b/soh/include/macros.h
@@ -319,16 +319,6 @@ extern GraphicsContext* __gfxCtx;
#define NUM_TRIALS 6
#define NUM_SHOP_ITEMS 64
#define NUM_SCRUBS 46
-#define FOREST_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_FOREST_TEMPLE) ? 6 : 5)
-#define FIRE_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_FIRE_TEMPLE) ? 5 : 8)
-#define WATER_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_WATER_TEMPLE) ? 2 : 6)
-#define SPIRIT_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_SPIRIT_TEMPLE) ? 7 : 5)
-#define SHADOW_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_SHADOW_TEMPLE) ? 6 : 5)
-#define BOTTOM_OF_THE_WELL_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_BOTTOM_OF_THE_WELL) ? 2 : 3)
-#define GERUDO_TRAINING_GROUND_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_GERUDO_TRAINING_GROUND) ? 3 : 9)
-#define GERUDO_FORTRESS_SMALL_KEY_MAX 4
-#define GANONS_CASTLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_INSIDE_GANONS_CASTLE) ? 3 : 2)
-#define TREASURE_GAME_SMALL_KEY_MAX 6
#ifdef __cplusplus
#define DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(rsk) \
diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp
index 492d37611..7475bd942 100644
--- a/soh/soh/Enhancements/gameplaystats.cpp
+++ b/soh/soh/Enhancements/gameplaystats.cpp
@@ -690,10 +690,31 @@ void GameplayStatsWindow::DrawElement() {
ImGui::Text("Note: Gameplay stats are saved to the current file and will be\nlost if you quit without saving.");
}
void InitStats(bool isDebug) {
- gSaveContext.ship.stats.heartPieces = isDebug ? 8 : 0;
- gSaveContext.ship.stats.heartContainers = isDebug ? 8 : 0;
- for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.ship.stats.dungeonKeys); dungeon++) {
- gSaveContext.ship.stats.dungeonKeys[dungeon] = isDebug ? 8 : 0;
+ int debugFile = isDebug ? CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugSaveFileMode"), 1) : 0;
+ switch (debugFile) {
+ case 1:
+ gSaveContext.ship.stats.heartPieces = 8;
+ gSaveContext.ship.stats.heartContainers = 8;
+ for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.ship.stats.dungeonKeys); dungeon++) {
+ gSaveContext.ship.stats.dungeonKeys[dungeon] = 8;
+ }
+ break;
+ case 2:
+ gSaveContext.ship.stats.heartPieces = 36;
+ gSaveContext.ship.stats.heartContainers = 8;
+ for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.ship.stats.dungeonKeys); dungeon++) {
+ // 9 for maxed, 0 for none
+ gSaveContext.ship.stats.dungeonKeys[dungeon] = 9;
+ }
+ break;
+ case 0:
+ default:
+ gSaveContext.ship.stats.heartPieces = 0;
+ gSaveContext.ship.stats.heartContainers = 0;
+ for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.ship.stats.dungeonKeys); dungeon++) {
+ gSaveContext.ship.stats.dungeonKeys[dungeon] = 0;
+ }
+ break;
}
gSaveContext.ship.stats.rtaTiming = CVarGetInteger(CVAR_GAMEPLAY_STATS("RTATiming"), 0);
gSaveContext.ship.stats.fileCreatedAt = GetUnixTimestamp();
diff --git a/soh/soh/Enhancements/randomizer/Traps.cpp b/soh/soh/Enhancements/randomizer/Traps.cpp
index 8c55d1031..3e0673503 100644
--- a/soh/soh/Enhancements/randomizer/Traps.cpp
+++ b/soh/soh/Enhancements/randomizer/Traps.cpp
@@ -1056,7 +1056,7 @@ static void InitTrickNames() {
trickNameTable[RG_SPEAK_HYLIAN] = {
// TODO_TRANSLATE
Text{ "Human Jingle Nut" },
- Text{ "Sheikah Jabber nut" },
+ Text{ "Sheikah Jabber Nut" },
Text{ "Lorulean Blabber Nut" },
};
trickNameTable[RG_SPEAK_KOKIRI] = {
diff --git a/soh/soh/Enhancements/randomizer/dungeon.cpp b/soh/soh/Enhancements/randomizer/dungeon.cpp
index 6cb077c63..7a9287aff 100644
--- a/soh/soh/Enhancements/randomizer/dungeon.cpp
+++ b/soh/soh/Enhancements/randomizer/dungeon.cpp
@@ -5,14 +5,20 @@
#include "SeedContext.h"
namespace Rando {
+extern "C" PlayState* gPlayState;
+extern "C" SaveContext gSaveContext;
+
DungeonInfo::DungeonInfo(std::string name_, const RandomizerHintTextKey hintKey_, const RandomizerGet map_,
const RandomizerGet compass_, const RandomizerGet smallKey_, const RandomizerGet keyRing_,
const RandomizerGet bossKey_, RandomizerGet reward_, RandomizerArea area_,
const uint8_t vanillaKeyCount_, const uint8_t mqKeyCount_,
- const RandomizerSettingKey mqSetting_)
+ const RandomizerSettingKey mqSetting_, const SceneID scene_,
+ const std::vector<uint8_t> vanillaDoorFlags_, const std::vector<uint8_t> randoDoorFlags_,
+ const std::vector<uint8_t> MQDoorFlags_)
: name(std::move(name_)), hintKey(hintKey_), map(map_), compass(compass_), smallKey(smallKey_), keyRing(keyRing_),
bossKey(bossKey_), reward(reward_), area(area_), vanillaKeyCount(vanillaKeyCount_), mqKeyCount(mqKeyCount_),
- mqSetting(mqSetting_) {
+ mqSetting(mqSetting_), scene(scene_), vanillaDoorFlags(vanillaDoorFlags_), randoDoorFlags(randoDoorFlags_),
+ MQDoorFlags(MQDoorFlags_) {
}
DungeonInfo::DungeonInfo()
: hintKey(RHT_NONE), map(RG_NONE), compass(RG_NONE), smallKey(RG_NONE), keyRing(RG_NONE), bossKey(RG_NONE),
@@ -92,6 +98,59 @@ RandomizerSettingKey DungeonInfo::GetMQSetting() const {
return mqSetting;
}
+int8_t FindUsedSmallKeys(const SaveContext* saveContext, const SceneID scene, const std::vector<uint8_t>* DoorFlags) {
+ // Get the swch value for the scene
+ uint32_t swch;
+ if (gPlayState != nullptr && gPlayState->sceneNum == scene) {
+ swch = gPlayState->actorCtx.flags.swch;
+ } else {
+ swch = saveContext->sceneFlags[scene].swch;
+ }
+
+ // Count the number of small keys doors unlocked
+ int8_t unlockedSmallKeyDoors = 0;
+ for (auto& smallKeyDoor : *DoorFlags) {
+ unlockedSmallKeyDoors += swch >> smallKeyDoor & 1;
+ }
+ return unlockedSmallKeyDoors;
+}
+
+int8_t FindCurrentSmallKeys(const SaveContext* saveContext, const SceneID scene) {
+ int8_t dungeonKeys = saveContext->inventory.dungeonKeys[scene];
+ if (dungeonKeys == -1) {
+ // never got keys, so can't have used keys
+ return 0;
+ }
+ return dungeonKeys;
+}
+
+int8_t FindTotalSmallKeys(const SaveContext* saveContext, const SceneID scene, const std::vector<uint8_t>* DoorFlags) {
+ return FindCurrentSmallKeys(saveContext, scene) + FindUsedSmallKeys(saveContext, scene, DoorFlags);
+}
+
+int8_t DungeonInfo::GetUsedSmallKeys(SaveContext* saveContext) const {
+ return FindUsedSmallKeys(saveContext, scene, GetDoorFlags());
+}
+
+int8_t DungeonInfo::GetCurrentSmallKeys(SaveContext* saveContext) const {
+ return FindCurrentSmallKeys(saveContext, scene);
+}
+
+int8_t DungeonInfo::GetTotalSmallKeys(SaveContext* saveContext) const {
+ return FindTotalSmallKeys(saveContext, scene, GetDoorFlags());
+}
+
+const std::vector<uint8_t>* DungeonInfo::GetDoorFlags() const {
+ if (IsMQ()) {
+ return &MQDoorFlags;
+ }
+ if (IS_RANDO) {
+ // Specifically non-MQ Rando, to handle an edge case in water temple
+ return &randoDoorFlags;
+ }
+ return &vanillaDoorFlags;
+}
+
void DungeonInfo::SetDungeonKnown(bool known) {
isDungeonModeKnown = known;
}
@@ -152,45 +211,53 @@ std::vector<RandomizerCheck> DungeonInfo::GetDungeonLocations() const {
}
Dungeons::Dungeons() {
- dungeonList[DEKU_TREE] = DungeonInfo("Deku Tree", RHT_DEKU_TREE, RG_DEKU_TREE_MAP, RG_DEKU_TREE_COMPASS, RG_NONE,
- RG_NONE, RG_NONE, RG_KOKIRI_EMERALD, RA_DEKU_TREE, 0, 0, RSK_MQ_DEKU_TREE);
- dungeonList[DODONGOS_CAVERN] =
- DungeonInfo("Dodongo's Cavern", RHT_DODONGOS_CAVERN, RG_DODONGOS_CAVERN_MAP, RG_DODONGOS_CAVERN_COMPASS,
- RG_NONE, RG_NONE, RG_NONE, RG_GORON_RUBY, RA_DODONGOS_CAVERN, 0, 0, RSK_MQ_DODONGOS_CAVERN);
- dungeonList[JABU_JABUS_BELLY] =
- DungeonInfo("Jabu Jabu's Belly", RHT_JABU_JABUS_BELLY, RG_JABU_JABUS_BELLY_MAP, RG_JABU_JABUS_BELLY_COMPASS,
- RG_NONE, RG_NONE, RG_NONE, RG_ZORA_SAPPHIRE, RA_JABU_JABUS_BELLY, 0, 0, RSK_MQ_JABU_JABU);
- dungeonList[FOREST_TEMPLE] =
- DungeonInfo("Forest Temple", RHT_FOREST_TEMPLE, RG_FOREST_TEMPLE_MAP, RG_FOREST_TEMPLE_COMPASS,
- RG_FOREST_TEMPLE_SMALL_KEY, RG_FOREST_TEMPLE_KEY_RING, RG_FOREST_TEMPLE_BOSS_KEY,
- RG_FOREST_MEDALLION, RA_FOREST_TEMPLE, 5, 6, RSK_MQ_FOREST_TEMPLE);
- dungeonList[FIRE_TEMPLE] = DungeonInfo("Fire Temple", RHT_FIRE_TEMPLE, RG_FIRE_TEMPLE_MAP, RG_FIRE_TEMPLE_COMPASS,
- RG_FIRE_TEMPLE_SMALL_KEY, RG_FIRE_TEMPLE_KEY_RING, RG_FIRE_TEMPLE_BOSS_KEY,
- RG_FIRE_MEDALLION, RA_FIRE_TEMPLE, 8, 5, RSK_MQ_FIRE_TEMPLE);
- dungeonList[WATER_TEMPLE] =
- DungeonInfo("Water Temple", RHT_WATER_TEMPLE, RG_WATER_TEMPLE_MAP, RG_WATER_TEMPLE_COMPASS,
- RG_WATER_TEMPLE_SMALL_KEY, RG_WATER_TEMPLE_KEY_RING, RG_WATER_TEMPLE_BOSS_KEY, RG_WATER_MEDALLION,
- RA_WATER_TEMPLE, 6, 2, RSK_MQ_WATER_TEMPLE);
+ dungeonList[DEKU_TREE] =
+ DungeonInfo("Deku Tree", RHT_DEKU_TREE, RG_DEKU_TREE_MAP, RG_DEKU_TREE_COMPASS, RG_NONE, RG_NONE, RG_NONE,
+ RG_KOKIRI_EMERALD, RA_DEKU_TREE, 0, 0, RSK_MQ_DEKU_TREE, SCENE_DEKU_TREE, {}, {}, {});
+ dungeonList[DODONGOS_CAVERN] = DungeonInfo(
+ "Dodongo's Cavern", RHT_DODONGOS_CAVERN, RG_DODONGOS_CAVERN_MAP, RG_DODONGOS_CAVERN_COMPASS, RG_NONE, RG_NONE,
+ RG_NONE, RG_GORON_RUBY, RA_DODONGOS_CAVERN, 0, 0, RSK_MQ_DODONGOS_CAVERN, SCENE_DODONGOS_CAVERN, {}, {}, {});
+ dungeonList[JABU_JABUS_BELLY] = DungeonInfo(
+ "Jabu Jabu's Belly", RHT_JABU_JABUS_BELLY, RG_JABU_JABUS_BELLY_MAP, RG_JABU_JABUS_BELLY_COMPASS, RG_NONE,
+ RG_NONE, RG_NONE, RG_ZORA_SAPPHIRE, RA_JABU_JABUS_BELLY, 0, 0, RSK_MQ_JABU_JABU, SCENE_JABU_JABU, {}, {}, {});
+ dungeonList[FOREST_TEMPLE] = DungeonInfo(
+ "Forest Temple", RHT_FOREST_TEMPLE, RG_FOREST_TEMPLE_MAP, RG_FOREST_TEMPLE_COMPASS, RG_FOREST_TEMPLE_SMALL_KEY,
+ RG_FOREST_TEMPLE_KEY_RING, RG_FOREST_TEMPLE_BOSS_KEY, RG_FOREST_MEDALLION, RA_FOREST_TEMPLE, 5, 6,
+ RSK_MQ_FOREST_TEMPLE, SCENE_FOREST_TEMPLE, { 0, 1, 2, 3, 4 }, { 0, 1, 2, 3, 4 }, { 0, 1, 2, 3, 4, 6 });
+ dungeonList[FIRE_TEMPLE] =
+ DungeonInfo("Fire Temple", RHT_FIRE_TEMPLE, RG_FIRE_TEMPLE_MAP, RG_FIRE_TEMPLE_COMPASS,
+ RG_FIRE_TEMPLE_SMALL_KEY, RG_FIRE_TEMPLE_KEY_RING, RG_FIRE_TEMPLE_BOSS_KEY, RG_FIRE_MEDALLION,
+ RA_FIRE_TEMPLE, 8, 5, RSK_MQ_FIRE_TEMPLE, SCENE_FIRE_TEMPLE, { 23, 24, 25, 26, 27, 29, 30, 31 },
+ { 23, 24, 25, 26, 27, 29, 30, 31 }, { 23, 24, 26, 27, 30 });
+ dungeonList[WATER_TEMPLE] = DungeonInfo(
+ "Water Temple", RHT_WATER_TEMPLE, RG_WATER_TEMPLE_MAP, RG_WATER_TEMPLE_COMPASS, RG_WATER_TEMPLE_SMALL_KEY,
+ RG_WATER_TEMPLE_KEY_RING, RG_WATER_TEMPLE_BOSS_KEY, RG_WATER_MEDALLION, RA_WATER_TEMPLE, 6, 2,
+ RSK_MQ_WATER_TEMPLE, SCENE_WATER_TEMPLE, { 1, 2, 5, 6, 9, 21 }, { 1, 2, 5, 6, 9 }, { 4, 21 });
dungeonList[SPIRIT_TEMPLE] =
DungeonInfo("Spirit Temple", RHT_SPIRIT_TEMPLE, RG_SPIRIT_TEMPLE_MAP, RG_SPIRIT_TEMPLE_COMPASS,
RG_SPIRIT_TEMPLE_SMALL_KEY, RG_SPIRIT_TEMPLE_KEY_RING, RG_SPIRIT_TEMPLE_BOSS_KEY,
- RG_SPIRIT_MEDALLION, RA_SPIRIT_TEMPLE, 5, 7, RSK_MQ_SPIRIT_TEMPLE);
+ RG_SPIRIT_MEDALLION, RA_SPIRIT_TEMPLE, 5, 7, RSK_MQ_SPIRIT_TEMPLE, SCENE_SPIRIT_TEMPLE,
+ { 13, 21, 27, 28, 30 }, { 13, 21, 27, 28, 30 }, { 1, 3, 18, 21, 27, 28, 30 });
dungeonList[SHADOW_TEMPLE] =
DungeonInfo("Shadow Temple", RHT_SHADOW_TEMPLE, RG_SHADOW_TEMPLE_MAP, RG_SHADOW_TEMPLE_COMPASS,
RG_SHADOW_TEMPLE_SMALL_KEY, RG_SHADOW_TEMPLE_KEY_RING, RG_SHADOW_TEMPLE_BOSS_KEY,
- RG_SHADOW_MEDALLION, RA_SHADOW_TEMPLE, 5, 6, RSK_MQ_SHADOW_TEMPLE);
- dungeonList[BOTTOM_OF_THE_WELL] =
- DungeonInfo("Bottom of the Well", RHT_BOTTOM_OF_THE_WELL, RG_BOTTOM_OF_THE_WELL_MAP,
- RG_BOTTOM_OF_THE_WELL_COMPASS, RG_BOTTOM_OF_THE_WELL_SMALL_KEY, RG_BOTTOM_OF_THE_WELL_KEY_RING,
- RG_NONE, RG_NONE, RA_BOTTOM_OF_THE_WELL, 3, 2, RSK_MQ_BOTTOM_OF_THE_WELL);
- dungeonList[ICE_CAVERN] = DungeonInfo("Ice Cavern", RHT_ICE_CAVERN, RG_ICE_CAVERN_MAP, RG_ICE_CAVERN_COMPASS,
- RG_NONE, RG_NONE, RG_NONE, RG_NONE, RA_ICE_CAVERN, 0, 0, RSK_MQ_ICE_CAVERN);
+ RG_SHADOW_MEDALLION, RA_SHADOW_TEMPLE, 5, 6, RSK_MQ_SHADOW_TEMPLE, SCENE_SHADOW_TEMPLE,
+ { 21, 22, 23, 24, 25 }, { 21, 22, 23, 24, 25 }, { 21, 22, 23, 24, 25, 27 });
+ dungeonList[BOTTOM_OF_THE_WELL] = DungeonInfo(
+ "Bottom of the Well", RHT_BOTTOM_OF_THE_WELL, RG_BOTTOM_OF_THE_WELL_MAP, RG_BOTTOM_OF_THE_WELL_COMPASS,
+ RG_BOTTOM_OF_THE_WELL_SMALL_KEY, RG_BOTTOM_OF_THE_WELL_KEY_RING, RG_NONE, RG_NONE, RA_BOTTOM_OF_THE_WELL, 3, 2,
+ RSK_MQ_BOTTOM_OF_THE_WELL, SCENE_BOTTOM_OF_THE_WELL, { 27, 28, 29 }, { 27, 28, 29 }, { 20, 21 });
+ dungeonList[ICE_CAVERN] =
+ DungeonInfo("Ice Cavern", RHT_ICE_CAVERN, RG_ICE_CAVERN_MAP, RG_ICE_CAVERN_COMPASS, RG_NONE, RG_NONE, RG_NONE,
+ RG_NONE, RA_ICE_CAVERN, 0, 0, RSK_MQ_ICE_CAVERN, SCENE_ICE_CAVERN, {}, {}, {});
dungeonList[GERUDO_TRAINING_GROUND] = DungeonInfo(
"Gerudo Training Ground", RHT_GERUDO_TRAINING_GROUND, RG_NONE, RG_NONE, RG_GERUDO_TRAINING_GROUND_SMALL_KEY,
- RG_GERUDO_TRAINING_GROUND_KEY_RING, RG_NONE, RG_NONE, RA_GERUDO_TRAINING_GROUND, 9, 3, RSK_MQ_GTG);
- dungeonList[GANONS_CASTLE] = DungeonInfo(
- "Ganon's Castle", RHT_GANONS_CASTLE, RG_NONE, RG_NONE, RG_GANONS_CASTLE_SMALL_KEY, RG_GANONS_CASTLE_KEY_RING,
- RG_GANONS_CASTLE_BOSS_KEY, RG_NONE, RA_GANONS_CASTLE, 2, 3, RSK_MQ_GANONS_CASTLE);
+ RG_GERUDO_TRAINING_GROUND_KEY_RING, RG_NONE, RG_NONE, RA_GERUDO_TRAINING_GROUND, 9, 3, RSK_MQ_GTG,
+ SCENE_GERUDO_TRAINING_GROUND, { 1, 3, 4, 5, 6, 7, 9, 10, 23 }, { 1, 3, 4, 5, 6, 7, 9, 10, 23 }, { 20, 23, 29 });
+ dungeonList[GANONS_CASTLE] =
+ DungeonInfo("Ganon's Castle", RHT_GANONS_CASTLE, RG_NONE, RG_NONE, RG_GANONS_CASTLE_SMALL_KEY,
+ RG_GANONS_CASTLE_KEY_RING, RG_GANONS_CASTLE_BOSS_KEY, RG_NONE, RA_GANONS_CASTLE, 2, 3,
+ RSK_MQ_GANONS_CASTLE, SCENE_INSIDE_GANONS_CASTLE, { 29, 30 }, { 29, 30 }, { 20, 21, 22 });
}
Dungeons::~Dungeons() = default;
diff --git a/soh/soh/Enhancements/randomizer/dungeon.h b/soh/soh/Enhancements/randomizer/dungeon.h
index 8685f913e..14613f7c6 100644
--- a/soh/soh/Enhancements/randomizer/dungeon.h
+++ b/soh/soh/Enhancements/randomizer/dungeon.h
@@ -6,13 +6,17 @@
#include <vector>
#include <string>
#include "nlohmann/json.hpp"
+#include "z64save.h"
+#include "z64scene.h"
namespace Rando {
class DungeonInfo {
public:
DungeonInfo(std::string name_, RandomizerHintTextKey hintKey_, RandomizerGet map_, RandomizerGet compass_,
RandomizerGet smallKey_, RandomizerGet keyRing_, RandomizerGet bossKey_, RandomizerGet reward_,
- RandomizerArea area_, uint8_t vanillaKeyCount_, uint8_t mqKeyCount_, RandomizerSettingKey mqSetting_);
+ RandomizerArea area_, uint8_t vanillaKeyCount_, uint8_t mqKeyCount_, RandomizerSettingKey mqSetting_,
+ SceneID scene_, std::vector<uint8_t> vanillaDoorFlags_, std::vector<uint8_t> randoDoorFlags_,
+ std::vector<uint8_t> MQDoorFlags_);
DungeonInfo();
~DungeonInfo();
@@ -33,7 +37,11 @@ class DungeonInfo {
RandomizerGet GetCompass() const;
RandomizerGet GetBossKey() const;
RandomizerGet GetReward() const;
+ int8_t GetUsedSmallKeys(SaveContext* saveContext) const;
+ int8_t GetCurrentSmallKeys(SaveContext* saveContext) const;
+ int8_t GetTotalSmallKeys(SaveContext* saveContext) const;
RandomizerSettingKey GetMQSetting() const;
+ const std::vector<uint8_t>* GetDoorFlags() const;
void SetDungeonKnown(bool known);
void PlaceVanillaMap() const;
void PlaceVanillaCompass() const;
@@ -45,21 +53,30 @@ class DungeonInfo {
private:
std::string name;
RandomizerHintTextKey hintKey;
- RandomizerArea area;
RandomizerGet map;
RandomizerGet compass;
RandomizerGet smallKey;
RandomizerGet keyRing;
RandomizerGet bossKey;
RandomizerGet reward;
- RandomizerSettingKey mqSetting;
- bool isDungeonModeKnown = true;
+ RandomizerArea area;
uint8_t vanillaKeyCount{};
uint8_t mqKeyCount{};
+ RandomizerSettingKey mqSetting;
+ bool isDungeonModeKnown = true;
bool masterQuest = false;
bool hasKeyRing = false;
+ SceneID scene;
+ std::vector<uint8_t> vanillaDoorFlags;
+ // Specifically non-MQ Rando, to handle an edge case in water temple
+ std::vector<uint8_t> randoDoorFlags;
+ std::vector<uint8_t> MQDoorFlags;
};
+int8_t FindUsedSmallKeys(const SaveContext* saveContext, const SceneID scene, const std::vector<uint8_t>* DoorFlags);
+int8_t FindCurrentSmallKeys(const SaveContext* saveContext, const SceneID scene);
+int8_t FindTotalSmallKeys(const SaveContext* saveContext, const SceneID scene, const std::vector<uint8_t>* DoorFlags);
+
typedef enum {
DEKU_TREE,
DODONGOS_CAVERN,
diff --git a/soh/soh/Enhancements/randomizer/location_access.cpp b/soh/soh/Enhancements/randomizer/location_access.cpp
index 7e1750fa3..694af768a 100644
--- a/soh/soh/Enhancements/randomizer/location_access.cpp
+++ b/soh/soh/Enhancements/randomizer/location_access.cpp
@@ -873,7 +873,7 @@ bool BeanPlanted(const RandomizerGet bean) {
}
// swchFlag found using the Actor Viewer to get the Obj_Bean parameters & 0x3F
- // not tested with multiple OTRs, but can be automated similarly to GetDungeonSmallKeyDoors
+ // not tested with multiple OTRs, but can be automated similarly to GetUsedSmallKeys
SceneID sceneID;
uint8_t swchFlag;
switch (bean) {
diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp
index 9621fcdf3..1cd8887fa 100644
--- a/soh/soh/Enhancements/randomizer/logic.cpp
+++ b/soh/soh/Enhancements/randomizer/logic.cpp
@@ -9,6 +9,7 @@
#include "SeedContext.h"
#include "macros.h"
#include "variables.h"
+#include "randomizer.h"
#include <spdlog/spdlog.h>
#include <ship/utils/StringHelper.h>
#include "soh/resource/type/Scene.h"
@@ -80,8 +81,6 @@ bool Logic::HasItem(RandomizerGet itemName) {
return CheckEquipment(RandoGetToEquipFlag.at(itemName)) || Get(LOGIC_MEDIGORON);
case RG_BIGGORON_SWORD:
return CheckEquipment(RandoGetToEquipFlag.at(itemName)) && mSaveContext->bgsFlag;
- case RG_POWER_BRACELET:
- return CheckRandoInf(RAND_INF_CAN_GRAB);
case RG_GORONS_BRACELET:
return CurrentUpgrade(UPG_STRENGTH);
case RG_SILVER_GAUNTLETS:
@@ -147,10 +146,27 @@ bool Logic::HasItem(RandomizerGet itemName) {
assert(false);
return false;
}
+ case RG_POWER_BRACELET:
+ case RG_CHILD_WALLET:
case RG_FISHING_POLE:
+ case RG_BRONZE_SCALE:
+ case RG_CLIMB:
+ case RG_CRAWL:
+ case RG_OPEN_CHEST:
case RG_ZELDAS_LETTER:
case RG_WEIRD_EGG:
case RG_GREG_RUPEE:
+ // Adult Trade
+ case RG_COJIRO:
+ case RG_ODD_MUSHROOM:
+ case RG_ODD_POTION:
+ case RG_POACHERS_SAW:
+ case RG_BROKEN_SWORD:
+ case RG_PRESCRIPTION:
+ case RG_EYEBALL_FROG:
+ case RG_EYEDROPS:
+ case RG_CLAIM_CHECK:
+ // Jabber Nuts
case RG_SPEAK_DEKU:
case RG_SPEAK_GERUDO:
case RG_SPEAK_GORON:
@@ -211,7 +227,7 @@ bool Logic::HasItem(RandomizerGet itemName) {
case RG_HYLIA_LAB_KEY:
case RG_FISHING_HOLE_KEY:
case RG_RUTOS_LETTER:
- return CheckRandoInf(RandoGetToRandInf.at(itemName));
+ return CheckRandoInf(StaticData::RandoGetToRandInf.at(itemName));
// Boss Keys
case RG_FOREST_TEMPLE_BOSS_KEY:
case RG_FIRE_TEMPLE_BOSS_KEY:
@@ -245,8 +261,6 @@ bool Logic::HasItem(RandomizerGet itemName) {
case RG_ICE_CAVERN_COMPASS:
return CheckDungeonItem(DUNGEON_COMPASS, RandoGetToDungeonScene.at(itemName));
// Wallets
- case RG_CHILD_WALLET:
- return CheckRandoInf(RAND_INF_HAS_WALLET);
case RG_ADULT_WALLET:
return CurrentUpgrade(UPG_WALLET) >= 1;
case RG_GIANT_WALLET:
@@ -254,31 +268,13 @@ bool Logic::HasItem(RandomizerGet itemName) {
case RG_TYCOON_WALLET:
return CurrentUpgrade(UPG_WALLET) >= 3;
// Scales
- case RG_BRONZE_SCALE:
- return CheckRandoInf(RAND_INF_CAN_SWIM);
case RG_SILVER_SCALE:
return CurrentUpgrade(UPG_SCALE) >= 1;
case RG_GOLDEN_SCALE:
return CurrentUpgrade(UPG_SCALE) >= 2;
- case RG_CLIMB:
- return CheckRandoInf(RAND_INF_CAN_CLIMB);
- case RG_CRAWL:
- return CheckRandoInf(RAND_INF_CAN_CRAWL);
- case RG_OPEN_CHEST:
- return CheckRandoInf(RAND_INF_CAN_OPEN_CHEST);
case RG_POCKET_EGG:
return CheckRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_EGG) ||
CheckRandoInf(RAND_INF_ADULT_TRADES_HAS_POCKET_CUCCO);
- case RG_COJIRO:
- case RG_ODD_MUSHROOM:
- case RG_ODD_POTION:
- case RG_POACHERS_SAW:
- case RG_BROKEN_SWORD:
- case RG_PRESCRIPTION:
- case RG_EYEBALL_FROG:
- case RG_EYEDROPS:
- case RG_CLAIM_CHECK:
- return CheckRandoInf(itemName - RG_COJIRO + RAND_INF_ADULT_TRADES_HAS_COJIRO);
case RG_BOTTLE_WITH_BIG_POE:
case RG_BOTTLE_WITH_BLUE_FIRE:
case RG_BOTTLE_WITH_BLUE_POTION:
@@ -1764,7 +1760,7 @@ bool Logic::CanTriggerLACS() {
(ctx->LACSCondition() == RO_LACS_TOKENS && GetGSCount() >= ctx->GetOption(RSK_LACS_TOKEN_COUNT).Get());
}
-bool Logic::SmallKeys(s16 scene, uint8_t requiredAmount) {
+bool Logic::SmallKeys(SceneID scene, uint8_t requiredAmount) {
if (HasItem(RG_SKELETON_KEY)) {
return true;
}
@@ -1782,9 +1778,32 @@ std::map<RandomizerGet, uint32_t> Logic::RandoGetToEquipFlag = {
{ RG_HOVER_BOOTS, EQUIP_FLAG_BOOTS_HOVER }
};
-std::map<RandomizerGet, uint32_t> Logic::RandoGetToRandInf = {
+std::map<RandomizerGet, uint32_t> StaticData::RandoGetToRandInf = {
+ { RG_BRONZE_SCALE, RAND_INF_CAN_SWIM },
+ { RG_POWER_BRACELET, RAND_INF_CAN_GRAB },
{ RG_ZELDAS_LETTER, RAND_INF_ZELDAS_LETTER },
+ { RG_CLIMB, RAND_INF_CAN_CLIMB },
+ { RG_CRAWL, RAND_INF_CAN_CRAWL },
+ { RG_OPEN_CHEST, RAND_INF_CAN_OPEN_CHEST },
+ { RG_CHILD_WALLET, RAND_INF_HAS_WALLET },
+ { RG_QUIVER_INF, RAND_INF_HAS_INFINITE_QUIVER },
+ { RG_BOMB_BAG_INF, RAND_INF_HAS_INFINITE_BOMB_BAG },
+ { RG_BULLET_BAG_INF, RAND_INF_HAS_INFINITE_BULLET_BAG },
+ { RG_STICK_UPGRADE_INF, RAND_INF_HAS_INFINITE_STICK_UPGRADE },
+ { RG_NUT_UPGRADE_INF, RAND_INF_HAS_INFINITE_NUT_UPGRADE },
+ { RG_MAGIC_INF, RAND_INF_HAS_INFINITE_MAGIC_METER },
+ { RG_BOMBCHU_INF, RAND_INF_HAS_INFINITE_BOMBCHUS },
+ { RG_WALLET_INF, RAND_INF_HAS_INFINITE_MONEY },
{ RG_WEIRD_EGG, RAND_INF_WEIRD_EGG },
+ { RG_COJIRO, RAND_INF_ADULT_TRADES_HAS_COJIRO },
+ { RG_ODD_MUSHROOM, RAND_INF_ADULT_TRADES_HAS_ODD_MUSHROOM },
+ { RG_ODD_POTION, RAND_INF_ADULT_TRADES_HAS_ODD_POTION },
+ { RG_POACHERS_SAW, RAND_INF_ADULT_TRADES_HAS_SAW },
+ { RG_BROKEN_SWORD, RAND_INF_ADULT_TRADES_HAS_SWORD_BROKEN },
+ { RG_PRESCRIPTION, RAND_INF_ADULT_TRADES_HAS_PRESCRIPTION },
+ { RG_EYEBALL_FROG, RAND_INF_ADULT_TRADES_HAS_FROG },
+ { RG_EYEDROPS, RAND_INF_ADULT_TRADES_HAS_EYEDROPS },
+ { RG_CLAIM_CHECK, RAND_INF_ADULT_TRADES_HAS_CLAIM_CHECK },
{ RG_RUTOS_LETTER, RAND_INF_OBTAINED_RUTOS_LETTER },
{ RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, RAND_INF_DEATH_MOUNTAIN_CRATER_BEAN_SOUL },
{ RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, RAND_INF_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL },
@@ -1853,7 +1872,7 @@ std::map<RandomizerGet, uint32_t> Logic::RandoGetToRandInf = {
{ RG_FISHING_HOLE_KEY, RAND_INF_FISHING_HOLE_KEY_OBTAINED },
};
-std::map<uint32_t, uint32_t> Logic::RandoGetToDungeonScene = {
+std::map<uint32_t, SceneID> Logic::RandoGetToDungeonScene = {
{ RG_FOREST_TEMPLE_SMALL_KEY, SCENE_FOREST_TEMPLE },
{ RG_FIRE_TEMPLE_SMALL_KEY, SCENE_FIRE_TEMPLE },
{ RG_WATER_TEMPLE_SMALL_KEY, SCENE_WATER_TEMPLE },
@@ -2302,7 +2321,7 @@ void Logic::ApplyItemEffect(Item& item, bool state) {
case RG_BACK_TOWER_KEY:
case RG_HYLIA_LAB_KEY:
case RG_FISHING_HOLE_KEY:
- SetRandoInf(RandoGetToRandInf.at(randoGet), state);
+ SetRandoInf(StaticData::RandoGetToRandInf.at(randoGet), state);
break;
case RG_TRIFORCE_PIECE:
mSaveContext->ship.quest.data.randomizer.triforcePiecesCollected += (!state ? -1 : 1);
@@ -2630,83 +2649,13 @@ void Logic::SetQuestItem(uint32_t item, bool state) {
}
}
-// Get the swch bit positions for the dungeon
-const std::vector<uint8_t>& GetDungeonSmallKeyDoors(const SceneID sceneId) {
- static const std::vector<uint8_t> emptyVector;
-
- static const std::vector<uint8_t> normalSmallKeyDoors{ 1, 2, 3, 4 };
- static const std::vector<uint8_t> fastSmallKeyDoors{ 1 };
- static const std::vector<uint8_t> freeSmallKeyDoors{};
-
- using SmallKeyDoorSets = std::pair<std::vector<uint8_t>, std::vector<uint8_t>>; // first = vanilla, second = MQ
- static const std::unordered_map<SceneID, SmallKeyDoorSets> dungeonSmallKeyDoors{
- { SCENE_FOREST_TEMPLE, { { 0, 1, 2, 3, 4 }, { 0, 1, 2, 3, 4, 6 } } },
- { SCENE_FIRE_TEMPLE, { { 23, 24, 25, 26, 27, 29, 30, 31 }, { 23, 24, 26, 27, 30 } } },
- { SCENE_WATER_TEMPLE, { { 1, 2, 5, 6, 9 }, { 4, 21 } } },
- { SCENE_SPIRIT_TEMPLE, { { 13, 21, 27, 28, 30 }, { 1, 3, 18, 21, 27, 28, 30 } } },
- { SCENE_SHADOW_TEMPLE, { { 21, 22, 23, 24, 25 }, { 21, 22, 23, 24, 25, 27 } } },
- { SCENE_BOTTOM_OF_THE_WELL, { { 27, 28, 29 }, { 20, 21 } } },
- { SCENE_GERUDO_TRAINING_GROUND, { { 1, 3, 4, 5, 6, 7, 9, 10, 23 }, { 20, 23, 29 } } },
- { SCENE_INSIDE_GANONS_CASTLE, { { 29, 30 }, { 20, 21, 22 } } },
- };
- static const std::vector<uint8_t> vanillaWaterTempleDoors{ 1, 2, 5, 6, 9, 21 };
-
+int8_t Logic::GetSmallKeyCount(SceneID sceneId) {
if (sceneId == SCENE_THIEVES_HIDEOUT) {
- if (RAND_GET_OPTION(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_NORMAL)) {
- return normalSmallKeyDoors;
- }
- if (RAND_GET_OPTION(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FAST)) {
- return fastSmallKeyDoors;
- }
- return freeSmallKeyDoors;
- }
-
- if (sceneId == SCENE_WATER_TEMPLE && IS_VANILLA) {
- return vanillaWaterTempleDoors;
- }
-
- auto dungeonInfo = Rando::Context::GetInstance()->GetDungeons()->GetDungeonFromScene(sceneId);
- if (dungeonInfo == nullptr) {
- return emptyVector;
+ std::vector<uint8_t> DoorFlags = THIEVES_HIDEOUT_DOOR_FLAGS;
+ return FindTotalSmallKeys(mSaveContext, SCENE_THIEVES_HIDEOUT, &DoorFlags);
}
- auto it = dungeonSmallKeyDoors.find(sceneId);
- if (it == dungeonSmallKeyDoors.end()) {
- return emptyVector;
- }
-
- return dungeonInfo->IsMQ() ? it->second.second : it->second.first;
-}
-
-int8_t Logic::GetUsedSmallKeyCount(SceneID sceneId) {
- const auto& smallKeyDoors = GetDungeonSmallKeyDoors(sceneId);
-
- // Get the swch value for the scene
- uint32_t swch;
- if (gPlayState != nullptr && gPlayState->sceneNum == sceneId) {
- swch = gPlayState->actorCtx.flags.swch;
- } else {
- swch = mSaveContext->sceneFlags[sceneId].swch;
- }
-
- // Count the number of small keys doors unlocked
- int8_t unlockedSmallKeyDoors = 0;
- for (auto& smallKeyDoor : smallKeyDoors) {
- unlockedSmallKeyDoors += swch >> smallKeyDoor & 1;
- }
-
- // RANDOTODO: Account for MQ Water trick that causes the basement lock to unlock when the player clears the stalfos
- // pit.
- return unlockedSmallKeyDoors;
-}
-
-uint8_t Logic::GetSmallKeyCount(uint32_t dungeonIndex) {
- int8_t dungeonKeys = mSaveContext->inventory.dungeonKeys[dungeonIndex];
- if (dungeonKeys == -1) {
- // never got keys, so can't have used keys
- return 0;
- }
- return dungeonKeys + GetUsedSmallKeyCount(SceneID(dungeonIndex));
+ return Rando::Context::GetInstance()->GetDungeons()->GetDungeonFromScene(sceneId)->GetTotalSmallKeys(mSaveContext);
}
void Logic::SetSmallKeyCount(uint32_t dungeonIndex, uint8_t count) {
diff --git a/soh/soh/Enhancements/randomizer/logic.h b/soh/soh/Enhancements/randomizer/logic.h
index 5764cae5e..be342fa4c 100644
--- a/soh/soh/Enhancements/randomizer/logic.h
+++ b/soh/soh/Enhancements/randomizer/logic.h
@@ -18,8 +18,6 @@ enum class GlitchType {
EquipSwap,
};
-const std::vector<uint8_t>& GetDungeonSmallKeyDoors(const SceneID sceneId);
-
class Logic {
public:
uint8_t Bottles = 0;
@@ -47,7 +45,7 @@ class Logic {
bool BAllowed();
bool HasBossSoul(RandomizerGet itemName);
bool CanOpenOverworldDoor(RandomizerGet itemName);
- bool SmallKeys(s16 scene, uint8_t requiredAmount);
+ bool SmallKeys(SceneID scene, uint8_t requiredAmount);
bool CanGroundJump(bool hasBombflower = false);
bool CanGroundJumpslash(bool hasBombflower = false);
bool CanMiddairGroundJump(bool hasBombflower = false);
@@ -140,8 +138,7 @@ class Logic {
bool CheckEquipment(uint32_t item);
bool CheckQuestItem(uint32_t item);
void SetQuestItem(uint32_t item, bool state);
- int8_t GetUsedSmallKeyCount(SceneID sceneId);
- uint8_t GetSmallKeyCount(uint32_t dungeonIndex);
+ int8_t GetSmallKeyCount(SceneID sceneId);
void SetSmallKeyCount(uint32_t dungeonIndex, uint8_t count);
bool CheckDungeonItem(uint32_t item, uint32_t dungeonIndex);
void SetDungeonItem(uint32_t item, uint32_t dungeonIndex, bool state);
@@ -157,9 +154,8 @@ class Logic {
void InitSaveContext();
void NewSaveContext();
static std::map<uint32_t, uint32_t> RandoGetToQuestItem;
- static std::map<uint32_t, uint32_t> RandoGetToDungeonScene;
+ static std::map<uint32_t, SceneID> RandoGetToDungeonScene;
static std::map<RandomizerGet, uint32_t> RandoGetToEquipFlag;
- static std::map<RandomizerGet, uint32_t> RandoGetToRandInf;
bool IsReverseAccessPossible();
bool DMCUpperToPots();
bool DMCPotsToPad();
diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp
index 6fcbda8fd..b5376c5c2 100644
--- a/soh/soh/Enhancements/randomizer/randomizer.cpp
+++ b/soh/soh/Enhancements/randomizer/randomizer.cpp
@@ -29,11 +29,14 @@
#include "soh/Notification/Notification.h"
#include "soh/ObjectExtension/ObjectExtension.h"
#include "soh/Enhancements/randomizer/RCToRandInf.h"
+#include "static_data.h"
+#include "dungeon.h"
extern "C" {
#include "src/overlays/actors/ovl_Obj_Bean/z_obj_bean.h"
extern void func_80B8FE00(ObjBean*); // trigger planting
+extern PlayState* gPlayState;
}
static ObjectExtension::Register<CheckIdentity> RegisterIdentity;
@@ -111,54 +114,6 @@ std::unordered_map<std::string, SceneID> spoilerFileDungeonToScene = {
{ "Ganon's Castle", SCENE_INSIDE_GANONS_CASTLE }
};
-// used for items that only set a rand inf when obtained
-std::unordered_map<RandomizerGet, RandomizerInf> randomizerGetToRandInf = {
- { RG_FISHING_POLE, RAND_INF_FISHING_POLE_FOUND },
- { RG_BRONZE_SCALE, RAND_INF_CAN_SWIM },
- { RG_POWER_BRACELET, RAND_INF_CAN_GRAB },
- { RG_CLIMB, RAND_INF_CAN_CLIMB },
- { RG_CRAWL, RAND_INF_CAN_CRAWL },
- { RG_OPEN_CHEST, RAND_INF_CAN_OPEN_CHEST },
- { RG_SPEAK_DEKU, RAND_INF_CAN_SPEAK_DEKU },
- { RG_SPEAK_GERUDO, RAND_INF_CAN_SPEAK_GERUDO },
- { RG_SPEAK_GORON, RAND_INF_CAN_SPEAK_GORON },
- { RG_SPEAK_HYLIAN, RAND_INF_CAN_SPEAK_HYLIAN },
- { RG_SPEAK_KOKIRI, RAND_INF_CAN_SPEAK_KOKIRI },
- { RG_SPEAK_ZORA, RAND_INF_CAN_SPEAK_ZORA },
- { RG_QUIVER_INF, RAND_INF_HAS_INFINITE_QUIVER },
- { RG_BOMB_BAG_INF, RAND_INF_HAS_INFINITE_BOMB_BAG },
- { RG_BULLET_BAG_INF, RAND_INF_HAS_INFINITE_BULLET_BAG },
- { RG_STICK_UPGRADE_INF, RAND_INF_HAS_INFINITE_STICK_UPGRADE },
- { RG_NUT_UPGRADE_INF, RAND_INF_HAS_INFINITE_NUT_UPGRADE },
- { RG_MAGIC_INF, RAND_INF_HAS_INFINITE_MAGIC_METER },
- { RG_BOMBCHU_INF, RAND_INF_HAS_INFINITE_BOMBCHUS },
- { RG_WALLET_INF, RAND_INF_HAS_INFINITE_MONEY },
- { RG_OCARINA_A_BUTTON, RAND_INF_HAS_OCARINA_A },
- { RG_OCARINA_C_UP_BUTTON, RAND_INF_HAS_OCARINA_C_UP },
- { RG_OCARINA_C_DOWN_BUTTON, RAND_INF_HAS_OCARINA_C_DOWN },
- { RG_OCARINA_C_LEFT_BUTTON, RAND_INF_HAS_OCARINA_C_LEFT },
- { RG_OCARINA_C_RIGHT_BUTTON, RAND_INF_HAS_OCARINA_C_RIGHT },
- { RG_DEATH_MOUNTAIN_CRATER_BEAN_SOUL, RAND_INF_DEATH_MOUNTAIN_CRATER_BEAN_SOUL },
- { RG_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL, RAND_INF_DEATH_MOUNTAIN_TRAIL_BEAN_SOUL },
- { RG_DESERT_COLOSSUS_BEAN_SOUL, RAND_INF_DESERT_COLOSSUS_BEAN_SOUL },
- { RG_GERUDO_VALLEY_BEAN_SOUL, RAND_INF_GERUDO_VALLEY_BEAN_SOUL },
- { RG_GRAVEYARD_BEAN_SOUL, RAND_INF_GRAVEYARD_BEAN_SOUL },
- { RG_KOKIRI_FOREST_BEAN_SOUL, RAND_INF_KOKIRI_FOREST_BEAN_SOUL },
- { RG_LAKE_HYLIA_BEAN_SOUL, RAND_INF_LAKE_HYLIA_BEAN_SOUL },
- { RG_LOST_WOODS_BRIDGE_BEAN_SOUL, RAND_INF_LOST_WOODS_BRIDGE_BEAN_SOUL },
- { RG_LOST_WOODS_BEAN_SOUL, RAND_INF_LOST_WOODS_BEAN_SOUL },
- { RG_ZORAS_RIVER_BEAN_SOUL, RAND_INF_ZORAS_RIVER_BEAN_SOUL },
- { RG_GOHMA_SOUL, RAND_INF_GOHMA_SOUL },
- { RG_KING_DODONGO_SOUL, RAND_INF_KING_DODONGO_SOUL },
- { RG_BARINADE_SOUL, RAND_INF_BARINADE_SOUL },
- { RG_PHANTOM_GANON_SOUL, RAND_INF_PHANTOM_GANON_SOUL },
- { RG_VOLVAGIA_SOUL, RAND_INF_VOLVAGIA_SOUL },
- { RG_MORPHA_SOUL, RAND_INF_MORPHA_SOUL },
- { RG_BONGO_BONGO_SOUL, RAND_INF_BONGO_BONGO_SOUL },
- { RG_TWINROVA_SOUL, RAND_INF_TWINROVA_SOUL },
- { RG_GANON_SOUL, RAND_INF_GANON_SOUL },
-};
-
#ifdef _MSC_VER
#pragma optimize("", off)
#else
@@ -296,9 +251,10 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerCheck(Randomizer
}
ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGet randoGet) {
- if (randomizerGetToRandInf.find(randoGet) != randomizerGetToRandInf.end()) {
- return Flags_GetRandomizerInf(randomizerGetToRandInf.find(randoGet)->second) ? CANT_OBTAIN_ALREADY_HAVE
- : CAN_OBTAIN;
+ if (Rando::StaticData::RandoGetToRandInf.find(randoGet) != Rando::StaticData::RandoGetToRandInf.end()) {
+ return Flags_GetRandomizerInf((RandomizerInf)Rando::StaticData::RandoGetToRandInf.find(randoGet)->second)
+ ? CANT_OBTAIN_ALREADY_HAVE
+ : CAN_OBTAIN;
}
// This is needed since Plentiful item pool also adds a third progressive wallet
@@ -311,6 +267,7 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGe
u8 infiniteUpgrades = GetRandoSettingValue(RSK_INFINITE_UPGRADES);
u8 numWallets = 2 + (u8)tycoonWallet + (infiniteUpgrades != RO_INF_UPGRADES_OFF ? 1 : 0);
+
switch (randoGet) {
case RG_NONE:
case RG_TRIFORCE:
@@ -520,23 +477,15 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGe
return Inventory_HasEmptyBottle() ? CAN_OBTAIN : CANT_OBTAIN_NEED_EMPTY_BOTTLE;
// Trade Items
- // TODO: Do we want to be strict about any of this?
- // case RG_WEIRD_EGG:
- // case RG_ZELDAS_LETTER:
- // case RG_POCKET_EGG:
- // case RG_COJIRO:
- // case RG_ODD_MUSHROOM:
- // case RG_ODD_POTION:
- // case RG_POACHERS_SAW:
- // case RG_BROKEN_SWORD:
- // case RG_PRESCRIPTION:
- // case RG_EYEBALL_FROG:
- // case RG_EYEDROPS:
- // case RG_CLAIM_CHECK:
// case RG_PROGRESSIVE_GORONSWORD:
// case RG_GIANTS_KNIFE:
// Misc Items
+ case RG_POCKET_EGG:
+ return Flags_GetRandomizerInf(RAND_INF_ADULT_TRADES_HAS_POCKET_EGG) ||
+ Flags_GetRandomizerInf(RAND_INF_ADULT_TRADES_HAS_POCKET_CUCCO)
+ ? CANT_OBTAIN_ALREADY_HAVE
+ : CAN_OBTAIN;
case RG_STONE_OF_AGONY:
return !CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY) ? CAN_OBTAIN : CANT_OBTAIN_ALREADY_HAVE;
case RG_GERUDO_MEMBERSHIP_CARD:
@@ -642,43 +591,54 @@ ItemObtainability Randomizer::GetItemObtainabilityFromRandomizerGet(RandomizerGe
case RG_GANONS_CASTLE_BOSS_KEY:
return !CHECK_DUNGEON_ITEM(DUNGEON_KEY_BOSS, SCENE_GANONS_TOWER) ? CAN_OBTAIN : CANT_OBTAIN_ALREADY_HAVE;
case RG_FOREST_TEMPLE_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_FOREST_TEMPLE] < FOREST_TEMPLE_SMALL_KEY_MAX
+ return OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::FOREST_TEMPLE)
+ ->GetTotalSmallKeys(&gSaveContext) < FOREST_TEMPLE_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
case RG_FIRE_TEMPLE_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_FIRE_TEMPLE] < FIRE_TEMPLE_SMALL_KEY_MAX
+ return OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::FIRE_TEMPLE)
+ ->GetTotalSmallKeys(&gSaveContext) < FIRE_TEMPLE_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
case RG_WATER_TEMPLE_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_WATER_TEMPLE] < WATER_TEMPLE_SMALL_KEY_MAX
+ return OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::WATER_TEMPLE)
+ ->GetTotalSmallKeys(&gSaveContext) < WATER_TEMPLE_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
case RG_SPIRIT_TEMPLE_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_SPIRIT_TEMPLE] < SPIRIT_TEMPLE_SMALL_KEY_MAX
+ return OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::SPIRIT_TEMPLE)
+ ->GetTotalSmallKeys(&gSaveContext) < SPIRIT_TEMPLE_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
case RG_SHADOW_TEMPLE_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_SHADOW_TEMPLE] < SHADOW_TEMPLE_SMALL_KEY_MAX
+ return OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::SHADOW_TEMPLE)
+ ->GetTotalSmallKeys(&gSaveContext) < SHADOW_TEMPLE_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
case RG_BOTTOM_OF_THE_WELL_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_BOTTOM_OF_THE_WELL] < BOTTOM_OF_THE_WELL_SMALL_KEY_MAX
+ return OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::BOTTOM_OF_THE_WELL)
+ ->GetTotalSmallKeys(&gSaveContext) < BOTTOM_OF_THE_WELL_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
case RG_GERUDO_TRAINING_GROUND_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_GERUDO_TRAINING_GROUND] <
- GERUDO_TRAINING_GROUND_SMALL_KEY_MAX
+ return OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::GERUDO_TRAINING_GROUND)
+ ->GetTotalSmallKeys(&gSaveContext) < GERUDO_TRAINING_GROUND_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
- case RG_GERUDO_FORTRESS_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_THIEVES_HIDEOUT] < GERUDO_FORTRESS_SMALL_KEY_MAX
+ case RG_GERUDO_FORTRESS_SMALL_KEY: {
+ std::vector<uint8_t> DoorFlags = THIEVES_HIDEOUT_DOOR_FLAGS;
+ return Rando::FindTotalSmallKeys(&gSaveContext, SCENE_THIEVES_HIDEOUT, &DoorFlags) <
+ GERUDO_FORTRESS_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
+ }
case RG_GANONS_CASTLE_SMALL_KEY:
- return gSaveContext.inventory.dungeonKeys[SCENE_INSIDE_GANONS_CASTLE] < GANONS_CASTLE_SMALL_KEY_MAX
+ return OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::GANONS_CASTLE)
+ ->GetTotalSmallKeys(&gSaveContext) < GANONS_CASTLE_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
case RG_TREASURE_GAME_SMALL_KEY:
+ // I assume this cannot be easily manipulated?
return gSaveContext.inventory.dungeonKeys[SCENE_TREASURE_BOX_SHOP] < TREASURE_GAME_SMALL_KEY_MAX
? CAN_OBTAIN
: CANT_OBTAIN_ALREADY_HAVE;
@@ -1112,8 +1072,8 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
Randomizer_GameplayStats_SetTimestamp(item);
// if it's an item that just sets a randomizerInf, set it
- if (randomizerGetToRandInf.find(item) != randomizerGetToRandInf.end()) {
- Flags_SetRandomizerInf(randomizerGetToRandInf.find(item)->second);
+ if (Rando::StaticData::RandoGetToRandInf.find(item) != Rando::StaticData::RandoGetToRandInf.end()) {
+ Flags_SetRandomizerInf((RandomizerInf)Rando::StaticData::RandoGetToRandInf.find(item)->second);
return Return_Item_Entry(giEntry, RG_NONE);
}
diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h
index 371d36bb5..5f3fd2c6d 100644
--- a/soh/soh/Enhancements/randomizer/randomizer.h
+++ b/soh/soh/Enhancements/randomizer/randomizer.h
@@ -14,6 +14,25 @@
#include "../custom-message/CustomMessageTypes.h"
#define MAX_SEED_STRING_SIZE 1024
+#define FOREST_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_FOREST_TEMPLE) ? 6 : 5)
+#define FIRE_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_FIRE_TEMPLE) ? 5 : 8)
+#define WATER_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_WATER_TEMPLE) ? 2 : 6)
+#define SPIRIT_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_SPIRIT_TEMPLE) ? 7 : 5)
+#define SHADOW_TEMPLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_SHADOW_TEMPLE) ? 6 : 5)
+#define BOTTOM_OF_THE_WELL_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_BOTTOM_OF_THE_WELL) ? 2 : 3)
+#define GERUDO_TRAINING_GROUND_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_GERUDO_TRAINING_GROUND) ? 3 : 9)
+#define GERUDO_FORTRESS_SMALL_KEY_MAX \
+ (OTRGlobals::Instance->gRandoContext->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FAST) ? 1 \
+ : OTRGlobals::Instance->gRandoContext->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FREE) ? 0 \
+ : 4)
+#define THIEVES_HIDEOUT_DOOR_FLAGS \
+ (OTRGlobals::Instance->gRandoContext->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FAST) \
+ ? std::vector<uint8_t>{ 1 } \
+ : OTRGlobals::Instance->gRandoContext->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FREE) \
+ ? std::vector<uint8_t>{} \
+ : std::vector<uint8_t>{ 1, 2, 3, 4 })
+#define GANONS_CASTLE_SMALL_KEY_MAX (ResourceMgr_IsSceneMasterQuest(SCENE_INSIDE_GANONS_CASTLE) ? 3 : 2)
+#define TREASURE_GAME_SMALL_KEY_MAX 6
class Randomizer {
private:
diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp
index 48657c29c..bc527bc4f 100644
--- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp
+++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp
@@ -19,6 +19,7 @@
#include "soh/SohGui/UIWidgets.hpp"
#include "soh/util.h"
#include "soh/Enhancements/randomizer/randomizer.h"
+#include "soh/Enhancements/randomizer/dungeon.h"
#include <fast/Fast3dGui.h>
@@ -579,56 +580,50 @@ ItemTrackerNumbers GetItemCurrentAndMax(ItemTrackerItem item) {
// Though the ammo/capacity naming doesn't really make sense for keys, we are
// hijacking the same system to display key counts as there are enough similarities
result.currentAmmo = MAX(gSaveContext.inventory.dungeonKeys[item.data], 0);
- result.currentCapacity = gSaveContext.ship.stats.dungeonKeys[item.data];
- switch (item.data) {
- case SCENE_FOREST_TEMPLE:
- result.maxCapacity = FOREST_TEMPLE_SMALL_KEY_MAX;
- break;
- case SCENE_FIRE_TEMPLE:
- result.maxCapacity = FIRE_TEMPLE_SMALL_KEY_MAX;
- break;
- case SCENE_WATER_TEMPLE:
- result.maxCapacity = WATER_TEMPLE_SMALL_KEY_MAX;
- break;
- case SCENE_SPIRIT_TEMPLE:
- result.maxCapacity = SPIRIT_TEMPLE_SMALL_KEY_MAX;
- break;
- case SCENE_SHADOW_TEMPLE:
- result.maxCapacity = SHADOW_TEMPLE_SMALL_KEY_MAX;
- break;
- case SCENE_BOTTOM_OF_THE_WELL:
- result.maxCapacity = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX;
- break;
- case SCENE_GERUDO_TRAINING_GROUND:
- result.maxCapacity = GERUDO_TRAINING_GROUND_SMALL_KEY_MAX;
- break;
- case SCENE_THIEVES_HIDEOUT:
- if (IS_RANDO) {
- switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GERUDO_FORTRESS)) {
- case RO_GF_CARPENTERS_NORMAL:
- result.maxCapacity = GERUDO_FORTRESS_SMALL_KEY_MAX;
- break;
- case RO_GF_CARPENTERS_FAST:
- result.maxCapacity = 1;
- break;
- case RO_GF_CARPENTERS_FREE:
- result.maxCapacity = 0;
- break;
- default:
- result.maxCapacity = 0;
- SPDLOG_ERROR(
- "Invalid value for RSK_GERUDO_FORTRESS: {}",
- OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_GERUDO_FORTRESS));
- assert(false);
- break;
+ if (item.data == SCENE_THIEVES_HIDEOUT) {
+ std::vector<uint8_t> DoorFlags = THIEVES_HIDEOUT_DOOR_FLAGS;
+ result.currentCapacity = Rando::FindTotalSmallKeys(&gSaveContext, SCENE_THIEVES_HIDEOUT, &DoorFlags);
+ result.maxCapacity = GERUDO_FORTRESS_SMALL_KEY_MAX;
+ } else {
+ result.currentCapacity = OTRGlobals::Instance->gRandoContext->GetDungeons()
+ ->GetDungeonFromScene(item.data)
+ ->GetTotalSmallKeys(&gSaveContext);
+ switch (item.data) {
+ case SCENE_FOREST_TEMPLE:
+ result.maxCapacity = FOREST_TEMPLE_SMALL_KEY_MAX;
+ break;
+ case SCENE_FIRE_TEMPLE:
+ result.maxCapacity = FIRE_TEMPLE_SMALL_KEY_MAX;
+ if (IS_RANDO &&
+ !(OTRGlobals::Instance->gRandoContext->GetOption(RSK_KEYSANITY)
+ .Is(RO_DUNGEON_ITEM_LOC_ANYWHERE) ||
+ OTRGlobals::Instance->gRandoContext->GetOption(RSK_KEYSANITY)
+ .Is(RO_DUNGEON_ITEM_LOC_OVERWORLD) ||
+ OTRGlobals::Instance->gRandoContext->GetOption(RSK_KEYSANITY)
+ .Is(RO_DUNGEON_ITEM_LOC_ANY_DUNGEON)) &&
+ OTRGlobals::Instance->gRandoContext->GetDungeon(Rando::FIRE_TEMPLE)->IsVanilla()) {
+ result.currentCapacity = result.currentCapacity - 1;
}
- } else {
- result.maxCapacity = GERUDO_FORTRESS_SMALL_KEY_MAX;
- }
- break;
- case SCENE_INSIDE_GANONS_CASTLE:
- result.maxCapacity = GANONS_CASTLE_SMALL_KEY_MAX;
- break;
+ break;
+ case SCENE_WATER_TEMPLE:
+ result.maxCapacity = WATER_TEMPLE_SMALL_KEY_MAX;
+ break;
+ case SCENE_SPIRIT_TEMPLE:
+ result.maxCapacity = SPIRIT_TEMPLE_SMALL_KEY_MAX;
+ break;
+ case SCENE_SHADOW_TEMPLE:
+ result.maxCapacity = SHADOW_TEMPLE_SMALL_KEY_MAX;
+ break;
+ case SCENE_BOTTOM_OF_THE_WELL:
+ result.maxCapacity = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX;
+ break;
+ case SCENE_GERUDO_TRAINING_GROUND:
+ result.maxCapacity = GERUDO_TRAINING_GROUND_SMALL_KEY_MAX;
+ break;
+ case SCENE_INSIDE_GANONS_CASTLE:
+ result.maxCapacity = GANONS_CASTLE_SMALL_KEY_MAX;
+ break;
+ }
}
break;
}
diff --git a/soh/soh/Enhancements/randomizer/savefile.cpp b/soh/soh/Enhancements/randomizer/savefile.cpp
index a1103a47f..f668ec799 100644
--- a/soh/soh/Enhancements/randomizer/savefile.cpp
+++ b/soh/soh/Enhancements/randomizer/savefile.cpp
@@ -396,8 +396,10 @@ void SetStartingItems() {
// We can resolve this by starting with some extra keys.
if (ResourceMgr_IsSceneMasterQuest(SCENE_SPIRIT_TEMPLE)) {
// MQ Spirit needs 3 keys.
- gSaveContext.inventory.dungeonKeys[SCENE_SPIRIT_TEMPLE] = 3;
- gSaveContext.ship.stats.dungeonKeys[SCENE_SPIRIT_TEMPLE] = 3;
+ if (gSaveContext.inventory.dungeonKeys[SCENE_SPIRIT_TEMPLE] < 3) {
+ gSaveContext.inventory.dungeonKeys[SCENE_SPIRIT_TEMPLE] = 3;
+ gSaveContext.ship.stats.dungeonKeys[SCENE_SPIRIT_TEMPLE] = 3;
+ }
}
}
diff --git a/soh/soh/Enhancements/randomizer/static_data.h b/soh/soh/Enhancements/randomizer/static_data.h
index eefe26819..5390c9ba1 100644
--- a/soh/soh/Enhancements/randomizer/static_data.h
+++ b/soh/soh/Enhancements/randomizer/static_data.h
@@ -95,6 +95,7 @@ class StaticData {
static std::vector<RandomizerGet> normalBottles;
static std::vector<RandomizerGet> beanSouls;
static std::vector<RandomizerGet> overworldKeys;
+ static std::map<RandomizerGet, uint32_t> RandoGetToRandInf;
static std::unordered_map<SceneID, std::set<RandomizerGet>> itemRestrictions;
static std::set<RandomizerGet> restrictFW;
static std::set<RandomizerGet> restrictSpells;