Implemented free camera (WIP)

Patch "Enable Free Camera" id "EnableFreeCamera" in "Debug" by "Hyper" does
/*
Enables the replay mode camera with controls similar to Generations Free Camera.

Controls:
- Start - toggle pause (use after enabling free camera).
- Back (Select) - toggle free camera.
- Left Stick - move camera.
- Left Stick Button (L3) - teleport player to camera.
- Right Stick - rotate camera.
- Right Stick Button (R3) - teleport camera to player.
- Left Bumper (L1) - move camera down.
- Right Bumper (R1) - move camera up.
- Left Trigger (L2) - move camera at slow speed.
- Right Trigger (R2) - move camera at fast speed.
- Left Trigger (L2) + Right Trigger (R2) - move camera at moderate speed.
- A (Cross) - reset camera speed.
- B (Circle) - decrease base camera speed.
- X (Square) - increase base camera speed.
- Y (Triangle) - reset field of view.
- D-Pad Up - increase field of view.
- D-Pad Down - decrease field of view.
- D-Pad Left - toggle camera position lock.
- D-Pad Right - toggle depth of field.
*/
This commit is contained in:
Hyper 2025-02-16 06:20:48 +00:00
parent df8234ab3b
commit 0aa1a256dd
11 changed files with 306 additions and 6 deletions

View file

