From 0ed782ddced5f3760d150a4490512cce4423015d Mon Sep 17 00:00:00 2001 From: djevangelia <263709373+djevangelia@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:03:18 +0200 Subject: [PATCH] Enhancement, drop throw-only actors toggle (#7023) Co-authored-by: djevangelia --- soh/soh/Enhancements/DropThrowOnlyActors.cpp | 31 +++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 8 +++++ soh/soh/SohGui/SohMenuEnhancements.cpp | 6 ++++ .../actors/ovl_player_actor/z_player.c | 26 +++++++++++----- 4 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 soh/soh/Enhancements/DropThrowOnlyActors.cpp diff --git a/soh/soh/Enhancements/DropThrowOnlyActors.cpp b/soh/soh/Enhancements/DropThrowOnlyActors.cpp new file mode 100644 index 0000000000..d00b1c8875 --- /dev/null +++ b/soh/soh/Enhancements/DropThrowOnlyActors.cpp @@ -0,0 +1,31 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "soh/cvar_prefixes.h" + +void RegisterAllowThrowOnlyDrop() { + COND_VB_SHOULD(VB_ON_ACTOR_THROW_ONLY_CHECK, true, { + Actor* actor = va_arg(args, Actor*); + + switch (actor->id) { + case ACTOR_EN_ISHI: + case ACTOR_EN_KUSA: + case ACTOR_EN_NIW: + case ACTOR_OBJ_TSUBO: + break; + default: + return; + } + + // Set correct flag for held actor + condition for the cvar + if (CVarGetInteger(CVAR_ENHANCEMENT("DropThrowOnlyObjects"), false)) { + actor->flags &= ~ACTOR_FLAG_THROW_ONLY; + *should = true; + } else { + actor->flags |= ACTOR_FLAG_THROW_ONLY; + *should = false; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterAllowThrowOnlyDrop, { CVAR_ENHANCEMENT("DropThrowOnlyObjects") }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index e0e55e83c1..845053d042 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2844,6 +2844,14 @@ typedef enum { // - `*Input` VB_THROW_OR_PUT_DOWN_HELD_ITEM, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + VB_ON_ACTOR_THROW_ONLY_CHECK, + // #### `result` // ```c // true diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 5ded153733..74bcc7757e 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -334,6 +334,12 @@ void SohMenu::AddMenuEnhancements() { "Butterflies will transform into a fairy as soon as you approach them with a Deku Stick, " "skipping the need to stand still and let the butterfly land on your stick.")); + AddWidget(path, "Allow Dropping Throw-Only Objects", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("DropThrowOnlyObjects")) + .Options(CheckboxOptions().Tooltip("Allows normally throw-only objects (such as Cuccos, pots, grass, and small " + "rocks) to be dropped by pressing A while standing still. Can be toggled " + "while holding an object.")); + AddWidget(path, "Convenience", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Quit Fishing at Door", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("QuitFishingAtDoor")) diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 6c35ff2490..521dc3249c 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -7461,13 +7461,23 @@ void func_8083EA94(Player* this, PlayState* play) { Player_AnimPlayOnce(play, this, GET_PLAYER_ANIM(PLAYER_ANIMGROUP_throw, this->modelAnimType)); } -s32 func_8083EAF0(Player* this, Actor* actor) { - if ((actor != NULL) && !(actor->flags & ACTOR_FLAG_THROW_ONLY) && +/** + * Checks if an actor can be thrown or dropped. + * It is assumed that the `actor` argument is the actor currently being carried. + * + * @return true if it can be thrown, false if it can be dropped. + */ +s32 Player_CanThrowCarriedActor(Player* this, Actor* actor) { + // If the actor arg is null, true will be returned. + // It doesn't make sense for a non-existent actor to be thrown or dropped, so + // the safety check should happen before this function is even called. + if ((actor != NULL) && + GameInteractor_Should(VB_ON_ACTOR_THROW_ONLY_CHECK, !(actor->flags & ACTOR_FLAG_THROW_ONLY), actor) && ((this->linearVelocity < 1.1f) || (actor->id == ACTOR_EN_BOM_CHU))) { - return 0; + return false; } - return 1; + return true; } s32 Player_ActionHandler_9(Player* this, PlayState* play) { @@ -7480,17 +7490,17 @@ s32 Player_ActionHandler_9(Player* this, PlayState* play) { CHECK_BTN_ANY(sControlInput->press.button, buttonsToCheck)), sControlInput)) { if (!func_80835644(play, this, this->heldActor)) { - if (!func_8083EAF0(this, this->heldActor)) { + if (!Player_CanThrowCarriedActor(this, this->heldActor)) { Player_SetupAction(play, this, Player_Action_808464B0, 1); Player_AnimPlayOnce(play, this, GET_PLAYER_ANIM(PLAYER_ANIMGROUP_put, this->modelAnimType)); } else { func_8083EA94(this, play); } } - return 1; + return true; } - return 0; + return false; } s32 func_8083EC18(Player* this, PlayState* play, u32 wallFlags) { @@ -11037,7 +11047,7 @@ void Player_UpdateInterface(PlayState* play, Player* this) { } else if ((this->stateFlags1 & PLAYER_STATE1_CARRYING_ACTOR) && (this->getItemId == GI_NONE) && (heldActor != NULL)) { if ((this->actor.bgCheckFlags & BGCHECKFLAG_GROUND) || (heldActor->id == ACTOR_EN_NIW)) { - if (func_8083EAF0(this, heldActor) == 0) { + if (!Player_CanThrowCarriedActor(this, heldActor)) { doAction = DO_ACTION_DROP; } else { doAction = DO_ACTION_THROW;