From 1be7533675660ec7c33fe303d707e8bac486a4ac Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 28 Mar 2026 13:52:55 -0400 Subject: [PATCH 1/8] Conslidate ganon2 hooks (#6432) --- .../Enhancements/GameplayStats/BossDefeatTimestamps.cpp | 8 ++++---- soh/soh/Enhancements/game-interactor/GameInteractor.h | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp b/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp index dff3bc9125..1cb05814b5 100644 --- a/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp +++ b/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp @@ -17,10 +17,10 @@ static void RegisterBossDefeatTimestamps() { BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_SST, TIMESTAMP_DEFEAT_BONGO_BONGO); BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_TW, TIMESTAMP_DEFEAT_TWINROVA); BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_GANON, TIMESTAMP_DEFEAT_GANONDORF); - BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_GANON2, TIMESTAMP_DEFEAT_GANON); - - COND_ID_HOOK(OnBossDefeat, ACTOR_BOSS_GANON2, true, - [](void* refActor) { gSaveContext.ship.stats.gameComplete = true; }); + COND_ID_HOOK(OnBossDefeat, ACTOR_BOSS_GANON2, true, [](void* refActor) { + gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.ship.stats.gameComplete = true; + }); } static RegisterShipInitFunc initFunc(RegisterBossDefeatTimestamps); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index bcd84d552f..20267760bb 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -225,6 +225,9 @@ class GameInteractor { static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect& effect); // Game Hooks + // + // Hooks should be idempotent and execution order is not guaranteed. + // If two operations must happen in a specific order, they should be placed in the same hook. HOOK_ID nextHookId = 1; template struct RegisteredGameHooks { From 0b3ebd584d1e8d5b7669f3f3ba09e110dd8e9e17 Mon Sep 17 00:00:00 2001 From: chartergirl64 Date: Sun, 29 Mar 2026 03:32:40 -0400 Subject: [PATCH 2/8] [bug fix] Changes item tracker capacity to recognize progressive chu bag limits (#6436) --- .../randomizer/randomizer_item_tracker.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index c2d6cf3be1..0ccc205a6d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -537,11 +537,24 @@ ItemTrackerNumbers GetItemCurrentAndMax(ItemTrackerItem item) { : 500; result.currentAmmo = gSaveContext.rupees; break; - case ITEM_BOMBCHU: - result.currentCapacity = INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU ? 50 : 0; + case ITEM_BOMBCHU: { + auto bombchuBag = RAND_GET_OPTION(RSK_BOMBCHU_BAG); + + uint8_t capacity = 0; + + if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU) { + if (bombchuBag.Is(RO_BOMBCHU_BAG_PROGRESSIVE)) { + capacity = OTRGlobals::Instance->gRandoContext->GetBombchuCapacity(); + } else { + capacity = 50; + } + } + + result.currentCapacity = capacity; result.maxCapacity = 50; result.currentAmmo = AMMO(ITEM_BOMBCHU); break; + } case ITEM_BEAN: result.currentCapacity = INV_CONTENT(ITEM_BEAN) == ITEM_BEAN ? 10 : 0; result.maxCapacity = 10; @@ -702,7 +715,7 @@ void DrawItemCount(ItemTrackerItem item, bool hideMax) { bool shouldDisplayAmmo = trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_AMMO || trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_AMMO_ONLY || // These items have a static capacity, so display ammo instead - item.id == ITEM_BOMBCHU || item.id == ITEM_BEAN || item.id == QUEST_SKULL_TOKEN || + item.id == ITEM_BEAN || item.id == QUEST_SKULL_TOKEN || item.id == ITEM_HEART_CONTAINER || item.id == ITEM_HEART_PIECE; bool shouldDisplayMax = !(trackerNumberDisplayMode == ITEM_TRACKER_NUMBER_CURRENT_CAPACITY_ONLY || From 4729eef7c8a21804e1a04262c39561e8edc0830e Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 29 Mar 2026 03:34:59 -0400 Subject: [PATCH 3/8] Make item category adjustments more consistent (#6434) --- .../Enhancements/randomizer/ShuffleCrates.cpp | 39 ++----------------- .../Enhancements/randomizer/ShuffleGrass.cpp | 3 +- .../Enhancements/randomizer/ShufflePots.cpp | 3 +- .../Enhancements/randomizer/ShuffleTrees.cpp | 20 +--------- .../randomizer/item_category_adj.cpp | 28 +++++++++++++ .../randomizer/item_category_adj.h | 18 +++++++++ soh/src/overlays/actors/ovl_En_Box/z_en_box.c | 17 +------- 7 files changed, 57 insertions(+), 71 deletions(-) create mode 100644 soh/soh/Enhancements/randomizer/item_category_adj.cpp create mode 100644 soh/soh/Enhancements/randomizer/item_category_adj.h diff --git a/soh/soh/Enhancements/randomizer/ShuffleCrates.cpp b/soh/soh/Enhancements/randomizer/ShuffleCrates.cpp index aed35dfa7b..a1216ce010 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleCrates.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleCrates.cpp @@ -4,6 +4,7 @@ #include #include "global.h" #include "soh/ObjectExtension/ObjectExtension.h" +#include "item_category_adj.h" extern "C" { #include "variables.h" @@ -35,24 +36,7 @@ extern "C" void ObjKibako2_RandomizerDraw(Actor* thisx, PlayState* play) { GetItemEntry crateItem = Rando::Context::GetInstance()->GetFinalGIEntry(crateIdentity->randomizerCheck, true, GI_NONE); - getItemCategory = crateItem.getItemCategory; - - // If they have bombchus, don't consider the bombchu item major - if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU && - ((crateItem.modIndex == MOD_RANDOMIZER && crateItem.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) || - (crateItem.modIndex == MOD_NONE && - (crateItem.getItemId == GI_BOMBCHUS_5 || crateItem.getItemId == GI_BOMBCHUS_10 || - crateItem.getItemId == GI_BOMBCHUS_20)))) { - getItemCategory = ITEM_CATEGORY_JUNK; - // If it's a bottle and they already have one, consider the item lesser - } else if ((crateItem.modIndex == MOD_RANDOMIZER && crateItem.getItemId >= RG_BOTTLE_WITH_RED_POTION && - crateItem.getItemId <= RG_BOTTLE_WITH_POE) || - (crateItem.modIndex == MOD_NONE && - (crateItem.getItemId == GI_BOTTLE || crateItem.getItemId == GI_MILK_BOTTLE))) { - if (gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE) { - getItemCategory = ITEM_CATEGORY_LESSER; - } - } + getItemCategory = Randomizer_AdjustItemCategory(crateItem); // Change texture switch (getItemCategory) { @@ -102,24 +86,7 @@ extern "C" void ObjKibako_RandomizerDraw(Actor* thisx, PlayState* play) { GetItemEntry smallCrateItem = Rando::Context::GetInstance()->GetFinalGIEntry(crateIdentity->randomizerCheck, true, GI_NONE); - getItemCategory = smallCrateItem.getItemCategory; - - // If they have bombchus, don't consider the bombchu item major - if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU && - ((smallCrateItem.modIndex == MOD_RANDOMIZER && smallCrateItem.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) || - (smallCrateItem.modIndex == MOD_NONE && - (smallCrateItem.getItemId == GI_BOMBCHUS_5 || smallCrateItem.getItemId == GI_BOMBCHUS_10 || - smallCrateItem.getItemId == GI_BOMBCHUS_20)))) { - getItemCategory = ITEM_CATEGORY_JUNK; - // If it's a bottle and they already have one, consider the item lesser - } else if ((smallCrateItem.modIndex == MOD_RANDOMIZER && smallCrateItem.getItemId >= RG_BOTTLE_WITH_RED_POTION && - smallCrateItem.getItemId <= RG_BOTTLE_WITH_POE) || - (smallCrateItem.modIndex == MOD_NONE && - (smallCrateItem.getItemId == GI_BOTTLE || smallCrateItem.getItemId == GI_MILK_BOTTLE))) { - if (gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE) { - getItemCategory = ITEM_CATEGORY_LESSER; - } - } + getItemCategory = Randomizer_AdjustItemCategory(smallCrateItem); // Change texture switch (getItemCategory) { diff --git a/soh/soh/Enhancements/randomizer/ShuffleGrass.cpp b/soh/soh/Enhancements/randomizer/ShuffleGrass.cpp index 351cbda26b..14053bff55 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleGrass.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleGrass.cpp @@ -1,6 +1,7 @@ #include #include "soh_assets.h" #include "static_data.h" +#include "item_category_adj.h" #include "soh/ObjectExtension/ObjectExtension.h" extern "C" { @@ -38,7 +39,7 @@ extern "C" void EnKusa_RandomizerDraw(Actor* thisx, PlayState* play) { if (csmc && (!requiresStoneAgony || (requiresStoneAgony && CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY)))) { auto itemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(grassIdentity->randomizerCheck, true, GI_NONE); - GetItemCategory getItemCategory = itemEntry.getItemCategory; + GetItemCategory getItemCategory = Randomizer_AdjustItemCategory(itemEntry); switch (getItemCategory) { case ITEM_CATEGORY_JUNK: diff --git a/soh/soh/Enhancements/randomizer/ShufflePots.cpp b/soh/soh/Enhancements/randomizer/ShufflePots.cpp index 08b7a7c0c9..3dd2c43837 100644 --- a/soh/soh/Enhancements/randomizer/ShufflePots.cpp +++ b/soh/soh/Enhancements/randomizer/ShufflePots.cpp @@ -1,6 +1,7 @@ #include "soh/OTRGlobals.h" #include "soh_assets.h" #include "static_data.h" +#include "item_category_adj.h" #include "soh/ObjectExtension/ObjectExtension.h" extern "C" { @@ -28,7 +29,7 @@ extern "C" void ObjTsubo_RandomizerDraw(Actor* thisx, PlayState* play) { if (csmc && (!requiresStoneAgony || (requiresStoneAgony && CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY)))) { auto itemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(potIdentity->randomizerCheck, true, GI_NONE); - GetItemCategory getItemCategory = itemEntry.getItemCategory; + GetItemCategory getItemCategory = Randomizer_AdjustItemCategory(itemEntry); switch (getItemCategory) { case ITEM_CATEGORY_LESSER: diff --git a/soh/soh/Enhancements/randomizer/ShuffleTrees.cpp b/soh/soh/Enhancements/randomizer/ShuffleTrees.cpp index fb8f922a92..da99a2dd66 100644 --- a/soh/soh/Enhancements/randomizer/ShuffleTrees.cpp +++ b/soh/soh/Enhancements/randomizer/ShuffleTrees.cpp @@ -2,6 +2,7 @@ #include "soh_assets.h" #include "static_data.h" #include "soh/ObjectExtension/ObjectExtension.h" +#include "item_category_adj.h" extern "C" { #include "variables.h" @@ -60,24 +61,7 @@ extern "C" void EnWood02_RandomizerDraw(Actor* thisx, PlayState* play) { getItemCategory = ITEM_CATEGORY_JUNK; } else { treeItem = Rando::Context::GetInstance()->GetFinalGIEntry(treeIdentity->randomizerCheck, true, GI_NONE); - getItemCategory = treeItem.getItemCategory; - - // If they have bombchus, don't consider the bombchu item major - if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU && - ((treeItem.modIndex == MOD_RANDOMIZER && treeItem.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) || - (treeItem.modIndex == MOD_NONE && - (treeItem.getItemId == GI_BOMBCHUS_5 || treeItem.getItemId == GI_BOMBCHUS_10 || - treeItem.getItemId == GI_BOMBCHUS_20)))) { - getItemCategory = ITEM_CATEGORY_JUNK; - // If it's a bottle and they already have one, consider the item lesser - } else if ((treeItem.modIndex == MOD_RANDOMIZER && treeItem.getItemId >= RG_BOTTLE_WITH_RED_POTION && - treeItem.getItemId <= RG_BOTTLE_WITH_POE) || - (treeItem.modIndex == MOD_NONE && - (treeItem.getItemId == GI_BOTTLE || treeItem.getItemId == GI_MILK_BOTTLE))) { - if (gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE) { - getItemCategory = ITEM_CATEGORY_LESSER; - } - } + getItemCategory = Randomizer_AdjustItemCategory(treeItem); } GraphicsContext* gfxCtx = play->state.gfxCtx; diff --git a/soh/soh/Enhancements/randomizer/item_category_adj.cpp b/soh/soh/Enhancements/randomizer/item_category_adj.cpp new file mode 100644 index 0000000000..a64f1ca833 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/item_category_adj.cpp @@ -0,0 +1,28 @@ +#include +#include "item_category_adj.h" +#include "z64item.h" +#include "variables.h" +#include "macros.h" + +GetItemCategory Randomizer_AdjustItemCategory(GetItemEntry item) { + GetItemCategory category = item.getItemCategory; + + // Downgrade bombchus to lesser if the player already has bombchus + if (INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU && + ((item.modIndex == MOD_RANDOMIZER && item.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) || + (item.modIndex == MOD_NONE && + (item.getItemId == GI_BOMBCHUS_5 || item.getItemId == GI_BOMBCHUS_10 || item.getItemId == GI_BOMBCHUS_20)))) { + category = ITEM_CATEGORY_LESSER; + } + + // Downgrade bottles to lesser if the player already has a bottle + if ((item.modIndex == MOD_RANDOMIZER && item.getItemId >= RG_BOTTLE_WITH_RED_POTION && + item.getItemId <= RG_BOTTLE_WITH_POE) || + (item.modIndex == MOD_NONE && (item.getItemId == GI_BOTTLE || item.getItemId == GI_MILK_BOTTLE))) { + if (gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE) { + category = ITEM_CATEGORY_LESSER; + } + } + + return category; +} diff --git a/soh/soh/Enhancements/randomizer/item_category_adj.h b/soh/soh/Enhancements/randomizer/item_category_adj.h new file mode 100644 index 0000000000..53a57e7d4f --- /dev/null +++ b/soh/soh/Enhancements/randomizer/item_category_adj.h @@ -0,0 +1,18 @@ +#pragma once + +#ifndef ITEM_CATEGORY_ADJ_H +#define ITEM_CATEGORY_ADJ_H + +#include "../item-tables/ItemTableTypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +GetItemCategory Randomizer_AdjustItemCategory(GetItemEntry item); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c index 7e0f762e94..fb4f09d2b5 100644 --- a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c +++ b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c @@ -5,6 +5,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/randomizer/item_category_adj.h" #define FLAGS 0 @@ -580,21 +581,7 @@ void EnBox_UpdateTexture(EnBox* this, PlayState* play) { this->dyna.actor.room != 6); // Exclude treasure game chests except for the final room if (!isVanilla) { - getItemCategory = chestItem.getItemCategory; - // If they have bombchus, don't consider the bombchu item major - if ((INV_CONTENT(ITEM_BOMBCHU) == ITEM_BOMBCHU && - ((chestItem.modIndex == MOD_RANDOMIZER && chestItem.getItemId == RG_PROGRESSIVE_BOMBCHU_BAG) || - (chestItem.modIndex == MOD_NONE && - (chestItem.getItemId == GI_BOMBCHUS_5 || chestItem.getItemId == GI_BOMBCHUS_10 || - chestItem.getItemId == GI_BOMBCHUS_20)))) || - // If it's a bottle and they already have one, consider the item lesser - ((chestItem.modIndex == MOD_RANDOMIZER && chestItem.getItemId >= RG_BOTTLE_WITH_RED_POTION && - chestItem.getItemId <= RG_BOTTLE_WITH_POE) || - (chestItem.modIndex == MOD_NONE && - (chestItem.getItemId == GI_BOTTLE || chestItem.getItemId == GI_MILK_BOTTLE)) && - gSaveContext.inventory.items[SLOT_BOTTLE_1] != ITEM_NONE)) { - getItemCategory = ITEM_CATEGORY_LESSER; - } + getItemCategory = Randomizer_AdjustItemCategory(chestItem); } switch (this->type) { From ebea14f2974787626ea36873aa7592732f2f691c Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 29 Mar 2026 12:45:48 -0400 Subject: [PATCH 4/8] Fix displayed token count (#6441) --- soh/soh/Enhancements/InjectItemCounts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/InjectItemCounts.cpp b/soh/soh/Enhancements/InjectItemCounts.cpp index 2aefe61033..8b33a9b648 100644 --- a/soh/soh/Enhancements/InjectItemCounts.cpp +++ b/soh/soh/Enhancements/InjectItemCounts.cpp @@ -24,7 +24,7 @@ void BuildSkulltulaMessage(uint16_t* textId, bool* loadFromMessageTable) { // Auto dismiss textbox after 0x3C (60) frames (about 3 seconds for OoT) msg = msg + "\x0E\x3C"; } - int16_t gsCount = gSaveContext.inventory.gsTokens + (IS_RANDO ? 1 : 0); + int16_t gsCount = gSaveContext.inventory.gsTokens; msg.Replace("[[gsCount]]", std::to_string(gsCount)); msg.AutoFormat(ITEM_SKULL_TOKEN); msg.LoadIntoFont(); From 5494a81eb14f135afd0115bead50c235d5b809b3 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 29 Mar 2026 12:46:47 -0400 Subject: [PATCH 5/8] Fix market sneak hook (#6440) --- soh/soh/Enhancements/TimeSavers/MarketSneak.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/soh/soh/Enhancements/TimeSavers/MarketSneak.cpp b/soh/soh/Enhancements/TimeSavers/MarketSneak.cpp index 2b688bc9eb..a8b190bf11 100644 --- a/soh/soh/Enhancements/TimeSavers/MarketSneak.cpp +++ b/soh/soh/Enhancements/TimeSavers/MarketSneak.cpp @@ -2,11 +2,17 @@ extern "C" { #include +extern PlayState* gPlayState; } // RANDOTODO: Port the rest of the behavior for this enhancement here. void BuildNightGuardMessage(uint16_t* textId, bool* loadFromMessageTable) { + // Other guards should not have their text overridden + if (gPlayState->sceneNum != SCENE_MARKET_ENTRANCE_NIGHT) { + return; + } + CustomMessage msg = CustomMessage("You look bored. Wanna go out for a walk?\x1B%gYes&No%w", "Du siehst gelangweilt aus. Willst Du einen Spaziergang machen?\x1B%gJa&Nein%w", "Tu as l'air de t'ennuyer. Tu veux aller faire un tour?\x1B%gOui&Non%w"); From 972ed22167bd6ca90b5be26bc814841c99551b22 Mon Sep 17 00:00:00 2001 From: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:05:56 +0100 Subject: [PATCH 6/8] reset possible ice trap models before populating item pool (#6442) --- soh/soh/Enhancements/randomizer/3drando/item_pool.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp index 8a962d1a7a..4e6101301e 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp @@ -149,6 +149,7 @@ static void PlaceItemsForType(RandomizerCheckType rctype, bool overworldActive = void GenerateItemPool() { // RANDOTODO proper removal of items not in pool or logically relevant instead of dummy checks. auto ctx = Rando::Context::GetInstance(); + ctx->possibleIceTrapModels.clear(); itemPool.clear(); junkPool.clear(); plentifulPool.clear(); From efc4086c6a42d661fab59492a12079dffaba7ea8 Mon Sep 17 00:00:00 2001 From: Pepper0ni <93387759+Pepper0ni@users.noreply.github.com> Date: Mon, 30 Mar 2026 02:32:31 +0100 Subject: [PATCH 7/8] Some Logic fixes (#6445) - Visible Collision added to more Skulls in Crates for consistency with similar situations - Visible Collision added to chests in crates in MQ fire and for more ways to hit the switch in MQ fire Lizalfos maze - Allow any jumpslash to break the pot in red ice in ice cavern with visible collision. - Add Visible Collision as a way to get the gold skull token in the rubble in 4F Gibdo room in MQ shadow - Turns out, while you take fall damage if you backflip onto a building from GF above jail, the floor is perfectly fine. - Granny's shop forgot to check the price of the item being sold for wallets. --- .../randomizer/location_access/dungeons/deku_tree.cpp | 2 +- .../randomizer/location_access/dungeons/fire_temple.cpp | 9 ++++----- .../randomizer/location_access/dungeons/ice_cavern.cpp | 5 ++--- .../location_access/dungeons/shadow_temple.cpp | 5 +++-- .../randomizer/location_access/dungeons/water_temple.cpp | 2 +- .../location_access/overworld/gerudo_fortress.cpp | 5 +++-- .../randomizer/location_access/overworld/kakariko.cpp | 3 ++- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp index 4c758d57cc..dfda237195 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/deku_tree.cpp @@ -244,7 +244,7 @@ void RegionTable_Init_DekuTree() { }, { //Locations LOCATION(RC_DEKU_TREE_MQ_MAP_CHEST, logic->HasItem(RG_OPEN_CHEST)), - LOCATION(RC_DEKU_TREE_MQ_GS_LOBBY, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA)), + LOCATION(RC_DEKU_TREE_MQ_GS_LOBBY, (logic->CanBreakCrates() || ctx->GetTrickOption(RT_VISIBLE_COLLISION)) && logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA)), LOCATION(RC_DEKU_TREE_MQ_LOBBY_HEART, true), LOCATION(RC_DEKU_TREE_MQ_2F_GRASS_1, logic->CanCutShrubs()), LOCATION(RC_DEKU_TREE_MQ_2F_GRASS_2, logic->CanCutShrubs()), diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp index c77d43cfb5..60f51f75dc 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/fire_temple.cpp @@ -725,7 +725,7 @@ void RegionTable_Init_FireTemple() { areaTable[RR_FIRE_TEMPLE_MQ_MAZE_CRATE_CAGE] = Region("Fire Temple MQ Maze Crate Cage", SCENE_FIRE_TEMPLE, {}, { //Locations - LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CHEST, logic->HasItem(RG_OPEN_CHEST)), + LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CHEST, logic->HasItem(RG_OPEN_CHEST) && (ctx->GetTrickOption(RT_VISIBLE_COLLISION) || logic->CanBreakCrates())), LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CRATE_1, logic->CanBreakCrates()), LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CRATE_2, logic->CanBreakCrates()), LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_LOWER_CRATE_3, logic->CanBreakCrates()), @@ -739,9 +739,8 @@ void RegionTable_Init_FireTemple() { areaTable[RR_FIRE_TEMPLE_MQ_UPPER_LIZALFOS_MAZE] = Region("Fire Temple MQ Upper Lizalfos Maze", SCENE_FIRE_TEMPLE, {}, {}, { //Exits ENTRANCE(RR_FIRE_TEMPLE_MQ_LOWER_LIZALFOS_MAZE, true), - //this cage is much more lenient than the lower cage as the switch is close to the front. sling, rang and bow all hit the switch easily, though might be too unintuitive for default logic - //This shouldn't come up in most cases anyway as most methods to get here need either a melee weapon or explosives - ENTRANCE(RR_FIRE_TEMPLE_MQ_MAZE_BOX_CAGE, AnyAgeTime([]{return logic->CanJumpslash() || logic->HasExplosives();})), + ENTRANCE(RR_FIRE_TEMPLE_MQ_MAZE_BOX_CAGE, AnyAgeTime([]{return logic->CanJumpslash() || logic->HasExplosives() || + (ctx->GetTrickOption(RT_VISIBLE_COLLISION) && (logic->CanUse(RG_FAIRY_BOW) || logic->CanUse(RG_FAIRY_SLINGSHOT) || logic->CanUse(RG_BOOMERANG)));})), ENTRANCE(RR_FIRE_TEMPLE_MQ_SHORTCUT_CLIMB, logic->HasExplosives()), //Implies RR_FIRE_TEMPLE_MQ_LOWER_LIZALFOS_MAZE access ENTRANCE(RR_FIRE_TEMPLE_MQ_ABOVE_MAZE, logic->HasExplosives() && logic->CanUse(RG_MEGATON_HAMMER) && (logic->CanUse(RG_LONGSHOT) || (logic->CanUse(RG_HOOKSHOT) && logic->CanUse(RG_SONG_OF_TIME)))), @@ -750,7 +749,7 @@ void RegionTable_Init_FireTemple() { areaTable[RR_FIRE_TEMPLE_MQ_MAZE_BOX_CAGE] = Region("Fire Temple MQ Maze Box Cage", SCENE_FIRE_TEMPLE, {}, { //Locations - LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CHEST, logic->HasItem(RG_OPEN_CHEST)), + LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CHEST, logic->HasItem(RG_OPEN_CHEST) && (ctx->GetTrickOption(RT_VISIBLE_COLLISION) || logic->CanBreakCrates())), LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CRATE_1, logic->CanBreakCrates()), LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CRATE_2, logic->CanBreakCrates()), LOCATION(RC_FIRE_TEMPLE_MQ_LIZALFOS_MAZE_UPPER_CRATE_3, logic->CanBreakCrates()), diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp index 24e905aa9a..176ffaee88 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ice_cavern.cpp @@ -50,10 +50,9 @@ void RegionTable_Init_IceCavern() { }, { //Locations LOCATION(RC_ICE_CAVERN_MAP_CHEST, logic->BlueFire() && logic->HasItem(RG_OPEN_CHEST)), - // very easy to break pot through ice with most weapons - // Bow extesnion is possible, but very precise: X = 403, Z = 2062-3, Rot = -11475, needs a setup and is its own trick + // Bow extension is possible, but very precise: X = 403, Z = 2062-3, Rot = -11475, needs a setup and is its own trick LOCATION(RC_ICE_CAVERN_FROZEN_POT_1, (logic->CanBreakPots() && logic->BlueFire()) || logic->HasExplosives() || - (ctx->GetTrickOption(RT_VISIBLE_COLLISION) && ((logic->CanStandingShield() && logic->CanUse(RG_KOKIRI_SWORD)) || logic->CanUse(RG_MASTER_SWORD) || logic->CanUse(RG_BIGGORON_SWORD) || logic->CanUse(RG_MEGATON_HAMMER))) || + (ctx->GetTrickOption(RT_VISIBLE_COLLISION) && logic->CanJumpslash()) || (ctx->GetTrickOption(RT_ITEM_EXTENSION) && logic->CanUse(RG_HOOKSHOT))), LOCATION(RC_ICE_CAVERN_MAP_ROOM_LEFT_HEART, true), LOCATION(RC_ICE_CAVERN_MAP_ROOM_MIDDLE_HEART, true), diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp index 0a0047a138..f5cf059855 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/shadow_temple.cpp @@ -155,7 +155,7 @@ void RegionTable_Init_ShadowTemple() { LOCATION(RC_SHADOW_TEMPLE_FALLING_SPIKES_POT_3, logic->CanUse(RG_BOOMERANG)), }, { //Exits - ENTRANCE(RR_SHADOW_TEMPLE_LOWER_HUGE_PIT, true), + ENTRANCE(RR_SHADOW_TEMPLE_LOWER_HUGE_PIT, !!ctx->GetTrickOption(RT_VISIBLE_COLLISION)), ENTRANCE(RR_SHADOW_TEMPLE_STONE_UMBRELLA_UPPER, ctx->GetTrickOption(RT_SHADOW_UMBRELLA_CLIP) || (ctx->GetTrickOption(RT_DAMAGE_BOOST_SIMPLE) && logic->TakeDamage()) || (logic->IsAdult && ((ctx->GetTrickOption(RT_SHADOW_UMBRELLA_HOVER) && logic->CanUse(RG_HOVER_BOOTS)) || logic->HasItem(RG_GORONS_BRACELET)))), }); @@ -638,7 +638,8 @@ void RegionTable_Init_ShadowTemple() { //Locations LOCATION(RC_SHADOW_TEMPLE_MQ_AFTER_WIND_ENEMY_CHEST, logic->CanKillEnemy(RE_GIBDO) && logic->HasItem(RG_OPEN_CHEST)), LOCATION(RC_SHADOW_TEMPLE_MQ_AFTER_WIND_HIDDEN_CHEST, logic->HasExplosives() && (ctx->GetTrickOption(RT_LENS_SHADOW_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->HasItem(RG_OPEN_CHEST)), - LOCATION(RC_SHADOW_TEMPLE_MQ_GS_AFTER_WIND, logic->HasExplosives()), + //The various methods for this can be a bit specific, might be worthy of it's own trick when it becomes relevant with dungeon shortcut settings. + LOCATION(RC_SHADOW_TEMPLE_MQ_GS_AFTER_WIND, logic->HasExplosives() || (ctx->GetTrickOption(RT_VISIBLE_COLLISION) && logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA))), LOCATION(RC_SHADOW_TEMPLE_MQ_BEFORE_BOAT_POT_1, logic->CanBreakPots()), LOCATION(RC_SHADOW_TEMPLE_MQ_BEFORE_BOAT_POT_2, logic->CanBreakPots()), }, { diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp index 03e9076e32..cfd2b39259 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/water_temple.cpp @@ -1358,7 +1358,7 @@ void RegionTable_Init_WaterTemple() { areaTable[RR_WATER_TEMPLE_MQ_CRATE_VORTEX_CAGE] = Region("Water Temple MQ Crate Vortex Cage", SCENE_WATER_TEMPLE, {}, { //Locations - LOCATION(RC_WATER_TEMPLE_MQ_GS_FREESTANDING_KEY_AREA, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA) && logic->CanBreakCrates()), + LOCATION(RC_WATER_TEMPLE_MQ_GS_FREESTANDING_KEY_AREA, logic->CanGetEnemyDrop(RE_GOLD_SKULLTULA) && (logic->CanBreakCrates() || ctx->GetTrickOption(RT_VISIBLE_COLLISION))), LOCATION(RC_WATER_TEMPLE_MQ_WHIRLPOOL_BEHIND_GATE_CRATE_1, logic->CanBreakCrates()), LOCATION(RC_WATER_TEMPLE_MQ_WHIRLPOOL_BEHIND_GATE_CRATE_2, logic->CanBreakCrates()), LOCATION(RC_WATER_TEMPLE_MQ_WHIRLPOOL_BEHIND_GATE_CRATE_3, logic->CanBreakCrates()), diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp index 26a64a08a3..9d797c0e0d 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp @@ -200,9 +200,10 @@ void RegionTable_Init_GerudoFortress() { LOCATION(RC_GF_ABOVE_JAIL_CRATE, true), }, { //Exits - //you don't take fall damage if you land on the rock with the flag on for some reason //there's a trick to reach RR_GF_LONG_ROOF - ENTRANCE(RR_GF_OUTSKIRTS, ctx->GetTrickOption(RT_UNINTUITIVE_JUMPS) || logic->TakeDamage()), + //For some reason, you take fall damage if you backflip onto the fortress but not onto the sand + //It's unintuitive to avoid being caught on landing, but that sends you to the same place anyway... + ENTRANCE(RR_GF_OUTSKIRTS, true), ENTRANCE(RR_GF_NEAR_CHEST, logic->CanUse(RG_LONGSHOT)), ENTRANCE(RR_GF_BELOW_CHEST, logic->TakeDamage()), ENTRANCE(RR_GF_JAIL_WINDOW, logic->CanUse(RG_HOOKSHOT)), diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/kakariko.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/kakariko.cpp index 29a133f3ad..de64cc2f74 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/kakariko.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/kakariko.cpp @@ -243,7 +243,8 @@ void RegionTable_Init_Kakariko() { }, { //Locations LOCATION(RC_KAK_TRADE_ODD_MUSHROOM, logic->IsAdult && logic->CanUse(RG_ODD_MUSHROOM)), - LOCATION(RC_KAK_GRANNYS_SHOP, logic->IsAdult && logic->HasItem(RG_SPEAK_HYLIAN) && (logic->CanUse(RG_ODD_MUSHROOM) || logic->TradeQuestStep(RG_ODD_MUSHROOM))), + LOCATION(RC_KAK_GRANNYS_SHOP, logic->IsAdult && logic->HasItem(RG_SPEAK_HYLIAN) && + (logic->CanUse(RG_ODD_MUSHROOM) || logic->TradeQuestStep(RG_ODD_MUSHROOM)) && GetCheckPrice() <= GetWalletCapacity()), }, { // Exits ENTRANCE(RR_KAK_BACKYARD, true), From 14b464bab2ecaa2cd18255acf235a0322b66b905 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 29 Mar 2026 21:33:16 -0400 Subject: [PATCH 8/8] Bean guy text formatting and rando fixes (#6444) --- .../game-interactor/vanilla-behavior/GIVanillaBehavior.h | 8 ++++++++ .../randomizer/Messages/MerchantMessages.cpp | 6 +++--- soh/soh/Enhancements/randomizer/hook_handlers.cpp | 9 +++++++++ soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c | 4 +++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index aae11fe86a..ae9ea89581 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1355,6 +1355,14 @@ typedef enum { // - `*DoorShutter` VB_LOCK_BOSS_DOOR, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnMs` + VB_MAGIC_BEAN_SALESMAN_TAKE_MONEY, + // #### `result` // ```c // CHECK_QUEST_ITEM(QUEST_SONG_EPONA) diff --git a/soh/soh/Enhancements/randomizer/Messages/MerchantMessages.cpp b/soh/soh/Enhancements/randomizer/Messages/MerchantMessages.cpp index 2492acc770..dae35c0d0b 100644 --- a/soh/soh/Enhancements/randomizer/Messages/MerchantMessages.cpp +++ b/soh/soh/Enhancements/randomizer/Messages/MerchantMessages.cpp @@ -49,9 +49,9 @@ void BuildBeanGuyMessage(uint16_t* textId, bool* loadFromMessageTable) { "I never thought I'd say this, but I'm selling the last %rMagic Bean%w.^%y99 Rupees%w, no " "less.\x1B%gYes&No%w", "Ich hätte nie gedacht, daß ich das sage, aber ich verkaufe die letzte^%rWundererbse%w für %y99 " - "Rubine%w.\x1B&%gJa&Nein%w", - "Je te vends mon dernier %rHaricot&magique%g pour %y99 Rubis%w.\x1B&%gAcheterNe pas acheter%w"); - msg.Format(); + "Rubine%w.\x1B%gJa&Nein%w", + "Je te vends mon dernier %rHaricot&magique%w pour %y99 Rubis%w.\x1B%gAcheter&Ne pas acheter%w"); + msg.AutoFormat(); } else if (*textId == TEXT_BEAN_SALESMAN_BUY_FOR_10) { msg = CustomMessage("Want to buy [[color]][[1]]%w for %y[[2]] Rupees%w?\x1B%gYes&No%w", "Möchten Sie [[color]][[1]]%w für %y[[2]] Rubin%w kaufen?\x1B%gJa&Nein%w", diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 73b2cda6de..340aad2874 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -980,6 +980,15 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l OTRGlobals::Instance->gRandoContext->GetItemLocation(RC_ZR_MAGIC_BEAN_SALESMAN)->GetPrice(); } else if (RAND_GET_OPTION(RSK_SKIP_PLANTING_BEANS)) { *should = gSaveContext.rupees >= 60; + } else if (BEANS_BOUGHT == 9) { + *should = gSaveContext.rupees >= 99; + } + break; + } + case VB_MAGIC_BEAN_SALESMAN_TAKE_MONEY: { + if (BEANS_BOUGHT == 9) { + Rupees_ChangeBy(-99); + *should = false; } break; } diff --git a/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c b/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c index cec35379c8..3c8442ba97 100644 --- a/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c +++ b/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c @@ -150,7 +150,9 @@ void EnMs_Talk(EnMs* this, PlayState* play) { void EnMs_Sell(EnMs* this, PlayState* play) { if (Actor_HasParent(&this->actor, play)) { - Rupees_ChangeBy(-sPrices[BEANS_BOUGHT]); + if (GameInteractor_Should(VB_MAGIC_BEAN_SALESMAN_TAKE_MONEY, true, this)) { + Rupees_ChangeBy(-sPrices[BEANS_BOUGHT]); + } this->actor.parent = NULL; this->actionFunc = EnMs_TalkAfterPurchase; } else {