#define ADDR_ASSERTS 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.Actions; using SharpPluginLoader.Core.Components; using SharpPluginLoader.Core.Configuration; #if ADDR_ASSERTS using System.Diagnostics; #endif // @TODO: // - targetCamera -> targetPlayerCamera // - UI button to disable fragile workarounds. // - Manully set freecam viewport index. // - Save direct camera offsets as preset. // - Ability to disable more inputs in freecam. /* PadDown = 0x198, PadOld = 0x19C, PadTrg = 0x1A0, PadRel = 0x1A4, PadChg = 0x1A8 uint PadDown = MemoryUtil.Read(sMhSteamController.Instance + 0x198); MemoryUtil.WriteBytes(sMhSteamController.Instance + 0x198, BitConverter.GetBytes(PadRel)); */ // Known Issues: // - Leaving dialoge when vp.Camera.Up is slightly off will not apply camera offsets when it should. // - Test Case: Go up to the handler with the camera left of the player. Talk to her and click Post a New Quest. // Then after the open book animation, leave the dialoge and the camera should jump. // - Getting up from cart doesn't apply offsets when it should. // - Greatly increasing FOV to mitigate culling when camera is rolled causes issues. // - HUD overlays misplaced. // - Other various hard to explain graphical errors. // - Offsetting the camera too much can easily break culling if the camera is behind the ground/wall/ceiling. namespace MHWNewCamera { public unsafe class Plugin : IPlugin { public string Name => "MHW New Camera"; public string Author => "Akon City Software"; private const double DEFAULT_FOV = 60.0; private bool modDisabled = false; private bool modToggleDisabled = false; private float cameraSpeed; private float cameraZoomSpeed; private float cameraSpeedModifier; private float cameraSensitivity; private int stickDeadzone; private double cameraPitchLimit; private double cameraFov = DEFAULT_FOV; 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 double cameraPitch = 0.0; private double cameraYaw = 0.0; private double cameraRoll = 0.0; private double? preZoomFov = null; private double? preZoomRoll = null; private bool freeCamera = false; private bool toggleFreeCamera = false; private bool unlockMovementHeld = false; private bool unlockMovementToggled = false; private bool offsetPerspective = false; private bool toggleOffsetPerspective = false; private Camera? targetCamera = null; private Player? lastPlayer = null; private float previousFov = -1.0f; private int previousCameraAnimState = 0; private bool changeAfterMapOpened = true; private bool changeAfterGetUp = true; private bool changeAfterInsideTent = true; private float? resetFovInView = null; 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 bool disableCollision = false; private bool disableExtraCollision = false; private bool disableGravity = false; private bool disableExtraGravity = false; private Patch disableXCollision; private Patch disableYCollision; private Patch disableSecondaryYCollision; private Patch disableZCollision; private Patch disableExtraYCollision; private Patch disableGravityYUpdate; private Patch disableGravityXZUpdate; /* private Patch disableRotationGravity; private Patch disableRotationGravity2; private Patch disableRotationGravity3; private Patch disableRotationGravity4; private Patch disableRotationGravity5; */ private Patch disableGravityEval; private void disableCollisionEnable() { disableXCollision.Enable(); disableYCollision.Enable(); disableSecondaryYCollision.Enable(); disableZCollision.Enable(); } private void disableCollisionDisable() { disableXCollision.Disable(); disableYCollision.Disable(); disableSecondaryYCollision.Disable(); disableZCollision.Disable(); } private void disableExtraCollisionEnable() { disableExtraYCollision.Enable(); } private void disableExtraCollisionDisable() { disableExtraYCollision.Disable(); } private void disableGravityEnable() { disableGravityYUpdate.Enable(); } private void disableGravityDisable() { disableGravityYUpdate.Disable(); } private void disableExtraGravityEnable() { disableGravityEval.Enable(); /* disableRotationGravity.Enable(); disableRotationGravity2.Enable(); disableRotationGravity3.Enable(); disableRotationGravity4.Enable(); disableRotationGravity5.Enable(); disableGravityXZUpdate.Enable(); */ } private void disableExtraGravityDisable() { disableGravityEval.Disable(); /* disableRotationGravity.Disable(); disableRotationGravity2.Disable(); disableRotationGravity3.Disable(); disableRotationGravity4.Disable(); disableRotationGravity5.Disable(); disableGravityXZUpdate.Disable(); */ } private string typedPresetName = ""; private static readonly MtObject sMhSteamController = SingletonManager.GetSingleton("sMhSteamController")!; public void OnLoad() { Config config = ConfigManager.GetConfig(this); Config.Settings settings = config.CameraSettings; cameraSpeed = settings.CameraSpeed; cameraZoomSpeed = settings.CameraZoomSpeed; cameraSpeedModifier = settings.CameraSpeedModifier; cameraSensitivity = settings.CameraSensitivity; stickDeadzone = settings.StickDeadzone; cameraPitchLimit = settings.CameraPitchLimit; toggleOffsetPerspective = config.PerspectiveCameraEnabled; if (config.Selected != "") { Config.Preset preset = config.Presets[config.Selected]; cameraFov = preset.FOV; cameraForward = preset.Forward; cameraLeft = preset.Left; cameraYOffset = preset.Up; cameraRoll = preset.Roll; } ConfigManager.SaveConfig(this); // Asserts based on version 15.23.00. // Hook where we can adjust the camera position. nint addr = PatternScanner.FindFirst(Pattern.FromString("48 8B C4 55 41 57 48 81 EC D8 00 00 00 44 0F 29 40 B8 45 33 FF ?? ?? ?? ?? ?? ?? ?? ?? ?? 48 8B E9")); #if ADDR_ASSERTS Trace.Assert(addr == 0x141fa5380); #endif calculateCameraHook = Hook.Create(addr, CalculateCameraHook); // Hook where we can directly modify the view matrix. addr = PatternScanner.FindFirst(Pattern.FromString("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 48 89 7C 24 20 41 54 41 56 41 57 48 81 EC 90 00 00 00 ?? ?? ?? ?? ?? ?? ?? 48 8B F9")); #if ADDR_ASSERTS Trace.Assert(addr == 0x14228eb60); #endif calculateViewHook = Hook.Create(addr, CalculateViewHook); // Hook where we can adjust the analog stick value the game uses. addr = PatternScanner.FindFirst(Pattern.FromString("66 0F 6E C1 0F 5B C0 85 C9 78 11")); #if ADDR_ASSERTS Trace.Assert(addr == 0x142107cb0); #endif calculateMovementHook = Hook.Create(addr, CalculateMovementHook); unchecked { // Noop collision checks. addr = PatternScanner.FindFirst(Pattern.FromString("F3 0F 11 06 F3 0F 10 48 04 F3 0F 58 4E 04 F3 0F 11 4E 04 F3 0F 10 40 08 F3 0F 58 46 08 F3 0F 11 46 08 44 8B AF E0 0B 00 00")); #if ADDR_ASSERTS Trace.Assert(addr == 0x141c001b5); #endif disableXCollision = new Patch(addr, [0x90, 0x90, 0x90, 0x90]); #if ADDR_ASSERTS Trace.Assert(addr + 0xe == 0x141c001c3); #endif disableYCollision = new Patch(addr + 0xe, [0x90, 0x90, 0x90, 0x90, 0x90]); #if ADDR_ASSERTS Trace.Assert(addr + 0x1d == 0x141c001d2); #endif disableZCollision = new Patch(addr + 0x1d, [0x90, 0x90, 0x90, 0x90, 0x90]); addr = PatternScanner.FindFirst(Pattern.FromString("F3 0F 11 46 04 F3 0F 10 46 08 F3 0F 5C C2 F3 0F 11 46 08 ?? ?? F3 0F")); #if ADDR_ASSERTS Trace.Assert(addr == 0x141c000d2); #endif disableSecondaryYCollision = new Patch(addr, [0x90, 0x90, 0x90, 0x90, 0x90]); addr = PatternScanner.FindFirst(Pattern.FromString("F3 0F 11 46 04 F3 0F 10 58 08 F3 0F 58 5E 08 F3 0F 11 5E 08 44 8B 87 D0 0B 00 00 41 F6 C0 03")); #if ADDR_ASSERTS Trace.Assert(addr == 0x141bfff90); #endif disableExtraYCollision = new Patch(addr, [0x90, 0x90, 0x90, 0x90, 0x90]); addr = PatternScanner.FindFirst(Pattern.FromString("F3 0F 11 83 80 00 00 00 F3 0F 11 8B 84 00 00 00 F3 0F 11 93 88 00 00 00 89 B3 8C 00 00 00 8B 83 A4 01 00 00 C1 E8 05 44 0F 29 A4 24 D0 01 00 00 44 0F 29 B4 24 B0 01 00 00 A8 01")); #if ADDR_ASSERTS Trace.Assert(addr + 0x8 == 0x1413259ed); #endif disableGravityYUpdate = new Patch(addr + 0x8, [ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, ]); disableGravityXZUpdate = new Patch(addr, [ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // Will already be applied. 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 ]); /* // +0xac0 // 141325C19 - F3 0F11 83 80000000 - movss [rbx+00000080],xmm0 // 141325C36 - F3 0F11 83 84000000 - movss [rbx+00000084],xmm0 // 141325C54 - F3 0F11 83 88000000 - movss [rbx+00000088],xmm0 addr = PatternScanner.FindFirst(Pattern.FromString("F3 0F 59 DB F3 0F 59 D2 F3 0F 11 83 80 00 00 00 F3 0F 58 E3 F3 0F 10 83 84 00 00 00 F3 41 0F 5C C4 F3 0F 58 E2 F3 0F 11 83 84 00 00 00 F3 0F 10 83 88 00 00 00 F3 41 0F 5C C5 44 0F 28 AC 24 C0 01 00 00 F3 0F 11 83 88 00 00 00")); #if ADDR_ASSERTS Trace.Assert(addr + 0x8 == 0x141325c19); #endif disableRotationGravity = new Patch(addr + 0x8, [0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]); disableRotationGravity2 = new Patch(addr + 0x25, [0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]); disableRotationGravity3 = new Patch(addr + 0x43, [0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]); // 1425D94E4 - F3 0F11 86 C0000000 - movss [rsi+000000C0],xmm0 // 1425D94F4 - F3 0F11 86 C8000000 - movss [rsi+000000C8],xmm0 addr = PatternScanner.FindFirst(Pattern.FromString("F3 0F 11 86 C0 00 00 00 F3 0F 10 85 48 02 00 00 F3 0F 11 86 C8 00 00 00 F3 0F 10 85 50 02 00 00 F3 0F 11 8E C4 00 00 00 F3 0F 10 8D 54 02 00 00 89 BE CC 00 00 00 F3 0F 11 86 D0 00 00 00 F3 0F 10 85 58 02 00 00 F3 0F 11 86 D8 00 00 00 F3 0F 10 85 68 02 00 00")); #if ADDR_ASSERTS Trace.Assert(addr == 0x1425D94E4); #endif disableRotationGravity4 = new Patch(addr, [0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]); disableRotationGravity5 = new Patch(addr + 0x10, [0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]); */ // Stop falling from moving player. // 141BFFEFC - F3 0F11 7E 04 - movss [rsi+04],xmm7 // // Instance (of many?) of Y position evaluation for falling? // 141325E2A - 44 0F2F 9B 80010000 - comiss xmm11,[rbx+00000180] // 141BFFF75 - E8 965872FF - call MonsterHunterWorld.exe+1325810 addr = PatternScanner.FindFirst(Pattern.FromString("F3 0F 11 97 54 15 00 00 F3 0F 11 8F 58 15 00 00 F3 0F 10 47 68 F3 0F 5E D8 44 89 B7 6C 15 00 00 F3 0F 5E D0 F3 0F 5E C8 F3 0F 11 9F 60 15 00 00 F3 0F 11 97 64 15 00 00 F3 0F 11 8F 68 15 00 00")); #if ADDR_ASSERTS Trace.Assert(addr + 0x40 == 0x141BFFF75); #endif disableGravityEval = new Patch(addr + 0x40, [0x90, 0x90, 0x90, 0x90, 0x90]); } } // These "assume" functions are really scuffed and almost certainly could have // more proper checks, I just haven't gotten around to it. // Quest board menu. private static bool assumeInQuestBoard(float fov) { return fov == 20.000038f || fov == 20.017189f || fov == 20.00004f; } private bool assumeOnRails(Viewport vp, Camera camera) { // 1. Seasonal gathering hub cutscene. // 2. Zoom into quest board or the handler's book. // 3. Leaving tent. // 4. Getting up from carting. // 5. Leaving dialoge with NPC. // Checking Up.Y != 1.0f is an attempt to include only #2. return vp.Camera != null && vp.Camera.Instance != camera.Instance && vp.Camera.FarClip == 4000000 && vp.Camera.Fix && vp.Camera.Up.Y != 1.0f; } private bool assumeInOpenMap(float fov) { if (fov == 50.942066f) { if (!changeAfterMapOpened) { return true; } // Assume the camera's FOV could end up at 50.942066 during normal gameplay. // Try to mitigate that by tracking the triggers for opening the map // (press and quickly release select or press A at departure location). if (Input.IsReleased(Button.Share) || Input.IsPressed(Button.Cross)) { changeAfterMapOpened = false; return true; } } if (!changeAfterMapOpened) changeAfterMapOpened = true; return false; } private bool assumeInCanteen(float fov, float previousFov) { if (fov == 50.0f) { if (!changeAfterGetUp) { return true; } // Assume that FOV will be greater than or equal to 51.0 before sitting at the canteen. // Also, assume that if FOV is equal to 50.0 in a different situation, previousFov // will be less than 51.0 because it would have been lowering last frame. // This is easily the most fragile check here. if (previousFov >= 51.0f) { if (Input.IsPressed(Button.Cross) || (Input.IsDown(Button.Triangle) || Input.IsReleased(Button.Triangle))) { changeAfterGetUp = false; return true; } } } if (!changeAfterGetUp) changeAfterGetUp = true; return false; } private bool assumeInEnterTent(Camera camera, int animState) { if (camera.Up.Y != 1.0f && animState == 5) { if (!changeAfterInsideTent) { return true; } // This is possible to trip if you spam A while loading into a level before // seeing the wingdrake animation. if (previousCameraAnimState != 5) { if (Input.IsPressed(Button.Cross)) { changeAfterInsideTent = false; return true; } } } if (!changeAfterInsideTent) changeAfterInsideTent = true; return false; } private void maybeSetFovForCulling(Viewport vp, Camera camera) { /* I don't think this would be correct. As of now I'm pretty sure roll is only * considered after this point, in CalculateViewHook. if (cameraRoll == 90.0 || cameraRoll == 270.0) { camera.AspectRatio = vp.Region.Height / (float)vp.Region.Width; } */ if (cameraRoll != 0.0 && camera.FieldOfView < 130.0f) { // The closer the FOV is to 180, the more odd behavior occurs. // 130 seems like a OK default for when the camera is rolled. // This will break HUD overlays like "!" on top of NPCs and the tent exit cutscene. resetFovInView = camera.FieldOfView; camera.FieldOfView = 130.0f; } /* else if (camera.FieldOfView < 60.0f) { resetFovInView = camera.FieldOfView; camera.FieldOfView = 60.0f; } */ } 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; } private static Quaternion getForward(Vector3 pos, Vector3 target) { return new Quaternion(target.X - pos.X, target.Y - pos.Y, target.Z - pos.Z, 0.0f); } // If freeCamera (camera.Move = false), this hook won't run. private void CalculateCameraHook(nint unknownPtr) { if (modDisabled) { calculateCameraHook!.Original(unknownPtr); return; } Viewport vp = CameraSystem.MainViewport; Camera? camera = offsetPerspective ? targetCamera : vp.Camera; if (camera == null) return; bool applyPerspective = offsetPerspective; if (offsetPerspective) { int cameraAnimState = MemoryUtil.Read(camera.Instance + 0x240); applyPerspective = camera.Fix && camera.Move; applyPerspective &= !assumeOnRails(vp, camera); applyPerspective &= !assumeInQuestBoard(camera.FieldOfView); applyPerspective &= !assumeInOpenMap(camera.FieldOfView); applyPerspective &= !assumeInCanteen(camera.FieldOfView, previousFov); applyPerspective &= !assumeInEnterTent(camera, cameraAnimState); previousCameraAnimState = cameraAnimState; } previousFov = camera.FieldOfView; calculateCameraHook!.Original(unknownPtr); if (applyPerspective) { Quaternion forward = Quaternion.Normalize(getForward(camera.Position, camera.Target)); setPerspective(camera, camera.Position, forward); camera.FieldOfView = (float)((cameraFov * previousFov) / 60.0); } if (toggleFreeCamera && !freeCamera) { freeCamera = true; camera.Move = false; preZoomFov = cameraFov; preZoomRoll = cameraRoll; if (applyPerspective) { cameraFov = camera.FieldOfView; } // Camera position might have changed. Quaternion forward = Quaternion.Normalize(getForward(camera.Position, camera.Target)); cameraPitch = Double.RadiansToDegrees(Math.Asin(forward.Y)); cameraYaw = Double.RadiansToDegrees(Math.Atan2(forward.Z, forward.X)); } if (applyPerspective) maybeSetFovForCulling(vp, camera); } private void CalculateViewHook(nint unknownPtr) { if (modDisabled) { calculateViewHook!.Original(unknownPtr); return; } Viewport vp = CameraSystem.MainViewport; bool notFreecam = offsetPerspective && !freeCamera; Camera? camera = notFreecam ? targetCamera : vp.Camera; bool updateView = freeCamera || offsetPerspective; if (resetFovInView != null && updateView && camera != null) { camera.FieldOfView = (float)resetFovInView; resetFovInView = null; } calculateViewHook!.Original(unknownPtr); if (updateView && cameraRoll != 0.0) { Matrix4x4 rollRotation = Matrix4x4.CreateRotationZ((float)Double.DegreesToRadians(cameraRoll)); vp.ViewMatrix *= rollRotation; } } private float CalculateMovementHook(int stickValue) { if (!modDisabled && freeCamera && !(unlockMovementHeld || unlockMovementToggled)) { stickValue = 0; } return calculateMovementHook!.Original(stickValue); } private void disableAllHooks() { if (disableExtraGravity) { disableExtraGravity = false; disableExtraGravityDisable(); } if (disableGravity) { disableGravity = false; disableGravityDisable(); } if (disableExtraCollision) { disableExtraCollision = false; disableExtraCollisionDisable(); } if (disableCollision) { disableCollision = false; disableCollisionDisable(); } } public void OnImGuiRender() { Config config = ConfigManager.GetConfig(this); float width = ImGui.GetWindowWidth(); if (ImGui.Checkbox("Disable Mod", ref modDisabled)) { if (modDisabled) { disableAllHooks(); if (freeCamera) { freeCamera = false; if (preZoomRoll != null) { cameraRoll = (double)preZoomRoll; preZoomRoll = null; } } for (int i = 0; i < 8; i++) { Viewport vp = CameraSystem.GetViewport(i); if (vp.Camera != null) vp.Camera.Move = true; } Player? player = Player.MainPlayer; if (player != null) { player.Rotation.X = 0.0f; player.Rotation.Z = 0.0f; } } } if (ImGui.BeginItemTooltip()) { ImGui.Text("Try to disable as much of the mod as possible."); ImGui.EndTooltip(); } if (modDisabled) return; ImGui.Checkbox("Disable Toggle Bind", ref modToggleDisabled); if (ImGui.BeginItemTooltip()) { ImGui.Text("Disable button combination (Hold RStick + Press LT) for toggling free camera."); ImGui.EndTooltip(); } ImGui.Checkbox("Enable Free Camera", ref toggleFreeCamera); if (toggleFreeCamera) { ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); } if (ImGui.Checkbox("Enable Perspective Camera", ref toggleOffsetPerspective)) { // Take the opposite because it's yet to be toggled. config.PerspectiveCameraEnabled = !offsetPerspective; ConfigManager.SaveConfig(this); } if (toggleFreeCamera) { ImGui.PopItemFlag(); ImGui.PopStyleVar(); } ImGui.PushID("Perspective"); float singleFov = (float)cameraFov; if (ImGui.DragFloat("Field of View", ref singleFov, 0.4f, 1.0f, 179.0f)) { cameraFov = singleFov; } ImGui.DragFloat("Forward", ref cameraForward, 0.4f); ImGui.DragFloat("Left", ref cameraLeft, 0.4f); ImGui.DragFloat("Up", ref cameraYOffset, 0.4f); if (ImGui.Button("Default")) { cameraFov = DEFAULT_FOV; cameraForward = 0.0f; cameraLeft = 0.0f; cameraXOffset = 0.0f; cameraYOffset = 0.0f; cameraZOffset = 0.0f; config.Selected = ""; ConfigManager.SaveConfig(this); } ImGui.SameLine(); if (ImGui.Button("Save")) { string name = typedPresetName; if (name != "") { Config.Preset preset = new Config.Preset(); preset.FOV = cameraFov; preset.Forward = cameraForward; preset.Left = cameraLeft; preset.Up = cameraYOffset; preset.Roll = cameraRoll; if (config.Presets.ContainsKey(name)) { config.Presets[name] = preset; } else { config.Presets.Add(name, preset); } config.Selected = name; ConfigManager.SaveConfig(this); } } ImGui.SameLine(); if (ImGui.Button("Delete")) { if (config.Selected != "") { config.Presets.Remove(config.Selected); config.Selected = ""; ConfigManager.SaveConfig(this); } } ImGui.SameLine(); ImGui.PushItemWidth(width * 0.2f); ImGui.InputText("##Preset Name", ref typedPresetName, 99); ImGui.SameLine(); if (ImGui.BeginCombo("##Preset", config.Selected)) { Dictionary.KeyCollection presetKeys = config.Presets.Keys; for (int i = 0; i < presetKeys.Count; i++) { string presetKey = presetKeys.ElementAt(i); bool isSelected = presetKey == config.Selected; if (ImGui.Selectable(presetKey, isSelected)) { config.Selected = presetKey; ConfigManager.SaveConfig(this); Config.Preset preset = config.Presets[config.Selected]; cameraFov = preset.FOV; cameraForward = preset.Forward; cameraLeft = preset.Left; cameraYOffset = preset.Up; cameraRoll = preset.Roll; } if (isSelected) ImGui.SetItemDefaultFocus(); } ImGui.EndCombo(); } ImGui.PopItemWidth(); ImGui.PopID(); ImGui.PushID("Settings"); ImGui.DragFloat("Camera Speed", ref cameraSpeed, 0.01f, 0.0f); if (ImGui.BeginItemTooltip()) { ImGui.Text("Movement speed in freecam."); ImGui.EndTooltip(); } ImGui.DragFloat("Zoom Speed", ref cameraZoomSpeed, 0.01f, 0.0f); if (ImGui.BeginItemTooltip()) { ImGui.Text("Rate of camera zoom."); ImGui.EndTooltip(); } ImGui.DragFloat("Speed Modifier", ref cameraSpeedModifier, 0.01f, 0.0f); if (ImGui.BeginItemTooltip()) { ImGui.Text("Value to multiply speed by when L2 is held."); ImGui.EndTooltip(); } ImGui.DragFloat("Camera Sensitivity", ref cameraSensitivity, 0.0025f, 0.0f); if (ImGui.BeginItemTooltip()) { ImGui.Text("Camera look sensitivity in freecam."); ImGui.EndTooltip(); } ImGui.InputInt("Stick Deadzone", ref stickDeadzone, 10); if (ImGui.Button("Default")) { cameraSpeed = Config.Settings.DEFAULT_SPEED; cameraZoomSpeed = Config.Settings.DEFAULT_ZOOM_SPEED; cameraSpeedModifier = Config.Settings.DEFAULT_SPEED_MODIFIER; cameraSensitivity = Config.Settings.DEFAULT_SENSITIVITY; stickDeadzone = Config.Settings.DEFAULT_DEADZONE; cameraPitchLimit = Config.Settings.DEFAULT_PITCH_LIMIT; } ImGui.SameLine(); if (ImGui.Button("Reset")) { Config.Settings settings = config.CameraSettings; cameraSpeed = settings.CameraSpeed; cameraZoomSpeed = settings.CameraZoomSpeed; cameraSpeedModifier = settings.CameraSpeedModifier; cameraSensitivity = settings.CameraSensitivity; stickDeadzone = settings.StickDeadzone; cameraPitchLimit = settings.CameraPitchLimit; } ImGui.SameLine(); if (ImGui.Button("Save")) { Config.Settings settings = config.CameraSettings; settings.CameraSpeed = cameraSpeed; settings.CameraZoomSpeed = cameraZoomSpeed; settings.CameraSpeedModifier = cameraSpeedModifier; settings.CameraSensitivity = cameraSensitivity; settings.StickDeadzone = stickDeadzone; settings.CameraPitchLimit = cameraPitchLimit; config.CameraSettings = settings; ConfigManager.SaveConfig(this); } ImGui.PopID(); ImGui.PushID("Debug"); if (ImGui.CollapsingHeader("DEBUG")) { ImGui.PushID("Camera"); if (targetCamera != null) { ImGui.Text($"Target Camera: {targetCamera.Instance:x}"); } ImGui.Text("Camera/Viewport:"); Viewport vp; for (int i = 0; i < 8; i++) { if (ImGui.CollapsingHeader($"Camera #{i}")) { ImGui.PushID($"Camera{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); var camera = vp.Camera; ImGui.Text($"Camera Pointer: {camera.Instance:x}"); ImGui.DragFloat("FOV", ref camera.FieldOfView, 0.45f); ImGui.Text($"Internal FOV: {previousFov}"); if (ImGui.BeginItemTooltip()) { ImGui.Text("What the FOV would be if not set by us."); ImGui.EndTooltip(); } ImGui.InputFloat("Aspect Ratio", ref camera.AspectRatio); ImGui.InputFloat("Far Clip", ref camera.FarClip); ImGui.InputFloat("Near Clip", ref camera.NearClip); ImGui.DragFloat3("Position", ref camera.Position, 0.45f); ImGui.DragFloat3("Target", ref camera.Target, 0.45f); ImGui.DragFloat3("Up", ref camera.Up, 0.45f); ImGui.Text("Offset:"); float offsetForward = 0.0f; if (ImGui.DragFloat("Forward", ref offsetForward, 0.45f)) { Quaternion forward = getForward(camera.Position, camera.Target); forward = Quaternion.Normalize(forward); camera.Position.X += forward.X * offsetForward; camera.Position.Y += forward.Y * offsetForward; camera.Position.Z += forward.Z * offsetForward; } float offsetLeft = 0.0f; if (ImGui.DragFloat("Left", ref offsetLeft, 0.45f)) { Quaternion forward = getForward(camera.Position, camera.Target); forward = Quaternion.Normalize(forward); Quaternion left = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); camera.Position.X += left.X * offsetLeft; camera.Position.Z += left.Z * offsetLeft; } if (i == 0) { ImGui.Text("Rotation:"); double pitch = cameraPitch; if (ImGui.InputDouble("Pitch", ref pitch, 0.25)) { cameraPitch = pitch; } double yaw = cameraYaw; if (ImGui.InputDouble("Yaw", ref yaw, 0.25)) { cameraYaw = yaw; } float roll = (float)cameraRoll; if (ImGui.DragFloat("Roll", ref roll, 0.25f)) { cameraRoll = roll; if (preZoomRoll != null) { preZoomRoll = roll; } } float pitchLimit = (float)cameraPitchLimit; if (ImGui.InputFloat("Pitch Limit", ref pitchLimit, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { cameraPitchLimit = pitchLimit; } } bool move = camera.Move; if (ImGui.Checkbox("Move", ref move)) { camera.Move = move; } ImGui.SameLine(); bool fix = camera.Fix; if (ImGui.Checkbox("Fix", ref fix)) { camera.Fix = fix; } } ImGui.PopID(); } } ImGui.PopID(); ImGui.PushID("Player"); Player? player = Player.MainPlayer; if (player != null) { ImGui.Separator(); ImGui.Text("Player:"); ImGui.Text($"Pointer: {player.Instance:x}"); ImGui.DragFloat3("Position", ref player.Position, 0.5f); if (ImGui.Checkbox("Disable Collision", ref disableCollision)) { if (disableCollision) { disableCollisionEnable(); } else { if (disableExtraGravity) { disableExtraGravity = false; disableExtraGravityDisable(); } if (disableGravity) { disableGravity = false; disableGravityDisable(); } if (disableExtraCollision) { disableExtraCollision = false; disableExtraCollisionDisable(); } disableCollisionDisable(); } } if (!disableCollision) { ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); } if (ImGui.Checkbox("Disable Another Y Collision", ref disableExtraCollision)) { if (disableExtraCollision) { disableExtraCollisionEnable(); } else { if (disableExtraGravity) { disableExtraGravity = false; disableExtraGravityDisable(); } if (disableGravity) { disableGravity = false; disableGravityDisable(); } disableExtraCollisionDisable(); } } if (ImGui.BeginItemTooltip()) { ImGui.Text("This will make your legs goofy, but it's needed to get on top of some things."); ImGui.EndTooltip(); } if (!disableCollision) { ImGui.PopItemFlag(); ImGui.PopStyleVar(); } if (!disableExtraCollision) { ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); } if (ImGui.Checkbox("Disable Y Gravity", ref disableGravity)) { if (disableGravity) { disableGravityEnable(); } else { if (disableExtraGravity) { disableExtraGravity = false; disableExtraGravityDisable(); } disableGravityDisable(); } } if (ImGui.BeginItemTooltip()) { ImGui.Text("Avoid falling when going too high (Y axis)."); ImGui.EndTooltip(); } if (!disableExtraCollision) { ImGui.PopItemFlag(); ImGui.PopStyleVar(); } if (!disableGravity) { ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); } if (ImGui.Checkbox("Disable More Gravity", ref disableExtraGravity)) { if (disableExtraGravity) { disableExtraGravityEnable(); } else { disableExtraGravityDisable(); } } if (ImGui.BeginItemTooltip()) { ImGui.Text("This can stop you from starting to fall when going out of bounds and/or rotating."); ImGui.EndTooltip(); } if (!disableGravity) { ImGui.PopItemFlag(); ImGui.PopStyleVar(); } Vector4 rotation; rotation.X = player.Rotation.X; rotation.Y = player.Rotation.Y; rotation.Z = player.Rotation.Z; rotation.W = player.Rotation.W; if (ImGui.SliderFloat4("Rotation", ref rotation, -1.0f, 1.0f)) { player.Rotation.X = rotation.X; player.Rotation.Y = rotation.Y; player.Rotation.Z = rotation.Z; player.Rotation.W = rotation.W; } if (ImGui.Button("Reset")) { player.Rotation.X = 0.0f; player.Rotation.Z = 0.0f; } Vector3 forward; forward.X = player.Forward.X; forward.Y = player.Forward.Y; forward.Z = player.Forward.Z; ImGui.DragFloat3("Forward", ref forward, 0.0f); ActionInfo currentActionInfo = player.ActionController.CurrentAction; SharpPluginLoader.Core.Actions.Action? currentAction = null; if (currentActionInfo.ActionSet >= 0 && currentActionInfo.ActionSet <= 3) { ActionList actionList = player.ActionController.GetActionList(currentActionInfo.ActionSet); if (currentActionInfo.ActionId >= 0 && currentActionInfo.ActionId < actionList.Count) { currentAction = actionList[currentActionInfo.ActionId]; } } AnimationLayerComponent? animationLayer = player.AnimationLayer; AnimationId currentAnimation = player.CurrentAnimation; ImGui.Text("Action/Animation:"); ImGui.Text($" Current: {currentAction}, {currentAnimation}"); if (animationLayer != null) { ImGui.Text($" Speed: {animationLayer.Speed:0.000}"); ImGui.Text($" Frame: {animationLayer.CurrentFrame:0.000}/{animationLayer.MaxFrame:0.000}"); } if (currentAction != null) { ImGui.Text($" Active Time: {currentAction.ActiveTime}"); // An experiment to test potential animation error could be try to keep deltasec as consistent as possible. ImGui.Text($" Delta Sec: {currentAction.DeltaSec}"); } /* // 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}, Fix: {player.Fix}"); } ImGui.PopID(); ImGui.Separator(); ImGui.PushID("Pad"); 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:"); ImGui.Text($" X: {PadLx}"); ImGui.Text($" Y: {PadLy}"); ImGui.Text($" L2: {PadLz}"); ImGui.Text("Right Stick:"); ImGui.Text($" X: {PadRx}"); ImGui.Text($" Y: {PadRy}"); ImGui.Text($" R2: {PadRz}"); ImGui.PopID(); } ImGui.PopID(); } public void OnUpdate(float deltaTime) { if (modDisabled) return; Viewport vp = CameraSystem.MainViewport; Camera? camera = vp.Camera; if (camera == null) return; Player? player = Player.MainPlayer; if (player != null) { if (offsetPerspective && player != lastPlayer) { // Update target camera on player change. targetCamera = camera; previousFov = -1.0f; } lastPlayer = player; if (toggleOffsetPerspective && !offsetPerspective) { offsetPerspective = true; targetCamera = camera; previousFov = -1.0f; } else if (!toggleOffsetPerspective && offsetPerspective) { offsetPerspective = false; targetCamera = null; } } if (!modToggleDisabled && (!Input.IsPressed(Button.R3) && Input.IsDown(Button.R3)) && Input.IsPressed(Button.L2)) { toggleFreeCamera = !toggleFreeCamera; } if (freeCamera) { if (!unlockMovementHeld && Input.IsPressed(Button.R2)) { unlockMovementHeld = true; } else if (unlockMovementHeld && Input.IsReleased(Button.R2)) { unlockMovementHeld = false; } if (unlockMovementHeld && Input.IsPressed(Button.L1)) { unlockMovementToggled = !unlockMovementToggled; } if (Input.IsDown(Button.R2)) { if (Input.IsDown(Button.L2)) { if (unlockMovementHeld) { unlockMovementHeld = false; } if (unlockMovementToggled) { unlockMovementToggled = false; } } else if (Input.IsReleased(Button.L2)) { unlockMovementHeld = true; } } if (Input.IsDown(Button.L2) && Input.IsDown(Button.R2)) { int PadLy = MemoryUtil.Read(sMhSteamController.Instance + 0x1bc); if (Math.Abs(PadLy) < stickDeadzone) PadLy = 0; double adjustedZoomSpeed = cameraZoomSpeed * (deltaTime * cameraFov); double Ly = PadLy / (Int16.MaxValue / adjustedZoomSpeed); cameraFov = Math.Clamp(cameraFov - Ly, 1.0, 179.0); } else if (!(unlockMovementHeld || unlockMovementToggled)) { int PadLy = MemoryUtil.Read(sMhSteamController.Instance + 0x1bc); if (Math.Abs(PadLy) < stickDeadzone) PadLy = 0; int PadLx = MemoryUtil.Read(sMhSteamController.Instance + 0x1b8); if (Math.Abs(PadLx) < stickDeadzone) PadLx = 0; double adjustedSpeed = cameraSpeed * (deltaTime * 100.0); double Lx = PadLx / (Int16.MaxValue / adjustedSpeed); double Ly = PadLy / (Int16.MaxValue / adjustedSpeed); Quaternion forward = getForward(camera.Position, camera.Target); double buttonOffset = adjustedSpeed / 2.0; if (Input.IsDown(Button.L2)) { buttonOffset *= cameraSpeedModifier; Lx *= cameraSpeedModifier; Ly *= cameraSpeedModifier; forward.Y = 0.0f; } if (Input.IsDown(Button.Down)) { camera.Position.Y -= (float)buttonOffset; } if (Input.IsDown(Button.Up)) { camera.Position.Y += (float)buttonOffset; } if (Input.IsDown(Button.Left)) { cameraRoll -= buttonOffset; } if (Input.IsDown(Button.Right)) { cameraRoll += buttonOffset; } forward = Quaternion.Normalize(forward); Quaternion left = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); camera.Position.X += (float)(left.X * Lx); camera.Position.Z += (float)(left.Z * Lx); camera.Position.X += (float)(forward.X * Ly); camera.Position.Y += (float)(forward.Y * Ly); camera.Position.Z += (float)(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; double adjustedSensitivity = cameraSensitivity * (deltaTime * cameraFov); double Rx = PadRx / (Int16.MaxValue / adjustedSensitivity); double Ry = PadRy / (Int16.MaxValue / adjustedSensitivity); cameraYaw += Rx; cameraPitch = Math.Clamp(cameraPitch + Ry, -cameraPitchLimit, cameraPitchLimit); // This is the same value the game uses for the player camera. Oddly the in-game view mode // uses 1.0 like I did here before, which is really bad for precision. double dist = 700.0 - cameraForward; camera.Target.X = camera.Position.X + (float)(dist * Math.Cos(Double.DegreesToRadians(cameraPitch)) * Math.Cos(Double.DegreesToRadians(cameraYaw))); camera.Target.Y = camera.Position.Y + (float)(dist * Math.Sin(Double.DegreesToRadians(cameraPitch))); camera.Target.Z = camera.Position.Z + (float)(dist * Math.Cos(Double.DegreesToRadians(cameraPitch)) * Math.Sin(Double.DegreesToRadians(cameraYaw))); camera.FieldOfView = (float)cameraFov; if (!toggleFreeCamera) { freeCamera = false; camera.Move = true; if (preZoomFov != null) { cameraFov = (double)preZoomFov; preZoomFov = null; } if (preZoomRoll != null) { cameraRoll = (double)preZoomRoll; preZoomRoll = null; } } maybeSetFovForCulling(vp, camera); } } } }