From 691d5a17233a030feecc337b62d7b02a8ac969d7 Mon Sep 17 00:00:00 2001
From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com>
Date: Mon, 24 Jun 2024 02:55:03 -0500
Subject: [PATCH] [IKSimulatedRootAngleFix] Initial release
---
.../IKSimulatedRootAngleFix.csproj | 2 +
IKSimulatedRootAngleFix/Main.cs | 131 ++++++++++++++++++
.../Properties/AssemblyInfo.cs | 32 +++++
IKSimulatedRootAngleFix/format.json | 23 +++
.../Integrations/ClappableLoadingHex.cs | 36 +++++
5 files changed, 224 insertions(+)
create mode 100644 IKSimulatedRootAngleFix/IKSimulatedRootAngleFix.csproj
create mode 100644 IKSimulatedRootAngleFix/Main.cs
create mode 100644 IKSimulatedRootAngleFix/Properties/AssemblyInfo.cs
create mode 100644 IKSimulatedRootAngleFix/format.json
create mode 100644 PropSpawnTweaks/Integrations/ClappableLoadingHex.cs
diff --git a/IKSimulatedRootAngleFix/IKSimulatedRootAngleFix.csproj b/IKSimulatedRootAngleFix/IKSimulatedRootAngleFix.csproj
new file mode 100644
index 0000000..e94f9dc
--- /dev/null
+++ b/IKSimulatedRootAngleFix/IKSimulatedRootAngleFix.csproj
@@ -0,0 +1,2 @@
+
+
diff --git a/IKSimulatedRootAngleFix/Main.cs b/IKSimulatedRootAngleFix/Main.cs
new file mode 100644
index 0000000..4c6a759
--- /dev/null
+++ b/IKSimulatedRootAngleFix/Main.cs
@@ -0,0 +1,131 @@
+using System.Reflection;
+using ABI_RC.Core.Player;
+using ABI_RC.Systems.IK;
+using ABI_RC.Systems.IK.VRIKHandlers;
+using ABI_RC.Systems.InputManagement;
+using ABI_RC.Systems.Movement;
+using HarmonyLib;
+using MelonLoader;
+using UnityEngine;
+
+namespace NAK.IKSimulatedRootAngleFix;
+
+public class IKSimulatedRootAngleFixMod : MelonMod
+{
+ public override void OnInitializeMelon()
+ {
+ HarmonyInstance.Patch( // fix offsetting of _ikSimulatedRootAngle when player rotates on wall or ceiling
+ typeof(IKHandler).GetMethod(nameof(IKHandler.OnPlayerHandleMovementParent),
+ BindingFlags.Public | BindingFlags.Instance),
+ prefix: new HarmonyMethod(typeof(IKSimulatedRootAngleFixMod).GetMethod(nameof(OnPlayerHandleMovementParent),
+ BindingFlags.NonPublic | BindingFlags.Static))
+ );
+
+ HarmonyInstance.Patch( // why did i dupe logic weirdly between Desktop & VR IKHandler ...
+ typeof(IKHandlerDesktop).GetMethod(nameof(IKHandlerDesktop.HandleBodyHeading),
+ BindingFlags.NonPublic | BindingFlags.Instance),
+ prefix: new HarmonyMethod(typeof(IKSimulatedRootAngleFixMod).GetMethod(nameof(OnHandleBodyHeading),
+ BindingFlags.NonPublic | BindingFlags.Static))
+ );
+
+ HarmonyInstance.Patch( // why did i dupe logic weirdly between Desktop & VR IKHandler ...
+ typeof(IKHandlerHalfBody).GetMethod(nameof(IKHandlerHalfBody.HandleRootAngle),
+ BindingFlags.NonPublic | BindingFlags.Instance),
+ prefix: new HarmonyMethod(typeof(IKSimulatedRootAngleFixMod).GetMethod(nameof(OnHandleRootAngle),
+ BindingFlags.NonPublic | BindingFlags.Static))
+ );
+ }
+
+ private static float GetRemappedPlayerHeading()
+ {
+ // invert, remap, then remap again so it matches playerlocal.eulerAngles.y that originally was used
+ // NOTE: we want to still use GetPlayerHeading because it accounts for gravity (and cause exterrata helped with that method specifically :3)
+ // NOTE: i am remapping it to match *original* DesktopVRIK logic before i reworked it native- dropping this remap into native method would not work
+ var playerHeading = (-IKSystem.Instance.GetPlayerHeading() + 180) % 360;
+ if (playerHeading < 0) playerHeading += 360;
+ return playerHeading;
+ }
+
+ private static bool OnPlayerHandleMovementParent(BetterBetterCharacterController.PlayerMoveOffset moveOffset, ref IKHandler __instance)
+ {
+ __instance._solver.AddPlatformMotion(moveOffset.DeltaPosition, moveOffset.DeltaRotation, moveOffset.PivotPosition);
+
+ Transform playerTransform = IKSystem.Instance.transform;
+ Vector3 up = playerTransform.up; // for simplicity, we will just use the current up, instead of calc past up
+
+ Quaternion playerRotation = playerTransform.rotation;
+ Quaternion deltaRotation = moveOffset.DeltaRotation;
+
+ // calculate the player's forward direction before the delta rotation was applied
+ Quaternion originalRotation = Quaternion.Inverse(deltaRotation) * playerRotation;
+
+ Vector3 forwardBeforeRotation = Vector3.ProjectOnPlane(originalRotation * Vector3.forward, up).normalized;
+ Vector3 forwardAfterRotation = Vector3.ProjectOnPlane(playerRotation * Vector3.forward, up).normalized;
+
+ // calculate the signed angle between the forward directions before and after the rotation around the up vector
+ float headingDelta = Vector3.SignedAngle(forwardBeforeRotation, forwardAfterRotation, up);
+ __instance._ikSimulatedRootAngle = Mathf.Repeat(__instance._ikSimulatedRootAngle + headingDelta, 360f);
+ return false;
+ }
+
+ private static bool OnHandleBodyHeading(ref IKHandlerDesktop __instance)
+ {
+ if (IKSystem.Instance.BodyHeadingLimit <= 0f)
+ return false;
+
+ float playerHeading = GetRemappedPlayerHeading();
+
+ // nicked original logic from DesktopVRIK, before i made it native and seemingly fucked it -_-
+ // https://github.com/NotAKidOnSteam/NAK_CVR_Mods/blob/db9d5a24b62c96e3c5c403ce3956cd3221955898/.DepricatedMods/DesktopVRIK/IK/IKHandlers/IKHandlerDesktop.cs#L68
+ var weightedAngleLimit = IKSystem.Instance.BodyHeadingLimit * __instance._solver.locomotion.weight;
+ var deltaAngleRoot = Mathf.DeltaAngle(playerHeading, __instance._ikSimulatedRootAngle);
+ var absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
+ if (absDeltaAngleRoot > weightedAngleLimit)
+ {
+ deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
+ __instance._ikSimulatedRootAngle = Mathf.MoveTowardsAngle(__instance._ikSimulatedRootAngle,
+ playerHeading, absDeltaAngleRoot - weightedAngleLimit);
+ }
+
+ __instance._solver.spine.rootHeadingOffset = deltaAngleRoot;
+
+ Vector3 axis = __instance._vrik.transform.rotation * Vector3.up;
+ if (IKSystem.Instance.PelvisHeadingWeight > 0f)
+ {
+ __instance._solver.spine.pelvisRotationOffset *= Quaternion.AngleAxis(
+ __instance._solver.spine.rootHeadingOffset * IKSystem.Instance.PelvisHeadingWeight, axis);
+ __instance._solver.spine.chestRotationOffset *= Quaternion.AngleAxis(
+ -__instance._solver.spine.rootHeadingOffset * IKSystem.Instance.PelvisHeadingWeight, axis);
+ }
+
+ if (IKSystem.Instance.ChestHeadingWeight > 0f)
+ __instance._solver.spine.chestRotationOffset *= Quaternion.AngleAxis(
+ __instance._solver.spine.rootHeadingOffset * IKSystem.Instance.ChestHeadingWeight, axis);
+
+ return false;
+ }
+
+ private static bool OnHandleRootAngle(ref IKHandlerHalfBody __instance)
+ {
+ float playerHeading = GetRemappedPlayerHeading();
+
+ var rootAngleLimit = 25f;
+ if (__instance._solver.spine.rotationWeight <= 0f || PlayerSetup.Instance.IsEmotePlaying())
+ rootAngleLimit = 180f;
+ else if (CVRInputManager.Instance.movementVector.sqrMagnitude > 0f) rootAngleLimit = 0f;
+
+ var weightedAngleLimit = rootAngleLimit * __instance._solver.locomotion.weight;
+ var deltaAngleRoot = Mathf.DeltaAngle(playerHeading, __instance._ikSimulatedRootAngle);
+ var absDeltaAngleRoot = Mathf.Abs(deltaAngleRoot);
+ if (absDeltaAngleRoot > weightedAngleLimit)
+ {
+ deltaAngleRoot = Mathf.Sign(deltaAngleRoot) * weightedAngleLimit;
+ __instance._ikSimulatedRootAngle = Mathf.MoveTowardsAngle(__instance._ikSimulatedRootAngle,
+ playerHeading, absDeltaAngleRoot - weightedAngleLimit);
+ }
+
+ __instance._solver.spine.maxRootAngle = rootAngleLimit;
+ __instance._solver.spine.rootHeadingOffset = deltaAngleRoot;
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/IKSimulatedRootAngleFix/Properties/AssemblyInfo.cs b/IKSimulatedRootAngleFix/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..1502161
--- /dev/null
+++ b/IKSimulatedRootAngleFix/Properties/AssemblyInfo.cs
@@ -0,0 +1,32 @@
+using MelonLoader;
+using NAK.IKSimulatedRootAngleFix.Properties;
+using System.Reflection;
+
+[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyTitle(nameof(NAK.IKSimulatedRootAngleFix))]
+[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
+[assembly: AssemblyProduct(nameof(NAK.IKSimulatedRootAngleFix))]
+
+[assembly: MelonInfo(
+ typeof(NAK.IKSimulatedRootAngleFix.IKSimulatedRootAngleFixMod),
+ nameof(NAK.IKSimulatedRootAngleFix),
+ AssemblyInfoParams.Version,
+ AssemblyInfoParams.Author,
+ downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKSimulatedRootAngleFix"
+)]
+
+[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
+[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
+[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
+[assembly: MelonColor(255, 246, 25, 99)] // red-pink
+[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
+[assembly: HarmonyDontPatchAll]
+
+namespace NAK.IKSimulatedRootAngleFix.Properties;
+internal static class AssemblyInfoParams
+{
+ public const string Version = "1.0.0";
+ public const string Author = "NotAKidoS";
+}
\ No newline at end of file
diff --git a/IKSimulatedRootAngleFix/format.json b/IKSimulatedRootAngleFix/format.json
new file mode 100644
index 0000000..88a0e52
--- /dev/null
+++ b/IKSimulatedRootAngleFix/format.json
@@ -0,0 +1,23 @@
+{
+ "_id": -1,
+ "name": "IKSimulatedRootAngleFix",
+ "modversion": "1.0.0",
+ "gameversion": "2024r175",
+ "loaderversion": "0.6.1",
+ "modtype": "Mod",
+ "author": "NotAKidoS",
+ "description": "Fixes a small issue with Desktop & HalfBody root angle being incorrectly calculated while on rotating Movement Parents. If you've ever noticed your body/feet insisting on facing opposite of the direction you are rotating, this fixes that.",
+ "searchtags": [
+ "ik",
+ "root",
+ "angle",
+ "burger"
+ ],
+ "requirements": [
+ "None"
+ ],
+ "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r34/IKSimulatedRootAngleFix.dll",
+ "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKSimulatedRootAngleFix/",
+ "changelog": "- Initial release",
+ "embedcolor": "#f61963"
+}
\ No newline at end of file
diff --git a/PropSpawnTweaks/Integrations/ClappableLoadingHex.cs b/PropSpawnTweaks/Integrations/ClappableLoadingHex.cs
new file mode 100644
index 0000000..7db3013
--- /dev/null
+++ b/PropSpawnTweaks/Integrations/ClappableLoadingHex.cs
@@ -0,0 +1,36 @@
+using NAK.PropSpawnTweaks.Components;
+using UnityEngine;
+
+namespace NAK.PropSpawnTweaks.Integrations;
+
+public static class TheClapperIntegration
+{
+ public static void Init()
+ {
+ PropSpawnTweaksMod.OnPropPlaceholderCreated += (placeholder) =>
+ {
+ if (placeholder.TryGetComponent(out PropLoadingHexagon loadingHexagon))
+ ClappableLoadingHex.Create(loadingHexagon);
+ };
+ }
+}
+
+public class ClappableLoadingHex : Kafe.TheClapper.Clappable
+{
+ [SerializeField] private PropLoadingHexagon _loadingHexagon;
+
+ public override void OnClapped(Vector3 clappablePosition)
+ {
+ if (_loadingHexagon == null) return;
+ _loadingHexagon.IsLoadingCanceled = true;
+ Kafe.TheClapper.TheClapper.EmitParticles(clappablePosition, new Color(1f, 1f, 0f), 2f);
+ }
+
+ public static void Create(PropLoadingHexagon loadingHexagon)
+ {
+ GameObject target = loadingHexagon.gameObject;
+ if (!target.gameObject.TryGetComponent(out ClappableLoadingHex clappableHexagon))
+ clappableHexagon = target.gameObject.AddComponent();
+ clappableHexagon._loadingHexagon = loadingHexagon;
+ }
+}
\ No newline at end of file