From 9c24ccec1aabeec9ac1984f3364cb9f22d3fe248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Sat, 27 Dec 2025 11:15:31 +0000 Subject: [PATCH] Fairysanity: allow using bottle (#6021) Based on fishsanity, which is refactored with hooks Also open up logic catching fairies at oasis if player has bottle --- .../randomizer/ShuffleFairies.cpp | 17 +++-- .../Enhancements/randomizer/fishsanity.cpp | 69 ++++++++++--------- soh/soh/Enhancements/randomizer/fishsanity.h | 20 ------ .../Enhancements/randomizer/hook_handlers.cpp | 32 --------- .../overworld/desert_colossus.cpp | 2 +- 5 files changed, 47 insertions(+), 93 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/ShuffleFairies.cpp b/soh/soh/Enhancements/randomizer/ShuffleFairies.cpp index d4a58ee55..0801da406 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleFairies.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleFairies.cpp @@ -98,17 +98,22 @@ void RegisterShuffleFairies() { // Grant item when picking up fairy. COND_VB_SHOULD(VB_FAIRY_HEAL, shouldRegister, { EnElf* enElf = va_arg(args, EnElf*); - const auto fairyIdentity = ObjectExtension::GetInstance().Get(&enElf->actor); - if (fairyIdentity == nullptr) { - return; - } - - if (fairyIdentity != nullptr && fairyIdentity->randomizerInf && fairyIdentity->randomizerInf != RAND_INF_MAX) { + if (fairyIdentity != nullptr && fairyIdentity->randomizerInf != RAND_INF_MAX) { Flags_SetRandomizerInf(fairyIdentity->randomizerInf); } }); + COND_VB_SHOULD(VB_BOTTLE_ACTOR, shouldRegister, { + Actor* actor = va_arg(args, Actor*); + const auto fairyIdentity = ObjectExtension::GetInstance().Get(actor); + if (fairyIdentity != nullptr && fairyIdentity->randomizerInf != RAND_INF_MAX) { + Flags_SetRandomizerInf(fairyIdentity->randomizerInf); + actor->parent = &GET_PLAYER(gPlayState)->actor; + *should = false; + } + }); + // Spawn fairies in fairy fountains COND_VB_SHOULD(VB_SPAWN_FOUNTAIN_FAIRIES, shouldRegisterFountain, { Actor* actor = va_arg(args, Actor*); diff --git a/soh/soh/Enhancements/randomizer/fishsanity.cpp b/soh/soh/Enhancements/randomizer/fishsanity.cpp index 83d326384..8cf5ec23c 100644 --- a/soh/soh/Enhancements/randomizer/fishsanity.cpp +++ b/soh/soh/Enhancements/randomizer/fishsanity.cpp @@ -51,11 +51,12 @@ ActorFunc drawFishing = NULL; ActorFunc drawEnFish = NULL; Color_RGB8 fsPulseColor = { 30, 240, 200 }; +static s16 fishGroupCounter = 0; +static bool enableAdvance = false; + namespace Rando { const FishIdentity Fishsanity::defaultIdentity = { RAND_INF_MAX, RC_UNKNOWN_CHECK }; bool Fishsanity::fishsanityHelpersInit = false; -s16 Fishsanity::fishGroupCounter = 0; -bool Fishsanity::enableAdvance = false; std::unordered_map Fishsanity::pondFishAgeMap; std::vector Fishsanity::childPondFish; std::vector Fishsanity::adultPondFish; @@ -447,38 +448,6 @@ void Fishsanity::OnActorUpdateHandler(void* refActor) { fishGroupCounter = 0; } } - -void Fishsanity::OnSceneInitHandler(int16_t sceneNum) { - if (sceneNum == SCENE_ZORAS_DOMAIN) { - fishGroupCounter = 0; - } -} - -void Fishsanity::OnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) { - va_list args; - va_copy(args, originalArgs); - - Actor* actor = va_arg(args, Actor*); - auto fs = OTRGlobals::Instance->gRandoContext->GetFishsanity(); - - va_end(args); - - if (id == VB_BOTTLE_ACTOR && actor->id == ACTOR_EN_FISH && fs->GetOverworldFishShuffled()) { - FishIdentity fish = OTRGlobals::Instance->gRandomizer->IdentifyFish(gPlayState->sceneNum, actor->params); - if (fish.randomizerCheck != RC_UNKNOWN_CHECK && !Flags_GetRandomizerInf(fish.randomizerInf)) { - Flags_SetRandomizerInf(fish.randomizerInf); - actor->parent = &GET_PLAYER(gPlayState)->actor; - *should = false; - } - } -} - -void Fishsanity::OnItemReceiveHandler(GetItemEntry itemEntry) { - if (enableAdvance) { - enableAdvance = false; - OTRGlobals::Instance->gRandoContext->GetFishsanity()->AdvancePond(); - } -} } // namespace Rando // C interface @@ -575,6 +544,37 @@ void Fishsanity_CloseGreyscaleColor(PlayState* play) { } } +void RegisterShuffleFish() { + bool shouldRegister = IS_RANDO && RAND_GET_OPTION(RSK_FISHSANITY) != RO_FISHSANITY_OFF; + COND_HOOK(OnSceneInit, shouldRegister, [](int16_t sceneNum) { + if (sceneNum == SCENE_ZORAS_DOMAIN) { + fishGroupCounter = 0; + } + }); + + COND_HOOK(OnActorInit, shouldRegister, Rando::Fishsanity::OnActorInitHandler); + COND_HOOK(OnActorUpdate, shouldRegister, Rando::Fishsanity::OnActorUpdateHandler); + COND_HOOK(OnItemReceive, shouldRegister, [](GetItemEntry itemEntry) { + if (enableAdvance) { + enableAdvance = false; + OTRGlobals::Instance->gRandoContext->GetFishsanity()->AdvancePond(); + } + }); + + COND_VB_SHOULD(VB_BOTTLE_ACTOR, shouldRegister, { + Actor* actor = va_arg(args, Actor*); + auto fs = OTRGlobals::Instance->gRandoContext->GetFishsanity(); + if (actor->id == ACTOR_EN_FISH && fs->GetOverworldFishShuffled()) { + FishIdentity fish = OTRGlobals::Instance->gRandomizer->IdentifyFish(gPlayState->sceneNum, actor->params); + if (fish.randomizerCheck != RC_UNKNOWN_CHECK && !Flags_GetRandomizerInf(fish.randomizerInf)) { + Flags_SetRandomizerInf(fish.randomizerInf); + actor->parent = &GET_PLAYER(gPlayState)->actor; + *should = false; + } + } + }); +} + void Rando::StaticData::RegisterFishLocations() { static bool registered = false; if (registered) @@ -634,4 +634,5 @@ void Rando::StaticData::RegisterFishLocations() { // clang-format on } +static RegisterShipInitFunc registerShuffleFish(RegisterShuffleFish, { "IS_RANDO" }); static RegisterShipInitFunc initFunc(Rando::StaticData::RegisterFishLocations); diff --git a/soh/soh/Enhancements/randomizer/fishsanity.h b/soh/soh/Enhancements/randomizer/fishsanity.h index 16271f28f..1584d1387 100644 --- a/soh/soh/Enhancements/randomizer/fishsanity.h +++ b/soh/soh/Enhancements/randomizer/fishsanity.h @@ -137,28 +137,11 @@ class Fishsanity { */ static void OnActorInitHandler(void* refActor); - /** - * @brief PlayerUpdate hook handler for fishsanity - */ - static void OnPlayerUpdateHandler(); - /** * @brief ActorUpdate hook handler for fishsanity */ static void OnActorUpdateHandler(void* refActor); - /** - * @brief SceneInit hook handler for fishsanity - */ - static void OnSceneInitHandler(int16_t sceneNum); - - /** - * @brief VB hook handler for fishsanity - */ - static void OnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs); - - static void OnItemReceiveHandler(GetItemEntry itemEntry); - private: /** * @brief Initialize helper statics if they have not been initialized yet @@ -184,9 +167,6 @@ class Fishsanity { */ static bool fishsanityHelpersInit; - static s16 fishGroupCounter; - static bool enableAdvance; - ///////////////////////////////////////////////////////// //// Helper data structures derived from static data //// ///////////////////////////////////////////////////////// diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index fcadbb534..23f938d79 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -6,7 +6,6 @@ #include "soh/Enhancements/item-tables/ItemTableManager.h" #include "soh/Enhancements/randomizer/randomizerTypes.h" #include "soh/Enhancements/randomizer/dungeon.h" -#include "soh/Enhancements/randomizer/fishsanity.h" #include "soh/Enhancements/randomizer/static_data.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" @@ -2438,12 +2437,6 @@ void RandomizerRegisterHooks() { static uint32_t onKaleidoUpdateHook = 0; static uint32_t onCuccoOrChickenHatchHook = 0; - static uint32_t fishsanityOnActorInitHook = 0; - static uint32_t fishsanityOnActorUpdateHook = 0; - static uint32_t fishsanityOnSceneInitHook = 0; - static uint32_t fishsanityOnVanillaBehaviorHook = 0; - static uint32_t fishsanityOnItemReceiveHook = 0; - GameInteractor::Instance->RegisterGameHook([](int32_t fileNum) { ShipInit::Init("IS_RANDO"); @@ -2470,13 +2463,6 @@ void RandomizerRegisterHooks() { GameInteractor::Instance->UnregisterGameHook(onKaleidoUpdateHook); GameInteractor::Instance->UnregisterGameHook(onCuccoOrChickenHatchHook); - GameInteractor::Instance->UnregisterGameHook(fishsanityOnActorInitHook); - GameInteractor::Instance->UnregisterGameHook(fishsanityOnActorUpdateHook); - GameInteractor::Instance->UnregisterGameHook(fishsanityOnSceneInitHook); - GameInteractor::Instance->UnregisterGameHook( - fishsanityOnVanillaBehaviorHook); - GameInteractor::Instance->UnregisterGameHook(fishsanityOnItemReceiveHook); - onFlagSetHook = 0; onSceneFlagSetHook = 0; onPlayerUpdateForRCQueueHook = 0; @@ -2496,12 +2482,6 @@ void RandomizerRegisterHooks() { onKaleidoUpdateHook = 0; onCuccoOrChickenHatchHook = 0; - fishsanityOnActorInitHook = 0; - fishsanityOnActorUpdateHook = 0; - fishsanityOnSceneInitHook = 0; - fishsanityOnVanillaBehaviorHook = 0; - fishsanityOnItemReceiveHook = 0; - if (!IS_RANDO) return; @@ -2553,18 +2533,6 @@ void RandomizerRegisterHooks() { if (RAND_GET_OPTION(RSK_FISHSANITY) != RO_FISHSANITY_OFF) { OTRGlobals::Instance->gRandoContext->GetFishsanity()->InitializeFromSave(); - - fishsanityOnActorInitHook = GameInteractor::Instance->RegisterGameHook( - Rando::Fishsanity::OnActorInitHandler); - fishsanityOnActorUpdateHook = GameInteractor::Instance->RegisterGameHook( - Rando::Fishsanity::OnActorUpdateHandler); - fishsanityOnSceneInitHook = GameInteractor::Instance->RegisterGameHook( - Rando::Fishsanity::OnSceneInitHandler); - fishsanityOnVanillaBehaviorHook = - GameInteractor::Instance->RegisterGameHook( - Rando::Fishsanity::OnVanillaBehaviorHandler); - fishsanityOnItemReceiveHook = GameInteractor::Instance->RegisterGameHook( - Rando::Fishsanity::OnItemReceiveHandler); } }); } diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/desert_colossus.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/desert_colossus.cpp index 753086218..f449799d9 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/desert_colossus.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/desert_colossus.cpp @@ -23,7 +23,7 @@ void RegionTable_Init_DesertColossus() { }, { //Exits //You can kinda get the fairies without entering the water, but it relies on them cooperating and leevers are jerks. should be a trick - Entrance(RR_DESERT_COLOSSUS_OASIS, []{return logic->CanUse(RG_SONG_OF_STORMS) && (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_IRON_BOOTS));}), + Entrance(RR_DESERT_COLOSSUS_OASIS, []{return logic->CanUse(RG_SONG_OF_STORMS) && (logic->HasItem(RG_BRONZE_SCALE) || logic->CanUse(RG_IRON_BOOTS) || logic->CanUse(RG_EMPTY_BOTTLE));}), Entrance(RR_COLOSSUS_GREAT_FAIRY_FOUNTAIN, []{return logic->HasExplosives();}), Entrance(RR_SPIRIT_TEMPLE_ENTRYWAY, []{return true;}), Entrance(RR_WASTELAND_NEAR_COLOSSUS, []{return true;}),