//#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: // - Trigger blue camera on minimap. // - Ability to disable more inputs in freecam. // - Update default binds. // - Bind to change preset. // - Bind to reverse freecam input (left AND right stick no effect on camera). // - Display equiped armor in the debug UI. // - Manully set freecam viewport index. // - Override in-game viewmode. // - +right and +forward to move character. // - Keyframes? // - Special case freecam for cutscenes and canteen animations. // Test cases: // Offset Perspective: // - For all cases roll should an extra consideration. // Constant FOV: // - In quest book/board GUI (uses player camera). // previousCameraAnimState = 5 (Don't apply offset): // - Zoom into quest board or The Handler's book. // - Open map animation. // - Entering/Leaving tent. // previousCameraAnimState = 4 (We choose to apply offset): // - Quest depart/return. // - Traveling on the lift in Astera. // previousCameraAnimState = 2 (Apply offset): // - Point camera towards quest board. // Player camera is the target of a transition: // - Leaving dialoge with NPC (The Handler, The Smithy, pub lasses, etc). // - Getting up from hot spring. // - Getting up from canteen. // - Getting up from cart. // - Seasonal gathering hub cutscene. // Directly moves player camera: // - Look at monster. // - Scoutflies point in a direction. // - Mount monster. // Free Camera: // - Toggle rapidly and there should be no visual jump. // - Story cutscenes. // - Canteen cutscene/animation. // - Inside tent. // - Equipment select inside tent. 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 disableMod = false; private float cameraSpeed; private float cameraZoomSpeed; private float cameraSpeedModifier; private float cameraSensitivity; private double cameraPitchLimit; private int stickDeadzone; private bool enableCombo = false; private Button[]? freeCameraCombo = null; private double cameraFov = DEFAULT_FOV; private float cameraForward = 0.0f; private float cameraRight = 0.0f; private float cameraXOffset = 0.0f; private float cameraYOffset = 0.0f; private float cameraZOffset = 0.0f; private double cameraYaw = 0.0; private double cameraPitch = 0.0; private double cameraRoll = 0.0; private int cameraWrapState = 0; private bool freeCamera = false; private bool enableFreeCamera = false; private Camera? vCamera = null; private bool cameraSetFromAlt = false; private Vector3 cameraPosition; private Vector3 cameraTarget; private double? preDetachFov = null; private double? preDetachRoll = null; private bool unlockMovementHeld = false; private bool unlockMovementToggled = false; private bool offsetPerspective = false; private bool enableOffsetPerspective = false; private bool applyPerspective = false; private Player? lastPlayer = null; private Camera? pCamera = null; private float previousFov = -1.0f; private int previousCameraAnimState = 0; private bool ignoreAnimState = false; private bool debugButton = false; private float plusRight = 0.0f; private float plusForward = 0.0f; private delegate void SetCameraDelegate(nint unknownPtr); private Hook? setCameraHook; private delegate void SetCameraTentDelegate(nint unknownPtr); private Hook? setCameraTentHook; private delegate void CalculateCameraDelegate(nint unknownPtr); private Hook? calculateCameraHook; private delegate void CameraUpDelegate(nint unknownPtr); private Hook? cameraUpHook; private delegate float CheckMovementDelegate(int stickValue); private Hook? checkMovementHook; private Patch disableCameraAnimation; 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 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(); } private void disableExtraGravityDisable() { disableGravityEval.Disable(); } private static readonly MtObject sMhSteamController = SingletonManager.GetSingleton("sMhSteamController")!; private nint controllerAddr() => sMhSteamController.Instance; private static readonly MtObject sOtomo = SingletonManager.GetSingleton("sOtomo")!; // Gui strings. private string typedCombo = ""; private string typedPresetName = ""; private string typedPositionName = ""; private string selectedPositionName = ""; public void OnLoad() { Config config = ConfigManager.GetConfig(this); disableMod = config.DisableMod; enableCombo = config.EnableCombo; freeCameraCombo = Config.ParseCombo(config.FreeCameraCombo); typedCombo = config.FreeCameraCombo.Replace(",", "+"); Config.Settings settings = config.CameraSettings; cameraSpeed = settings.CameraSpeed; cameraZoomSpeed = settings.CameraZoomSpeed; cameraSpeedModifier = settings.CameraSpeedModifier; cameraSensitivity = settings.CameraSensitivity; cameraPitchLimit = settings.CameraPitchLimit; if (cameraPitchLimit != -1.0f) { cameraPitchLimit = Math.Clamp(cameraPitchLimit, 0.0f, 89.95f); } stickDeadzone = settings.StickDeadzone; enableOffsetPerspective = config.PerspectiveCameraEnabled; if (config.Selected != "") { Config.Preset preset = config.Presets[config.Selected]; cameraFov = preset.FOV; cameraForward = preset.Forward; cameraRight = preset.Right; cameraYOffset = preset.Up; cameraRoll = preset.Roll; } ConfigManager.SaveConfig(this); // Asserts based on version 15.23.00. // @TODO: Handle addr not found. // A place to check state before CalculateCameraHook. nint addr = PatternScanner.FindFirst(Pattern.FromString("48 8B C4 48 89 58 08 48 89 70 10 48 89 78 18 4C 89 60 20 55 41 56 41 57 48 8D A8 28 FE FF FF 48 81 EC C0 02 00 00 0F 29 70 D8 48 8B F1")); #if ADDR_ASSERTS Trace.Assert(addr == 0x141fa16d0); // nint #endif setCameraHook = Hook.Create(addr, SetCameraHook); // Very special case for inside tent. addr = PatternScanner.FindFirst(Pattern.FromString("48 89 5C 24 10 48 89 6C 24 18 48 89 7C 24 20 41 56 48 83 EC 60 48 8B D9 0F 57 DB ?? ?? ?? ?? ?? ?? ?? 0F 57 D2 33 D2")); #if ADDR_ASSERTS Trace.Assert(addr == 0x142106450); // nint #endif setCameraTentHook = Hook.Create(addr, SetCameraTentHook); // Hook where we can adjust the camera position. 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); // nint #endif calculateCameraHook = Hook.Create(addr, CalculateCameraHook); // Hook where we can set Camera.Up before it's used. addr = PatternScanner.FindFirst(Pattern.FromString("40 53 48 81 EC 80 00 00 00 8B 81 B0 17 00 00 48 8B D9 85 C0 ?? ??")); #if ADDR_ASSERTS Trace.Assert(addr == 0x141fa11a0); // nint #endif cameraUpHook = Hook.Create(addr, CameraUpHook); // Hook where we can change the analog stick value the game uses for character movement. addr = PatternScanner.FindFirst(Pattern.FromString("66 0F 6E C1 0F 5B C0 85 C9 78 11")); #if ADDR_ASSERTS Trace.Assert(addr == 0x142107cb0); // int #endif checkMovementHook = Hook.Create(addr, CheckMovementHook); unchecked { addr = PatternScanner.FindFirst(Pattern.FromString("40 53 48 81 EC B0 00 00 00 48 8B D9 48 8B D1 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 48 8B 03 48 8D 54 24 30")); #if ADDR_ASSERTS Trace.Assert(addr == 0x1423b1420); // nint #endif disableCameraAnimation = new Patch(addr + 0x50, [ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 ]); // 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 ]); // 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]); } } 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); } private void setCameraRoll(Camera camera) { // Don't test our luck with precision weirdness if we know we can be exact. if (cameraRoll == 0.0) { camera.Up.X = 0.0f; camera.Up.Y = 1.0f; camera.Up.Z = 0.0f; } else if (cameraRoll == 180.0) { camera.Up.X = 0.0f; camera.Up.Y = -1.0f; camera.Up.Z = 0.0f; } else { // yaw + 90.0 = point right. camera.Up.X = (float)(Math.Sin(Double.DegreesToRadians(cameraRoll)) * Math.Cos(Double.DegreesToRadians(cameraPitch)) * Math.Cos(Double.DegreesToRadians(cameraYaw + 90.0))); camera.Up.Y = (float)(Math.Cos(Double.DegreesToRadians(cameraRoll))); camera.Up.Z = (float)(Math.Sin(Double.DegreesToRadians(cameraRoll)) * Math.Cos(Double.DegreesToRadians(cameraPitch)) * Math.Sin(Double.DegreesToRadians(cameraYaw + 90.0))); } } private void setPerspective(Camera camera) { Quaternion forward = getForward(camera.Position, camera.Target); Quaternion right = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); forward = Quaternion.Normalize(forward); right = Quaternion.Normalize(right); Vector3 pos = camera.Position; pos.X += forward.X * cameraForward; pos.Y += forward.Y * cameraForward; pos.Z += forward.Z * cameraForward; pos.X += right.X * cameraRight; pos.Z += right.Z * cameraRight; pos.X += cameraXOffset; if (debugButton) pos.Y += cameraYOffset; pos.Z += cameraZOffset; camera.Position = pos; if (debugButton && cameraFov != DEFAULT_FOV) { camera.FieldOfView = (float)((cameraFov * previousFov) / DEFAULT_FOV); } } private void setTentBasePos(Vector3 pos, Vector3 target) { // @TODO: This can be picked out of the code. nint baseAddr = MemoryUtil.Read(0x145011f58); // Default: MemoryUtil.WriteBytes(baseAddr + 0x3e20, BitConverter.GetBytes(pos.X)); // 0.0 MemoryUtil.WriteBytes(baseAddr + 0x3e24, BitConverter.GetBytes(pos.Y)); // -19850.0 MemoryUtil.WriteBytes(baseAddr + 0x3e28, BitConverter.GetBytes(pos.Z)); // 270.0 MemoryUtil.WriteBytes(baseAddr + 0x3e30, BitConverter.GetBytes(target.X)); // 0.0 MemoryUtil.WriteBytes(baseAddr + 0x3e34, BitConverter.GetBytes(target.Y)); // -19830.0 MemoryUtil.WriteBytes(baseAddr + 0x3e38, BitConverter.GetBytes(target.Z)); // 0.0 } private void disableFreeCamera() { freeCamera = false; if (preDetachFov != null) { cameraFov = (double)preDetachFov; preDetachFov = null; } if (preDetachRoll != null) { cameraRoll = (double)preDetachRoll; preDetachRoll = null; } cameraWrapState = 0; setTentBasePos(new Vector3(0.0f, -19850.0f, 270.0f), new Vector3(0.0f, -19830.0f, 0.0f)); cameraSetFromAlt = false; } private void updateFreecam(Camera camera) { float deltaTime = camera.DeltaTime; if (Input.IsDown(Button.L2) && Input.IsDown(Button.R2)) // Left stick zoom. { int PadLy = MemoryUtil.Read(controllerAddr() + 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)) // Move camera. { int PadLy = MemoryUtil.Read(controllerAddr() + 0x1bc); if (Math.Abs(PadLy) < stickDeadzone) PadLy = 0; int PadLx = MemoryUtil.Read(controllerAddr() + 0x1b8); if (Math.Abs(PadLx) < stickDeadzone) PadLx = 0; double adjustedSpeed = cameraSpeed * deltaTime; double Lx = PadLx / (Int16.MaxValue / adjustedSpeed); double Ly = PadLy / (Int16.MaxValue / adjustedSpeed); Quaternion forward = getForward(cameraPosition, cameraTarget); if (Input.IsDown(Button.L2)) { forward.Y = 0.0f; Lx *= cameraSpeedModifier; Ly *= cameraSpeedModifier; double buttonOffset = adjustedSpeed / 2.0; buttonOffset *= cameraSpeedModifier; if (Input.IsDown(Button.Up)) { cameraPosition.Y += (float)buttonOffset; } if (Input.IsDown(Button.Down)) { cameraPosition.Y -= (float)buttonOffset; } if (Input.IsDown(Button.Left)) { cameraRoll -= buttonOffset; } if (Input.IsDown(Button.Right)) { cameraRoll += buttonOffset; } } Quaternion right = forward; right.Y = 0.0f; right = right * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); forward = Quaternion.Normalize(forward); right = Quaternion.Normalize(right); cameraPosition.X += (float)(right.X * Lx); cameraPosition.Z += (float)(right.Z * Lx); cameraPosition.X += (float)(forward.X * Ly); cameraPosition.Y += (float)(forward.Y * Ly); cameraPosition.Z += (float)(forward.Z * Ly); if (plusForward != 0.0f) { cameraPosition.X += (float)(forward.X * plusForward * deltaTime); cameraPosition.Y += (float)(forward.Y * plusForward * deltaTime); cameraPosition.Z += (float)(forward.Z * plusForward * deltaTime); } } // Camera look. int PadRx = MemoryUtil.Read(controllerAddr() + 0x1b0); if (Math.Abs(PadRx) < stickDeadzone) PadRx = 0; int PadRy = MemoryUtil.Read(controllerAddr() + 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; if (cameraYaw >= 180.0) { cameraYaw -= 360.0; } else if (cameraYaw < -180.0) { cameraYaw += 360.0; } if (cameraPitchLimit >= 0.0) { cameraPitch = Math.Clamp(cameraPitch + Ry, -cameraPitchLimit, cameraPitchLimit); } else { cameraPitch += Ry; if (cameraPitch >= 90.0 && cameraWrapState == 0) { cameraRoll += 180.0; cameraWrapState = 1; } else if (cameraPitch < -90.0 && cameraWrapState == 0) { cameraPitch += 360.0; cameraRoll += 180.0; cameraWrapState = 1; } else if (cameraPitch < 90.0 && cameraWrapState == 1) { cameraRoll -= 180.0; cameraWrapState = 0; } else if (cameraPitch >= 270.0 && cameraWrapState == 1) { cameraPitch -= 360.0; cameraRoll -= 180.0; cameraWrapState = 0; } } if (cameraRoll >= 360.0) { cameraRoll -= 360.0; } else if (cameraRoll < 0.0) { cameraRoll += 360.0; } if (plusRight != 0.0) { cameraYaw += plusRight * deltaTime; } // 700.0 is the same value the game uses for the player camera. The in-game view mode // uses 1.0 like I did here before, which is really bad for precision. double dist = 700.0 - cameraForward; cameraTarget.X = cameraPosition.X + (float)(dist * Math.Cos(Double.DegreesToRadians(cameraPitch)) * Math.Cos(Double.DegreesToRadians(cameraYaw))); cameraTarget.Y = cameraPosition.Y + (float)(dist * Math.Sin(Double.DegreesToRadians(cameraPitch))); cameraTarget.Z = cameraPosition.Z + (float)(dist * Math.Cos(Double.DegreesToRadians(cameraPitch)) * Math.Sin(Double.DegreesToRadians(cameraYaw))); camera.Position = cameraPosition; camera.Target = cameraTarget; camera.FieldOfView = (float)cameraFov; } private Camera? getVisibleCamera() { for (int i = 0; i < 8; i++) { Viewport vp = CameraSystem.GetViewport(i); // A viewport can be visible with a null camera. if (vp.Visible && vp.Camera != null) { if (freeCamera && vp.Camera != vCamera) { // This will still be broken in most cases, just less broken. cameraPosition = vp.Camera.Position; cameraTarget = vp.Camera.Target; } return vp.Camera; } } return null; } private void SetCameraHook(nint unknownPtr) { if (!disableMod) { vCamera = getVisibleCamera(); if (pCamera == null && lastPlayer != null) { // This assume the visible camera when, or right after, MainPlayer is set is the player camera. pCamera = vCamera; } if (pCamera != null) { previousCameraAnimState = MemoryUtil.Read(pCamera.Instance + 0x240); if (previousCameraAnimState == 5) { // 1:314 = Wingdrake landing on area enter. // 1:319 = Scared wingdrake landing next to monster. Player player = Player.MainPlayer!; ActionInfo currentActionInfo = player.ActionController.CurrentAction; if (currentActionInfo.ActionSet == 1 && (currentActionInfo.ActionId == 314 || currentActionInfo.ActionId == 319)) { // Temporarily disable evaluation of cameraAnimState != 5. ignoreAnimState = true; } } else if (ignoreAnimState) { // Once cameraAnimState is no longer 5, resume default logic. ignoreAnimState = false; } } else { previousCameraAnimState = 0; } } setCameraHook!.Original(unknownPtr); } private void SetCameraTentHook(nint unknownPtr) { if (!disableMod && freeCamera) { setTentBasePos(cameraPosition, cameraTarget); } setCameraTentHook!.Original(unknownPtr); if (!disableMod && freeCamera && vCamera != null) { updateFreecam(vCamera); setCameraRoll(vCamera); cameraSetFromAlt = true; } } private static bool assumeInQuestBoard(float fov) { return fov == 20.000038f || fov == 20.017189f || fov == 20.00004f; } // If camera.Move = false, this hook won't run. private void CalculateCameraHook(nint unknownPtr) { if (disableMod) { calculateCameraHook!.Original(unknownPtr); return; } if (enableOffsetPerspective && !offsetPerspective) { offsetPerspective = true; } else if (!enableOffsetPerspective && offsetPerspective) { offsetPerspective = false; } if (pCamera != null) { // previousFov is the pre-offset FOV value. previousFov = pCamera.FieldOfView; } applyPerspective = offsetPerspective && !freeCamera && pCamera != null; if (applyPerspective) { applyPerspective &= (previousCameraAnimState != 5 || ignoreAnimState); applyPerspective &= !assumeInQuestBoard(previousFov); } if (applyPerspective && !debugButton) { // Applying Y offset here keeps crosshair UI centered, but affects the angle of the camera. pCamera!.Position.Y += cameraYOffset; if (cameraFov != DEFAULT_FOV) { pCamera!.FieldOfView = (float)((cameraFov * previousFov) / DEFAULT_FOV); } } calculateCameraHook!.Original(unknownPtr); // Setting the perspective at this point (after Original()), allows the offset to not // negatively affect the right stick camera movement. If set earlier, camera movement // would be too snappy and incorrectly smoothed. If set too much later, it may not // be considered by lower-level functions like culling. if (applyPerspective) { setPerspective(pCamera!); } if (vCamera != null) { if (!freeCamera) { // We need pitch and yaw to apply cameraRoll and avoid a jump if applyPerspective = false. // @TODO: These values could just be read from the in-game camera. Quaternion forward = Quaternion.Normalize(getForward(vCamera.Position, vCamera.Target)); cameraYaw = Double.RadiansToDegrees(Math.Atan2(forward.Z, forward.X)); cameraPitch = Double.RadiansToDegrees(Math.Asin(forward.Y)); if (enableFreeCamera) { freeCamera = true; cameraPosition = vCamera.Position; cameraTarget = vCamera.Target; preDetachFov = cameraFov; preDetachRoll = cameraRoll; // The value of cameraFov is effectively a new default and scales accordingly // based on the in-game FOV. Take the calculated FOV here because that's what is // actually shown, which avoids a jump when toggling freecam. This is also why // we don't enable freecam until after applying a offset on this update. cameraFov = vCamera.FieldOfView; } } // No else if because we wan't to updateFreecam() if freeCamera was enabled above. if (freeCamera) { if (!cameraSetFromAlt) { updateFreecam(vCamera); } cameraSetFromAlt = false; if (!enableFreeCamera) { disableFreeCamera(); } } } } private void CameraUpHook(nint unknownPtr) { cameraUpHook!.Original(unknownPtr); if (disableMod) return; // This is still runs before culling. Checking Camera.Fix for tent idle, at least. if ((applyPerspective || freeCamera) && vCamera != null && vCamera.Fix) { setCameraRoll(vCamera); } if (applyPerspective) { setCameraRoll(pCamera!); } } private float CheckMovementHook(int stickValue) { if (!disableMod && freeCamera && !(unlockMovementHeld || unlockMovementToggled)) { stickValue = 0; } return checkMovementHook!.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 disableMod)) { if (disableMod) { disableAllHooks(); if (freeCamera) { disableFreeCamera(); } 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; } } config.DisableMod = disableMod; ConfigManager.SaveConfig(this); } if (ImGui.BeginItemTooltip()) { ImGui.Text("Try to disable as much of the mod as possible."); ImGui.EndTooltip(); } if (disableMod) return; ImGui.Separator(); ImGui.Checkbox("Enable Free Camera", ref enableFreeCamera); if (enableFreeCamera) { ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true); ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); } if (ImGui.Checkbox("Enable Perspective Camera", ref enableOffsetPerspective)) { config.PerspectiveCameraEnabled = enableOffsetPerspective; ConfigManager.SaveConfig(this); } if (enableFreeCamera) { ImGui.PopItemFlag(); ImGui.PopStyleVar(); } ImGui.Separator(); 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.25f); ImGui.DragFloat("Right", ref cameraRight, 0.25f); ImGui.DragFloat("Up", ref cameraYOffset, 0.025f); float roll = (float)cameraRoll; if (ImGui.DragFloat("Roll", ref roll, 0.25f)) { cameraRoll = roll; if (preDetachRoll != null) { preDetachRoll = roll; } } if (ImGui.Button("Default")) { cameraFov = DEFAULT_FOV; cameraForward = 0.0f; cameraRight = 0.0f; cameraXOffset = 0.0f; cameraYOffset = 0.0f; cameraZOffset = 0.0f; cameraRoll = 0.0f; config.Selected = ""; ConfigManager.SaveConfig(this); } ImGui.SameLine(); ImGui.PushItemWidth(width * 0.15f); ImGui.InputText("##Preset Name", ref typedPresetName, 99); ImGui.SameLine(); if (ImGui.Button("Save")) { string name = typedPresetName; if (name != "") { Config.Preset preset = new Config.Preset(); preset.FOV = cameraFov; preset.Forward = cameraForward; preset.Right = cameraRight; 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.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; cameraRight = preset.Right; cameraYOffset = preset.Up; cameraRoll = preset.Roll; } if (isSelected) ImGui.SetItemDefaultFocus(); } ImGui.EndCombo(); } ImGui.PopItemWidth(); ImGui.SameLine(); if (ImGui.Button("Delete")) { if (config.Selected != "") { config.Presets.Remove(config.Selected); config.Selected = ""; ConfigManager.SaveConfig(this); } } 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(); } float pitchLimit = (float)cameraPitchLimit; if (ImGui.InputFloat("Pitch Limit", ref pitchLimit, 0.0f, 0.0f, null, ImGuiInputTextFlags.EnterReturnsTrue)) { cameraPitchLimit = pitchLimit; if (cameraPitchLimit != -1.0f) { cameraPitchLimit = Math.Clamp(cameraPitchLimit, 0.0f, 89.95f); } } if (ImGui.BeginItemTooltip()) { ImGui.Text("-1.0 = Wrap around (go upside down)."); 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; cameraPitchLimit = Config.Settings.DEFAULT_PITCH_LIMIT; stickDeadzone = Config.Settings.DEFAULT_DEADZONE; } ImGui.SameLine(); if (ImGui.Button("Reset")) { Config.Settings settings = config.CameraSettings; cameraSpeed = settings.CameraSpeed; cameraZoomSpeed = settings.CameraZoomSpeed; cameraSpeedModifier = settings.CameraSpeedModifier; cameraSensitivity = settings.CameraSensitivity; cameraPitchLimit = settings.CameraPitchLimit; stickDeadzone = settings.StickDeadzone; } ImGui.SameLine(); if (ImGui.Button("Save")) { Config.Settings settings = config.CameraSettings; settings.CameraSpeed = cameraSpeed; settings.CameraZoomSpeed = cameraZoomSpeed; settings.CameraSpeedModifier = cameraSpeedModifier; settings.CameraSensitivity = cameraSensitivity; settings.CameraPitchLimit = cameraPitchLimit; settings.StickDeadzone = stickDeadzone; config.CameraSettings = settings; ConfigManager.SaveConfig(this); } ImGui.PopID(); ImGui.PushID("Binds"); if (ImGui.CollapsingHeader("Binds")) { if (ImGui.Checkbox("Toggle Free Camera", ref enableCombo)) { config.EnableCombo = enableCombo; ConfigManager.SaveConfig(this); } if (ImGui.BeginItemTooltip()) { ImGui.Text("Hold the first button and press the second."); ImGui.EndTooltip(); } ImGui.SameLine(); if (freeCameraCombo == null) { ImGui.PushStyleColor(ImGuiCol.Text, 0xff0000ff); } ImGui.PushItemWidth(width * 0.15f); ImGui.InputText("##Toggle Free Camera", ref typedCombo, 12); if (freeCameraCombo == null) { ImGui.PopStyleColor(); } if (ImGui.BeginItemTooltip()) { ImGui.Text("Form: \"RT+LB\", \"R3+L2\", \"A+LStick\", \"Up+RT\", \"Cross+Triangle\", etc."); ImGui.EndTooltip(); } ImGui.PopItemWidth(); ImGui.SameLine(); if (ImGui.Button("Save")) { string comboString = typedCombo.Replace("+", ","); freeCameraCombo = Config.ParseCombo(comboString); if (freeCameraCombo != null) { config.FreeCameraCombo = comboString; ConfigManager.SaveConfig(this); } } } ImGui.PopID(); ImGui.PushID("Debug"); if (ImGui.CollapsingHeader("DEBUG/Extras")) { ImGui.Checkbox("Debug Button", ref debugButton); if (ImGui.BeginItemTooltip()) { ImGui.Text("Currently toggles how early camera Y position and FOV are set (off = earlier).\nIf you want to see why: go out, set a big \"Up\" offset, aim in your slinger and click this button on and off."); ImGui.EndTooltip(); } ImGui.PushID("Camera"); if (pCamera != null) { ImGui.Text($"Player Camera: {pCamera.Instance:x}"); ImGui.Text($"Applying Offset: {applyPerspective}"); ImGui.Text($"previousCameraAnimState: {previousCameraAnimState}"); } ImGui.Separator(); ImGui.Text("Camera/Viewport:"); for (int i = 0; i < 8; i++) { ImGui.PushID($"Viewport{i}"); Viewport vp = CameraSystem.GetViewport(i); if (ImGui.CollapsingHeader($"Viewport #{i} ({(vp.Visible ? "visible" : "inactive")})")) { 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); if (camera == pCamera) { 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.PushItemWidth(width * 0.15f); ImGui.InputText("##Position Name", ref typedPositionName, 99); ImGui.SameLine(); if (ImGui.Button("Save")) { string name = typedPositionName; if (name != "") { Config.SavedPosition pos = new Config.SavedPosition(); pos.PosX = camera.Position.X; pos.PosY = camera.Position.Y; pos.PosZ = camera.Position.Z; pos.TargetX = camera.Target.X; pos.TargetY = camera.Target.Y; pos.TargetZ = camera.Target.Z; pos.Roll = cameraRoll; if (config.Positions.ContainsKey(name)) { config.Positions[name] = pos; } else { config.Positions.Add(name, pos); } ConfigManager.SaveConfig(this); } } ImGui.SameLine(); if (ImGui.BeginCombo("##Positions", "")) { Dictionary.KeyCollection positionKeys = config.Positions.Keys; for (int j = 0; j < positionKeys.Count; j++) { string positionKey = positionKeys.ElementAt(j); if (ImGui.Selectable(positionKey)) { Config.SavedPosition pos = config.Positions[positionKey]; selectedPositionName = positionKey; cameraPosition = new Vector3(pos.PosX, pos.PosY, pos.PosZ); cameraTarget = new Vector3(pos.TargetX, pos.TargetY, pos.TargetZ); Quaternion forward = Quaternion.Normalize(getForward(cameraPosition, cameraTarget)); cameraYaw = Double.RadiansToDegrees(Math.Atan2(forward.Z, forward.X)); cameraPitch = Double.RadiansToDegrees(Math.Asin(forward.Y)); camera.Position = cameraPosition; camera.Target = cameraTarget; cameraRoll = pos.Roll; } } ImGui.EndCombo(); } ImGui.PopItemWidth(); ImGui.SameLine(); if (ImGui.Button("Delete")) { if (selectedPositionName != "") { config.Positions.Remove(selectedPositionName); selectedPositionName = ""; ConfigManager.SaveConfig(this); } } if (camera == vCamera) { float yaw = (float)cameraYaw; if (ImGui.DragFloat("Yaw", ref yaw, 0.25f)) { cameraYaw = yaw; } float pitch = (float)cameraPitch; if (ImGui.DragFloat("Pitch", ref pitch, 0.25f)) { cameraPitch = pitch; } float offsetRoll = (float)cameraRoll; if (ImGui.DragFloat("Roll", ref offsetRoll, 0.25f)) { cameraRoll = offsetRoll; } if (camera == pCamera && !freeCamera) { ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().Alpha * 0.5f); } 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; cameraPosition = camera.Position; } float offsetRight = 0.0f; if (ImGui.DragFloat("Right", ref offsetRight, 0.45f)) { Quaternion forward = getForward(camera.Position, camera.Target); Quaternion right = forward * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(Single.DegreesToRadians(180.0f))); forward = Quaternion.Normalize(forward); right = Quaternion.Normalize(right); camera.Position.X += right.X * offsetRight; camera.Position.Z += right.Z * offsetRight; cameraPosition = camera.Position; } if (camera == pCamera && !freeCamera) { ImGui.PopStyleVar(); } } 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); Vector3 forward; forward.X = player.Forward.X; forward.Y = player.Forward.Y; forward.Z = player.Forward.Z; ImGui.DragFloat3("Forward", ref forward, 0.0f); 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; } 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} {currentActionInfo}, {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}"); */ bool move = player.Move; if (ImGui.Checkbox("Move", ref move)) { player.Move = move; } ImGui.SameLine(); bool fix = player.Fix; if (ImGui.Checkbox("Fix", ref fix)) { player.Fix = fix; } } ImGui.PopID(); ImGui.Separator(); ImGui.PushID("Palico"); ImGui.Text("Palico/Otomo:"); ImGui.Text($"Pointer: {sOtomo.Instance:x}"); ImGui.PopID(); ImGui.Separator(); ImGui.PushID("Pad"); ImGui.Text("Pad:"); ImGui.Text($"Pointer: {controllerAddr():x}"); int PadRx = MemoryUtil.Read(controllerAddr() + 0x1b0); int PadRy = MemoryUtil.Read(controllerAddr() + 0x1b4); int PadLx = MemoryUtil.Read(controllerAddr() + 0x1b8); int PadLy = MemoryUtil.Read(controllerAddr() + 0x1bc); byte PadRz = MemoryUtil.Read(controllerAddr() + 0x1c0); byte PadLz = MemoryUtil.Read(controllerAddr() + 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.DragFloat("+right", ref plusRight, 0.05f); ImGui.DragFloat("+forward", ref plusForward, 0.05f); ImGui.PopID(); } ImGui.PopID(); } private void checkUnlockMovement() { 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; } } } private void checkPlayerChange() { Player? player = Player.MainPlayer; if (player != lastPlayer) { pCamera = null; lastPlayer = player; } } public void OnUpdate(float deltaTime) { if (disableMod) return; // Consider that pCamera could become an invalid pointer. checkPlayerChange(); if (enableCombo && freeCameraCombo != null) { Button b1 = freeCameraCombo[0]; Button b2 = freeCameraCombo[1]; if ((!Input.IsPressed(b1) && Input.IsDown(b1)) && Input.IsPressed(b2)) { enableFreeCamera = !enableFreeCamera; } } if (freeCamera) { checkUnlockMovement(); } } } }