Revise Arrow Cycling (#6490)

1. simplify UI, flashing buttons are unnecessary
2. change arrow without drawing a new arrow
This commit is contained in:
Philip Dubé 2026-04-12 23:03:46 +00:00 committed by GitHub
parent d855742c2f
commit 25eb09180d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -8,8 +8,7 @@ extern "C" {
#include "overlays/actors/ovl_En_Arrow/z_en_arrow.h" #include "overlays/actors/ovl_En_Arrow/z_en_arrow.h"
s32 func_808351D4(Player* thisx, PlayState* play); // Arrow nocked s32 func_808351D4(Player* thisx, PlayState* play); // Arrow nocked
s32 func_808353D8(Player* thisx, PlayState* play); // Aiming in first person void EnArrow_Init(Actor* thisx, PlayState* play);
void Player_InitItemAction(PlayState* play, Player* thisx, PlayerItemAction itemAction);
extern PlayState* gPlayState; extern PlayState* gPlayState;
} }
@ -20,16 +19,6 @@ extern PlayState* gPlayState;
static const s16 sMagicArrowCosts[] = { 4, 4, 8 }; 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[] = { static const PlayerItemAction sArrowCycleOrder[] = {
PLAYER_IA_BOW, PLAYER_IA_BOW,
PLAYER_IA_BOW_FIRE, PLAYER_IA_BOW_FIRE,
@ -54,11 +43,11 @@ static bool HasArrowType(PlayerItemAction itemAction) {
case PLAYER_IA_BOW: case PLAYER_IA_BOW:
return true; return true;
case PLAYER_IA_BOW_FIRE: 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: 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: case PLAYER_IA_BOW_LIGHT:
return (INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT); return INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT;
default: default:
return false; 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() { static bool CanCycleArrows() {
Player* player = GET_PLAYER(gPlayState); Player* player = GET_PLAYER(gPlayState);
// don't allow cycling during minigames return LINK_IS_ADULT && !gSaveContext.minigameState && gPlayState->sceneNum != SCENE_SHOOTING_GALLERY &&
if (gSaveContext.minigameState == MINIGAME_STATUS_ACTIVE) { !(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && player->rideActor == NULL &&
return false;
}
return !(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && player->rideActor == NULL &&
INV_CONTENT(SLOT_BOW) == ITEM_BOW && 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_FIRE) == ITEM_ARROW_FIRE || INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE ||
INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT); 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) { static void UpdateButtonAlpha(s16 flashAlpha, bool isButtonBow, u16* buttonAlpha) {
if (isButtonBow) { if (isButtonBow) {
*buttonAlpha = flashAlpha; *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; gSaveContext.buttonStatus[i] = BTN_ENABLED;
sButtonFlashTimer = BUTTON_FLASH_DURATION;
sButtonFlashCount = 0;
} }
} }
UpdateFlashEffect(play);
} }
static void CycleToNextArrow(PlayState* play, Player* player) { bool ArrowCycleMain() {
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() {
if (gPlayState == nullptr || !CanCycleArrows()) { if (gPlayState == nullptr || !CanCycleArrows()) {
return; return false;
} }
if (sJustCycledFrames > 0) {
sJustCycledFrames--;
}
UpdateFlashEffect(gPlayState);
Player* player = GET_PLAYER(gPlayState); Player* player = GET_PLAYER(gPlayState);
Input* input = &gPlayState->state.input[0]; if (player->heldActor != NULL && player->heldActor->id == ACTOR_EN_ARROW) {
if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) {
if (IsHoldingMagicBow(player) && gSaveContext.magicState != MAGIC_STATE_IDLE && player->heldActor == NULL) { if (IsHoldingMagicBow(player) && gSaveContext.magicState != MAGIC_STATE_IDLE && player->heldActor == NULL) {
Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
return; return true;
} }
// reset magic state to IDLE before cycling to prevent error sound // reset magic state to IDLE before cycling to prevent error sound
gSaveContext.magicState = MAGIC_STATE_IDLE; 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() { 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 // 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, { COND_VB_SHOULD(VB_EXECUTE_PLAYER_ACTION_FUNC, CVAR_ARROW_CYCLE_VALUE, {
Player* player = (Player*)va_arg(args, void*); Player* player = (Player*)va_arg(args, void*);
Input* input = (Input*)va_arg(args, void*); Input* input = (Input*)va_arg(args, void*);
if ((IsAimingBow(player) || sJustCycledFrames > 0) && CHECK_BTN_ANY(input->cur.button, BTN_R)) { if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) {
input->cur.button &= ~BTN_R; if (ArrowCycleMain()) {
input->press.button &= ~BTN_R; input->cur.button &= ~BTN_R;
input->press.button &= ~BTN_R;
}
} }
}); });
@ -270,9 +189,9 @@ void RegisterArrowCycle() {
if (gSaveContext.magic < sMagicArrowCosts[magicArrowType]) { if (gSaveContext.magic < sMagicArrowCosts[magicArrowType]) {
*arrowType = ARROW_NORMAL; *arrowType = ARROW_NORMAL;
} else {
*should = false;
} }
*should = false;
}); });
COND_VB_SHOULD(VB_EN_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, { COND_VB_SHOULD(VB_EN_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, {