Disable Fixed Camera Enhancement (#6083)

Co-authored-by: PurpleHato <linkvssangoku.jr@gmail.com>
This commit is contained in:
Jordyn Hardyman
2026-01-03 13:07:19 -05:00
committed by GitHub
parent 6c724a2c01
commit 9af262c1bb
6 changed files with 253 additions and 1 deletions

View File

@@ -0,0 +1,229 @@
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/ShipInit.hpp"
#include <set>
#include <unordered_map>
extern "C" {
#include "functions.h"
#include "variables.h"
#include "z64bgcheck.h"
}
#define CVAR_DISABLE_FIXED_CAMERA_NAME CVAR_ENHANCEMENT("DisableFixedCamera")
#define CVAR_DISABLE_FIXED_CAMERA_VALUE CVarGetInteger(CVAR_DISABLE_FIXED_CAMERA_NAME, 0)
static const std::set<SceneID> fixedCameraSceneList = {
SCENE_MARKET_ENTRANCE_DAY,
SCENE_MARKET_ENTRANCE_NIGHT,
SCENE_MARKET_ENTRANCE_RUINS,
SCENE_BACK_ALLEY_DAY,
SCENE_BACK_ALLEY_NIGHT,
SCENE_MARKET_DAY,
SCENE_MARKET_NIGHT,
SCENE_MARKET_RUINS,
SCENE_CASTLE_COURTYARD_ZELDA,
SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY,
SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT,
SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS,
SCENE_FOREST_TEMPLE,
SCENE_KNOW_IT_ALL_BROS_HOUSE,
SCENE_TWINS_HOUSE,
SCENE_MIDOS_HOUSE,
SCENE_SARIAS_HOUSE,
SCENE_BACK_ALLEY_HOUSE,
SCENE_POTION_SHOP_GRANNY,
SCENE_SHOOTING_GALLERY,
SCENE_LINKS_HOUSE,
SCENE_DOG_LADY_HOUSE,
SCENE_STABLE,
SCENE_IMPAS_HOUSE,
SCENE_KAKARIKO_CENTER_GUEST_HOUSE,
SCENE_CARPENTERS_TENT,
SCENE_GRAVEKEEPERS_HUT,
};
static int16_t sSetNormalCam = -1;
static bool sIsCamApplied = false;
static int sCheckItemCamState = -1;
static s16 sStoreLastCamType = -1;
extern "C" void DisableFixedCamera_CheckCameraState(PlayState* play);
struct CamDataBackup {
CamData* original = nullptr;
CamData* copy = nullptr;
size_t len = 0;
bool active = false;
};
static std::unordered_map<CollisionHeader*, CamDataBackup> sCamDataBackups;
static void DisableFixedCamera_ResetState() {
sSetNormalCam = -1;
sIsCamApplied = false;
sCheckItemCamState = -1;
sStoreLastCamType = -1;
}
static void DisableFixedCamera_RestoreAllCameraData() {
for (auto& [colHeader, backup] : sCamDataBackups) {
if (colHeader != nullptr && backup.active) {
colHeader->cameraDataList = backup.original;
backup.active = false;
}
delete[] backup.copy;
backup.copy = nullptr;
backup.original = nullptr;
backup.len = 0;
}
sCamDataBackups.clear();
}
// Helper to check if a camera type is a fixed camera
static bool IsFixedCameraType(s16 type) {
return type == CAM_SET_PREREND_FIXED || type == CAM_SET_PREREND_PIVOT || type == CAM_SET_PIVOT_FROM_SIDE;
}
static void RegisterDisableFixedCamera() {
const bool disableFixedCamEnabled = CVAR_DISABLE_FIXED_CAMERA_VALUE != 0;
COND_HOOK(OnCameraState, disableFixedCamEnabled,
[](PlayState* play) { DisableFixedCamera_CheckCameraState(play); });
if (!disableFixedCamEnabled) {
DisableFixedCamera_RestoreAllCameraData();
DisableFixedCamera_ResetState();
}
}
static RegisterShipInitFunc initFunc(RegisterDisableFixedCamera, { CVAR_DISABLE_FIXED_CAMERA_NAME });
static void DisableFixedCamera_RestoreCameraData(CollisionHeader* colHeader) {
if (colHeader == nullptr) {
return;
}
auto it = sCamDataBackups.find(colHeader);
if (it == sCamDataBackups.end() || !it->second.active) {
return;
}
colHeader->cameraDataList = it->second.original;
it->second.active = false;
}
extern "C" void DisableFixedCamera_SetNormalCamera(PlayState* play) {
CollisionHeader* colHeader = BgCheck_GetCollisionHeader(&play->colCtx, BGCHECK_SCENE);
if (colHeader != nullptr && colHeader->cameraDataList != nullptr && colHeader->cameraDataListLen > 0) {
CamDataBackup& backup = sCamDataBackups[colHeader];
if (backup.copy == nullptr) {
backup.original = colHeader->cameraDataList;
backup.len = colHeader->cameraDataListLen;
backup.copy = new CamData[backup.len];
memcpy(backup.copy, backup.original, sizeof(CamData) * backup.len);
}
if (colHeader->cameraDataList != backup.copy) {
colHeader->cameraDataList = backup.copy;
}
backup.active = true;
for (size_t i = 0; i < colHeader->cameraDataListLen; i++) {
if (IsFixedCameraType(colHeader->cameraDataList[i].cameraSType)) {
colHeader->cameraDataList[i].cameraSType = CAM_SET_NORMAL0;
}
}
}
play->unk_1242B = 0;
if (IsFixedCameraType(play->mainCamera.setting)) {
play->mainCamera.setting = CAM_SET_NORMAL0;
play->mainCamera.prevSetting = CAM_SET_NORMAL0;
}
Camera_ChangeSetting(&play->mainCamera, CAM_SET_NORMAL0);
Camera_ChangeMode(&play->mainCamera, CAM_MODE_NORMAL);
}
extern "C" void DisableFixedCamera_CheckCameraState(PlayState* play) {
const bool disableFixedCamEnabled = CVarGetInteger(CVAR_DISABLE_FIXED_CAMERA_NAME, 0) != 0;
if (!disableFixedCamEnabled) {
CollisionHeader* colHeader = BgCheck_GetCollisionHeader(&play->colCtx, BGCHECK_SCENE);
DisableFixedCamera_RestoreCameraData(colHeader);
return;
}
// prevents normal cam from taking effect during open cutscene to avoid crash
if (play->sceneNum == SCENE_LINKS_HOUSE && gSaveContext.cutsceneIndex == 0xFFF1) {
return;
}
// Only compute player state if we're in a relevant scene
const bool isInFixedCameraScene = fixedCameraSceneList.contains(static_cast<SceneID>(play->sceneNum));
if (!isInFixedCameraScene) {
bool sceneChanged = play->sceneNum != sSetNormalCam;
if (sceneChanged) {
sSetNormalCam = play->sceneNum;
sIsCamApplied = false;
sStoreLastCamType = -1;
// Clean up backups when leaving fixed camera scenes
for (auto& [key, backup] : sCamDataBackups) {
delete[] backup.copy;
}
sCamDataBackups.clear();
}
DisableFixedCamera_RestoreCameraData(BgCheck_GetCollisionHeader(&play->colCtx, BGCHECK_SCENE));
return;
}
bool sceneChanged = play->sceneNum != sSetNormalCam;
bool itemCamChanged = false;
if (sceneChanged) {
sSetNormalCam = play->sceneNum;
sIsCamApplied = false;
sStoreLastCamType = -1;
}
Player* player = (Player*)play->actorCtx.actorLists[ACTORCAT_PLAYER].head;
bool ocarinaPulling = player && (player->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING);
bool bottleUsing = false;
if (player) {
bool inItemCs = (player->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS) != 0;
bool isBottleAction =
(player->itemAction >= PLAYER_IA_BOTTLE) && (player->itemAction <= PLAYER_IA_BOTTLE_FAIRY);
bottleUsing = inItemCs && isBottleAction;
}
bool itemCamActive = ocarinaPulling || bottleUsing;
if (sCheckItemCamState == -1) {
sCheckItemCamState = itemCamActive;
} else if (sCheckItemCamState != static_cast<int>(itemCamActive)) {
sCheckItemCamState = itemCamActive;
itemCamChanged = true;
}
if (!sceneChanged && !itemCamChanged) {
return;
}
// sets cam when ocarina or bottle is used and sets it back to normal when done
if (itemCamChanged && itemCamActive) {
if (play->mainCamera.camDataIdx >= 0) {
sStoreLastCamType = play->mainCamera.camDataIdx;
}
Camera_ChangeSetting(&play->mainCamera, CAM_SET_TURN_AROUND);
Camera_ChangeMode(&play->mainCamera, CAM_MODE_NORMAL);
if (sStoreLastCamType >= 0) {
play->mainCamera.camDataIdx = sStoreLastCamType;
}
return;
}
if (itemCamChanged && !itemCamActive) {
DisableFixedCamera_SetNormalCamera(play);
if (sStoreLastCamType >= 0) {
play->mainCamera.camDataIdx = sStoreLastCamType;
}
return;
}
if (!sIsCamApplied) {
DisableFixedCamera_SetNormalCamera(play);
sIsCamApplied = true;
}
}

View File

@@ -11,6 +11,7 @@ DEFINE_HOOK(OnLoadGame, (int32_t fileNum));
DEFINE_HOOK(OnExitGame, (int32_t fileNum));
DEFINE_HOOK(OnGameStateMainStart, ());
DEFINE_HOOK(OnGameFrameUpdate, ());
DEFINE_HOOK(OnCameraState, (PlayState * play));
DEFINE_HOOK(OnItemReceive, (GetItemEntry itemEntry));
DEFINE_HOOK(OnEquipmentDelete, (int16_t equipmentType, uint16_t equipValue));
DEFINE_HOOK(OnSaleEnd, (GetItemEntry itemEntry));

View File

@@ -29,6 +29,10 @@ void GameInteractor_ExecuteOnGameFrameUpdate() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnGameFrameUpdate>();
}
void GameInteractor_ExecuteOnCameraState(PlayState* play) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnCameraState>(play);
}
void GameInteractor_ExecuteOnItemReceiveHooks(GetItemEntry itemEntry) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnItemReceive>(itemEntry);
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnItemReceive>(itemEntry);

