diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 8d3acf22f..4b87a179a 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -92,7 +92,6 @@ typedef struct { /* */ u32 count[COUNT_MAX]; /* */ u32 entrancesDiscovered[SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT]; /* */ u32 scenesDiscovered[SAVEFILE_SCENES_DISCOVERED_IDX_COUNT]; - /* */ u8 locationsSkipped[RC_MAX]; /* */ bool rtaTiming; /* */ uint64_t fileCreatedAt; } SohStats; @@ -292,6 +291,7 @@ typedef struct { /* */ SohStats sohStats; /* */ u8 temporaryWeapon; /* */ FaroresWindData backupFW; + /* */ RandomizerCheckTrackerData checkTrackerData[RC_MAX]; // #endregion // #region SOH [Randomizer] // Upstream TODO: Move these to their own struct or name to more obviously specific to Randomizer diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index e49c99d22..ae711c093 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -159,6 +159,7 @@ public: DEFINE_HOOK(OnSceneSpawnActors, void()); DEFINE_HOOK(OnPlayerUpdate, void()); DEFINE_HOOK(OnOcarinaSongAction, void()); + DEFINE_HOOK(OnShopSlotChange, void(uint8_t cursorIndex, int16_t price)); DEFINE_HOOK(OnActorInit, void(void* actor)); DEFINE_HOOK(OnActorUpdate, void(void* actor)); DEFINE_HOOK(OnActorKill, void(void* actor)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 1cb6f48a8..13df133a0 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -58,6 +58,10 @@ void GameInteractor_ExecuteOnOcarinaSongAction() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t price) { + GameInteractor::Instance->ExecuteHooks(cursorIndex, price); +} + void GameInteractor_ExecuteOnActorInit(void* actor) { GameInteractor::Instance->ExecuteHooks(actor); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index b23b64798..47114a300 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -24,6 +24,7 @@ void GameInteractor_ExecuteOnActorKill(void* actor); void GameInteractor_ExecuteOnEnemyDefeat(void* actor); void GameInteractor_ExecuteOnPlayerBonk(); void GameInteractor_ExecuteOnOcarinaSongAction(); +void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t price); void GameInteractor_ExecuteOnPlayDestroy(); void GameInteractor_ExecuteOnPlayDrawEnd(); diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index ea95afe8e..31bf36d3c 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -334,9 +334,6 @@ void LoadStatsVersion1() { SaveManager::Instance->LoadArray("entrancesDiscovered", ARRAY_COUNT(gSaveContext.sohStats.entrancesDiscovered), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.sohStats.entrancesDiscovered[i]); }); - SaveManager::Instance->LoadArray("locationsSkipped", ARRAY_COUNT(gSaveContext.sohStats.locationsSkipped), [](size_t i) { - SaveManager::Instance->LoadData("", gSaveContext.sohStats.locationsSkipped[i]); - }); } void SaveStats(SaveContext* saveContext, int sectionID, bool fullSave) { @@ -378,9 +375,6 @@ void SaveStats(SaveContext* saveContext, int sectionID, bool fullSave) { SaveManager::Instance->SaveArray("entrancesDiscovered", ARRAY_COUNT(saveContext->sohStats.entrancesDiscovered), [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->sohStats.entrancesDiscovered[i]); }); - SaveManager::Instance->SaveArray("locationsSkipped", ARRAY_COUNT(saveContext->sohStats.locationsSkipped), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->sohStats.locationsSkipped[i]); - }); } void GameplayStatsRow(const char* label, const std::string& value, ImVec4 color = COLOR_WHITE) { @@ -688,9 +682,6 @@ void InitStats(bool isDebug) { for (int entrancesIdx = 0; entrancesIdx < ARRAY_COUNT(gSaveContext.sohStats.entrancesDiscovered); entrancesIdx++) { gSaveContext.sohStats.entrancesDiscovered[entrancesIdx] = 0; } - for (int rc = 0; rc < ARRAY_COUNT(gSaveContext.sohStats.locationsSkipped); rc++) { - gSaveContext.sohStats.locationsSkipped[rc] = 0; - } strncpy(gSaveContext.sohStats.buildVersion, (const char*) gBuildVersion, sizeof(gSaveContext.sohStats.buildVersion) - 1); gSaveContext.sohStats.buildVersion[sizeof(gSaveContext.sohStats.buildVersion) - 1] = 0; diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 78a9d0525..3e9c0f7c5 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -6,10 +6,9 @@ #include #include #include -#include +#include #include #include -#include #include "3drando/rando_main.hpp" #include "3drando/random.hpp" #include "../../UIWidgets.hpp" @@ -21,14 +20,16 @@ #include #include "randomizer_check_objects.h" #include "randomizer_tricks.h" +#include "randomizer_check_tracker.h" #include #include #include #include "draw.h" #include "rando_hash.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" #include -#include #include "randomizer_settings_window.h" +#include "savefile.h" extern "C" uint32_t ResourceMgr_IsGameMasterQuest(); extern "C" uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum); @@ -48,6 +49,8 @@ std::set excludedLocations; std::set enabledTricks; std::set enabledGlitches; +std::set> checkTrackerStates; + u8 generated; char* seedString; @@ -133,18 +136,6 @@ Randomizer::Randomizer() { item.GetName().french, }; } - for (auto area : rcAreaNames) { - SpoilerfileAreaNameToEnum[area.second] = area.first; - } - SpoilerfileAreaNameToEnum["Inside Ganon's Castle"] = RCAREA_GANONS_CASTLE; - SpoilerfileAreaNameToEnum["the Lost Woods"] = RCAREA_LOST_WOODS; - SpoilerfileAreaNameToEnum["the Market"] = RCAREA_MARKET; - SpoilerfileAreaNameToEnum["the Graveyard"] = RCAREA_GRAVEYARD; - SpoilerfileAreaNameToEnum["Haunted Wasteland"] = RCAREA_WASTELAND; - SpoilerfileAreaNameToEnum["outside Ganon's Castle"] = RCAREA_HYRULE_CASTLE; - for (auto [type, name] : hintTypeNames) { - SpoilerfileHintTypeNameToEnum[name] = type; - } } Sprite* Randomizer::GetSeedTexture(uint8_t index) { @@ -2601,6 +2592,11 @@ RandomizerCheckObject Randomizer::GetCheckObjectFromActor(s16 actorId, s16 scene if((actorParams & 0xF) < 10) specialRc = RC_MARKET_TREASURE_CHEST_GAME_ITEM_5; } break; + case SCENE_SACRED_FOREST_MEADOW: + if (actorId == ACTOR_EN_SA) { + specialRc = RC_SONG_FROM_SARIA; + } + break; case SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY: case SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT: case SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS: @@ -4106,9 +4102,8 @@ void RandomizerSettingsWindow::DrawElement() { ImGui::PopItemWidth(); switch (CVarGetInteger("gRandomizeShuffleKeyRings", RO_KEYRINGS_OFF)) { case RO_KEYRINGS_COUNT: - maxKeyringCount = - (CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) == RO_GF_NORMAL && - CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) != RO_GERUDO_KEYS_VANILLA) ? 9 : 8; + maxKeyringCount = (CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) == RO_GF_NORMAL && + CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) != RO_GERUDO_KEYS_VANILLA) ? 9 : 8; UIWidgets::PaddedEnhancementSliderInt("Key Ring Count: %d", "##RandomizeShuffleKeyRingsRandomCount", "gRandomizeShuffleKeyRingsRandomCount", 1, @@ -4117,7 +4112,7 @@ void RandomizerSettingsWindow::DrawElement() { case RO_KEYRINGS_SELECTION: disableGFKeyring = CVarGetInteger("gRandomizeGerudoFortress", RO_GF_NORMAL) != RO_GF_NORMAL || CVarGetInteger("gRandomizeGerudoKeys", RO_GERUDO_KEYS_VANILLA) == RO_GERUDO_KEYS_VANILLA; - UIWidgets::EnhancementCheckbox( "Gerudo Fortress##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsGerudoFortress", + UIWidgets::EnhancementCheckbox("Gerudo Fortress##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsGerudoFortress", disableGFKeyring, "Disabled because the currently selected Gerudo Fortress Carpenters\n setting and/or Gerudo Fortress Keys setting is incompatible with \nhaving a Gerudo Fortress keyring."); UIWidgets::EnhancementCheckbox("Forest Temple##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsForestTemple"); UIWidgets::EnhancementCheckbox("Fire Temple##RandomizeShuffleKeyRings", "gRandomizeShuffleKeyRingsFireTemple"); @@ -4397,15 +4392,13 @@ void RandomizerSettingsWindow::DrawElement() { UIWidgets::PaddedSeparator(); // Complete mask quest - UIWidgets::EnhancementCheckbox(Settings::CompleteMaskQuest.GetName().c_str(), - "gRandomizeCompleteMaskQuest"); + UIWidgets::EnhancementCheckbox(Settings::CompleteMaskQuest.GetName().c_str(), "gRandomizeCompleteMaskQuest"); UIWidgets::InsertHelpHoverText("Once the happy mask shop is opened, all masks will be available to be borrowed."); UIWidgets::PaddedSeparator(); // Skip Scarecrow Song - UIWidgets::EnhancementCheckbox(Settings::FreeScarecrow.GetName().c_str(), - "gRandomizeSkipScarecrowsSong"); + UIWidgets::EnhancementCheckbox(Settings::FreeScarecrow.GetName().c_str(), "gRandomizeSkipScarecrowsSong"); UIWidgets::InsertHelpHoverText( "Start with the ability to summon Pierre the scarecrow. Pulling out an ocarina in the usual locations will automatically summon him." ); @@ -5220,12 +5213,13 @@ void RandomizerSettingsWindow::DrawElement() { UIWidgets::EnhancementCheckbox(Settings::StartingSongOfTime.GetName().c_str(), "gRandomizeStartingSongOfTime"); UIWidgets::EnhancementCheckbox(Settings::StartingSongOfStorms.GetName().c_str(), "gRandomizeStartingSongOfStorms"); UIWidgets::PaddedSeparator(); + ImGui::Text("Warp Songs"); UIWidgets::PaddedSeparator(); - UIWidgets::EnhancementCheckbox(Settings::StartingMinuetOfForest.GetName().c_str(), "gRandomizeStartingMinuetOfForest"); + UIWidgets::EnhancementCheckbox(Settings::StartingMinuetOfForest.GetName().c_str(), "gRandomizeStartingMinuetOfForest"); UIWidgets::EnhancementCheckbox(Settings::StartingBoleroOfFire.GetName().c_str(), "gRandomizeStartingBoleroOfFire"); UIWidgets::EnhancementCheckbox(Settings::StartingSerenadeOfWater.GetName().c_str(), "gRandomizeStartingSerenadeOfWater"); - UIWidgets::EnhancementCheckbox(Settings::StartingRequiemOfSpirit.GetName().c_str(), "gRandomizeStartingRequiemOfSpirit"); + UIWidgets::EnhancementCheckbox(Settings::StartingRequiemOfSpirit.GetName().c_str(), "gRandomizeStartingRequiemOfSpirit"); UIWidgets::EnhancementCheckbox(Settings::StartingNocturneOfShadow.GetName().c_str(), "gRandomizeStartingNocturneOfShadow"); UIWidgets::EnhancementCheckbox(Settings::StartingPreludeOfLight.GetName().c_str(), "gRandomizeStartingPreludeOfLight"); UIWidgets::PaddedSeparator(); diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h index 82347d994..a726cecde 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.h +++ b/soh/soh/Enhancements/randomizer/randomizer.h @@ -38,7 +38,6 @@ class Randomizer { void ParseItemLocationsFile(const char* spoilerFileName, bool silent); void ParseEntranceDataFile(const char* spoilerFileName, bool silent); bool IsItemVanilla(RandomizerGet randoGet); - GetItemEntry GetItemEntryFromRGData(RandomizerGetData rgData, GetItemID ogItemId, bool checkObtainability = true); int16_t GetVanillaMerchantPrice(RandomizerCheck check); public: @@ -78,6 +77,7 @@ class Randomizer { RandomizerInf GetRandomizerInfFromCheck(RandomizerCheck rc); RandomizerGetData GetRandomizerGetDataFromActor(s16 actorId, s16 sceneNum, s16 actorParams); RandomizerGetData GetRandomizerGetDataFromKnownCheck(RandomizerCheck randomizerCheck); + GetItemEntry GetItemEntryFromRGData(RandomizerGetData rgData, GetItemID ogItemId, bool checkObtainability = true); std::string GetChildAltarText() const; std::string GetAdultAltarText() const; std::string GetGanonText() const; diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 9d77a9a29..b1e843497 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -20,6 +20,16 @@ typedef struct { uint8_t id; } Sprite; +// Check tracker check visibility categories +typedef enum { + RCSHOW_UNCHECKED, + RCSHOW_SEEN, + RCSHOW_IDENTIFIED, + RCSHOW_SCUMMED, + RCSHOW_COLLECTED, + RCSHOW_SAVED, +} RandomizerCheckStatus; + typedef enum { HINT_TYPE_TRIAL, HINT_TYPE_ALWAYS, @@ -1461,6 +1471,13 @@ typedef enum { RSK_MAX } RandomizerSettingKey; +typedef struct { + RandomizerCheckStatus status; + uint16_t skipped; + int16_t price; + uint16_t hintItem; +} RandomizerCheckTrackerData; + //Generic Settings (any binary option can use this) // off/on typedef enum { diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp index c244baffd..be05be35d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_objects.cpp @@ -891,6 +891,9 @@ std::map RandomizerCheckObjects::GetAllRCAreaBySce for (int id = (int)SCENE_MARKET_ENTRANCE_DAY; id <= (int)SCENE_MARKET_RUINS; id++) { rcAreaBySceneID[(SceneID)id] = RCAREA_MARKET; } + rcAreaBySceneID[SCENE_TEMPLE_OF_TIME] = RCAREA_MARKET; + rcAreaBySceneID[SCENE_CASTLE_COURTYARD_GUARDS_DAY] = RCAREA_HYRULE_CASTLE; + rcAreaBySceneID[SCENE_CASTLE_COURTYARD_GUARDS_NIGHT] = RCAREA_HYRULE_CASTLE; } return rcAreaBySceneID; } diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 6a7f2dc6c..0884a694f 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -1,4 +1,7 @@ #include "randomizer_check_tracker.h" +#include "randomizer_entrance_tracker.h" +#include "randomizer_item_tracker.h" +#include "randomizerTypes.h" #include "../../OTRGlobals.h" #include "../../UIWidgets.hpp" @@ -8,8 +11,7 @@ #include #include "3drando/item_location.hpp" #include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "randomizerTypes.h" - +#include "z64item.h" extern "C" { #include "variables.h" @@ -18,58 +20,123 @@ extern "C" { extern PlayState* gPlayState; } extern "C" uint32_t ResourceMgr_IsSceneMasterQuest(s16 sceneNum); +extern "C" GetItemEntry ItemTable_RetrieveEntry(s16 modIndex, s16 getItemID); + +extern std::vector dungeonRewardStones; +extern std::vector dungeonRewardMedallions; +extern std::vector songItems; +extern std::vector equipmentItems; + +#define RCO_RAORU { RC_GIFT_FROM_SAGES, RCVORMQ_BOTH, RCTYPE_DUNGEON_REWARD, RCAREA_MARKET, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, false, "Gift from Raoru", "Gift from Raoru", true }; + +using json = nlohmann::json; + +void to_json(json& j, const RandomizerCheckTrackerData& rctd) { + j = json { + { "status", rctd.status == RCSHOW_COLLECTED ? RCSHOW_SAVED : rctd.status }, + { "skipped", rctd.skipped }, + { "price", rctd.price }, + { "hintItem", rctd.hintItem }}; + } + +void from_json(const json& j, RandomizerCheckTrackerData& rctd) { + j.at("status").get_to(rctd.status); + j.at("skipped").get_to(rctd.skipped); + j.at("hintItem").get_to(rctd.hintItem); + j.at("price").get_to(rctd.price); +} namespace CheckTracker { -void Teardown(); -void InitializeChecks(); -void UpdateChecks(); -void UpdateInventoryChecks(); -void DrawLocation(RandomizerCheckObject rcObj, RandomizerCheckShow* thisCheckStatus); +// settings +bool showShops; +bool showOverworldTokens; +bool showDungeonTokens; +bool showBeans; +bool showScrubs; +bool showMerchants; +bool showCows; +bool showAdultTrade; +bool showKokiriSword; +bool showWeirdEgg; +bool showGerudoCard; +bool showFrogSongRupees; +bool showStartingMapsCompasses; +bool showKeysanity; +bool showGerudoFortressKeys; +bool showBossKeysanity; +bool showGanonBossKey; +bool showOcarinas; +bool show100SkullReward; +bool showLinksPocket; +bool fortressFast; +bool fortressNormal; + +bool bypassRandoCheck = true; +// persistent during gameplay +bool initialized; +bool doAreaScroll; +bool previousShowHidden = false; +bool hideShopRightChecks = true; + +bool checkCollected = false; +int checkLoops = 0; +int checkCounter = 0; +u16 savedFrames = 0; +bool messageCloseCheck = false; +bool pendingSaleCheck = false; +bool transitionCheck = false; + +std::map startingShopItem = { { SCENE_KOKIRI_SHOP, RC_KF_SHOP_ITEM_1 }, + { SCENE_BAZAAR, RC_MARKET_BAZAAR_ITEM_1 }, + { SCENE_POTION_SHOP_MARKET, RC_MARKET_POTION_SHOP_ITEM_1 }, + { SCENE_BOMBCHU_SHOP, RC_MARKET_BOMBCHU_SHOP_ITEM_1 }, + { SCENE_POTION_SHOP_KAKARIKO, RC_KAK_POTION_SHOP_ITEM_1 }, + { SCENE_ZORA_SHOP, RC_ZD_SHOP_ITEM_1 }, + { SCENE_GORON_SHOP, RC_GC_SHOP_ITEM_1 } }; + +std::map RCAreaFromSceneID = { + {SCENE_DEKU_TREE, RCAREA_DEKU_TREE}, + {SCENE_DODONGOS_CAVERN, RCAREA_DODONGOS_CAVERN}, + {SCENE_JABU_JABU, RCAREA_JABU_JABUS_BELLY}, + {SCENE_FOREST_TEMPLE, RCAREA_FOREST_TEMPLE}, + {SCENE_FIRE_TEMPLE, RCAREA_FIRE_TEMPLE}, + {SCENE_WATER_TEMPLE, RCAREA_WATER_TEMPLE}, + {SCENE_SHADOW_TEMPLE, RCAREA_SHADOW_TEMPLE}, + {SCENE_SPIRIT_TEMPLE, RCAREA_SPIRIT_TEMPLE}, + {SCENE_BOTTOM_OF_THE_WELL, RCAREA_BOTTOM_OF_THE_WELL}, + {SCENE_ICE_CAVERN, RCAREA_ICE_CAVERN}, + {SCENE_GERUDO_TRAINING_GROUND, RCAREA_GERUDO_TRAINING_GROUND}, + {SCENE_INSIDE_GANONS_CASTLE, RCAREA_GANONS_CASTLE}, +}; + +std::map> checksByArea; +bool areasFullyChecked[RCAREA_INVALID]; +u32 areasSpoiled = 0; +bool showVOrMQ; +s8 areaChecksGotten[32]; //| "Kokiri Forest (4/9)" +bool optCollapseAll; // A bool that will collapse all checks once +bool optExpandAll; // A bool that will expand all checks once +RandomizerCheck lastItemGetCheck = RC_UNKNOWN_CHECK; +RandomizerCheck lastLocationChecked = RC_UNKNOWN_CHECK; +RandomizerCheckArea previousArea = RCAREA_INVALID; +RandomizerCheckArea currentArea = RCAREA_INVALID; +std::vector checkAreas; +std::vector itemsReceived; +OSContPad* trackerButtonsPressed; + void BeginFloatWindows(std::string UniqueName, bool& open, ImGuiWindowFlags flags = 0); +bool CompareChecks(RandomizerCheckObject, RandomizerCheckObject); +bool CheckByArea(RandomizerCheckArea); +void DrawLocation(RandomizerCheckObject); void EndFloatWindows(); -void UpdateOrdering(bool init = false); -bool ShouldUpdateChecks(); -bool CompareCheckObject(RandomizerCheckObject i, RandomizerCheckObject j); -bool HasItemBeenCollected(RandomizerCheckObject obj); -bool HasItemBeenSkipped(RandomizerCheckObject obj); +bool HasItemBeenCollected(RandomizerCheck); +void LoadSettings(); void RainbowTick(); -RandomizerCheckShow GetCheckStatus(RandomizerCheckObject rcObj, int idx); - - -Color_RGBA8 Color_Bg_Default = { 0, 0, 0, 255 }; // Black -Color_RGBA8 Color_Main_Default = { 255, 255, 255, 255 }; // White -Color_RGBA8 Color_Area_Incomplete_Extra_Default = { 255, 255, 255, 255 }; // White -Color_RGBA8 Color_Area_Complete_Extra_Default = { 255, 255, 255, 255 }; // White -Color_RGBA8 Color_Unchecked_Extra_Default = { 255, 255, 255, 255 }; // White -Color_RGBA8 Color_Skipped_Main_Default = { 160, 160, 160, 255 }; // Grey -Color_RGBA8 Color_Skipped_Extra_Default = { 160, 160, 160, 255 }; // Grey -Color_RGBA8 Color_Seen_Extra_Default = { 255, 255, 255, 255 }; // TODO -Color_RGBA8 Color_Hinted_Extra_Default = { 255, 255, 255, 255 }; // TODO -Color_RGBA8 Color_Checked_Extra_Default = { 255, 255, 255, 255 }; // TODO -Color_RGBA8 Color_Scummed_Extra_Default = { 255, 255, 255, 255 }; // TODO -Color_RGBA8 Color_Saved_Extra_Default = { 0, 185, 0, 255 }; // Green - -Color_RGBA8 Color_Background = { 0, 0, 0, 255 }; - -Color_RGBA8 Color_Area_Incomplete_Main = { 255, 255, 255, 255 }; //White -Color_RGBA8 Color_Area_Incomplete_Extra = { 255, 255, 255, 255 }; //White -Color_RGBA8 Color_Area_Complete_Main = { 255, 255, 255, 255 }; // White -Color_RGBA8 Color_Area_Complete_Extra = { 255, 255, 255, 255 }; // White -Color_RGBA8 Color_Unchecked_Main = { 255, 255, 255, 255 }; //White -Color_RGBA8 Color_Unchecked_Extra = { 255, 255, 255, 255 }; //Useless -Color_RGBA8 Color_Skipped_Main = { 255, 255, 255, 255 }; //Grey -Color_RGBA8 Color_Skipped_Extra = { 255, 255, 255, 255 }; //Grey -Color_RGBA8 Color_Seen_Main = { 255, 255, 255, 255 }; //TODO -Color_RGBA8 Color_Seen_Extra = { 255, 255, 255, 255 }; //TODO -Color_RGBA8 Color_Hinted_Main = { 255, 255, 255, 255 }; //TODO -Color_RGBA8 Color_Hinted_Extra = { 255, 255, 255, 255 }; //TODO -Color_RGBA8 Color_Checked_Main = { 255, 255, 255, 255 }; //TODO -Color_RGBA8 Color_Checked_Extra = { 255, 255, 255, 255 }; //TODO -Color_RGBA8 Color_Scummed_Main = { 255, 255, 255, 255 }; //TODO -Color_RGBA8 Color_Scummed_Extra = { 255, 255, 255, 255 }; //TODO -Color_RGBA8 Color_Saved_Main = { 255, 255, 255, 255 }; //White -Color_RGBA8 Color_Saved_Extra = { 0, 185, 0, 255 }; //Green +void UpdateAreas(RandomizerCheckArea area); +void UpdateInventoryChecks(); +void UpdateOrdering(RandomizerCheckArea); +int sectionId; SceneID DungeonSceneLookupByArea(RandomizerCheckArea area) { switch (area) { @@ -89,33 +156,620 @@ SceneID DungeonSceneLookupByArea(RandomizerCheckArea area) { } } -// persistent during gameplay -bool initialized = false; -bool doInitialize = false; -std::map checkStatusMap; -u32 areasFullyChecked = 0; -u32 areasSpoiled = 0; -bool showVOrMQ; -s8 areaChecksTotal[32]; //| For sorting and showing -s8 areaChecksGotten[32]; //| "Kokiri Forest (4/9)" -std::vector checks; -bool optCollapseAll; // A bool that will collapse all checks once -bool optExpandAll; // A bool that will expand all checks once -RandomizerCheck lastLocationChecked = RC_UNKNOWN_CHECK; -RandomizerCheckArea previousArea = RCAREA_INVALID; -RandomizerCheckArea currentArea = RCAREA_INVALID; +Color_RGBA8 Color_Bg_Default = { 0, 0, 0, 255 }; // Black +Color_RGBA8 Color_Main_Default = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Area_Incomplete_Extra_Default = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Area_Complete_Extra_Default = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Unchecked_Extra_Default = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Skipped_Main_Default = { 160, 160, 160, 255 }; // Grey +Color_RGBA8 Color_Skipped_Extra_Default = { 160, 160, 160, 255 }; // Grey +Color_RGBA8 Color_Seen_Extra_Default = { 255, 255, 255, 255 }; // TODO +Color_RGBA8 Color_Hinted_Extra_Default = { 255, 255, 255, 255 }; // TODO +Color_RGBA8 Color_Collected_Extra_Default = { 242, 101, 34, 255 }; // Orange +Color_RGBA8 Color_Scummed_Extra_Default = { 0, 174, 239, 255 }; // Blue +Color_RGBA8 Color_Saved_Extra_Default = { 0, 185, 0, 255 }; // Green + +Color_RGBA8 Color_Background = { 0, 0, 0, 255 }; + +Color_RGBA8 Color_Area_Incomplete_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Area_Incomplete_Extra = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Area_Complete_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Area_Complete_Extra = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Unchecked_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Unchecked_Extra = { 255, 255, 255, 255 }; // Useless +Color_RGBA8 Color_Skipped_Main = { 160, 160, 160, 255 }; // Grey +Color_RGBA8 Color_Skipped_Extra = { 160, 160, 160, 255 }; // Grey +Color_RGBA8 Color_Seen_Main = { 255, 255, 255, 255 }; // TODO +Color_RGBA8 Color_Seen_Extra = { 160, 160, 160, 255 }; // TODO +Color_RGBA8 Color_Hinted_Main = { 255, 255, 255, 255 }; // TODO +Color_RGBA8 Color_Hinted_Extra = { 255, 255, 255, 255 }; // TODO +Color_RGBA8 Color_Collected_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Collected_Extra = { 242, 101, 34, 255 }; // Orange +Color_RGBA8 Color_Scummed_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Scummed_Extra = { 0, 174, 239, 255 }; // Blue +Color_RGBA8 Color_Saved_Main = { 255, 255, 255, 255 }; // White +Color_RGBA8 Color_Saved_Extra = { 0, 185, 0, 255 }; // Green std::vector buttons = { BTN_A, BTN_B, BTN_CUP, BTN_CDOWN, BTN_CLEFT, BTN_CRIGHT, BTN_L, BTN_Z, BTN_R, BTN_START, BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT }; +void SetLastItemGetRC(RandomizerCheck rc) { + lastItemGetCheck = rc; +} + +void DefaultCheckData(RandomizerCheck rc) { + gSaveContext.checkTrackerData[rc].status = RCSHOW_UNCHECKED; + gSaveContext.checkTrackerData[rc].skipped = 0; + gSaveContext.checkTrackerData[rc].hintItem = RC_UNKNOWN_CHECK; +} + +void SongFromImpa() { + if (IS_RANDO) { + if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SKIP_CHILD_ZELDA) == RO_GENERIC_ON && IS_RANDO) { + if (gSaveContext.checkTrackerData[RC_SONG_FROM_IMPA].status != RCSHOW_SAVED) { + gSaveContext.checkTrackerData[RC_SONG_FROM_IMPA].status = RCSHOW_SAVED; + } + } + } +} + +void GiftFromSages() { + if (!IS_RANDO) { + DefaultCheckData(RC_GIFT_FROM_SAGES); + } +} + +std::vector checks; +// Function for adding Link's Pocket check +void LinksPocket() { + if (IS_RANDO) { + if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING || + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_DUNGEON_REWARDS) == RO_DUNGEON_REWARDS_END_OF_DUNGEON) { + DefaultCheckData(RC_LINKS_POCKET); + gSaveContext.checkTrackerData[RC_LINKS_POCKET].status = RCSHOW_SAVED; + } + } +} + +void TrySetAreas() { + if (checksByArea.empty()) { + for (int i = RCAREA_KOKIRI_FOREST; i < RCAREA_INVALID; i++) { + checksByArea.emplace(static_cast(i), std::vector()); + } + } +} + +void SetCheckCollected(RandomizerCheck rc) { + gSaveContext.checkTrackerData[rc].status = RCSHOW_COLLECTED; + RandomizerCheckObject rcObj; + if (rc == RC_GIFT_FROM_SAGES && !IS_RANDO) { + rcObj = RCO_RAORU; + } else { + rcObj = RandomizerCheckObjects::GetAllRCObjects().find(rc)->second; + } + if (!gSaveContext.checkTrackerData[rc].skipped) { + areaChecksGotten[rcObj.rcArea]++; + } else { + gSaveContext.checkTrackerData[rc].skipped = false; + } + if (!checkAreas.empty()) { + checkAreas.erase(checkAreas.begin()); + } + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); + + doAreaScroll = true; + UpdateOrdering(rcObj.rcArea); + UpdateInventoryChecks(); +} + +bool IsAreaScene(SceneID sceneNum) { + switch (sceneNum) { + case SCENE_HYRULE_FIELD: + case SCENE_KAKARIKO_VILLAGE: + case SCENE_GRAVEYARD: + case SCENE_ZORAS_RIVER: + case SCENE_KOKIRI_FOREST: + case SCENE_SACRED_FOREST_MEADOW: + case SCENE_LAKE_HYLIA: + case SCENE_ZORAS_DOMAIN: + case SCENE_ZORAS_FOUNTAIN: + case SCENE_GERUDO_VALLEY: + case SCENE_LOST_WOODS: + case SCENE_DESERT_COLOSSUS: + case SCENE_GERUDOS_FORTRESS: + case SCENE_HAUNTED_WASTELAND: + case SCENE_HYRULE_CASTLE: + case SCENE_DEATH_MOUNTAIN_TRAIL: + case SCENE_DEATH_MOUNTAIN_CRATER: + case SCENE_GORON_CITY: + case SCENE_LON_LON_RANCH: + case SCENE_DEKU_TREE: + case SCENE_DODONGOS_CAVERN: + case SCENE_JABU_JABU: + case SCENE_FOREST_TEMPLE: + case SCENE_FIRE_TEMPLE: + case SCENE_WATER_TEMPLE: + case SCENE_SPIRIT_TEMPLE: + case SCENE_SHADOW_TEMPLE: + case SCENE_BOTTOM_OF_THE_WELL: + case SCENE_ICE_CAVERN: + case SCENE_GERUDO_TRAINING_GROUND: + case SCENE_GANONS_TOWER: + case SCENE_INSIDE_GANONS_CASTLE: + case SCENE_BACK_ALLEY_DAY: + case SCENE_BACK_ALLEY_NIGHT: + case SCENE_MARKET_DAY: + case SCENE_MARKET_NIGHT: + case SCENE_MARKET_RUINS: + return true; + default: + return false; + } +} + +RandomizerCheckArea AreaFromEntranceGroup[] = { + RCAREA_INVALID, + RCAREA_KOKIRI_FOREST, + RCAREA_LOST_WOODS, + RCAREA_SACRED_FOREST_MEADOW, + RCAREA_KAKARIKO_VILLAGE, + RCAREA_GRAVEYARD, + RCAREA_DEATH_MOUNTAIN_TRAIL, + RCAREA_DEATH_MOUNTAIN_CRATER, + RCAREA_GORON_CITY, + RCAREA_ZORAS_RIVER, + RCAREA_ZORAS_DOMAIN, + RCAREA_ZORAS_FOUNTAIN, + RCAREA_HYRULE_FIELD, + RCAREA_LON_LON_RANCH, + RCAREA_LAKE_HYLIA, + RCAREA_GERUDO_VALLEY, + RCAREA_WASTELAND, + RCAREA_MARKET, + RCAREA_HYRULE_CASTLE, +}; + +RandomizerCheckArea GetCheckArea() { + auto scene = static_cast(gPlayState->sceneNum); + bool grottoScene = (scene == SCENE_GROTTOS || scene == SCENE_FAIRYS_FOUNTAIN); + const EntranceData* ent = GetEntranceData(grottoScene ? ENTRANCE_RANDO_GROTTO_EXIT_START + GetCurrentGrottoId() : gSaveContext.entranceIndex); + RandomizerCheckArea area = RCAREA_INVALID; + if (ent != nullptr && !IsAreaScene(scene) && ent->type != ENTRANCE_TYPE_DUNGEON) { + if (ent->source == "Desert Colossus" || ent->destination == "Desert Colossus") { + area = RCAREA_DESERT_COLOSSUS; + } else if (ent->source == "Gerudo Fortress" || ent->destination == "Gerudo Fortress") { + area = RCAREA_GERUDO_FORTRESS; + } else { + area = AreaFromEntranceGroup[ent->dstGroup]; + } + } + if (area == RCAREA_INVALID) { + if (grottoScene && (GetCurrentGrottoId() == -1) && (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_GROTTO_ENTRANCES) == RO_GENERIC_OFF)) { + area = previousArea; + } else { + area = RandomizerCheckObjects::GetRCAreaBySceneID(scene); + } + } + return area; +} + +bool vector_contains_scene(std::vector vec, const int16_t scene) { + return std::any_of(vec.begin(), vec.end(), [&](const auto& x) { return x == scene; }); +} + +std::vector skipScenes = {SCENE_GANON_BOSS, SCENE_GANONS_TOWER_COLLAPSE_EXTERIOR, SCENE_GANON_BOSS, SCENE_INSIDE_GANONS_CASTLE_COLLAPSE, SCENE_GANONS_TOWER_COLLAPSE_INTERIOR}; + +bool EvaluateCheck(RandomizerCheckObject rco) { + if (HasItemBeenCollected(rco.rc) && gSaveContext.checkTrackerData[rco.rc].status != RCSHOW_COLLECTED && + gSaveContext.checkTrackerData[rco.rc].status != RCSHOW_SAVED) { + SetCheckCollected(rco.rc); + return true; + } + return false; +} + +bool CheckByArea(RandomizerCheckArea area = RCAREA_INVALID) { + if (area == RCAREA_INVALID) { + area = checkAreas.front(); + } + if (area != RCAREA_INVALID) { + auto areaChecks = checksByArea.find(area)->second; + if (checkCounter >= areaChecks.size()) { + checkCounter = 0; + checkLoops++; + } + auto rco = areaChecks.at(checkCounter); + return EvaluateCheck(rco); + } +} + +void SetShopSeen(uint32_t sceneNum, bool prices) { + RandomizerCheck start = startingShopItem.find(sceneNum)->second; + if (GetCheckArea() == RCAREA_KAKARIKO_VILLAGE && sceneNum == SCENE_BAZAAR) { + start = RC_KAK_BAZAAR_ITEM_1; + } + bool statusChanged = false; + for (int i = start; i < start + 8; i++) { + if (gSaveContext.checkTrackerData[i].status == RCSHOW_UNCHECKED) { + gSaveContext.checkTrackerData[i].status = RCSHOW_SEEN; + statusChanged = true; + } + } + if (statusChanged) { + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); + } +} + +bool HasItemBeenCollected(RandomizerCheck rc) { + if (gPlayState == nullptr) { + return false; + } + ItemLocation* x = Location(rc); + SpoilerCollectionCheck check = x->GetCollectionCheck(); + auto flag = check.flag; + auto scene = check.scene; + auto type = check.type; + + switch (type) { + case SpoilerCollectionCheckType::SPOILER_CHK_ALWAYS_COLLECTED: + return true; + case SpoilerCollectionCheckType::SPOILER_CHK_CHEST: + return (gPlayState->sceneNum == scene && gPlayState->actorCtx.flags.chest & (1 << flag)) || + gSaveContext.sceneFlags[scene].chest & (1 << flag); + case SpoilerCollectionCheckType::SPOILER_CHK_COLLECTABLE: + return (gPlayState->sceneNum == scene && gPlayState->actorCtx.flags.collect & (1 << flag)) || + gSaveContext.sceneFlags[scene].collect & (1 << flag); + case SpoilerCollectionCheckType::SPOILER_CHK_MERCHANT: + case SpoilerCollectionCheckType::SPOILER_CHK_SHOP_ITEM: + case SpoilerCollectionCheckType::SPOILER_CHK_COW: + case SpoilerCollectionCheckType::SPOILER_CHK_SCRUB: + case SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF: + return Flags_GetRandomizerInf(OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(rc)); + case SpoilerCollectionCheckType::SPOILER_CHK_EVENT_CHK_INF: + return gSaveContext.eventChkInf[flag / 16] & (0x01 << flag % 16); + case SpoilerCollectionCheckType::SPOILER_CHK_GERUDO_MEMBERSHIP_CARD: + return CHECK_FLAG_ALL(gSaveContext.eventChkInf[0x09], 0x0F); + case SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA: + return GET_GS_FLAGS(scene) & flag; + case SpoilerCollectionCheckType::SPOILER_CHK_INF_TABLE: + return gSaveContext.infTable[scene] & INDEX_TO_16BIT_LITTLE_ENDIAN_BITMASK(flag); + case SpoilerCollectionCheckType::SPOILER_CHK_ITEM_GET_INF: + return gSaveContext.itemGetInf[flag / 16] & INDEX_TO_16BIT_LITTLE_ENDIAN_BITMASK(flag); + case SpoilerCollectionCheckType::SPOILER_CHK_MAGIC_BEANS: + return BEANS_BOUGHT >= 10; + case SpoilerCollectionCheckType::SPOILER_CHK_NONE: + return false; + case SpoilerCollectionCheckType::SPOILER_CHK_GRAVEDIGGER: + // Gravedigger has a fix in place that means one of two save locations. Check both. + return (gSaveContext.itemGetInf[1] & 0x1000) || // vanilla flag + ((IS_RANDO || CVarGetInteger("gGravediggingTourFix", 0)) && + gSaveContext.sceneFlags[scene].collect & (1 << flag) || (gPlayState->actorCtx.flags.collect & (1 << flag))); // rando/fix flag + default: + return false; + } + return false; +} + +void CheckTrackerDialogClosed() { + if (messageCloseCheck) { + messageCloseCheck = false; + } +} + +void CheckTrackerShopSlotChange(uint8_t cursorSlot, int16_t basePrice) { + if (gPlayState->sceneNum == SCENE_HAPPY_MASK_SHOP) { // Happy Mask Shop is not used in rando, so is not tracked + return; + } + + auto slot = startingShopItem.find(gPlayState->sceneNum)->second + cursorSlot; + if (GetCheckArea() == RCAREA_KAKARIKO_VILLAGE && gPlayState->sceneNum == SCENE_BAZAAR) { + slot = RC_KAK_BAZAAR_ITEM_1 + cursorSlot; + } + auto status = gSaveContext.checkTrackerData[slot].status; + if (status == RCSHOW_SEEN) { + gSaveContext.checkTrackerData[slot].status = RCSHOW_IDENTIFIED; + gSaveContext.checkTrackerData[slot].price = basePrice; + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); + } +} + +void CheckTrackerTransition(uint32_t sceneNum) { + if (!GameInteractor::IsSaveLoaded()) { + return; + } + gSaveContext; + if (transitionCheck) { + transitionCheck = false; + } + doAreaScroll = true; + previousArea = currentArea; + currentArea = GetCheckArea(); + switch (sceneNum) { + case SCENE_KOKIRI_SHOP: + case SCENE_BAZAAR: + case SCENE_POTION_SHOP_MARKET: + case SCENE_BOMBCHU_SHOP: + case SCENE_POTION_SHOP_KAKARIKO: + case SCENE_GORON_SHOP: + case SCENE_ZORA_SHOP: + SetShopSeen(sceneNum, false); + break; + } +} + +void CheckTrackerFrame() { + if (!GameInteractor::IsSaveLoaded()) { + return; + } + if (!checkAreas.empty() && !transitionCheck && !messageCloseCheck && !pendingSaleCheck) { + for (int i = 0; i < 10; i++) { + if (CheckByArea()) { + checkCounter = 0; + break; + } else { + checkCounter++; + } + } + if (checkLoops > 15) { + checkAreas.erase(checkAreas.begin()); + checkLoops = 0; + } + } + if (savedFrames > 0 && !pendingSaleCheck && !messageCloseCheck) { + savedFrames--; + } +} + +void CheckTrackerSaleEnd(GetItemEntry giEntry) { + if (pendingSaleCheck) { + pendingSaleCheck = false; + } +} + +void CheckTrackerItemReceive(GetItemEntry giEntry) { + if (!GameInteractor::IsSaveLoaded() || vector_contains_scene(skipScenes, gPlayState->sceneNum)) { + return; + } + auto scene = static_cast(gPlayState->sceneNum); + // Vanilla special item checks + if (!IS_RANDO) { + if (giEntry.itemId == ITEM_SHIELD_DEKU) { + SetCheckCollected(RC_KF_SHOP_ITEM_3); + return; + }else if (giEntry.itemId == ITEM_KOKIRI_EMERALD) { + SetCheckCollected(RC_QUEEN_GOHMA); + return; + } else if (giEntry.itemId == ITEM_GORON_RUBY) { + SetCheckCollected(RC_KING_DODONGO); + return; + } else if (giEntry.itemId == ITEM_ZORA_SAPPHIRE) { + SetCheckCollected(RC_BARINADE); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_FOREST) { + SetCheckCollected(RC_PHANTOM_GANON); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_FIRE) { + SetCheckCollected(RC_VOLVAGIA); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_WATER) { + SetCheckCollected(RC_MORPHA); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_SHADOW) { + SetCheckCollected(RC_BONGO_BONGO); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_SPIRIT) { + SetCheckCollected(RC_TWINROVA); + return; + } else if (giEntry.itemId == ITEM_MEDALLION_LIGHT) { + SetCheckCollected(RC_GIFT_FROM_SAGES); + return; + } else if (giEntry.itemId == ITEM_SONG_LULLABY) { + SetCheckCollected(RC_SONG_FROM_IMPA); + return; + } else if (giEntry.itemId == ITEM_SONG_EPONA) { + SetCheckCollected(RC_SONG_FROM_MALON); + return; + } else if (giEntry.itemId == ITEM_SONG_SARIA) { + SetCheckCollected(RC_SONG_FROM_SARIA); + return; + } else if (giEntry.itemId == ITEM_SONG_SUN) { + SetCheckCollected(RC_SONG_FROM_ROYAL_FAMILYS_TOMB); + return; + } else if (giEntry.itemId == ITEM_SONG_TIME) { + SetCheckCollected(RC_SONG_FROM_OCARINA_OF_TIME); + return; + } else if (giEntry.itemId == ITEM_SONG_STORMS) { + SetCheckCollected(RC_SONG_FROM_WINDMILL); + return; + } else if (giEntry.itemId == ITEM_SONG_MINUET) { + SetCheckCollected(RC_SHEIK_IN_FOREST); + return; + } else if (giEntry.itemId == ITEM_SONG_BOLERO) { + SetCheckCollected(RC_SHEIK_IN_CRATER); + return; + } else if (giEntry.itemId == ITEM_SONG_SERENADE) { + SetCheckCollected(RC_SHEIK_IN_ICE_CAVERN); + return; + } else if (giEntry.itemId == ITEM_SONG_NOCTURNE) { + SetCheckCollected(RC_SHEIK_IN_KAKARIKO); + return; + } else if (giEntry.itemId == ITEM_SONG_REQUIEM) { + SetCheckCollected(RC_SHEIK_AT_COLOSSUS); + return; + } else if (giEntry.itemId == ITEM_SONG_PRELUDE) { + SetCheckCollected(RC_SHEIK_AT_TEMPLE); + return; + } else if (giEntry.itemId == ITEM_BRACELET) { + SetCheckCollected(RC_GC_DARUNIAS_JOY); + return; + } else if (giEntry.itemId == ITEM_LETTER_ZELDA) { + SetCheckCollected(RC_HC_ZELDAS_LETTER); + return; + } else if (giEntry.itemId == ITEM_WEIRD_EGG) { + SetCheckCollected(RC_HC_MALON_EGG); + return; + } else if (giEntry.itemId == ITEM_BEAN) { + SetCheckCollected(RC_ZR_MAGIC_BEAN_SALESMAN); + return; + } + } + auto checkArea = GetCheckArea(); + if (gSaveContext.pendingSale != ITEM_NONE) { + pendingSaleCheck = true; + checkAreas.push_back(checkArea); + return; + } + if (scene == SCENE_DESERT_COLOSSUS && (gSaveContext.entranceIndex == 485 || gSaveContext.entranceIndex == 489)) { + checkAreas.push_back(RCAREA_SPIRIT_TEMPLE); + return; + } + if (GET_PLAYER(gPlayState) == nullptr) { + transitionCheck = true; + return; + } + if (gPlayState->msgCtx.msgMode != MSGMODE_NONE) { + checkAreas.push_back(checkArea); + messageCloseCheck = true; + return; + } + if (IS_RANDO || (!IS_RANDO && giEntry.getItemCategory != ITEM_CATEGORY_JUNK)) { + checkAreas.push_back(checkArea); + checkCollected = true; + } +} + +void InitTrackerData(bool isDebug) { + TrySetAreas(); + for (auto& [rc, rco] : RandomizerCheckObjects::GetAllRCObjects()) { + if (rc != RC_UNKNOWN_CHECK && rc != RC_MAX) { + DefaultCheckData(rc); + } + } + UpdateAllOrdering(); + UpdateInventoryChecks(); +} + +void SaveTrackerData(SaveContext* saveContext, int sectionID, bool gameSave) { + SaveManager::Instance->SaveArray("checks", ARRAY_COUNT(saveContext->checkTrackerData), [&](size_t i) { + if (saveContext->checkTrackerData[i].status == RCSHOW_COLLECTED) { + if (gameSave || savedFrames > 0) { + gSaveContext.checkTrackerData[i].status = saveContext->checkTrackerData[i].status = RCSHOW_SAVED; + UpdateAllOrdering(); + UpdateInventoryChecks(); + } else { + saveContext->checkTrackerData[i].status = RCSHOW_SCUMMED; + } + } + SaveManager::Instance->SaveStruct("", [&]() { + SaveManager::Instance->SaveData("status", saveContext->checkTrackerData[i].status); + SaveManager::Instance->SaveData("skipped", saveContext->checkTrackerData[i].skipped); + SaveManager::Instance->SaveData("price", saveContext->checkTrackerData[i].price); + SaveManager::Instance->SaveData("hintItem", saveContext->checkTrackerData[i].hintItem); + }); + }); +} + +void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) { + SaveTrackerData(saveContext, sectionID, fullSave); + if (fullSave) { + savedFrames = 40; + } +} + +void LoadFile() { + Teardown(); + LoadSettings(); + TrySetAreas(); + SaveManager::Instance->LoadArray("checks", RC_MAX, [](size_t i) { + SaveManager::Instance->LoadStruct("", [&]() { + SaveManager::Instance->LoadData("status", gSaveContext.checkTrackerData[i].status); + SaveManager::Instance->LoadData("skipped", gSaveContext.checkTrackerData[i].skipped); + SaveManager::Instance->LoadData("price", gSaveContext.checkTrackerData[i].price); + SaveManager::Instance->LoadData("hintItem", gSaveContext.checkTrackerData[i].hintItem); + }); + RandomizerCheckTrackerData entry = gSaveContext.checkTrackerData[i]; + RandomizerCheck rc = static_cast(i); + if (rc == RC_UNKNOWN_CHECK || rc == RC_MAX || + !RandomizerCheckObjects::GetAllRCObjects().contains(rc)) + return; + + RandomizerCheckObject entry2; + if (rc == RC_GIFT_FROM_SAGES && !IS_RANDO) { + entry2 = RCO_RAORU; + } else { + entry2 = RandomizerCheckObjects::GetAllRCObjects().find(rc)->second; + } + if (!IsVisibleInCheckTracker(entry2)) return; + + checksByArea.find(entry2.rcArea)->second.push_back(entry2); + if (entry.status == RCSHOW_SAVED || entry.skipped) { + areaChecksGotten[entry2.rcArea]++; + } + + if (areaChecksGotten[entry2.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(entry2.rcArea)) { + areasSpoiled |= (1 << entry2.rcArea); + } + }); + if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING && IS_RANDO) { + s8 startingAge = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_STARTING_AGE); + RandomizerCheckArea startingArea; + switch (startingAge) { + case RO_AGE_CHILD: + startingArea = RCAREA_KOKIRI_FOREST; + break; + case RO_AGE_ADULT: + startingArea = RCAREA_MARKET; + break; + default: + startingArea = RCAREA_KOKIRI_FOREST; + break; + } + RandomizerCheckObject linksPocket = { RC_LINKS_POCKET, RCVORMQ_BOTH, RCTYPE_LINKS_POCKET, startingArea, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, false, "Link's Pocket", "Link's Pocket" }; + + checksByArea.find(startingArea)->second.push_back(linksPocket); + areaChecksGotten[startingArea]++; + } + + showVOrMQ = (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_RANDOM_NUMBER || + (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER && + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) < 12)); + LinksPocket(); + SongFromImpa(); + GiftFromSages(); + initialized = true; + UpdateAllOrdering(); + UpdateInventoryChecks(); +} + +void Teardown() { + initialized = false; + for (auto& [rcArea, vec] : checksByArea) { + vec.clear(); + areaChecksGotten[rcArea] = 0; + } + checksByArea.clear(); + areasSpoiled = 0; + checkCollected = false; + checkLoops = 0; + + lastLocationChecked = RC_UNKNOWN_CHECK; +} + +void UpdateCheck(uint32_t check, RandomizerCheckTrackerData data) { + auto area = RandomizerCheckObjects::GetAllRCObjects().find(static_cast(check))->second.rcArea; + if (!gSaveContext.checkTrackerData[check].skipped && data.skipped) { + areaChecksGotten[area]++; + } else if (gSaveContext.checkTrackerData[check].skipped && !data.skipped) { + areaChecksGotten[area]--; + } + gSaveContext.checkTrackerData[check] = data; + UpdateOrdering(area); +} + void CheckTrackerWindow::DrawElement() { ImGui::SetNextWindowSize(ImVec2(400, 540), ImGuiCond_FirstUseEver); - if (doInitialize) { - Teardown(); - InitializeChecks(); - } else if (initialized && (gPlayState == nullptr || gSaveContext.fileNum < 0 || gSaveContext.fileNum > 2)) { - Teardown(); + if (!initialized && (gPlayState == nullptr || gSaveContext.fileNum < 0 || gSaveContext.fileNum > 2)) { return; } @@ -139,7 +793,7 @@ void CheckTrackerWindow::DrawElement() { BeginFloatWindows("Check Tracker", mIsVisible, ImGuiWindowFlags_NoScrollbar); - if (!initialized) { + if (!GameInteractor::IsSaveLoaded()) { ImGui::Text("Waiting for file load..."); //TODO Language EndFloatWindows(); return; @@ -148,26 +802,10 @@ void CheckTrackerWindow::DrawElement() { SceneID sceneId = SCENE_ID_MAX; if (gPlayState != nullptr) { sceneId = (SceneID)gPlayState->sceneNum; - currentArea = RandomizerCheckObjects::GetRCAreaBySceneID(sceneId); } - bool doAreaScroll = - (currentArea != RCAREA_INVALID && currentArea != previousArea && - sceneId != SCENE_GROTTOS && // Don't move for grottos - sceneId != SCENE_FAIRYS_FOUNTAIN && sceneId != SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS && sceneId != SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC && // Don't move for fairy fountains - sceneId != SCENE_BAZAAR && sceneId != SCENE_SHOOTING_GALLERY // Don't move for Bazaar/Gallery, as it moves between Kak and Market - ); - previousArea = currentArea; areasSpoiled |= (1 << currentArea); - //Only update the checks if something has changed - if (ShouldUpdateChecks()) { - UpdateChecks(); - UpdateInventoryChecks(); - UpdateOrdering(); - } - - //Quick Options #ifdef __WIIU__ float headerHeight = 40.0f; @@ -186,10 +824,11 @@ void CheckTrackerWindow::DrawElement() { UIWidgets::EnhancementCheckbox( "Show Hidden Items", "gCheckTrackerOptionShowHidden", false, "When active, items will show hidden checks by default when updated to this state."); - ImGui::SameLine(); + UIWidgets::PaddedSeparator(); if (ImGui::Button("Expand All")) { optCollapseAll = false; optExpandAll = true; + doAreaScroll = true; } ImGui::SameLine(); if (ImGui::Button("Collapse All")) { @@ -230,93 +869,89 @@ void CheckTrackerWindow::DrawElement() { std::string stemp; s32 areaMask = 1; - // Logic for each check - for (auto& obj : checks) - { + for (auto& [rcArea, objs] : checksByArea) { + RandomizerCheckArea thisArea = currentArea; - //New Area to be drawn - if (obj.rcArea != lastArea) - { - //Last Area needs to be cleaned up - if (lastArea != RCAREA_INVALID && doDraw) { - ImGui::TreePop(); - UIWidgets::PaddedSeparator(); - } - lastArea = obj.rcArea; - - //Decide if we should skip because of hiding rules - thisAreaFullyChecked = ((areasFullyChecked & areaMask) != 0); - if (!showHidden && ( - hideComplete && thisAreaFullyChecked || - hideIncomplete && !thisAreaFullyChecked - )) { - doDraw = false; - } - else { - //Get the colour for the area - if (thisAreaFullyChecked) { - mainColor = areaCompleteColor; - extraColor = extraCompleteColor; - } else { - mainColor = areaIncompleteColor; - extraColor = extraIncompleteColor; - } - - //Draw the area - collapseLogic = !thisAreaFullyChecked; - if (doingCollapseOrExpand) { - if (optExpandAll) { - collapseLogic = true; - } else if (optCollapseAll) { - collapseLogic = false; - } - } - stemp = RandomizerCheckObjects::GetRCAreaName(obj.rcArea) + "##TreeNode"; - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(mainColor.r / 255.0f, mainColor.g / 255.0f, - mainColor.b / 255.0f, mainColor.a / 255.0f)); - if (doingCollapseOrExpand) - ImGui::SetNextItemOpen(collapseLogic, ImGuiCond_Always); - else - ImGui::SetNextItemOpen(!thisAreaFullyChecked, ImGuiCond_Once); - doDraw = ImGui::TreeNode(stemp.c_str()); - ImGui::PopStyleColor(); - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f, - extraColor.b / 255.0f, extraColor.a / 255.0f)); - - isThisAreaSpoiled = areasSpoiled & areaMask || CVarGetInteger("gCheckTrackerOptionMQSpoilers", 0); - - if (isThisAreaSpoiled) { - if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(obj.rcArea)) { - if (OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(DungeonSceneLookupByArea(obj.rcArea))) - ImGui::Text("(%d/%d) - MQ", areaChecksGotten[obj.rcArea], areaChecksTotal[obj.rcArea]); - else - ImGui::Text("(%d/%d) - Vanilla", areaChecksGotten[obj.rcArea], areaChecksTotal[obj.rcArea]); - } else { - ImGui::Text("(%d/%d)", areaChecksGotten[obj.rcArea], areaChecksTotal[obj.rcArea]); - } - } else { - ImGui::Text("???", areaChecksGotten[obj.rcArea], areaChecksTotal[obj.rcArea]); - } - - ImGui::PopStyleColor(); - - //Keep areas loaded between transitions - if (currentArea == obj.rcArea && doAreaScroll) - ImGui::SetScrollHereY(0.0f); - } - - areaMask <<= 1; + const int areaChecksTotal = static_cast(objs.size()); + thisAreaFullyChecked = (areaChecksGotten[rcArea] == areaChecksTotal); + //Last Area needs to be cleaned up + if (lastArea != RCAREA_INVALID && doDraw) { + UIWidgets::PaddedSeparator(); } + lastArea = rcArea; + if (previousShowHidden != showHidden) { + previousShowHidden = showHidden; + doAreaScroll = true; + } + if (!showHidden && ( + hideComplete && thisAreaFullyChecked || + hideIncomplete && !thisAreaFullyChecked + )) { + doDraw = false; + } else { + //Get the colour for the area + if (thisAreaFullyChecked) { + mainColor = areaCompleteColor; + extraColor = extraCompleteColor; + } else { + mainColor = areaIncompleteColor; + extraColor = extraIncompleteColor; + } - if (doDraw && isThisAreaSpoiled) - DrawLocation(obj, &checkStatusMap.find(obj.rc)->second); + //Draw the area + collapseLogic = !thisAreaFullyChecked; + if (doingCollapseOrExpand) { + if (optExpandAll) { + collapseLogic = true; + } else if (optCollapseAll) { + collapseLogic = false; + } + } + stemp = RandomizerCheckObjects::GetRCAreaName(rcArea) + "##TreeNode"; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(mainColor.r / 255.0f, mainColor.g / 255.0f, + mainColor.b / 255.0f, mainColor.a / 255.0f)); + if (doingCollapseOrExpand) + ImGui::SetNextItemOpen(collapseLogic, ImGuiCond_Always); + else + ImGui::SetNextItemOpen(!thisAreaFullyChecked, ImGuiCond_Once); + doDraw = ImGui::TreeNode(stemp.c_str()); + ImGui::PopStyleColor(); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f, + extraColor.b / 255.0f, extraColor.a / 255.0f)); + + isThisAreaSpoiled = areasSpoiled & areaMask || CVarGetInteger("gCheckTrackerOptionMQSpoilers", 0); + + if (isThisAreaSpoiled) { + if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(rcArea)) { + if (OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(DungeonSceneLookupByArea(rcArea))) + ImGui::Text("(%d/%d) - MQ", areaChecksGotten[rcArea], areaChecksTotal); + else + ImGui::Text("(%d/%d) - Vanilla", areaChecksGotten[rcArea], areaChecksTotal); + } else { + ImGui::Text("(%d/%d)", areaChecksGotten[rcArea], areaChecksTotal); + } + } else { + ImGui::Text("???"); + } + + ImGui::PopStyleColor(); + + //Keep areas loaded between transitions + if (thisArea == rcArea && doAreaScroll) { + ImGui::SetScrollHereY(0.0f); + doAreaScroll = false; + } + for (auto rco : objs) { + if (doDraw && isThisAreaSpoiled && IsVisibleInCheckTracker(rco)) + DrawLocation(rco); + } + if (doDraw) + ImGui::TreePop(); + } + areaMask <<= 1; } - //Clean up last area - if (doDraw) - ImGui::TreePop(); - ImGui::EndTable(); //Checks Lead-out ImGui::EndTable(); //Quick Options Lead-out EndFloatWindows(); @@ -357,30 +992,8 @@ void EndFloatWindows() { ImGui::End(); } -bool showShops; -bool showOverworldTokens; -bool showDungeonTokens; -bool showBeans; -bool showScrubs; -bool showMerchants; -bool showCows; -bool showAdultTrade; -bool showKokiriSword; -bool showWeirdEgg; -bool showGerudoCard; -bool showFrogSongRupees; -bool showStartingMapsCompasses; -bool showKeysanity; -bool showGerudoFortressKeys; -bool showBossKeysanity; -bool showGanonBossKey; -bool showOcarinas; -bool show100SkullReward; -bool fortressFast; -bool fortressNormal; - void LoadSettings() { - //If in randomzer, then get the setting and check if in general we should be showing the settings + //If in randomzer (n64ddFlag), then get the setting and check if in general we should be showing the settings //If in vanilla, _try_ to show items that at least are needed for 100% showShops = IS_RANDO ? ( @@ -435,6 +1048,10 @@ void LoadSettings() { show100SkullReward = IS_RANDO ? OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_100_GS_REWARD) == RO_GENERIC_YES : false; + showLinksPocket = IS_RANDO ? // don't show Link's Pocket if not randomizer, or if rando and pocket is disabled + OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING + :false; + hideShopRightChecks = IS_RANDO ? CVarGetInteger("gCheckTrackerOptionHideRightShopChecks", 1) : false; if (IS_RANDO) { switch (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_TOKENS)) { @@ -477,213 +1094,120 @@ void LoadSettings() { } bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj) { - return - (rcObj.rcArea != RCAREA_INVALID) && // don't show Invalid locations - (rcObj.rcType != RCTYPE_GOSSIP_STONE) && //TODO: Don't show hints until tracker supports them - (rcObj.rcType != RCTYPE_CHEST_GAME) && // don't show non final reward chest game checks until we support shuffling them - (rcObj.rc != RC_HC_ZELDAS_LETTER) && // don't show zeldas letter until we support shuffling it - (!RandomizerCheckObjects::AreaIsDungeon(rcObj.rcArea) || - rcObj.vOrMQ == RCVORMQ_BOTH || - rcObj.vOrMQ == RCVORMQ_MQ && OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) || - rcObj.vOrMQ == RCVORMQ_VANILLA && !OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) - ) && - (rcObj.rcType != RCTYPE_SHOP || showShops) && - (rcObj.rcType != RCTYPE_SCRUB || - showScrubs || - rcObj.rc == RC_LW_DEKU_SCRUB_NEAR_BRIDGE || // The 3 scrubs that are always randomized - rcObj.rc == RC_HF_DEKU_SCRUB_GROTTO || - rcObj.rc == RC_LW_DEKU_SCRUB_GROTTO_FRONT - ) && - (rcObj.rcType != RCTYPE_MERCHANT || showMerchants) && - (rcObj.rcType != RCTYPE_OCARINA || showOcarinas) && - (rcObj.rcType != RCTYPE_SKULL_TOKEN || - (showOverworldTokens && RandomizerCheckObjects::AreaIsOverworld(rcObj.rcArea)) || - (showDungeonTokens && RandomizerCheckObjects::AreaIsDungeon(rcObj.rcArea)) - ) && - (rcObj.rcType != RCTYPE_COW || showCows) && - (rcObj.rcType != RCTYPE_ADULT_TRADE || - showAdultTrade || - rcObj.rc == RC_KAK_ANJU_AS_ADULT || // adult trade checks that are always shuffled - rcObj.rc == RC_DMT_TRADE_CLAIM_CHECK // even when shuffle adult trade is off - ) && - (rcObj.rc != RC_KF_KOKIRI_SWORD_CHEST || showKokiriSword) && - (rcObj.rc != RC_ZR_MAGIC_BEAN_SALESMAN || showBeans) && - (rcObj.rc != RC_HC_MALON_EGG || showWeirdEgg) && - (rcObj.rcType != RCTYPE_FROG_SONG || showFrogSongRupees) && - (rcObj.rcType != RCTYPE_MAP_COMPASS || showStartingMapsCompasses) && - (rcObj.rcType != RCTYPE_SMALL_KEY || showKeysanity) && - (rcObj.rcType != RCTYPE_BOSS_KEY || showBossKeysanity) && - (rcObj.rcType != RCTYPE_GANON_BOSS_KEY || showGanonBossKey) && - (rcObj.rc != RC_KAK_100_GOLD_SKULLTULA_REWARD || show100SkullReward) && - (rcObj.rcType != RCTYPE_GF_KEY && rcObj.rc != RC_GF_GERUDO_MEMBERSHIP_CARD || - (showGerudoCard && rcObj.rc == RC_GF_GERUDO_MEMBERSHIP_CARD) || - (fortressNormal && showGerudoFortressKeys && rcObj.rcType == RCTYPE_GF_KEY) || - (fortressFast && showGerudoFortressKeys && rcObj.rc == RC_GF_NORTH_F1_CARPENTER) - ); -} - - -void InitializeChecks() { - if (gPlayState == nullptr || gSaveContext.fileNum < 0 || gSaveContext.fileNum > 2) - return; - - int count = 0; - - //Link's Pocket - if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING) { - s8 startingAge = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_STARTING_AGE); - RandomizerCheckArea startingArea; - switch (startingAge) { - case RO_AGE_CHILD: - startingArea = RCAREA_KOKIRI_FOREST; - break; - case RO_AGE_ADULT: - startingArea = RCAREA_MARKET; - break; - default: - startingArea = RCAREA_KOKIRI_FOREST; - break; - } - RandomizerCheckObject linksPocket = { RC_LINKS_POCKET, RCVORMQ_BOTH, RCTYPE_LINKS_POCKET, startingArea, ACTOR_ID_MAX, SCENE_ID_MAX, 0x00, GI_NONE, false, "Link's Pocket", "Link's Pocket" }; - - checks.push_back(linksPocket); - checkStatusMap.emplace(RC_LINKS_POCKET, RCSHOW_SAVED); - count++; - areaChecksTotal[startingArea]++; - areaChecksGotten[startingArea]++; - } - - LoadSettings(); - for (auto& [rcCheck, rcObj] : RandomizerCheckObjects::GetAllRCObjects()) { - if (!IsVisibleInCheckTracker(rcObj)) - continue; - - checks.push_back(rcObj); - checkStatusMap.emplace(rcObj.rc, RCSHOW_UNCHECKED); - count++; - areaChecksTotal[rcObj.rcArea]++; - - if (areaChecksGotten[rcObj.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(rcObj.rcArea)) - areasSpoiled |= (1 << rcObj.rcArea); - } - - showVOrMQ = (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_RANDOM_NUMBER || - (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER && - OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) < 12) + if (IS_RANDO) { + return + (rcObj.rcArea != RCAREA_INVALID) && // don't show Invalid locations + (rcObj.rcType != RCTYPE_GOSSIP_STONE) && //TODO: Don't show hints until tracker supports them + (rcObj.rcType != RCTYPE_CHEST_GAME) && // don't show non final reward chest game checks until we support shuffling them + (rcObj.rc != RC_HC_ZELDAS_LETTER) && // don't show zeldas letter until we support shuffling it + (rcObj.rc != RC_LINKS_POCKET || showLinksPocket) && + (!RandomizerCheckObjects::AreaIsDungeon(rcObj.rcArea) || + rcObj.vOrMQ == RCVORMQ_BOTH || + rcObj.vOrMQ == RCVORMQ_MQ && OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) || + rcObj.vOrMQ == RCVORMQ_VANILLA && !OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) + ) && + (rcObj.rcType != RCTYPE_SHOP || (showShops && (!hideShopRightChecks || hideShopRightChecks && rcObj.actorParams > 0x03))) && + (rcObj.rcType != RCTYPE_SCRUB || + showScrubs || + rcObj.rc == RC_LW_DEKU_SCRUB_NEAR_BRIDGE || // The 3 scrubs that are always randomized + rcObj.rc == RC_HF_DEKU_SCRUB_GROTTO || + rcObj.rc == RC_LW_DEKU_SCRUB_GROTTO_FRONT + ) && + (rcObj.rcType != RCTYPE_MERCHANT || showMerchants) && + (rcObj.rcType != RCTYPE_OCARINA || showOcarinas) && + (rcObj.rcType != RCTYPE_SKULL_TOKEN || + (showOverworldTokens && RandomizerCheckObjects::AreaIsOverworld(rcObj.rcArea)) || + (showDungeonTokens && RandomizerCheckObjects::AreaIsDungeon(rcObj.rcArea)) + ) && + (rcObj.rcType != RCTYPE_COW || showCows) && + (rcObj.rcType != RCTYPE_ADULT_TRADE || + showAdultTrade || + rcObj.rc == RC_KAK_ANJU_AS_ADULT || // adult trade checks that are always shuffled + rcObj.rc == RC_DMT_TRADE_CLAIM_CHECK // even when shuffle adult trade is off + ) && + (rcObj.rc != RC_KF_KOKIRI_SWORD_CHEST || showKokiriSword) && + (rcObj.rc != RC_ZR_MAGIC_BEAN_SALESMAN || showBeans) && + (rcObj.rc != RC_HC_MALON_EGG || showWeirdEgg) && + (rcObj.rcType != RCTYPE_FROG_SONG || showFrogSongRupees) && + (rcObj.rcType != RCTYPE_MAP_COMPASS || showStartingMapsCompasses) && + (rcObj.rcType != RCTYPE_SMALL_KEY || showKeysanity) && + (rcObj.rcType != RCTYPE_BOSS_KEY || showBossKeysanity) && + (rcObj.rcType != RCTYPE_GANON_BOSS_KEY || showGanonBossKey) && + (rcObj.rc != RC_KAK_100_GOLD_SKULLTULA_REWARD || show100SkullReward) && + (rcObj.rcType != RCTYPE_GF_KEY && rcObj.rc != RC_GF_GERUDO_MEMBERSHIP_CARD || + (showGerudoCard && rcObj.rc == RC_GF_GERUDO_MEMBERSHIP_CARD) || + (fortressNormal && showGerudoFortressKeys && rcObj.rcType == RCTYPE_GF_KEY) || + (fortressFast && showGerudoFortressKeys && rcObj.rc == RC_GF_NORTH_F1_CARPENTER) ); - - UpdateChecks(); - UpdateInventoryChecks(); - UpdateOrdering(true); - doInitialize = false; - initialized = true; -} - -void Teardown() { - initialized = false; - checkStatusMap.clear(); - areasFullyChecked = 0; - areasSpoiled = 0; - checks.clear(); - lastLocationChecked = RC_UNKNOWN_CHECK; - for (int i = 0; i < sizeof(areaChecksTotal); i++) { - areaChecksTotal[i] = 0; - areaChecksGotten[i] = 0; } - doInitialize = true; -} - -int slowCheckIdx = 0; -// Checks only one check every call -bool SlowUpdateCheck() { - bool ret = false; - auto checkIt = checks.begin() + slowCheckIdx; - if (checkIt == checks.end()) { - slowCheckIdx = 0; - return false; + else if (rcObj.vanillaCompletion) { + return (rcObj.vOrMQ == RCVORMQ_BOTH || + rcObj.vOrMQ == RCVORMQ_MQ && OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) || + rcObj.vOrMQ == RCVORMQ_VANILLA && !OTRGlobals::Instance->gRandomizer->masterQuestDungeons.contains(rcObj.sceneId) || + rcObj.rc == RC_GIFT_FROM_SAGES) && rcObj.rc != RC_LINKS_POCKET; } - - RandomizerCheckObject rcObj = *checkIt; - RandomizerCheckShow lastStatus = checkStatusMap.find(rcObj.rc)->second; - if (lastStatus != GetCheckStatus(rcObj, slowCheckIdx)) - ret = true; - - slowCheckIdx++; - return ret; -} - -bool ShouldUpdateChecks() { - // TODO eventually will need to be hooked into game elements rather than just save file - if (CVarGetInteger("gCheckTrackerOptionPerformanceMode", 0)) - return SlowUpdateCheck(); - else - return true; + return false; } void UpdateInventoryChecks() { //For all the areas with compasses, if you have one, spoil the area - for (u8 i = RCAREA_DEKU_TREE; i <= RCAREA_ICE_CAVERN; i++) - if (gSaveContext.inventory.dungeonItems[i - RCAREA_DEKU_TREE] & 0x02) - areasSpoiled |= (1 << i); + for (u8 i = SCENE_DEKU_TREE; i <= SCENE_GERUDO_TRAINING_GROUND; i++) + if (CHECK_DUNGEON_ITEM(DUNGEON_MAP, i)) + areasSpoiled |= (1 << RCAreaFromSceneID.at((SceneID)i)); } -void UpdateChecks() { - int idx = 0; - RandomizerCheckObject* lastCheck; - RandomizerCheckShow lastStatus; - for (auto& rcObj : checks) { - RandomizerCheckShow* checkStatusPtr = &checkStatusMap.find(rcObj.rc)->second; - lastStatus = *checkStatusPtr; - *checkStatusPtr = GetCheckStatus(rcObj, idx); - - //Update areasFullyChecked - if (lastStatus != *checkStatusPtr) { - if (lastStatus != RCSHOW_CHECKED && lastStatus != RCSHOW_SAVED && (*checkStatusPtr == RCSHOW_CHECKED || *checkStatusPtr == RCSHOW_SAVED)) - areaChecksGotten[rcObj.rcArea]++; - else if ((lastStatus == RCSHOW_CHECKED || lastStatus == RCSHOW_SAVED) && *checkStatusPtr != RCSHOW_CHECKED && *checkStatusPtr != RCSHOW_SAVED) - areaChecksGotten[rcObj.rcArea]--; - - if (areaChecksGotten[rcObj.rcArea] == areaChecksTotal[rcObj.rcArea]) - areasFullyChecked |= (1 << rcObj.rcArea); - else - areasFullyChecked &= (0xFFFFFFFF - (1 << rcObj.rcArea)); - - if (areaChecksGotten[rcObj.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(rcObj.rcArea)) - areasSpoiled |= (1 << rcObj.rcArea); - } - - lastCheck = &rcObj; - - idx++; - } +void UpdateAreaFullyChecked(RandomizerCheckArea area) { } -void UpdateOrdering(bool init) { +void UpdateAreas(RandomizerCheckArea area) { + areasFullyChecked[area] = areaChecksGotten[area] == checksByArea.find(area)->second.size(); + if (areaChecksGotten[area] != 0 || RandomizerCheckObjects::AreaIsOverworld(area)) + areasSpoiled |= (1 << area); +} + +void UpdateAllOrdering() { // Sort the entire thing - if (init) { - std::sort(checks.begin(), checks.end(), CompareCheckObject); - return; - } - - //sort each area individually - int startOffset = 0; - int endOffset = 0; - for (int x = 0; x < sizeof(areaChecksTotal); x++) { - endOffset = startOffset + areaChecksTotal[x]; - std::sort(checks.begin() + startOffset, checks.begin() + endOffset, CompareCheckObject); - startOffset += areaChecksTotal[x]; + for (int i = 0; i < RCAREA_INVALID; i++) { + UpdateOrdering(static_cast(i)); } } -bool CompareCheckObject(RandomizerCheckObject i, RandomizerCheckObject j) { - if (i.rcArea < j.rcArea) +void UpdateOrdering(RandomizerCheckArea rcArea) { + // Sort a single area + if(checksByArea.contains(rcArea)) { + std::sort(checksByArea.find(rcArea)->second.begin(), checksByArea.find(rcArea)->second.end(), CompareChecks); + } +} + +bool IsEoDCheck(RandomizerCheckType type) { + return type == RCTYPE_BOSS_HEART_OR_OTHER_REWARD || type == RCTYPE_DUNGEON_REWARD; +} + +bool CompareChecks(RandomizerCheckObject i, RandomizerCheckObject j) { + RandomizerCheckTrackerData iShow = gSaveContext.checkTrackerData[i.rc]; + RandomizerCheckTrackerData jShow = gSaveContext.checkTrackerData[j.rc]; + bool iCollected = iShow.status == RCSHOW_COLLECTED || iShow.status == RCSHOW_SAVED; + bool iSaved = iShow.status == RCSHOW_SAVED; + bool jCollected = jShow.status == RCSHOW_COLLECTED || jShow.status == RCSHOW_SAVED; + bool jSaved = jShow.status == RCSHOW_SAVED; + if (!iCollected && jCollected) return true; - else if (i.rcArea > j.rcArea) + else if (iCollected && !jCollected) return false; - if (checkStatusMap.find(i.rc)->second < checkStatusMap.find(j.rc)->second) + if (!iSaved && jSaved) return true; - else if (checkStatusMap.find(i.rc)->second > checkStatusMap.find(j.rc)->second) + else if (iSaved && !jSaved) + return false; + + if (!iShow.skipped && jShow.skipped) + return true; + else if (iShow.skipped && !jShow.skipped) + return false; + + if (!IsEoDCheck(i.rcType) && IsEoDCheck(j.rcType)) + return true; + else if (IsEoDCheck(i.rcType) && !IsEoDCheck(j.rcType)) return false; if (i.rc < j.rc) @@ -694,108 +1218,54 @@ bool CompareCheckObject(RandomizerCheckObject i, RandomizerCheckObject j) { return false; } -RandomizerCheckShow GetCheckStatus(RandomizerCheckObject rcObj, int idx) { - if (HasItemBeenCollected(rcObj)) - return RCSHOW_SAVED; // TODO: use SAVED until we hook into game elements without requiring a save. Then we'll use CHECKED - - if (HasItemBeenSkipped(rcObj)) - return RCSHOW_SKIPPED; - - return RCSHOW_UNCHECKED; - - // TODO Seen, Hinted, Scummed, saved/checked +bool IsHeartPiece(GetItemID giid) { + return giid == GI_HEART_PIECE || giid == GI_HEART_PIECE_WIN; } -bool HasItemBeenSkipped(RandomizerCheckObject obj) { - return gSaveContext.sohStats.locationsSkipped[obj.rc] == 1; -} - -bool HasItemBeenCollected(RandomizerCheckObject obj) { - ItemLocation* x = Location(obj.rc); - SpoilerCollectionCheck check = x->GetCollectionCheck(); - auto flag = check.flag; - auto scene = check.scene; - auto type = check.type; - - switch (type) { - case SpoilerCollectionCheckType::SPOILER_CHK_ALWAYS_COLLECTED: - return true; - case SpoilerCollectionCheckType::SPOILER_CHK_CHEST: - return gSaveContext.sceneFlags[scene].chest & (1 << flag); - case SpoilerCollectionCheckType::SPOILER_CHK_COLLECTABLE: - return gSaveContext.sceneFlags[scene].collect & (1 << flag); - case SpoilerCollectionCheckType::SPOILER_CHK_MERCHANT: - case SpoilerCollectionCheckType::SPOILER_CHK_SHOP_ITEM: - case SpoilerCollectionCheckType::SPOILER_CHK_COW: - case SpoilerCollectionCheckType::SPOILER_CHK_SCRUB: - case SpoilerCollectionCheckType::SPOILER_CHK_RANDOMIZER_INF: - return Flags_GetRandomizerInf(OTRGlobals::Instance->gRandomizer->GetRandomizerInfFromCheck(obj.rc)); - case SpoilerCollectionCheckType::SPOILER_CHK_EVENT_CHK_INF: - return gSaveContext.eventChkInf[flag / 16] & (0x01 << flag % 16); - case SpoilerCollectionCheckType::SPOILER_CHK_GERUDO_MEMBERSHIP_CARD: - return CHECK_FLAG_ALL(gSaveContext.eventChkInf[0x09], 0x0F); - case SpoilerCollectionCheckType::SPOILER_CHK_GOLD_SKULLTULA: - return GET_GS_FLAGS(scene) & flag; - case SpoilerCollectionCheckType::SPOILER_CHK_INF_TABLE: - return gSaveContext.infTable[scene] & INDEX_TO_16BIT_LITTLE_ENDIAN_BITMASK(flag); - case SpoilerCollectionCheckType::SPOILER_CHK_ITEM_GET_INF: - return gSaveContext.itemGetInf[flag / 16] & INDEX_TO_16BIT_LITTLE_ENDIAN_BITMASK(flag); - case SpoilerCollectionCheckType::SPOILER_CHK_MAGIC_BEANS: - return BEANS_BOUGHT >= 10; - case SpoilerCollectionCheckType::SPOILER_CHK_NONE: - return false; - case SpoilerCollectionCheckType::SPOILER_CHK_GRAVEDIGGER: - // Gravedigger has a fix in place that means one of two save locations. Check both. - return (gSaveContext.itemGetInf[1] & 0x1000) || // vanilla flag - ((IS_RANDO || CVarGetInteger("gGravediggingTourFix", 0)) && - gSaveContext.sceneFlags[scene].collect & (1 << flag)); // rando/fix flag - default: - return false; - } - return false; -} - -void DrawLocation(RandomizerCheckObject rcObj, RandomizerCheckShow* thisCheckStatus) { - Color_RGBA8 mainColor; +void DrawLocation(RandomizerCheckObject rcObj) { + Color_RGBA8 mainColor; Color_RGBA8 extraColor; std::string txt; bool showHidden = CVarGetInteger("gCheckTrackerOptionShowHidden", 0); - - if (*thisCheckStatus == RCSHOW_UNCHECKED) { - if (!showHidden && CVarGetInteger("gCheckTrackerUncheckedHide", 0)) + RandomizerCheckTrackerData checkData = gSaveContext.checkTrackerData[rcObj.rc]; + RandomizerCheckStatus status = checkData.status; + bool skipped = checkData.skipped; + if (status == RCSHOW_COLLECTED) { + if (!showHidden && CVarGetInteger("gCheckTrackerCollectedHide", 0)) return; - mainColor = CVarGetColor("gCheckTrackerUncheckedMainColor", Color_Main_Default); - extraColor = CVarGetColor("gCheckTrackerUncheckedExtraColor", Color_Unchecked_Extra_Default); - } else if (*thisCheckStatus == RCSHOW_SKIPPED) { - if (!showHidden && CVarGetInteger("gCheckTrackerSkippedHide", 0)) - return; - mainColor = CVarGetColor("gCheckTrackerSkippedMainColor", Color_Main_Default); - extraColor = CVarGetColor("gCheckTrackerSkippedExtraColor", Color_Skipped_Extra_Default); - } else if (*thisCheckStatus == RCSHOW_SEEN) { - if (!showHidden && CVarGetInteger("gCheckTrackerSeenHide", 0)) - return; - mainColor = CVarGetColor("gCheckTrackerSeenMainColor", Color_Main_Default); - extraColor = CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default); - } else if (*thisCheckStatus == RCSHOW_HINTED) { - if (!showHidden && CVarGetInteger("gCheckTrackerHintedHide", 0)) - return; - mainColor = CVarGetColor("gCheckTrackerHintedMainColor", Color_Main_Default); - extraColor = CVarGetColor("gCheckTrackerHintedExtraColor", Color_Hinted_Extra_Default); - } else if (*thisCheckStatus == RCSHOW_CHECKED) { - if (!showHidden && CVarGetInteger("gCheckTrackerCheckedHide", 0)) - return; - mainColor = CVarGetColor("gCheckTrackerCheckedMainColor", Color_Main_Default); - extraColor = CVarGetColor("gCheckTrackerCheckedExtraColor", Color_Checked_Extra_Default); - } else if (*thisCheckStatus == RCSHOW_SCUMMED) { - if (!showHidden && CVarGetInteger("gCheckTrackerScummedHide", 0)) - return; - mainColor = CVarGetColor("gCheckTrackerScummedMainColor", Color_Main_Default); - extraColor = CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default); - } else if (*thisCheckStatus == RCSHOW_SAVED) { + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerCollectedExtraColor", Color_Collected_Extra_Default) : + CVarGetColor("gCheckTrackerCollectedMainColor", Color_Main_Default); + extraColor = CVarGetColor("gCheckTrackerCollectedExtraColor", Color_Collected_Extra_Default); + } else if (status == RCSHOW_SAVED) { if (!showHidden && CVarGetInteger("gCheckTrackerSavedHide", 0)) return; - mainColor = CVarGetColor("gCheckTrackerSavedMainColor", Color_Main_Default); + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSavedExtraColor", Color_Saved_Extra_Default) : + CVarGetColor("gCheckTrackerSavedMainColor", Color_Main_Default); extraColor = CVarGetColor("gCheckTrackerSavedExtraColor", Color_Saved_Extra_Default); + } else if (skipped) { + if (!showHidden && CVarGetInteger("gCheckTrackerSkippedHide", 0)) + return; + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSkippedExtraColor", Color_Skipped_Extra_Default) : + CVarGetColor("gCheckTrackerSkippedMainColor", Color_Main_Default); + extraColor = CVarGetColor("gCheckTrackerSkippedExtraColor", Color_Skipped_Extra_Default); + } else if (status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED) { + if (!showHidden && CVarGetInteger("gCheckTrackerSeenHide", 0)) + return; + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default) : + CVarGetColor("gCheckTrackerSeenMainColor", Color_Main_Default); + extraColor = CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default); + } else if (status == RCSHOW_SCUMMED) { + if (!showHidden && CVarGetInteger("gCheckTrackerKnownHide", 0)) + return; + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default) : + CVarGetColor("gCheckTrackerScummedMainColor", Color_Main_Default); + extraColor = CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default); + } else if (status == RCSHOW_UNCHECKED) { + if (!showHidden && CVarGetInteger("gCheckTrackerUncheckedHide", 0)) + return; + mainColor = !IsHeartPiece(rcObj.ogItemId) && !IS_RANDO ? CVarGetColor("gCheckTrackerUncheckedExtraColor", Color_Unchecked_Extra_Default) : + CVarGetColor("gCheckTrackerUncheckedMainColor", Color_Main_Default); + extraColor = CVarGetColor("gCheckTrackerUncheckedExtraColor", Color_Unchecked_Extra_Default); } //Main Text @@ -803,17 +1273,19 @@ void DrawLocation(RandomizerCheckObject rcObj, RandomizerCheckShow* thisCheckSta if (lastLocationChecked == rcObj.rc) txt = "* " + txt; - // Draw button - for Skipped/Unchecked only - if (*thisCheckStatus == RCSHOW_UNCHECKED || *thisCheckStatus == RCSHOW_SKIPPED) { - bool skipped = (*thisCheckStatus == RCSHOW_SKIPPED); - if (ImGui::ArrowButton(std::to_string(rcObj.rc).c_str(), skipped ? ImGuiDir_Left : ImGuiDir_Right)) { + // Draw button - for Skipped/Seen/Scummed/Unchecked only + if (status == RCSHOW_UNCHECKED || status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED || status == RCSHOW_SCUMMED || skipped) { + if (UIWidgets::StateButton(std::to_string(rcObj.rc).c_str(), skipped ? ICON_FA_PLUS : ICON_FA_TIMES)) { if (skipped) { - gSaveContext.sohStats.locationsSkipped[rcObj.rc] = 0; - *thisCheckStatus = RCSHOW_UNCHECKED; + gSaveContext.checkTrackerData[rcObj.rc].skipped = false; + areaChecksGotten[rcObj.rcArea]--; } else { - gSaveContext.sohStats.locationsSkipped[rcObj.rc] = 1; - *thisCheckStatus = RCSHOW_SKIPPED; + gSaveContext.checkTrackerData[rcObj.rc].skipped = true; + areaChecksGotten[rcObj.rcArea]++; } + UpdateOrdering(rcObj.rcArea); + UpdateInventoryChecks(); + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); } } else { ImGui::InvisibleButton("", ImVec2(20.0f, 10.0f)); @@ -826,42 +1298,63 @@ void DrawLocation(RandomizerCheckObject rcObj, RandomizerCheckShow* thisCheckSta ImGui::PopStyleColor(); //Draw the extra info - if (*thisCheckStatus != RCSHOW_UNCHECKED) { - switch (*thisCheckStatus) { - case RCSHOW_SAVED: - case RCSHOW_CHECKED: - case RCSHOW_SCUMMED: - if (IS_RANDO) - txt = OTRGlobals::Instance->gRandomizer - ->EnumToSpoilerfileGetName[gSaveContext.itemLocations[rcObj.rc].get.rgID][gSaveContext.language]; - else if (gSaveContext.language == LANGUAGE_ENG) - txt = ItemFromGIID(rcObj.ogItemId).GetName().english; - else if (gSaveContext.language == LANGUAGE_FRA) - txt = ItemFromGIID(rcObj.ogItemId).GetName().french; - break; - case RCSHOW_SKIPPED: - txt = "Skipped"; //TODO language - break; - case RCSHOW_SEEN: - if (IS_RANDO) - txt = OTRGlobals::Instance->gRandomizer - ->EnumToSpoilerfileGetName[gSaveContext.itemLocations[rcObj.rc].get.fakeRgID][gSaveContext.language]; - else if (gSaveContext.language == LANGUAGE_ENG) - txt = ItemFromGIID(rcObj.ogItemId).GetName().english; - else if (gSaveContext.language == LANGUAGE_FRA) - txt = ItemFromGIID(rcObj.ogItemId).GetName().french; - break; - case RCSHOW_HINTED: - txt = "Hints are WIP"; // TODO language - break; - } + txt = ""; + if (checkData.hintItem != 0) { + // TODO hints + } else if (status != RCSHOW_UNCHECKED) { + switch (status) { + case RCSHOW_SAVED: + case RCSHOW_COLLECTED: + case RCSHOW_SCUMMED: + if (IS_RANDO) { + txt = OTRGlobals::Instance->gRandomizer->EnumToSpoilerfileGetName[gSaveContext.itemLocations[rcObj.rc].get.rgID][gSaveContext.language]; + } else { + if (IsHeartPiece(rcObj.ogItemId)) { + if (gSaveContext.language == LANGUAGE_ENG || gSaveContext.language == LANGUAGE_GER) { + txt = ItemFromGIID(rcObj.ogItemId).GetName().english; + } else if (gSaveContext.language == LANGUAGE_FRA) { + txt = ItemFromGIID(rcObj.ogItemId).GetName().french; + } + } + } + break; + case RCSHOW_IDENTIFIED: + case RCSHOW_SEEN: + if (IS_RANDO) { + if (gSaveContext.itemLocations[rcObj.rc].get.rgID == RG_ICE_TRAP) { + if (status == RCSHOW_IDENTIFIED) { + txt = gSaveContext.itemLocations[rcObj.rc].get.trickName; + } else { + txt = OTRGlobals::Instance->gRandomizer->EnumToSpoilerfileGetName[gSaveContext.itemLocations[rcObj.rc].get.fakeRgID][gSaveContext.language]; + } + } else { + txt = OTRGlobals::Instance->gRandomizer->EnumToSpoilerfileGetName[gSaveContext.itemLocations[rcObj.rc].get.rgID][gSaveContext.language]; + } + if (status == RCSHOW_IDENTIFIED) { + txt += fmt::format(" - {}", gSaveContext.checkTrackerData[rcObj.rc].price); + } + } else { + if (IsHeartPiece(rcObj.ogItemId)) { + if (gSaveContext.language == LANGUAGE_ENG || gSaveContext.language == LANGUAGE_GER) { + txt = ItemFromGIID(rcObj.ogItemId).GetName().english; + } else if (gSaveContext.language == LANGUAGE_FRA) { + txt = ItemFromGIID(rcObj.ogItemId).GetName().french; + } + } + } + break; + } + } + if (txt == "" && skipped) + txt = "Skipped"; //TODO language + + if (txt != "") { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f, extraColor.b / 255.0f, extraColor.a / 255.0f)); ImGui::SameLine(); ImGui::Text(" (%s)", txt.c_str()); ImGui::PopStyleColor(); } - } static std::set rainbowCVars = { @@ -871,7 +1364,7 @@ static std::set rainbowCVars = { "gCheckTrackerSkippedMainColor", "gCheckTrackerSkippedExtraColor", "gCheckTrackerSeenMainColor", "gCheckTrackerSeenExtraColor", "gCheckTrackerHintedMainColor", "gCheckTrackerHintedExtraColor", - "gCheckTrackerCheckedMainColor", "gCheckTrackerCheckedExtraColor", + "gCheckTrackerCollectedMainColor", "gCheckTrackerCollectedExtraColor", "gCheckTrackerScummedMainColor", "gCheckTrackerScummedExtraColor", "gCheckTrackerSavedMainColor", "gCheckTrackerSavedExtraColor", }; @@ -898,7 +1391,7 @@ void RainbowTick() { void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, const char* cvarExtraName, Color_RGBA8& main_color, Color_RGBA8& extra_color, Color_RGBA8& main_default_color, - Color_RGBA8& extra_default_color, const char* cvarHideName) { + Color_RGBA8& extra_default_color, const char* cvarHideName, const char* tooltip) { Color_RGBA8 cvarMainColor = CVarGetColor(cvarMainName, main_default_color); Color_RGBA8 cvarExtraColor = CVarGetColor(cvarExtraName, extra_default_color); main_color = cvarMainColor; @@ -940,10 +1433,13 @@ void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, ImGui::EndTable(); } } + if (tooltip != "") { + ImGui::SameLine(); + ImGui::Text(" ?"); + UIWidgets::Tooltip(tooltip); + } } - - static const char* windowType[] = { "Floating", "Window" }; static const char* displayType[] = { "Always", "Combo Button Hold" }; static const char* buttonStrings[] = { "A Button", "B Button", "C-Up", "C-Down", "C-Left", "C-Right", "L Button", @@ -983,22 +1479,22 @@ void CheckTrackerSettingsWindow::DrawElement() { UIWidgets::LabeledRightAlignedEnhancementCombobox("Combo Button 2", "gCheckTrackerComboButton2", buttonStrings, TRACKER_COMBO_BUTTON_R); } } - UIWidgets::EnhancementCheckbox("Performance mode", "gCheckTrackerOptionPerformanceMode"); - UIWidgets::Tooltip("Slows down checking for updates to 1 check per frame. Only required if experiencing poor performance when using Check Tracker."); UIWidgets::EnhancementCheckbox("Vanilla/MQ Dungeon Spoilers", "gCheckTrackerOptionMQSpoilers"); - UIWidgets::Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked. "); + UIWidgets::Tooltip("If enabled, Vanilla/MQ dungeons will show on the tracker immediately. Otherwise, Vanilla/MQ dungeon locations must be unlocked."); + UIWidgets::EnhancementCheckbox("Hide right-side shop item checks", "gCheckTrackerOptionHideRightShopChecks", false, "", UIWidgets::CheckboxGraphics::Cross, true); + UIWidgets::Tooltip("If enabled, will prevent the tracker from displaying slots 1-4 in all shops. Requires save reload."); ImGui::TableNextColumn(); - ImGuiDrawTwoColorPickerSection("Area Incomplete", "gCheckTrackerAreaMainIncompleteColor", "gCheckTrackerAreaExtraIncompleteColor", Color_Area_Incomplete_Main, Color_Area_Incomplete_Extra, Color_Main_Default, Color_Area_Incomplete_Extra_Default, "gCheckTrackerAreaIncompleteHide" ); - ImGuiDrawTwoColorPickerSection("Area Complete", "gCheckTrackerAreaMainCompleteColor", "gCheckTrackerAreaExtraCompleteColor", Color_Area_Complete_Main, Color_Area_Complete_Extra, Color_Main_Default, Color_Area_Complete_Extra_Default, "gCheckTrackerAreaCompleteHide" ); - ImGuiDrawTwoColorPickerSection("Unchecked", "gCheckTrackerUncheckedMainColor", "gCheckTrackerUncheckedExtraColor", Color_Unchecked_Main, Color_Unchecked_Extra, Color_Main_Default, Color_Unchecked_Extra_Default, "gCheckTrackerUncheckedHide" ); - ImGuiDrawTwoColorPickerSection("Skipped", "gCheckTrackerSkippedMainColor", "gCheckTrackerSkippedExtraColor", Color_Skipped_Main, Color_Skipped_Extra, Color_Main_Default, Color_Skipped_Extra_Default, "gCheckTrackerSkippedHide" ); - ImGuiDrawTwoColorPickerSection("Seen (WIP)", "gCheckTrackerSeenMainColor", "gCheckTrackerSeenExtraColor", Color_Seen_Main, Color_Seen_Extra, Color_Main_Default, Color_Seen_Extra_Default, "gCheckTrackerSeenHide" ); - ImGuiDrawTwoColorPickerSection("Hinted (WIP)", "gCheckTrackerHintedMainColor", "gCheckTrackerHintedExtraColor", Color_Hinted_Main, Color_Hinted_Extra, Color_Main_Default, Color_Hinted_Extra_Default, "gCheckTrackerHintedHide" ); - ImGuiDrawTwoColorPickerSection("Checked (WIP)", "gCheckTrackerCheckedMainColor", "gCheckTrackerCheckedExtraColor", Color_Checked_Main, Color_Checked_Extra, Color_Main_Default, Color_Checked_Extra_Default, "gCheckTrackerCheckedHide" ); - ImGuiDrawTwoColorPickerSection("Scummed (WIP)", "gCheckTrackerScummedMainColor", "gCheckTrackerScummedExtraColor", Color_Scummed_Main, Color_Scummed_Extra, Color_Main_Default, Color_Scummed_Extra_Default, "gCheckTrackerScummedHide" ); - ImGuiDrawTwoColorPickerSection("Saved", "gCheckTrackerSavedMainColor", "gCheckTrackerSavedExtraColor", Color_Saved_Main, Color_Saved_Extra, Color_Main_Default, Color_Saved_Extra_Default, "gCheckTrackerSavedHide" ); + CheckTracker::ImGuiDrawTwoColorPickerSection("Area Incomplete", "gCheckTrackerAreaMainIncompleteColor", "gCheckTrackerAreaExtraIncompleteColor", Color_Area_Incomplete_Main, Color_Area_Incomplete_Extra, Color_Main_Default, Color_Area_Incomplete_Extra_Default, "gCheckTrackerAreaIncompleteHide", ""); + CheckTracker::ImGuiDrawTwoColorPickerSection("Area Complete", "gCheckTrackerAreaMainCompleteColor", "gCheckTrackerAreaExtraCompleteColor", Color_Area_Complete_Main, Color_Area_Complete_Extra, Color_Main_Default, Color_Area_Complete_Extra_Default, "gCheckTrackerAreaCompleteHide", ""); + CheckTracker::ImGuiDrawTwoColorPickerSection("Unchecked", "gCheckTrackerUncheckedMainColor", "gCheckTrackerUncheckedExtraColor", Color_Unchecked_Main, Color_Unchecked_Extra, Color_Main_Default, Color_Unchecked_Extra_Default, "gCheckTrackerUncheckedHide", "Checks you have not interacted with at all."); + CheckTracker::ImGuiDrawTwoColorPickerSection("Skipped", "gCheckTrackerSkippedMainColor", "gCheckTrackerSkippedExtraColor", Color_Skipped_Main, Color_Skipped_Extra, Color_Main_Default, Color_Skipped_Extra_Default, "gCheckTrackerSkippedHide", ""); + CheckTracker::ImGuiDrawTwoColorPickerSection("Seen", "gCheckTrackerSeenMainColor", "gCheckTrackerSeenExtraColor", Color_Seen_Main, Color_Seen_Extra, Color_Main_Default, Color_Seen_Extra_Default, "gCheckTrackerSeenHide", "Used for shops. Shows item names for shop slots when walking in, and prices when highlighting them in buy mode."); + CheckTracker::ImGuiDrawTwoColorPickerSection("Scummed", "gCheckTrackerScummedMainColor", "gCheckTrackerScummedExtraColor", Color_Scummed_Main, Color_Scummed_Extra, Color_Main_Default, Color_Scummed_Extra_Default, "gCheckTrackerScummedHide", "Checks you collect, but then reload before saving so you no longer have them."); + //CheckTracker::ImGuiDrawTwoColorPickerSection("Hinted (WIP)", "gCheckTrackerHintedMainColor", "gCheckTrackerHintedExtraColor", Color_Hinted_Main, Color_Hinted_Extra, Color_Main_Default, Color_Hinted_Extra_Default, "gCheckTrackerHintedHide", ""); + CheckTracker::ImGuiDrawTwoColorPickerSection("Collected", "gCheckTrackerCollectedMainColor", "gCheckTrackerCollectedExtraColor", Color_Collected_Main, Color_Collected_Extra, Color_Main_Default, Color_Collected_Extra_Default, "gCheckTrackerCollectedHide", "Checks you have collected without saving or reloading yet."); + CheckTracker::ImGuiDrawTwoColorPickerSection("Saved", "gCheckTrackerSavedMainColor", "gCheckTrackerSavedExtraColor", Color_Saved_Main, Color_Saved_Extra, Color_Main_Default, Color_Saved_Extra_Default, "gCheckTrackerSavedHide", "Checks that you saved the game while having collected."); ImGui::PopStyleVar(1); ImGui::EndTable(); @@ -1006,7 +1502,7 @@ void CheckTrackerSettingsWindow::DrawElement() { } void CheckTrackerWindow::InitElement() { - Color_Background = CVarGetColor("gCheckTrackerBgColor", Color_Bg_Default); + Color_Background = CVarGetColor("gCheckTrackerBgColor", Color_Bg_Default); Color_Area_Incomplete_Main = CVarGetColor("gCheckTrackerAreaMainIncompleteColor", Color_Main_Default); Color_Area_Incomplete_Extra = CVarGetColor("gCheckTrackerAreaExtraIncompleteColor", Color_Area_Incomplete_Extra_Default); Color_Area_Complete_Main = CVarGetColor("gCheckTrackerAreaMainCompleteColor", Color_Main_Default); @@ -1019,19 +1515,25 @@ void CheckTrackerWindow::InitElement() { Color_Seen_Extra = CVarGetColor("gCheckTrackerSeenExtraColor", Color_Seen_Extra_Default); Color_Hinted_Main = CVarGetColor("gCheckTrackerHintedMainColor", Color_Main_Default); Color_Hinted_Extra = CVarGetColor("gCheckTrackerHintedExtraColor", Color_Hinted_Extra_Default); - Color_Checked_Main = CVarGetColor("gCheckTrackerCheckedMainColor", Color_Main_Default); - Color_Checked_Extra = CVarGetColor("gCheckTrackerCheckedExtraColor", Color_Checked_Extra_Default); + Color_Collected_Main = CVarGetColor("gCheckTrackerCollectedMainColor", Color_Main_Default); + Color_Collected_Extra = CVarGetColor("gCheckTrackerCollectedExtraColor", Color_Collected_Extra_Default); Color_Scummed_Main = CVarGetColor("gCheckTrackerScummedMainColor", Color_Main_Default); Color_Scummed_Extra = CVarGetColor("gCheckTrackerScummedExtraColor", Color_Scummed_Extra_Default); Color_Saved_Main = CVarGetColor("gCheckTrackerSavedMainColor", Color_Main_Default); Color_Saved_Extra = CVarGetColor("gCheckTrackerSavedExtraColor", Color_Saved_Extra_Default); - GameInteractor::Instance->RegisterGameHook([](uint32_t fileNum) { - doInitialize = true; - }); - GameInteractor::Instance->RegisterGameHook([](uint32_t fileNum) { + SaveManager::Instance->AddInitFunction(InitTrackerData); + sectionId = SaveManager::Instance->AddSaveFunction("trackerData", 1, SaveFile, true, -1); + SaveManager::Instance->AddLoadFunction("trackerData", 1, LoadFile); + GameInteractor::Instance->RegisterGameHook([](uint32_t fileNum) { Teardown(); }); + GameInteractor::Instance->RegisterGameHook(CheckTrackerItemReceive); + GameInteractor::Instance->RegisterGameHook(CheckTrackerSaleEnd); + GameInteractor::Instance->RegisterGameHook(CheckTrackerFrame); + GameInteractor::Instance->RegisterGameHook(CheckTrackerTransition); + GameInteractor::Instance->RegisterGameHook(CheckTrackerShopSlotChange); + LocationTable_Init(); } diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h index 0ed3a8526..b2193f696 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.h @@ -1,4 +1,7 @@ #pragma once +#include +#include "randomizerTypes.h" +#include "randomizer_check_objects.h" #include @@ -26,17 +29,6 @@ class CheckTrackerWindow : public LUS::GuiWindow { void UpdateElement() override {}; }; -// Check tracker check visibility categories -typedef enum { - RCSHOW_UNCHECKED, - RCSHOW_SKIPPED, - RCSHOW_SEEN, - RCSHOW_HINTED, - RCSHOW_CHECKED, - RCSHOW_SCUMMED, - RCSHOW_SAVED, -} RandomizerCheckShow; - //Converts an index into a Little Endian bitmask, as follows: //00: 0000000100000000 //01: 0000001000000000 @@ -51,6 +43,18 @@ typedef enum { //repeat... #define INDEX_TO_16BIT_LITTLE_ENDIAN_BITMASK(idx) (0x8000 >> (7 - (idx % 8) + ((idx % 16) / 8) * 8)) +void DefaultCheckData(RandomizerCheck rc); +void Teardown(); +void UpdateAllOrdering(); +bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj); +void InitTrackerData(bool isDebug); +void SetLastItemGetRC(RandomizerCheck rc); +RandomizerCheckArea GetCheckArea(); +void CheckTrackerDialogClosed(); +void ToggleShopRightChecks(); +void UpdateCheck(uint32_t, RandomizerCheckTrackerData); +} // namespace CheckTracker -} // namespace CheckTracker \ No newline at end of file +void to_json(nlohmann::json & j, const RandomizerCheckTrackerData& rctd); +void from_json(const nlohmann::json& j, RandomizerCheckTrackerData& rctd); diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index 3fcfff43b..fe335c2b6 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -319,7 +319,7 @@ const EntranceData entranceData[] = { { 0x03AC, 0x0130, SINGLE_SCENE_INFO(0x5E), "Haunted Wasteland", "GF", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_GERUDO_VALLEY, ENTRANCE_TYPE_OVERWORLD, "hw,gerudo fortress"}, { 0x0123, 0x0365, SINGLE_SCENE_INFO(0x5E), "Haunted Wasteland", "Desert Colossus", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_OVERWORLD, "dc,hw"}, { 0x0365, 0x0123, SINGLE_SCENE_INFO(0x5C), "Desert Colossus", "Haunted Wasteland", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_OVERWORLD, "dc,hw"}, - { 0x0588, 0x057C, SINGLE_SCENE_INFO(0x5C), "Colossus", "Colossus Great Fairy Fountain", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_INTERIOR, "dc", 1}, + { 0x0588, 0x057C, SINGLE_SCENE_INFO(0x5C), "Desert Colossus", "Colossus Great Fairy Fountain", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_INTERIOR, "dc", 1}, { 0x0700, 0x0800, SINGLE_SCENE_INFO(0x5C), "Desert Colossus", "Colossus Grotto", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_GROTTO, "dc,scrubs", 1}, { 0x0082, 0x01E1, SINGLE_SCENE_INFO(0x5C), "Desert Colossus", "Spirit Temple", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_DUNGEON, "dc", 1}, { 0x057C, 0x0588, {{ 0x3D, 0x02 }}, "Colossus Great Fairy Fountain", "Colossus", ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_GROUP_HAUNTED_WASTELAND, ENTRANCE_TYPE_INTERIOR, "dc"}, @@ -537,6 +537,14 @@ void SortEntranceListByArea(EntranceOverride* entranceList, u8 byDest) { } } +s16 GetLastEntranceOverride() { + return lastEntranceIndex; +} + +s16 GetCurrentGrottoId() { + return currentGrottoId; +} + void SetCurrentGrottoIDForTracker(s16 entranceIndex) { currentGrottoId = entranceIndex; } diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h index e0c216dcf..fc5346aa3 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.h @@ -3,6 +3,7 @@ #include #include #include + #include typedef enum { @@ -80,6 +81,9 @@ void SetCurrentGrottoIDForTracker(int16_t entranceIndex); void SetLastEntranceOverrideForTracker(int16_t entranceIndex); void ClearEntranceTrackingData(); void InitEntranceTrackingData(); +s16 GetLastEntranceOverride(); +s16 GetCurrentGrottoId(); +const EntranceData* GetEntranceData(s16); class EntranceTrackerWindow : public LUS::GuiWindow { public: @@ -88,4 +92,4 @@ class EntranceTrackerWindow : public LUS::GuiWindow { void InitElement() override; void DrawElement() override; void UpdateElement() override {}; -}; \ No newline at end of file +}; diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index 22084c11e..f24ab818b 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -611,7 +611,7 @@ void DrawItem(ItemTrackerItem item) { break; } - if (hasItem && item.id != actualItemId && actualItemTrackerItemMap.find(actualItemId) != actualItemTrackerItemMap.end()) { + if (GameInteractor::IsSaveLoaded() && (hasItem && item.id != actualItemId && actualItemTrackerItemMap.find(actualItemId) != actualItemTrackerItemMap.end())) { item = actualItemTrackerItemMap[actualItemId]; } @@ -634,7 +634,7 @@ void DrawBottle(ItemTrackerItem item) { uint32_t actualItemId = gSaveContext.inventory.items[SLOT(item.id) + item.data]; bool hasItem = actualItemId != ITEM_NONE; - if (hasItem && item.id != actualItemId && actualItemTrackerItemMap.find(actualItemId) != actualItemTrackerItemMap.end()) { + if (GameInteractor::IsSaveLoaded() && (hasItem && item.id != actualItemId && actualItemTrackerItemMap.find(actualItemId) != actualItemTrackerItemMap.end())) { item = actualItemTrackerItemMap[actualItemId]; } diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 74ca8dc07..8b56d352c 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -2198,3 +2198,7 @@ extern "C" void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex) { extern "C" void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement) { gfx_register_blended_texture(name, mask, replacement); } + +extern "C" void CheckTracker_OnMessageClose() { + CheckTracker::CheckTrackerDialogClosed(); +} \ No newline at end of file diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 29d4a3f84..b24f59d8b 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -154,6 +154,7 @@ void EntranceTracker_SetCurrentGrottoID(s16 entranceIndex); void EntranceTracker_SetLastEntranceOverride(s16 entranceIndex); void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement); void SaveManager_ThreadPoolWait(); +void CheckTracker_OnMessageClose(); int32_t GetGIID(uint32_t itemID); #endif diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 4e84bfab5..651c9563c 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -1590,9 +1590,6 @@ void SaveManager::LoadBaseVersion3() { SaveManager::Instance->LoadArray("entrancesDiscovered", ARRAY_COUNT(gSaveContext.sohStats.entrancesDiscovered), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.sohStats.entrancesDiscovered[i]); }); - SaveManager::Instance->LoadArray("locationsSkipped", ARRAY_COUNT(gSaveContext.sohStats.locationsSkipped), [](size_t i) { - SaveManager::Instance->LoadData("", gSaveContext.sohStats.locationsSkipped[i]); - }); }); SaveManager::Instance->LoadArray("sceneFlags", ARRAY_COUNT(gSaveContext.sceneFlags), [](size_t i) { SaveManager::Instance->LoadStruct("", [&i]() { diff --git a/soh/soh/UIWidgets.cpp b/soh/soh/UIWidgets.cpp index 64d94cc69..15bb088a3 100644 --- a/soh/soh/UIWidgets.cpp +++ b/soh/soh/UIWidgets.cpp @@ -690,4 +690,48 @@ namespace UIWidgets { } ImGui::PopID(); } + + bool StateButtonEx(const char* str_id, const char* label, ImVec2 size, ImGuiButtonFlags flags) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + + const ImGuiID id = window->GetID(str_id); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + const float default_size = ImGui::GetFrameHeight(); + ImGui::ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); + if (!ImGui::ItemAdd(bb, id)) + return false; + + if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 bg_col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive + : hovered ? ImGuiCol_ButtonHovered + : ImGuiCol_Button); + //const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); + ImGui::RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, {0.55f, 0.45f}, &bb); + /*ImGui::RenderArrow(window->DrawList, + bb.Min + + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), + text_col, dir);*/ + + IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); + return pressed; + } + + bool StateButton(const char* str_id, const char* label) { + float sz = ImGui::GetFrameHeight(); + return StateButtonEx(str_id, label, ImVec2(sz, sz), ImGuiButtonFlags_None); + } } diff --git a/soh/soh/UIWidgets.hpp b/soh/soh/UIWidgets.hpp index 5df2f6e9b..b18487977 100644 --- a/soh/soh/UIWidgets.hpp +++ b/soh/soh/UIWidgets.hpp @@ -95,6 +95,7 @@ namespace UIWidgets { void DrawFlagArray32(const std::string& name, uint32_t& flags); void DrawFlagArray16(const std::string& name, uint16_t& flags); void DrawFlagArray8(const std::string& name, uint8_t& flags); + bool StateButton(const char* str_id, const char* label); } #endif /* UIWidgets_hpp */ diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c index c88c698c7..78eac4ece 100644 --- a/soh/src/code/z_message_PAL.c +++ b/soh/src/code/z_message_PAL.c @@ -3384,6 +3384,7 @@ void Message_Update(PlayState* play) { } sLastPlayedSong = 0xFF; osSyncPrintf("OCARINA_MODE=%d chk_ocarina_no=%d\n", play->msgCtx.ocarinaMode, msgCtx->unk_E3F2); + CheckTracker_OnMessageClose(); break; case MSGMODE_PAUSED: break; diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index db938a4c2..dfafbb7ff 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -967,6 +967,7 @@ void Play_Update(PlayState* play) { R_UPDATE_RATE = 3; } + // Transition end for standard transitions GameInteractor_ExecuteOnTransitionEndHooks(play->sceneNum); } play->sceneLoadFlag = 0; @@ -1075,6 +1076,9 @@ void Play_Update(PlayState* play) { R_UPDATE_RATE = 3; play->sceneLoadFlag = 0; play->transitionMode = 0; + + // Transition end for sandstorm effect (delayed until effect is finished) + GameInteractor_ExecuteOnTransitionEndHooks(play->sceneNum); } } else { if (play->envCtx.sandstormEnvA == 255) { @@ -1109,6 +1113,9 @@ void Play_Update(PlayState* play) { R_UPDATE_RATE = 3; play->sceneLoadFlag = 0; play->transitionMode = 0; + + // Transition end for sandstorm effect (delayed until effect is finished) + GameInteractor_ExecuteOnTransitionEndHooks(play->sceneNum); } } break; diff --git a/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c b/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c index 5d3ebd5eb..2ae32e916 100644 --- a/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c +++ b/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c @@ -15,6 +15,7 @@ #include "objects/object_masterkokirihead/object_masterkokirihead.h" #include "soh/Enhancements/randomizer/randomizer_entrance.h" #include "soh/Enhancements/cosmetics/cosmeticsTypes.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_WHILE_CULLED) @@ -1005,6 +1006,7 @@ void EnOssan_State_FacingShopkeeper(EnOssan* this, PlayState* play, Player* play Interface_SetDoAction(play, DO_ACTION_DECIDE); this->stickLeftPrompt.isEnabled = false; func_80078884(NA_SE_SY_CURSOR); + GameInteractor_ExecuteOnShopSlotChangeHooks(this->cursorIndex, this->shelfSlots[this->cursorIndex]->basePrice); } } else if ((this->stickAccumX > 0) || (dpad && CHECK_BTN_ALL(input->press.button, dRight))) { nextIndex = EnOssan_SetCursorIndexFromNeutral(this, 0); @@ -1014,6 +1016,7 @@ void EnOssan_State_FacingShopkeeper(EnOssan* this, PlayState* play, Player* play Interface_SetDoAction(play, DO_ACTION_DECIDE); this->stickRightPrompt.isEnabled = false; func_80078884(NA_SE_SY_CURSOR); + GameInteractor_ExecuteOnShopSlotChangeHooks(this->cursorIndex, this->shelfSlots[this->cursorIndex]->basePrice); } } } @@ -1277,6 +1280,7 @@ void EnOssan_State_BrowseLeftShelf(EnOssan* this, PlayState* play, Player* playe } EnOssan_CursorUpDown(this, play); if (this->cursorIndex != prevIndex) { + GameInteractor_ExecuteOnShopSlotChangeHooks(this->cursorIndex, this->shelfSlots[this->cursorIndex]->basePrice); Message_ContinueTextbox(play, this->shelfSlots[this->cursorIndex]->actor.textId); func_80078884(NA_SE_SY_CURSOR); } @@ -1346,6 +1350,7 @@ void EnOssan_State_BrowseRightShelf(EnOssan* this, PlayState* play, Player* play } EnOssan_CursorUpDown(this, play); if (this->cursorIndex != prevIndex) { + GameInteractor_ExecuteOnShopSlotChangeHooks(this->cursorIndex, this->shelfSlots[this->cursorIndex]->basePrice); Message_ContinueTextbox(play, this->shelfSlots[this->cursorIndex]->actor.textId); func_80078884(NA_SE_SY_CURSOR); }