Rando: Mark checks as seen in the Check Tracker from explicit item hints (#6946)

Only apply with clear hints & when hint is for a unique RHT
This commit is contained in:
Mark Eldridge
2026-07-25 10:42:02 +09:30
committed by GitHub
parent c4e92a70f4
commit 783139310a
2 changed files with 81 additions and 10 deletions
@@ -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)
@@ -17,6 +17,7 @@
#include "soh/ObjectExtension/ObjectExtension.h"
#include "overlays/actors/ovl_En_GirlA/z_en_girla.h"
#include <array>
#include <string>
#include <sstream>
#include <vector>
@@ -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<uint16_t, RHT_MAX> 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<GameInteractor::OnSceneFlagSet>(CheckTrackerSceneFlagSet);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnFlagSet>(CheckTrackerFlagSet);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnDialogMessage>(CheckTrackerDialogMessage);
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnRandoHintRevealed>(CheckTrackerHintRevealed);
}
void CheckTrackerWindow::UpdateElement() {