@ -137,6 +137,7 @@ set(UNLEASHED_RECOMP_PATCHES_CXX_SOURCES
"patches/CTitleStateIntro_patches.cpp" "patches/CTitleStateIntro_patches.cpp"
"patches/CTitleStateMenu_patches.cpp" "patches/CTitleStateMenu_patches.cpp"
"patches/fps_patches.cpp" "patches/fps_patches.cpp"
"patches/free_camera_patches.cpp"
"patches/frontend_listener.cpp" "patches/frontend_listener.cpp"
"patches/input_patches.cpp" "patches/input_patches.cpp"
"patches/inspire_patches.cpp" "patches/inspire_patches.cpp"

View file

@ -94,6 +94,7 @@
#include "SWA/Player/Character/EvilSonic/EvilSonic.h" #include "SWA/Player/Character/EvilSonic/EvilSonic.h"
#include "SWA/Player/Character/EvilSonic/EvilSonicContext.h" #include "SWA/Player/Character/EvilSonic/EvilSonicContext.h"
#include "SWA/Player/Character/EvilSonic/Hud/EvilHudGuide.h" #include "SWA/Player/Character/EvilSonic/Hud/EvilHudGuide.h"
#include "SWA/Replay/Camera/ReplayFreeCamera.h"
#include "SWA/Sequence/Unit/SequenceUnitBase.h" #include "SWA/Sequence/Unit/SequenceUnitBase.h"
#include "SWA/Sequence/Unit/SequenceUnitPlayMovie.h" #include "SWA/Sequence/Unit/SequenceUnitPlayMovie.h"
#include "SWA/Sequence/Utility/SequencePlayMovieWrapper.h" #include "SWA/Sequence/Utility/SequencePlayMovieWrapper.h"

View file

@ -4,10 +4,10 @@
namespace SWA namespace SWA
{ {
class CCameraController : public Hedgehog::Universe::CStateMachineBase::CStateBase class CCameraController
{ {
public: public:
SWA_INSERT_PADDING(0x04); SWA_INSERT_PADDING(0x64);
be<float> m_FieldOfView; be<float> m_FieldOfView;
SWA_INSERT_PADDING(0x68); SWA_INSERT_PADDING(0x68);
}; };

View file

@ -33,6 +33,9 @@ namespace SWA
// ms_IsRenderDebugPositionDraw: デバッグ位置描画 // ms_IsRenderDebugPositionDraw: デバッグ位置描画
static inline bool* ms_IsRenderDebugPositionDraw; static inline bool* ms_IsRenderDebugPositionDraw;
// N/A
static inline bool* ms_IsRenderDepthOfField;
// ms_IsRenderGameMainHud: ゲームメインHUD 描画 // ms_IsRenderGameMainHud: ゲームメインHUD 描画
static inline bool* ms_IsRenderGameMainHud; static inline bool* ms_IsRenderGameMainHud;
@ -65,6 +68,7 @@ namespace SWA
ms_IsRenderDebugDraw = (bool*)MmGetHostAddress(0x8328BB23); ms_IsRenderDebugDraw = (bool*)MmGetHostAddress(0x8328BB23);
ms_IsRenderDebugDrawText = (bool*)MmGetHostAddress(0x8328BB25); ms_IsRenderDebugDrawText = (bool*)MmGetHostAddress(0x8328BB25);
ms_IsRenderDebugPositionDraw = (bool*)MmGetHostAddress(0x8328BB24); ms_IsRenderDebugPositionDraw = (bool*)MmGetHostAddress(0x8328BB24);
ms_IsRenderDepthOfField = (bool*)MmGetHostAddress(0x83302720);
ms_IsRenderGameMainHud = (bool*)MmGetHostAddress(0x8328BB27); ms_IsRenderGameMainHud = (bool*)MmGetHostAddress(0x8328BB27);
ms_IsRenderHud = (bool*)MmGetHostAddress(0x8328BB26); ms_IsRenderHud = (bool*)MmGetHostAddress(0x8328BB26);
ms_IsRenderHudPause = (bool*)MmGetHostAddress(0x8328BB28); ms_IsRenderHudPause = (bool*)MmGetHostAddress(0x8328BB28);

View file

@ -0,0 +1,16 @@
#pragma once
#include <SWA.inl>
#include "SWA/Camera/CameraController.h"
namespace SWA
{
class CReplayFreeCamera : public CCameraController
{
public:
SWA_INSERT_PADDING(0x90);
be<float> m_Speed;
};
SWA_ASSERT_OFFSETOF(CReplayFreeCamera, m_Speed, 0x160);
}

View file

@ -1,5 +1,6 @@
#include <kernel/function.h> #include <kernel/function.h>
#include <api/SWA.h> #include <api/SWA.h>
#include <patches/free_camera_patches.h>
#include <ui/achievement_menu.h> #include <ui/achievement_menu.h>
#include <ui/button_guide.h> #include <ui/button_guide.h>
#include <ui/options_menu.h> #include <ui/options_menu.h>
@ -119,6 +120,9 @@ bool CHudPauseMiscInjectOptionsMidAsmHook(PPCRegister& pThis)
PPC_FUNC_IMPL(__imp__sub_824B0930); PPC_FUNC_IMPL(__imp__sub_824B0930);
PPC_FUNC(sub_824B0930) PPC_FUNC(sub_824B0930)
{ {
if (FreeCameraPatches::s_isActive)
return;
if (App::s_isLoading) if (App::s_isLoading)
{ {
__imp__sub_824B0930(ctx, base); __imp__sub_824B0930(ctx, base);

View file

@ -1,9 +1,10 @@
#include "camera_patches.h"
#include <api/SWA.h> #include <api/SWA.h>
#include <gpu/video.h>
#include <patches/aspect_ratio_patches.h>
#include <patches/free_camera_patches.h>
#include <ui/game_window.h> #include <ui/game_window.h>
#include <user/config.h> #include <user/config.h>
#include <gpu/video.h>
#include "camera_patches.h"
#include "aspect_ratio_patches.h"
void CameraAspectRatioMidAsmHook(PPCRegister& r30, PPCRegister& r31) void CameraAspectRatioMidAsmHook(PPCRegister& r30, PPCRegister& r31)
{ {
@ -35,7 +36,9 @@ void CameraFieldOfViewMidAsmHook(PPCRegister& r31, PPCRegister& f31)
{ {
auto camera = (SWA::CCamera*)g_memory.Translate(r31.u32); auto camera = (SWA::CCamera*)g_memory.Translate(r31.u32);
f31.f64 = AdjustFieldOfView(f31.f64, camera->m_HorzAspectRatio); f31.f64 = FreeCameraPatches::s_isActive
? FreeCameraPatches::s_fieldOfView
: AdjustFieldOfView(f31.f64, camera->m_HorzAspectRatio);
} }
PPC_FUNC_IMPL(__imp__sub_824697B0); PPC_FUNC_IMPL(__imp__sub_824697B0);

View file

@ -0,0 +1,222 @@
#include "free_camera_patches.h"
#include <api/SWA.h>
#include <os/logger.h>
#include <ui/game_window.h>
#include <user/config.h>
#include <app.h>
#define DEGREES_TO_RADIANS(x) (float)(x / 180.0f * M_PI)
#define RADIANS_TO_DEGREES(x) (float)(x / M_PI * 180.0f)
constexpr float DEFAULT_SPEED = 0.5f;
constexpr float DEFAULT_FOV = 45.0f;
constexpr float MOVE_SPEED_SLOW = 0.075f;
constexpr float MOVE_SPEED_FAST = 8.0f;
constexpr float MOVE_SPEED_MODIFIER_RATIO = 0.02f;
constexpr float FOV_MODIFIER_RATIO = 1.0f;
static float g_baseSpeed = DEFAULT_SPEED;
static float g_baseFOV = DEFAULT_FOV;
static bool g_isCameraLocked;
static float g_speed;
static float g_fov;
static void ResetParameters()
{
g_isCameraLocked = false;
g_speed = g_baseSpeed = DEFAULT_SPEED;
*SWA::SGlobals::ms_IsRenderDepthOfField = true;
FreeCameraPatches::s_fieldOfView = g_fov = g_baseFOV = DEFAULT_FOV;
}
bool EnableFreeCameraMidAsmHook()
{
return Config::EnableFreeCamera;
}
bool FreeCameraNullInputMidAsmHook()
{
return Config::EnableFreeCamera;
}
// Original input: D-Pad Up
bool FreeCameraActivationInputMidAsmHook(PPCRegister& r11, PPCRegister& r27, PPCRegister& r28)
{
if (!Config::EnableFreeCamera)
return false;
static auto isChangedCameraMode = false;
if ((r11.u32 & SWA::eKeyState_Select) != 0)
{
if (++r28.u32 >= 2)
r28.u32 = 0;
isChangedCameraMode = true;
}
FreeCameraPatches::s_isActive = r28.u32 > 0;
if (isChangedCameraMode)
{
ResetParameters();
switch (r28.u32)
{
case 0:
LOGN("[Free Camera] Disabled");
break;
case 1:
LOGN("[Free Camera] Enabled");
break;
}
isChangedCameraMode = false;
if (FreeCameraPatches::s_isActive && *SWA::SGlobals::ms_IsRenderHud)
{
*SWA::SGlobals::ms_IsRenderHud = false;
}
else
{
*SWA::SGlobals::ms_IsRenderHud = true;
}
}
return true;
}
// Original input: D-Pad Left
void FreeCameraTeleportToPlayerInputMidAsmHook(PPCRegister& r4)
{
if (Config::EnableFreeCamera)
r4.u32 = SWA::eKeyState_RightStick;
}
// Original inputs: X (Square) / Y (Triangle)
bool FreeCameraSpeedInputMidAsmHook(PPCRegister& r31)
{
if (!Config::EnableFreeCamera)
return false;
auto pCamera = (SWA::CReplayFreeCamera*)g_memory.Translate(r31.u32);
auto pInputState = SWA::CInputState::GetInstance();
if (!pInputState)
return false;
auto& rPadState = pInputState->GetPadState();
auto factor = App::s_deltaTime / (1.0f / 60.0f);
auto aspectRatio = (float)GameWindow::s_width / (float)GameWindow::s_height;
if (g_isCameraLocked)
{
g_speed = 0.0f;
}
else
{
static auto isLeftTriggerSpeedModifier = false;
static auto isRightTriggerSpeedModifier = false;
if (rPadState.IsDown(SWA::eKeyState_LeftTrigger))
{
g_speed = MOVE_SPEED_SLOW;
isLeftTriggerSpeedModifier = true;
}
else if (isLeftTriggerSpeedModifier)
{
g_speed = g_baseSpeed;
isLeftTriggerSpeedModifier = false;
}
if (rPadState.IsDown(SWA::eKeyState_RightTrigger))
{
g_speed = MOVE_SPEED_FAST;
isRightTriggerSpeedModifier = true;
}
else if (isRightTriggerSpeedModifier)
{
g_speed = g_baseSpeed;
isRightTriggerSpeedModifier = false;
}
if (isLeftTriggerSpeedModifier && isRightTriggerSpeedModifier)
g_speed = MOVE_SPEED_FAST / 3;
if (rPadState.IsDown(SWA::eKeyState_A))
g_speed = g_baseSpeed = DEFAULT_SPEED;
if (rPadState.IsDown(SWA::eKeyState_B))
{
g_baseSpeed -= MOVE_SPEED_MODIFIER_RATIO * factor;
g_speed = g_baseSpeed;
LOGFN("[Free Camera] Speed: {}", g_speed);
}
if (rPadState.IsDown(SWA::eKeyState_X))
{
g_baseSpeed += MOVE_SPEED_MODIFIER_RATIO * factor;
g_speed = g_baseSpeed;
LOGFN("[Free Camera] Speed: {}", g_speed);
}
auto isResetFOV = rPadState.IsDown(SWA::eKeyState_Y);
auto isIncreaseFOV = rPadState.IsDown(SWA::eKeyState_DpadUp);
auto isDecreaseFOV = rPadState.IsDown(SWA::eKeyState_DpadDown);
auto fovScaleFactor = 0.0f;
if (isIncreaseFOV)
{
fovScaleFactor = FOV_MODIFIER_RATIO;
}
else if (isDecreaseFOV)
{
fovScaleFactor = -FOV_MODIFIER_RATIO;
}
g_speed = std::clamp(g_speed, 0.01f, 20.0f);
g_fov = fmodf(isResetFOV ? DEFAULT_FOV : g_fov + fovScaleFactor * App::s_deltaTime * 60.0f, 180.0f);
}
if (rPadState.IsTapped(SWA::eKeyState_DpadLeft))
{
g_isCameraLocked = !g_isCameraLocked;
g_speed = g_baseSpeed;
if (g_isCameraLocked)
{
LOGN("[Free Camera] Locked");
}
else
{
LOGN("[Free Camera] Unlocked");
}
}
if (rPadState.IsTapped(SWA::eKeyState_DpadRight))
{
*SWA::SGlobals::ms_IsRenderDepthOfField = !*SWA::SGlobals::ms_IsRenderDepthOfField;
if (*SWA::SGlobals::ms_IsRenderDepthOfField)
{
LOGN("[Free Camera] Depth of Field ON");
}
else
{
LOGN("[Free Camera] Depth of Field OFF");
}
}
pCamera->m_Speed = g_speed;
FreeCameraPatches::s_fieldOfView = 2.0f * atan(tan(DEGREES_TO_RADIANS(g_fov / 2.0f) * (16.0f / 9.0f / std::min(aspectRatio, 16.0f / 9.0f))));
return true;
}

View file

@ -0,0 +1,9 @@
#pragma once
class FreeCameraPatches
{
public:
static inline bool s_isActive;
static inline float s_fieldOfView;
};

View file

@ -80,6 +80,7 @@ CONFIG_DEFINE_HIDDEN("Codes", bool, DisableAutoSaveWarning, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, DisableDLCIcon, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableDLCIcon, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, DisableDWMRoundedCorners, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableDWMRoundedCorners, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, EnableEventCollisionDebugView, false); CONFIG_DEFINE_HIDDEN("Codes", bool, EnableEventCollisionDebugView, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, EnableFreeCamera, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, EnableGIMipLevelDebugView, false); CONFIG_DEFINE_HIDDEN("Codes", bool, EnableGIMipLevelDebugView, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, EnableObjectCollisionDebugView, false); CONFIG_DEFINE_HIDDEN("Codes", bool, EnableObjectCollisionDebugView, false);
CONFIG_DEFINE_HIDDEN("Codes", bool, EnableStageCollisionDebugView, false); CONFIG_DEFINE_HIDDEN("Codes", bool, EnableStageCollisionDebugView, false);

View file

@ -1086,3 +1086,42 @@ registers = ["r4"]
name = "AnimationDataMakeMidAsmHook" name = "AnimationDataMakeMidAsmHook"
address = 0x82BB38E4 address = 0x82BB38E4
registers = ["r31", "r29", "r28"] registers = ["r31", "r29", "r28"]
[[midasm_hook]]
name = "EnableFreeCameraMidAsmHook"
address = 0x825389F0
jump_address_on_true = 0x825389F4
[[midasm_hook]]
name = "EnableFreeCameraMidAsmHook"
address = 0x82538A18
jump_address_on_true = 0x82538A1C
[[midasm_hook]]
name = "FreeCameraActivationInputMidAsmHook"
address = 0x824569BC
registers = ["r11", "r27", "r28"]
jump_address_on_true = 0x824569D4
[[midasm_hook]]
name = "FreeCameraTeleportToPlayerInputMidAsmHook"
address = 0x8245C21C
registers = ["r4"]
[[midasm_hook]]
name = "FreeCameraSpeedInputMidAsmHook"
address = 0x8245C318
registers = ["r31"]
jump_address_on_true = 0x8245C38C
# Disable "change to free camera" input.
[[midasm_hook]]
name = "FreeCameraNullInputMidAsmHook"
address = 0x8245BCE4
jump_address_on_true = 0x8245BDB4
# Disable "change to pan camera" input.
[[midasm_hook]]
name = "FreeCameraNullInputMidAsmHook"
address = 0x8245BDC4
jump_address_on_true = 0x8245BEAC