From 6a31700820b85ff7a02e29d89e4bc885a1677d29 Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Tue, 10 Feb 2026 00:07:34 +0000 Subject: [PATCH] Fixes some Stone of Agony bugs and refactors it to a VB Hook (#6235) Fixes #1727. Specifically, it removes a lot of old situations in which the stone of agony icon would've been hidden, such as shield and z-targeting. It will still be hidden while talking to an NPC or in cutscenes. This does also introduce a subtle change in behavior. It now behaves a bit more like the Shard of Agony from the 3ds version. Instead of always being visible after the stone of agony is obtained, it is now hidden until you get close to a secret spot, appearing at a slightly further range than when the rumble would start. --- soh/soh/Enhancements/Graphics/VisualAgony.cpp | 136 ++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 9 ++ .../actors/ovl_player_actor/z_player.c | 103 +------------ 3 files changed, 146 insertions(+), 102 deletions(-) create mode 100644 soh/soh/Enhancements/Graphics/VisualAgony.cpp diff --git a/soh/soh/Enhancements/Graphics/VisualAgony.cpp b/soh/soh/Enhancements/Graphics/VisualAgony.cpp new file mode 100644 index 000000000..de7fcee33 --- /dev/null +++ b/soh/soh/Enhancements/Graphics/VisualAgony.cpp @@ -0,0 +1,136 @@ +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/cosmetics/cosmeticsTypes.h" +#include "soh/ShipInit.hpp" +#include "textures/icon_item_24_static/icon_item_24_static.h" + +extern "C" { +#include "macros.h" +#include "variables.h" +#include "functions.h" + +extern PlayState* gPlayState; +int16_t OTRGetRectDimensionFromLeftEdge(float v); +float OTRGetDimensionFromLeftEdge(float v); +float OTRGetDimensionFromRightEdge(float v); +void FrameInterpolation_RecordOpenChild(const void* a, int b); +void FrameInterpolation_RecordCloseChild(void); +} + +#define CVAR_VISUAL_AGONY_NAME CVAR_ENHANCEMENT("VisualAgony") +#define CVAR_VISUAL_AGONY_VALUE CVarGetInteger(CVAR_VISUAL_AGONY_NAME, 0) +#define SHOULD_HIDE_VISUAL_AGONY \ + ((player->stateFlags1 & \ + (PLAYER_STATE1_IN_CUTSCENE | PLAYER_STATE1_DEAD | PLAYER_STATE1_TALKING | PLAYER_STATE1_LOADING)) || \ + GameInteractor_NoUIActive()) + +void DrawVisualAgony(Player* player, double temp) { + if (SHOULD_HIDE_VISUAL_AGONY || player->closestSecretDistSq > 80000.0f) { + return; + } + Color_RGB8 stoneOfAgonyColor = { 255, 255, 255 }; + if (CVarGetInteger(CVAR_COSMETIC("HUD.StoneOfAgony.Changed"), 0)) { + stoneOfAgonyColor = CVarGetColor24(CVAR_COSMETIC("HUD.StoneOfAgony.Value"), stoneOfAgonyColor); + } + s16 Top_Margins = (CVarGetInteger(CVAR_COSMETIC("HUD.Margin.T"), 0) * -1); + s16 Left_Margins = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.L"), 0); + s16 Right_Margins = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.R"), 0); + s16 X_Margins_VSOA; + s16 Y_Margins_VSOA; + if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.UseMargins"), 0) != 0) { + if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == ORIGINAL_LOCATION) { + X_Margins_VSOA = Left_Margins; + } else { + X_Margins_VSOA = 0; + } + Y_Margins_VSOA = Top_Margins; + } else { + X_Margins_VSOA = 0; + Y_Margins_VSOA = 0; + } + s16 PosX_VSOA_ori = OTRGetRectDimensionFromLeftEdge(26) + X_Margins_VSOA; + s16 PosY_VSOA_ori = 60 + Y_Margins_VSOA; + s16 PosX_VSOA; + s16 PosY_VSOA; + if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) != 0) { + PosY_VSOA = CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosY"), 0) + Y_Margins_VSOA; + if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == ANCHOR_LEFT) { + if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.UseMargins"), 0) != 0) { + X_Margins_VSOA = Left_Margins; + }; + PosX_VSOA = + OTRGetDimensionFromLeftEdge(CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosX"), 0) + X_Margins_VSOA); + } else if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == ANCHOR_RIGHT) { + if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.UseMargins"), 0) != 0) { + X_Margins_VSOA = Right_Margins; + } + PosX_VSOA = + OTRGetDimensionFromRightEdge(CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosX"), 0) + X_Margins_VSOA); + } else if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == ANCHOR_NONE) { + PosX_VSOA = CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosX"), 0); + } else if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == HIDDEN) { + PosX_VSOA = -9999; + } else { + PosX_VSOA = PosX_VSOA_ori; + } + } else { + PosY_VSOA = PosY_VSOA_ori; + PosX_VSOA = PosX_VSOA_ori; + } + + int rectLeft = PosX_VSOA; // Left X Pos + int rectTop = PosY_VSOA; // Top Y Pos + int rectWidth = 24; // Texture Width + int rectHeight = 24; // Texture Heigh + int DefaultIconA = 50; // Default icon alpha (55 on 255) + + OPEN_DISPS(gPlayState->state.gfxCtx); + gDPPipeSync(OVERLAY_DISP++); + + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, stoneOfAgonyColor.r, stoneOfAgonyColor.g, stoneOfAgonyColor.b, DefaultIconA); + + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, PRIMITIVE, + ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); + if (player->unk_6A0 > 4000000.0f) { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, stoneOfAgonyColor.r, stoneOfAgonyColor.g, stoneOfAgonyColor.b, 255); + } else { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, stoneOfAgonyColor.r, stoneOfAgonyColor.g, stoneOfAgonyColor.b, + DefaultIconA); + } + if (temp == 0 || temp <= 0.1f) { + /*Fail check, it is used to draw off the icon when + link is standing out range but do not refresh unk_6A0. + Also used to make a default value in my case.*/ + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, stoneOfAgonyColor.r, stoneOfAgonyColor.g, stoneOfAgonyColor.b, + DefaultIconA); + } + gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 255); + gDPSetOtherMode(OVERLAY_DISP++, + G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_IA16 | G_TL_TILE | + G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE, + G_AC_NONE | G_ZS_PRIM | G_RM_XLU_SURF | G_RM_XLU_SURF2); + gDPLoadTextureBlock(OVERLAY_DISP++, gQuestIconStoneOfAgonyTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 24, 24, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, + G_TX_NOLOD); + gDPSetOtherMode(OVERLAY_DISP++, + G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_IA16 | G_TL_TILE | + G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE, + G_AC_NONE | G_ZS_PRIM | G_RM_XLU_SURF | G_RM_XLU_SURF2); + gSPWideTextureRectangle(OVERLAY_DISP++, rectLeft << 2, rectTop << 2, (rectLeft + rectWidth) << 2, + (rectTop + rectHeight) << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + CLOSE_DISPS(gPlayState->state.gfxCtx); +} + +void RegisterVisualAgony() { + COND_VB_SHOULD(VB_RUMBLE_FOR_SECRET, CVAR_VISUAL_AGONY_VALUE, { + Player* player = va_arg(args, Player*); + double temp = va_arg(args, double); + DrawVisualAgony(player, temp); + if (*should == true && !SHOULD_HIDE_VISUAL_AGONY) { + Audio_PlaySoundGeneral(NA_SE_SY_MESSAGE_WOMAN, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterVisualAgony, { CVAR_VISUAL_AGONY_NAME }); \ No newline at end of file diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index da262f6ea..0cf3fe92b 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2597,6 +2597,15 @@ typedef enum { // - `s32` commonType VB_SET_IDLE_ANIM, + // #### `result` + // ```c + // player->unk_6A0 > 4000000.0f + // ``` + // #### `args` + // - `*Player` + // - `double` (temp - promoted from `f32`) + VB_RUMBLE_FOR_SECRET + } GIVanillaBehavior; #endif 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 617b76fec..944a7bd82 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -20,7 +20,6 @@ #include "overlays/misc/ovl_kaleido_scope/z_kaleido_scope.h" #include "objects/gameplay_keep/gameplay_keep.h" #include "objects/object_link_child/object_link_child.h" -#include "textures/icon_item_24_static/icon_item_24_static.h" #include #include "soh/Enhancements/item-tables/ItemTableTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" @@ -11763,108 +11762,8 @@ void Player_DetectRumbleSecrets(Player* this) { this->unk_6A0 += temp; - /*Prevent it on horse, while jumping and on title screen. - If you fly around no stone of agony for you! */ - Color_RGB8 stoneOfAgonyColor = { 255, 255, 255 }; - if (CVarGetInteger(CVAR_COSMETIC("HUD.StoneOfAgony.Changed"), 0)) { - stoneOfAgonyColor = CVarGetColor24(CVAR_COSMETIC("HUD.StoneOfAgony.Value"), stoneOfAgonyColor); - } - if (CVarGetInteger(CVAR_ENHANCEMENT("VisualAgony"), 0) && !this->stateFlags1 && !GameInteractor_NoUIActive()) { - s16 Top_Margins = (CVarGetInteger(CVAR_COSMETIC("HUD.Margin.T"), 0) * -1); - s16 Left_Margins = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.L"), 0); - s16 Right_Margins = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.R"), 0); - s16 X_Margins_VSOA; - s16 Y_Margins_VSOA; - if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.UseMargins"), 0) != 0) { - if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == ORIGINAL_LOCATION) { - X_Margins_VSOA = Left_Margins; - }; - Y_Margins_VSOA = Top_Margins; - } else { - X_Margins_VSOA = 0; - Y_Margins_VSOA = 0; - } - s16 PosX_VSOA_ori = OTRGetRectDimensionFromLeftEdge(26) + X_Margins_VSOA; - s16 PosY_VSOA_ori = 60 + Y_Margins_VSOA; - s16 PosX_VSOA; - s16 PosY_VSOA; - if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) != 0) { - PosY_VSOA = CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosY"), 0) + Y_Margins_VSOA; - if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == ANCHOR_LEFT) { - if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.UseMargins"), 0) != 0) { - X_Margins_VSOA = Left_Margins; - }; - PosX_VSOA = OTRGetDimensionFromLeftEdge(CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosX"), 0) + - X_Margins_VSOA); - } else if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == ANCHOR_RIGHT) { - if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.UseMargins"), 0) != 0) { - X_Margins_VSOA = Right_Margins; - }; - PosX_VSOA = OTRGetDimensionFromRightEdge(CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosX"), 0) + - X_Margins_VSOA); - } else if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == ANCHOR_NONE) { - PosX_VSOA = CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosX"), 0); - } else if (CVarGetInteger(CVAR_COSMETIC("HUD.VisualSoA.PosType"), 0) == HIDDEN) { - PosX_VSOA = -9999; - } - } else { - PosY_VSOA = PosY_VSOA_ori; - PosX_VSOA = PosX_VSOA_ori; - } - - int rectLeft = PosX_VSOA; // Left X Pos - int rectTop = PosY_VSOA; // Top Y Pos - int rectWidth = 24; // Texture Width - int rectHeight = 24; // Texture Heigh - int DefaultIconA = 50; // Default icon alpha (55 on 255) - - OPEN_DISPS(gPlayState->state.gfxCtx); - gDPPipeSync(OVERLAY_DISP++); - - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, stoneOfAgonyColor.r, stoneOfAgonyColor.g, stoneOfAgonyColor.b, - DefaultIconA); - - gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, - PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); - if (this->unk_6A0 > 4000000.0f) { - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, stoneOfAgonyColor.r, stoneOfAgonyColor.g, stoneOfAgonyColor.b, - 255); - } else { - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, stoneOfAgonyColor.r, stoneOfAgonyColor.g, stoneOfAgonyColor.b, - DefaultIconA); - } - if (temp == 0 || temp <= 0.1f) { - /*Fail check, it is used to draw off the icon when - link is standing out range but do not refresh unk_6A0. - Also used to make a default value in my case.*/ - gDPSetPrimColor(OVERLAY_DISP++, 0, 0, stoneOfAgonyColor.r, stoneOfAgonyColor.g, stoneOfAgonyColor.b, - DefaultIconA); - } - gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 255); - gDPSetOtherMode(OVERLAY_DISP++, - G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_POINT | G_TT_IA16 | G_TL_TILE | - G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE, - G_AC_NONE | G_ZS_PRIM | G_RM_XLU_SURF | G_RM_XLU_SURF2); - gDPLoadTextureBlock(OVERLAY_DISP++, gQuestIconStoneOfAgonyTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 24, 24, 0, - G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, - G_TX_NOLOD, G_TX_NOLOD); - gDPSetOtherMode(OVERLAY_DISP++, - G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_IA16 | G_TL_TILE | - G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE, - G_AC_NONE | G_ZS_PRIM | G_RM_XLU_SURF | G_RM_XLU_SURF2); - gSPWideTextureRectangle(OVERLAY_DISP++, rectLeft << 2, rectTop << 2, (rectLeft + rectWidth) << 2, - (rectTop + rectHeight) << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); - CLOSE_DISPS(gPlayState->state.gfxCtx); - } - - if (this->unk_6A0 > 4000000.0f) { + if (GameInteractor_Should(VB_RUMBLE_FOR_SECRET, this->unk_6A0 > 4000000.0f, this, temp)) { this->unk_6A0 = 0.0f; - if (CVarGetInteger(CVAR_ENHANCEMENT("VisualAgony"), 0) && !this->stateFlags1 && - !GameInteractor_NoUIActive()) { - // This audio is placed here and not in previous CVar check to prevent ears ra.. :) - Audio_PlaySoundGeneral(NA_SE_SY_MESSAGE_WOMAN, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, - &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale); - } Player_RequestRumble(this, 120, 20, 10, 0); } }