From ec2a3d7aa2c2b7e39be9e83279ebc4eda078d98f Mon Sep 17 00:00:00 2001 From: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> Date: Mon, 22 Jun 2026 06:20:33 +0100 Subject: [PATCH] Fix numerous Small Key Tracking bugs, excess items now more reliably turn into Blupees (#6750) --- libultraship | 2 +- soh/include/macros.h | 10 -- soh/soh/Enhancements/gameplaystats.cpp | 29 +++- soh/soh/Enhancements/randomizer/Traps.cpp | 2 +- soh/soh/Enhancements/randomizer/dungeon.cpp | 133 +++++++++++---- soh/soh/Enhancements/randomizer/dungeon.h | 25 ++- .../randomizer/location_access.cpp | 2 +- soh/soh/Enhancements/randomizer/logic.cpp | 151 ++++++------------ soh/soh/Enhancements/randomizer/logic.h | 10 +- .../Enhancements/randomizer/randomizer.cpp | 114 +++++-------- soh/soh/Enhancements/randomizer/randomizer.h | 19 +++ .../randomizer/randomizer_item_tracker.cpp | 93 +++++------ soh/soh/Enhancements/randomizer/savefile.cpp | 6 +- soh/soh/Enhancements/randomizer/static_data.h | 1 + 14 files changed, 307 insertions(+), 290 deletions(-) diff --git a/libultraship b/libultraship index 2c63e7edef..1d98291404 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 2c63e7edef577cd2a3726ce24dfedd7f088f12ae +Subproject commit 1d9829140444268827a1369095bcc93518ff3b4d diff --git a/soh/include/macros.h b/soh/include/macros.h index dd51786f19..eb377ffa0e 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 492d37611b..7475bd942e 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 8c55d10314..3e06735039 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 6cb077c630..7a9287aff5 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 vanillaDoorFlags_, const std::vector randoDoorFlags_, + const std::vector 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* 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* 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* 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 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 8685f913ee..14613f7c67 100644 --- a/soh/soh/Enhancements/randomizer/dungeon.h +++ b/soh/soh/Enhancements/randomizer/dungeon.h @@ -6,13 +6,17 @@ #include #include #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 vanillaDoorFlags_, std::vector randoDoorFlags_, + std::vector 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* 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 vanillaDoorFlags; + // Specifically non-MQ Rando, to handle an edge case in water temple + std::vector randoDoorFlags; + std::vector MQDoorFlags; }; +int8_t FindUsedSmallKeys(const SaveContext* saveContext, const SceneID scene, const std::vector* DoorFlags); +int8_t FindCurrentSmallKeys(const SaveContext* saveContext, const SceneID scene); +int8_t FindTotalSmallKeys(const SaveContext* saveContext, const SceneID scene, const std::vector* 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 7e1750fa32..694af768a5 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 9621fcdf35..1cd8887fa8 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 #include #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 Logic::RandoGetToEquipFlag = { { RG_HOVER_BOOTS, EQUIP_FLAG_BOOTS_HOVER } }; -std::map Logic::RandoGetToRandInf = { +std::map 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 Logic::RandoGetToRandInf = { { RG_FISHING_HOLE_KEY, RAND_INF_FISHING_HOLE_KEY_OBTAINED }, }; -std::map Logic::RandoGetToDungeonScene = { +std::map 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& GetDungeonSmallKeyDoors(const SceneID sceneId) { - static const std::vector emptyVector; - - static const std::vector normalSmallKeyDoors{ 1, 2, 3, 4 }; - static const std::vector fastSmallKeyDoors{ 1 }; - static const std::vector freeSmallKeyDoors{}; - - using SmallKeyDoorSets = std::pair, std::vector>; // first = vanilla, second = MQ - static const std::unordered_map 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 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; + std::vector DoorFlags = THIEVES_HIDEOUT_DOOR_FLAGS; + return FindTotalSmallKeys(mSaveContext, SCENE_THIEVES_HIDEOUT, &DoorFlags); } - if (sceneId == SCENE_WATER_TEMPLE && IS_VANILLA) { - return vanillaWaterTempleDoors; - } - - auto dungeonInfo = Rando::Context::GetInstance()->GetDungeons()->GetDungeonFromScene(sceneId); - if (dungeonInfo == nullptr) { - return emptyVector; - } - - 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 5764cae5e3..be342fa4ce 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& 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 RandoGetToQuestItem; - static std::map RandoGetToDungeonScene; + static std::map RandoGetToDungeonScene; static std::map RandoGetToEquipFlag; - static std::map RandoGetToRandInf; bool IsReverseAccessPossible(); bool DMCUpperToPots(); bool DMCPotsToPad(); diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 6fcbda8fde..b5376c5c20 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 RegisterIdentity; @@ -111,54 +114,6 @@ std::unordered_map spoilerFileDungeonToScene = { { "Ganon's Castle", SCENE_INSIDE_GANONS_CASTLE } }; -// used for items that only set a rand inf when obtained -std::unordered_map 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 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 371d36bb51..5f3fd2c6d4 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{ 1 } \ + : OTRGlobals::Instance->gRandoContext->GetOption(RSK_GERUDO_FORTRESS).Is(RO_GF_CARPENTERS_FREE) \ + ? std::vector{} \ + : std::vector{ 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 48657c29c4..bc527bc4f8 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 @@ -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 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 a1103a47f4..f668ec799c 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 eefe268194..5390c9ba1e 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 normalBottles; static std::vector beanSouls; static std::vector overworldKeys; + static std::map RandoGetToRandInf; static std::unordered_map> itemRestrictions; static std::set restrictFW; static std::set restrictSpells;