From 1f57f72acd852f4e13c3c348156998bcb21449e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 18 Mar 2026 16:23:33 +0000 Subject: [PATCH] Hookify Sunlight Arrows (#6366) --- soh/soh/Enhancements/SunlightArrows.cpp | 97 ++++++++++++++++++ .../vanilla-behavior/GIVanillaBehavior.h | 8 ++ .../ovl_Obj_Lightswitch/z_obj_lightswitch.c | 98 ++----------------- 3 files changed, 113 insertions(+), 90 deletions(-) create mode 100644 soh/soh/Enhancements/SunlightArrows.cpp diff --git a/soh/soh/Enhancements/SunlightArrows.cpp b/soh/soh/Enhancements/SunlightArrows.cpp new file mode 100644 index 000000000..73e894c24 --- /dev/null +++ b/soh/soh/Enhancements/SunlightArrows.cpp @@ -0,0 +1,97 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/randomizer/SeedContext.h" +#include "soh/ShipInit.hpp" +#include "soh/ObjectExtension/ObjectExtension.h" + +extern "C" { +#include "overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.h" +} + +static ColliderJntSphElementInit sColliderLightArrowElementInit[] = { + { + { + ELEMTYPE_UNK0, + { 0x00000000, 0x00, 0x00 }, + { 0x00202000, 0x00, 0x00 }, + TOUCH_NONE, + BUMP_ON, + OCELEM_ON, + }, + { 0, { { 0, 0, 0 }, 19 }, 100 }, + }, +}; +static ColliderJntSphInit sColliderLightArrowInit = { + { + COLTYPE_NONE, + AT_NONE, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_2, + COLSHAPE_JNTSPH, + }, + 1, + sColliderLightArrowElementInit, +}; + +struct SunlightArrowData { + bool activatedByLightArrow = false; +}; + +static ObjectExtension::Register SunlightArrowDataRegister; + +void RegisterSunlightArrowsHooks() { + bool shouldRegister = + CVarGetInteger(CVAR_ENHANCEMENT("SunlightArrows"), 0) || (IS_RANDO && RAND_GET_OPTION(RSK_SUNLIGHT_ARROWS)); + + COND_ID_HOOK(OnActorInit, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actor) { + auto* thisx = (ObjLightswitch*)actor; + Collider_SetJntSph(gPlayState, &thisx->collider, &thisx->actor, &sColliderLightArrowInit, thisx->colliderItems); + Collider_UpdateSpheres(0, &thisx->collider); + }); + + COND_ID_HOOK(OnActorDestroy, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actor) { + auto* thisx = (ObjLightswitch*)actor; + + auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); + if (sunData != nullptr && sunData->activatedByLightArrow) { + switch (thisx->actor.params >> 4 & 3) { + case OBJLIGHTSWITCH_TYPE_STAY_ON: + case OBJLIGHTSWITCH_TYPE_2: + case OBJLIGHTSWITCH_TYPE_1: + // Unset the switch flag on room exit to prevent the rock in the wall from + // vanishing on its own after activating the sun switch by Light Arrow + // Also prevents the cobra mirror from rotating to face the sun on its own + // Makes sun switches temporary when activated by Light Arrows (will turn off on room exit) + if (thisx->actor.room != 25) { + Flags_UnsetSwitch(gPlayState, thisx->actor.params >> 8 & 0x3F); + } + break; + case OBJLIGHTSWITCH_TYPE_BURN: + break; + } + } + }); + + COND_VB_SHOULD(VB_LIGHTSWITCH_OFF, shouldRegister, { + ObjLightswitch* thisx = va_arg(args, ObjLightswitch*); + auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); + if (sunData != nullptr && sunData->activatedByLightArrow) { + *should = false; + } + }); + + COND_ID_HOOK(ShouldActorUpdate, ACTOR_OBJ_LIGHTSWITCH, shouldRegister, [](void* actorPtr, bool* result) { + ObjLightswitch* thisx = (ObjLightswitch*)actorPtr; + if ((thisx->collider.base.acFlags & AC_HIT) && thisx->collider.base.ac != nullptr) { + auto sunData = ObjectExtension::GetInstance().Get(&thisx->actor); + if (sunData == nullptr) { + ObjectExtension::GetInstance().Set(&thisx->actor, SunlightArrowData{}); + sunData = ObjectExtension::GetInstance().Get(&thisx->actor); + } + + sunData->activatedByLightArrow = thisx->collider.base.ac->id == ACTOR_EN_ARROW; + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterSunlightArrowsHooks, { "IS_RANDO", CVAR_ENHANCEMENT("SunlightArrows") }); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index a86996242..2d70dd02b 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1323,6 +1323,14 @@ typedef enum { // - `*EnKz` VB_KING_ZORA_TUNIC_CHECK, + // #### `result` + // ```c + // varies + // ``` + // #### `args` + // - `ObjLightswitch*` + VB_LIGHTSWITCH_OFF, + // #### `result` // ```c // true diff --git a/soh/src/overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.c b/soh/src/overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.c index 18408617d..6f99ff87e 100644 --- a/soh/src/overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.c +++ b/soh/src/overlays/actors/ovl_Obj_Lightswitch/z_obj_lightswitch.c @@ -8,7 +8,7 @@ #include "vt.h" #include "overlays/actors/ovl_Obj_Oshihiki/z_obj_oshihiki.h" #include "objects/object_lightswitch/object_lightswitch.h" -#include "soh/OTRGlobals.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -74,36 +74,6 @@ static ColliderJntSphInit sColliderJntSphInit = { 1, sColliderJntSphElementInit, }; -// Collider info used for "Sunlight Arrows" -static ColliderJntSphElementInit sColliderLightArrowElementInit[] = { - { - { - ELEMTYPE_UNK0, - { 0x00000000, 0x00, 0x00 }, - { 0x00202000, 0x00, 0x00 }, - TOUCH_NONE, - BUMP_ON, - OCELEM_ON, - }, - { 0, { { 0, 0, 0 }, 19 }, 100 }, - }, -}; -// Sphere collider used for "Sunlight Arrows" -static ColliderJntSphInit sColliderLightArrowInit = { - { - COLTYPE_NONE, - AT_NONE, - AC_ON | AC_TYPE_PLAYER, - OC1_ON | OC1_TYPE_ALL, - OC2_TYPE_2, - COLSHAPE_JNTSPH, - }, - 1, - sColliderLightArrowElementInit, -}; - -bool sunSwitchActivatedByLightArrow = false; -bool sunLightArrowsEnabledOnSunSwitchLoad = false; static CollisionCheckInfoInit sColChkInfoInit = { 0, 12, 60, MASS_IMMOVABLE }; @@ -123,17 +93,8 @@ static InitChainEntry sInitChain[] = { void ObjLightswitch_InitCollider(ObjLightswitch* this, PlayState* play) { s32 pad; - // Initialize this with the sun switch, so it can't be affected by toggling while the actor is loaded - sunLightArrowsEnabledOnSunSwitchLoad = CVarGetInteger(CVAR_ENHANCEMENT("SunlightArrows"), 0) || - (IS_RANDO && Randomizer_GetSettingValue(RSK_SUNLIGHT_ARROWS)); - Collider_InitJntSph(play, &this->collider); - // If "Sunlight Arrows" is enabled, set up the collider to allow Light Arrow hits - if (sunLightArrowsEnabledOnSunSwitchLoad) { - Collider_SetJntSph(play, &this->collider, &this->actor, &sColliderLightArrowInit, this->colliderItems); - } else { - Collider_SetJntSph(play, &this->collider, &this->actor, &sColliderJntSphInit, this->colliderItems); - } + Collider_SetJntSph(play, &this->collider, &this->actor, &sColliderJntSphInit, this->colliderItems); Matrix_SetTranslateRotateYXZ(this->actor.world.pos.x, this->actor.world.pos.y + (this->actor.shape.yOffset * this->actor.scale.y), this->actor.world.pos.z, &this->actor.shape.rot); @@ -249,26 +210,6 @@ void ObjLightswitch_Destroy(Actor* thisx, PlayState* play2) { PlayState* play = play2; ObjLightswitch* this = (ObjLightswitch*)thisx; - // Unset the switch flag on room exit to prevent the rock in the wall from - // vanishing on its own after activating the sun switch by Light Arrow - // Also prevents the cobra mirror from rotating to face the sun on its own - // Makes sun switches temporary when activated by Light Arrows (will turn off on room exit) - if (sunSwitchActivatedByLightArrow) { - switch (this->actor.params >> 4 & 3) { - case OBJLIGHTSWITCH_TYPE_STAY_ON: - case OBJLIGHTSWITCH_TYPE_2: - case OBJLIGHTSWITCH_TYPE_1: - // Except for this one, because we want the chain platform to stay down for good - if (this->actor.room != 25) { - Flags_UnsetSwitch(play, this->actor.params >> 8 & 0x3F); - } - sunSwitchActivatedByLightArrow = false; - break; - case OBJLIGHTSWITCH_TYPE_BURN: - break; - } - } - Collider_DestroyJntSph(play, &this->collider); } @@ -279,9 +220,6 @@ void ObjLightswitch_SetupOff(ObjLightswitch* this) { this->color[1] = 125 << 6; this->color[2] = 255 << 6; this->alpha = 255 << 6; - if (sunLightArrowsEnabledOnSunSwitchLoad) { - sunSwitchActivatedByLightArrow = false; - } } // A Sun Switch that is currently turned off void ObjLightswitch_Off(ObjLightswitch* this, PlayState* play) { @@ -291,13 +229,6 @@ void ObjLightswitch_Off(ObjLightswitch* this, PlayState* play) { if (this->collider.base.acFlags & AC_HIT) { ObjLightswitch_SetupTurnOn(this); ObjLightswitch_SetSwitchFlag(this, play); - // Remember if we've been activated by a Light Arrow, so we can - // prevent the switch from immediately turning back off - if (sunLightArrowsEnabledOnSunSwitchLoad) { - if (this->collider.base.ac != NULL && this->collider.base.ac->id == ACTOR_EN_ARROW) { - sunSwitchActivatedByLightArrow = true; - } - } } break; case OBJLIGHTSWITCH_TYPE_1: @@ -364,33 +295,20 @@ void ObjLightswitch_On(ObjLightswitch* this, PlayState* play) { if (!Flags_GetSwitch(play, this->actor.params >> 8 & 0x3F)) { ObjLightswitch_SetupTurnOff(this); } - // If hit by sunlight after already being turned on, then behave as if originally activated by sunlight - if (sunLightArrowsEnabledOnSunSwitchLoad && (this->collider.base.acFlags & AC_HIT)) { - if (this->collider.base.ac != NULL && this->collider.base.ac->id != ACTOR_EN_ARROW) { - sunSwitchActivatedByLightArrow = false; - } - } break; case OBJLIGHTSWITCH_TYPE_1: - if (this->collider.base.acFlags & AC_HIT && !(this->prevFrameACflags & AC_HIT)) { + if (GameInteractor_Should(VB_LIGHTSWITCH_OFF, + this->collider.base.acFlags & AC_HIT && !(this->prevFrameACflags & AC_HIT), + this)) { ObjLightswitch_SetupTurnOff(this); ObjLightswitch_ClearSwitchFlag(this, play); } break; case OBJLIGHTSWITCH_TYPE_2: - // If hit by sunlight after already being turned on, then behave as if originally activated by sunlight - if (sunLightArrowsEnabledOnSunSwitchLoad && (this->collider.base.acFlags & AC_HIT)) { - if (this->collider.base.ac != NULL && this->collider.base.ac->id != ACTOR_EN_ARROW) { - sunSwitchActivatedByLightArrow = false; - } - } - if (!(this->collider.base.acFlags & AC_HIT)) { + if (GameInteractor_Should(VB_LIGHTSWITCH_OFF, !(this->collider.base.acFlags & AC_HIT), this)) { if (this->timer >= 7) { - // If we aren't using Enhanced Light Arrows, let the switch turn off normally - if (!sunSwitchActivatedByLightArrow) { - ObjLightswitch_SetupTurnOff(this); - ObjLightswitch_ClearSwitchFlag(this, play); - } + ObjLightswitch_SetupTurnOff(this); + ObjLightswitch_ClearSwitchFlag(this, play); } else { this->timer++; }