View File

@@ -14,6 +14,7 @@ void GameInteractor_ExecuteOnLoadGame(int32_t fileNum);
void GameInteractor_ExecuteOnExitGame(int32_t fileNum);
void GameInteractor_ExecuteOnGameStateMainStart();
void GameInteractor_ExecuteOnGameFrameUpdate();
void GameInteractor_ExecuteOnCameraState(PlayState* play);
void GameInteractor_ExecuteOnItemReceiveHooks(GetItemEntry itemEntry);
void GameInteractor_ExecuteOnEquipmentDelete(int16_t equipmentType, uint16_t equipValue);
void GameInteractor_ExecuteOnSaleEndHooks(GetItemEntry itemEntry);

View File

@@ -560,6 +560,22 @@ void SohMenu::AddMenuEnhancements() {
.Options(CheckboxOptions().Tooltip("Disables 2D pre-rendered backgrounds. Enable this when using a mod that "
"implements 3D backdrops for these areas.\n"
"Requires Scene Change to alter."));
AddWidget(path, "Disable Fixed Camera", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_ENHANCEMENT("DisableFixedCamera"))
.RaceDisable(false)
.PreFunc([](WidgetInfo& info) {
if (CVarGetInteger(CVAR_ENHANCEMENT("3DSceneRender"), 0) == 0) {
CVarSetInteger(CVAR_ENHANCEMENT("DisableFixedCamera"), 0);
info.options->disabled = true;
} else {
info.options->disabled = false;
}
info.options->disabledTooltip = "Requires \"Disable 2D Pre-Rendered Scenes\" to be enabled.";
})
.Options(CheckboxOptions().Tooltip(
"Disables the fixed camera in maps that use 2D pre-rendered backgrounds. Enable this when using a mod "
"that implements 3D backdrops for these areas.\n"
"Requires Scene Change to alter."));
AddWidget(path, "Ingame Text Spacing: %d", WIDGET_CVAR_SLIDER_INT)
.CVar(CVAR_ENHANCEMENT("TextSpacing"))
.RaceDisable(false)

View File

@@ -631,7 +631,6 @@ void Play_Init(GameState* thisx) {
} else {
play->unk_1242B = 0;
}
Interface_SetSceneRestrictions(play);
Environment_PlaySceneSequence(play);
gSaveContext.seqId = play->sequenceCtx.seqId;
@@ -1303,6 +1302,8 @@ void Play_Update(PlayState* play) {
skip:
PLAY_LOG(3801);
GameInteractor_ExecuteOnCameraState(play);
if (!isPaused || gDbgCamEnabled) {
s32 i;