using ImGuiNET; using System.Numerics; using SharpPluginLoader.Core; using SharpPluginLoader.Core.Entities; using SharpPluginLoader.Core.IO; using SharpPluginLoader.Core.View; using SharpPluginLoader.Core.Memory; using SharpPluginLoader.Core.MtTypes; using SharpPluginLoader.Core.Actions; namespace MHWNewCamera { public unsafe class Plugin : IPlugin { public string Name => "MHW New Camera"; public string Author => "pizza"; private float cameraFov = 60.0f; private float cameraForward = 0.0f; private float cameraLeft = 0.0f; private float cameraXOffset = 0.0f; private float cameraYOffset = 0.0f; private float cameraZOffset = 0.0f; private float cameraKeySpeed = 0.08f; private float cameraSpeed = 0.55f; private float cameraSensitivity = 0.112f; private int stickDeadzone = 4500; private float cameraPitch = 0.0f; private float cameraYaw = 0.0f; private float cameraRoll = 0.0f; public struct Binds { public Key ToggleFreeCamera; public Key TogglePerspectiveCamera; public Key IncreaseSpeed; public Key DecreaseSpeed; public Key IncreaseFOV; public Key DecreaseFOV; public Key Forward; public Key Backward; public Key Up; public Key Down; public Key Left; public Key Right; }; private Binds binds; private bool freeCamera = false; private bool enableFreeCamera = false; private bool movementFreezeHeld = false; private bool movementFreezeToggled = false; private bool offsetPerspective = false; private bool enableOffsetPerspective = false; private Camera? targetCamera = null; private float previousFov = -1.0f; private bool decreasingFov = false; private bool changeAfterOpenMap = true; private bool updateFovInView = false; private delegate void CalculateCameraDelegate(nint unknownPtr); private Hook? calculateCameraHook; private delegate void CalculateViewDelegate(nint unknownPtr); private Hook? calculateViewHook; private delegate float CalculateMovementDelegate(int stickValue); private Hook? calculateMovementHook; private Patch disableXCollision; private Patch disableYCollision; private Patch disableSecondaryYCollision; private Patch disableZCollision; private Patch disableExtraYCollision; private bool disableCollision = false; private bool disableExtraCollision = false; private static readonly MtObject sMhSteamController = SingletonManager.GetSingleton("sMhSteamController")!; public void OnLoad() { binds.ToggleFreeCamera = Key.NumPad0; binds.TogglePerspectiveCamera = Key.NumPadPeriod; binds.IncreaseSpeed = Key.NumPadPlus; binds.DecreaseSpeed = Key.NumPadMinus; binds.IncreaseFOV = Key.PageUp; binds.DecreaseFOV = Key.PageDown; binds.Forward = Key.Up; binds.Backward = Key.Down; binds.Left = Key.Left; binds.Right = Key.Right; // + Control (left or right). binds.Up = Key.Up; binds.Down = Key.Down; // Hook where we can adjust the camera position. calculateCameraHook = Hook.Create(0x141fa5380, CalculateCameraHook); //calculateCameraHook = Hook.Create(0x142290620, CalculateCameraHook); // Hook where we can directly modify the view matrix. calculateViewHook = Hook.Create(0x14228eb60, CalculateViewHook); //calculateViewHook2 = Hook.Create(0x14228fb80, CalculateViewHook2); // Hook where we can adjust the analog stick value the game uses. calculateMovementHook = Hook.Create(0x142107cb0, CalculateMovementHook); unchecked { // Noop collision checks. disableXCollision = new Patch((nint)0x141c001b5, [0x90, 0x90, 0x90, 0x90]); disableYCollision = new Patch((nint)0x141c001c3, [0x90, 0x90, 0x90, 0x90, 0x90]); disableSecondaryYCollision = new Patch((nint)0x141c000d2, [0x90, 0x90, 0x90, 0x90, 0x90]); disableZCollision = new Patch((nint)0x141c001d2, [0x90, 0x90, 0x90, 0x90, 0x90]); disableExtraYCollision = new Patch((nint)0x141bfff90, [0x90, 0x90, 0x90, 0x90, 0x90]); } } private static bool assumeInQuestBoard(float fov) { // 20.x = quest board return fov == 20.000038f || fov == 20.017189f || fov == 20.00004f; } private bool assumeInAnimation(Viewport vp, Camera camera) { return vp.Camera != null && vp.Camera.Instance != camera.Instance && vp.Camera.FarClip == 4000000 && vp.Camera.Fix; } private bool assumeInOpenMapAnimation(float fov) { // Assume the camera's fov could end up at 50.942066 during normal // gameplay. Try to mitigate it by tracking the triggers for opening // the map (press and quickly release select, press A at departure location). // This is a really rough hack but better than nothing for now. if (fov == 50.942066f) { if (!changeAfterOpenMap) return true; if (Input.IsReleased(Button.Share) || Input.IsPressed(Button.Cross)) { changeAfterOpenMap = false; return true; } } if (!changeAfterOpenMap) { changeAfterOpenMap = true; } return false; } private void setPerspective(Camera camera, Vector3 pos, Quaternion forward) { Quaternion left = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); pos.X += forward.X * cameraForward; pos.Y += forward.Y * cameraForward; pos.Z += forward.Z * cameraForward; pos.X -= left.X * cameraLeft; pos.Z -= left.Z * cameraLeft; pos.X += cameraXOffset; pos.Y += cameraYOffset; pos.Z += cameraZOffset; camera.Position = pos; camera.FieldOfView = cameraFov; } private void adjustForCameraRoll(Viewport vp, Camera camera) { camera.AspectRatio = vp.Region.Height / (float)vp.Region.Width; // Increase FOV for culling then set it back in CalculateViewHook. camera.FieldOfView *= 2.0f; updateFovInView = true; } private void CalculateCameraHook(nint unknownPtr) { calculateCameraHook!.Original(unknownPtr); Camera? camera = targetCamera; Viewport vp = CameraSystem.MainViewport; if (camera == null) return; Vector3 pos = camera.Position; Vector3 target = camera.Target; Quaternion forward = Quaternion.Normalize(new Quaternion(target.X - pos.X, target.Y - pos.Y, target.Z - pos.Z, 0.0f)); decreasingFov = previousFov >= 0.0f && previousFov > camera.FieldOfView; previousFov = camera.FieldOfView; if ((offsetPerspective && !freeCamera) && camera.Fix && camera.Move && !assumeInQuestBoard(camera.FieldOfView) && !(assumeInAnimation(vp, camera) && decreasingFov) && !assumeInOpenMapAnimation(camera.FieldOfView)) { setPerspective(camera, pos, forward); if (cameraRoll != 0.0f) adjustForCameraRoll(vp, camera); } if (enableFreeCamera && !freeCamera) { freeCamera = true; camera.Move = false; pos = camera.Position; forward = Quaternion.Normalize(new Quaternion(target.X - pos.X, target.Y - pos.Y, target.Z - pos.Z, 0.0f)); cameraPitch = Single.RadiansToDegrees((float)Math.Asin(forward.Y)); cameraYaw = Single.RadiansToDegrees((float)Math.Atan2(forward.Z, forward.X)); } } private void CalculateViewHook(nint unknownPtr) { Viewport vp = CameraSystem.MainViewport; bool updateView = freeCamera || offsetPerspective; if (updateFovInView) { if (updateView) { Camera? camera; if ((camera = freeCamera ? vp.Camera : targetCamera) != null) { camera.FieldOfView = cameraFov; } } updateFovInView = false; } calculateViewHook!.Original(unknownPtr); if (updateView) { vp.ViewMatrix *= Matrix4x4.CreateRotationZ(Single.DegreesToRadians(cameraRoll)); } } private float CalculateMovementHook(int stickValue) { if (freeCamera && !(movementFreezeHeld || movementFreezeToggled)) stickValue = 0; return calculateMovementHook!.Original(stickValue); } private static void drawVector(Vector3 v, String name) { ImGui.Text($" {name}:"); ImGui.Text($" X: {v.X:0.00}"); ImGui.Text($" Y: {v.Y:0.00}"); ImGui.Text($" Z: {v.Z:0.00}"); } /* private static void drawQuaternion(MtQuaternion q, String name) { ImGui.Text($" {name}:"); ImGui.Text($" X: {q.X:0.00}"); ImGui.Text($" Y: {q.Y:0.00}"); ImGui.Text($" Z: {q.Z:0.00}"); ImGui.Text($" W: {q.W:0.00}"); } */ private static void inputVector(ref Vector3 v, String name, String key) { ImGui.PushID(name + key); ImGui.Text($" {name}:"); float x = v.X; float y = v.Y; float z = v.Z; if (ImGui.InputFloat($"X", ref x, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { v.X = x; } if (ImGui.InputFloat($"Y", ref y, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { v.Y = y; } if (ImGui.InputFloat($"Z", ref z, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { v.Z = z; } ImGui.PopID(); } private static void inputQuaternion(ref MtQuaternion q, String name, String key) { ImGui.PushID(name + key); ImGui.Text($" {name}:"); ImGui.SliderFloat($"X", ref q.X, -1.0f, 1.0f); ImGui.SliderFloat($"Y", ref q.Y, -1.0f, 1.0f); ImGui.SliderFloat($"Z", ref q.Z, -1.0f, 1.0f); ImGui.SliderFloat($"W", ref q.W, -1.0f, 1.0f); ImGui.PopID(); } public void OnImGuiRender() { ImGui.Checkbox("Enable Free Camera", ref enableFreeCamera); if (enableFreeCamera) { ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); } ImGui.Checkbox("Enable Perspective Camera", ref enableOffsetPerspective); if (enableFreeCamera) { ImGui.PopStyleVar(); } ImGui.PushID("Perspective"); if (ImGui.SliderFloat("Field of View", ref cameraFov, 1.0f, 180.0f)) { } ImGui.SliderFloat("Forward", ref cameraForward, -300.0f, 300.0f); ImGui.SliderFloat("Left", ref cameraLeft, -300.0f, 300.0f); ImGui.SliderFloat("Up", ref cameraYOffset, -300.0f, 300.0f); if (ImGui.Button("Reset")) { cameraFov = 60.0f; cameraForward = 0.0f; cameraLeft = 0.0f; cameraXOffset = 0.0f; cameraYOffset = 0.0f; cameraZOffset = 0.0f; } ImGui.PopID(); ImGui.PushID("Settings"); ImGui.SliderFloat("Camera Keybind Speed", ref cameraKeySpeed, 0.01f, 3.0f); if (ImGui.BeginItemTooltip()) { ImGui.Text("Camera Adjustment Keybinds"); ImGui.EndTooltip(); } ImGui.SliderFloat("Camera Speed", ref cameraSpeed, 0.1f, 10.0f); if (ImGui.BeginItemTooltip()) { ImGui.Text("Camera Movement in Free Camera"); ImGui.EndTooltip(); } ImGui.SliderFloat("Camera Sensitivity", ref cameraSensitivity, 0.005f, 1.00f); if (ImGui.BeginItemTooltip()) { ImGui.Text("Camera Sensitivity in Free Camera"); ImGui.EndTooltip(); } ImGui.InputInt("Stick Deadzone", ref stickDeadzone); if (ImGui.Button("Reset")) { cameraKeySpeed = 0.08f; cameraSpeed = 0.55f; cameraSensitivity = 0.112f; stickDeadzone = 4500; } ImGui.PopID(); if (ImGui.CollapsingHeader("DEBUG")) { ImGui.Text($"Camera/Viewport:"); Viewport vp; for (int i = 0; i < 1; i++) { vp = CameraSystem.GetViewport(i); ImGui.Text($" Pointer: {vp.Instance:x}"); ImGui.Text($" Visible: {vp.Visible}"); ImGui.Text($" Region: {vp.Region.X} {vp.Region.Y} {vp.Region.Width} {vp.Region.Height}"); if (vp.Camera != null) { ImGui.InputFloat("X Offset", ref cameraXOffset); ImGui.InputFloat("Y Offset", ref cameraYOffset); ImGui.InputFloat("Z Offset", ref cameraZOffset); ImGui.Text($" Camera Pointer: {vp.Camera.Instance:x}"); ImGui.InputFloat("FOV", ref vp.Camera.FieldOfView); ImGui.Text($" Internal FOV: {previousFov}"); ImGui.InputFloat("Aspect Ratio", ref vp.Camera.AspectRatio); ImGui.InputFloat("Far Clip", ref vp.Camera.FarClip); ImGui.InputFloat("Near Clip", ref vp.Camera.NearClip); inputVector(ref vp.Camera.Position, "Position", "cam"); inputVector(ref vp.Camera.Target, "Target", "cam"); inputVector(ref vp.Camera.Up, "Up", "cam"); ImGui.Text(" Rotation:"); float pitch = cameraPitch; if (ImGui.InputFloat("Pitch", ref pitch, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { cameraPitch = pitch; } float yaw = cameraYaw; if (ImGui.InputFloat("Yaw", ref yaw, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { cameraYaw = yaw; } float roll = cameraRoll; if (ImGui.InputFloat("Roll", ref roll, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { cameraRoll = roll; } ImGui.Text($" Move: {vp.Camera.Move}"); ImGui.Text($" Fix: {vp.Camera.Fix}"); } } ImGui.Text($"Pad:"); ImGui.Text($" Pointer: {sMhSteamController.Instance:x}"); int PadRx = MemoryUtil.Read(sMhSteamController.Instance + 0x1b0); int PadRy = MemoryUtil.Read(sMhSteamController.Instance + 0x1b4); int PadLx = MemoryUtil.Read(sMhSteamController.Instance + 0x1b8); int PadLy = MemoryUtil.Read(sMhSteamController.Instance + 0x1bc); byte PadRz = MemoryUtil.Read(sMhSteamController.Instance + 0x1c0); byte PadLz = MemoryUtil.Read(sMhSteamController.Instance + 0x1c1); ImGui.Text($" Left Stick: {PadLx} {PadLy} {PadLz}"); ImGui.Text($" Right Stick: {PadRx} {PadRy} {PadRz}"); ImGui.Separator(); ImGui.Text($"Player:"); Player? player = Player.MainPlayer; if (player != null) { ImGui.PushID("Player"); ImGui.Text($" Pointer: {player.Instance:x}"); inputVector(ref player.Position, "Position", "player"); if (ImGui.Checkbox("Disable Collision", ref disableCollision)) { if (disableCollision) { disableXCollision.Enable(); disableYCollision.Enable(); disableSecondaryYCollision.Enable(); disableZCollision.Enable(); } else { disableXCollision.Disable(); disableYCollision.Disable(); disableSecondaryYCollision.Disable(); disableZCollision.Disable(); } } if (ImGui.Checkbox("Disable Extra Y Collision", ref disableExtraCollision)) { if (disableExtraCollision) { disableExtraYCollision.Enable(); } else { disableExtraYCollision.Disable(); } } if (ImGui.BeginItemTooltip()) { ImGui.Text("This will make your legs goofy, but it's needed to get on top of some things."); ImGui.EndTooltip(); } inputQuaternion(ref player.Rotation, "Rotation", "player"); if (ImGui.Button("Reset")) { player.Rotation.X = 0.0f; player.Rotation.Z = 0.0f; } drawVector(player.Forward, "Forward"); ActionController actionController = player.ActionController; ImGui.Text($" Action: {actionController.CurrentAction} Animation: {player.CurrentAnimation} Frame: {player.AnimationLayer!.CurrentFrame:0.000}"); // https://github.com/HunterPie/HunterPie/blob/fa73f81ed0cdc921a6cf63f96c0fcff3688d88c2/HunterPie.Integrations/Datasources/MonsterHunterWorld/Entity/Game/MHWGame.cs#L74 bool playerInMenu = false; nint offset = MemoryUtil.Read(0x1451c4640); if (offset != 0) { offset += 0x13FD0; offset = MemoryUtil.Read(offset); if (offset != 0) { offset += 0xB734; playerInMenu = MemoryUtil.Read(offset) == 1; } } ImGui.Text($" In Menu: {playerInMenu}"); ImGui.Text($" Move: {player.Move}"); ImGui.Text($" Fix: {player.Fix}"); ImGui.PopID(); } } } public void OnUpdate(float deltaTime) { float adjustedKeySpeed = cameraKeySpeed * deltaTime * 1000.0f; float adjustedSpeed = cameraSpeed * deltaTime * 1000.0f; float adjustedSensitivity = cameraSensitivity * deltaTime * 1000.0f; if (Input.IsPressed(binds.ToggleFreeCamera)) { enableFreeCamera = !enableFreeCamera; } if (Input.IsPressed(binds.TogglePerspectiveCamera)) { enableOffsetPerspective = !enableOffsetPerspective; } if (Input.IsDown(binds.IncreaseSpeed)) { cameraSpeed += 0.5f * deltaTime; } if (Input.IsDown(binds.DecreaseSpeed)) { cameraSpeed -= 0.5f * deltaTime; if (cameraSpeed < 0.1f) cameraSpeed = 0.1f; } if (!(Input.IsDown(Key.RightControl) || Input.IsDown(Key.LeftControl))) { if (Input.IsDown(binds.IncreaseFOV)) { cameraFov += adjustedKeySpeed / 2.0f; } if (Input.IsDown(binds.DecreaseFOV)) { cameraFov -= adjustedKeySpeed / 2.0f; } if (Input.IsDown(binds.Forward)) { cameraForward += adjustedKeySpeed; } if (Input.IsDown(binds.Backward)) { cameraForward -= adjustedKeySpeed; } if (Input.IsDown(binds.Left)) { cameraLeft += adjustedKeySpeed; } if (Input.IsDown(binds.Right)) { cameraLeft -= adjustedKeySpeed; } } else { if (Input.IsDown(binds.Up)) { cameraYOffset += adjustedKeySpeed; } if (Input.IsDown(binds.Down)) { cameraYOffset -= adjustedKeySpeed; } } Viewport vp = CameraSystem.MainViewport; Camera? camera = vp.Camera; if (camera == null) return; Player? player = Player.MainPlayer; if (enableOffsetPerspective && !offsetPerspective) { offsetPerspective = true; targetCamera = camera; } else if (!enableOffsetPerspective && offsetPerspective) { offsetPerspective = false; } if (!offsetPerspective) { targetCamera = camera; } if (!enableFreeCamera && freeCamera) { freeCamera = false; camera.Move = true; /* if (player != null && (movementFreezeHeld || movementFreezeToggled)) { player.Move = true; } */ } if (freeCamera) { if (player != null) { if (!movementFreezeHeld && Input.IsPressed(Button.R1)) { //player.Move = !player.Move; //movementFreezeToggled = !player.Move; movementFreezeToggled = !movementFreezeToggled; } if (!movementFreezeToggled) { if (Input.IsPressed(Button.R2)) { //player.Move = false; movementFreezeHeld = true; } if (Input.IsReleased(Button.R2)) { //player.Move = true; movementFreezeHeld = false; } } } if (!(movementFreezeHeld || movementFreezeToggled)) { int PadLx = MemoryUtil.Read(sMhSteamController.Instance + 0x1b8); if (Math.Abs(PadLx) < stickDeadzone) PadLx = 0; int PadLy = MemoryUtil.Read(sMhSteamController.Instance + 0x1bc); if (Math.Abs(PadLy) < stickDeadzone) PadLy = 0; float Lx = (PadLx / (Int16.MaxValue / adjustedSpeed)); float Ly = (PadLy / (Int16.MaxValue / adjustedSpeed)); Quaternion forward = Quaternion.Normalize(new Quaternion( camera.Target.X - camera.Position.X, camera.Target.Y - camera.Position.Y, camera.Target.Z - camera.Position.Z, 0.0f)); if (Input.IsDown(Button.L2)) { if (Input.IsDown(Button.Down)) { camera.Position.Y -= adjustedKeySpeed; } if (Input.IsDown(Button.Up)) { camera.Position.Y += adjustedKeySpeed; } Lx /= 3.0f; Ly /= 3.0f; forward.Y = 0.0f; } forward = Quaternion.Normalize(forward); Quaternion left = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); camera.Position.X += left.X * Lx; camera.Position.Z += left.Z * Lx; camera.Position.X += forward.X * Ly; camera.Position.Y += forward.Y * Ly; camera.Position.Z += forward.Z * Ly; } int PadRx = MemoryUtil.Read(sMhSteamController.Instance + 0x1b0); if (Math.Abs(PadRx) < stickDeadzone) PadRx = 0; int PadRy = MemoryUtil.Read(sMhSteamController.Instance + 0x1b4); if (Math.Abs(PadRy) < stickDeadzone) PadRy = 0; float Rx = PadRx / (Int16.MaxValue / adjustedSensitivity); float Ry = PadRy / (Int16.MaxValue / adjustedSensitivity); cameraYaw += Rx; cameraPitch += Ry; float cap = 89.5f; if(cameraPitch > cap) cameraPitch = cap; if(cameraPitch < -cap) cameraPitch = -cap; camera.Target.X = camera.Position.X + ((float)Math.Cos(Single.DegreesToRadians(cameraPitch)) * (float)Math.Cos(Single.DegreesToRadians(cameraYaw))); camera.Target.Y = camera.Position.Y + (float)Math.Sin(Single.DegreesToRadians(cameraPitch)); camera.Target.Z = camera.Position.Z + ((float)Math.Cos(Single.DegreesToRadians(cameraPitch)) * (float)Math.Sin(Single.DegreesToRadians(cameraYaw))); camera.FieldOfView = cameraFov; if (cameraRoll != 0.0f) adjustForCameraRoll(vp, camera); } } } }