From 70a83f647fa5927d69ea10ff99af24b5ef657095 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Wed, 4 Oct 2023 08:03:36 -0700 Subject: [PATCH] Check Tracker Update (#2668) * Initial commit. What works so far: Data file loads, saves, deletes, and is created properly. - Need to run `HasItemBeenCollected` when creating in case of mid-seed regen. Organized checks into maps set by area. Areas show in tracker. - Checks pulled from checkObjectsByArea are not currently listed. - Areas are being assumed completed and hidden at the start. * Checks now populate. Still not counting area totals properly. * Don't track RC_LINKS_POCKET in the data file, and instead manually add it in every time a save is load. rcObjects doesn't contain Link's Pocket location (duh). * Moved ItemReceive hook to randomizer_check_tracker.cpp. Skipped items are properly sorted and formatted. Saved items contribute to an area's gotten/skipped checks on load. Changed skipped items default main color to gray. General code cleanup. * Skipped checks now contribute to area totals for area completion logic. * Mid-menu rework transfer. * Started check lookup on item receive. Tried to enable scum checking on save. * Fixed scum check on save. * More code cleanup, which revealed some issues with loading and saving. Loading now works 100%, but entries in checkTrackerData are disappearing in code somewhere, causing issues with later checks. Scummed color now displays properly. Collected but unsaved displays properly. Unchecked, Skipped, Seen and Scummed checks all have arrows for skipping/unskipping. Counts a little messed up, probably double adding skips. * Fixed item counts being off across resets. Restarts were fine. * Beginning of rework for individual check updates. Adds `Actor lastCheck` to PlayState to allow assigning of last actor to give an item. This allows precise tracking of GS, freestanding PoH, etc. Works for chests too. Currently does not work for shops, unknown for normal NPC gives. Doesn't track last gives for non-checks, like drops from grass, pots, crates, etc. * Actor-based checks fully fleshed out, but can't help with sale-based checks. Implemented `pendingSaleCheck` to back out of an RC check cycle if `gSaveContext->pendingSale` is not NONE when `OnItemReceive` is called, processed through `OnSaleEnd`. This should be the final change necessary to streamline the checks. * Temporary hook to message box close to tie in trackers for non-gs/chest actors that aren't added to `GetCheckFromActor`. This triggers a 2-frame countdown to check with `HasItemBeenCollected` since half of the methods in there aren't updated until after a text box closes. Added scene tracking to get last scene so that checks in scenes that span several overworld areas could be tied to their areas properly. Modified tracker data creation to apply Link's Pocket and Song from Impa conditionally based on randomizer settings instead of just being assumed to be done. May need modification for checking click and drag spoiler loading. Removed the scene equivalence check from CheckChecks so that an entire area is checked each time to avoid issues with, e.g., leaving a shop before a pending sale finishes. * Changed check data creation to use RSKs from gRandomizer to account for settings loaded from drag and drop spoilers. * Beginning of tracker code organization. Changed check color defaults. * Reverted moving tracker colors to header. Some individual check fixes. * Missed something from previous merge? * Removed last remnant of removed performance mode. * Better handle checking when skullsanity is off. * Song checks sceneIDs don't match the scenes they're actually in for some reason. Removed that check. * SaleEnd checks don't need the GetItemEntry parameter. Also set a flag that should prevent the check tracker from running during vanilla saves should that not be resolved by the time it's merged. * Attempting to have only the relevant checks added to the tracker data, as well as showing vanilla checks for non-rando saves. Not working yet (everything is displayed in vanilla). * Added vanilla check tracker population via `vanillaCheck` bool in `RandomizerCheckObject`. This is also added to `IsVisibleInImGui` to handle file loading and saving to eliminate checks that aren't part of vanilla or the rando seed. Implemented deleted tracker data file recreation. Added some extra item name text checks. Songs are still... tricky, so they stay blank. Utilize local copies of the randomizer check objects that are applicable for performance purposes. Fixed vanilla item-giving cutscene crashes when triggered mid-transition. * Apparently not all toolchains can handle macro instantiation without all parameters, like VS can. * Fixed scummed detail color not showing properly on tracker. * Fixed dungeon area totals and GS checks not functioning properly. * Major revamp of checking code. Doesn't rely on `GetCheckFromActor` at all anymore, but instead simply sets a flag to evaluate a number of checks in an area every frame until the check that was gotten is identified. With the new setup, it's much less hardware intensive, and at 60fps 6 checks per frame shows no noticeable effect on framerate. I can envision needing to add a collectedCount in order to account for times when two checks are gotten one right after the other. Changed area scroll to happen from `OnTransitionEnd`, and allow for scrolling in bazaar (was disabled previously because it would jump back and forth between Kak and Market while you were in one). Also setup area check order updating by area instead of all at the same time. * Fixed shooting gallery not reporting proper area. Prevent checks from actually happening if area is RCAREA_INVALID, now that all areas are reporting something if they have checks. Fixed `GetCheckArea()` not updating checks for standard scenes. Lots of vanilla tracking updates, mainly manual checks for gems, medallions and songs, since the often don't have proper scenes in the data. Prevent vanilla from triggering check loops if junk items are collected. Fixed sorting based on saved vs collected. Prevent item name display for vanilla runs. Change coloring of checks so the check name reflects status in vanilla runs. * Fixed "Recheck Area" button erroneously adding to an area's check totals? * Fix DMC vanilla checks. * Fix check tracker data recreation on data file loss. Removed redundant file exists check from SaveTrackerDataHook, as it doesn't matter if it exists or not at that point. Limited check loops to 3 to avoid infinite check loops on non-check/junk item pickup. Added more checks to disable tracker operations if a save is not running. Changed check ordering to put boss reward and heart container checks at the end of whatever RCSHOW group they belong to. * Fixed IsRunning calculation. Further improving vanilla checking. Starting framework for checking medallions, stones, and songs on data file recreation. Fixed medallion check collection. Added GIFT_FROM_SAGES to check list for vanilla file for tracking light medallion. Added check loop limitation to prevent infinite checking after picking up junk item from the ground. * Finished vanilla file recreation and recheck (songs and dungeon rewards all check properly now). This includes deku shield with KF shop item 3. Finished Gift from Raoru light medallion tracking for vanilla. Commented all CheckByScene functionality for now. * Fixed new save data file creation. Disabled entrance area calculation for now, as it only tracks the previous entrance for some reason. Fixed area detection for ToT checks. * GetCheckArea() now utilizes EntranceData almost exclusively, via either `gSaveContext.entranceIndex` or the entrance tracker's `currentGrottoId` in the case of grottos. This also means that EVERYTHING CAN AUTOSCROLL NOW. Entrance shuffles are now much easier to track. Autoscroll is now also triggered on save, just in case someone isn't autosaving and has a lot of checks that get converted to saved. * Fixed missing GS check in LW. * Added area scroll on toggling Show Hidden Items. May configure differently later. Fixed area detection for entrances in Gerudo Fortress/Valley and Collossus. Haunted wasteland doesn't autoscroll due to a bug in `OnTransitionEnd` hook, but checks still evaluate properly there. Rely on scene-based area detection if scene is a main overworld or dungeon scene. * Fixed grotto detection when shuffle is off. * Small code cleanup. Fixed Colossus hand chest checks. * Missing lus bump from merge conflict resolution * Fixed Colossus Grotto making `GetCheckArea` return Wasteland. * Improved OnItemReceive processing for non-token GS checks, since they can sometimes take long enough for the checking process to time out before the item is registered as received. * First attempt at thread safety for tracker data file writing. Seems to work, but might need more testing. * Fixed Recheck Area not unskipping items that register as saved. Improved delayed saving with autosaving, as the autosave triggered quite often before the tracker data save, making things not properly register as saved. * merge cleanup * Converted check tracker data to the sectional saves, adding `CheckTrackerData` to `SaveContext`. Implemented section ID returning and fullSave boolean passing to section functions from my PR temporarily for it to work properly. Moved `RandomizerCheckTrackerData` enum to `randomizerTypes.h` to accommodate that. Changed `Randomizer_SaveInit` to a `SaveManager::InitFunc` to allow for other randomizer-dependent sections to be initialized after that. Required a little refactoring to maintain intro cutscene skip when starting a randomizer file. * Revert section index return and randomizer init changes. Will need to wait on yet more changes to main. * Fixed check tracker initialization (shouldn't be tied to window initialization), restored saving/loading functionality. * Removed `CheckTracker::Init` and put SaveManager calls in `CheckTrackerWindow::InitElement`. Also a bit of cleanup from transition back to save file use. * Fixed tracker displaying check categories while file not loaded. * Fix Darunia's Joy check not marking in vanilla. * Fix autosave not triggering change from collected to saved in tracker data. * Changed default colors for scummed and collected display. * Merge upgrade code cleanup. * Add `OnTransitionEnd` calls to sandstorm transitions both to and from Wasteland. Also improved `gSaveContext.lastScene` assignment for both transition types. Allowed a bit of cleanup in `GetCheckArea`. * Added `StateButton`, a button like `ArrowButton` but that allows text instead of arrow icons. Apparently the changes from ArrowButton to StateButton happened in a previous commit... * Changed section name to `trackerDataCheck` to force SoH to load it after the randomizer section, as it required some randoSettings to be loaded first, and nothing else I tried to make the randomizer section load before it worked. This hacks a solution to checks not displaying on fast file load to a specific slot. F*** you, SaveManager, and f*** you too, JSON. * Forgot to change the section string for loading with the name change. * Fix check ordering for checks that trigger the autosave. * Adds option to remove right-side shop items (slots 1-4) from the tracker list. Enabled by default. * Fix default state of Hide Shop Right Checks checkbox. * Fixes grotto and great fairy scrolling and checks. Fixes array overflow from `checkTrackerData` which was creating the issue trying to load the base and randomizer sections first, among other things. That also fixed the massive file loads that were being exhibited in debug mode. * Fix shooting galleries being set as collected again when being played a second time after getting the checks. * Fix Bazaar autoscroll. * Add Saria's Song to `GetCheckFromActor` and removed some limitations from the messageCloseCheck function to make that check track properly. * Fix Song from Impa check. Implemented prevention for multiple "collections" of great fairies, just in case getting the health refill would trigger it with the previous setup. * Fix ice traps on GS tokens not triggering OnItemReceive. * Complete fix for ice trap collection from GS. Add autoscroll when clicking "Expand All". * Add `OnShopSlotChange` with cursorSlot and basePrice parameters. * Fixed include in en_ossan for shop slot hook. Added registration for `OnShopSlotChange` in the tracker, storing the price in a new `price` field in `CheckTrackerData`. Added "seen" functionality to shop checks. Displays the model item name upon first entering a shop, adding the price and switching to trickName (if it's an ice trap) upon navigating to the slot in buy mode, triggered by an invisible "identified" status that mirrors "seen" in every other way. Added tooltips to most options for check tracker color picking to describe what each status actually means. * `std::format` pls * So apparently std::format just decided to break with the latest merge from develop, but fmt::format exists and works? * Removed the last vestiges of `locationsSkipped`. Other general code and formatting cleanups. Moved `IsGameRunning` to `OTRGlobals` so the item tracker could also access it. Used preceding to "fix" item and bottle display in the item tracker on startup. * Some more code cleanup. Removed "Recheck Area" button and relevant code. Backported changes to Anchor branch applicable for single-player, including making a checkAreas vector and structuring the frame by frame checks around that. Also includes fix for Silver Gauntlets and Mirror Shield check collection crash associated with those changes. Fixed Kakariko Bazaar "seen" updates. Fixed tracker window not showing on initial load like it should. * Forgot 1 formatting fix. * Removed conditions for showing Song from Impa (isn't junk under certain conditions, so should show all the time). * Fix vanilla checks, add Zelda's Letter and Malon's Egg to manual check collection. Fix autoscroll while in child stealth section. * Fix crash in Happy Mask Shop in OnSlotChange (referenced non-existent shop id in a tracker-specific enum). * General code cleanup. * Missed one reversion. * One more. * Fix column alignment in `randomizer_check_objects`. * Fix file encoding on `randomizer_check_tracker`. Again. * Fix indentation for `actualItemtrackerItemMap`. Also removed unnecessary parts of the map. * Rename `HasEqItem` to `HasEquipment`. * Slightly better indentation for `actualItemTrackerItemMap`. * Add magic bean salesman to vanilla check tracking, and genericized deku shield to trigger KF shop item 3 wherever you get the shield. Renamed `vanillaCheck` to `vanillaHundoCheck` to (supposedly) clarify the meaning of the usage. * One more rename to `vanillaCompletion` to avoid possible confusion with 100% speedrun conditions. * give me a break XD Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com> * Changes suggested by briaguya (rename `RandomizerCheckShow` to `Status`, unused code, newline formatting) * Remove unused `itemNames` table. * Remove `IsGameRunning` in favor of `GameInteractor::IsSaveLoaded`. * Restore anti-spoiler functionality for dungeons with dungeon maps. * Review cleanup. * Fix prices not showing for Kak bazaar items. --------- Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com> --- soh/include/z64save.h | 2 +- .../game-interactor/GameInteractor.h | 1 + .../game-interactor/GameInteractor_Hooks.cpp | 4 + .../game-interactor/GameInteractor_Hooks.h | 1 + soh/soh/Enhancements/gameplaystats.cpp | 9 - .../Enhancements/randomizer/randomizer.cpp | 44 +- soh/soh/Enhancements/randomizer/randomizer.h | 2 +- .../Enhancements/randomizer/randomizerTypes.h | 17 + .../randomizer/randomizer_check_objects.cpp | 3 + .../randomizer/randomizer_check_tracker.cpp | 1568 +++++++++++------ .../randomizer/randomizer_check_tracker.h | 28 +- .../randomizer_entrance_tracker.cpp | 10 +- .../randomizer/randomizer_entrance_tracker.h | 6 +- .../randomizer/randomizer_item_tracker.cpp | 4 +- soh/soh/OTRGlobals.cpp | 4 + soh/soh/OTRGlobals.h | 1 + soh/soh/SaveManager.cpp | 3 - soh/soh/UIWidgets.cpp | 44 + soh/soh/UIWidgets.hpp | 1 + soh/src/code/z_message_PAL.c | 1 + soh/src/code/z_play.c | 7 + .../overlays/actors/ovl_En_Ossan/z_en_ossan.c | 5 + 22 files changed, 1177 insertions(+), 588 deletions(-) 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); }