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 @@
-
+
-