├── Dependencies ├── iRSDKSharp.dll ├── YamlDotNet.Core.dll └── iRacingSdkWrapper.dll ├── Media ├── sharpoverlayicon.ico ├── sharpoverlayicon.png └── sharpoverlaylogo.png ├── Fonts └── EurostileExtendedBlack.ttf ├── .editorconfig ├── Services ├── IClear.cs ├── FuelServices │ ├── CustomEventArgs.cs │ ├── IFuelCalculator.cs │ ├── FuelContext.cs │ ├── RaceHistory.cs │ ├── FuelModel.cs │ ├── FuelEventArgs.cs │ ├── AddFuelHistoryDTO.cs │ ├── LapServices │ │ ├── TimeKeeper.cs │ │ ├── LapTracker.cs │ │ ├── LapAnalyzer.cs │ │ └── LapCountCalculator.cs │ ├── SetupChangeTracker.cs │ ├── FinishLineLocator.cs │ ├── PitServices │ │ ├── PitTimeTracker.cs │ │ └── PitManager.cs │ ├── FuelRepository.cs │ └── FuelCalculatorService.cs ├── Base │ ├── ServiceCore.cs │ ├── WindowStateService.cs │ ├── SimReader.cs │ └── WindowState.cs ├── OverlaysService.cs ├── JotService.cs └── Spotter │ └── BarSpotterService.cs ├── Models ├── StartType.cs ├── SessionType.cs ├── Input.cs ├── Enums.cs ├── Overlays.cs ├── Lap.cs ├── Driver.cs ├── SimulationOutputDTO.cs ├── FuelViewModel.cs └── Settings.cs ├── Utilities ├── Enums │ └── FinishLineLocation.cs ├── DefaultTickRates.cs ├── Telemetries │ ├── ITelemetryParser.cs │ └── TelemetryParser.cs └── Sessions │ ├── ISessionParser.cs │ └── SessionParser.cs ├── Strategies ├── LastLapStrategy.cs ├── StrategyViewModel.cs ├── IFuelStrategy.cs ├── FiveLapStrategy.cs ├── FullRaceStrategy.cs └── CoreStrategy.cs ├── fuelHistory.json ├── Events └── WindowStateEventArgs.cs ├── AssemblyInfo.cs ├── FuelDebug.xaml.cs ├── Converters └── BrushToColorConverter.cs ├── App.xaml.cs ├── LICENSE.txt ├── SharpOverlay.sln ├── App.xaml ├── InputGraph.xaml ├── README.md ├── Wind.xaml ├── SharpOverlay.csproj ├── FuelCalculator.xaml.cs ├── .gitattributes ├── BarSpotter.xaml ├── MainWindow.xaml.cs ├── FuelDebug.xaml ├── Wind.xaml.cs ├── FuelCalculator.xaml ├── BarSpotter.xaml.cs ├── .gitignore ├── InputGraph.xaml.cs └── MainWindow.xaml /Dependencies/iRSDKSharp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiuC39/SharpOverlay/HEAD/Dependencies/iRSDKSharp.dll -------------------------------------------------------------------------------- /Media/sharpoverlayicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiuC39/SharpOverlay/HEAD/Media/sharpoverlayicon.ico -------------------------------------------------------------------------------- /Media/sharpoverlayicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiuC39/SharpOverlay/HEAD/Media/sharpoverlayicon.png -------------------------------------------------------------------------------- /Media/sharpoverlaylogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiuC39/SharpOverlay/HEAD/Media/sharpoverlaylogo.png -------------------------------------------------------------------------------- /Dependencies/YamlDotNet.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiuC39/SharpOverlay/HEAD/Dependencies/YamlDotNet.Core.dll -------------------------------------------------------------------------------- /Fonts/EurostileExtendedBlack.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiuC39/SharpOverlay/HEAD/Fonts/EurostileExtendedBlack.ttf -------------------------------------------------------------------------------- /Dependencies/iRacingSdkWrapper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TiberiuC39/SharpOverlay/HEAD/Dependencies/iRacingSdkWrapper.dll -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA1859: Use concrete types when possible for improved performance 4 | dotnet_diagnostic.CA1859.severity = none 5 | -------------------------------------------------------------------------------- /Services/IClear.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Services 2 | { 3 | public interface IClear 4 | { 5 | void Clear(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Models/StartType.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Models 2 | { 3 | public enum StartType 4 | { 5 | Unknown = -1, 6 | Rolling, 7 | Standing 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Services/FuelServices/CustomEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpOverlay.Services.FuelServices 4 | { 5 | public abstract class CustomEventArgs : EventArgs 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Models/SessionType.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Models 2 | { 3 | public enum SessionType 4 | { 5 | Invalid = -1, 6 | Practice, 7 | Qualifying, 8 | Race 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Utilities/Enums/FinishLineLocation.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Utilities.Enums 2 | { 3 | public enum FinishLineLocation 4 | { 5 | Unknown, 6 | AlongPitRoad, 7 | AfterPitRoad 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Services/FuelServices/IFuelCalculator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpOverlay.Services.FuelServices 4 | { 5 | public interface IFuelCalculator 6 | { 7 | event EventHandler FuelUpdated; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Services/FuelServices/FuelContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SharpOverlay.Services.FuelServices 4 | { 5 | public class FuelContext 6 | { 7 | public Dictionary ByTrack { get; set; } = []; 8 | } 9 | } -------------------------------------------------------------------------------- /Services/FuelServices/RaceHistory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SharpOverlay.Services.FuelServices 4 | { 5 | public class RaceHistory 6 | { 7 | public Dictionary ByCarId { get; set; } = []; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Models/Input.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Models 2 | { 3 | public class Input 4 | { 5 | public float Throttle { get; set; } 6 | public float Brake { get; set; } 7 | public float Clutch { get; set; } 8 | public float Steering { get; set; } 9 | public float ABS { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Strategies/LastLapStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Strategies 2 | { 3 | public class LastLapStrategy : CoreStrategy 4 | { 5 | private const string _name = "LAST"; 6 | 7 | public LastLapStrategy(double fuelCutOff) 8 | :base(_name, fuelCutOff) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Services/FuelServices/FuelModel.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Services.FuelServices 2 | { 3 | public class FuelModel 4 | { 5 | public double Consumption { get; set; } 6 | public double LapTime { get; set; } 7 | public int LapCount { get; set; } 8 | public double PitStopTime { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Utilities/DefaultTickRates.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Utilities 2 | { 3 | public static class DefaultTickRates 4 | { 5 | public const int Default = 60; 6 | public const int FuelCalculator = 4; 7 | public const int Wind = 10; 8 | public const int BarSpotter = 60; 9 | public const int InputGraph = 60; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Services/FuelServices/FuelEventArgs.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using System; 3 | 4 | namespace SharpOverlay.Services.FuelServices 5 | { 6 | public class FuelEventArgs : EventArgs 7 | { 8 | public FuelEventArgs(FuelViewModel viewModel) 9 | { 10 | ViewModel = viewModel; 11 | } 12 | 13 | public FuelViewModel ViewModel { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /fuelHistory.json: -------------------------------------------------------------------------------- 1 | { 2 | "ByTrack": { 3 | "192": { 4 | "ByCarId": { 5 | "169": { 6 | "Consumption": 3.3064637121700096, 7 | "LapTime": 127.4894027, 8 | "LapCount": 3 9 | } 10 | } 11 | }, 12 | "341": { 13 | "ByCarId": { 14 | "169": { 15 | "Consumption": 0, 16 | "LapTime": 121.927803, 17 | "LapCount": -1 18 | } 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Services/FuelServices/AddFuelHistoryDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpOverlay.Services.FuelServices 4 | { 5 | public class AddFuelHistoryDTO 6 | { 7 | public int TrackId { get; set; } 8 | public double Consumption { get; set; } 9 | public int LapCount { get; set; } 10 | public TimeSpan LapTime { get; set; } 11 | public TimeSpan PitStopTime { get; set; } 12 | public int CarId { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Strategies/StrategyViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Strategies 2 | { 3 | public class StrategyViewModel 4 | { 5 | public string Name { get; set; } = null!; 6 | public double FuelConsumption { get; set; } 7 | public double LapsOfFuelRemaining { get; set; } 8 | public double RefuelAmount { get; set; } 9 | public bool DoesRequireRefueling => RefuelAmount > 0; 10 | public double FuelAtEnd { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Strategies/IFuelStrategy.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using SharpOverlay.Services; 3 | using System.Collections.Generic; 4 | 5 | namespace SharpOverlay.Strategies 6 | { 7 | public interface IFuelStrategy : IClear 8 | { 9 | void Calculate(List lapsCompleted, int sessionLapsRemaining); 10 | void UpdateRefuel(double currentFuelLevel, int sessionLapsRemaining); 11 | void UpdateLapsOfFuelRemaining(double currentFuelLevel); 12 | 13 | bool RequiresRefueling(); 14 | 15 | StrategyViewModel GetView(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Events/WindowStateEventArgs.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Services.Base; 2 | using System; 3 | 4 | namespace SharpOverlay.Events 5 | { 6 | public class WindowStateEventArgs : EventArgs 7 | { 8 | public WindowStateEventArgs(WindowState windowState) 9 | { 10 | IsOpen = windowState.IsOpen; 11 | IsEnabled = windowState.IsEnabled; 12 | IsInTestMode = windowState.IsInTestMode; 13 | } 14 | 15 | public bool IsOpen { get; } 16 | public bool IsEnabled { get; } 17 | public bool IsInTestMode { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /Models/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace SharpOverlay.Models 2 | { 3 | public class Enums 4 | { 5 | public enum CarLeftRight 6 | { 7 | irsdk_LROff, 8 | irsdk_LRClear, // no cars around us. 9 | irsdk_LRCarLeft, // there is a car to our left. 10 | irsdk_LRCarRight, // there is a car to our right. 11 | irsdk_LRCarLeftRight, // there are cars on each side. 12 | irsdk_LR2CarsLeft, // there are two cars to our left. 13 | irsdk_LR2CarsRight // there are two cars to our right. 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Windows; 3 | 4 | [assembly: ThemeInfo( 5 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 6 | //(used if a resource is not found in the page, 7 | // or application resource dictionaries) 8 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 9 | //(used if a resource is not found in the page, 10 | // app, or any theme specific resource dictionaries) 11 | )] 12 | 13 | -------------------------------------------------------------------------------- /Strategies/FiveLapStrategy.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SharpOverlay.Strategies 6 | { 7 | public class FiveLapStrategy : CoreStrategy 8 | { 9 | private const string _name = "5L"; 10 | 11 | public FiveLapStrategy(double fuelCutOff) 12 | :base(_name, fuelCutOff) 13 | { 14 | } 15 | 16 | protected override double GetAverageFuelConsumption(List lapsCompleted) 17 | => lapsCompleted.Count > 5 ? lapsCompleted.TakeLast(5).Average(l => l.FuelUsed) : base.GetAverageFuelConsumption(lapsCompleted); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Strategies/FullRaceStrategy.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SharpOverlay.Strategies 6 | { 7 | public class FullRaceStrategy : CoreStrategy 8 | { 9 | private const string _name = "FULL"; 10 | 11 | public FullRaceStrategy(double fuelCutOff) 12 | : base(_name, fuelCutOff) 13 | { 14 | } 15 | 16 | protected override double GetAverageFuelConsumption(List lapsCompleted) 17 | => lapsCompleted.Count > 1 ? lapsCompleted.Skip(1).Average(l => l.FuelUsed) : base.GetAverageFuelConsumption(lapsCompleted); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Models/Overlays.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | 8 | namespace SharpOverlay.Models 9 | { 10 | public class Overlay 11 | { 12 | public Type Type { get; set; } 13 | public Window? Window { get; set; } 14 | public bool IsEnabled { get; set; } 15 | public bool IsOpen { get; set; } 16 | 17 | public Overlay(Type type, bool isEnabled, bool isOpen) 18 | { 19 | Type = type; 20 | Window = null; 21 | IsEnabled = isEnabled; 22 | IsOpen = isOpen; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FuelDebug.xaml.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Services.FuelServices; 2 | using System.Windows; 3 | 4 | namespace SharpOverlay 5 | { 6 | /// 7 | /// Interaction logic for FuelDebugWindow.xaml 8 | /// 9 | public partial class FuelDebugWindow : Window 10 | { 11 | private readonly IFuelCalculator _service; 12 | public FuelDebugWindow(IFuelCalculator dataService) 13 | { 14 | _service = dataService; 15 | 16 | _service.FuelUpdated += ExecuteOnFuelUpdated; 17 | 18 | Topmost = true; 19 | 20 | InitializeComponent(); 21 | } 22 | 23 | public void ExecuteOnFuelUpdated(object? sender, FuelEventArgs e) 24 | { 25 | DataContext = e.ViewModel; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Models/Lap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpOverlay.Models 4 | { 5 | public class Lap 6 | { 7 | public Lap() 8 | { 9 | 10 | } 11 | private Lap(int lapNumber) 12 | { 13 | Number = lapNumber; 14 | } 15 | 16 | public Lap(int lapNumber, double startingFuel) 17 | :this(lapNumber) 18 | { 19 | StartingFuel = startingFuel; 20 | } 21 | 22 | public Lap(int lapNumber, TimeSpan lapTime) 23 | :this(lapNumber) 24 | { 25 | Time = lapTime; 26 | } 27 | 28 | public int Number { get; set; } 29 | public TimeSpan Time { get; set; } 30 | public double StartingFuel { get; set; } 31 | public double EndingFuel { get; set; } 32 | public double FuelUsed { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Services/FuelServices/LapServices/TimeKeeper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpOverlay.Services.FuelServices.LapServices 5 | { 6 | public class TimeKeeper 7 | { 8 | private readonly List _lapTimes = new List(); 9 | 10 | private TimeSpan _previousTimeAtLine = TimeSpan.Zero; 11 | private TimeSpan _currentTimeAtLine = TimeSpan.Zero; 12 | 13 | public TimeSpan GetLapTime() 14 | { 15 | var lapTime = _previousTimeAtLine - _currentTimeAtLine; 16 | 17 | if (lapTime > TimeSpan.Zero) 18 | return lapTime; 19 | 20 | return TimeSpan.Zero; 21 | } 22 | 23 | public void MarkTime(TimeSpan time) 24 | { 25 | _previousTimeAtLine = _currentTimeAtLine; 26 | _currentTimeAtLine = time; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Converters/BrushToColorConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Windows.Media; 5 | 6 | namespace SharpOverlay.Converters 7 | { 8 | [ValueConversion(typeof(SolidColorBrush), typeof(System.Drawing.Color))] 9 | public class BrushToColorConverter : IValueConverter 10 | { 11 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | var col = (Color)value; 14 | var c = Color.FromArgb(col.A, col.R, col.G, col.B); 15 | return new SolidColorBrush(c); 16 | } 17 | 18 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 19 | { 20 | var c = (SolidColorBrush)value; 21 | var col = Color.FromArgb(c.Color.A, c.Color.R, c.Color.G, c.Color.B); 22 | return col; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Services/FuelServices/SetupChangeTracker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.ApplicationModel.VoiceCommands; 3 | 4 | namespace SharpOverlay.Services.FuelServices 5 | { 6 | public class SetupChangeTracker 7 | { 8 | private string _setupName = string.Empty; 9 | 10 | public bool IsSetupChanged { get; private set; } 11 | 12 | public void UpdateSetupName(string newSetupName) 13 | { 14 | if (CheckIfDifferent(newSetupName)) 15 | { 16 | _setupName = newSetupName; 17 | } 18 | } 19 | 20 | private bool CheckIfDifferent(string newSetupName) 21 | { 22 | if (_setupName != newSetupName) 23 | { 24 | IsSetupChanged = true; 25 | 26 | return true; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | public void Reset() 33 | { 34 | IsSetupChanged = false; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Services/FuelServices/FinishLineLocator.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Utilities.Enums; 2 | 3 | namespace SharpOverlay.Services.FuelServices 4 | { 5 | public class FinishLineLocator : IClear 6 | { 7 | private FinishLineLocation _finishLineLocation = FinishLineLocation.Unknown; 8 | 9 | public void Clear() 10 | { 11 | _finishLineLocation = FinishLineLocation.Unknown; 12 | } 13 | 14 | public void DetermineFinishLineLocation(float driverTrackPct) 15 | { 16 | if (driverTrackPct > 0.9) 17 | { 18 | _finishLineLocation = FinishLineLocation.AfterPitRoad; 19 | } 20 | else 21 | { 22 | _finishLineLocation = FinishLineLocation.AlongPitRoad; 23 | } 24 | } 25 | 26 | public bool IsFinishLineKnown() 27 | => _finishLineLocation != FinishLineLocation.Unknown; 28 | 29 | public bool IsFinishLineAfterPits() 30 | => _finishLineLocation == FinishLineLocation.AfterPitRoad; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /App.xaml.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using SharpOverlay.Services; 3 | using System; 4 | using System.Windows; 5 | using System.Windows.Media; 6 | using Velopack; 7 | 8 | namespace SharpOverlay 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | public static Settings appSettings = new Settings(); 16 | 17 | [STAThread] 18 | private static void Main(string[] args) 19 | { 20 | VelopackApp.Build().Run(); 21 | 22 | JotService.tracker.Apply(appSettings); 23 | 24 | if (appSettings.GeneralSettings.UseHardwareAcceleration) { 25 | RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.Default; 26 | } 27 | else 28 | { 29 | RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly; 30 | } 31 | 32 | App app = new(); 33 | app.InitializeComponent(); 34 | app.Run(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Services/Base/ServiceCore.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Events; 2 | using SharpOverlay.Models; 3 | using System.Windows; 4 | 5 | namespace SharpOverlay.Services.Base 6 | { 7 | public abstract class ServiceCore where T : Settings 8 | { 9 | private readonly BaseSettings _settings; 10 | private readonly Window _window; 11 | 12 | protected ServiceCore(WindowStateService windowStateService, BaseSettings settings, Window window) 13 | { 14 | _settings = settings; 15 | _window = window; 16 | windowStateService.WindowStateChanged += OnWindowStateChanged; 17 | } 18 | 19 | private void OnWindowStateChanged(object? sender, WindowStateEventArgs eventArgs) 20 | { 21 | if (eventArgs.IsOpen && eventArgs.IsEnabled) 22 | { 23 | _window.Show(); 24 | 25 | //if (_settings.IsInTestMode) 26 | // HandleTestMode(_settings.IsInTestMode); 27 | } 28 | else 29 | { 30 | _window.Hide(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 SharpOverlay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SharpOverlay.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpOverlay", "SharpOverlay.csproj", "{FD0F582A-5298-4F96-AB8A-ED41E28B195D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {FD0F582A-5298-4F96-AB8A-ED41E28B195D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {FD0F582A-5298-4F96-AB8A-ED41E28B195D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {FD0F582A-5298-4F96-AB8A-ED41E28B195D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {FD0F582A-5298-4F96-AB8A-ED41E28B195D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9090D832-F786-4B3A-92C9-FAF993DB204C} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Services/OverlaysService.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpOverlay.Services 5 | { 6 | public static class OverlaysService 7 | { 8 | public static IList overlays { get; private set; } 9 | static OverlaysService() 10 | { 11 | overlays = new List(); 12 | overlays.Add(new Overlay(typeof(InputGraph), App.appSettings.InputGraphSettings.IsEnabled, false)); 13 | overlays.Add(new Overlay(typeof(BarSpotter), App.appSettings.BarSpotterSettings.IsEnabled, false)); 14 | overlays.Add(new Overlay(typeof(Wind), App.appSettings.WindSettings.IsEnabled, false)); 15 | overlays.Add(new Overlay(typeof(FuelCalculatorWindow), App.appSettings.FuelSettings.IsEnabled, false)); 16 | } 17 | 18 | public static void UpdateEnabledStatus() 19 | { 20 | overlays[0].IsEnabled = App.appSettings.InputGraphSettings.IsEnabled; 21 | overlays[1].IsEnabled = App.appSettings.BarSpotterSettings.IsEnabled; 22 | overlays[2].IsEnabled = App.appSettings.WindSettings.IsEnabled; 23 | overlays[3].IsEnabled = App.appSettings.FuelSettings.IsEnabled; 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Utilities/Telemetries/ITelemetryParser.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using iRacingSdkWrapper.Bitfields; 3 | using System.Collections.Generic; 4 | 5 | namespace SharpOverlay.Utilities.Telemetries 6 | { 7 | public interface ITelemetryParser 8 | { 9 | public int PlayerCarIdx { get; } 10 | public int PlayerCarClassId { get; } 11 | public double PlayerPctOnTrack { get; } 12 | Dictionary PositionCarIdxInClass { get; } 13 | Dictionary PositionCarIdxInRace { get; } 14 | int CurrentSessionNumber { get; } 15 | bool HasSwitchedSessions { get; } 16 | float[] CarIdxPctOnTrack { get; } 17 | 18 | void Clear(); 19 | void ParseCurrentSessionNumber(TelemetryInfo telemetry); 20 | void ParsePlayerCarClassId(TelemetryInfo telemetry); 21 | void ParsePlayerCarIdx(TelemetryInfo telemetry); 22 | void ParsePlayerPctOnTrack(TelemetryInfo telemetry); 23 | void ParsePositionCarIdxInPlayerClass(TelemetryInfo telemetry, int paceCarIdx); 24 | void ParsePositionCarIdxForWholeRace(TelemetryInfo telemetry, int paceCarIdx); 25 | SessionFlags GetSessionFlag(TelemetryInfo telemetry); 26 | void ParseCarIdxOnTrack(TelemetryInfo telemetry); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Services/FuelServices/PitServices/PitTimeTracker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SharpOverlay.Services.FuelServices.PitServices 6 | { 7 | public class PitTimeTracker 8 | { 9 | private TimeSpan _pitDuration = TimeSpan.Zero; 10 | private TimeSpan _timeAtPitStart = TimeSpan.Zero; 11 | private List _pitStopDurations = new List(); 12 | 13 | public bool IsTrackingTime { get; private set; } 14 | 15 | public TimeSpan GetPitDuration() 16 | => _pitDuration; 17 | 18 | public void Start(TimeSpan timeLeft) 19 | { 20 | _timeAtPitStart = timeLeft; 21 | IsTrackingTime = true; 22 | } 23 | 24 | public void Stop(TimeSpan timeLeft) 25 | { 26 | if (_timeAtPitStart > TimeSpan.Zero) 27 | { 28 | _pitDuration = _timeAtPitStart - timeLeft; 29 | _timeAtPitStart = TimeSpan.Zero; 30 | 31 | _pitStopDurations.Add(timeLeft); 32 | } 33 | 34 | IsTrackingTime = false; 35 | } 36 | 37 | public TimeSpan GetAvgPitStopTime() 38 | { 39 | return TimeSpan.FromSeconds(_pitStopDurations.Average(t => t.TotalSeconds)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /App.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /InputGraph.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Utilities/Sessions/ISessionParser.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using iRacingSdkWrapper.JsonModels; 3 | using SharpOverlay.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace SharpOverlay.Utilities.Sessions 8 | { 9 | public interface ISessionParser 10 | { 11 | Dictionary Drivers { get; } 12 | string EventType { get; } 13 | SessionType SessionType { get; } 14 | List Sessions { get; } 15 | StartType StartType { get; } 16 | int SessionLaps { get; } 17 | int PaceCarIdx { get; } 18 | bool IsMultiClassRace { get; } 19 | List Sectors { get; } 20 | int CarId { get; } 21 | int TrackId { get; } 22 | 23 | void Clear(); 24 | TimeSpan GetBestLapTime(int leaderIdx, int currentSessionNumber); 25 | void ParseCurrentSessionType(SessionInfo sessionInfo, int currentSessionNumber); 26 | void ParseRaceType(SessionInfo sessionInfo); 27 | void ParseDrivers(SessionInfo sessionInfo); 28 | void ParsePaceCarIdx(SessionInfo sessionInfo); 29 | void ParseLapsInSession(SessionInfo sessionInfo, int currentSessionNumber); 30 | void ParseSessions(SessionInfo sessionInfo); 31 | void ParseStartType(SessionInfo sessionInfo); 32 | void ParseSectors(SessionInfo sessionInfo); 33 | void ParseTrackId(SessionInfo sessionInfo); 34 | void ParseCarId(SessionInfo sessionInfo); 35 | void ParseEventType(SessionInfo sessionInfo); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Models/Driver.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | 3 | namespace SharpOverlay.Models 4 | { 5 | public class Driver 6 | { 7 | public Driver() 8 | { 9 | } 10 | 11 | /// 12 | /// The identifier (CarIdx) of this driver (unique to this session) 13 | /// 14 | public int CarIdx { get; set; } 15 | 16 | /// 17 | /// The current position of the driver 18 | /// 19 | public int Position { get; set; } 20 | 21 | /// 22 | /// The name of the driver 23 | /// 24 | public string Name { get; set; } 25 | /// 26 | /// The car number of this driver 27 | /// 28 | public string Number { get; set; } 29 | /// 30 | /// Used to determine if a driver is in the pits, off or on track 31 | /// 32 | public TrackSurfaces TrackSurface { get; set; } 33 | 34 | 35 | /// 36 | /// The lap this driver is currently in 37 | /// 38 | public int Lap { get; set; } 39 | 40 | /// 41 | /// The distance along the current lap of this driver (in percentage) 42 | /// 43 | public float LapDistancePct { get; set; } 44 | 45 | /// 46 | /// The relative distance between you and this driver (in percentage). 47 | /// 48 | public float RelativeLapDistancePct { get; set; } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Services/Base/WindowStateService.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using SharpOverlay.Events; 3 | using SharpOverlay.Models; 4 | using System; 5 | using System.ComponentModel; 6 | 7 | namespace SharpOverlay.Services.Base 8 | { 9 | public class WindowStateService 10 | { 11 | private readonly WindowState _windowState; 12 | public event EventHandler? WindowStateChanged; 13 | 14 | public WindowStateService(SimReader reader, BaseSettings settings) 15 | { 16 | settings.PropertyChanged += ExecuteOnProperty; 17 | 18 | reader.OnTelemetryUpdated += ExecuteOnTelemetry; 19 | 20 | _windowState = new WindowState(settings); 21 | } 22 | 23 | public void ExecuteOnTelemetry(object? sender, SdkWrapper.TelemetryUpdatedEventArgs args) 24 | { 25 | _windowState.Update(args); 26 | 27 | RaiseEventIfNewData(); 28 | } 29 | 30 | public void ExecuteOnProperty(object? sender, PropertyChangedEventArgs args) 31 | { 32 | _windowState.Update(args); 33 | 34 | RaiseEventIfNewData(); 35 | } 36 | 37 | private void RaiseEventIfNewData() 38 | { 39 | if (_windowState.RequiresChange) 40 | { 41 | RaiseEvent(); 42 | _windowState.CompleteChange(); 43 | } 44 | } 45 | private void RaiseEvent() 46 | { 47 | WindowStateChanged?.Invoke(this, new WindowStateEventArgs(_windowState)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Services/FuelServices/LapServices/LapTracker.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace SharpOverlay.Services.FuelServices.LapServices 6 | { 7 | public class LapTracker : IClear 8 | { 9 | private readonly List _completedLaps = []; 10 | private Lap? _currentLap; 11 | 12 | public void StartNewLap(int lapNumber, double startingFuelLevel) 13 | { 14 | var newLap = new Lap(lapNumber, startingFuelLevel); 15 | 16 | _currentLap = newLap; 17 | } 18 | 19 | public void CompleteCurrentLap(double endingFuelLevel, TimeSpan lapTime) 20 | { 21 | _currentLap!.EndingFuel = endingFuelLevel; 22 | 23 | _currentLap.Time = lapTime; 24 | _currentLap.FuelUsed = _currentLap.StartingFuel - _currentLap.EndingFuel; 25 | 26 | _completedLaps.Add(_currentLap); 27 | } 28 | 29 | public Lap? GetCurrentLap() 30 | => _currentLap; 31 | 32 | public void ResetCurrentLap() 33 | { 34 | _currentLap = null; 35 | } 36 | 37 | public List GetPlayerLaps() 38 | => _completedLaps; 39 | 40 | public int GetCompletedLapsCount() 41 | => _completedLaps.Count - 1; 42 | 43 | public void Clear() 44 | { 45 | _completedLaps.Clear(); 46 | ResetCurrentLap(); 47 | } 48 | 49 | public void StartWithHistory(int lapNumber, FuelModel entry) 50 | { 51 | StartNewLap(lapNumber, entry.Consumption); 52 | CompleteCurrentLap(0, TimeSpan.FromSeconds(entry.LapTime)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Logo](https://raw.githubusercontent.com/TiberiuC39/SharpOverlay/master/Media/sharpoverlaylogo.png) 3 | 4 | --- 5 | 6 | ## What is it? 7 | SharpOverlay is a set of iRacing overlays built on .NET & WPF. It aims to provide free & open-source versions of useful widgets. 8 | 9 | ## Features 10 | * Input Graph 11 | * Bar Spotter 12 | * Wind Direction & Speed 13 | * Fuel Calculator (new!) 14 | 15 | 16 | https://github.com/user-attachments/assets/5c3325ea-c5ea-44e9-9ca9-cda07f0411b7 17 | 18 | 19 | ## Usage 20 | ⚠️ Run iRacing in borderless windowed mode to avoid any issues. 21 | 22 | Use the bottom right corner to resize the windows. 23 | 24 | VR Users: Use OpenKneeboard to render the overlays in VR. 25 | 26 | ## Download 27 | 28 | Download the latest installer [here](https://github.com/TiberiuC39/SharpOverlay/releases/latest/download/SharpOverlay-win-Setup.exe), or portable version [here](https://github.com/TiberiuC39/SharpOverlay/releases/latest/download/SharpOverlay-win-Portable.zip). 29 | Installation is straight forward, double-click the setup exe or unzip the archive and start the app with SharpOverlay.exe 30 | 31 | When a new version is available, a green button will prompt you to update. 32 | ## Bugs & Feature Requests 33 | Send any bug reports and feature requests via GitHub issues. 34 | 35 | ## Building locally 36 | For building the solution, VS 2022 with the .NET desktop development workload should suffice. 37 | 38 | ## Mentions 39 | 40 | [iSimpleRadar](https://github.com/marcoscavaleiro/iSimpleRadar) - Much of the Bar Spotter code is based on this project. 41 | 42 | ## Donate 43 | If you find the overlays useful and would like to support the project, you can do so on Ko-fi. 44 | 45 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Q5Q211EKTG) 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Wind.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Models/SimulationOutputDTO.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using iRacingSdkWrapper.Bitfields; 3 | using System; 4 | 5 | namespace SharpOverlay.Models 6 | { 7 | public class SimulationOutputDTO 8 | { 9 | public SimulationOutputDTO() 10 | { 11 | } 12 | public SimulationOutputDTO(TelemetryInfo telemetry) 13 | { 14 | FuelLevel = telemetry.FuelLevel.Value; 15 | CurrentLapNumber = telemetry.Lap.Value; 16 | IsOnPitRoad = telemetry.IsOnPitRoad.Value; 17 | TrackSurface = telemetry.PlayerTrackSurface.Value; 18 | IsReceivingService = telemetry.IsPitstopActive.Value; 19 | PlayerTrackDistPct = telemetry.LapDistPct.Value; 20 | EnterExitResetButton = telemetry.EnterExitReset.Value; 21 | SessionState = telemetry.SessionState.Value; 22 | LastLapTime = TimeSpan.FromSeconds(telemetry.LapLastLapTime.Value); 23 | SessionTimeRemaining = TimeSpan.FromSeconds(telemetry.SessionTimeRemain.Value); 24 | CarIdxLastLapTime = telemetry.CarIdxLastLapTime.Value; 25 | CarIdxLapCompleted = telemetry.CarIdxLapCompleted.Value; 26 | IsOnTrack = telemetry.IsOnTrack.Value; 27 | SessionFlag = (SessionFlags) telemetry.SessionFlags.Value.Value; 28 | } 29 | 30 | public double FuelLevel { get; } 31 | public int CurrentLapNumber { get; } 32 | public bool IsOnPitRoad { get; } 33 | public bool IsReceivingService { get; } 34 | public TrackSurfaces TrackSurface { get; } 35 | public float PlayerTrackDistPct { get; } 36 | public int EnterExitResetButton { get; } 37 | public SessionStates SessionState { get; } 38 | public TimeSpan LastLapTime { get; } 39 | public TimeSpan SessionTimeRemaining { get; } 40 | public float[] CarIdxLastLapTime { get; } 41 | public int[] CarIdxLapCompleted { get; } 42 | public bool IsOnTrack { get; } 43 | public SessionFlags SessionFlag { get; } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Services/Base/SimReader.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using System; 3 | 4 | namespace SharpOverlay.Services.Base 5 | { 6 | public class SimReader 7 | { 8 | private readonly SdkWrapper _sdkWrapper; 9 | 10 | public int DriverId => _sdkWrapper.DriverId; 11 | 12 | public SimReader(int tickRate = 60) 13 | { 14 | _sdkWrapper = new SdkWrapper(); 15 | AdjustTickRate(tickRate); 16 | 17 | _sdkWrapper.Connected += ExecuteOnConnected; 18 | _sdkWrapper.Disconnected += ExecuteOnDisconnected; 19 | _sdkWrapper.TelemetryUpdated += ExecuteOnTelemetry; 20 | _sdkWrapper.SessionUpdated += ExecuteOnSession; 21 | 22 | _sdkWrapper.Start(); 23 | } 24 | 25 | public void AdjustTickRate(int newTickRate) 26 | { 27 | _sdkWrapper.TelemetryUpdateFrequency = newTickRate; 28 | } 29 | 30 | public SessionInfo GetSessionInfo() 31 | { 32 | return _sdkWrapper.GetSessionInfoWithoutEvent(); 33 | } 34 | 35 | public TelemetryInfo GetTelemetryInfo() 36 | { 37 | return _sdkWrapper.GetTelemetryInfoWithoutEvent(); 38 | } 39 | 40 | public event EventHandler? OnConnected; 41 | public event EventHandler? OnDisconnected; 42 | public event EventHandler? OnTelemetryUpdated; 43 | public event EventHandler? OnSessionUpdated; 44 | 45 | protected virtual void ExecuteOnConnected(object? sender, EventArgs args) 46 | { 47 | OnConnected?.Invoke(this, args); 48 | } 49 | 50 | protected virtual void ExecuteOnDisconnected(object? sender, EventArgs args) 51 | { 52 | OnDisconnected?.Invoke(this, args); 53 | } 54 | 55 | protected virtual void ExecuteOnTelemetry(object? sender, SdkWrapper.TelemetryUpdatedEventArgs e) 56 | { 57 | OnTelemetryUpdated?.Invoke(this, e); 58 | } 59 | 60 | protected virtual void ExecuteOnSession(object? sender, SdkWrapper.SessionUpdatedEventArgs e) 61 | { 62 | OnSessionUpdated?.Invoke(this, e); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Models/FuelViewModel.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using iRacingSdkWrapper.Bitfields; 3 | using SharpOverlay.Strategies; 4 | using System; 5 | using System.Collections.ObjectModel; 6 | 7 | namespace SharpOverlay.Models 8 | { 9 | public class FuelViewModel 10 | { 11 | 12 | public ObservableCollection Strategies { get; set; } 13 | 14 | public int LapsCompleted { get; set; } 15 | public double RaceLapsRemaining { get; set; } 16 | public double CurrentFuelLevel { get; set; } 17 | public double ConsumedFuel { get; set; } 18 | public float AverageFuelConsumption { get; set; } 19 | public double RefuelRequired { get; set; } 20 | public bool DoesRequireRefueling => RefuelRequired > 0; 21 | public double LapsOfFuelRemaining { get; set; } 22 | 23 | public float LastLapConsumption { get; set; } 24 | public double LastLapRefuelRequired { get; set; } 25 | public float LastLapLapsOfFuelRemaining { get; set; } 26 | 27 | public float FiveLapAverage { get; set; } 28 | public double FiveLapRefuelRequired { get; set; } 29 | public float FiveLapLapsOfFuelRemaining { get; set; } 30 | 31 | 32 | public Lap? CurrentLap { get; set; } 33 | public double LapsRemainingInRace { get; set; } 34 | public bool IsInService { get; set; } 35 | public bool HasBegunService { get; set; } 36 | public TimeSpan AverageLapTime { get; set; } 37 | public bool HasCompletedService { get; set; } 38 | public float AvgFuelPerLap { get; set; } 39 | public bool IsRollingStart { get; set; } 40 | public bool HasResetToPits { get; set; } 41 | public bool IsRaceStart { get; set; } 42 | public TimeSpan LeaderAvgLapTime { get; set; } 43 | public TimeSpan LeaderTimeToCompleteLap { get; set; } 44 | public TimeSpan EstLapTime { get; set; } 45 | public int CurrentSessionNumber { get; set; } 46 | public int LeaderIdx { get; set; } 47 | public int PlayerIdx { get; set; } 48 | public bool IsOnPitRoad { get; set; } 49 | public TrackSurfaces TrackSurface { get; set; } 50 | public SessionStates SessionState { get; set; } 51 | public SessionFlags SessionFlag { get; internal set; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SharpOverlay.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net8.0-windows10.0.17763.0 6 | SharpOverlay 7 | enable 8 | true 9 | 0.3.0.0 10 | $(AssemblyVersion) 11 | true 12 | SharpOverlay 13 | SharpOverlay.App 14 | Media\sharpoverlayicon.ico 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Dependencies\iRacingSdkWrapper.dll 42 | 43 | 44 | Dependencies\iRSDKSharp.dll 45 | 46 | 47 | Dependencies\YamlDotNet.Core.dll 48 | 49 | 50 | 51 | 52 | 53 | 54 | Always 55 | 56 | 57 | Always 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Services/FuelServices/LapServices/LapAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper.JsonModels; 2 | using SharpOverlay.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace SharpOverlay.Services.FuelServices.LapServices 8 | { 9 | public class LapAnalyzer : IClear 10 | { 11 | private readonly Dictionary> _driversLaps = []; 12 | 13 | public void Clear() 14 | { 15 | _driversLaps.Clear(); 16 | } 17 | 18 | public void CollectAllDriversLaps(Dictionary drivers, Dictionary lastLapTimes, int[] carIdxLapsCompleted) 19 | { 20 | foreach ((int idx, _) in drivers) 21 | { 22 | if (!_driversLaps.ContainsKey(idx)) 23 | { 24 | _driversLaps.Add(idx, new List()); 25 | } 26 | 27 | var laps = _driversLaps[idx]; 28 | int? lastLapNumber = laps.LastOrDefault()?.Number; 29 | 30 | if (carIdxLapsCompleted[idx] > (lastLapNumber ?? 0)) 31 | { 32 | int lapNumber = carIdxLapsCompleted[idx]; 33 | var lapTime = lastLapTimes[idx]; 34 | laps.Add(new Lap(lapNumber, lapTime)); 35 | } 36 | } 37 | } 38 | 39 | public int GetLeaderIdx(Dictionary positionIdx) 40 | { 41 | const int invalidLeaderPosition = -1; 42 | 43 | int leaderPosition = positionIdx.Keys.Count > 0 ? positionIdx.Keys.Min() : invalidLeaderPosition; 44 | 45 | if (leaderPosition > invalidLeaderPosition) 46 | { 47 | return positionIdx[leaderPosition]; 48 | } 49 | 50 | return invalidLeaderPosition; 51 | } 52 | 53 | public Dictionary> GetDriversLaps() 54 | => _driversLaps; 55 | 56 | public TimeSpan GetLapTime(int carIdx) 57 | { 58 | if (!_driversLaps.ContainsKey(carIdx) || carIdx < 0) 59 | { 60 | return TimeSpan.Zero; 61 | } 62 | 63 | var validLaps = _driversLaps[carIdx].Where(l => l.Time > TimeSpan.Zero); 64 | 65 | if (validLaps.Any()) 66 | { 67 | return TimeSpan.FromSeconds(validLaps 68 | .Average(l => l.Time.TotalSeconds)); 69 | } 70 | 71 | return TimeSpan.Zero; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Services/FuelServices/LapServices/LapCountCalculator.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper.Bitfields; 2 | using System; 3 | 4 | namespace SharpOverlay.Services.FuelServices.LapServices 5 | { 6 | public class LapCountCalculator 7 | { 8 | public int CalculateLapsRemaining(float driverPctOnTrack, TimeSpan timeRemainingInSession, TimeSpan averageLapTime) 9 | { 10 | if (averageLapTime > TimeSpan.Zero && 11 | timeRemainingInSession > TimeSpan.Zero) 12 | { 13 | TimeSpan timeToCompleteLap = (1 - driverPctOnTrack) * averageLapTime; 14 | 15 | double lapsBeforeRounding = (timeRemainingInSession - timeToCompleteLap) / averageLapTime + 1; 16 | 17 | int lapsRemaining = (int) Math.Ceiling(lapsBeforeRounding); 18 | 19 | return lapsRemaining; 20 | } 21 | 22 | return default; 23 | } 24 | 25 | public int CalculateLapsRemaining(int sessionLaps, int completedLaps) 26 | => sessionLaps - completedLaps; 27 | 28 | public int CalculateLapsRemainingMultiClass(TimeSpan timeLeftInSession, 29 | float raceLeaderPctOnTrack, float playerPctOnTrack, 30 | TimeSpan avgTimeRaceLeader, TimeSpan avgTimePlayer, SessionFlags flag) 31 | { 32 | if (avgTimeRaceLeader <= TimeSpan.Zero) 33 | { 34 | return 0; 35 | } 36 | 37 | var timeToCompleteLapLeader = (1 - raceLeaderPctOnTrack) * avgTimeRaceLeader; 38 | 39 | var timeRemainingAfterLineCross = timeLeftInSession - timeToCompleteLapLeader; 40 | 41 | if (timeRemainingAfterLineCross <= TimeSpan.Zero) 42 | { 43 | if (flag == SessionFlags.Green) 44 | { 45 | return 2; 46 | } 47 | 48 | return 1; 49 | } 50 | else if (avgTimeRaceLeader > timeRemainingAfterLineCross) 51 | { 52 | timeLeftInSession += avgTimeRaceLeader - timeRemainingAfterLineCross; 53 | } 54 | 55 | int leaderLapsRemaining = CalculateLapsRemaining(raceLeaderPctOnTrack, timeLeftInSession, avgTimeRaceLeader); 56 | 57 | var timeRequiredForLeader = leaderLapsRemaining * avgTimeRaceLeader; 58 | 59 | int playerLapsRemaining = CalculateLapsRemaining(playerPctOnTrack, timeRequiredForLeader, avgTimePlayer); 60 | 61 | return playerLapsRemaining; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Services/Base/WindowState.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using SharpOverlay.Models; 3 | using System.ComponentModel; 4 | 5 | namespace SharpOverlay.Services.Base 6 | { 7 | public class WindowState 8 | { 9 | public WindowState(BaseSettings settings) 10 | { 11 | UpdateIsOpen(settings.IsOpen); 12 | UpdateIsEnabled(settings.IsEnabled); 13 | UpdateIsInTestMode(settings.IsInTestMode); 14 | } 15 | 16 | public bool IsOpen { get; private set; } 17 | public bool IsEnabled { get; private set; } 18 | public bool IsInTestMode { get; private set; } 19 | 20 | public bool RequiresChange { get; private set; } 21 | 22 | public void Update(SdkWrapper.TelemetryUpdatedEventArgs eventArgs) 23 | { 24 | bool isCarOnTrack = eventArgs.TelemetryInfo.IsOnTrack.Value; 25 | 26 | if (!IsInTestMode) 27 | { 28 | UpdateIsOpen(isCarOnTrack); 29 | } 30 | } 31 | 32 | public void Update(PropertyChangedEventArgs eventArgs) 33 | { 34 | string propertyName = eventArgs.PropertyName!; 35 | 36 | if (propertyName == nameof(IsEnabled)) 37 | { 38 | UpdateIsEnabled(!IsEnabled); 39 | } 40 | else if (propertyName == nameof(IsOpen)) 41 | { 42 | UpdateIsOpen(!IsOpen); 43 | } 44 | else if (propertyName == nameof(IsInTestMode)) 45 | { 46 | UpdateIsInTestMode(!IsInTestMode); 47 | } 48 | } 49 | 50 | public void CompleteChange() 51 | { 52 | RequiresChange = false; 53 | } 54 | 55 | private void UpdateIsOpen(bool isOpen) 56 | { 57 | if (IsOpen == !isOpen) 58 | { 59 | IsOpen = isOpen; 60 | RaiseChange(); 61 | } 62 | } 63 | 64 | private void UpdateIsEnabled(bool isEnabled) 65 | { 66 | if (IsEnabled == !isEnabled) 67 | { 68 | IsEnabled = isEnabled; 69 | RaiseChange(); 70 | } 71 | } 72 | 73 | private void UpdateIsInTestMode(bool isInTestMode) 74 | { 75 | if (IsInTestMode == !isInTestMode) 76 | { 77 | IsInTestMode = isInTestMode; 78 | RaiseChange(); 79 | } 80 | } 81 | 82 | private void RaiseChange() 83 | { 84 | RequiresChange = true; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /FuelCalculator.xaml.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Events; 2 | using SharpOverlay.Models; 3 | using SharpOverlay.Services; 4 | using SharpOverlay.Services.Base; 5 | using SharpOverlay.Services.FuelServices; 6 | using SharpOverlay.Utilities; 7 | using System.ComponentModel; 8 | using System.Windows; 9 | using System.Windows.Input; 10 | 11 | namespace SharpOverlay 12 | { 13 | /// 14 | /// Interaction logic for FuelCalculator.xaml 15 | /// 16 | public partial class FuelCalculatorWindow : Window 17 | { 18 | private readonly FuelSettings _settings = App.appSettings.FuelSettings; 19 | 20 | private readonly IFuelCalculator _fuelCalculator; 21 | private readonly SimReader _simReader = new(DefaultTickRates.FuelCalculator); 22 | private readonly WindowStateService _windowStateService; 23 | private FuelDebugWindow? _fuelDebugWindow; 24 | 25 | public FuelCalculatorWindow() 26 | { 27 | _windowStateService = new WindowStateService(_simReader, _settings); 28 | _fuelCalculator = new FuelCalculatorService(_simReader); 29 | 30 | _fuelCalculator.FuelUpdated += OnFuelUpdate; 31 | 32 | _windowStateService.WindowStateChanged += OnWindowStateChange; 33 | 34 | _settings.PropertyChanged += OnPropertyChange; 35 | 36 | JotService.tracker.Track(this); 37 | 38 | Topmost = true; 39 | 40 | InitializeComponent(); 41 | } 42 | 43 | private void OnWindowStateChange(object? sender, WindowStateEventArgs e) 44 | { 45 | if (e.IsOpen && e.IsEnabled) 46 | { 47 | Show(); 48 | } 49 | else 50 | { 51 | Hide(); 52 | } 53 | } 54 | 55 | private void Window_MouseDown(object? sender, MouseButtonEventArgs e) 56 | { 57 | if (e.ChangedButton == MouseButton.Left) 58 | DragMove(); 59 | } 60 | 61 | private void OnFuelUpdate(object? sender, FuelEventArgs e) 62 | { 63 | this.DataContext = e.ViewModel; 64 | } 65 | 66 | private void OnPropertyChange(object? sender, PropertyChangedEventArgs e) 67 | { 68 | if (_settings.IsInTestMode) 69 | { 70 | _fuelDebugWindow = new FuelDebugWindow(_fuelCalculator); 71 | _fuelDebugWindow.Show(); 72 | } 73 | else if (_fuelDebugWindow is not null) 74 | { 75 | _fuelCalculator.FuelUpdated -= _fuelDebugWindow!.ExecuteOnFuelUpdated; 76 | _fuelDebugWindow.Hide(); 77 | _fuelDebugWindow = null; 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Services/FuelServices/PitServices/PitManager.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | 3 | namespace SharpOverlay.Services.FuelServices.PitServices 4 | { 5 | public class PitManager : IClear 6 | { 7 | private bool _isInService; 8 | private bool _hasBegunService; 9 | private bool _hasCompletedService; 10 | private bool _hasEnteredPits; 11 | private bool _isOnPitRoad; 12 | private bool _isComingOutOfPits; 13 | 14 | public void Clear() 15 | { 16 | _isInService = false; 17 | _hasBegunService = false; 18 | _hasCompletedService = false; 19 | _hasEnteredPits = false; 20 | _isOnPitRoad = false; 21 | _isComingOutOfPits = false; 22 | } 23 | 24 | public bool HasBegunService() 25 | => _hasBegunService; 26 | 27 | public bool HasFinishedService() 28 | => _hasCompletedService; 29 | public bool IsOnPitRoad() 30 | => _isOnPitRoad; 31 | 32 | public bool HasEnteredPits() 33 | => _hasEnteredPits; 34 | 35 | public bool HasResetToPits(int enterExitResetButton) 36 | => enterExitResetButton == 1 && !_hasEnteredPits; 37 | 38 | public void SetPitRoadStatus(bool isOnPitRoad, TrackSurfaces trackSurface) 39 | { 40 | if (!_hasEnteredPits && trackSurface == TrackSurfaces.AproachingPits) 41 | { 42 | _hasEnteredPits = true; 43 | } 44 | else if (!_isOnPitRoad && isOnPitRoad) 45 | { 46 | _isOnPitRoad = true; 47 | } 48 | else if (_isOnPitRoad && !isOnPitRoad && trackSurface != TrackSurfaces.InPitStall) 49 | { 50 | _isOnPitRoad = false; 51 | _hasEnteredPits = false; 52 | _isComingOutOfPits = true; 53 | } 54 | } 55 | 56 | public void SetPitServiceStatus(bool isReceivingPitService) 57 | { 58 | if (isReceivingPitService && !_isInService) 59 | { 60 | _hasBegunService = true; 61 | _isInService = true; 62 | } 63 | else if (!isReceivingPitService && _isInService) 64 | { 65 | _isInService = false; 66 | _hasCompletedService = true; 67 | } 68 | } 69 | 70 | public void ResetBegunServiceStatus() 71 | { 72 | _hasBegunService = false; 73 | } 74 | 75 | public void ResetFinishedServiceStatus() 76 | { 77 | _hasCompletedService = false; 78 | } 79 | 80 | public bool IsComingOutOfPits() 81 | => _isComingOutOfPits; 82 | 83 | public void ResetIsComingOutOfPits() 84 | { 85 | _isComingOutOfPits = false; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Services/JotService.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using Jot; 3 | using System.Windows; 4 | using System.Windows.Media; 5 | 6 | namespace SharpOverlay.Services 7 | { 8 | public static class JotService 9 | { 10 | public static Tracker tracker = new Tracker(); 11 | 12 | static JotService() 13 | { 14 | tracker.Configure() 15 | .Id(w => w.Name) 16 | .Properties(w => new { w.Top, w.Width, w.Height, w.Left, w.WindowState }) 17 | .PersistOn(nameof(Window.Closing)) 18 | .StopTrackingOn(nameof(Window.Closing)); 19 | 20 | tracker.Configure() 21 | .Property(p => p.BarSpotterSettings.IsEnabled, false) 22 | .Property(p => p.BarSpotterSettings.BarWidth, 20) 23 | .Property(p => p.BarSpotterSettings.BarLength, 450) 24 | .Property(p => p.BarSpotterSettings.BarColor, new SolidColorBrush(Colors.Orange)) 25 | .Property(p => p.BarSpotterSettings.ThreeWideBarColor, new SolidColorBrush(Colors.Red)) 26 | .Property(p => p.InputGraphSettings.IsEnabled, false) 27 | .Property(p => p.InputGraphSettings.UseRawValues, true) 28 | .Property(p => p.InputGraphSettings.ShowClutch, false) 29 | .Property(p => p.InputGraphSettings.BackgroundColor, new SolidColorBrush(Color.FromArgb(80, 0, 0, 0))) 30 | .Property(p => p.InputGraphSettings.ThrottleColor, new SolidColorBrush(Colors.Green)) 31 | .Property(p => p.InputGraphSettings.BrakeColor, new SolidColorBrush(Colors.Red)) 32 | .Property(p => p.InputGraphSettings.ClutchColor, new SolidColorBrush(Colors.Blue)) 33 | .Property(p => p.InputGraphSettings.ABSColor, new SolidColorBrush(Colors.Yellow)) 34 | .Property(p => p.InputGraphSettings.SteeringColor, new SolidColorBrush(Colors.Gray)) 35 | .Property(p => p.InputGraphSettings.ShowABS, false) 36 | .Property(p => p.InputGraphSettings.LineWidth, 3) 37 | .Property(p => p.InputGraphSettings.ShowPercentageThrottle, false) 38 | .Property(p => p.InputGraphSettings.ShowPercentageBrake, false) 39 | .Property(p => p.InputGraphSettings.ShowPercentageClutch, false) 40 | .Property(p => p.WindSettings.IsEnabled, false) 41 | .Property(p => p.WindSettings.UseMph, false) 42 | 43 | .Property(p => p.FuelSettings.IsEnabled, false) 44 | 45 | .Property(p => p.GeneralSettings.UseHardwareAcceleration, false) 46 | .PersistOn("PropertyChanged", p => p.GeneralSettings) 47 | 48 | .PersistOn("PropertyChanged", p => p.BarSpotterSettings) 49 | .PersistOn("PropertyChanged", p => p.InputGraphSettings) 50 | .PersistOn("PropertyChanged", p => p.WindSettings) 51 | .PersistOn("PropertyChanged", p => p.FuelSettings); 52 | 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Strategies/CoreStrategy.cs: -------------------------------------------------------------------------------- 1 | using SharpOverlay.Models; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SharpOverlay.Strategies 6 | { 7 | public abstract class CoreStrategy : IFuelStrategy 8 | { 9 | private readonly double _fuelCutOff; 10 | protected CoreStrategy(string name, double fuelCutOff) 11 | { 12 | Name = name; 13 | _fuelCutOff = fuelCutOff; 14 | } 15 | 16 | private string Name { get; } 17 | 18 | private double FuelConsumption { get; set; } 19 | 20 | private double LapsOfFuelRemaining { get; set; } 21 | 22 | private double RefuelRequired { get; set; } 23 | 24 | private double FuelAtEnd { get; set; } 25 | 26 | public void Calculate(List lapsCompleted, int sessionLapsRemaining) 27 | { 28 | FuelConsumption = GetAverageFuelConsumption(lapsCompleted); 29 | 30 | if (lapsCompleted.Count > 0) 31 | { 32 | Lap lastLap = lapsCompleted.Last(); 33 | 34 | double currentFuelLevel = lastLap.EndingFuel; 35 | 36 | UpdateRefuel(currentFuelLevel, sessionLapsRemaining); 37 | } 38 | } 39 | 40 | public void UpdateRefuel(double currentFuelLevel, int sessionLapsRemaining) 41 | { 42 | if (sessionLapsRemaining == 0) 43 | { 44 | RefuelRequired = 0; 45 | } 46 | else if (FuelConsumption > 0) 47 | { 48 | double fuelRequired = sessionLapsRemaining * FuelConsumption; 49 | 50 | FuelAtEnd = currentFuelLevel - fuelRequired; 51 | 52 | RefuelRequired = fuelRequired - currentFuelLevel; 53 | 54 | if (FuelAtEnd > 0 && FuelAtEnd < _fuelCutOff) 55 | { 56 | double difference = _fuelCutOff - FuelAtEnd; 57 | RefuelRequired += difference; 58 | } 59 | } 60 | 61 | UpdateLapsOfFuelRemaining(currentFuelLevel); 62 | } 63 | 64 | public StrategyViewModel GetView() 65 | => new StrategyViewModel() 66 | { 67 | Name = Name, 68 | FuelAtEnd = FuelAtEnd, 69 | RefuelAmount = RefuelRequired, 70 | LapsOfFuelRemaining = LapsOfFuelRemaining, 71 | FuelConsumption = FuelConsumption 72 | }; 73 | 74 | protected virtual double GetAverageFuelConsumption(List lapsCompleted) 75 | => lapsCompleted.Count > 0 ? lapsCompleted.Last().FuelUsed : default; 76 | 77 | public void UpdateLapsOfFuelRemaining(double currentFuelLevel) 78 | { 79 | if (FuelConsumption > 0) 80 | { 81 | LapsOfFuelRemaining = currentFuelLevel / FuelConsumption; 82 | } 83 | } 84 | 85 | public void Clear() 86 | { 87 | FuelAtEnd = 0; 88 | RefuelRequired = 0; 89 | LapsOfFuelRemaining = 0; 90 | FuelConsumption = 0; 91 | } 92 | 93 | public bool RequiresRefueling() 94 | => RefuelRequired > 0; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /BarSpotter.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Dark.Net; 2 | using SharpOverlay.Models; 3 | using SharpOverlay.Services; 4 | using System; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using Velopack; 8 | using Velopack.Sources; 9 | using Window = System.Windows.Window; 10 | 11 | namespace SharpOverlay 12 | { 13 | /// 14 | /// Interaction logic for MainWindow.xaml 15 | /// 16 | public partial class MainWindow : System.Windows.Window 17 | { 18 | public UpdateManager mgr = new UpdateManager(new GithubSource("https://github.com/tiberiuc39/sharpoverlay", null, false)); 19 | public MainWindow() 20 | { 21 | Services.JotService.tracker.Track(App.appSettings); 22 | Services.JotService.tracker.PersistAll(); 23 | DarkNet.Instance.SetWindowThemeWpf(this, Dark.Net.Theme.Auto); 24 | InitializeComponent(); 25 | HandleOverlayStatus(); 26 | this.DataContext = App.appSettings; 27 | if(App.appSettings.IsUpdate) 28 | { 29 | updateButton.Visibility = Visibility.Visible; 30 | } 31 | } 32 | 33 | private async void Window_Loaded(object sender, RoutedEventArgs e) 34 | { 35 | await CheckForUpdate(); 36 | } 37 | 38 | private async Task CheckForUpdate() 39 | { 40 | // check for new version 41 | var newVersion = mgr.CheckForUpdatesAsync().Result; 42 | if (newVersion == null) 43 | return; // no update available 44 | 45 | updateButton.Visibility = Visibility.Visible; 46 | 47 | } 48 | private async Task UpdateApp() 49 | { 50 | var newVersion = await mgr.CheckForUpdatesAsync(); 51 | await mgr.DownloadUpdatesAsync(newVersion); 52 | 53 | // install new version and restart app 54 | mgr.ApplyUpdatesAndRestart(newVersion); 55 | } 56 | public void HandleOverlayStatus() 57 | { 58 | OverlaysService.UpdateEnabledStatus(); 59 | foreach (Overlay o in OverlaysService.overlays) 60 | { 61 | if (o.IsEnabled && !o.IsOpen) 62 | { 63 | o.Window = (Window)Activator.CreateInstance(o.Type); 64 | var showMethod = o.Window.GetType().GetMethod("Show"); 65 | showMethod.Invoke(o.Window, null); 66 | o.IsOpen = true; 67 | 68 | o.Window.Visibility = Visibility.Hidden; 69 | } 70 | if (!o.IsEnabled && o.IsOpen) 71 | { 72 | var closeMethod = o.Window.GetType().GetMethod("Close"); 73 | closeMethod.Invoke(o.Window, null); 74 | o.Window = null; 75 | o.IsOpen = false; 76 | } 77 | } 78 | } 79 | 80 | private void windowToggle(object sender, RoutedEventArgs e) 81 | { 82 | //HandleOverlayStatus(); 83 | } 84 | 85 | private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) 86 | { 87 | var sInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.ToString()) 88 | { 89 | UseShellExecute = true, 90 | }; 91 | System.Diagnostics.Process.Start(sInfo); 92 | } 93 | 94 | private async void updateButton_Click(object sender, RoutedEventArgs e) 95 | { 96 | await UpdateApp(); 97 | } 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /Services/Spotter/BarSpotterService.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using SharpOverlay.Models; 3 | using SharpOverlay.Services.Base; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace SharpOverlay.Services.Spotter 9 | { 10 | public class BarSpotterService : IClear 11 | { 12 | private const int _carLengthInM = 5; 13 | private const int _outOfFrameOffset = 1; 14 | private readonly List _drivers = []; 15 | private Driver _me = new Driver(); 16 | private double _trackLengthInM; 17 | private Driver? _closest; 18 | private double _centerOffset = 1; 19 | 20 | public BarSpotterService(SimReader simReader) 21 | { 22 | simReader.OnTelemetryUpdated += OnTelemetry; 23 | simReader.OnSessionUpdated += OnSession; 24 | } 25 | 26 | public void Clear() 27 | { 28 | _drivers.Clear(); 29 | _me = new Driver(); 30 | _trackLengthInM = 0; 31 | _closest = null; 32 | _centerOffset = _outOfFrameOffset; 33 | } 34 | 35 | private void OnSession(object? sender, SdkWrapper.SessionUpdatedEventArgs e) 36 | { 37 | if (_trackLengthInM == 0) 38 | { 39 | _trackLengthInM = e.SessionInfo.WeekendInfo.TrackLength * 1000; 40 | } 41 | 42 | ParseDrivers(e); 43 | } 44 | 45 | private void ParseDrivers(SdkWrapper.SessionUpdatedEventArgs e) 46 | { 47 | _drivers.Clear(); 48 | 49 | foreach (var racer in e.SessionInfo.Drivers) 50 | { 51 | var driver = new Driver() 52 | { 53 | CarIdx = racer.CarIdx, 54 | Name = racer.UserName 55 | }; 56 | 57 | if (driver.CarIdx == e.SessionInfo.Player.DriverCarIdx) 58 | { 59 | _me = driver; 60 | } 61 | else if (driver.Name != "Pace Car") 62 | { 63 | _drivers.Add(driver); 64 | } 65 | } 66 | } 67 | 68 | private void OnTelemetry(object? sender, SdkWrapper.TelemetryUpdatedEventArgs e) 69 | { 70 | var driverTrackPct = e.TelemetryInfo.CarIdxLapDistPct.Value; 71 | 72 | CalculateRelativeDistanceForAllDrivers(driverTrackPct); 73 | 74 | _closest = FindClosest(); 75 | 76 | var distancePerPercentOfTrack = _trackLengthInM / 100; 77 | 78 | _centerOffset = CalculateOffset(_closest.RelativeLapDistancePct, distancePerPercentOfTrack); 79 | } 80 | 81 | private double CalculateOffset(float closestRelativePct, double distancePerPercentOfTrack) 82 | { 83 | var distanceToClosestInM = closestRelativePct * distancePerPercentOfTrack; 84 | var absoluteDistanceToClosest = Math.Abs(distanceToClosestInM); 85 | 86 | if (absoluteDistanceToClosest <= _carLengthInM) 87 | { 88 | return distanceToClosestInM / _carLengthInM; 89 | } 90 | 91 | return _outOfFrameOffset; 92 | } 93 | 94 | private Driver FindClosest() 95 | { 96 | var closest = _drivers.MinBy(d => Math.Abs(d.RelativeLapDistancePct)); 97 | 98 | return closest ?? new Driver() 99 | { 100 | RelativeLapDistancePct = 2 101 | }; 102 | } 103 | 104 | private void CalculateRelativeDistanceForAllDrivers(float[] driverTrackPct) 105 | { 106 | _me.LapDistancePct = driverTrackPct[_me.CarIdx]; 107 | 108 | foreach (var driver in _drivers) 109 | { 110 | driver.LapDistancePct = driverTrackPct[driver.CarIdx]; 111 | driver.RelativeLapDistancePct = driver.LapDistancePct - _me.LapDistancePct; 112 | } 113 | } 114 | 115 | public double CenterOffset() 116 | { 117 | return _centerOffset * 100; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Services/FuelServices/FuelRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text.Json; 4 | 5 | namespace SharpOverlay.Services.FuelServices 6 | { 7 | public class FuelRepository 8 | { 9 | private const string _fileName = "fuelHistory.json"; 10 | private readonly FuelContext _repository; 11 | 12 | public FuelRepository() 13 | { 14 | var list = InitializeRepository(); 15 | 16 | _repository = list; 17 | } 18 | 19 | public void AddOrUpdate(AddFuelHistoryDTO newData) 20 | { 21 | if (_repository.ByTrack.TryGetValue(newData.TrackId, out RaceHistory trackRaceHistory)) 22 | { 23 | AddOrUpdateEntry(newData, trackRaceHistory); 24 | } 25 | else 26 | { 27 | CreateNewEntry(newData); 28 | } 29 | } 30 | 31 | private void CreateNewEntry(AddFuelHistoryDTO newData) 32 | { 33 | var newModel = new FuelModel() 34 | { 35 | Consumption = newData.Consumption, 36 | LapCount = newData.LapCount, 37 | LapTime = newData.LapTime.TotalSeconds 38 | }; 39 | 40 | var newRace = new RaceHistory() 41 | { 42 | ByCarId = new Dictionary() 43 | { 44 | { newData.CarId, newModel } 45 | } 46 | }; 47 | 48 | _repository.ByTrack.TryAdd(newData.TrackId, newRace); 49 | } 50 | 51 | private static void AddOrUpdateEntry(AddFuelHistoryDTO newData, RaceHistory trackRaceHistory) 52 | { 53 | if (trackRaceHistory.ByCarId.TryGetValue(newData.CarId, out FuelModel entry)) 54 | { 55 | entry.LapCount = newData.LapCount; 56 | entry.LapTime = newData.LapTime.TotalSeconds; 57 | entry.Consumption = newData.Consumption; 58 | entry.PitStopTime = newData.PitStopTime.TotalSeconds; 59 | } 60 | else 61 | { 62 | var newModel = new FuelModel() 63 | { 64 | Consumption = newData.Consumption, 65 | LapCount = newData.LapCount, 66 | LapTime = newData.LapTime.TotalSeconds, 67 | PitStopTime = newData.PitStopTime.TotalSeconds, 68 | }; 69 | 70 | trackRaceHistory.ByCarId.TryAdd(newData.CarId, newModel); 71 | } 72 | } 73 | 74 | public FuelModel? Get(int trackId, int carId) 75 | { 76 | FuelModel? item = null; 77 | 78 | if (_repository.ByTrack.TryGetValue(trackId, out RaceHistory trackHistory)) 79 | { 80 | if (trackHistory.ByCarId.TryGetValue(carId, out item)); 81 | } 82 | 83 | return item; 84 | } 85 | 86 | public void Remove(int trackId) 87 | { 88 | _repository.ByTrack.Remove(trackId); 89 | } 90 | 91 | public void Save() 92 | { 93 | string filePath = "../" + _fileName; 94 | 95 | if (_repository.ByTrack.Count > 0) 96 | { 97 | var options = new JsonSerializerOptions() 98 | { 99 | WriteIndented = true 100 | }; 101 | 102 | string json = JsonSerializer.Serialize(_repository, options); 103 | 104 | File.WriteAllText(filePath, json); 105 | } 106 | } 107 | 108 | public void Update(FuelContext history) 109 | { 110 | throw new System.NotImplementedException(); 111 | } 112 | 113 | private FuelContext InitializeRepository() 114 | { 115 | string filePath = "../../../" + _fileName; 116 | FuelContext? context = null; 117 | 118 | try 119 | { 120 | string json = File.ReadAllText(filePath); 121 | 122 | if (!string.IsNullOrEmpty(json)) 123 | context = JsonSerializer.Deserialize(json); 124 | } 125 | catch (FileNotFoundException) 126 | { 127 | using (var stream = File.Create(filePath)); 128 | } 129 | 130 | return context ?? new FuelContext(); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /FuelDebug.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 22 | 23 | 31 | 32 | 45 | 46 | 47 | 48 | 49 | 77 | 78 | -------------------------------------------------------------------------------- /Utilities/Telemetries/TelemetryParser.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using iRacingSdkWrapper.Bitfields; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace SharpOverlay.Utilities.Telemetries 7 | { 8 | public class TelemetryParser : ITelemetryParser 9 | { 10 | public int PlayerCarClassId { get; private set; } 11 | public int PlayerCarIdx { get; private set; } 12 | public int CurrentSessionNumber { get; private set; } 13 | public bool HasSwitchedSessions { get; private set; } 14 | public Dictionary PositionCarIdxInClass { get; private set; } = []; 15 | public Dictionary PositionCarIdxInRace { get; private set; } = []; 16 | 17 | public double PlayerPctOnTrack { get; private set; } 18 | public float[] CarIdxPctOnTrack { get; private set; } = null!; 19 | 20 | public void ParsePositionCarIdxForWholeRace(TelemetryInfo telemetry, int paceCarIdx) 21 | { 22 | var carIdxPositions = telemetry.CarIdxPosition.Value; 23 | 24 | for (int idx = 0; idx < carIdxPositions.Length; idx++) 25 | { 26 | if (idx == paceCarIdx) 27 | continue; 28 | 29 | var currentPosition = carIdxPositions[idx]; 30 | 31 | if (currentPosition > 0) 32 | { 33 | if (!PositionCarIdxInRace.ContainsKey(currentPosition)) 34 | { 35 | PositionCarIdxInRace.Add(currentPosition, idx); 36 | } 37 | else 38 | { 39 | PositionCarIdxInRace[currentPosition] = idx; 40 | } 41 | } 42 | } 43 | } 44 | 45 | public void ParsePositionCarIdxInPlayerClass(TelemetryInfo telemetry, int paceCarIdx) 46 | { 47 | var carIdxClass = telemetry.CarIdxClass.Value; 48 | var carIdxPositions = telemetry.CarIdxPosition.Value; 49 | 50 | for (int idx = 0; idx < carIdxClass.Length; idx++) 51 | { 52 | if (idx == paceCarIdx) 53 | continue; 54 | 55 | if (carIdxClass[idx] == PlayerCarClassId) 56 | { 57 | var currentPosition = carIdxPositions[idx]; 58 | 59 | if (currentPosition > 0) 60 | { 61 | if (!PositionCarIdxInClass.ContainsKey(currentPosition)) 62 | { 63 | PositionCarIdxInClass.Add(currentPosition, idx); 64 | } 65 | else 66 | { 67 | PositionCarIdxInClass[currentPosition] = idx; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | public void ParseCarIdxOnTrack(TelemetryInfo telemetry) 75 | { 76 | CarIdxPctOnTrack = telemetry.CarIdxLapDistPct.Value; 77 | } 78 | 79 | 80 | 81 | public void ParsePlayerCarIdx(TelemetryInfo telemetry) 82 | { 83 | int playerIdx = telemetry.PlayerCarIdx.Value; 84 | 85 | PlayerCarIdx = playerIdx; 86 | } 87 | 88 | public void ParsePlayerCarClassId(TelemetryInfo telemetry) 89 | { 90 | int playerCarClass = telemetry.CarIdxClass.Value[PlayerCarIdx]; 91 | 92 | PlayerCarClassId = playerCarClass; 93 | } 94 | 95 | public void ParseCurrentSessionNumber(TelemetryInfo telemetry) 96 | { 97 | int currentSessionNumber = telemetry.SessionNum.Value; 98 | 99 | if (CurrentSessionNumber != currentSessionNumber) 100 | { 101 | HasSwitchedSessions = true; 102 | } 103 | else if (HasSwitchedSessions) 104 | { 105 | HasSwitchedSessions = false; 106 | } 107 | 108 | CurrentSessionNumber = currentSessionNumber; 109 | } 110 | 111 | public static Dictionary GetDriversLastLapTime(int paceCarIdx, float[] lapTimes) 112 | { 113 | var driversLastLaps = new Dictionary(); 114 | 115 | for (int idx = 0; idx < lapTimes.Length; idx++) 116 | { 117 | if (idx == paceCarIdx) 118 | continue; 119 | 120 | float lapTime = lapTimes[idx]; 121 | 122 | driversLastLaps.Add(idx, TimeSpan.FromSeconds(lapTime)); 123 | } 124 | 125 | return driversLastLaps; 126 | } 127 | 128 | public SessionFlags GetSessionFlag(TelemetryInfo telemetry) 129 | { 130 | var flag = telemetry.SessionFlags.Value; 131 | 132 | return (SessionFlags)flag.Value; 133 | } 134 | 135 | public void Clear() 136 | { 137 | PlayerCarIdx = -1; 138 | PlayerCarClassId = 0; 139 | PositionCarIdxInClass.Clear(); 140 | PositionCarIdxInRace.Clear(); 141 | HasSwitchedSessions = false; 142 | PlayerPctOnTrack = 0; 143 | CurrentSessionNumber = 0; 144 | } 145 | 146 | public void ParsePlayerPctOnTrack(TelemetryInfo telemetry) 147 | { 148 | PlayerPctOnTrack = telemetry.CarIdxLapDistPct.Value[PlayerCarIdx]; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Wind.xaml.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using SharpOverlay.Events; 3 | using SharpOverlay.Models; 4 | using SharpOverlay.Services.Base; 5 | using SharpOverlay.Utilities; 6 | using System; 7 | using System.ComponentModel; 8 | using System.Windows; 9 | using System.Windows.Input; 10 | using System.Windows.Media; 11 | 12 | namespace SharpOverlay 13 | { 14 | /// 15 | /// Interaction logic for Wind.xaml 16 | /// 17 | public partial class Wind : Window 18 | { 19 | private readonly SimReader _simReader = new SimReader(DefaultTickRates.Wind); 20 | private readonly WindowStateService _windowStateService; 21 | private WindSettings _settings = App.appSettings.WindSettings; 22 | 23 | private readonly Color StartColor = Color.FromArgb(255, 0, 255, 0); 24 | private readonly Color CenterColor = Color.FromArgb(255, 255, 255, 0); 25 | private readonly Color EndColor = Color.FromArgb(255, 255, 0, 0); 26 | 27 | private double windDir; 28 | private float yawNorth; 29 | private float windSpeed; 30 | private float lastWindSpeed = 999; 31 | public Wind() 32 | { 33 | InitializeComponent(); 34 | Services.JotService.tracker.Track(this); 35 | 36 | _windowStateService = new WindowStateService(_simReader, _settings); 37 | 38 | _simReader.OnTelemetryUpdated += Wrapper_TelemetryUpdated; 39 | 40 | _settings.PropertyChanged += settings_TestMode; 41 | _windowStateService.WindowStateChanged += OnWindowStateChanged; 42 | 43 | WindSpeedLabel.Foreground = new SolidColorBrush(StartColor); 44 | WindDirIcon.Foreground = new SolidColorBrush(StartColor); 45 | } 46 | 47 | private void OnWindowStateChanged(object? sender, WindowStateEventArgs e) 48 | { 49 | if (e.IsOpen && e.IsEnabled) 50 | { 51 | Show(); 52 | } 53 | else 54 | { 55 | Hide(); 56 | } 57 | } 58 | 59 | private void settings_TestMode(object? sender, PropertyChangedEventArgs e) 60 | { 61 | if (_settings.IsInTestMode) 62 | { 63 | WindWindow.BorderThickness = new Thickness(5); 64 | } 65 | else 66 | { 67 | WindWindow.BorderThickness = new Thickness(0); 68 | } 69 | } 70 | 71 | private void Wrapper_TelemetryUpdated(object? sender, SdkWrapper.TelemetryUpdatedEventArgs e) 72 | { 73 | 74 | windDir = (e.TelemetryInfo.WindDir.Value + Math.PI / 180); 75 | yawNorth = e.TelemetryInfo.YawNorth.Value; 76 | windSpeed = e.TelemetryInfo.WindVel.Value * 3.6f; 77 | 78 | double finalAngle = (windDir - yawNorth) * 57.2958 + 180; 79 | ((RotateTransform)((TransformGroup)WindDirIcon.RenderTransform).Children[0]).Angle = finalAngle; 80 | if (Math.Abs(windSpeed - lastWindSpeed) > 1) 81 | { 82 | lastWindSpeed = windSpeed; 83 | SetColor(windSpeed / 40); 84 | if (_settings.UseMph) 85 | { 86 | WindSpeedLabel.Content = (int)(windSpeed * 0.62); 87 | } 88 | else 89 | { 90 | WindSpeedLabel.Content = (int)windSpeed; 91 | } 92 | } 93 | } 94 | 95 | private void SetColor(double percentage) 96 | { 97 | var correctColor = GradientPick(percentage, StartColor, CenterColor, EndColor); 98 | WindDirIcon.Foreground = new SolidColorBrush(correctColor); 99 | WindSpeedLabel.Foreground = new SolidColorBrush(correctColor); 100 | } 101 | 102 | private void Window_MouseDown(object sender, MouseButtonEventArgs e) 103 | { 104 | if (e.ChangedButton == MouseButton.Left) 105 | DragMove(); 106 | } 107 | 108 | private void Window_SizeChanged(object sender, SizeChangedEventArgs e) 109 | { 110 | // Maintain a 1:1 aspect ratio 111 | if (e.WidthChanged) 112 | { 113 | this.Height = this.ActualWidth; 114 | } 115 | else if (e.HeightChanged) 116 | { 117 | this.Width = this.ActualHeight; 118 | } 119 | } 120 | 121 | public byte LinearInterp(byte start, byte end, double percentage) => (byte)(start + (int)Math.Round(percentage * (end - start))); 122 | public Color ColorInterp(Color start, Color end, double percentage) => 123 | Color.FromArgb(LinearInterp(start.A, end.A, percentage), 124 | LinearInterp(start.R, end.R, percentage), 125 | LinearInterp(start.G, end.G, percentage), 126 | LinearInterp(start.B, end.B, percentage)); 127 | public Color GradientPick(double percentage, Color Start, Color Center, Color End) 128 | { 129 | if (percentage < 0.5) 130 | return ColorInterp(Start, Center, percentage / 0.5); 131 | else if (percentage == 0.5) 132 | return Center; 133 | else if (percentage > 1) 134 | return End; 135 | else 136 | return ColorInterp(Center, End, (percentage - 0.5) / 0.5); 137 | 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Utilities/Sessions/SessionParser.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using iRacingSdkWrapper.JsonModels; 3 | using SharpOverlay.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text.Json; 9 | 10 | namespace SharpOverlay.Utilities.Sessions 11 | { 12 | public class SessionParser : ISessionParser 13 | { 14 | public List Sessions { get; private set; } = []; 15 | public Dictionary Drivers { get; private set; } = []; 16 | public StartType StartType { get; private set; } 17 | public string EventType { get; private set; } 18 | public SessionType SessionType { get; private set; } 19 | public int SessionLaps { get; private set; } 20 | public int PaceCarIdx { get; private set; } 21 | public bool IsMultiClassRace { get; private set; } 22 | public List Sectors { get; private set; } 23 | public int TrackId { get; private set; } 24 | public int CarId { get; private set; } 25 | 26 | public bool IsSetupChanged { get; private set; } 27 | 28 | public void ParseSectors(SessionInfo sessionInfo) 29 | { 30 | Sectors = sessionInfo.Sectors; 31 | } 32 | 33 | public void ParseDrivers(SessionInfo sessionInfo) 34 | { 35 | var drivers = sessionInfo.Drivers.ToDictionary(d => d.CarIdx, d => d); 36 | 37 | Drivers = drivers; 38 | } 39 | 40 | public void ParsePaceCarIdx(SessionInfo sessionInfo) 41 | { 42 | PaceCarIdx = sessionInfo.Player.PaceCarIdx; 43 | } 44 | 45 | public void ParseSessions(SessionInfo sessionInfo) 46 | { 47 | //SaveSessionInfoToJsonString(sessionInfo); 48 | 49 | Sessions = sessionInfo.Sessions; 50 | } 51 | 52 | 53 | public TimeSpan GetBestLapTime(int carIdx, int currentSessionNumber = default) 54 | { 55 | if (Sessions.Count > 0) 56 | { 57 | var currentSession = Sessions[currentSessionNumber]; 58 | 59 | if (currentSession.ResultsPositions != null) 60 | { 61 | var resultEntry = currentSession.ResultsPositions.Where(r => r.CarIdx == carIdx).FirstOrDefault(); 62 | 63 | if (resultEntry != null) 64 | { 65 | var bestTime = TimeSpan.FromSeconds(resultEntry.FastestTime); 66 | 67 | return bestTime; 68 | } 69 | } 70 | } 71 | 72 | return TimeSpan.FromSeconds(-1); 73 | } 74 | 75 | public void ParseStartType(SessionInfo sessionInfo) 76 | { 77 | int standingStartValue = sessionInfo.WeekendInfo.WeekendOptions.StandingStart; 78 | 79 | switch (standingStartValue) 80 | { 81 | case 0: 82 | StartType = StartType.Rolling; 83 | break; 84 | case 1: 85 | StartType = StartType.Standing; 86 | break; 87 | default: 88 | StartType = StartType.Unknown; 89 | break; 90 | } 91 | } 92 | 93 | public void ParseCurrentSessionType(SessionInfo sessionInfo, int currentSessionNumber = default) 94 | { 95 | string session = sessionInfo.Sessions[currentSessionNumber].SessionType; 96 | 97 | if (Enum.TryParse(session, out SessionType sessionType)) 98 | { 99 | SessionType = sessionType; 100 | } 101 | } 102 | 103 | public void ParseLapsInSession(SessionInfo sessionInfo, int currentSessionNumber = default) 104 | { 105 | var currentSession = sessionInfo.Sessions[currentSessionNumber]; 106 | 107 | string currentSessionLaps = currentSession.SessionLaps; 108 | 109 | const int unlimitedLaps = -1; 110 | 111 | if (int.TryParse(currentSessionLaps, out int lapsInCurrentSession)) 112 | { 113 | SessionLaps = lapsInCurrentSession; 114 | } 115 | else 116 | { 117 | SessionLaps = unlimitedLaps; 118 | } 119 | } 120 | 121 | private void SaveSessionInfoToJsonString(SessionInfo sessionInfo) 122 | { 123 | var jsonString = JsonSerializer.Serialize(sessionInfo, new JsonSerializerOptions() 124 | { 125 | WriteIndented = true 126 | }); 127 | 128 | File.WriteAllText($"../../../{sessionInfo.WeekendInfo.TrackName + sessionInfo.WeekendInfo.WeekendOptions.Date}.txt", jsonString); 129 | } 130 | 131 | public void Clear() 132 | { 133 | Sessions.Clear(); 134 | Drivers.Clear(); 135 | SessionLaps = -1; 136 | SessionType = SessionType.Invalid; 137 | StartType = StartType.Unknown; 138 | } 139 | 140 | public void ParseRaceType(SessionInfo sessionInfo) 141 | { 142 | IsMultiClassRace = sessionInfo.WeekendInfo.NumCarClasses > 1; 143 | } 144 | 145 | public void ParseTrackId(SessionInfo sessionInfo) 146 | { 147 | TrackId = sessionInfo.WeekendInfo.TrackID; 148 | } 149 | 150 | public void ParseCarId(SessionInfo sessionInfo) 151 | { 152 | var driver = sessionInfo.Drivers.FirstOrDefault(d => d.CarIdx == sessionInfo.Player.DriverCarIdx); 153 | 154 | if (driver != null) 155 | { 156 | CarId = driver.CarID; 157 | } 158 | } 159 | 160 | public void ParseEventType(SessionInfo sessionInfo) 161 | { 162 | EventType = sessionInfo.WeekendInfo.EventType; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /FuelCalculator.xaml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 28 | 29 | 37 | 38 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /BarSpotter.xaml.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using SharpOverlay.Events; 3 | using SharpOverlay.Models; 4 | using SharpOverlay.Services.Base; 5 | using SharpOverlay.Services.Spotter; 6 | using SharpOverlay.Utilities; 7 | using System; 8 | using System.Windows; 9 | using System.Windows.Controls; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using Rectangle = System.Windows.Shapes.Rectangle; 13 | 14 | namespace SharpOverlay 15 | { 16 | /// 17 | /// Interaction logic for BarSpotter.xaml 18 | /// 19 | public partial class BarSpotter : Window 20 | { 21 | private readonly SimReader _simReader = new(DefaultTickRates.BarSpotter); 22 | private readonly WindowStateService _windowStateService; 23 | private readonly BarSpotterService _service; 24 | 25 | private readonly BarSpotterSettings _settings = App.appSettings.BarSpotterSettings; 26 | 27 | private Enums.CarLeftRight carLeftRight; 28 | 29 | public BarSpotter() 30 | { 31 | InitializeComponent(); 32 | this.DataContext = App.appSettings.BarSpotterSettings; 33 | Services.JotService.tracker.Track(this); 34 | 35 | _service = new BarSpotterService(_simReader); 36 | _windowStateService = new WindowStateService(_simReader, _settings); 37 | 38 | _simReader.OnConnected += OnConnected; 39 | _simReader.OnDisconnected += OnDisconnected; 40 | _simReader.OnTelemetryUpdated += OnTelemetryUpdated; 41 | 42 | _windowStateService.WindowStateChanged += OnWindowStateChanged; 43 | 44 | barSpotterWindow.SizeChanged += window_SetBarEqualToWindow; 45 | } 46 | 47 | private void OnWindowStateChanged(object? sender, WindowStateEventArgs e) 48 | { 49 | if (e.IsInTestMode && e.IsEnabled) 50 | { 51 | HandleTestMode(); 52 | } 53 | else if (e.IsOpen && e.IsEnabled) 54 | { 55 | Show(); 56 | } 57 | else 58 | { 59 | Hide(); 60 | } 61 | } 62 | 63 | private void OnConnected(object? sender, EventArgs e) 64 | { 65 | CarClear(); 66 | } 67 | 68 | private void OnDisconnected(object? sender, EventArgs e) 69 | { 70 | _service.Clear(); 71 | } 72 | 73 | private void Window_MouseDown(object? sender, MouseButtonEventArgs e) 74 | { 75 | if (e.ChangedButton == MouseButton.Left) 76 | DragMove(); 77 | } 78 | 79 | private void HandleTestMode() 80 | { 81 | if (_settings.IsInTestMode && _settings.IsEnabled) 82 | { 83 | RenderBothBars(); 84 | Show(); 85 | leftCanvas.Visibility = Visibility.Visible; 86 | rightCanvas.Visibility = Visibility.Visible; 87 | } 88 | else 89 | { 90 | CarClear(); 91 | } 92 | } 93 | 94 | private void window_SetBarEqualToWindow(object? sender, EventArgs e) 95 | { 96 | if (_settings.IsInTestMode) 97 | { 98 | leftFill.Height = _settings.BarLength; 99 | rightFill.Height = _settings.BarLength; 100 | } 101 | } 102 | 103 | private void OnTelemetryUpdated(object? sender, SdkWrapper.TelemetryUpdatedEventArgs e) 104 | { 105 | if (!_settings.IsInTestMode) 106 | { 107 | carLeftRight = (Enums.CarLeftRight)e.TelemetryInfo.CarLeftRight.Value; 108 | 109 | if (carLeftRight == Enums.CarLeftRight.irsdk_LRClear) 110 | { 111 | CarClear(); 112 | return; 113 | } 114 | 115 | if (carLeftRight == Enums.CarLeftRight.irsdk_LRCarLeft) 116 | { 117 | double pixelOffset = PixelOffset(); 118 | 119 | RenderLeftBar(pixelOffset); 120 | 121 | rightCanvas.Visibility = Visibility.Hidden; 122 | leftCanvas.Visibility = Visibility.Visible; 123 | } 124 | else if (carLeftRight == Enums.CarLeftRight.irsdk_LRCarRight) 125 | { 126 | double offset = _service.CenterOffset(); 127 | 128 | var pixelOffset = grid.ActualHeight * -offset; 129 | 130 | RenderRightBar(pixelOffset); 131 | rightCanvas.Visibility = Visibility.Visible; 132 | leftCanvas.Visibility = Visibility.Hidden; 133 | } 134 | else if (carLeftRight == Enums.CarLeftRight.irsdk_LRCarLeftRight) 135 | { 136 | RenderBothBars(); 137 | } 138 | else if (carLeftRight == Enums.CarLeftRight.irsdk_LR2CarsLeft) 139 | { 140 | RenderLeftBar(0, _settings.ThreeWideBarColor); 141 | leftCanvas.Visibility = Visibility.Visible; 142 | rightCanvas.Visibility = Visibility.Hidden; 143 | } 144 | else if (carLeftRight == Enums.CarLeftRight.irsdk_LR2CarsRight) 145 | { 146 | RenderRightBar(0, _settings.ThreeWideBarColor); 147 | rightCanvas.Visibility = Visibility.Visible; 148 | leftCanvas.Visibility = Visibility.Hidden; 149 | } 150 | } 151 | } 152 | 153 | private void CarClear() 154 | { 155 | RenderLeftBar(grid.ActualHeight); 156 | RenderRightBar(grid.ActualHeight); 157 | 158 | leftCanvas.Visibility = Visibility.Hidden; 159 | rightCanvas.Visibility = Visibility.Hidden; 160 | } 161 | 162 | private double PixelOffset() 163 | { 164 | double offset = _service.CenterOffset(); 165 | 166 | var pixelOffset = grid.ActualHeight * -offset; 167 | return pixelOffset; 168 | } 169 | 170 | private void RenderLeftBar(double offset) 171 | { 172 | RenderBar(leftFill, offset); 173 | } 174 | 175 | private void RenderLeftBar(double offset, Brush color) 176 | { 177 | RenderBar(leftFill, offset, color); 178 | } 179 | private void RenderRightBar(double offset, Brush color) 180 | { 181 | RenderBar(rightFill, offset, color); 182 | } 183 | 184 | private void RenderRightBar(double offset) 185 | { 186 | RenderBar(rightFill, offset); 187 | } 188 | 189 | private void RenderBothBars() 190 | { 191 | leftFill.Fill = _settings.ThreeWideBarColor; 192 | rightFill.Fill = _settings.ThreeWideBarColor; 193 | 194 | Canvas.SetTop(leftFill, 0); 195 | Canvas.SetTop(rightFill, 0); 196 | 197 | leftCanvas.Visibility = Visibility.Visible; 198 | rightCanvas.Visibility = Visibility.Visible; 199 | } 200 | 201 | private void RenderBar(Rectangle rect, double offset) 202 | { 203 | RenderBar(rect, offset, _settings.BarColor); 204 | } 205 | 206 | private void RenderBar(Rectangle rect, double offset, Brush color) 207 | { 208 | rect.Fill = color; 209 | Canvas.SetTop(rect, offset); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /InputGraph.xaml.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using ScottPlot.Plottables; 3 | using SharpOverlay.Events; 4 | using SharpOverlay.Models; 5 | using SharpOverlay.Services.Base; 6 | using SharpOverlay.Utilities; 7 | using System; 8 | using System.ComponentModel; 9 | using System.Windows; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | 13 | namespace SharpOverlay 14 | { 15 | /// 16 | /// Interaction logic for InputGraph.xaml 17 | /// 18 | public partial class InputGraph : Window 19 | { 20 | private DataStreamer throttleStreamer; 21 | private DataStreamer brakeStreamer; 22 | private DataStreamer clutchStreamer; 23 | private DataStreamer steeringStreamer; 24 | private DataStreamer absStreamer; 25 | 26 | private Input input = new Input(); 27 | 28 | private readonly InputGraphSettings _settings = App.appSettings.InputGraphSettings; 29 | private readonly SimReader _simReader = new SimReader(DefaultTickRates.InputGraph); 30 | private readonly WindowStateService _windowStateService; 31 | 32 | public InputGraph() 33 | { 34 | InitializeComponent(); 35 | Services.JotService.tracker.Track(this); 36 | 37 | _windowStateService = new WindowStateService(_simReader, _settings); 38 | 39 | _simReader.OnTelemetryUpdated += iracingWrapper_TelemetryUpdated; 40 | 41 | _windowStateService.WindowStateChanged += ExecuteOnStateChange; 42 | 43 | _settings.PropertyChanged += graph_HandleSettingUpdated; 44 | 45 | HookStreamer(ref throttleStreamer, _settings.ThrottleColor, true); 46 | HookStreamer(ref brakeStreamer, _settings.BrakeColor, true); 47 | HookStreamer(ref clutchStreamer, _settings.ClutchColor, _settings.ShowClutch); 48 | HookStreamer(ref steeringStreamer, _settings.SteeringColor, _settings.ShowSteering); 49 | HookStreamer(ref absStreamer, _settings.SteeringColor, true); 50 | 51 | PlotSetup(); 52 | SetColorPercentageLabels(); 53 | } 54 | 55 | private void ExecuteOnStateChange(object? sender, WindowStateEventArgs args) 56 | { 57 | if (args.IsOpen && args.IsEnabled) 58 | { 59 | Show(); 60 | } 61 | else 62 | { 63 | Hide(); 64 | } 65 | } 66 | 67 | private void graph_HandleSettingUpdated(object? sender, PropertyChangedEventArgs e) 68 | { 69 | SetStreamerColorAndWidth(ref throttleStreamer, _settings.ThrottleColor); 70 | SetStreamerColorAndWidth(ref brakeStreamer, _settings.BrakeColor); 71 | SetStreamerColorAndWidth(ref clutchStreamer, _settings.ClutchColor); 72 | SetStreamerColorAndWidth(ref steeringStreamer, _settings.SteeringColor); 73 | SetStreamerColorAndWidth(ref absStreamer, _settings.ABSColor); 74 | 75 | //InputPlot.Plot.DataBackground.Color = TransformColor(App.appSettings.InputGraphSettings.BackgroundColor); 76 | InputPlot.Plot.FigureBackground.Color = TransformColor(_settings.BackgroundColor); 77 | InputPlot.Refresh(); 78 | 79 | if (_settings.ShowClutch) 80 | { 81 | clutchStreamer.IsVisible = true; 82 | } 83 | else if (!_settings.ShowClutch) 84 | { 85 | clutchStreamer.IsVisible = false; 86 | } 87 | 88 | if (_settings.ShowSteering) 89 | { 90 | steeringStreamer.IsVisible = true; 91 | } 92 | else if (!_settings.ShowSteering) 93 | { 94 | steeringStreamer.IsVisible = false; 95 | } 96 | 97 | SetColorPercentageLabels(); 98 | } 99 | 100 | private void UpdateInputs(TelemetryInfo telemetryInfo) 101 | { 102 | if (_settings.UseRawValues) 103 | { 104 | input.Brake = telemetryInfo.BrakeRaw.Value * 100; 105 | input.Throttle = telemetryInfo.ThrottleRaw.Value * 100; 106 | input.Clutch = (1 - telemetryInfo.ClutchRaw.Value) * 100; 107 | } 108 | else 109 | { 110 | input.Brake = telemetryInfo.Brake.Value * 100; 111 | input.Throttle = telemetryInfo.Throttle.Value * 100; 112 | input.Clutch = (1 - telemetryInfo.Clutch.Value) * 100; 113 | } 114 | 115 | input.Steering = telemetryInfo.SteeringWheelAngle.Value * 10 + 50; 116 | input.ABS = telemetryInfo.BrakeABSactive.Value ? input.Brake : 0; 117 | 118 | if (BrakePercentage.IsVisible) 119 | { 120 | BrakePercentage.Content = $"B: {Math.Round(input.Brake, 0)} %"; 121 | } 122 | 123 | if (ThrottlePercentage.IsVisible) 124 | { 125 | ThrottlePercentage.Content = $"T: {Math.Round(input.Throttle, 0)} %"; 126 | } 127 | 128 | if (ClutchPercentage.IsVisible) 129 | { 130 | ClutchPercentage.Content = $"C: {Math.Round(input.ABS, 0)} %"; 131 | } 132 | } 133 | 134 | private void AddInputsToStreamers(Input input) 135 | { 136 | throttleStreamer.Add(input.Throttle); 137 | brakeStreamer.Add(input.Brake); 138 | if (input.ABS != 0) 139 | 140 | absStreamer.Add(input.ABS); 141 | else 142 | { 143 | absStreamer.Add(double.NaN); 144 | } 145 | 146 | if (_settings.ShowClutch) 147 | { 148 | clutchStreamer.Add(input.Clutch); 149 | } 150 | 151 | if (_settings.ShowSteering) 152 | { 153 | steeringStreamer.Add(input.Steering); 154 | } 155 | } 156 | 157 | private void iracingWrapper_TelemetryUpdated(object? sender, SdkWrapper.TelemetryUpdatedEventArgs e) 158 | { 159 | UpdateInputs(e.TelemetryInfo); 160 | AddInputsToStreamers(input); 161 | InputPlot.Refresh(); 162 | } 163 | 164 | private void Window_MouseDown(object? sender, MouseButtonEventArgs e) 165 | { 166 | if (e.ChangedButton == MouseButton.Left) 167 | DragMove(); 168 | } 169 | 170 | private void PlotSetup() 171 | { 172 | InputPlot.Menu.Clear(); 173 | 174 | InputPlot.Plot.FigureBackground.Color = TransformColor(_settings.BackgroundColor); 175 | InputPlot.Plot.Axes.Frameless(); 176 | 177 | InputPlot.Plot.Axes.SetLimitsY(-5, 105); 178 | InputPlot.Plot.Axes.SetLimitsX(0, 500); 179 | 180 | InputPlot.UserInputProcessor.IsEnabled = false; 181 | InputPlot.Interaction.IsEnabled = false; 182 | } 183 | 184 | private void HookStreamer(ref DataStreamer ds, SolidColorBrush color, bool isVisible) 185 | { 186 | ds = InputPlot.Plot.Add.DataStreamer(500); 187 | 188 | ds.Color = TransformColor(color); 189 | 190 | ds.LineWidth = _settings.LineWidth; 191 | 192 | ds.ViewScrollLeft(); 193 | 194 | ds.ManageAxisLimits = false; 195 | 196 | ds.IsVisible = isVisible; 197 | } 198 | 199 | private void SetStreamerColorAndWidth(ref DataStreamer dataStreamer, SolidColorBrush color) 200 | { 201 | dataStreamer.Color = TransformColor(color); 202 | 203 | dataStreamer.LineWidth = _settings.LineWidth; 204 | } 205 | 206 | private ScottPlot.Color TransformColor(SolidColorBrush color) 207 | { 208 | return new ScottPlot.Color(color.Color.R, color.Color.G, color.Color.B, color.Color.A); 209 | } 210 | 211 | private void SetColorPercentageLabels() 212 | { 213 | ThrottlePercentage.Visibility = _settings.ShowPercentageThrottle ? Visibility.Visible : Visibility.Hidden; 214 | ThrottlePercentage.Foreground = _settings.ThrottleColor; 215 | 216 | BrakePercentage.Visibility = _settings.ShowPercentageBrake ? Visibility.Visible : Visibility.Hidden; 217 | BrakePercentage.Foreground = _settings.BrakeColor; 218 | 219 | ClutchPercentage.Visibility = _settings.ShowPercentageClutch ? Visibility.Visible : Visibility.Hidden; 220 | ClutchPercentage.Foreground = _settings.ClutchColor; 221 | 222 | SteeringPercentage.Visibility = _settings.ShowPercentageSteering ? Visibility.Visible : Visibility.Hidden; 223 | SteeringPercentage.Foreground = _settings.SteeringColor; 224 | } 225 | } 226 | } -------------------------------------------------------------------------------- /Models/Settings.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Media; 3 | using Colors = System.Windows.Media.Colors; 4 | 5 | namespace SharpOverlay.Models 6 | { 7 | public abstract class BaseSettings : INotifyPropertyChanged 8 | { 9 | public event PropertyChangedEventHandler? PropertyChanged; 10 | 11 | protected virtual void OnPropertyChanged(string propertyName) 12 | { 13 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 14 | } 15 | 16 | private bool _isEnabled; 17 | 18 | public bool IsEnabled 19 | { 20 | get => _isEnabled; 21 | set 22 | { 23 | _isEnabled = value; 24 | OnPropertyChanged(nameof(IsEnabled)); 25 | } 26 | } 27 | 28 | private bool _isOpen; 29 | public bool IsOpen 30 | { 31 | get => _isOpen; 32 | set 33 | { 34 | _isOpen = value; 35 | OnPropertyChanged(nameof(IsOpen)); 36 | } 37 | } 38 | 39 | private bool _isInTestMode; 40 | public bool IsInTestMode 41 | { 42 | get => _isInTestMode; 43 | set 44 | { 45 | _isInTestMode = value; 46 | OnPropertyChanged(nameof(IsInTestMode)); 47 | } 48 | } 49 | } 50 | public class BarSpotterSettings : BaseSettings 51 | { 52 | 53 | private double _barWidth; 54 | 55 | private double _barLength; 56 | 57 | private SolidColorBrush? _barColor; 58 | 59 | private SolidColorBrush? _threeWideBarColor; 60 | 61 | 62 | public double BarWidth 63 | { 64 | get => _barWidth; 65 | set 66 | { 67 | _barWidth = value; 68 | OnPropertyChanged(nameof(BarWidth)); 69 | } 70 | } 71 | 72 | public double BarLength 73 | { 74 | get => _barLength; 75 | set 76 | { 77 | _barLength = value; 78 | OnPropertyChanged(nameof(BarLength)); 79 | } 80 | } 81 | 82 | public SolidColorBrush? BarColor 83 | { 84 | get => _barColor; 85 | set 86 | { 87 | _barColor = value; 88 | OnPropertyChanged(nameof(BarColor)); 89 | } 90 | } 91 | 92 | public SolidColorBrush? ThreeWideBarColor 93 | { 94 | get => _threeWideBarColor; 95 | set 96 | { 97 | _threeWideBarColor = value; 98 | OnPropertyChanged(nameof(ThreeWideBarColor)); 99 | } 100 | } 101 | } 102 | 103 | public class InputGraphSettings : BaseSettings 104 | { 105 | private bool _showClutch; 106 | public bool ShowClutch 107 | { 108 | get => _showClutch; 109 | set 110 | { 111 | _showClutch = value; 112 | OnPropertyChanged(nameof(ShowClutch)); 113 | } 114 | } 115 | 116 | 117 | private bool _useRawValues; 118 | public bool UseRawValues 119 | { 120 | get => _useRawValues; 121 | set 122 | { 123 | _useRawValues = value; 124 | OnPropertyChanged(nameof(UseRawValues)); 125 | } 126 | } 127 | 128 | private SolidColorBrush? _backgroundColor; 129 | 130 | private SolidColorBrush? _throttleColor; 131 | 132 | private SolidColorBrush? _brakeColor; 133 | 134 | private SolidColorBrush? _clutchColor; 135 | 136 | private SolidColorBrush? _steeringColor; 137 | 138 | private SolidColorBrush? _ABSColor; 139 | 140 | public SolidColorBrush? BackgroundColor 141 | 142 | { 143 | get => _backgroundColor; 144 | set 145 | { 146 | _backgroundColor = value; 147 | OnPropertyChanged(nameof(BackgroundColor)); 148 | } 149 | } 150 | 151 | public SolidColorBrush? ThrottleColor 152 | 153 | { 154 | get => _throttleColor; 155 | set 156 | { 157 | _throttleColor = value; 158 | OnPropertyChanged(nameof(ThrottleColor)); 159 | } 160 | } 161 | 162 | public SolidColorBrush? BrakeColor 163 | { 164 | get => _brakeColor; 165 | set 166 | { 167 | _brakeColor = value; 168 | OnPropertyChanged(nameof(BrakeColor)); 169 | } 170 | } 171 | 172 | public SolidColorBrush? ClutchColor 173 | { 174 | get => _clutchColor; 175 | set 176 | { 177 | _clutchColor = value; 178 | OnPropertyChanged(nameof(ClutchColor)); 179 | } 180 | } 181 | 182 | public SolidColorBrush? SteeringColor 183 | { 184 | get => _steeringColor; 185 | set 186 | { 187 | _steeringColor = value; 188 | OnPropertyChanged(nameof(SteeringColor)); 189 | } 190 | } 191 | 192 | public SolidColorBrush? ABSColor 193 | { 194 | get => _ABSColor; 195 | set 196 | { 197 | _ABSColor = value; 198 | OnPropertyChanged(nameof(ABSColor)); 199 | } 200 | } 201 | 202 | private int _lineWidth; 203 | 204 | public int LineWidth 205 | { 206 | get => _lineWidth; 207 | set 208 | { 209 | _lineWidth = value; 210 | OnPropertyChanged(nameof(LineWidth)); 211 | } 212 | } 213 | 214 | private bool _showABS; 215 | 216 | public bool ShowABS 217 | { 218 | get => _showABS; 219 | set 220 | { 221 | _showABS = value; 222 | OnPropertyChanged(nameof(ShowABS)); 223 | } 224 | } 225 | 226 | private bool _showPercentageBrake; 227 | public bool ShowPercentageBrake 228 | { 229 | get => _showPercentageBrake; 230 | set 231 | { 232 | _showPercentageBrake = value; 233 | OnPropertyChanged(nameof(ShowPercentageBrake)); 234 | } 235 | } 236 | 237 | private bool _showPercentageThrottle; 238 | public bool ShowPercentageThrottle 239 | { 240 | get => _showPercentageThrottle; 241 | set 242 | { 243 | _showPercentageThrottle = value; 244 | OnPropertyChanged(nameof(_showPercentageThrottle)); 245 | } 246 | } 247 | 248 | private bool _showPercentageClutch; 249 | public bool ShowPercentageClutch 250 | { 251 | get => _showPercentageClutch; 252 | set 253 | { 254 | _showPercentageClutch = value; 255 | OnPropertyChanged(nameof(ShowPercentageClutch)); 256 | } 257 | } 258 | 259 | private bool _showPercentageSteering; 260 | public bool ShowPercentageSteering 261 | { 262 | get => _showPercentageSteering; 263 | set 264 | { 265 | _showPercentageSteering = value; 266 | OnPropertyChanged(nameof(ShowPercentageSteering)); 267 | } 268 | } 269 | 270 | private bool _showSteering; 271 | 272 | public bool ShowSteering 273 | { 274 | get => _showSteering; 275 | set 276 | { 277 | _showSteering = value; 278 | OnPropertyChanged(nameof(ShowSteering)); 279 | } 280 | } 281 | 282 | public InputGraphSettings() 283 | { 284 | ThrottleColor = new SolidColorBrush(Colors.Green); 285 | BrakeColor = new SolidColorBrush(Colors.Red); 286 | ClutchColor = new SolidColorBrush(Colors.Blue); 287 | SteeringColor = new SolidColorBrush(Colors.Gray); 288 | ABSColor = new SolidColorBrush(Colors.Yellow); 289 | UseRawValues = true; 290 | ShowClutch = false; 291 | ShowPercentageThrottle = false; 292 | ShowPercentageBrake = false; 293 | ShowPercentageClutch = false; 294 | } 295 | } 296 | 297 | public class WindSettings : BaseSettings 298 | { 299 | private bool _useMph; 300 | 301 | public bool UseMph 302 | { 303 | get => _useMph; 304 | set 305 | { 306 | _useMph = value; 307 | OnPropertyChanged(nameof(UseMph)); 308 | } 309 | } 310 | } 311 | 312 | public class FuelSettings : BaseSettings 313 | { 314 | private bool _IsInPositioningMode; 315 | 316 | public bool IsInPositioningMode 317 | { 318 | get => _IsInPositioningMode; 319 | set 320 | { 321 | _IsInPositioningMode = value; 322 | OnPropertyChanged(nameof(IsInPositioningMode)); 323 | } 324 | } 325 | } 326 | 327 | public class GeneralSettings : INotifyPropertyChanged 328 | { 329 | private bool _useHardwareAcceleration; 330 | 331 | public bool UseHardwareAcceleration 332 | { 333 | get => _useHardwareAcceleration; 334 | set 335 | { 336 | _useHardwareAcceleration = value; 337 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UseHardwareAcceleration))); 338 | } 339 | } 340 | 341 | public event PropertyChangedEventHandler? PropertyChanged; 342 | } 343 | 344 | public class Settings : INotifyPropertyChanged 345 | { 346 | public GeneralSettings GeneralSettings { get; set; } 347 | public BarSpotterSettings BarSpotterSettings { get; set; } 348 | public InputGraphSettings InputGraphSettings { get; set; } 349 | public WindSettings WindSettings { get; set; } 350 | public FuelSettings FuelSettings { get; set; } 351 | public bool IsUpdate { get; set; } 352 | 353 | public event PropertyChangedEventHandler? PropertyChanged; 354 | 355 | public Settings() 356 | { 357 | GeneralSettings = new GeneralSettings(); 358 | InputGraphSettings = new InputGraphSettings(); 359 | BarSpotterSettings = new BarSpotterSettings(); 360 | WindSettings = new WindSettings(); 361 | FuelSettings = new FuelSettings(); 362 | } 363 | } 364 | } 365 | 366 | -------------------------------------------------------------------------------- /Services/FuelServices/FuelCalculatorService.cs: -------------------------------------------------------------------------------- 1 | using iRacingSdkWrapper; 2 | using iRacingSdkWrapper.Bitfields; 3 | using SharpOverlay.Models; 4 | using SharpOverlay.Services.Base; 5 | using SharpOverlay.Services.FuelServices.LapServices; 6 | using SharpOverlay.Services.FuelServices.PitServices; 7 | using SharpOverlay.Strategies; 8 | using SharpOverlay.Utilities.Sessions; 9 | using SharpOverlay.Utilities.Telemetries; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Collections.ObjectModel; 13 | using System.Linq; 14 | 15 | namespace SharpOverlay.Services.FuelServices 16 | { 17 | public class FuelCalculatorService : IFuelCalculator 18 | { 19 | private const double _fuelCutOff = 0.3; 20 | 21 | private readonly FuelRepository _repository; 22 | 23 | private readonly ISessionParser _sessionParser; 24 | private readonly ITelemetryParser _telemetryParser; 25 | 26 | private readonly List _strategyList; 27 | 28 | private readonly LapTracker _lapTracker; 29 | private readonly LapCountCalculator _lapCountCalculator; 30 | private readonly LapAnalyzer _lapAnalyzer; 31 | 32 | private readonly PitManager _pitManager; 33 | private readonly PitTimeTracker _pitTimeTracker; 34 | private readonly FinishLineLocator _finishLineLocator; 35 | private int _lapsRemainingInRace; 36 | private bool _isRaceStart; 37 | 38 | public FuelCalculatorService(SimReader simReader) 39 | { 40 | _sessionParser = new SessionParser(); 41 | _telemetryParser = new TelemetryParser(); 42 | _lapTracker = new LapTracker(); 43 | _lapCountCalculator = new LapCountCalculator(); 44 | _lapAnalyzer = new LapAnalyzer(); 45 | _pitManager = new PitManager(); 46 | _pitTimeTracker = new PitTimeTracker(); 47 | _finishLineLocator = new FinishLineLocator(); 48 | 49 | _repository = new FuelRepository(); 50 | 51 | _strategyList = new List 52 | { 53 | new FullRaceStrategy(_fuelCutOff), 54 | new LastLapStrategy(_fuelCutOff), 55 | new FiveLapStrategy(_fuelCutOff) 56 | }; 57 | 58 | simReader.OnConnected += ExecuteOnConnected; 59 | simReader.OnDisconnected += ExecuteOnDisconnected; 60 | simReader.OnTelemetryUpdated += ExecuteOnTelemetryEvent; 61 | simReader.OnSessionUpdated += ExecuteOnSessionEvent; 62 | } 63 | 64 | private void ExecuteOnDisconnected(object? sender, EventArgs args) 65 | { 66 | if (_sessionParser.SessionType == SessionType.Race) 67 | { 68 | var fullStrat = _strategyList.First(); 69 | 70 | var view = fullStrat.GetView(); 71 | 72 | var fuelConsump = view.FuelConsumption; 73 | 74 | var lapCount = _lapTracker.GetCompletedLapsCount(); 75 | var lapTime = _lapAnalyzer.GetLapTime(_telemetryParser.PlayerCarIdx); 76 | 77 | int trackId = _sessionParser.TrackId; 78 | int carID = _sessionParser.CarId; 79 | 80 | var pitStopTime = _pitTimeTracker.GetAvgPitStopTime(); 81 | 82 | var newData = new AddFuelHistoryDTO() 83 | { 84 | CarId = carID, 85 | Consumption = fuelConsump, 86 | LapCount = lapCount, 87 | TrackId = trackId, 88 | LapTime = lapTime, 89 | PitStopTime = pitStopTime, 90 | }; 91 | 92 | _repository.AddOrUpdate(newData); 93 | _repository.Save(); 94 | } 95 | 96 | Clear(); 97 | RaiseEvent(); 98 | } 99 | 100 | private void RaiseEvent() 101 | { 102 | FuelUpdated(this, new FuelEventArgs(GetViewModel(new SimulationOutputDTO()))); 103 | } 104 | 105 | private void ExecuteOnConnected(object? sender, EventArgs args) 106 | { 107 | RaiseEvent(); 108 | } 109 | 110 | public event EventHandler FuelUpdated = null!; 111 | 112 | private void Clear() 113 | { 114 | _lapsRemainingInRace = 0; 115 | 116 | _sessionParser.Clear(); 117 | _telemetryParser.Clear(); 118 | _lapTracker.Clear(); 119 | _lapAnalyzer.Clear(); 120 | _pitManager.Clear(); 121 | _finishLineLocator.Clear(); 122 | 123 | _strategyList.ForEach(s => s.Clear()); 124 | } 125 | 126 | private void ExecuteOnTelemetryEvent(object? sender, SdkWrapper.TelemetryUpdatedEventArgs eventArgs) 127 | { 128 | var telemetry = eventArgs.TelemetryInfo; 129 | 130 | _telemetryParser.ParseCurrentSessionNumber(telemetry); 131 | _telemetryParser.ParsePlayerCarIdx(telemetry); 132 | _telemetryParser.ParsePlayerCarClassId(telemetry); 133 | _telemetryParser.ParsePlayerPctOnTrack(telemetry); 134 | _telemetryParser.ParsePositionCarIdxInPlayerClass(telemetry, _sessionParser.PaceCarIdx); 135 | _telemetryParser.ParsePositionCarIdxForWholeRace(telemetry, _sessionParser.PaceCarIdx); 136 | _telemetryParser.ParseCarIdxOnTrack(telemetry); 137 | 138 | var simulationOutput = new SimulationOutputDTO(telemetry); 139 | 140 | if (IsSessionStateValid(simulationOutput.SessionState)) 141 | { 142 | var driversDict = _sessionParser.Drivers; 143 | var driversLastLapTime = TelemetryParser.GetDriversLastLapTime(_sessionParser.PaceCarIdx, simulationOutput.CarIdxLastLapTime); 144 | var lapsCompletedByCarIdx = simulationOutput.CarIdxLapCompleted; 145 | 146 | _lapAnalyzer.CollectAllDriversLaps(driversDict, driversLastLapTime, lapsCompletedByCarIdx); 147 | 148 | RunFuelCalculations(simulationOutput); 149 | } 150 | else if (IsSessionStateInvalid(simulationOutput.SessionState)) 151 | { 152 | Clear(); 153 | } 154 | else if (!simulationOutput.IsOnTrack 155 | && telemetry.IsReplayPlaying.Value 156 | && _lapTracker.GetCurrentLap() is not null) 157 | { 158 | _lapTracker.ResetCurrentLap(); 159 | } 160 | 161 | _strategyList.ForEach(s => s.UpdateLapsOfFuelRemaining(simulationOutput.FuelLevel)); 162 | 163 | FuelUpdated(this, new FuelEventArgs(GetViewModel(simulationOutput))); 164 | } 165 | 166 | private bool IsSessionStateValid(SessionStates sessionState) 167 | { 168 | return sessionState == SessionStates.Racing 169 | || sessionState == SessionStates.GetInCar 170 | || sessionState == SessionStates.ParadeLaps 171 | || sessionState == SessionStates.Checkered; 172 | } 173 | 174 | private bool IsSessionStateInvalid(SessionStates sessionState) 175 | { 176 | return _telemetryParser.HasSwitchedSessions 177 | || sessionState == SessionStates.CoolDown 178 | || sessionState == SessionStates.Invalid 179 | || sessionState == SessionStates.Warmup; 180 | } 181 | 182 | private void ExecuteOnSessionEvent(object? sender, SdkWrapper.SessionUpdatedEventArgs eventArgs) 183 | { 184 | var sessionInfo = eventArgs.SessionInfo; 185 | 186 | _sessionParser.ParseEventType(sessionInfo); 187 | 188 | _sessionParser.ParseSectors(sessionInfo); 189 | 190 | _sessionParser.ParseLapsInSession(sessionInfo, _telemetryParser.CurrentSessionNumber); 191 | _sessionParser.ParseCurrentSessionType(sessionInfo, _telemetryParser.CurrentSessionNumber); 192 | _sessionParser.ParseStartType(sessionInfo); 193 | _sessionParser.ParseDrivers(sessionInfo); 194 | _sessionParser.ParsePaceCarIdx(sessionInfo); 195 | _sessionParser.ParseSessions(sessionInfo); 196 | _sessionParser.ParseRaceType(sessionInfo); 197 | 198 | _sessionParser.ParseCarId(sessionInfo); 199 | _sessionParser.ParseTrackId(sessionInfo); 200 | } 201 | 202 | private void RunFuelCalculations(SimulationOutputDTO simulationOutput) 203 | { 204 | var trackSurface = simulationOutput.TrackSurface; 205 | 206 | if (trackSurface == TrackSurfaces.AproachingPits && simulationOutput.PlayerTrackDistPct < 0.5) 207 | { 208 | trackSurface = TrackSurfaces.OnTrack; // EXITING PITS GETS REPORTED AS APPROACHING PITS ????????????????????????? 209 | } 210 | _pitManager.SetPitRoadStatus(simulationOutput.IsOnPitRoad, trackSurface); 211 | _pitManager.SetPitServiceStatus(simulationOutput.IsReceivingService); 212 | 213 | if (trackSurface == TrackSurfaces.AproachingPits && !_pitTimeTracker.IsTrackingTime) 214 | { 215 | _pitTimeTracker.Start(simulationOutput.SessionTimeRemaining); 216 | } 217 | else if (_pitTimeTracker.IsTrackingTime && _sessionParser.Sectors[1].StartPct - simulationOutput.PlayerTrackDistPct < 0) 218 | { 219 | _pitTimeTracker.Stop(simulationOutput.SessionTimeRemaining); 220 | } 221 | 222 | var currentLap = _lapTracker.GetCurrentLap(); 223 | 224 | if (currentLap is null) 225 | { 226 | if (simulationOutput.FuelLevel == 0 || _sessionParser.TrackId == 0) 227 | { 228 | return; 229 | } 230 | 231 | int trackId = _sessionParser.TrackId; 232 | int carId = _sessionParser.CarId; 233 | 234 | var entry = _repository.Get(trackId, carId); 235 | 236 | if (entry is not null) 237 | { 238 | _lapTracker.StartNewLap(simulationOutput.CurrentLapNumber - 1, simulationOutput.FuelLevel + entry.Consumption); 239 | _lapTracker.CompleteCurrentLap(simulationOutput.FuelLevel, TimeSpan.FromSeconds(entry.LapTime)); 240 | 241 | _lapsRemainingInRace = _lapCountCalculator.CalculateLapsRemaining(simulationOutput.PlayerTrackDistPct, simulationOutput.SessionTimeRemaining, TimeSpan.FromSeconds(entry.LapTime)); 242 | 243 | foreach (var strategy in _strategyList) 244 | { 245 | strategy.Calculate(_lapTracker.GetPlayerLaps(), _lapsRemainingInRace); 246 | } 247 | } 248 | else 249 | { 250 | _lapTracker.StartNewLap(simulationOutput.CurrentLapNumber, simulationOutput.FuelLevel); 251 | } 252 | 253 | var sessionState = simulationOutput.SessionState; 254 | var sessionType = _sessionParser.SessionType; 255 | 256 | if (IsRaceStart(sessionState, simulationOutput.CurrentLapNumber, sessionType)) 257 | { 258 | _isRaceStart = true; 259 | } 260 | } 261 | else if (_isRaceStart && simulationOutput.CurrentLapNumber == 2) // Simulator flickers quickly to lap 2 in Race 262 | // after the going through start finish line on lap 0 to 1 263 | { 264 | _isRaceStart = false; 265 | } 266 | else if (_pitManager.HasFinishedService()) 267 | { 268 | int lastLapnumber = _lapTracker.GetPlayerLaps().Last().Number; 269 | 270 | _lapTracker.StartNewLap(++lastLapnumber, simulationOutput.FuelLevel); 271 | 272 | currentLap = _lapTracker.GetCurrentLap()!; 273 | 274 | _strategyList.ForEach(s => s.UpdateRefuel(currentLap.StartingFuel, _lapsRemainingInRace)); 275 | 276 | _pitManager.ResetFinishedServiceStatus(); 277 | } 278 | else if (_pitManager.HasBegunService()) 279 | { 280 | if (simulationOutput.SessionFlag != SessionFlags.Repair) 281 | { 282 | _lapTracker.CompleteCurrentLap(simulationOutput.FuelLevel, simulationOutput.LastLapTime); 283 | } 284 | 285 | CalculateFuelAndLapData(simulationOutput); 286 | 287 | _pitManager.ResetBegunServiceStatus(); 288 | } 289 | else if (_pitManager.HasResetToPits(simulationOutput.EnterExitResetButton)) 290 | { 291 | currentLap.StartingFuel = simulationOutput.FuelLevel; 292 | 293 | if (_sessionParser.EventType != "Test" && simulationOutput.CurrentLapNumber > currentLap.Number) 294 | { 295 | currentLap.Number++; 296 | } 297 | 298 | _strategyList.ForEach(s => s.UpdateRefuel(currentLap.StartingFuel, _lapsRemainingInRace)); 299 | } 300 | else if (IsCrossingFinishLine(simulationOutput.CurrentLapNumber, currentLap.Number) 301 | && simulationOutput.SessionState != SessionStates.ParadeLaps) 302 | { 303 | if (!_finishLineLocator.IsFinishLineKnown() && _pitManager.IsComingOutOfPits()) 304 | { 305 | _finishLineLocator.DetermineFinishLineLocation(simulationOutput.PlayerTrackDistPct); 306 | } 307 | 308 | if (_finishLineLocator.IsFinishLineAfterPits() && _pitManager.IsComingOutOfPits()) 309 | { 310 | currentLap.Number++; 311 | 312 | _pitManager.ResetIsComingOutOfPits(); 313 | } 314 | else if (!_pitManager.IsOnPitRoad()) 315 | { 316 | if (currentLap.Number != 0) 317 | { 318 | _lapTracker.CompleteCurrentLap(simulationOutput.FuelLevel, simulationOutput.LastLapTime); 319 | } 320 | 321 | _lapTracker.StartNewLap(simulationOutput.CurrentLapNumber, simulationOutput.FuelLevel); 322 | } 323 | 324 | CalculateFuelAndLapData(simulationOutput); 325 | } 326 | } 327 | 328 | private static bool IsRaceStart(SessionStates sessionState, int currentLapNumber, SessionType sessionType) 329 | { 330 | return (sessionState == SessionStates.Racing || sessionState == SessionStates.ParadeLaps) 331 | && currentLapNumber == 0 && sessionType == SessionType.Race; 332 | } 333 | 334 | private static bool IsCrossingFinishLine(int currentLapNumberTelemetry, int currentLapNumberTracked) 335 | => currentLapNumberTelemetry > currentLapNumberTracked; 336 | 337 | private void CalculateFuelAndLapData(SimulationOutputDTO simulationOutput) 338 | { 339 | //manual track of time 340 | //pitstop duration 341 | //history 342 | 343 | int leaderIdx = FindLeader(); 344 | 345 | if (_sessionParser.SessionLaps > 0) 346 | { 347 | _lapsRemainingInRace = _lapCountCalculator.CalculateLapsRemaining(_sessionParser.SessionLaps, simulationOutput.CarIdxLapCompleted[leaderIdx]); 348 | } 349 | else if (leaderIdx >= 0) 350 | { 351 | var leaderAverageLapTime = _lapAnalyzer.GetLapTime(leaderIdx); 352 | 353 | if (leaderAverageLapTime > TimeSpan.Zero) 354 | { 355 | if (_sessionParser.IsMultiClassRace) 356 | { 357 | var raceLeaderPctOnTrack = _telemetryParser.CarIdxPctOnTrack[leaderIdx]; 358 | var playerAverageLapTime = _lapAnalyzer.GetLapTime(_telemetryParser.PlayerCarIdx); 359 | 360 | _lapsRemainingInRace = _lapCountCalculator.CalculateLapsRemainingMultiClass(simulationOutput.SessionTimeRemaining, 361 | raceLeaderPctOnTrack, simulationOutput.PlayerTrackDistPct, leaderAverageLapTime, playerAverageLapTime, simulationOutput.SessionFlag); 362 | } 363 | else 364 | { 365 | _lapsRemainingInRace = _lapCountCalculator.CalculateLapsRemaining(_telemetryParser.CarIdxPctOnTrack[leaderIdx], simulationOutput.SessionTimeRemaining, leaderAverageLapTime); 366 | } 367 | } 368 | else 369 | { 370 | int trackId = _sessionParser.TrackId; 371 | int carId = _sessionParser.CarId; 372 | 373 | var entry = _repository.Get(trackId, carId); 374 | 375 | if (entry is not null) 376 | { 377 | _lapsRemainingInRace = _lapCountCalculator.CalculateLapsRemaining(simulationOutput.PlayerTrackDistPct, simulationOutput.SessionTimeRemaining, TimeSpan.FromSeconds(entry.LapTime)); 378 | } 379 | } 380 | } 381 | 382 | foreach (var strategy in _strategyList) 383 | { 384 | strategy.Calculate(_lapTracker.GetPlayerLaps(), _lapsRemainingInRace); 385 | } 386 | } 387 | 388 | private int FindLeader() 389 | { 390 | int leaderIdx; 391 | 392 | if (_sessionParser.SessionType != SessionType.Race) 393 | { 394 | leaderIdx = _telemetryParser.PlayerCarIdx; 395 | } 396 | else if (_sessionParser.IsMultiClassRace) 397 | { 398 | leaderIdx = _lapAnalyzer.GetLeaderIdx(_telemetryParser.PositionCarIdxInRace); 399 | } 400 | else 401 | { 402 | leaderIdx = _lapAnalyzer.GetLeaderIdx(_telemetryParser.PositionCarIdxInClass); 403 | } 404 | 405 | return leaderIdx; 406 | } 407 | 408 | private FuelViewModel GetViewModel(SimulationOutputDTO simulationOutput) 409 | { 410 | var strategies = new ObservableCollection 411 | { 412 | _strategyList[0].GetView(), 413 | _strategyList[1].GetView(), 414 | _strategyList[2].GetView() 415 | }; 416 | 417 | var completedLaps = _lapTracker.GetPlayerLaps(); 418 | 419 | return new FuelViewModel() 420 | { 421 | Strategies = strategies, 422 | 423 | ConsumedFuel = completedLaps.Sum(l => l.FuelUsed), 424 | CurrentFuelLevel = simulationOutput.FuelLevel, 425 | LapsCompleted = completedLaps.Count, 426 | RaceLapsRemaining = _lapsRemainingInRace, 427 | 428 | HasResetToPits = _pitManager.HasResetToPits(simulationOutput.EnterExitResetButton), 429 | IsRollingStart = _sessionParser.StartType == StartType.Rolling, 430 | SessionFlag = simulationOutput.SessionFlag, 431 | IsRaceStart = _isRaceStart, 432 | CurrentSessionNumber = _telemetryParser.CurrentSessionNumber, 433 | CurrentLap = _lapTracker.GetCurrentLap(), 434 | TrackSurface = simulationOutput.TrackSurface, 435 | SessionState = simulationOutput.SessionState, 436 | IsOnPitRoad = _pitManager.IsOnPitRoad(), 437 | HasBegunService = _pitManager.HasBegunService(), 438 | HasCompletedService = _pitManager.HasFinishedService(), 439 | }; 440 | } 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 108 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 235 | 236 | 237 | 238 | 239 | 240 | 241 |