public void SdkOnTelemetryUpdated(iRacingData telemetry) { // Cache info _telemetry = telemetry; // Update drivers telemetry this.UpdateDriverTelemetry(telemetry); }
public void ParseTelemetry(iRacingData e, Boolean isRaceOrQualifying) { LapDistance = Math.Abs(e.CarIdxLapDistPct[Driver.Id]); // We need to make sure we start the first lap(IsNewLap), we cant use LapsCompleted for this as it does not increas first time we cross the s/f line but CarIdxLap does so we use that. // we only wanna do this for opponents in race and qualifying insertStartLap = (startLapInserted == false && Lap < e.CarIdxLap[Driver.Id] && e.CarIdxLap[Driver.Id] > 0) && ((Driver.IsCurrentDriver) || (isRaceOrQualifying && !Driver.IsCurrentDriver)); Lap = e.CarIdxLap[Driver.Id]; TrackSurface = e.CarIdxTrackSurface[Driver.Id]; // turns out that the other method for setting the current sector was flawed as when first connecting it would set sector to 1 and then it would be unable to switch to sector 3 when gridding up, // so catching crossing the s/f line when the light goes green to set a HasCrossedSFLine didnt happen. CurrentSector = GetCurrentSector(); IsNewLap = false; if (_prevSector == 3 && (CurrentSector == 1) || (LapsCompleted < _driver.CurrentResults.LapsComplete && !_driver.IsCurrentDriver && TrackSurface == TrackSurfaces.NotInWorld) || (insertStartLap && !Driver.IsCurrentDriver && !hasCrossedSFLineToStartRace)) { // make sure we dont this on the opening lap more then once hasCrossedSFLineToStartRace = true; HasCrossedSFLine = true; // This is not accurate for playes that are not in live telemetry but its not used in any calculations in this case. GameTimeWhenLastCrossedSFLine = (float)e.SessionTime; } else { HasCrossedSFLine = false; } if (_prevSector != CurrentSector) { _prevSector = CurrentSector; } if (_driver.CurrentResults.LapsComplete > LapsCompleted || insertStartLap) { startLapInserted = true; IsNewLap = true; } LapsCompleted = _driver.CurrentResults.LapsComplete; CorrectedLapDistance = FixPercentagesOnLapChange(LapDistance); LiveLapsCompleted = e.CarIdxLapCompleted[Driver.Id] < LapsCompleted ? LapsCompleted : e.CarIdxLapCompleted[Driver.Id]; Gear = e.CarIdxGear[Driver.Id]; Rpm = e.CarIdxRPM[Driver.Id]; //for local player we use data from telemetry as its updated faster then session info, //we do not have lastlaptime from opponents available in telemetry so we use data from sessioninfo. if (Driver.Id == e.PlayerCarIdx) { LapTimePrevious = e.LapLastLapTime; } else { LapTimePrevious = _driver.CurrentResults.LastTime; } PreviousLapWasValid = LapTimePrevious > 1; TotalLapDistanceCorrected = TotalLapDistance; }
private void UpdateDriverTelemetry(iRacingData info) { foreach (var driver in _drivers) { driver.Live.CalculateSpeed(info, _sessionData.Track.Length); driver.UpdateLiveInfo(info, _isRaceOrQualifying); } this.CalculateLivePositions(info); }
internal void UpdateLiveInfo(iRacingData e, Boolean isRaceOrQualifying) { this.Live.ParseTelemetry(e, isRaceOrQualifying); }
public override Object ReadGameData(Boolean forSpotter) { lock (this) { if (!initialised) { if (!InitialiseInternal()) { throw new GameDataReadException("Failed to initialise shared memory"); } } try { if (sdk.IsConnected()) { if (sim == null) { sim = new Sim(); } if (forSpotter) { return((int)sdk.GetData("CarLeftRight")); } _DriverId = (int)sdk.GetData("PlayerCarIdx"); int newUpdate = sdk.Header.SessionInfoUpdate; bool hasNewSessionData = false; bool isNewSession = false; if (newUpdate != lastUpdate) { var sessionNum = TryGetSessionNum(); if (sessionNum != null) { string sessionInfoUnFiltred = sdk.GetSessionInfoString(); if (sessionInfoUnFiltred == null) { return(null); } string sessionInfoFiltred = new SessionInfo(sessionInfoUnFiltred).Yaml; isNewSession = sim.SdkOnSessionInfoUpdated(sessionInfoFiltred, (int)sessionNum, DriverId); lastUpdate = newUpdate; hasNewSessionData = true; } else { return(null); } } iRacingData irData = new iRacingData(sdk, hasNewSessionData && dumpToFile, isNewSession); sim.SdkOnTelemetryUpdated(irData); iRacingStructWrapper structWrapper = new iRacingStructWrapper(); structWrapper.ticksWhenRead = DateTime.UtcNow.Ticks; structWrapper.data = sim; if (dumpToFile && dataToDump != null) { dataToDump.Add(new iRacingStructDumpWrapper() { ticksWhenRead = structWrapper.ticksWhenRead, data = irData }); } return(structWrapper); } else { return(null); } } catch (Exception ex) { throw new GameDataReadException(ex.Message, ex); } } }
public void CalculateSpeed(iRacingData telemetry, double?trackLengthKm) { if (telemetry == null) { return; } if (trackLengthKm == null) { return; } try { var t1 = telemetry.SessionTime; var t0 = _prevSpeedUpdateTime; var time = t1 - t0; if (time < SPEED_CALC_INTERVAL) { // Ignore return; } var p1 = telemetry.CarIdxLapDistPct[this.Driver.Id]; var p0 = _prevSpeedUpdateDist; if (p1 < -0.5 || _driver.Live.TrackSurface == TrackSurfaces.NotInWorld) { // Not in world? return; } if (p0 - p1 > 0.5) { // Lap crossing p1 += 1; } var distancePct = p1 - p0; var distance = distancePct * trackLengthKm.GetValueOrDefault() * 1000; //meters if (time >= Double.Epsilon) { this.Speed = distance / (time); // m/s } else { if (distance < 0) { this.Speed = Double.NegativeInfinity; } else { this.Speed = Double.PositiveInfinity; } } this.SpeedKph = this.Speed * 3.6; _prevSpeedUpdateTime = t1; _prevSpeedUpdateDist = p1; } catch (Exception) { //Log.Instance.LogError("Calculating speed of car " + this.Driver.Id, ex); this.Speed = 0; } }
private void CalculateLivePositions(iRacingData telemetry) { if (!_raceSessionInProgress) { this._carIdxToGameTimeOffTrack.Clear(); } // Assume race has finished. this._raceSessionInProgress = false; // In a race that is not yet in checkered flag mode, // Live positions are determined from track position (total lap distance) // Any other conditions (race finished, P, Q, etc), positions are ordered as result positions SessionFlags flag = (SessionFlags)telemetry.SessionFlags; if (this.SessionData.SessionType == "Race" && flag.HasFlag(SessionFlags.Checkered)) { // We need to check if player is the first (p1) to cross the s/f line as HasCrossedSFLine is only true for 1 tick if (raceEndState == RaceEndState.WAITING_TO_CROSS_LINE || _driver.Live.IsNewLap) { if (this._driver.Live.IsNewLap) { Console.WriteLine("Player just crossed line to finish race"); raceEndState = RaceEndState.FINISHED; this._driver.FinishStatus = Driver.FinishState.Finished; } } else if (raceEndState == RaceEndState.NONE) { Console.WriteLine("Starting wait for player crossing line"); raceEndState = RaceEndState.WAITING_TO_CROSS_LINE; } } else { raceEndState = RaceEndState.NONE; } if (this.SessionData.SessionType == "Race" && raceEndState != RaceEndState.FINISHED && (flag.HasFlag(SessionFlags.StartGo) || flag.HasFlag(SessionFlags.StartHidden /*yellow?*/)) && telemetry.PlayerCarPosition > 0) { this._raceSessionInProgress = true; // When driver disconnects (or in other cases I am not sure about yet), TotalLapDitance // gets ceiled to the nearest integer. Because of that, for the reminder of a lap such car is // ahead of others by TotalLapDitance, which results incorrect positions announced. // // To mitigate that, try detecting such cases and using floor(TotalLapDitance) instead. // // Also, try detecting cars that finished and use YAML reported positions instead for those. if (this._gameTimeWhenWhiteFlagTriggered == -1.0 && flag.HasFlag(SessionFlags.White)) { this._gameTimeWhenWhiteFlagTriggered = telemetry.SessionTime; } // Correct the distances foreach (var driver in _drivers) { if (driver.IsSpectator || driver.IsPaceCar || driver.CurrentResults.IsOut) { continue; } if (Math.Floor(driver.Live.TotalLapDistanceCorrected) > driver.Live.LiveLapsCompleted) { driver.Live.TotalLapDistanceCorrected = (float)driver.Live.LiveLapsCompleted; } } // If leader has not finished yet, check if he just did. if (_leaderFinished == null && this._gameTimeWhenWhiteFlagTriggered != -1.0) { // See if leading driver crossed s/f line after race time expired. foreach (var driver in _drivers.OrderByDescending(d => d.Live.TotalLapDistanceCorrected)) { if (driver.IsSpectator || driver.IsPaceCar || driver.CurrentResults.IsOut) { continue; } if (driver.IsCurrentDriver) { // It appears that player s/f crossing time is set before the actual s/f crossing. So, for player "Finished" state // rely on logic that waits for new lap crossing. continue; } if (driver.Live.GameTimeWhenLastCrossedSFLine > this._gameTimeWhenWhiteFlagTriggered) { _leaderFinished = driver; } // Only check assumed leader by lapdist (skipping lapped vehicles, spectators etc). break; } } // Now, detect lapped and retired finishers. foreach (var driver in _drivers) { if (_leaderFinished != null && driver.Live.GameTimeWhenLastCrossedSFLine >= _leaderFinished.Live.GameTimeWhenLastCrossedSFLine) { if (driver.IsCurrentDriver || driver.IsPaceCar) { // It appears that player s/f crossing time is set before the actual s/f crossing. So, for player "Finished" state // rely on logic that waits for new lap crossing. continue; } // Everyone, who crosses s/f after leader finished, finishes too. driver.FinishStatus = Driver.FinishState.Finished; } // Try detecting disconnects. Save last time seen off world, and mark as disconnect if // stays off world long enough. if (driver.FinishStatus == Driver.FinishState.Unknown) // Don't do any processing for Finished and Retired. { if (driver.Live.TrackSurface == TrackSurfaces.NotInWorld) { var timeSinceOffWorld = -1.0; if (!this._carIdxToGameTimeOffTrack.TryGetValue(driver.Id, out timeSinceOffWorld)) { this._carIdxToGameTimeOffTrack.Add(driver.Id, telemetry.SessionTime); } else if (telemetry.SessionTime - timeSinceOffWorld > SECONDS_OFF_WORLD_TILL_RETIRED) { driver.FinishStatus = Driver.FinishState.Retired; Console.WriteLine("Marking driver: " + driver.Name + " as retired."); } } else { this._carIdxToGameTimeOffTrack.Remove(driver.Id); } } if (driver.FinishStatus == Driver.FinishState.Retired && driver.Live.TrackSurface != TrackSurfaces.NotInWorld) { driver.FinishStatus = Driver.FinishState.Unknown; Console.WriteLine("Driver: " + driver.Name + " was previously marked as retired, shown up again."); } } // Determine live position from lap distance. int pos = 1; foreach (var driver in _drivers.OrderByDescending(d => d.Live.TotalLapDistanceCorrected)) { if (driver.IsSpectator || driver.IsPaceCar || driver.CurrentResults.IsOut) { driver.Live.Position = 1001; // Make it obvious those guys are not tracked. continue; } if (driver.FinishStatus == Driver.FinishState.Finished && driver.Live.TrackSurface == TrackSurfaces.NotInWorld) { // When finished driver disconnects, use game reported position. // This should not mess up order of drivers following, because all the // drivers are ordered by TotalLapDistanceCorrected. driver.Live.Position = driver.CurrentResults.Position; if (driver.Live.Position == 0) { driver.Live.Position = 1000; // Game sends nonsense again. Mark those so that they don't interfere with sorting anywhere. } } else { driver.Live.Position = pos; } pos++; } } else // Not Race or Finished. { // Clear out cached finished leader. Ugly data model, ugly workarounds :( _leaderFinished = null; _gameTimeWhenWhiteFlagTriggered = -1.0; // In P or Q, set live position from result position (== best lap according to iRacing) foreach (var driver in _drivers) { if (driver.IsSpectator || driver.IsPaceCar) { driver.Live.Position = 1001; // Make it obvious those guys are not tracked. continue; } if (telemetry.CarIdxPosition[driver.Id] > 0 && raceEndState != RaceEndState.FINISHED) { driver.Live.Position = telemetry.CarIdxPosition[driver.Id]; } else { driver.Live.Position = driver.CurrentResults.Position; } if (telemetry.CarIdxClassPosition[driver.Id] > 0 && raceEndState != RaceEndState.FINISHED) { driver.Live.ClassPosition = telemetry.CarIdxClassPosition[driver.Id]; } else { driver.Live.ClassPosition = driver.CurrentResults.ClassPosition; } if (driver.Live.Position == 0) { driver.Live.Position = 1000; // Game sends nonsense again. Mark those so that they don't interfere with sorting anywhere. } if (driver.Live.ClassPosition == 0) { driver.Live.ClassPosition = 1000; // Game sends nonsense again. Mark those so that they don't interfere with sorting anywhere. } } } }