diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerMiscEnums.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerMiscEnums.h index 8e3b39a452..83316836af 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerMiscEnums.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerMiscEnums.h @@ -185,7 +185,7 @@ RANDO_ENUM_END(RandomizerCheckArea) // Check tracker check visibility categories RANDO_ENUM_BEGIN(RandomizerCheckStatus) RANDO_ENUM_ITEM(RCSHOW_UNCHECKED) -RANDO_ENUM_ITEM(RCSHOW_SEEN) +RANDO_ENUM_ITEM(RCSHOW_SEEN_OR_HINTED) RANDO_ENUM_ITEM(RCSHOW_IDENTIFIED) RANDO_ENUM_ITEM(RCSHOW_SCUMMED) RANDO_ENUM_ITEM(RCSHOW_COLLECTED) diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 30a23568ea..8cb108c721 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -17,6 +17,7 @@ #include "soh/ObjectExtension/ObjectExtension.h" #include "overlays/actors/ovl_En_GirlA/z_en_girla.h" +#include #include #include #include @@ -313,7 +314,7 @@ bool IsCheckHidden(RandomizerCheck rc) { bool available = itemLocation->IsAvailable(); bool skipped = itemLocation->GetIsSkipped(); bool obtained = itemLocation->HasObtained(); - bool seen = status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED; + bool seen = status == RCSHOW_SEEN_OR_HINTED || status == RCSHOW_IDENTIFIED; bool scummed = status == RCSHOW_SCUMMED; bool unchecked = status == RCSHOW_UNCHECKED; @@ -510,7 +511,7 @@ void SetShopSeen(uint32_t sceneNum, bool prices) { bool statusChanged = false; for (int i = start; i < start + 8; i++) { if (OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->GetCheckStatus() == RCSHOW_UNCHECKED) { - OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetCheckStatus(RCSHOW_SEEN); + OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetCheckStatus(RCSHOW_SEEN_OR_HINTED); statusChanged = true; } } @@ -519,6 +520,72 @@ void SetShopSeen(uint32_t sceneNum, bool prices) { } } +// Items share hint text keys: all six jabber nuts are "the ability to speak". +// Counted once on first use. +static bool HintNamesItemUniquely(RandomizerGet rg) { + static const auto keyUses = [] { + std::array uses{}; + for (const auto& item : Rando::StaticData::GetItemTable()) { + uses[item.GetHintKey()]++; + } + return uses; + }(); + return keyUses[Rando::StaticData::RetrieveItem(rg).GetHintKey()] == 1; +} + +// Only HINT_TYPE_ITEM hints name a check's item outright; other types stay +// ambiguous. Marks Seen, not Identified, since hints never state a price. +static bool ApplyItemHintToChecks(RandomizerHint hintKey) { + // Ambiguous/obscure hints reuse the same phrase across items (all four swords are + // just "a sword"), so only clear hints are safe to mark - skip anything else + if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_HINT_CLARITY) != RO_HINT_CLARITY_CLEAR) { + return false; + } + + if (hintKey == RH_NONE) { + return false; + } + + // The hint-revealed hook can fire for hints the seed has disabled. + auto hint = OTRGlobals::Instance->gRandoContext->GetHint(hintKey); + if (!hint->IsEnabled() || hint->GetHintType() != HINT_TYPE_ITEM) { + return false; + } + + // Loop over hinted locations, apply the ones which are unambiguous + bool changed = false; + for (RandomizerCheck rc : hint->GetHintedLocations()) { + if (rc == RC_UNKNOWN_CHECK) { + continue; + } + auto loc = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); + // Ice traps hint, and display, as their disguise. + RandomizerGet named = loc->GetPlacedRandomizerGet(); + auto& overrides = OTRGlobals::Instance->gRandoContext->overrides; + if (named == RG_ICE_TRAP && overrides.contains(rc)) { + named = overrides[rc].LooksLike(); + } + if (!HintNamesItemUniquely(named)) { + // The hint could mean several items, no spoilers! + continue; + } + if (loc->GetCheckStatus() == RCSHOW_UNCHECKED) { + loc->SetCheckStatus(RCSHOW_SEEN_OR_HINTED); + changed = true; + } + } + return changed; +} + +void CheckTrackerHintRevealed(RandomizerHint hintKey) { + if (!GameInteractor::IsSaveLoaded() || !IS_RANDO) { + return; + } + if (ApplyItemHintToChecks(hintKey)) { + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); + } +} + void CheckTrackerLoadGame(int32_t fileNum) { if (IS_BOSS_RUSH) { return; @@ -626,7 +693,7 @@ void CheckTrackerShopSlotChange(uint8_t cursorSlot, int16_t basePrice) { slot = RC_KAK_BAZAAR_ITEM_1 + cursorSlot; } auto status = OTRGlobals::Instance->gRandoContext->GetItemLocation(slot)->GetCheckStatus(); - if (status == RCSHOW_SEEN) { + if (status == RCSHOW_SEEN_OR_HINTED) { OTRGlobals::Instance->gRandoContext->GetItemLocation(slot)->SetCheckStatus(RCSHOW_IDENTIFIED); SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); RecalculateAvailableChecks(); @@ -868,9 +935,11 @@ void CheckTrackerFlagSet(int16_t flagType, int32_t flag) { } void CheckTrackerDialogMessage() { + // These dialogues state the price, so a Seen check upgrades to Identified. auto identifyCheck = [](RandomizerCheck rc) { auto loc = OTRGlobals::Instance->gRandoContext->GetItemLocation(rc); - if (loc->GetCheckStatus() == RCSHOW_UNCHECKED) { + RandomizerCheckStatus status = loc->GetCheckStatus(); + if (status == RCSHOW_UNCHECKED || status == RCSHOW_SEEN_OR_HINTED) { loc->SetCheckStatus(RCSHOW_IDENTIFIED); RecalculateAvailableChecks(); } @@ -1319,13 +1388,14 @@ bool ShouldShowCheck(RandomizerCheck check) { Rando::StaticData::GetLocation(check)->GetName() + " " + RandomizerCheckObjects::GetRCAreaName(Rando::StaticData::GetLocation(check)->GetArea())); if (itemLoc->HasObtained() || itemLoc->GetCheckStatus() == RCSHOW_SCUMMED || - (!mystery && (itemLoc->GetCheckStatus() == RCSHOW_IDENTIFIED || itemLoc->GetCheckStatus() == RCSHOW_SEEN) && + (!mystery && + (itemLoc->GetCheckStatus() == RCSHOW_IDENTIFIED || itemLoc->GetCheckStatus() == RCSHOW_SEEN_OR_HINTED) && itemLoc->GetPlacedRandomizerGet() != RG_ICE_TRAP)) { search += " " + itemLoc->GetPlacedItemName().GetForLanguage(gSaveContext.language); } else if (itemLoc->GetCheckStatus() == RCSHOW_IDENTIFIED && !mystery) { search += OTRGlobals::Instance->gRandoContext->overrides[check].GetTrickName().GetForLanguage(gSaveContext.language); - } else if (itemLoc->GetCheckStatus() == RCSHOW_SEEN && !mystery) { + } else if (itemLoc->GetCheckStatus() == RCSHOW_SEEN_OR_HINTED && !mystery) { search += Rando::StaticData::RetrieveItem(OTRGlobals::Instance->gRandoContext->overrides[check].LooksLike()) .GetName() .GetForLanguage(gSaveContext.language); @@ -1937,7 +2007,7 @@ void DrawLocation(RandomizerCheck rc) { ? Color_Skipped_Extra : Color_Skipped_Main; extraColor = Color_Skipped_Extra; - } else if (status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED) { + } else if (status == RCSHOW_SEEN_OR_HINTED || status == RCSHOW_IDENTIFIED) { if (!showHidden && hideSeen) { return; } @@ -1980,7 +2050,7 @@ void DrawLocation(RandomizerCheck rc) { // Draw button - for Skipped/Seen/Scummed/Unchecked only ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 4.0f, 3.0f }); float sz = ImGui::GetFrameHeight(); - if (status == RCSHOW_UNCHECKED || status == RCSHOW_SEEN || status == RCSHOW_IDENTIFIED || + if (status == RCSHOW_UNCHECKED || status == RCSHOW_SEEN_OR_HINTED || status == RCSHOW_IDENTIFIED || status == RCSHOW_SCUMMED || skipped) { if (UIWidgets::StateButton(std::to_string(rc).c_str(), skipped ? ICON_FA_PLUS : ICON_FA_TIMES, ImVec2(sz, sz), UIWidgets::ButtonOptions().Color(THEME_COLOR))) { @@ -2051,7 +2121,7 @@ void DrawLocation(RandomizerCheck rc) { } break; case RCSHOW_IDENTIFIED: - case RCSHOW_SEEN: + case RCSHOW_SEEN_OR_HINTED: if (IS_RANDO) { const auto checkType = loc->GetRCType(); const bool hideMerchantName = @@ -2444,6 +2514,7 @@ void CheckTrackerWindow::InitElement() { GameInteractor::Instance->RegisterGameHook(CheckTrackerSceneFlagSet); GameInteractor::Instance->RegisterGameHook(CheckTrackerFlagSet); GameInteractor::Instance->RegisterGameHook(CheckTrackerDialogMessage); + GameInteractor::Instance->RegisterGameHook(CheckTrackerHintRevealed); } void CheckTrackerWindow::UpdateElement() {