From bbebb77f624061cbc6d4b15590c7a1d9eaa7529f Mon Sep 17 00:00:00 2001 From: mckinlee Date: Wed, 15 Oct 2025 12:37:41 -0400 Subject: [Enhancement] Remote Bombchu (#1283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RemoteBombchu enhancement, steering math needs some work * Updated steering logic to rotate around the bombchu’s up axis * feedback update --------- Co-authored-by: Garrett Cox --- mm/2s2h/BenGui/BenMenu.cpp | 5 ++ mm/2s2h/Enhancements/Camera/FreeLook.cpp | 7 ++ mm/2s2h/Enhancements/Equipment/RemoteBombchu.cpp | 92 ++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 mm/2s2h/Enhancements/Equipment/RemoteBombchu.cpp diff --git a/mm/2s2h/BenGui/BenMenu.cpp b/mm/2s2h/BenGui/BenMenu.cpp index c1c8d5563..2cfc503e8 100644 --- a/mm/2s2h/BenGui/BenMenu.cpp +++ b/mm/2s2h/BenGui/BenMenu.cpp @@ -930,6 +930,11 @@ void BenMenu::AddEnhancements() { .CVar("gEnhancements.PlayerActions.ArrowCycle") .Options(CheckboxOptions().Tooltip( "While aiming the bow, use R to cycle between Normal, Fire, Ice and Light arrows.")); + AddWidget(path, "Remote Bombchu Control", WIDGET_CVAR_CHECKBOX) + .CVar("gEnhancements.PlayerActions.RemoteBombchu") + .Options(CheckboxOptions().Tooltip( + "Allows you to control the direction of the Bombchu while it is moving. Press B to detonate. Press A to " + "stop controlling the Bombchu.")); AddWidget(path, "Bombchu Drops", WIDGET_CVAR_CHECKBOX) .CVar("gEnhancements.Equipment.ChuDrops") .Options( diff --git a/mm/2s2h/Enhancements/Camera/FreeLook.cpp b/mm/2s2h/Enhancements/Camera/FreeLook.cpp index b4b1fa359..29a43dae9 100644 --- a/mm/2s2h/Enhancements/Camera/FreeLook.cpp +++ b/mm/2s2h/Enhancements/Camera/FreeLook.cpp @@ -17,6 +17,9 @@ extern CameraSetting sCameraSettings[]; extern s32 sCameraInterfaceFlags; } +// Check if bombchu remote control is active +extern bool IsBombchuFocused(); + // Static Data Used For Free Camera static bool sCanFreeLook = false; @@ -151,6 +154,10 @@ bool Camera_CanFreeLook(Camera* camera) { if (gPlayState != nullptr && Player_InCsMode(gPlayState)) { sCanFreeLook = false; } + // Disable freecam during bombchu control + if (IsBombchuFocused()) { + sCanFreeLook = false; + } return sCanFreeLook; } diff --git a/mm/2s2h/Enhancements/Equipment/RemoteBombchu.cpp b/mm/2s2h/Enhancements/Equipment/RemoteBombchu.cpp new file mode 100644 index 000000000..64175b4f9 --- /dev/null +++ b/mm/2s2h/Enhancements/Equipment/RemoteBombchu.cpp @@ -0,0 +1,92 @@ +#include "public/bridge/consolevariablebridge.h" +#include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.h" + +void EnBomChu_Move(EnBomChu*, PlayState*); +void EnBomChu_Explode(EnBomChu*, PlayState*); +void EnBomChu_UpdateRotation(EnBomChu*); +} + +#define CVAR_NAME "gEnhancements.PlayerActions.RemoteBombchu" +#define CVAR CVarGetInteger(CVAR_NAME, 0) + +static bool focused = false; +static EnBomChu* activeBombchu = nullptr; + +bool IsBombchuFocused() { + return focused; +} + +static void ReleaseBombchuFocus() { + Player* player = GET_PLAYER(gPlayState); + Camera_SetFocalActor(Play_GetCamera(gPlayState, player->subCamId), &player->actor); + player->stateFlags1 &= ~PLAYER_STATE1_20; + focused = false; +} + +void RegisterRemoteBombchu() { + // Link can deploy multiple bombchus at once. Only assume control of the last placed one. + COND_ID_HOOK(OnActorInit, ACTOR_EN_BOM_CHU, CVAR, [](Actor* actor) { activeBombchu = (EnBomChu*)actor; }); + + COND_ID_HOOK(OnActorDestroy, ACTOR_EN_BOM_CHU, CVAR, [](Actor* actor) { + Player* player = GET_PLAYER(gPlayState); + + if (actor == (Actor*)activeBombchu) { + activeBombchu = nullptr; + + if (focused) { + ReleaseBombchuFocus(); + } + } + }); + + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_BOM_CHU, CVAR, [](Actor* actor) { + if (actor != (Actor*)activeBombchu) { + return; + } + + Player* player = GET_PLAYER(gPlayState); + Input* input = &gPlayState->state.input[0]; + + if (activeBombchu->actionFunc == EnBomChu_Move) { + if (!focused) { + Camera_SetFocalActor(Play_GetCamera(gPlayState, CutsceneManager_GetCurrentSubCamId(actor->csId)), + actor); + player->stateFlags1 |= PLAYER_STATE1_20; + focused = true; + } + + f32 turnRate = 1500.0f; + f32 stickX = input->cur.stick_x; + if (fabsf(stickX) > 10.0f) { + s16 turnAngle = (s16)(turnRate * (stickX / 85.0f)); + + Matrix_RotateAxisF(BINANG_TO_RAD(-turnAngle), &activeBombchu->axisUp, MTXMODE_NEW); + Vec3f newAxisForwards; + Matrix_MultVec3f(&activeBombchu->axisForwards, &newAxisForwards); + Math_Vec3f_Copy(&activeBombchu->axisForwards, &newAxisForwards); + Math3D_Vec3f_Cross(&activeBombchu->axisUp, &activeBombchu->axisForwards, &activeBombchu->axisLeft); + + EnBomChu_UpdateRotation(activeBombchu); + activeBombchu->actor.shape.rot.x = -activeBombchu->actor.world.rot.x; + activeBombchu->actor.shape.rot.y = activeBombchu->actor.world.rot.y; + activeBombchu->actor.shape.rot.z = activeBombchu->actor.world.rot.z; + } + + if (input->press.button & BTN_B) { + EnBomChu_Explode(activeBombchu, gPlayState); + } + + if (input->press.button & BTN_A) { + activeBombchu = nullptr; + ReleaseBombchuFocus(); + } + } + }); +} + +static RegisterShipInitFunc initFunc(RegisterRemoteBombchu, { CVAR_NAME }); -- cgit v1.2.3