mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-08-27 00:44:04 -04:00
207 lines
6.5 KiB
C++
207 lines
6.5 KiB
C++
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
|
#include "soh/ShipInit.hpp"
|
|
|
|
extern "C" {
|
|
#include "functions.h"
|
|
#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, CAM_ID_MAIN, 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, CAM_ID_MAIN);
|
|
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, CAM_ID_MAIN, 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 = static_cast<f32>(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 });
|