mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 06:19:22 +00:00
RCCVirtualSteeringWheel: renamed mod, fixed things
This commit is contained in:
parent
3a00ff104a
commit
5c8c724b58
13 changed files with 445 additions and 314 deletions
|
@ -1,203 +0,0 @@
|
||||||
using ABI_RC.Core.InteractionSystem;
|
|
||||||
using ABI_RC.Core.InteractionSystem.Base;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// Fix multi-grab (limitation of Pickupable)
|
|
||||||
// Think this can be fixed by forcing ungrab & monitoring ourselves for release
|
|
||||||
// Add configurable override for steering range
|
|
||||||
// Fix steering wheel resetting immediatly on release
|
|
||||||
// Fix input patch not being multiplicative (so joysticks can still work)
|
|
||||||
// Prevent pickup in Desktop
|
|
||||||
|
|
||||||
public class SteeringWheelPickup : Pickupable
|
|
||||||
{
|
|
||||||
private RCC_CarControllerV3 _carController;
|
|
||||||
|
|
||||||
private static readonly Dictionary<RCC_CarControllerV3, SteeringWheelPickup> ActiveWheels = new();
|
|
||||||
|
|
||||||
public static float GetSteerInput(RCC_CarControllerV3 carController)
|
|
||||||
{
|
|
||||||
if (ActiveWheels.TryGetValue(carController, out SteeringWheelPickup wheel) && wheel.IsPickedUp)
|
|
||||||
return wheel.GetNormalizedValue();
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetupSteeringWheel(RCC_CarControllerV3 carController)
|
|
||||||
{
|
|
||||||
_carController = carController;
|
|
||||||
if (!ActiveWheels.ContainsKey(carController))
|
|
||||||
{
|
|
||||||
ActiveWheels[carController] = this;
|
|
||||||
carController.useCounterSteering = false;
|
|
||||||
carController.useSteeringSmoother = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
|
||||||
{
|
|
||||||
if (_carController != null && ActiveWheels.ContainsKey(_carController))
|
|
||||||
ActiveWheels.Remove(_carController);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Configuration Properties Override
|
|
||||||
|
|
||||||
public override bool DisallowTheft => true;
|
|
||||||
public override float MaxGrabDistance => 0.8f;
|
|
||||||
public override float MaxPushDistance => 0f;
|
|
||||||
public override bool IsAutoHold => false;
|
|
||||||
public override bool IsObjectRotationAllowed => false;
|
|
||||||
public override bool IsObjectPushPullAllowed => false;
|
|
||||||
public override bool IsObjectUseAllowed => false;
|
|
||||||
|
|
||||||
public override bool CanPickup => IsPickupable && _carController?.SteeringWheel != null;
|
|
||||||
|
|
||||||
#endregion Configuration Properties Override
|
|
||||||
|
|
||||||
#region RCC Stuff
|
|
||||||
|
|
||||||
private float GetMaxSteeringRange()
|
|
||||||
=> _carController.steerAngle * Mathf.Abs(_carController.steeringWheelAngleMultiplier);
|
|
||||||
|
|
||||||
private float GetSteeringWheelSign()
|
|
||||||
=> Mathf.Sign(_carController.steeringWheelAngleMultiplier) * -1f; // Idk
|
|
||||||
|
|
||||||
private Vector3 GetSteeringWheelLocalAxis()
|
|
||||||
{
|
|
||||||
return _carController.steeringWheelRotateAround switch
|
|
||||||
{
|
|
||||||
RCC_CarControllerV3.SteeringWheelRotateAround.XAxis => Vector3.right,
|
|
||||||
RCC_CarControllerV3.SteeringWheelRotateAround.YAxis => Vector3.up,
|
|
||||||
RCC_CarControllerV3.SteeringWheelRotateAround.ZAxis => Vector3.forward,
|
|
||||||
_ => Vector3.forward
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion RCC Stuff
|
|
||||||
|
|
||||||
#region Rotation Tracking
|
|
||||||
|
|
||||||
private readonly List<Transform> _trackedTransforms = new();
|
|
||||||
private readonly List<Vector3> _lastPositions = new();
|
|
||||||
private readonly List<float> _totalAngles = new();
|
|
||||||
private bool _isTracking;
|
|
||||||
private float _averageAngle;
|
|
||||||
|
|
||||||
private void StartTrackingTransform(Transform trans)
|
|
||||||
{
|
|
||||||
if (trans == null) return;
|
|
||||||
|
|
||||||
_trackedTransforms.Add(trans);
|
|
||||||
_lastPositions.Add(GetLocalPositionWithoutRotation(transform.position));
|
|
||||||
_totalAngles.Add(0f);
|
|
||||||
_isTracking = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopTrackingTransform(Transform trans)
|
|
||||||
{
|
|
||||||
int index = _trackedTransforms.IndexOf(trans);
|
|
||||||
if (index != -1)
|
|
||||||
{
|
|
||||||
_trackedTransforms.RemoveAt(index);
|
|
||||||
_lastPositions.RemoveAt(index);
|
|
||||||
_totalAngles.RemoveAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
_isTracking = _trackedTransforms.Count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateRotationTracking()
|
|
||||||
{
|
|
||||||
if (!_isTracking || _trackedTransforms.Count == 0) return;
|
|
||||||
|
|
||||||
Vector3 trackingAxis = GetSteeringWheelLocalAxis();
|
|
||||||
|
|
||||||
for (int i = 0; i < _trackedTransforms.Count; i++)
|
|
||||||
{
|
|
||||||
if (_trackedTransforms[i] == null) continue;
|
|
||||||
|
|
||||||
Vector3 currentPosition = GetLocalPositionWithoutRotation(_trackedTransforms[i].position);
|
|
||||||
if (currentPosition == _lastPositions[i]) continue;
|
|
||||||
|
|
||||||
Vector3 previousVector = _lastPositions[i];
|
|
||||||
Vector3 currentVector = currentPosition;
|
|
||||||
|
|
||||||
previousVector = Vector3.ProjectOnPlane(previousVector, trackingAxis).normalized;
|
|
||||||
currentVector = Vector3.ProjectOnPlane(currentVector, trackingAxis).normalized;
|
|
||||||
|
|
||||||
if (previousVector.sqrMagnitude > 0.001f && currentVector.sqrMagnitude > 0.001f)
|
|
||||||
{
|
|
||||||
float deltaAngle = Vector3.SignedAngle(previousVector, currentVector, trackingAxis);
|
|
||||||
if (Mathf.Abs(deltaAngle) < 90f) _totalAngles[i] += deltaAngle; // Prevent big tracking jumps
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastPositions[i] = currentPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate average every frame using only valid transforms
|
|
||||||
float sumAngles = 0f;
|
|
||||||
int validTransforms = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < _trackedTransforms.Count; i++)
|
|
||||||
{
|
|
||||||
if (_trackedTransforms[i] == null) continue;
|
|
||||||
sumAngles += _totalAngles[i];
|
|
||||||
validTransforms++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validTransforms > 0)
|
|
||||||
_averageAngle = sumAngles / validTransforms;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float GetNormalizedValue()
|
|
||||||
{
|
|
||||||
float maxRange = GetMaxSteeringRange();
|
|
||||||
// return Mathf.Clamp(_averageAngle / (maxRange * 0.5f), -1f, 1f);
|
|
||||||
return Mathf.Clamp(_averageAngle / (maxRange), -1f, 1f) * GetSteeringWheelSign();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector3 GetLocalPositionWithoutRotation(Vector3 worldPosition)
|
|
||||||
{
|
|
||||||
Transform steeringTransform = _carController.SteeringWheel;
|
|
||||||
|
|
||||||
Quaternion localRotation = steeringTransform.localRotation;
|
|
||||||
steeringTransform.localRotation = _carController.orgSteeringWheelRot;
|
|
||||||
|
|
||||||
Vector3 localPosition = steeringTransform.InverseTransformPoint(worldPosition);
|
|
||||||
steeringTransform.localRotation = localRotation;
|
|
||||||
|
|
||||||
return localPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Rotation Tracking
|
|
||||||
|
|
||||||
public override void OnGrab(InteractionContext context, Vector3 grabPoint)
|
|
||||||
{
|
|
||||||
if (ControllerRay?.pivotPoint == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
StartTrackingTransform(ControllerRay.transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnDrop(InteractionContext context)
|
|
||||||
{
|
|
||||||
if (ControllerRay?.transform != null)
|
|
||||||
StopTrackingTransform(ControllerRay.transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update()
|
|
||||||
{
|
|
||||||
if (!IsPickedUp || ControllerRay?.pivotPoint == null || _carController == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
UpdateRotationTracking();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Unused Abstract Method Implementations
|
|
||||||
|
|
||||||
public override void OnUseDown(InteractionContext context) { }
|
|
||||||
public override void OnUseUp(InteractionContext context) { }
|
|
||||||
public override void OnFlingTowardsTarget(Vector3 target) { }
|
|
||||||
|
|
||||||
#endregion Unused Abstract Method Implementations
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
using MelonLoader;
|
|
||||||
using NAK.GrabbableSteeringWheel.Patches;
|
|
||||||
|
|
||||||
namespace NAK.GrabbableSteeringWheel;
|
|
||||||
|
|
||||||
public class GrabbableSteeringWheelMod : MelonMod
|
|
||||||
{
|
|
||||||
internal static MelonLogger.Instance Logger;
|
|
||||||
|
|
||||||
#region Melon Preferences
|
|
||||||
|
|
||||||
// private static readonly MelonPreferences_Category Category =
|
|
||||||
// MelonPreferences.CreateCategory(nameof(GrabbableSteeringWheelMod));
|
|
||||||
//
|
|
||||||
// private static readonly MelonPreferences_Entry<bool> EntryEnabled =
|
|
||||||
// Category.CreateEntry(
|
|
||||||
// "use_legacy_mitigation",
|
|
||||||
// true,
|
|
||||||
// "Enabled",
|
|
||||||
// description: "Enable legacy content camera hack when in Legacy worlds.");
|
|
||||||
|
|
||||||
#endregion Melon Preferences
|
|
||||||
|
|
||||||
#region Melon Events
|
|
||||||
|
|
||||||
public override void OnInitializeMelon()
|
|
||||||
{
|
|
||||||
Logger = LoggerInstance;
|
|
||||||
|
|
||||||
ApplyPatches(typeof(RCCCarControllerV3_Patches));
|
|
||||||
ApplyPatches(typeof(CVRInputManager_Patches));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Melon Events
|
|
||||||
|
|
||||||
#region Melon Mod Utilities
|
|
||||||
|
|
||||||
private static void InitializeIntegration(string modName, Action integrationAction)
|
|
||||||
{
|
|
||||||
if (RegisteredMelons.All(it => it.Info.Name != modName))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Logger.Msg($"Initializing {modName} integration.");
|
|
||||||
integrationAction.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyPatches(Type type)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HarmonyInstance.PatchAll(type);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
|
||||||
LoggerInstance.Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion Melon Mod Utilities
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
using MelonLoader;
|
|
||||||
|
|
||||||
namespace NAK.GrabbableSteeringWheel;
|
|
||||||
|
|
||||||
internal static class ModSettings
|
|
||||||
{
|
|
||||||
#region Constants
|
|
||||||
|
|
||||||
internal const string ModName = nameof(GrabbableSteeringWheel);
|
|
||||||
// internal const string LCM_SettingsCategory = "Legacy Content Mitigation";
|
|
||||||
|
|
||||||
#endregion Constants
|
|
||||||
|
|
||||||
#region Melon Preferences
|
|
||||||
|
|
||||||
private static readonly MelonPreferences_Category Category =
|
|
||||||
MelonPreferences.CreateCategory(ModName);
|
|
||||||
|
|
||||||
// internal static readonly MelonPreferences_Entry<bool> EntryAutoForLegacyWorlds =
|
|
||||||
// Category.CreateEntry("auto_for_legacy_worlds", true,
|
|
||||||
// "Auto For Legacy Worlds", description: "Should Legacy View be auto enabled for detected Legacy worlds?");
|
|
||||||
//
|
|
||||||
#endregion Melon Preferences
|
|
||||||
}
|
|
40
RCCVirtualSteeringWheel/Main.cs
Normal file
40
RCCVirtualSteeringWheel/Main.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using MelonLoader;
|
||||||
|
using NAK.RCCVirtualSteeringWheel.Patches;
|
||||||
|
|
||||||
|
namespace NAK.RCCVirtualSteeringWheel;
|
||||||
|
|
||||||
|
public class RCCVirtualSteeringWheelMod : MelonMod
|
||||||
|
{
|
||||||
|
private static MelonLogger.Instance Logger;
|
||||||
|
|
||||||
|
#region Melon Events
|
||||||
|
|
||||||
|
public override void OnInitializeMelon()
|
||||||
|
{
|
||||||
|
Logger = LoggerInstance;
|
||||||
|
|
||||||
|
ApplyPatches(typeof(RCCCarControllerV3_Patches));
|
||||||
|
ApplyPatches(typeof(CVRInputManager_Patches));
|
||||||
|
|
||||||
|
Logger.Msg(ModSettings.EntryCustomSteeringRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Melon Events
|
||||||
|
|
||||||
|
#region Melon Mod Utilities
|
||||||
|
|
||||||
|
private void ApplyPatches(Type type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HarmonyInstance.PatchAll(type);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||||
|
LoggerInstance.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Melon Mod Utilities
|
||||||
|
}
|
31
RCCVirtualSteeringWheel/ModSettings.cs
Normal file
31
RCCVirtualSteeringWheel/ModSettings.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using MelonLoader;
|
||||||
|
|
||||||
|
namespace NAK.RCCVirtualSteeringWheel;
|
||||||
|
|
||||||
|
internal static class ModSettings
|
||||||
|
{
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
private const string ModName = nameof(RCCVirtualSteeringWheel);
|
||||||
|
|
||||||
|
#endregion Constants
|
||||||
|
|
||||||
|
#region Melon Preferences
|
||||||
|
|
||||||
|
private static readonly MelonPreferences_Category Category =
|
||||||
|
MelonPreferences.CreateCategory(ModName);
|
||||||
|
|
||||||
|
internal static readonly MelonPreferences_Entry<bool> EntryOverrideSteeringRange =
|
||||||
|
Category.CreateEntry("override_steering_range", false,
|
||||||
|
"Override Steering Range", description: "Should the steering wheel use a custom steering range instead of the vehicle's default?");
|
||||||
|
|
||||||
|
internal static readonly MelonPreferences_Entry<float> EntryCustomSteeringRange =
|
||||||
|
Category.CreateEntry("custom_steering_range", 60f,
|
||||||
|
"Custom Steering Range", description: "The custom steering range in degrees when override is enabled (default: 60)");
|
||||||
|
|
||||||
|
internal static readonly MelonPreferences_Entry<bool> EntryInvertSteering =
|
||||||
|
Category.CreateEntry("invert_steering", false,
|
||||||
|
"Invert Steering", description: "Inverts the steering direction");
|
||||||
|
|
||||||
|
#endregion Melon Preferences
|
||||||
|
}
|
|
@ -1,21 +1,21 @@
|
||||||
using ABI_RC.Systems.InputManagement;
|
using ABI_RC.Systems.InputManagement;
|
||||||
using ABI_RC.Systems.Movement;
|
using ABI_RC.Systems.Movement;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using NAK.RCCVirtualSteeringWheel.Util;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace NAK.GrabbableSteeringWheel.Patches;
|
namespace NAK.RCCVirtualSteeringWheel.Patches;
|
||||||
|
|
||||||
internal static class RCCCarControllerV3_Patches
|
internal static class RCCCarControllerV3_Patches
|
||||||
{
|
{
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(typeof(RCC_CarControllerV3), nameof(RCC_CarControllerV3.Awake))]
|
[HarmonyPatch(typeof(RCC_CarControllerV3), nameof(RCC_CarControllerV3.Awake))]
|
||||||
private static void Postfix_RCC_CarControllerV3_Awake(ref RCC_CarControllerV3 __instance)
|
private static void Postfix_RCC_CarControllerV3_Awake(RCC_CarControllerV3 __instance)
|
||||||
{
|
{
|
||||||
Transform steeringWheelTransform = __instance.SteeringWheel;
|
Transform steeringWheelTransform = __instance.SteeringWheel;
|
||||||
if (steeringWheelTransform == null)
|
if (steeringWheelTransform == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RCC_CarControllerV3 v3 = __instance;
|
|
||||||
BoneVertexBoundsUtility.CalculateBoneWeightedBounds(
|
BoneVertexBoundsUtility.CalculateBoneWeightedBounds(
|
||||||
steeringWheelTransform,
|
steeringWheelTransform,
|
||||||
0.8f,
|
0.8f,
|
||||||
|
@ -25,12 +25,7 @@ internal static class RCCCarControllerV3_Patches
|
||||||
if (!result.IsValid)
|
if (!result.IsValid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
BoxCollider boxCollider = steeringWheelTransform.gameObject.AddComponent<BoxCollider>();
|
SteeringWheelRoot.SetupSteeringWheel(__instance, result.LocalBounds);
|
||||||
boxCollider.center = result.LocalBounds.center;
|
|
||||||
boxCollider.size = result.LocalBounds.size;
|
|
||||||
|
|
||||||
SteeringWheelPickup steeringWheel = steeringWheelTransform.gameObject.AddComponent<SteeringWheelPickup>();
|
|
||||||
steeringWheel.SetupSteeringWheel(v3);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +37,11 @@ internal static class CVRInputManager_Patches
|
||||||
private static void Postfix_CVRInputManager_UpdateInput(ref CVRInputManager __instance)
|
private static void Postfix_CVRInputManager_UpdateInput(ref CVRInputManager __instance)
|
||||||
{
|
{
|
||||||
// Steering input is clamped in RCC component
|
// Steering input is clamped in RCC component
|
||||||
if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat())
|
if (BetterBetterCharacterController.Instance.IsSittingOnControlSeat()
|
||||||
__instance.steering += SteeringWheelPickup.GetSteerInput(
|
&& SteeringWheelRoot.TryGetWheelInput(
|
||||||
BetterBetterCharacterController.Instance._lastCvrSeat._carController);
|
BetterBetterCharacterController.Instance._lastCvrSeat._carController, out float steeringValue))
|
||||||
|
{
|
||||||
|
__instance.steering = steeringValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,21 +1,21 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using NAK.GrabbableSteeringWheel;
|
using NAK.RCCVirtualSteeringWheel;
|
||||||
using NAK.GrabbableSteeringWheel.Properties;
|
using NAK.RCCVirtualSteeringWheel.Properties;
|
||||||
|
|
||||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||||
[assembly: AssemblyTitle(nameof(NAK.GrabbableSteeringWheel))]
|
[assembly: AssemblyTitle(nameof(NAK.RCCVirtualSteeringWheel))]
|
||||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||||
[assembly: AssemblyProduct(nameof(NAK.GrabbableSteeringWheel))]
|
[assembly: AssemblyProduct(nameof(NAK.RCCVirtualSteeringWheel))]
|
||||||
|
|
||||||
[assembly: MelonInfo(
|
[assembly: MelonInfo(
|
||||||
typeof(GrabbableSteeringWheelMod),
|
typeof(RCCVirtualSteeringWheelMod),
|
||||||
nameof(NAK.GrabbableSteeringWheel),
|
nameof(NAK.RCCVirtualSteeringWheel),
|
||||||
AssemblyInfoParams.Version,
|
AssemblyInfoParams.Version,
|
||||||
AssemblyInfoParams.Author,
|
AssemblyInfoParams.Author,
|
||||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/GrabbableSteeringWheel"
|
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||||
|
@ -25,7 +25,7 @@ using NAK.GrabbableSteeringWheel.Properties;
|
||||||
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||||
[assembly: HarmonyDontPatchAll]
|
[assembly: HarmonyDontPatchAll]
|
||||||
|
|
||||||
namespace NAK.GrabbableSteeringWheel.Properties;
|
namespace NAK.RCCVirtualSteeringWheel.Properties;
|
||||||
internal static class AssemblyInfoParams
|
internal static class AssemblyInfoParams
|
||||||
{
|
{
|
||||||
public const string Version = "1.0.0";
|
public const string Version = "1.0.0";
|
|
@ -2,6 +2,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net48</TargetFramework>
|
<TargetFramework>net48</TargetFramework>
|
||||||
|
<RootNamespace>GrabbableSteeringWheel</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="BTKUILib">
|
<Reference Include="BTKUILib">
|
||||||
|
@ -11,7 +12,4 @@
|
||||||
<HintPath>..\.ManagedLibs\TheClapper.dll</HintPath>
|
<HintPath>..\.ManagedLibs\TheClapper.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Utility\" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
|
@ -0,0 +1,36 @@
|
||||||
|
using ABI_RC.Core.InteractionSystem;
|
||||||
|
using ABI_RC.Core.InteractionSystem.Base;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.RCCVirtualSteeringWheel;
|
||||||
|
|
||||||
|
public class SteeringWheelPickup : Pickupable
|
||||||
|
{
|
||||||
|
#region Public Properties
|
||||||
|
public override bool DisallowTheft => true;
|
||||||
|
public override float MaxGrabDistance => 0.8f;
|
||||||
|
public override float MaxPushDistance => 0f;
|
||||||
|
public override bool IsAutoHold => false;
|
||||||
|
public override bool IsObjectRotationAllowed => false;
|
||||||
|
public override bool IsObjectPushPullAllowed => false;
|
||||||
|
public override bool IsObjectUseAllowed => false;
|
||||||
|
public override bool CanPickup => IsPickupable && !IsPickedUp;
|
||||||
|
internal SteeringWheelRoot root;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
public override void OnUseDown(InteractionContext context) { }
|
||||||
|
public override void OnUseUp(InteractionContext context) { }
|
||||||
|
public override void OnFlingTowardsTarget(Vector3 target) { }
|
||||||
|
|
||||||
|
public override void OnGrab(InteractionContext context, Vector3 grabPoint) {
|
||||||
|
if (ControllerRay?.pivotPoint != null)
|
||||||
|
root.StartTrackingTransform(ControllerRay.transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDrop(InteractionContext context) {
|
||||||
|
if (ControllerRay?.transform != null)
|
||||||
|
root.StopTrackingTransform(ControllerRay.transform);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
|
@ -0,0 +1,313 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.RCCVirtualSteeringWheel;
|
||||||
|
|
||||||
|
public class SteeringWheelRoot : MonoBehaviour
|
||||||
|
{
|
||||||
|
#region Static Variables
|
||||||
|
|
||||||
|
private static readonly Dictionary<RCC_CarControllerV3, SteeringWheelRoot> ActiveWheels = new();
|
||||||
|
|
||||||
|
#endregion Static Variables
|
||||||
|
|
||||||
|
#region Static Methods
|
||||||
|
|
||||||
|
public static bool TryGetWheelInput(RCC_CarControllerV3 carController, out float steeringInput)
|
||||||
|
{
|
||||||
|
if (ActiveWheels.TryGetValue(carController, out SteeringWheelRoot wheel) && wheel._averageAngle != 0f)
|
||||||
|
{
|
||||||
|
steeringInput = wheel.GetNormalizedValue();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
steeringInput = 0f;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetupSteeringWheel(RCC_CarControllerV3 carController, Bounds steeringWheelBounds)
|
||||||
|
{
|
||||||
|
Transform steeringWheel = carController.SteeringWheel;
|
||||||
|
if (carController == null) return;
|
||||||
|
|
||||||
|
SteeringWheelRoot wheel = steeringWheel.gameObject.AddComponent<SteeringWheelRoot>();
|
||||||
|
wheel._carController = carController;
|
||||||
|
|
||||||
|
Array.Resize(ref wheel._pickups, 2);
|
||||||
|
CreatePickup(out wheel._pickups[0]);
|
||||||
|
CreatePickup(out wheel._pickups[1]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
void CreatePickup(out SteeringWheelPickup wheelPickup)
|
||||||
|
{
|
||||||
|
GameObject pickup = new()
|
||||||
|
{
|
||||||
|
transform =
|
||||||
|
{
|
||||||
|
parent = steeringWheel.transform,
|
||||||
|
localPosition = Vector3.zero,
|
||||||
|
localRotation = Quaternion.identity,
|
||||||
|
localScale = Vector3.one
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BoxCollider collider = pickup.AddComponent<BoxCollider>();
|
||||||
|
collider.size = steeringWheelBounds.size;
|
||||||
|
collider.center = steeringWheelBounds.center;
|
||||||
|
|
||||||
|
wheelPickup = pickup.AddComponent<SteeringWheelPickup>();
|
||||||
|
wheelPickup.root = wheel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Static Methods
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
private bool IsWheelBeingHeld => _pickups[0].IsPickedUp || _pickups[1].IsPickedUp;
|
||||||
|
|
||||||
|
#endregion Public Properties
|
||||||
|
|
||||||
|
#region Private Variables
|
||||||
|
|
||||||
|
private RCC_CarControllerV3 _carController;
|
||||||
|
private SteeringWheelPickup[] _pickups;
|
||||||
|
|
||||||
|
private float _originalSteeringWheelAngleMultiplier;
|
||||||
|
private float _originalSteeringWheelSign;
|
||||||
|
|
||||||
|
private readonly List<Transform> _trackedTransforms = new();
|
||||||
|
private readonly List<Vector3> _lastPositions = new();
|
||||||
|
private readonly List<float> _totalAngles = new();
|
||||||
|
|
||||||
|
private bool _isTracking;
|
||||||
|
private float _averageAngle;
|
||||||
|
private float _timeWheelReleased = -1f;
|
||||||
|
private const float RETURN_TO_CENTER_DURATION = 2f;
|
||||||
|
|
||||||
|
#endregion Private Variables
|
||||||
|
|
||||||
|
#region Unity Events
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
ActiveWheels.TryAdd(_carController, this);
|
||||||
|
InitializeWheel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (_carController == null) return;
|
||||||
|
UpdateWheelState();
|
||||||
|
UpdateSteeringBehavior();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
ActiveWheels.Remove(_carController);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Unity Events
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
internal void StartTrackingTransform(Transform trans)
|
||||||
|
{
|
||||||
|
if (trans == null) return;
|
||||||
|
|
||||||
|
var currentAngle = 0f;
|
||||||
|
if (_isTracking)
|
||||||
|
{
|
||||||
|
var sum = 0f;
|
||||||
|
var validTransforms = 0;
|
||||||
|
for (var i = 0; i < _trackedTransforms.Count; i++)
|
||||||
|
if (_trackedTransforms[i] != null)
|
||||||
|
{
|
||||||
|
sum += _totalAngles[i];
|
||||||
|
validTransforms++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validTransforms > 0)
|
||||||
|
currentAngle = sum / validTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
_trackedTransforms.Add(trans);
|
||||||
|
_lastPositions.Add(GetLocalPositionWithoutRotation(transform.position));
|
||||||
|
_totalAngles.Add(currentAngle);
|
||||||
|
_isTracking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void StopTrackingTransform(Transform trans)
|
||||||
|
{
|
||||||
|
var index = _trackedTransforms.IndexOf(trans);
|
||||||
|
if (index == -1) return;
|
||||||
|
|
||||||
|
var currentAverage = CalculateCurrentAverage();
|
||||||
|
_trackedTransforms.RemoveAt(index);
|
||||||
|
_lastPositions.RemoveAt(index);
|
||||||
|
_totalAngles.RemoveAt(index);
|
||||||
|
|
||||||
|
if (_trackedTransforms.Count <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 0; i < _totalAngles.Count; i++)
|
||||||
|
_totalAngles[i] = currentAverage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float CalculateCurrentAverage()
|
||||||
|
{
|
||||||
|
var sum = 0f;
|
||||||
|
var validTransforms = 0;
|
||||||
|
for (var i = 0; i < _trackedTransforms.Count; i++)
|
||||||
|
if (_trackedTransforms[i] != null)
|
||||||
|
{
|
||||||
|
sum += _totalAngles[i];
|
||||||
|
validTransforms++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return validTransforms > 0 ? sum / validTransforms : 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public Methods
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private void InitializeWheel()
|
||||||
|
{
|
||||||
|
_originalSteeringWheelAngleMultiplier = _carController.steeringWheelAngleMultiplier;
|
||||||
|
_originalSteeringWheelSign = Mathf.Sign(_originalSteeringWheelAngleMultiplier);
|
||||||
|
_carController.useCounterSteering = false;
|
||||||
|
_carController.useSteeringSmoother = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWheelState()
|
||||||
|
{
|
||||||
|
var isHeld = IsWheelBeingHeld;
|
||||||
|
if (!isHeld && _timeWheelReleased < 0f)
|
||||||
|
_timeWheelReleased = Time.time;
|
||||||
|
else if (isHeld)
|
||||||
|
_timeWheelReleased = -1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSteeringBehavior()
|
||||||
|
{
|
||||||
|
UpdateSteeringMultiplier();
|
||||||
|
|
||||||
|
if (IsWheelBeingHeld)
|
||||||
|
UpdateRotationTracking();
|
||||||
|
else if (_timeWheelReleased >= 0f)
|
||||||
|
HandleWheelReturn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSteeringMultiplier()
|
||||||
|
{
|
||||||
|
_carController.steeringWheelAngleMultiplier = ModSettings.EntryOverrideSteeringRange.Value
|
||||||
|
? ModSettings.EntryCustomSteeringRange.Value * _originalSteeringWheelSign / _carController.steerAngle
|
||||||
|
: _originalSteeringWheelAngleMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleWheelReturn()
|
||||||
|
{
|
||||||
|
var timeSinceRelease = Time.time - _timeWheelReleased;
|
||||||
|
if (timeSinceRelease < RETURN_TO_CENTER_DURATION)
|
||||||
|
{
|
||||||
|
var t = timeSinceRelease / RETURN_TO_CENTER_DURATION;
|
||||||
|
_averageAngle = Mathf.Lerp(_averageAngle, 0f, t);
|
||||||
|
|
||||||
|
for (var i = 0; i < _totalAngles.Count; i++)
|
||||||
|
_totalAngles[i] = _averageAngle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_averageAngle = 0f;
|
||||||
|
for (var i = 0; i < _totalAngles.Count; i++)
|
||||||
|
_totalAngles[i] = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetMaxSteeringRange()
|
||||||
|
{
|
||||||
|
return _carController.steerAngle * Mathf.Abs(_carController.steeringWheelAngleMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetSteeringWheelSign()
|
||||||
|
{
|
||||||
|
return _originalSteeringWheelSign * (ModSettings.EntryInvertSteering.Value ? 1f : -1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 GetSteeringWheelLocalAxis()
|
||||||
|
{
|
||||||
|
return _carController.steeringWheelRotateAround switch
|
||||||
|
{
|
||||||
|
RCC_CarControllerV3.SteeringWheelRotateAround.XAxis => Vector3.right,
|
||||||
|
RCC_CarControllerV3.SteeringWheelRotateAround.YAxis => Vector3.up,
|
||||||
|
RCC_CarControllerV3.SteeringWheelRotateAround.ZAxis => Vector3.forward,
|
||||||
|
_ => Vector3.forward
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 GetLocalPositionWithoutRotation(Vector3 worldPosition)
|
||||||
|
{
|
||||||
|
Transform steeringTransform = _carController.SteeringWheel;
|
||||||
|
Quaternion localRotation = steeringTransform.localRotation;
|
||||||
|
steeringTransform.localRotation = _carController.orgSteeringWheelRot;
|
||||||
|
Vector3 localPosition = steeringTransform.InverseTransformPoint(worldPosition);
|
||||||
|
steeringTransform.localRotation = localRotation;
|
||||||
|
return localPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetNormalizedValue()
|
||||||
|
{
|
||||||
|
return Mathf.Clamp(_averageAngle / GetMaxSteeringRange(), -1f, 1f) * GetSteeringWheelSign();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRotationTracking()
|
||||||
|
{
|
||||||
|
if (!_isTracking || _trackedTransforms.Count == 0) return;
|
||||||
|
|
||||||
|
Vector3 trackingAxis = GetSteeringWheelLocalAxis();
|
||||||
|
UpdateTransformAngles(trackingAxis);
|
||||||
|
UpdateAverageAngle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTransformAngles(Vector3 trackingAxis)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _trackedTransforms.Count; i++)
|
||||||
|
{
|
||||||
|
if (_trackedTransforms[i] == null) continue;
|
||||||
|
|
||||||
|
Vector3 currentPosition = GetLocalPositionWithoutRotation(_trackedTransforms[i].position);
|
||||||
|
if (currentPosition == _lastPositions[i]) continue;
|
||||||
|
|
||||||
|
Vector3 previousVector = Vector3.ProjectOnPlane(_lastPositions[i], trackingAxis).normalized;
|
||||||
|
Vector3 currentVector = Vector3.ProjectOnPlane(currentPosition, trackingAxis).normalized;
|
||||||
|
|
||||||
|
if (previousVector.sqrMagnitude > 0.001f && currentVector.sqrMagnitude > 0.001f)
|
||||||
|
{
|
||||||
|
var deltaAngle = Vector3.SignedAngle(previousVector, currentVector, trackingAxis);
|
||||||
|
if (Mathf.Abs(deltaAngle) < 90f)
|
||||||
|
_totalAngles[i] += deltaAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastPositions[i] = currentPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAverageAngle()
|
||||||
|
{
|
||||||
|
var sumAngles = 0f;
|
||||||
|
var validTransforms = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < _trackedTransforms.Count; i++)
|
||||||
|
{
|
||||||
|
if (_trackedTransforms[i] == null) continue;
|
||||||
|
sumAngles += _totalAngles[i];
|
||||||
|
validTransforms++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validTransforms > 0)
|
||||||
|
_averageAngle = sumAngles / validTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Private Methods
|
||||||
|
}
|
|
@ -6,6 +6,8 @@ using Unity.Profiling;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Animations;
|
using UnityEngine.Animations;
|
||||||
|
|
||||||
|
namespace NAK.RCCVirtualSteeringWheel.Util;
|
||||||
|
|
||||||
public class BoneVertexBoundsUtility : MonoBehaviour
|
public class BoneVertexBoundsUtility : MonoBehaviour
|
||||||
{
|
{
|
||||||
private static readonly ProfilerMarker s_calculateBoundsMarker = new("BoneVertexBounds.Calculate");
|
private static readonly ProfilerMarker s_calculateBoundsMarker = new("BoneVertexBounds.Calculate");
|
||||||
|
@ -201,7 +203,7 @@ public class BoneVertexBoundsUtility : MonoBehaviour
|
||||||
{
|
{
|
||||||
yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints =>
|
yield return StartCoroutine(ProcessSkinnedMeshRenderersLocal(bone, weightThreshold, skinnedPoints =>
|
||||||
{
|
{
|
||||||
if (skinnedPoints == null || skinnedPoints.Count <= 0) return;
|
if (skinnedPoints is not { Count: > 0 }) return;
|
||||||
allWeightedPoints.AddRange(skinnedPoints);
|
allWeightedPoints.AddRange(skinnedPoints);
|
||||||
hasValidPoints = true;
|
hasValidPoints = true;
|
||||||
}));
|
}));
|
|
@ -1,7 +1,7 @@
|
||||||
# GrabbableSteeringWheel
|
# RCCVirtualSteeringWheel
|
||||||
|
|
||||||
Experiment with making rigged RCC vehicle steering wheels work for VR steering input.
|
Experiment with making rigged RCC vehicle steering wheels work for VR steering input.
|
||||||
~~~~
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||||
|
@ -12,3 +12,4 @@ https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||||
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
|
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
|
||||||
|
|
||||||
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
||||||
|
~~~~
|
Loading…
Add table
Add a link
Reference in a new issue