diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseConfiguration.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseConfiguration.cs new file mode 100644 index 000000000..ea7423214 --- /dev/null +++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseConfiguration.cs @@ -0,0 +1,86 @@ +using RaceElement.HUD.Overlay.Configuration; + +namespace RaceElement.HUD.Common.Overlays.Driving.DualSense; + +internal sealed class DualSenseConfiguration : OverlayConfiguration +{ + public DualSenseConfiguration() + { + GenericConfiguration.AlwaysOnTop = false; + GenericConfiguration.Window = false; + GenericConfiguration.Opacity = 1.0f; + GenericConfiguration.AllowRescale = false; + } + + + [ConfigGrouping("Brake Slip", "Adjust the slip effect whilst applying the brakes.")] + public BrakeSlipHaptics BrakeSlip { get; init; } = new(); + public sealed class BrakeSlipHaptics + { + /// + /// The brake in percentage (divide by 100f if you want 0-1 value) + /// + [ToolTip("The minimum brake percentage before any effects are applied. See this like a deadzone.")] + [FloatRange(0.1f, 99f, 0.1f, 1)] + public float BrakeThreshold { get; init; } = 3f; + + [FloatRange(0.05f, 6f, 0.002f, 3)] + public float FrontSlipThreshold { get; init; } = 0.25f; + + [FloatRange(0.05f, 6f, 0.002f, 3)] + public float RearSlipThreshold { get; init; } = 0.25f; + + [ToolTip("Higher is stronger dynamic feedback.")] + [IntRange(1, 8, 1)] + public int FeedbackStrength { get; init; } = 3; + + [ToolTip("Sets the min frequency of the vibration effect in the trigger.")] + [IntRange(1, 10, 1)] + public int MinFrequency { get; init; } = 6; + + [ToolTip("Sets the max frequency of the vibration effect in the trigger.")] + [IntRange(20, 150, 1)] + public int MaxFrequency { get; init; } = 100; + + [ToolTip("Change the amplitude(strength) of the vibration effect in the trigger.")] + [IntRange(1, 8, 1)] + public int Amplitude { get; init; } = 6; + } + + [ConfigGrouping("Throttle Slip", "Adjust the slip effect whilst applying the throttle.\nModify the threshold to increase or decrease sensitivity in different situations.")] + public ThrottleSlipHaptics ThrottleSlip { get; init; } = new(); + public sealed class ThrottleSlipHaptics + { + /// + /// The throttle in percentage (divide by 100f if you want 0-1 value) + /// + [ToolTip("The minimum throttle percentage before any effects are applied. See this like a deadzone.")] + [FloatRange(0.1f, 99f, 0.1f, 1)] + public float ThrottleThreshold { get; init; } = 3f; + + [ToolTip("Decrease this treshold to increase the sensitivity when the front wheels slip (understeer).")] + [FloatRange(0.05f, 6f, 0.002f, 3)] + public float FrontSlipThreshold { get; init; } = 0.35f; + + [ToolTip("Decrease this treshold to increase the sensitivity when the rear wheels slip (oversteer).")] + [FloatRange(0.05f, 10f, 0.002f, 3)] + public float RearSlipThreshold { get; init; } = 0.25f; + + [ToolTip("Higher is stronger dynamic feedback.")] + [IntRange(1, 8, 1)] + public int FeedbackStrength { get; init; } = 2; + + [ToolTip("Sets the min frequency of the vibration effect in the trigger.")] + [IntRange(1, 10, 1)] + public int MinFrequency { get; init; } = 6; + + [ToolTip("Sets the max frequency of the vibration effect in the trigger.")] + [IntRange(20, 150, 1)] + public int MaxFrequency { get; init; } = 100; + + [ToolTip("Change the amplitude(strength) of the vibration effect in the trigger.")] + [IntRange(1, 8, 1)] + public int Amplitude { get; init; } = 7; + } + +} diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseJob.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseJob.cs new file mode 100644 index 000000000..f2e5a865a --- /dev/null +++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseJob.cs @@ -0,0 +1,17 @@ +using RaceElement.Core.Jobs.Loop; +using static RaceElement.HUD.Common.Overlays.Driving.DualSense.Resources; + +namespace RaceElement.HUD.Common.Overlays.Driving.DualSense; + +internal sealed class HapticsJob(DualSenseOverlay overlay) : AbstractLoopJob +{ + public sealed override void RunAction() + { + if (!overlay.ShouldRender()) + return; + + TriggerHaptics.HandleAcceleration(overlay._config); + TriggerHaptics.HandleBraking(overlay._config); + } + public override void AfterCancel() { ds5w_set_trigger_effect_off(0); ds5w_set_trigger_effect_off(1); } +} diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseOverlay.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseOverlay.cs new file mode 100644 index 000000000..3187e53c6 --- /dev/null +++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/DualSenseOverlay.cs @@ -0,0 +1,78 @@ +using RaceElement.Data.Games; +using RaceElement.HUD.Overlay.Internal; +using System.Diagnostics; +using System.Drawing; +using System.Reflection; +using System.Text; + +using static RaceElement.HUD.Common.Overlays.Driving.DualSense.Resources; + +namespace RaceElement.HUD.Common.Overlays.Driving.DualSense; + +[Overlay(Name = "DualSense", + Description = "Adds trigger effects to the DualSense Controller.\n See Guide in the Discord of Race Element for instructions.", + OverlayCategory = OverlayCategory.Inputs, + OverlayType = OverlayType.Drive, + Game = Game.RaceRoom | Game.AssettoCorsa1 | Game.AssettoCorsaEvo, + Authors = ["Reinier Klarenberg", "Guillaume Stordeur"] +)] +internal sealed class DualSenseOverlay : CommonAbstractOverlay +{ + internal readonly DualSenseConfiguration _config = new(); + private HapticsJob _hapticsJob; + + public DualSenseOverlay(Rectangle rectangle) : base(rectangle, "DualSense") + { + Width = 1; Height = 1; + RefreshRateHz = 1; + AllowReposition = false; + } + + public sealed override void BeforeStart() + { + if (IsPreviewing) return; + + ExtractDs5ApiDll(); + + ds5w_init(); + _hapticsJob = new HapticsJob(this) { IntervalMillis = 1000 / 200 }; + _hapticsJob.Run(); + } + public sealed override void BeforeStop() + { + if (IsPreviewing) return; + + _hapticsJob?.CancelJoin(); + + ds5w_shutdown(); + } + + /// + /// Extracts the embbed dll in this namespace folder, to the executing assembly folder. + /// only extracts if it does not exists. Does not check for version!!! + /// + private static void ExtractDs5ApiDll() + { + string dllPath = Path.Combine(AppContext.BaseDirectory, Path.GetFileName("ds5w_x64.dll")); + FileInfo dllFile = new(dllPath); + if (dllFile.Exists) return; + + string resourceName = "RaceElement.HUD.Common.Overlays.Driving.DualSense.ds5w_x64.dll"; + var assembly = Assembly.GetExecutingAssembly(); + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + throw new FileNotFoundException($"Could not find resource: {resourceName}"); + + using var fileStream = dllFile.Open(FileMode.Create, FileAccess.Write); + stream.CopyTo(fileStream); + stream.Close(); + fileStream.Close(); + } + } + + public sealed override bool ShouldRender() => DefaultShouldRender() && !IsPreviewing; + + public sealed override void Render(Graphics g) { } + +} diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/Resources.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/Resources.cs new file mode 100644 index 000000000..bb8a16a15 --- /dev/null +++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/Resources.cs @@ -0,0 +1,24 @@ +using System.Runtime.InteropServices; + +namespace RaceElement.HUD.Common.Overlays.Driving.DualSense; + +internal static partial class Resources +{ + [LibraryImport("ds5w_x64.dll", SetLastError = true)] + public static partial int ds5w_init(); + + [LibraryImport("ds5w_x64.dll", SetLastError = true)] + public static partial void ds5w_shutdown(); + + [LibraryImport("ds5w_x64.dll", SetLastError = true)] + public static partial int ds5w_set_trigger_effect_off(int left); + + [LibraryImport("ds5w_x64.dll", SetLastError = true)] + public static partial int ds5w_set_trigger_effect_vibration(int left, int pos, int amp, int freq); + + [LibraryImport("ds5w_x64.dll", SetLastError = true)] + public static partial int ds5w_set_trigger_effect_feedback(int left, int pos, int strength); + + [LibraryImport("ds5w_x64.dll", SetLastError = true)] + public static partial int ds5w_set_trigger_effect_weapon(int left, int start, int end, int strength); +} diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/TriggerHaptics.cs b/Race Element.HUD.Common/Overlays/Driving/DualSense/TriggerHaptics.cs new file mode 100644 index 000000000..2abe4d66f --- /dev/null +++ b/Race Element.HUD.Common/Overlays/Driving/DualSense/TriggerHaptics.cs @@ -0,0 +1,96 @@ +using RaceElement.Data.Common; +using RaceElement.Util.SystemExtensions; +using static RaceElement.HUD.Common.Overlays.Driving.DualSense.Resources; + +namespace RaceElement.HUD.Common.Overlays.Driving.DualSense; + +internal static class TriggerHaptics +{ + public static void HandleBraking(DualSenseConfiguration config) + { + // TODO: add either an option to threshold it on brake input or based on some curve? + if (SimDataProvider.LocalCar.Inputs.Brake <= config.BrakeSlip.BrakeThreshold / 100f) + { + ds5w_set_trigger_effect_off(1); + return; + } + float[] slipRatios = SimDataProvider.LocalCar.Tyres.SlipRatio; + if (slipRatios.Length != 4) + { + ds5w_set_trigger_effect_off(1); + return; + } + + float slipRatioFront = Math.Max(slipRatios[0], slipRatios[1]); + float slipRatioRear = Math.Max(slipRatios[2], slipRatios[3]); + + // TODO: add option for front and rear ratio threshold. + if (slipRatioFront > config.BrakeSlip.FrontSlipThreshold || slipRatioRear > config.BrakeSlip.RearSlipThreshold) + { + float frontslipCoefecient = slipRatioFront * 4f; + frontslipCoefecient.ClipMax(7.5f); + + float rearSlipCoefecient = slipRatioFront * 2f; + rearSlipCoefecient.ClipMax(5f); + + float magicValue = frontslipCoefecient + rearSlipCoefecient; + float percentage = magicValue * 1.0f / 12.5f; + + if (percentage >= 0.05f) + { + ds5w_set_trigger_effect_feedback(1, 0, (int)(config.BrakeSlip.FeedbackStrength * percentage)); + Thread.Sleep((int)(1000f / 250)); + ds5w_set_trigger_effect_off(1); + } + + int freq = (int)(config.BrakeSlip.MaxFrequency * percentage); + freq.ClipMin(config.BrakeSlip.MinFrequency); + ds5w_set_trigger_effect_vibration(1, 0, config.BrakeSlip.Amplitude, freq); + Thread.Sleep((int)(1000f / 250 * 4)); + } + else + ds5w_set_trigger_effect_off(1); + } + + public static void HandleAcceleration(DualSenseConfiguration config) + { + if (SimDataProvider.LocalCar.Inputs.Throttle <= config.ThrottleSlip.ThrottleThreshold / 100f) + { + ds5w_set_trigger_effect_off(0); + return; + } + float[] slipRatios = SimDataProvider.LocalCar.Tyres.SlipRatio; + if (slipRatios.Length != 4) + { + ds5w_set_trigger_effect_off(0); + return; + } + + float slipRatioFront = Math.Max(slipRatios[0], slipRatios[1]); + float slipRatioRear = Math.Max(slipRatios[2], slipRatios[3]); + + if (slipRatioFront > config.ThrottleSlip.FrontSlipThreshold || slipRatioRear > config.ThrottleSlip.RearSlipThreshold) + { + float frontslipCoefecient = slipRatioFront * 3f; + frontslipCoefecient.ClipMax(5); + float rearSlipCoefecient = slipRatioFront * 5f; + rearSlipCoefecient.ClipMax(7.5f); + + float magicValue = frontslipCoefecient + rearSlipCoefecient; + float percentage = magicValue * 1.0f / 12.5f; + if (percentage >= 0.05f) + { + ds5w_set_trigger_effect_feedback(0, 0, (int)(config.ThrottleSlip.FeedbackStrength * percentage)); + Thread.Sleep((int)(1000f / 250)); + ds5w_set_trigger_effect_off(0); + } + + int freq = (int)(config.ThrottleSlip.MaxFrequency * percentage); + freq.ClipMin(config.ThrottleSlip.MinFrequency); + ds5w_set_trigger_effect_vibration(0, 0, config.ThrottleSlip.Amplitude, freq); + Thread.Sleep((int)(1000f / 250 * 4)); + } + else + ds5w_set_trigger_effect_off(0); + } +} diff --git a/Race Element.HUD.Common/Overlays/Driving/DualSense/ds5w_x64.dll b/Race Element.HUD.Common/Overlays/Driving/DualSense/ds5w_x64.dll new file mode 100644 index 000000000..a6e93968e Binary files /dev/null and b/Race Element.HUD.Common/Overlays/Driving/DualSense/ds5w_x64.dll differ diff --git a/Race Element.HUD.Common/Race Element.HUD.Common.csproj b/Race Element.HUD.Common/Race Element.HUD.Common.csproj index 10cc9a7e1..8d4bed244 100644 --- a/Race Element.HUD.Common/Race Element.HUD.Common.csproj +++ b/Race Element.HUD.Common/Race Element.HUD.Common.csproj @@ -24,6 +24,12 @@ True True + + + + + + @@ -36,7 +42,6 @@ - + -