From 25eb09180d9f476cc9bcfaa65214ab2786f75d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= <159546+serprex@users.noreply.github.com> Date: Sun, 12 Apr 2026 23:03:46 +0000 Subject: [PATCH] Revise Arrow Cycling (#6490) 1. simplify UI, flashing buttons are unnecessary 2. change arrow without drawing a new arrow --- soh/soh/Enhancements/ArrowCycle.cpp | 169 ++++++++-------------------- 1 file changed, 44 insertions(+), 125 deletions(-) diff --git a/soh/soh/Enhancements/ArrowCycle.cpp b/soh/soh/Enhancements/ArrowCycle.cpp index 45d8d3445..202f24c4e 100644 --- a/soh/soh/Enhancements/ArrowCycle.cpp +++ b/soh/soh/Enhancements/ArrowCycle.cpp @@ -8,8 +8,7 @@ extern "C" { #include "overlays/actors/ovl_En_Arrow/z_en_arrow.h" s32 func_808351D4(Player* thisx, PlayState* play); // Arrow nocked -s32 func_808353D8(Player* thisx, PlayState* play); // Aiming in first person -void Player_InitItemAction(PlayState* play, Player* thisx, PlayerItemAction itemAction); +void EnArrow_Init(Actor* thisx, PlayState* play); extern PlayState* gPlayState; } @@ -20,16 +19,6 @@ extern PlayState* gPlayState; static const s16 sMagicArrowCosts[] = { 4, 4, 8 }; -#define MINIGAME_STATUS_ACTIVE 1 - -static const s16 BUTTON_FLASH_DURATION = 3; -static const s16 BUTTON_FLASH_COUNT = 3; -static const s16 BUTTON_HIGHLIGHT_ALPHA = 128; - -static s16 sButtonFlashTimer = 0; -static s16 sButtonFlashCount = 0; -static s16 sJustCycledFrames = 0; - static const PlayerItemAction sArrowCycleOrder[] = { PLAYER_IA_BOW, PLAYER_IA_BOW_FIRE, @@ -54,11 +43,11 @@ static bool HasArrowType(PlayerItemAction itemAction) { case PLAYER_IA_BOW: return true; case PLAYER_IA_BOW_FIRE: - return (INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE); + return INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE; case PLAYER_IA_BOW_ICE: - return (INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE); + return INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE; case PLAYER_IA_BOW_LIGHT: - return (INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT); + return INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT; default: return false; } @@ -77,15 +66,24 @@ static s32 GetBowItemForArrow(PlayerItemAction itemAction) { } } +static ArrowType GetArrowTypeForArrow(s8 itemAction) { + switch (itemAction) { + case PLAYER_IA_BOW_FIRE: + return ARROW_FIRE; + case PLAYER_IA_BOW_ICE: + return ARROW_ICE; + case PLAYER_IA_BOW_LIGHT: + return ARROW_LIGHT; + default: + return ARROW_NORMAL; + } +} + static bool CanCycleArrows() { Player* player = GET_PLAYER(gPlayState); - // don't allow cycling during minigames - if (gSaveContext.minigameState == MINIGAME_STATUS_ACTIVE) { - return false; - } - - return !(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && player->rideActor == NULL && + return LINK_IS_ADULT && !gSaveContext.minigameState && gPlayState->sceneNum != SCENE_SHOOTING_GALLERY && + !(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && player->rideActor == NULL && INV_CONTENT(SLOT_BOW) == ITEM_BOW && (INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE || INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE || INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT); @@ -113,66 +111,6 @@ static s8 GetNextArrowType(s8 currentArrowType) { static void UpdateButtonAlpha(s16 flashAlpha, bool isButtonBow, u16* buttonAlpha) { if (isButtonBow) { *buttonAlpha = flashAlpha; - if (sButtonFlashTimer == 0) { - *buttonAlpha = 255; - } - } -} - -static void UpdateFlashEffect(PlayState* play) { - if (sButtonFlashTimer <= 0) { - return; - } - - sButtonFlashTimer--; - s16 flashAlpha = (sButtonFlashTimer % 3) ? BUTTON_HIGHLIGHT_ALPHA : 255; - - if (sButtonFlashTimer == 0 && sButtonFlashCount < BUTTON_FLASH_COUNT - 1) { - sButtonFlashTimer = BUTTON_FLASH_DURATION; - sButtonFlashCount++; - } - UpdateButtonAlpha(flashAlpha, - (gSaveContext.equips.buttonItems[1] == ITEM_BOW) || - (gSaveContext.equips.buttonItems[1] >= ITEM_BOW_ARROW_FIRE && - gSaveContext.equips.buttonItems[1] <= ITEM_BOW_ARROW_LIGHT), - &play->interfaceCtx.cLeftAlpha); - - UpdateButtonAlpha(flashAlpha, - (gSaveContext.equips.buttonItems[2] == ITEM_BOW) || - (gSaveContext.equips.buttonItems[2] >= ITEM_BOW_ARROW_FIRE && - gSaveContext.equips.buttonItems[2] <= ITEM_BOW_ARROW_LIGHT), - &play->interfaceCtx.cDownAlpha); - - UpdateButtonAlpha(flashAlpha, - (gSaveContext.equips.buttonItems[3] == ITEM_BOW) || - (gSaveContext.equips.buttonItems[3] >= ITEM_BOW_ARROW_FIRE && - gSaveContext.equips.buttonItems[3] <= ITEM_BOW_ARROW_LIGHT), - &play->interfaceCtx.cRightAlpha); - - if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) { - UpdateButtonAlpha(flashAlpha, - (gSaveContext.equips.buttonItems[4] == ITEM_BOW) || - (gSaveContext.equips.buttonItems[4] >= ITEM_BOW_ARROW_FIRE && - gSaveContext.equips.buttonItems[4] <= ITEM_BOW_ARROW_LIGHT), - &play->interfaceCtx.dpadRightAlpha); - - UpdateButtonAlpha(flashAlpha, - (gSaveContext.equips.buttonItems[5] == ITEM_BOW) || - (gSaveContext.equips.buttonItems[5] >= ITEM_BOW_ARROW_FIRE && - gSaveContext.equips.buttonItems[5] <= ITEM_BOW_ARROW_LIGHT), - &play->interfaceCtx.dpadLeftAlpha); - - UpdateButtonAlpha(flashAlpha, - (gSaveContext.equips.buttonItems[6] == ITEM_BOW) || - (gSaveContext.equips.buttonItems[6] >= ITEM_BOW_ARROW_FIRE && - gSaveContext.equips.buttonItems[6] <= ITEM_BOW_ARROW_LIGHT), - &play->interfaceCtx.dpadDownAlpha); - - UpdateButtonAlpha(flashAlpha, - (gSaveContext.equips.buttonItems[7] == ITEM_BOW) || - (gSaveContext.equips.buttonItems[7] >= ITEM_BOW_ARROW_FIRE && - gSaveContext.equips.buttonItems[7] <= ITEM_BOW_ARROW_LIGHT), - &play->interfaceCtx.dpadUpAlpha); } } @@ -193,72 +131,53 @@ static void UpdateEquippedBow(PlayState* play, s8 arrowType) { } gSaveContext.buttonStatus[i] = BTN_ENABLED; - sButtonFlashTimer = BUTTON_FLASH_DURATION; - sButtonFlashCount = 0; } } - - UpdateFlashEffect(play); } -static void CycleToNextArrow(PlayState* play, Player* player) { - s8 nextArrow = GetNextArrowType(player->heldItemAction); - - if (player->heldActor != NULL && player->heldActor->id == ACTOR_EN_ARROW) { - EnArrow* arrow = (EnArrow*)player->heldActor; - - if (arrow->actor.child != NULL) { - Actor_Kill(arrow->actor.child); - } - - Actor_Kill(&arrow->actor); - } - - Player_InitItemAction(play, player, (PlayerItemAction)nextArrow); - UpdateEquippedBow(play, nextArrow); - Audio_PlaySoundGeneral(NA_SE_PL_CHANGE_ARMS, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, - &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); - sJustCycledFrames = 2; -} - -void ArrowCycleMain() { +bool ArrowCycleMain() { if (gPlayState == nullptr || !CanCycleArrows()) { - return; + return false; } - if (sJustCycledFrames > 0) { - sJustCycledFrames--; - } - - UpdateFlashEffect(gPlayState); - Player* player = GET_PLAYER(gPlayState); - Input* input = &gPlayState->state.input[0]; - - if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) { + if (player->heldActor != NULL && player->heldActor->id == ACTOR_EN_ARROW) { if (IsHoldingMagicBow(player) && gSaveContext.magicState != MAGIC_STATE_IDLE && player->heldActor == NULL) { Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); - return; + return true; } // reset magic state to IDLE before cycling to prevent error sound gSaveContext.magicState = MAGIC_STATE_IDLE; - CycleToNextArrow(gPlayState, player); + s8 nextArrow = GetNextArrowType(player->heldItemAction); + player->heldItemAction = nextArrow; + player->itemAction = nextArrow; + Actor* arrow = player->heldActor; + + if (arrow->child != NULL) { + Actor_Kill(arrow->child); + arrow->child = NULL; + } + arrow->params = GetArrowTypeForArrow(nextArrow); + EnArrow_Init(arrow, gPlayState); + UpdateEquippedBow(gPlayState, nextArrow); + return true; } + return false; } void RegisterArrowCycle() { - COND_ID_HOOK(OnActorUpdate, ACTOR_PLAYER, CVAR_ARROW_CYCLE_VALUE, [](void* actor) { ArrowCycleMain(); }); - // suppress shield input when R is held while aiming to allow arrow cycling COND_VB_SHOULD(VB_EXECUTE_PLAYER_ACTION_FUNC, CVAR_ARROW_CYCLE_VALUE, { Player* player = (Player*)va_arg(args, void*); Input* input = (Input*)va_arg(args, void*); - if ((IsAimingBow(player) || sJustCycledFrames > 0) && CHECK_BTN_ANY(input->cur.button, BTN_R)) { - input->cur.button &= ~BTN_R; - input->press.button &= ~BTN_R; + if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) { + if (ArrowCycleMain()) { + input->cur.button &= ~BTN_R; + input->press.button &= ~BTN_R; + } } }); @@ -270,9 +189,9 @@ void RegisterArrowCycle() { if (gSaveContext.magic < sMagicArrowCosts[magicArrowType]) { *arrowType = ARROW_NORMAL; + } else { + *should = false; } - - *should = false; }); COND_VB_SHOULD(VB_EN_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, {