i control boom (#6136)

This commit is contained in:
mckinlee
2026-01-16 19:26:31 -05:00
committed by GitHub
parent 04df48944a
commit 69f151e79d
2 changed files with 212 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
extern "C" {
#include "src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.h"
void EnBomChu_Move(EnBomChu*, PlayState*);
void EnBomChu_Explode(EnBomChu*, PlayState*);
s32 Camera_BGCheck(Camera* camera, Vec3f* from, Vec3f* to);
}
#define CVAR_REMOTE_BOMBCHU_NAME CVAR_ENHANCEMENT("RemoteBombchu")
#define CVAR_REMOTE_BOMBCHU_DEFAULT 0
#define CVAR_REMOTE_BOMBCHU_VALUE CVarGetInteger(CVAR_REMOTE_BOMBCHU_NAME, CVAR_REMOTE_BOMBCHU_DEFAULT)
// Camera constants
#define CAM_DIST 200.0f
#define CAM_HEIGHT 50.0f
#define CAM_OFFSET_UP 15.0f
// Control constants
#define TURN_RATE 1200.0f
#define STICK_THRESHOLD 10.0f
typedef struct {
EnBomChu* activeChu;
s16 subCamId;
bool isActive;
Vec3f cameraEye;
Vec3f cameraAt;
} RemoteBombchuState;
static RemoteBombchuState sState = { nullptr, SUBCAM_NONE, false, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } };
static void RotateVectorAroundAxis(Vec3f* vec, Vec3f* axis, f32 angle) {
Matrix_RotateAxis(angle, axis, MTXMODE_NEW);
Vec3f result;
Matrix_MultVec3f(vec, &result);
*vec = result;
}
static void StartControl(PlayState* play) {
if (sState.isActive)
return;
sState.subCamId = Play_CreateSubCamera(play);
if (sState.subCamId == SUBCAM_NONE)
return;
Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT);
Play_ChangeCameraStatus(play, sState.subCamId, CAM_STAT_ACTIVE);
// Initialize camera vectors from main camera for smooth transition
Camera* mainCam = Play_GetCamera(play, MAIN_CAM);
sState.cameraEye = mainCam->eye;
sState.cameraAt = mainCam->at;
Player* player = GET_PLAYER(play);
player->stateFlags1 |= PLAYER_STATE1_INPUT_DISABLED;
// Reset first-person/item camera state
player->unk_6AD = 0;
sState.isActive = true;
}
static void StopControl(PlayState* play) {
if (!sState.isActive)
return;
if (sState.subCamId != SUBCAM_NONE) {
Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_ACTIVE);
Play_ClearCamera(play, sState.subCamId);
sState.subCamId = SUBCAM_NONE;
}
Player* player = GET_PLAYER(play);
player->stateFlags1 &= ~PLAYER_STATE1_INPUT_DISABLED;
sState.activeChu = nullptr;
sState.isActive = false;
}
// UpdateCamera function
static void UpdateCamera(EnBomChu* chu, PlayState* play) {
Vec3f targetEye, targetAt;
// Calculate target "At" point (looking at the bombchu, slightly above)
targetAt.x = chu->actor.world.pos.x + chu->axisUp.x * CAM_OFFSET_UP;
targetAt.y = chu->actor.world.pos.y + chu->axisUp.y * CAM_OFFSET_UP;
targetAt.z = chu->actor.world.pos.z + chu->axisUp.z * CAM_OFFSET_UP;
// Calculate target "Eye" position (behind and above the bombchu)
targetEye.x = targetAt.x - chu->axisForwards.x * CAM_DIST + chu->axisUp.x * CAM_HEIGHT;
targetEye.y = targetAt.y - chu->axisForwards.y * CAM_DIST + chu->axisUp.y * CAM_HEIGHT;
targetEye.z = targetAt.z - chu->axisForwards.z * CAM_DIST + chu->axisUp.z * CAM_HEIGHT;
// Smoothly approach "At" position
Math_ApproachF(&sState.cameraAt.x, targetAt.x, 0.2f, 30.0f);
Math_ApproachF(&sState.cameraAt.y, targetAt.y, 0.2f, 30.0f);
Math_ApproachF(&sState.cameraAt.z, targetAt.z, 0.2f, 30.0f);
Camera* camera = Play_GetCamera(play, sState.subCamId);
// Check if the ideal target eye position collides with walls
Vec3f safeEye = targetEye;
Camera_BGCheck(camera, &sState.cameraAt, &safeEye);
// Smoothly approach the *safe* eye position
Math_ApproachF(&sState.cameraEye.x, safeEye.x, 0.2f, 50.0f);
Math_ApproachF(&sState.cameraEye.y, safeEye.y, 0.2f, 50.0f);
Math_ApproachF(&sState.cameraEye.z, safeEye.z, 0.2f, 50.0f);
Play_CameraSetAtEye(play, sState.subCamId, &sState.cameraAt, &sState.cameraEye);
}
static void HandleSteering(EnBomChu* chu, Input* input) {
f32 stickX = input->cur.stick_x;
if (fabsf(stickX) <= STICK_THRESHOLD)
return;
// Calculate turn angle based on stick input
f32 turnAngle = BINANG_TO_RAD((s16)(TURN_RATE * (stickX / 85.0f)));
// Rotate forward and left vectors around the up axis
RotateVectorAroundAxis(&chu->axisForwards, &chu->axisUp, -turnAngle);
RotateVectorAroundAxis(&chu->axisLeft, &chu->axisUp, -turnAngle);
// Update actor rotation from the new orientation
MtxF mf;
mf.xx = chu->axisLeft.x;
mf.yx = chu->axisLeft.y;
mf.zx = chu->axisLeft.z;
mf.xy = chu->axisUp.x;
mf.yy = chu->axisUp.y;
mf.zy = chu->axisUp.z;
mf.xz = chu->axisForwards.x;
mf.yz = chu->axisForwards.y;
mf.zz = chu->axisForwards.z;
Matrix_MtxFToYXZRotS(&mf, &chu->actor.world.rot, 0);
chu->actor.world.rot.x = -chu->actor.world.rot.x;
chu->actor.shape.rot = chu->actor.world.rot;
chu->actor.shape.rot.x = -chu->actor.shape.rot.x;
}
static void HandleInput(EnBomChu* chu, Input* input, PlayState* play) {
HandleSteering(chu, input);
if (input->press.button & BTN_B) {
EnBomChu_Explode(chu, play);
}
if (input->press.button & BTN_A) {
StopControl(play);
}
}
// Track the most recently spawned bombchu
static void OnActorInit(void* refActor) {
sState.activeChu = (EnBomChu*)refActor;
}
// Clean up if active bombchu is destroyed
static void OnActorDestroy(void* refActor) {
if (refActor == sState.activeChu) {
if (sState.isActive) {
StopControl(gPlayState);
}
sState.activeChu = nullptr;
}
}
// Main update logic
static void OnActorUpdate(void* refActor) {
if (refActor != sState.activeChu)
return;
if (sState.activeChu->actionFunc != EnBomChu_Move)
return;
// Start control when bombchu begins moving
if (!sState.isActive) {
StartControl(gPlayState);
}
// Exit early if control was not established or was stopped
if (!sState.isActive)
return;
HandleInput(sState.activeChu, &gPlayState->state.input[0], gPlayState);
// Check again after input handling (user might have pressed A to stop)
if (!sState.isActive)
return;
UpdateCamera(sState.activeChu, gPlayState);
}
void RegisterRemoteBombchu() {
COND_ID_HOOK(OnActorInit, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorInit);
COND_ID_HOOK(OnActorDestroy, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorDestroy);
COND_ID_HOOK(OnActorUpdate, ACTOR_EN_BOM_CHU, CVAR_REMOTE_BOMBCHU_VALUE, OnActorUpdate);
}
static RegisterShipInitFunc initFunc(RegisterRemoteBombchu, { CVAR_REMOTE_BOMBCHU_NAME });

View File

@@ -846,6 +846,12 @@ void SohMenu::AddMenuEnhancements() {
path.column = SECTION_COLUMN_2;
AddWidget(path, "Explosives", WIDGET_SEPARATOR_TEXT);
AddWidget(path, "Remote Bombchu", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_ENHANCEMENT("RemoteBombchu"))
.Options(CheckboxOptions().Tooltip("Allows you to control a Bombchu after dropping it.\n"
"Control Stick: Steer\n"
"B: Detonate\n"
"A: Quit Control"));
AddWidget(path, "Deku Nuts Explode Bombs", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_ENHANCEMENT("NutsExplodeBombs"))
.Options(CheckboxOptions().Tooltip("Make Deku Nuts explode Bombs, similar to how they interact with Bombchus. "