From dbc2ff09b5bfe845055161e28ca6510649aad3ef Mon Sep 17 00:00:00 2001 From: Rozelette Date: Wed, 11 Jun 2025 16:37:34 -0500 Subject: [PATCH 1/9] Convert actor health bars to use ObjectExtension (#5565) --- soh/include/z64actor.h | 3 -- .../ObjectExtension/ActorMaximumHealth.cpp | 29 +++++++++++++++++++ soh/soh/ObjectExtension/ActorMaximumHealth.h | 17 +++++++++++ soh/src/code/z_actor.c | 10 ------- soh/src/code/z_parameter.c | 3 +- soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c | 3 +- 6 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 soh/soh/ObjectExtension/ActorMaximumHealth.cpp create mode 100644 soh/soh/ObjectExtension/ActorMaximumHealth.h diff --git a/soh/include/z64actor.h b/soh/include/z64actor.h index 6d8fd3ca0..77172bda4 100644 --- a/soh/include/z64actor.h +++ b/soh/include/z64actor.h @@ -266,9 +266,6 @@ typedef struct Actor { /* 0x134 */ ActorFunc draw; // Draw Routine. Called by `Actor_Draw` /* 0x138 */ ActorResetFunc reset; /* 0x13C */ char dbgPad[0x10]; // Padding that only exists in the debug rom - // #region SOH [General] - /* */ u8 maximumHealth; // Max health value for use with health bars, set on actor init - // #endregion } Actor; // size = 0x14C typedef enum { diff --git a/soh/soh/ObjectExtension/ActorMaximumHealth.cpp b/soh/soh/ObjectExtension/ActorMaximumHealth.cpp new file mode 100644 index 000000000..9c710822f --- /dev/null +++ b/soh/soh/ObjectExtension/ActorMaximumHealth.cpp @@ -0,0 +1,29 @@ +#include "ActorMaximumHealth.h" +#include "soh/ObjectExtension/ObjectExtension.h" +#include "soh/ShipInit.hpp" +#include "soh/Enhancements/game-interactor/GameInteractor.h" + +struct ActorMaximumHealth { + u8 maximumHealth = 0; +}; +static ObjectExtension::Register ActorMaximumHealthRegister; + +u8 GetActorMaximumHealth(const Actor* actor) { + const ActorMaximumHealth* maxHealth = ObjectExtension::GetInstance().Get(actor); + return maxHealth != nullptr ? maxHealth->maximumHealth : ActorMaximumHealth{}.maximumHealth; +} + +void SetActorMaximumHealth(const Actor* actor, u8 maximumHealth) { + ObjectExtension::GetInstance().Set(actor, ActorMaximumHealth{ maximumHealth }); +} + +static void ActorMaximumHealth_Register() { + COND_HOOK(OnActorInit, true, [](void* ptr) { + Actor* actor = static_cast(ptr); + if (actor->category == ACTORCAT_ENEMY) { + SetActorMaximumHealth(actor, actor->colChkInfo.health); + } + }); +} + +RegisterShipInitFunc actorMaximumHealthInit(ActorMaximumHealth_Register); \ No newline at end of file diff --git a/soh/soh/ObjectExtension/ActorMaximumHealth.h b/soh/soh/ObjectExtension/ActorMaximumHealth.h new file mode 100644 index 000000000..d3ee67acd --- /dev/null +++ b/soh/soh/ObjectExtension/ActorMaximumHealth.h @@ -0,0 +1,17 @@ +#ifndef ACTOR_MAXIMUM_HEALTH_H +#define ACTOR_MAXIMUM_HEALTH_H + +#ifdef __cplusplus +extern "C" { +#include "z64actor.h" +#endif + +// Max health value for use with health bars, set on actor init +u8 GetActorMaximumHealth(const Actor* actor); +void SetActorMaximumHealth(const Actor* actor, u8 maximumHealth); + +#ifdef __cplusplus +} +#endif + +#endif // ACTOR_MAXIMUM_HEALTH_H \ No newline at end of file diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 0b9d20bc5..3933f263d 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -1260,11 +1260,6 @@ void Actor_Init(Actor* actor, PlayState* play) { actor->init = NULL; GameInteractor_ExecuteOnActorInit(actor); - - // For enemy health bar we need to know the max health during init - if (actor->category == ACTORCAT_ENEMY) { - actor->maximumHealth = actor->colChkInfo.health; - } } } @@ -2625,11 +2620,6 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { actor->init = NULL; GameInteractor_ExecuteOnActorInit(actor); - - // For enemy health bar we need to know the max health during init - if (actor->category == ACTORCAT_ENEMY) { - actor->maximumHealth = actor->colChkInfo.health; - } } actor = actor->next; } else if (!Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) { diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index cc6245241..0fc923070 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -24,6 +24,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/gameplaystats.h" +#include "soh/ObjectExtension/ActorMaximumHealth.h" #include "message_data_static.h" extern MessageTableEntry* sNesMessageEntryTablePtr; @@ -3643,7 +3644,7 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) { f32 scaleY = -0.75f; f32 scaledHeight = -texHeight * scaleY; f32 halfBarWidth = endTexWidth + ((f32)healthbar_fillWidth / 2); - s16 healthBarFill = ((f32)actor->colChkInfo.health / actor->maximumHealth) * healthbar_fillWidth; + s16 healthBarFill = ((f32)actor->colChkInfo.health / GetActorMaximumHealth(actor)) * healthbar_fillWidth; if (anchorType == ENEMYHEALTH_ANCHOR_ACTOR) { // Get actor projected position diff --git a/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c b/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c index 217afe31f..13afa66f6 100644 --- a/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c +++ b/soh/src/overlays/actors/ovl_En_Fz/z_en_fz.c @@ -2,6 +2,7 @@ #include "objects/object_fz/object_fz.h" #include "soh/frame_interpolation.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorMaximumHealth.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -725,7 +726,7 @@ void EnFz_Draw(Actor* thisx, PlayState* play) { // displayLists, so we need to recompute the index based on the scaled health (using the maximum health value) and // clamp the final result for safety. if (CVarGetInteger(CVAR_ENHANCEMENT("EnemySizeScalesHealth"), 0)) { - u8 scaledHealth = (u8)(((f32)this->actor.colChkInfo.health / this->actor.maximumHealth) * 6); + u8 scaledHealth = (u8)(((f32)this->actor.colChkInfo.health / GetActorMaximumHealth(this)) * 6); index = (6 - scaledHealth) >> 1; index = CLAMP(index, 0, 2); } From af99ef8e07e5d19f9750b53cbd6024b314a046e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 11 Jun 2025 21:38:12 +0000 Subject: [PATCH 2/9] Hookify TreesDropSticks (#5566) * Hookify TreesDropSticks * fix off by one --- soh/soh/Enhancements/TreesDropSticks.cpp | 31 +++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 16 ++++++++++ soh/src/code/z_en_item00.c | 4 +-- .../actors/ovl_En_Wood02/z_en_wood02.c | 9 ++---- 4 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 soh/soh/Enhancements/TreesDropSticks.cpp diff --git a/soh/soh/Enhancements/TreesDropSticks.cpp b/soh/soh/Enhancements/TreesDropSticks.cpp new file mode 100644 index 000000000..7fdc4eb0a --- /dev/null +++ b/soh/soh/Enhancements/TreesDropSticks.cpp @@ -0,0 +1,31 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "src/overlays/actors/ovl_En_Wood02/z_en_wood02.h" +} + +extern PlayState* gPlayState; + +void RegisterTreesDropSticks() { + COND_VB_SHOULD(VB_TREE_DROP_COLLECTIBLE, CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0), { + if (INV_CONTENT(ITEM_STICK) != ITEM_NONE) { + EnWood02* tree = va_arg(args, EnWood02*); + Vec3f dropsSpawnPt = tree->actor.world.pos; + dropsSpawnPt.y += 200.0f; + + *should = false; + for (s32 numDrops = Rand_Next() % 4; numDrops > 0; numDrops--) { + Item_DropCollectible(gPlayState, &dropsSpawnPt, ITEM00_STICK); + } + } + }); + + COND_VB_SHOULD(VB_PREVENT_ADULT_STICK, CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0), { + if (INV_CONTENT(ITEM_STICK) != ITEM_NONE) { + *should = false; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterTreesDropSticks, { CVAR_ENHANCEMENT("TreesDropSticks") }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index fea114198..5159118b1 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1697,6 +1697,15 @@ typedef enum { // - `*ObjTsubo` VB_POT_SETUP_DRAW, + // #### `result` + // ```c + // dropId == ITEM00_STICK + // ``` + // #### `args` + // - None + VB_PREVENT_ADULT_STICK, + + // #### `result` // #### `result` // ```c // true @@ -2073,6 +2082,13 @@ typedef enum { VB_TRANSITION_TO_SAVE_SCREEN_ON_DEATH, // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnWood02` + VB_TREE_DROP_COLLECTIBLE, + // ```c // true // ``` diff --git a/soh/src/code/z_en_item00.c b/soh/src/code/z_en_item00.c index 040549466..b6bf5ecdf 100644 --- a/soh/src/code/z_en_item00.c +++ b/soh/src/code/z_en_item00.c @@ -4,8 +4,8 @@ #include "overlays/effects/ovl_Effect_Ss_Dead_Sound/z_eff_ss_dead_sound.h" #include "textures/icon_item_static/icon_item_static.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/OTRGlobals.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/OTRGlobals.h" #define FLAGS 0 @@ -1536,7 +1536,7 @@ s16 func_8001F404(s16 dropId) { if (LINK_IS_ADULT) { if (dropId == ITEM00_SEEDS) { dropId = ITEM00_ARROWS_SMALL; - } else if ((dropId == ITEM00_STICK) && !(CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0))) { + } else if (GameInteractor_Should(VB_PREVENT_ADULT_STICK, dropId == ITEM00_STICK)) { dropId = ITEM00_RUPEE_GREEN; } } else { diff --git a/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c b/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c index 72218e3b4..2fe2445a6 100644 --- a/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c +++ b/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c @@ -6,6 +6,7 @@ #include "z_en_wood02.h" #include "objects/object_wood02/object_wood02.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS 0 @@ -327,7 +328,6 @@ void EnWood02_Update(Actor* thisx, PlayState* play2) { Vec3f dropsSpawnPt; s32 i; s32 leavesParams; - s32 numDrops; // Despawn extra trees in a group if out of range if ((this->spawnType == WOOD_SPAWN_SPAWNED) && (this->actor.parent != NULL)) { @@ -358,12 +358,7 @@ void EnWood02_Update(Actor* thisx, PlayState* play2) { dropsSpawnPt.y += 200.0f; if ((this->unk_14C >= 0) && (this->unk_14C < 0x64)) { - if (CVarGetInteger(CVAR_ENHANCEMENT("TreesDropSticks"), 0) && INV_CONTENT(ITEM_STICK) != ITEM_NONE) { - numDrops = Rand_ZeroOne() * 4; - for (i = 0; i < numDrops; ++i) { - Item_DropCollectible(play, &dropsSpawnPt, ITEM00_STICK); - } - } else { + if (GameInteractor_Should(VB_TREE_DROP_COLLECTIBLE, true, this)) { Item_DropCollectibleRandom(play, &this->actor, &dropsSpawnPt, this->unk_14C << 4); } } else if (this->actor.home.rot.z != 0) { From 07328a0ecb8f80882d4b585e0f748e59588d1b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 11 Jun 2025 21:38:34 +0000 Subject: [PATCH 3/9] remove leftover No Dampe Fire checkbox (#5561) got moved to difficulty dropdown --- soh/soh/SohGui/SohMenuEnhancements.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 3ffa9cfed..feb36eebb 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1586,9 +1586,6 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Keese and Guay no longer target you and simply ignore you as if you were wearing the " "Skull Mask.")); - AddWidget(path, "No Dampe Fire", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_CHEAT("NoDampeFire")) - .Options(CheckboxOptions().Tooltip("Dampe won't drop fireballs during race.")); AddWidget(path, "Glitch Aids", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Easy Frame Advancing with Pause", WIDGET_CVAR_CHECKBOX) From 52debea44b19b034822ee812958ff6c2c563cea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 11 Jun 2025 21:41:54 +0000 Subject: [PATCH 4/9] Difficulty: CuccosToReturn (#5552) * Difficulty: CuccosToReturn Mirrors rando option * remove RSK_CUCCO_COUNT * revert forcing rando to at least 1 --- soh/soh/Enhancements/CuccosToReturn.cpp | 19 +++++++++++++++++++ .../Enhancements/randomizer/hook_handlers.cpp | 7 ------- .../randomizer/option_descriptions.cpp | 1 - .../Enhancements/randomizer/randomizerTypes.h | 1 - soh/soh/Enhancements/randomizer/settings.cpp | 11 +++-------- soh/soh/SohGui/SohMenuEnhancements.cpp | 4 ++++ 6 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 soh/soh/Enhancements/CuccosToReturn.cpp diff --git a/soh/soh/Enhancements/CuccosToReturn.cpp b/soh/soh/Enhancements/CuccosToReturn.cpp new file mode 100644 index 000000000..317437216 --- /dev/null +++ b/soh/soh/Enhancements/CuccosToReturn.cpp @@ -0,0 +1,19 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "soh/Enhancements/randomizer/context.h" + +extern "C" { +extern PlayState* gPlayState; +#include "src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.h" +} + +void RegisterCuccosToReturn() { + COND_VB_SHOULD(VB_SET_CUCCO_COUNT, CVarGetInteger(CVAR_ENHANCEMENT("CuccosToReturn"), 7) != 7, { + EnNiwLady* enNiwLady = va_arg(args, EnNiwLady*); + // Override starting Cucco count using setting value + enNiwLady->cuccosInPen = 7 - CVarGetInteger(CVAR_ENHANCEMENT("CuccosToReturn"), 7); + *should = false; + }); +} + +static RegisterShipInitFunc initFunc(RegisterCuccosToReturn, { CVAR_ENHANCEMENT("CuccosToReturn") }); diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index fca4dfae7..ea8ce1e12 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -925,13 +925,6 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l *should = Flags_GetRandomizerInf(RAND_INF_LEARNED_EPONA_SONG); break; } - case VB_SET_CUCCO_COUNT: { - EnNiwLady* enNiwLady = va_arg(args, EnNiwLady*); - // Override starting Cucco count using setting value - enNiwLady->cuccosInPen = 7 - RAND_GET_OPTION(RSK_CUCCO_COUNT); - *should = false; - break; - } case VB_KING_ZORA_THANK_CHILD: { // Allow turning in Ruto's letter even if you have already rescued her if (!Flags_GetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED)) { diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index ff068943b..16c89a1c4 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -595,7 +595,6 @@ void Settings::CreateOptionDescriptions() { "\n" "Greg as Wildcard - Greg does not change logic, Greg helps obtain GBK, max number of " "rewards on slider does not change."; - mOptionDescriptions[RSK_CUCCO_COUNT] = "The amount of cuccos needed to claim the reward from Anju the Cucco Lady."; mOptionDescriptions[RSK_BIG_POE_COUNT] = "The Poe collector will give a reward for turning in this many Big Poes."; mOptionDescriptions[RSK_SKIP_CHILD_STEALTH] = "The crawlspace into Hyrule Castle goes straight to Zelda, skipping the guards."; diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index e5d82b18b..2e3d1f86e 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -5805,7 +5805,6 @@ typedef enum { RSK_STARTING_NUTS, RSK_FULL_WALLETS, RSK_SHUFFLE_CHEST_MINIGAME, - RSK_CUCCO_COUNT, RSK_BIG_POE_COUNT, RSK_SKIP_EPONA_RACE, RSK_COMPLETE_MASK_QUEST, diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 1c81e2d58..dbd29409f 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -269,7 +269,6 @@ void Settings::CreateOptions() { OPT_BOOL(RSK_SKIP_EPONA_RACE, "Skip Epona Race", {"Don't Skip", "Skip"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SkipEponaRace"), mOptionDescriptions[RSK_SKIP_EPONA_RACE], WidgetType::Checkbox, RO_GENERIC_DONT_SKIP); OPT_BOOL(RSK_SKIP_SCARECROWS_SONG, "Skip Scarecrow's Song", CVAR_RANDOMIZER_SETTING("SkipScarecrowsSong"), mOptionDescriptions[RSK_SKIP_SCARECROWS_SONG]); OPT_U8(RSK_BIG_POE_COUNT, "Big Poe Target Count", {NumOpts(0, 10)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("BigPoeTargetCount"), mOptionDescriptions[RSK_BIG_POE_COUNT], WidgetType::Slider, 10); - OPT_U8(RSK_CUCCO_COUNT, "Cuccos to return", {NumOpts(0, 7)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("CuccosToReturn"), mOptionDescriptions[RSK_CUCCO_COUNT], WidgetType::Slider, 7); OPT_BOOL(RSK_COMPLETE_MASK_QUEST, "Complete Mask Quest", CVAR_RANDOMIZER_SETTING("CompleteMaskQuest"), mOptionDescriptions[RSK_COMPLETE_MASK_QUEST]); OPT_U8(RSK_GOSSIP_STONE_HINTS, "Gossip Stone Hints", {"No Hints", "Need Nothing", "Mask of Truth", "Stone of Agony"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("GossipStoneHints"), mOptionDescriptions[RSK_GOSSIP_STONE_HINTS], WidgetType::Combobox, RO_GOSSIP_STONES_NEED_NOTHING, false, IMFLAG_NONE); OPT_U8(RSK_HINT_CLARITY, "Hint Clarity", {"Obscure", "Ambiguous", "Clear"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("HintClarity"), mOptionDescriptions[RSK_HINT_CLARITY], WidgetType::Combobox, RO_HINT_CLARITY_CLEAR, true, IMFLAG_INDENT); @@ -1316,8 +1315,8 @@ void Settings::CreateOptions() { WidgetContainerType::TABLE); mOptionGroups[RSG_TIMESAVERS_IMGUI] = OptionGroup::SubGroup( "Timesavers", - { &mOptions[RSK_CUCCO_COUNT], &mOptions[RSK_BIG_POE_COUNT], &mOptions[RSK_SKIP_CHILD_ZELDA], - &mOptions[RSK_SKIP_EPONA_RACE], &mOptions[RSK_COMPLETE_MASK_QUEST], &mOptions[RSK_SKIP_SCARECROWS_SONG] }, + { &mOptions[RSK_BIG_POE_COUNT], &mOptions[RSK_SKIP_CHILD_ZELDA], &mOptions[RSK_SKIP_EPONA_RACE], + &mOptions[RSK_COMPLETE_MASK_QUEST], &mOptions[RSK_SKIP_SCARECROWS_SONG] }, WidgetContainerType::COLUMN); mOptionGroups[RSG_ITEM_POOL_HINTS_IMGUI] = OptionGroup::SubGroup("", { @@ -1582,7 +1581,6 @@ void Settings::CreateOptions() { &mOptions[RSK_SKIP_EPONA_RACE], &mOptions[RSK_SKIP_SCARECROWS_SONG], &mOptions[RSK_BIG_POE_COUNT], - &mOptions[RSK_CUCCO_COUNT], &mOptions[RSK_COMPLETE_MASK_QUEST], }); mOptionGroups[RSG_MISC] = OptionGroup("Miscellaneous Settings", @@ -2878,11 +2876,8 @@ void Context::FinalizeSettings(const std::set& excludedLocation if (mOptions[RSK_FISHSANITY].IsNot(RO_FISHSANITY_HYRULE_LOACH)) { mOptions[RSK_LOACH_HINT].Set(RO_GENERIC_OFF); } - - if (mOptions[RSK_CUCCO_COUNT].Is(0)) { - mOptions[RSK_CHICKENS_HINT].Set(RO_GENERIC_OFF); - } } + void Settings::ParseJson(nlohmann::json spoilerFileJson) { mContext->SetSeedString(spoilerFileJson["seed"].get()); mContext->SetSeed(spoilerFileJson["finalSeed"].get()); diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index feb36eebb..58f197512 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1138,6 +1138,10 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("CuccoStayDurationMult")) .Options(IntSliderOptions().Min(1).Max(5).DefaultValue(1).Format("%dx").Tooltip( "Cuccos will stay in place longer after putting them down, by a multiple of the value of the slider.")); + AddWidget(path, "Cuccos Needed By Anju: %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("CuccosToReturn")) + .Options(IntSliderOptions().Min(0).Max(7).DefaultValue(7).Format("%d").Tooltip( + "The amount of cuccos needed to receive bottle from Anju the Cucco Lady.")); path.column = SECTION_COLUMN_3; AddWidget(path, "Enemies", WIDGET_SEPARATOR_TEXT); From 9432b3420ba328c12aec285d901999d129728cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Thu, 12 Jun 2025 20:23:47 +0000 Subject: [PATCH 5/9] tts: only announce timer at 10s intervals (#5559) * tts: only announce timer at 30s 1. reading out every second doesn't have enough time to even say more than a number 2. tts is hard to hear the rest of the game over while it's counting non stop * under a minute announce every 10s --- soh/soh/Enhancements/tts/tts.cpp | 39 +++++++++++++++----------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/soh/soh/Enhancements/tts/tts.cpp b/soh/soh/Enhancements/tts/tts.cpp index b8a43453f..978e5ca24 100644 --- a/soh/soh/Enhancements/tts/tts.cpp +++ b/soh/soh/Enhancements/tts/tts.cpp @@ -147,28 +147,25 @@ void RegisterOnInterfaceUpdateHook() { timer = gSaveContext.subTimerSeconds; } - if (timer > 0) { - if (timer > prevTimer || (timer % 30 == 0 && prevTimer != timer)) { - uint32_t minutes = timer / 60; - uint32_t seconds = timer % 60; - char* announceBuf = ttsAnnounceBuf; - char arg[8]; // at least big enough where no s8 string will overflow - if (minutes > 0) { - snprintf(arg, sizeof(arg), "%d", minutes); - auto translation = GetParameritizedText((minutes > 1) ? "minutes_plural" : "minutes_singular", - TEXT_BANK_MISC, arg); - announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s ", translation.c_str()); - } - if (seconds > 0) { - snprintf(arg, sizeof(arg), "%d", seconds); - auto translation = GetParameritizedText((seconds > 1) ? "seconds_plural" : "seconds_singular", - TEXT_BANK_MISC, arg); - announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s", translation.c_str()); - } - assert(announceBuf < ttsAnnounceBuf + sizeof(ttsAnnounceBuf)); - SpeechSynthesizer::Instance->Speak(ttsAnnounceBuf, GetLanguageCode()); - prevTimer = timer; + if (timer > 0 && timer % (timer < 60 ? 10 : 30) == 0 && timer != prevTimer) { + uint32_t minutes = timer / 60; + uint32_t seconds = timer % 60; + char* announceBuf = ttsAnnounceBuf; + char arg[8]; // at least big enough where no s8 string will overflow + if (minutes > 0) { + snprintf(arg, sizeof(arg), "%d", minutes); + auto translation = + GetParameritizedText((minutes > 1) ? "minutes_plural" : "minutes_singular", TEXT_BANK_MISC, arg); + announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s ", translation.c_str()); } + if (seconds > 0) { + snprintf(arg, sizeof(arg), "%d", seconds); + auto translation = + GetParameritizedText((seconds > 1) ? "seconds_plural" : "seconds_singular", TEXT_BANK_MISC, arg); + announceBuf += snprintf(announceBuf, sizeof(ttsAnnounceBuf), "%s", translation.c_str()); + } + assert(announceBuf < ttsAnnounceBuf + sizeof(ttsAnnounceBuf)); + SpeechSynthesizer::Instance->Speak(ttsAnnounceBuf, GetLanguageCode()); } prevTimer = timer; From bf3add7a7228f6ccc067d9a80b47c511c1af2cd0 Mon Sep 17 00:00:00 2001 From: Malkierian Date: Thu, 12 Jun 2025 13:24:19 -0700 Subject: [PATCH 6/9] Re-add `ShipInit::InitAll()` to the end of `applyPreset()`. (#5574) --- soh/soh/Enhancements/Presets/Presets.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/soh/soh/Enhancements/Presets/Presets.cpp b/soh/soh/Enhancements/Presets/Presets.cpp index d50b45360..b84cb95ca 100644 --- a/soh/soh/Enhancements/Presets/Presets.cpp +++ b/soh/soh/Enhancements/Presets/Presets.cpp @@ -110,6 +110,7 @@ void applyPreset(std::string presetName, std::vector includeSecti } } } + ShipInit::InitAll(); } void DrawPresetSelector(std::vector includeSections, std::string presetLoc, bool disabled) { From 62c03abfd47fb57a3b137687555c38ebfd592a1e Mon Sep 17 00:00:00 2001 From: Jason <125489486+UchuuJ@users.noreply.github.com> Date: Thu, 12 Jun 2025 21:39:21 +0100 Subject: [PATCH 7/9] Updates docs/MODDING.md example to reflect current codebase (#5558) * Update docs/MODDING.md change example to reflect current codebase * Changes to MODDING.md (#5558): fixes slight typo * Changes to MODDING.md (#5558): fix typo and remove uneeded explanation that minimum and maximum are floats --- docs/MODDING.md | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/MODDING.md b/docs/MODDING.md index 1f4f4b607..d410a0d06 100644 --- a/docs/MODDING.md +++ b/docs/MODDING.md @@ -41,15 +41,28 @@ You can name your branch whatever you want, but it's recommended to name it some The limit is your imagination. You can add new features, fix bugs, add new mods, or even change the way the game works. We will demonstrate this by creating a mod that changes the speed of the day/night cycle. -Let's being by finding where the time is updated. Thankfully in the save editor we have a slider already hooked up to the time of day so we can check there for reference. The save editor file is at `soh/soh/Enhancements/debugger/debugSaveEditor.cpp`, if we do a quick search within that file for time we will find the following at line 400: +Let's begin by finding where the time is updated. Thankfully in the save editor we have a slider already hooked up to the time of day so we can check there for reference. The save editor file is at `soh/soh/Enhancements/debugger/debugSaveEditor.cpp`, if we do a quick search within that file for time we will find the following at around line 217: ```cpp -const uint16_t dayTimeMin = 0; -const uint16_t dayTimeMax = 0xFFFF; -ImGui::SliderScalar("Time", ImGuiDataType_U16, &gSaveContext.dayTime, &dayTimeMin, &dayTimeMax); + SliderInt("Time", (int32_t*)&gSaveContext.dayTime, intSliderOptionsBase.Min(0).Max(0xFFFF).Tooltip("Time of day")); + if (Button("Dawn", buttonOptionsBase)) { + gSaveContext.dayTime = 0x4000; + } + ImGui::SameLine(); + if (Button("Noon", buttonOptionsBase)) { + gSaveContext.dayTime = 0x8000; + } + ImGui::SameLine(); + if (Button("Sunset", buttonOptionsBase)) { + gSaveContext.dayTime = 0xC001; + } + ImGui::SameLine(); + if (Button("Midnight", buttonOptionsBase)) { + gSaveContext.dayTime = 0; + } ``` -So this tells us that `gSaveContext.dayTime` is what we're looking for. Let's now do a global search for this to see if we can find where it is updated. We find the following in `soh/src/code/z_kankyo.c` line 925: +So this tells us that `gSaveContext.dayTime` is what we're looking for. Let's now do a global search for this to see if we can find where it is updated. We find the following in `soh/src/code/z_kankyo.c` around line 925: ```cpp if (IS_DAY || gTimeIncrement >= 0x190) { @@ -71,16 +84,19 @@ if (IS_DAY || gTimeIncrement >= 0x190) { } ``` -Rebuild the game and launch it, then load a save file. You should see that the time of day is now moving much faster. Terrific! While we could wrap this up and call it a day, we could make this user configurable by making a few more changes. I think a slider would be good for this, there's a slider in the cheat menu that we can use as a reference. Let's find it in `soh/soh/SohMenuBar.cpp` around line 1120: +Rebuild the game and launch it, then load a save file. You should see that the time of day is now moving much faster. Terrific! While we could wrap this up and call it a day, we could make this user configurable by making a few more changes. I think a slider would be good for this, there's a slider in the cheat menu that we can use as a reference. Let's find it in `soh/soh/SohGui/SohMenuEnhancements.cpp` around line 1565: ```cpp -UIWidgets::EnhancementSliderFloat("Hookshot Reach Multiplier: %.1fx", "##gCheatHookshotReachMultiplier", "gCheatHookshotReachMultiplier", 1.0f, 5.0f, "", 1.0f, false); + AddWidget(path, "Hookshot Reach Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("HookshotReachMultiplier")) + .Options(FloatSliderOptions().Format("%.2f").Min(1.0f).Max(5.0f)); ``` - -The float values being passed in here are `minimum`, `maximum`, and `default` respectively. We'll make our minimum 0.2 to allow it to move slower, and our maximum 5.0 to allow it to move up to 5x faster. We'll also set the default to 1.0 so that it doesn't change the behavior by default. Copy this line and paste it below, then make the relevant changes: +This adds a `Widget` which sets a CVar, which then sets the options of the slider. We'll make our minimum 0.2 to allow it to move slower, and our maximum 5.0 to allow it to move up to 5x faster. We'll also set the default to 1.0 so that it doesn't change the behavior by default. Copy this line and paste it below, then make the relevant changes: ```cpp -UIWidgets::EnhancementSliderFloat("Time Multiplier: %.1fx", "##gCheatTimeMultiplier", "gCheatTimeMultiplier", 0.2f, 5.0f, "", 1.0f, false); + AddWidget(path, "Time Multiplier: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_CHEAT("TimeOfDayMultiplier")) + .Options(FloatSliderOptions().Format("%.2f").Min(0.2f).Max(5.0f).DefaultValue(1.0f)); ``` Now we need to replace our hard coded values with the new variable. We can do this by replacing the `10` with a cvar call @@ -88,10 +104,10 @@ Now we need to replace our hard coded values with the new variable. We can do th ```diff if (IS_DAY || gTimeIncrement >= 0x190) { - gSaveContext.dayTime += gTimeIncrement * 10; -+ gSaveContext.dayTime += gTimeIncrement * CVarGetFloat("gCheatTimeMultiplier", 1.0f); ++ gSaveContext.dayTime += gTimeIncrement * CVarGetFloat(CVAR_CHEAT("TimeOfDayMultiplier"),1.0f); } else { - gSaveContext.dayTime += gTimeIncrement * 2 * 10; -+ gSaveContext.dayTime += gTimeIncrement * 2 * CVarGetFloat("gCheatTimeMultiplier", 1.0f); ++ gSaveContext.dayTime += gTimeIncrement * 2 * CVarGetFloat(CVAR_CHEAT("TimeOfDayMultiplier"),1.0f); } ``` From 213ea742eb7dac6111f63ee43994e1106334cafd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Sat, 14 Jun 2025 05:43:25 +0000 Subject: [PATCH 8/9] Gerudo Guards: offer to throw you in jail (#5390) * Gerudo Guards: offer to throw you in jail Necessary for logic to use being captured in routing * don't intercept Ge3 * TODO_TRANSLATE * cleanup --- .../custom-message/CustomMessageTypes.h | 1 + .../vanilla-behavior/GIVanillaBehavior.h | 8 ++++++ .../Enhancements/randomizer/hook_handlers.cpp | 11 +++++++- soh/soh/OTRGlobals.cpp | 10 ++++--- soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c | 27 ++++++++++--------- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 3bcbaf7b0..b5bc0fd6e 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -163,6 +163,7 @@ typedef enum { TEXT_ANJU_ROUND_THEM_UP_OR_YOULL_PAY = 0x503C, TEXT_ANJU_DONT_TEASE_MY_CUCCOS = 0x503D, TEXT_BIG_POE_COLLECTED_RANDO = 0x5090, + TEXT_GERUDO_GUARD_FRIENDLY = 0x6005, TEXT_HBA_NOT_ON_HORSE = 0x603F, TEXT_HBA_INITIAL_EXPLAINATION = 0x6040, TEXT_HBA_WANT_TO_TRY_AGAIN_YES_NO = 0x6041, diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 5159118b1..e76a806ab 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -575,6 +575,14 @@ typedef enum { // - None VB_GANON_HEAL_BEFORE_FIGHT, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnGe2` + VB_GERUDO_GUARD_SET_ACTION_AFTER_TALK, + // #### `result` // See logic in // ```c diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index ea8ce1e12..994e0fba6 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -46,6 +46,7 @@ extern "C" { #include "src/overlays/actors/ovl_En_Hy/z_en_hy.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" #include "src/overlays/actors/ovl_En_Ds/z_en_ds.h" #include "src/overlays/actors/ovl_En_Gm/z_en_gm.h" #include "src/overlays/actors/ovl_En_Js/z_en_js.h" @@ -55,7 +56,6 @@ extern "C" { #include "src/overlays/actors/ovl_En_Xc/z_en_xc.h" #include "src/overlays/actors/ovl_Fishing/z_fishing.h" #include "src/overlays/actors/ovl_En_Mk/z_en_mk.h" -#include "src/overlays/actors/ovl_En_Ge1/z_en_ge1.h" #include "draw.h" extern SaveContext gSaveContext; @@ -69,6 +69,8 @@ extern void EnMk_Wait(EnMk* enMk, PlayState* play); extern void func_80ABA778(EnNiwLady* enNiwLady, PlayState* play); extern void EnGe1_Wait_Archery(EnGe1* enGe1, PlayState* play); extern void EnGe1_SetAnimationIdle(EnGe1* enGe1); +extern void EnGe1_SetAnimationIdle(EnGe1* enGe1); +extern void EnGe2_SetupCapturePlayer(EnGe2* enGe2, PlayState* play); } bool LocMatchesQuest(Rando::Location loc) { @@ -1435,6 +1437,13 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l } break; } + case VB_GERUDO_GUARD_SET_ACTION_AFTER_TALK: + if (gPlayState->msgCtx.choiceIndex == 0) { + EnGe2* enGe2 = va_arg(args, EnGe2*); + EnGe2_SetupCapturePlayer(enGe2, gPlayState); + *should = false; + } + break; case VB_GERUDOS_BE_FRIENDLY: { *should = CHECK_QUEST_ITEM(QUEST_GERUDO_CARD); break; diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index a2135006a..9ac03b48e 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -2236,7 +2236,7 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { (Randomizer_GetSettingValue(RSK_GOSSIP_STONE_HINTS) == RO_GOSSIP_STONES_NEED_STONE && CHECK_QUEST_ITEM(QUEST_STONE_OF_AGONY)))) { - Actor* stone = GET_PLAYER(play)->talkActor; + Actor* stone = player->talkActor; RandomizerHint stoneHint = RH_NONE; s16 hintParams = stone->params & 0xFF; @@ -2294,7 +2294,7 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { (RandomizerInf)((textId - TEXT_SHOP_ITEM_RANDOM_CONFIRM) + RAND_INF_SHOP_ITEMS_KF_SHOP_ITEM_1)); messageEntry = OTRGlobals::Instance->gRandomizer->GetMerchantMessage(rc, TEXT_SHOP_ITEM_RANDOM_CONFIRM); } else if (textId == TEXT_SCRUB_RANDOM) { - EnDns* enDns = (EnDns*)GET_PLAYER(play)->talkActor; + EnDns* enDns = (EnDns*)player->talkActor; RandomizerCheck rc = OTRGlobals::Instance->gRandomizer->GetCheckFromRandomizerInf( (RandomizerInf)enDns->sohScrubIdentity.randomizerInf); messageEntry = OTRGlobals::Instance->gRandomizer->GetMerchantMessage( @@ -2344,7 +2344,7 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(Randomizer::merchantMessageTableID, textId, MF_AUTO_FORMAT); } else if (textId == TEXT_SKULLTULA_PEOPLE_IM_CURSED) { - actorParams = GET_PLAYER(play)->talkActor->params; + actorParams = player->talkActor->params; if (actorParams == 1 && ctx->GetOption(RSK_KAK_10_SKULLS_HINT)) { messageEntry = ctx->GetHint(RH_KAK_10_SKULLS_HINT)->GetHintMessage(MF_AUTO_FORMAT); } else if (actorParams == 2 && ctx->GetOption(RSK_KAK_20_SKULLS_HINT)) { @@ -2443,6 +2443,10 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { } else if (textId == TEXT_BIG_POE_COLLECTED_RANDO) { messageEntry = CustomMessageManager::Instance->RetrieveMessage(customMessageTableID, textId, MF_AUTO_FORMAT); + } else if (textId == TEXT_GERUDO_GUARD_FRIENDLY && player->talkActor->id == ACTOR_EN_GE2) { + // TODO_TRANSLATE Translate into french and german + messageEntry = CustomMessage("Want me to throw you in jail?&\x1B#Yes please&No thanks#", { QM_GREEN }); + messageEntry.AutoFormat(); } } if (textId == TEXT_GS_NO_FREEZE || textId == TEXT_GS_FREEZE) { diff --git a/soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c b/soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c index 89e846a23..776a241b1 100644 --- a/soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c +++ b/soh/src/overlays/actors/ovl_En_Ge2/z_en_ge2.c @@ -439,20 +439,21 @@ void EnGe2_LookAtPlayer(EnGe2* this, PlayState* play) { void EnGe2_SetActionAfterTalk(EnGe2* this, PlayState* play) { if (Actor_TextboxIsClosing(&this->actor, play)) { - - switch (this->actor.params & 0xFF) { - case GE2_TYPE_PATROLLING: - EnGe2_ChangeAction(this, GE2_ACTION_ABOUTTURN); - break; - case GE2_TYPE_STATIONARY: - EnGe2_ChangeAction(this, GE2_ACTION_STAND); - break; - case GE2_TYPE_GERUDO_CARD_GIVER: - this->actionFunc = EnGe2_WaitLookAtPlayer; - break; + if (GameInteractor_Should(VB_GERUDO_GUARD_SET_ACTION_AFTER_TALK, true, this)) { + switch (this->actor.params & 0xFF) { + case GE2_TYPE_PATROLLING: + EnGe2_ChangeAction(this, GE2_ACTION_ABOUTTURN); + break; + case GE2_TYPE_STATIONARY: + EnGe2_ChangeAction(this, GE2_ACTION_STAND); + break; + case GE2_TYPE_GERUDO_CARD_GIVER: + this->actionFunc = EnGe2_WaitLookAtPlayer; + break; + } + this->actor.update = EnGe2_UpdateFriendly; + this->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; } - this->actor.update = EnGe2_UpdateFriendly; - this->actor.flags &= ~ACTOR_FLAG_TALK_OFFER_AUTO_ACCEPTED; } EnGe2_TurnToFacePlayer(this, play); } From 330e64180cb3cc8c94b65c434717aa2fa7b32911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Sun, 15 Jun 2025 17:19:58 +0000 Subject: [PATCH 9/9] Difficulty: SwitchTimerMultiplier (#5555) * Difficulty: FireTimerMultiplier Introduces slider to adjust timer on fire walls resetting switches * rename, add more timers * also shadow trial, dampe race, deku water * avoid decrementing timer to 0, which with BgMizu can cause timer to go below 0 & break * gtg eye statue * also scale torches * tooltip * Limit difficulty: torches stop at -3 & shadow temple torch puzzle stops at -4 * put timer condition as should when convenient --- .../Enhancements/SwitchTimerMultiplier.cpp | 29 +++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 9 ++++++ soh/soh/SohGui/SohMenuEnhancements.cpp | 5 ++++ .../ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c | 9 ++++-- .../ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c | 6 +++- .../ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c | 4 ++- .../ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c | 4 ++- .../ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c | 5 +++- .../ovl_Bg_Relay_Objects/z_bg_relay_objects.c | 5 ++-- .../actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c | 7 +++-- .../actors/ovl_En_Siofuki/z_en_siofuki.c | 6 +++- .../actors/ovl_Obj_Syokudai/z_obj_syokudai.c | 3 +- 12 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 soh/soh/Enhancements/SwitchTimerMultiplier.cpp diff --git a/soh/soh/Enhancements/SwitchTimerMultiplier.cpp b/soh/soh/Enhancements/SwitchTimerMultiplier.cpp new file mode 100644 index 000000000..94982837d --- /dev/null +++ b/soh/soh/Enhancements/SwitchTimerMultiplier.cpp @@ -0,0 +1,29 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +extern PlayState* gPlayState; +} + +void RegisterSwitchTimerMultiplier() { + COND_VB_SHOULD(VB_SWITCH_TIMER_TICK, CVarGetInteger(CVAR_ENHANCEMENT("SwitchTimerMultiplier"), 0) != 0, { + int multiplier = CVarGetInteger(CVAR_ENHANCEMENT("SwitchTimerMultiplier"), 0); + if (multiplier != 0) { + Actor* actor = va_arg(args, Actor*); + if (multiplier < -3 && actor->id == ACTOR_OBJ_SYOKUDAI) { + multiplier = -3; + } else if (multiplier < -4 && actor->id == ACTOR_BG_GND_DARKMEIRO) { + multiplier = -4; + } + + if (multiplier > 0 && gPlayState->gameplayFrames % (multiplier + 1) != 0) { + *should = false; + } else if (gPlayState->gameplayFrames % (6 + multiplier) == 0) { + s16* timer = va_arg(args, s16*); + *timer -= *timer > 1; + } + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterSwitchTimerMultiplier, { CVAR_ENHANCEMENT("SwitchTimerMultiplier") }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index e76a806ab..eb7bf6e23 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2000,6 +2000,15 @@ typedef enum { // - `*ShotSun` VB_SPAWN_SONG_FAIRY, + // #### `result` + // ```c + // varies, never set should to true + // ``` + // #### `args` + // - `*Actor` + // - `*s16` - timer value + VB_SWITCH_TIMER_TICK, + // #### `result` // ```c // (this->stateFlags1 & PLAYER_STATE1_CARRYING_ACTOR) && (this->heldActor != NULL) && diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 58f197512..15cb81f9d 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1123,6 +1123,11 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip("Dying will delete your file.\n\n" ICON_FA_EXCLAMATION_TRIANGLE " WARNING " ICON_FA_EXCLAMATION_TRIANGLE "\nTHIS IS NOT REVERSIBLE!\nUSE AT YOUR OWN RISK!")); + AddWidget(path, "Switch Timer Multiplier", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR_ENHANCEMENT("SwitchTimerMultiplier")) + .Options(IntSliderOptions().Min(-5).Max(5).DefaultValue(0).Format("%+d").Tooltip( + "-5 will be half as much time, +5 will be 6x as much time. Affects timed switches, torches, GTG statue " + "eyes, & doors in race with Dampe.")); AddWidget(path, "Always Win Goron Pot", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("GoronPot")) .Options(CheckboxOptions().Tooltip("Always get the Heart Piece/Purple Rupee from the Spinning Goron Pot.")); diff --git a/soh/src/overlays/actors/ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c b/soh/src/overlays/actors/ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c index 620bf18ab..4ade8918c 100644 --- a/soh/src/overlays/actors/ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c +++ b/soh/src/overlays/actors/ovl_Bg_Gnd_Darkmeiro/z_bg_gnd_darkmeiro.c @@ -6,6 +6,7 @@ #include "z_bg_gnd_darkmeiro.h" #include "objects/object_demo_kekkai/object_demo_kekkai.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -115,7 +116,9 @@ void BgGndDarkmeiro_UpdateBlockTimer(BgGndDarkmeiro* this, PlayState* play) { if (Flags_GetSwitch(play, ((this->dyna.actor.params >> 8) & 0x3F) + 1)) { if (this->actionFlags & 4) { if (this->timer1 > 0) { - this->timer1--; + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, this, &this->timer1)) { + this->timer1--; + } } else { Flags_UnsetSwitch(play, ((this->dyna.actor.params >> 8) & 0x3F) + 1); this->actionFlags &= ~4; @@ -131,7 +134,9 @@ void BgGndDarkmeiro_UpdateBlockTimer(BgGndDarkmeiro* this, PlayState* play) { if (Flags_GetSwitch(play, ((this->dyna.actor.params >> 8) & 0x3F) + 2)) { if (this->actionFlags & 8) { if (this->timer2 > 0) { - this->timer2--; + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, &this->timer2)) { + this->timer2--; + } } else { Flags_UnsetSwitch(play, ((this->dyna.actor.params >> 8) & 0x3F) + 2); this->actionFlags &= ~8; diff --git a/soh/src/overlays/actors/ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c b/soh/src/overlays/actors/ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c index d818bf03e..de1578b24 100644 --- a/soh/src/overlays/actors/ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c +++ b/soh/src/overlays/actors/ovl_Bg_Hidan_Curtain/z_bg_hidan_curtain.c @@ -6,6 +6,7 @@ #include "z_bg_hidan_curtain.h" #include "objects/gameplay_keep/gameplay_keep.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -191,7 +192,10 @@ void BgHidanCurtain_TurnOff(BgHidanCurtain* this, PlayState* play) { } void BgHidanCurtain_WaitForTimer(BgHidanCurtain* this, PlayState* play) { - DECR(this->timer); + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, this, &this->timer)) { + DECR(this->timer); + } + if (this->timer == 0) { this->actionFunc = BgHidanCurtain_TurnOn; } diff --git a/soh/src/overlays/actors/ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c b/soh/src/overlays/actors/ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c index 2caf4183d..3d46f41c5 100644 --- a/soh/src/overlays/actors/ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c +++ b/soh/src/overlays/actors/ovl_Bg_Hidan_Fwbig/z_bg_hidan_fwbig.c @@ -8,6 +8,7 @@ #include "z_bg_hidan_fwbig.h" #include "objects/gameplay_keep/gameplay_keep.h" #include "objects/object_hidan_objects/object_hidan_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -165,9 +166,10 @@ void BgHidanFwbig_Lower(BgHidanFwbig* this, PlayState* play) { } void BgHidanFwbig_WaitForTimer(BgHidanFwbig* this, PlayState* play) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, this, &this->timer)) { this->timer--; } + if (this->timer == 0) { this->actionFunc = BgHidanFwbig_Rise; } diff --git a/soh/src/overlays/actors/ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c b/soh/src/overlays/actors/ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c index 981e13839..20851b200 100644 --- a/soh/src/overlays/actors/ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c +++ b/soh/src/overlays/actors/ovl_Bg_Menkuri_Eye/z_bg_menkuri_eye.c @@ -6,6 +6,7 @@ #include "z_bg_menkuri_eye.h" #include "objects/object_menkuri_objects/object_menkuri_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_DRAW_CULLING_DISABLED @@ -90,7 +91,8 @@ void BgMenkuriEye_Update(Actor* thisx, PlayState* play) { if (!Flags_GetSwitch(play, this->actor.params)) { if (this->framesUntilDisable != -1) { - if (this->framesUntilDisable != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->framesUntilDisable != 0, this, + &this->framesUntilDisable)) { this->framesUntilDisable -= 1; } if (this->framesUntilDisable == 0) { diff --git a/soh/src/overlays/actors/ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c b/soh/src/overlays/actors/ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c index ffa56a074..19985b2ce 100644 --- a/soh/src/overlays/actors/ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c +++ b/soh/src/overlays/actors/ovl_Bg_Mizu_Shutter/z_bg_mizu_shutter.c @@ -1,5 +1,6 @@ #include "z_bg_mizu_shutter.h" #include "objects/object_mizu_objects/object_mizu_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -137,7 +138,9 @@ void BgMizuShutter_Move(BgMizuShutter* this, PlayState* play) { void BgMizuShutter_WaitForTimer(BgMizuShutter* this, PlayState* play) { if (this->timerMax != 0x3F * 20) { - this->timer--; + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, this, &this->timer)) { + this->timer--; + } func_8002F994(&this->dyna.actor, this->timer); if (this->timer == 0) { Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_METALDOOR_CLOSE); diff --git a/soh/src/overlays/actors/ovl_Bg_Relay_Objects/z_bg_relay_objects.c b/soh/src/overlays/actors/ovl_Bg_Relay_Objects/z_bg_relay_objects.c index f7b663cf9..40887b202 100644 --- a/soh/src/overlays/actors/ovl_Bg_Relay_Objects/z_bg_relay_objects.c +++ b/soh/src/overlays/actors/ovl_Bg_Relay_Objects/z_bg_relay_objects.c @@ -6,6 +6,7 @@ #include "z_bg_relay_objects.h" #include "objects/object_relay_objects/object_relay_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -133,7 +134,7 @@ void func_808A90F4(BgRelayObjects* this, PlayState* play) { void func_808A91AC(BgRelayObjects* this, PlayState* play) { if (this->unk_169 != 5) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, this, &this->timer)) { this->timer--; } func_8002F994(&this->dyna.actor, this->timer); @@ -168,7 +169,7 @@ void BgRelayObjects_DoNothing(BgRelayObjects* this, PlayState* play) { } void func_808A932C(BgRelayObjects* this, PlayState* play) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, &this->timer)) { this->timer--; } if (this->timer == 0) { diff --git a/soh/src/overlays/actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c b/soh/src/overlays/actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c index 4c8ca0050..6715d140c 100644 --- a/soh/src/overlays/actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c +++ b/soh/src/overlays/actors/ovl_Bg_Ydan_Hasi/z_bg_ydan_hasi.c @@ -6,6 +6,7 @@ #include "z_bg_ydan_hasi.h" #include "objects/object_ydan_objects/object_ydan_objects.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -126,9 +127,10 @@ void BgYdanHasi_MoveWater(BgYdanHasi* this, PlayState* play) { } void BgYdanHasi_DecWaterTimer(BgYdanHasi* this, PlayState* play) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, this, &this->timer)) { this->timer--; } + func_8002F994(&this->dyna.actor, this->timer); if (this->timer == 0) { this->actionFunc = BgYdanHasi_MoveWater; @@ -145,9 +147,10 @@ void BgYdanHasi_SetupThreeBlocks(BgYdanHasi* this, PlayState* play) { } void BgYdanHasi_UpdateThreeBlocks(BgYdanHasi* this, PlayState* play) { - if (this->timer != 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->timer != 0, this, &this->timer)) { this->timer--; } + if (this->timer == 0) { if (Math_StepToF(&this->dyna.actor.world.pos.y, this->dyna.actor.home.pos.y, 3.0f) != 0) { Flags_UnsetSwitch(play, this->type); diff --git a/soh/src/overlays/actors/ovl_En_Siofuki/z_en_siofuki.c b/soh/src/overlays/actors/ovl_En_Siofuki/z_en_siofuki.c index 664c75f82..7587b7a8b 100644 --- a/soh/src/overlays/actors/ovl_En_Siofuki/z_en_siofuki.c +++ b/soh/src/overlays/actors/ovl_En_Siofuki/z_en_siofuki.c @@ -6,6 +6,7 @@ #include "z_en_siofuki.h" #include "objects/object_siofuki/object_siofuki.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -188,7 +189,10 @@ void func_80AFC218(EnSiofuki* this, PlayState* play) { func_80AFBE8C(this, play); func_80AFC1D0(this, play); - this->timer--; + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, true, this, &this->timer)) { + this->timer--; + } + if (this->timer < 0) { Flags_UnsetSwitch(play, ((u16)this->dyna.actor.params >> 6) & 0x3F); switch (((u16)this->dyna.actor.params >> 0xC) & 0xF) { diff --git a/soh/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c b/soh/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c index 4758066e2..ab81c8421 100644 --- a/soh/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c +++ b/soh/src/overlays/actors/ovl_Obj_Syokudai/z_obj_syokudai.c @@ -8,6 +8,7 @@ #include "overlays/actors/ovl_En_Arrow/z_en_arrow.h" #include "objects/gameplay_keep/gameplay_keep.h" #include "objects/object_syokudai/object_syokudai.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_HOOKSHOT_PULLS_PLAYER) @@ -239,7 +240,7 @@ void ObjSyokudai_Update(Actor* thisx, PlayState* play2) { Collider_UpdateCylinder(&this->actor, &this->colliderFlame); CollisionCheck_SetAC(play, &play->colChkCtx, &this->colliderFlame.base); - if (this->litTimer > 0) { + if (GameInteractor_Should(VB_SWITCH_TIMER_TICK, this->litTimer > 0, this, &this->litTimer)) { this->litTimer--; if ((this->litTimer == 0) && (torchType != 0)) { sLitTorchCount--;