Fix numerous Small Key Tracking bugs, excess items now more reliably turn into Blupees (#6750)

This commit is contained in:
Pepper0ni
2026-06-22 06:20:33 +01:00
committed by GitHub
parent cb215ede87
commit ec2a3d7aa2
14 changed files with 307 additions and 290 deletions
-10
View File
@@ -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) \
+25 -4
View File
@@ -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();
+1 -1
View File
@@ -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] = {
+100 -33
View File
@@ -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;
+21 -4
View File
@@ -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,
@@ -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) {
+50 -101
View File
@@ -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;
std::vector<uint8_t> 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) {
+3 -7
View File
@@ -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();
+37 -77
View File
@@ -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);
}
@@ -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:
@@ -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;
}
+4 -2
View File
@@ -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;
}
}
}
@@ -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;