From ca0a17cfe717a1760e040e519a033b621c73d3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= <159546+serprex@users.noreply.github.com> Date: Fri, 14 Aug 2026 22:24:03 +0000 Subject: [PATCH] bombchu bowling setting: first item (#7070) desirable for speedrun settings that want less variance. also use for moving rando logic into hooks --- .../Enhancements/Minigames/BombchuBowling.cpp | 17 +++++ soh/soh/Enhancements/enhancementTypes.h | 10 +++ .../vanilla-behavior/GIVanillaBehavior.h | 17 +++++ soh/soh/Enhancements/randomizer/SeedContext.h | 1 - .../Enhancements/randomizer/hook_handlers.cpp | 31 +++++++++ soh/soh/SohGui/SohMenuEnhancements.cpp | 19 +++++- .../ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c | 67 +++++++------------ 7 files changed, 118 insertions(+), 44 deletions(-) diff --git a/soh/soh/Enhancements/Minigames/BombchuBowling.cpp b/soh/soh/Enhancements/Minigames/BombchuBowling.cpp index 24ae8246c4..1d734d7f15 100644 --- a/soh/soh/Enhancements/Minigames/BombchuBowling.cpp +++ b/soh/soh/Enhancements/Minigames/BombchuBowling.cpp @@ -1,8 +1,10 @@ +#include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ShipInit.hpp" extern "C" { #include "functions.h" +#include "src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.h" extern PlayState* gPlayState; } @@ -19,6 +21,9 @@ static constexpr s32 CUCCO_BOWLING_AMMO_DEFAULT = 10; #define CVAR_BOWLING_AMMO_NAME CVAR_ENHANCEMENT("BombchuBowlingAmmo") #define CVAR_BOWLING_AMMO_VALUE CVarGetInteger(CVAR_BOWLING_AMMO_NAME, CUCCO_BOWLING_AMMO_DEFAULT) +#define CVAR_BOWLING_FIRST_PRIZE_NAME CVAR_ENHANCEMENT("BombchuBowlingFirstPrize") +#define CVAR_BOWLING_FIRST_PRIZE_VALUE CVarGetInteger(CVAR_BOWLING_FIRST_PRIZE_NAME, BOWLING_FIRST_PRIZE_RANDOM) + static constexpr f32 CUCCO_SEARCH_Z = -520.0f; typedef enum { @@ -88,8 +93,20 @@ static void RegisterBombchuBowlingAmmo() { }); } +static void RegisterBombchuBowlingFirstPrize() { + COND_VB_SHOULD(VB_SET_BOMBCHU_BOWLING_PRIZE_SELECT, + !IS_RANDO && CVAR_BOWLING_VALUE && (CVAR_BOWLING_FIRST_PRIZE_VALUE != BOWLING_FIRST_PRIZE_RANDOM), { + EnBomBowlMan* bowlMan = va_arg(args, EnBomBowlMan*); + s16 prizeSelect = CVAR_BOWLING_FIRST_PRIZE_VALUE - 1; + bowlMan->prizeSelect = prizeSelect; + *should = false; + }); +} + static RegisterShipInitFunc initFunc_SmallCucco(RegisterBombchuBowlingNoSmallCucco, { CVAR_BOWLING_NAME, CVAR_CUCCO_SMALL_NAME }); static RegisterShipInitFunc initFunc_BigCucco(RegisterBombchuBowlingNoBigCucco, { CVAR_BOWLING_NAME, CVAR_CUCCO_BIG_NAME }); static RegisterShipInitFunc initFunc_Ammo(RegisterBombchuBowlingAmmo, { CVAR_BOWLING_NAME, CVAR_BOWLING_AMMO_NAME }); +static RegisterShipInitFunc initFunc_FirstPrize(RegisterBombchuBowlingFirstPrize, + { CVAR_BOWLING_NAME, CVAR_BOWLING_FIRST_PRIZE_NAME, "IS_RANDO" }); diff --git a/soh/soh/Enhancements/enhancementTypes.h b/soh/soh/Enhancements/enhancementTypes.h index 6dced42635..e235bba4a7 100644 --- a/soh/soh/Enhancements/enhancementTypes.h +++ b/soh/soh/Enhancements/enhancementTypes.h @@ -124,6 +124,16 @@ typedef enum { INGO_RACE_NONE, } IngoRaceType; +// The Bombchu Bowling prize cycle always runs in this order, only the entry it starts on is random +typedef enum { + BOWLING_FIRST_PRIZE_RANDOM, + BOWLING_FIRST_PRIZE_BOMB_BAG, + BOWLING_FIRST_PRIZE_PURPLE_RUPEE, + BOWLING_FIRST_PRIZE_BOMBCHUS, + BOWLING_FIRST_PRIZE_HEART_PIECE, + BOWLING_FIRST_PRIZE_BOMBS, +} BombchuBowlingFirstPrize; + typedef enum { RANDOMIZE_OFF, RANDOMIZE_ON_NEW_SCENE, diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index e41669089d..85d1483335 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2477,6 +2477,23 @@ typedef enum { // - `None` VB_SET_BOMBCHU_BOWLING_AMMO, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnBomBowlMan` + // - `s16*` (prize to show, an `ExItemType`) + VB_SET_BOMBCHU_BOWLING_PRIZE, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnBomBowlMan` + VB_SET_BOMBCHU_BOWLING_PRIZE_SELECT, + // #### `result` // ```c // true diff --git a/soh/soh/Enhancements/randomizer/SeedContext.h b/soh/soh/Enhancements/randomizer/SeedContext.h index 5dc7d6ee25..8630e600ce 100644 --- a/soh/soh/Enhancements/randomizer/SeedContext.h +++ b/soh/soh/Enhancements/randomizer/SeedContext.h @@ -1,7 +1,6 @@ #pragma once #include "randomizerTypes.h" -#include "z64save.h" #include "item_override.h" #include "hint.h" #include "fishsanity.h" diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 78df18747e..fc4c7b9068 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -45,6 +45,7 @@ extern "C" { #include "src/overlays/actors/ovl_En_Box/z_en_box.h" #include "src/overlays/actors/ovl_En_Skj/z_en_skj.h" #include "src/overlays/actors/ovl_En_Hy/z_en_hy.h" +#include "src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.h" #include "src/overlays/actors/ovl_En_Bom_Bowl_Pit/z_en_bom_bowl_pit.h" #include "src/overlays/actors/ovl_En_Ge1/z_en_ge1.h" #include "src/overlays/actors/ovl_En_Ge2/z_en_ge2.h" @@ -1940,6 +1941,36 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l *should = INV_CONTENT((RAND_GET_OPTION(RSK_BOMBCHU_BAG) ? ITEM_BOMBCHU : ITEM_BOMB)) != ITEM_NONE; break; } + case VB_SET_BOMBCHU_BOWLING_PRIZE_SELECT: { + EnBomBowlMan* bowlMan = va_arg(args, EnBomBowlMan*); + bowlMan->prizeSelect = 0; + *should = false; + break; + } + case VB_SET_BOMBCHU_BOWLING_PRIZE: { + EnBomBowlMan* bowlMan = va_arg(args, EnBomBowlMan*); + s16* prize = va_arg(args, s16*); + switch (bowlMan->prizeSelect) { + case 0: + *prize = Flags_GetItemGetInf(ITEMGETINF_11) ? EXITEM_PURPLE_RUPEE_BOWLING : EXITEM_BOMB_BAG_BOWLING; + break; + case 1: + *prize = + Flags_GetItemGetInf(ITEMGETINF_12) ? EXITEM_PURPLE_RUPEE_BOWLING : EXITEM_HEART_PIECE_BOWLING; + break; + case 2: + *prize = EXITEM_BOMBCHUS_BOWLING; + break; + case 3: + *prize = EXITEM_PURPLE_RUPEE_BOWLING; + break; + case 4: + *prize = EXITEM_BOMBS_BOWLING; + break; + } + *should = false; + break; + } case VB_SHOULD_CHECK_FOR_FISHING_RECORD: { f32 sFishOnHandLength = *va_arg(args, f32*); *should = *should || ShouldGiveFishingPrize(sFishOnHandLength); diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 9393eaf95b..5672a45f79 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -5,13 +5,14 @@ #include #include #include -#include "soh/Enhancements/randomizer/randomizer.h" #include "soh/Enhancements/Restorations/GetItemManipulation.h" +#include "soh/Enhancements/randomizer/SeedContext.h" #include extern "C" { #include "functions.h" #include "variables.h" +#include "macros.h" extern PlayState* gPlayState; } @@ -87,6 +88,15 @@ static const std::map sleepingWaterfallOptions = { { WATERFALL_NEVER, "Never" }, }; +static const std::map bombchuBowlingFirstPrizeOptions = { + { BOWLING_FIRST_PRIZE_RANDOM, "Random" }, + { BOWLING_FIRST_PRIZE_BOMB_BAG, "Bomb Bag" }, + { BOWLING_FIRST_PRIZE_PURPLE_RUPEE, "Purple Rupee" }, + { BOWLING_FIRST_PRIZE_BOMBCHUS, "Bombchus" }, + { BOWLING_FIRST_PRIZE_HEART_PIECE, "Heart Piece" }, + { BOWLING_FIRST_PRIZE_BOMBS, "Bombs" }, +}; + static const std::map allPowers = { { DAMAGE_VANILLA, "Vanilla (1x)" }, { DAMAGE_DOUBLE, "Double (2x)" }, { DAMAGE_QUADRUPLE, "Quadruple (4x)" }, { DAMAGE_OCTUPLE, "Octuple (8x)" }, @@ -1524,6 +1534,13 @@ void SohMenu::AddMenuEnhancements() { .DefaultValue(10) .Format("%d bombchus") .Tooltip("The number of Bombchus available at the start of the Bombchu Bowling minigame.")); + AddWidget(path, "First Prize", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("BombchuBowlingFirstPrize")) + .PreFunc(bombchuBowlingDisabledFunc) + .Options(ComboboxOptions() + .ComboMap(bombchuBowlingFirstPrizeOptions) + .DefaultIndex(BOWLING_FIRST_PRIZE_RANDOM) + .Tooltip("The prize the cycle starts on.")); AddWidget(path, "Horseback Archery", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Customize Behavior##HBA", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("CustomizeHorsebackArchery")) diff --git a/soh/src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c b/soh/src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c index ea9b13a355..baf0a27d02 100644 --- a/soh/src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c +++ b/soh/src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c @@ -3,8 +3,6 @@ #include "overlays/actors/ovl_En_Syateki_Niw/z_en_syateki_niw.h" #include "overlays/actors/ovl_En_Ex_Item/z_en_ex_item.h" #include "objects/object_bg/object_bg.h" -#include "soh/OTRGlobals.h" -#include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS \ @@ -88,7 +86,9 @@ void EnBomBowlMan_Init(Actor* thisx, PlayState* play2) { } } - this->prizeSelect = IS_RANDO ? 0 : (s16)Rand_ZeroFloat(4.99f); + if (GameInteractor_Should(VB_SET_BOMBCHU_BOWLING_PRIZE_SELECT, true, this)) { + this->prizeSelect = (s16)Rand_ZeroFloat(4.99f); + } this->actor.targetMode = 1; this->actionFunc = EnBomBowMan_SetupWaitAsleep; } @@ -174,17 +174,9 @@ void EnBomBowMan_CheckBeatenDC(EnBomBowlMan* this, PlayState* play) { this->eyeMode = CHU_GIRL_EYES_AWAKE; this->blinkTimer = (s16)Rand_ZeroFloat(60.0f) + 20; - bool bombchuBowlingClosed; - if (IS_RANDO) { - // when rando'd, check if we have bombchus if chus are in logic - // and check if we have a bomb bag if chus aren't in logic - u8 explosive = Randomizer_GetSettingValue(RSK_BOMBCHU_BAG) ? ITEM_BOMBCHU : ITEM_BOMB; - bombchuBowlingClosed = (INV_CONTENT(explosive) == ITEM_NONE); - } else { - // if not rando'd, check if we have beaten Dodongo's Cavern - bombchuBowlingClosed = !((Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) || BREG(2)); - } - if (bombchuBowlingClosed) { + // Check for beaten Dodongo's Cavern + if (!GameInteractor_Should(VB_BE_ABLE_TO_PLAY_BOMBCHU_BOWLING, + (Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) || BREG(2))) { this->actionFunc = EnBomBowMan_WaitNotBeatenDC; } else { this->actor.textId = 0x18; @@ -407,39 +399,30 @@ void EnBomBowMan_ChooseShowPrize(EnBomBowlMan* this, PlayState* play) { SkelAnime_Update(&this->skelAnime); if (this->prizeRevealTimer == 0) { - switch (this->prizeSelect) { - case 0: - prizeTemp = EXITEM_BOMB_BAG_BOWLING; - if (Flags_GetItemGetInf(ITEMGETINF_11)) { + if (GameInteractor_Should(VB_SET_BOMBCHU_BOWLING_PRIZE, true, this, &prizeTemp)) { + switch (this->prizeSelect) { + case 0: + prizeTemp = EXITEM_BOMB_BAG_BOWLING; + if (Flags_GetItemGetInf(ITEMGETINF_11)) { + prizeTemp = EXITEM_PURPLE_RUPEE_BOWLING; + } + break; + case 1: prizeTemp = EXITEM_PURPLE_RUPEE_BOWLING; - } - break; - case 1: - if (!IS_RANDO) { - prizeTemp = EXITEM_PURPLE_RUPEE_BOWLING; - } else { + break; + case 2: + prizeTemp = EXITEM_BOMBCHUS_BOWLING; + break; + case 3: prizeTemp = EXITEM_HEART_PIECE_BOWLING; if (Flags_GetItemGetInf(ITEMGETINF_12)) { prizeTemp = EXITEM_PURPLE_RUPEE_BOWLING; } - } - break; - case 2: - prizeTemp = EXITEM_BOMBCHUS_BOWLING; - break; - case 3: - if (!IS_RANDO) { - prizeTemp = EXITEM_HEART_PIECE_BOWLING; - if (Flags_GetItemGetInf(ITEMGETINF_12)) { - prizeTemp = EXITEM_PURPLE_RUPEE_BOWLING; - } - } else { - prizeTemp = EXITEM_PURPLE_RUPEE_BOWLING; - } - break; - case 4: - prizeTemp = EXITEM_BOMBS_BOWLING; - break; + break; + case 4: + prizeTemp = EXITEM_BOMBS_BOWLING; + break; + } } this->prizeIndex = prizeTemp;