From 3e83fe98a1ae1e537d9290b2783a266d4a3cd0d3 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Mon, 20 Jul 2026 17:15:00 +0100 Subject: [PATCH 1/4] New: Convert train.dat parser to block format --- source/Plugins/Formats.OpenBve/Block.cs | 142 +++ .../Plugins/Formats.OpenBve/DAT/TrainDat.cs | 138 +++ .../Enums/TrainDat/TrainDatFormats.cs} | 5 +- .../Enums/TrainDat/TrainDatKey.cs | 56 + .../Enums/TrainDat/TrainDatSection.cs | 37 + .../Formats.OpenBve/Formats.OpenBve.csproj | 4 + .../Train.OpenBve/Train.OpenBve.csproj | 1 - .../Train.OpenBve/Train/BVE/TrainDatParser.cs | 1097 ++++++----------- 8 files changed, 764 insertions(+), 716 deletions(-) create mode 100644 source/Plugins/Formats.OpenBve/DAT/TrainDat.cs rename source/Plugins/{Train.OpenBve/Train/BVE/TrainDatparser.Formats.cs => Formats.OpenBve/Enums/TrainDat/TrainDatFormats.cs} (89%) create mode 100644 source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatKey.cs create mode 100644 source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatSection.cs diff --git a/source/Plugins/Formats.OpenBve/Block.cs b/source/Plugins/Formats.OpenBve/Block.cs index 230bcfd6bf..9b6c438eab 100644 --- a/source/Plugins/Formats.OpenBve/Block.cs +++ b/source/Plugins/Formats.OpenBve/Block.cs @@ -775,6 +775,148 @@ public virtual bool GetNextRawValue(out string s) return false; } + /// Gets the next double from the block + public virtual bool GetNextDouble(T2 key, NumberRange range, out double d) + { + d = -1; + if (rawValues.Count > 0) + { + KeyValuePair value = rawValues.Dequeue(); + if(NumberFormats.TryParseDoubleVb6(value.Value, out double newValue)) + { + switch (range) + { + case NumberRange.Any: + d = newValue; + return true; + case NumberRange.Positive: + if (newValue > 0) + { + d = newValue; + return true; + } + currentHost.AddMessage(MessageType.Warning, false, "Value " + newValue + " is not a positive double in Key " + key + " in Section " + Key + " at line " + value.Key); + return false; + case NumberRange.NonNegative: + if (newValue >= 0) + { + d = newValue; + return true; + } + currentHost.AddMessage(MessageType.Warning, false, "Value " + newValue + " is not a non-negative double in Key " + key + " in Section " + Key + " at line " + value.Key); + return false; + case NumberRange.NonZero: + if (newValue != 0) + { + d = newValue; + return true; + } + currentHost.AddMessage(MessageType.Warning, false, "Value " + newValue + " is not a non-zero double in Key " + key + " in Section " + Key + " at line " + value.Key); + return false; + } + } + + currentHost.AddMessage("Value is invalid in " + key + " in " + Key + " at line " + value.Key + " in file " + FileName); + } + return false; + } + + public virtual bool GetNextInt(T2 key, NumberRange range, out int d) + { + d = -1; + if (rawValues.Count > 0) + { + KeyValuePair value = rawValues.Dequeue(); + if (NumberFormats.TryParseIntVb6(value.Value, out int newValue)) + { + switch (range) + { + case NumberRange.Any: + d = newValue; + return true; + case NumberRange.Positive: + if (newValue > 0) + { + d = newValue; + return true; + } + currentHost.AddMessage(MessageType.Warning, false, "Value " + newValue + " is not a positive double in Key " + key + " in Section " + Key + " at line " + value.Key); + return false; + case NumberRange.NonNegative: + if (newValue >= 0) + { + d = newValue; + return true; + } + currentHost.AddMessage(MessageType.Warning, false, "Value " + newValue + " is not a non-negative double in Key " + key + " in Section " + Key + " at line " + value.Key); + return false; + case NumberRange.NonZero: + if (newValue != 0) + { + d = newValue; + return true; + } + currentHost.AddMessage(MessageType.Warning, false, "Value " + newValue + " is not a non-zero double in Key " + key + " in Section " + Key + " at line " + value.Key); + return false; + } + } + + currentHost.AddMessage("Value is invalid in " + key + " in " + Key + " at line " + value.Key + " in file " + FileName); + } + return false; + } + + public virtual bool GetNextBool(T2 key, out bool b) + { + if (rawValues.Count > 0) + { + KeyValuePair value = rawValues.Dequeue(); + int i = int.Parse(value.Value); + b = i == 1; + return true; + } + + b = false; + return false; + } + + public virtual bool GetNextDoubleArray(char separator, out double[] array) + { + if (rawValues.Count > 0) + { + string s = rawValues.Dequeue().Value; + string[] splitString = s.Split(separator); + array = new double[splitString.Length]; + for (int i = 0; i < array.Length; i++) + { + if (!NumberFormats.TryParseDoubleVb6(splitString[i], out array[i])) + { + Array.Resize(ref array, i); + break; + } + } + + return true; + } + + array = Array.Empty(); + return false; + } + + public virtual bool GetNextEnumValue(T2 key, out T3 enumValue) where T3 : struct, Enum + { + if (rawValues.Count > 0) + { + string s = rawValues.Dequeue().Value; + if (Enum.TryParse(s, out enumValue) && Enum.IsDefined(typeof(T3), enumValue)) + { + return true; + } + } + enumValue = default(T3); + return false; + } + public virtual bool GetNextPath(string absolutePath, out string finalPath) { if (rawValues.Count > 0) diff --git a/source/Plugins/Formats.OpenBve/DAT/TrainDat.cs b/source/Plugins/Formats.OpenBve/DAT/TrainDat.cs new file mode 100644 index 0000000000..76d085c589 --- /dev/null +++ b/source/Plugins/Formats.OpenBve/DAT/TrainDat.cs @@ -0,0 +1,138 @@ +using OpenBveApi.Hosts; +using OpenBveApi.Math; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Formats.OpenBve +{ + /// Root block for a .CFG type file + public class TrainDatFile : Block where T1 : struct, Enum where T2 : struct, Enum + { + /// The format of the current file + public readonly TrainDatFormats Format; + + public readonly int Version; + public TrainDatFile(string myFile, HostInterface currentHost) : base(-1, default, myFile, currentHost) + { + string[] lines = File.ReadAllLines(myFile); + List blockLines = new List(); + bool addToBlock = false; + T1 previousSection = default(T1); + bool formatFound = false; + int startingLine = 0; + Version = 0; + for (int i = 0; i < lines.Length; i++) + { + int semiColon = lines[i].IndexOf(';'); + if (semiColon != -1) + { + lines[i] = lines[i].Substring(0, semiColon); + } + lines[i] = lines[i].Trim(); + if (lines[i].Length > 0 && !formatFound) + { + formatFound = true; + Format = ParseFormat(lines[i], out Version); + } + + + if (lines[i].Length > 0 && lines[i][0] == '#') + { + if (!Enum.TryParse(lines[i].Substring(1), true, out T1 currentSection)) + { + addToBlock = false; + } + else + { + addToBlock = true; + } + + if (blockLines.Count > 0) + { + subBlocks.Add(new TrainDatSection(blockLines, previousSection, startingLine + 1, myFile, currentHost)); + blockLines.Clear(); + } + + startingLine = i; + previousSection = currentSection; + } + else + { + if (addToBlock && !string.IsNullOrEmpty(lines[i])) + { + blockLines.Add(lines[i]); + } + } + } + + if (blockLines.Count > 0) + { + subBlocks.Add(new TrainDatSection(blockLines, previousSection, startingLine, myFile, currentHost)); + } + } + + public class TrainDatSection : Block where T1 : struct, Enum where T2 : struct, Enum + { + public TrainDatSection(List blockLines, T1 myKey, int startingLine, string myFile, HostInterface currentHost) : base(-1, myKey, myFile, currentHost) + { + for (int i = 0; i < blockLines.Count; i++) + { + rawValues.Enqueue(new KeyValuePair(startingLine + i, blockLines[i])); + } + } + } + + /// Parse the format of the specified train.dat + /// The version line of the train.dat + /// The version of the specified OpenBVE train.dat + private static TrainDatFormats ParseFormat(string line, out int version) + { + version = -1; + switch (line.ToLowerInvariant()) + { + case "bve1200000": + return TrainDatFormats.BVE1200000; + case "bve1210000": + return TrainDatFormats.BVE1210000; + case "bve1220000": + return TrainDatFormats.BVE1220000; + case "bve2000000": + return TrainDatFormats.BVE2000000; + case "bve2060000": + return TrainDatFormats.BVE2060000; + case "openbve": + version = 0; + return TrainDatFormats.openBVE; + case "#acceleration": + case "#deceleration": + case "#delay": + case "#move": + case "#brake": + case "#pressure": + case "#handle": + case "#cab": + case "#car": + case "#device": + case "motor_p1": + case "motor_p2": + case "brake_p1": + case "brake_p2": + version = 0; + return TrainDatFormats.MissingHeader; + default: + if (line.ToLowerInvariant().StartsWith("openbve")) + { + string tt = line.Substring(7, line.Length - 7).Trim(); + if (!NumberFormats.TryParseIntVb6(tt, out version)) + { + version = -1; + } + return TrainDatFormats.openBVE; + } + + return line.ToLowerInvariant().StartsWith("bve") ? TrainDatFormats.UnknownBVE : TrainDatFormats.Unsupported; + } + } + } +} diff --git a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatparser.Formats.cs b/source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatFormats.cs similarity index 89% rename from source/Plugins/Train.OpenBve/Train/BVE/TrainDatparser.Formats.cs rename to source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatFormats.cs index 22b402e892..3bbd10b7df 100644 --- a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatparser.Formats.cs +++ b/source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatFormats.cs @@ -1,8 +1,7 @@ -// ReSharper disable InconsistentNaming -namespace Train.OpenBve +namespace Formats.OpenBve { /// The different train.dat format types - internal enum TrainDatFormats + public enum TrainDatFormats { /// The train.dat format string is unsupported Unsupported = -1, diff --git a/source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatKey.cs b/source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatKey.cs new file mode 100644 index 0000000000..8b237f5baa --- /dev/null +++ b/source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatKey.cs @@ -0,0 +1,56 @@ +namespace Formats.OpenBve +{ + public enum TrainDatKey + { + BrakeDeceleration, + CoefficientOfStaticFriction, + CoefficientOfRollingResistance, + CoefficientOfAerodynamicDrag, + ElectricBrakeDelayUp, + ElectricBrakeDelayDown, + JerkPowerUp, + JerkPowerDown, + JerkBrakeUp, + JerkBrakeDown, + BrakeCylinderUp, + BrakeCylinderDown, + BrakeSystemType, + ElectropneumaticType, + BrakeControlSpeed, + LocoBrakeType, + BrakeCylinderServiceMaximumPressure, + BrakeCylinderEmergencyMaximumPressure, + MainReservoirMinimumPressure, + MainReservoirMaximumPressure, + BrakePipePressure, + HandleType, + NumberOfPowerNotches, + NumberOfBrakeNotches, + PowerReduceSteps, + EbHandleBehaviour, + LocoBrakeNotches, + DriverPowerNotches, + DriverBrakeNotches, + DriverX, + DriverY, + DriverZ, + MotorCarMass, + NumberOfMotorCars, + TrailerCarMass, + NumberOfTrailerCars, + CarLength, + FrontCarIsMotorCar, + LengthOfCar, + WidthOfCar, + HeightOfCar, + ExposedFrontalArea, + UnexposedFrontalArea, + DefaultSafetySystems, + ReAdhesionDeviceType, + PassAlarm, + DoorOpenMode, + DoorCloseMode, + DoorWidth, + DoorMaxTolerance, + } +} diff --git a/source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatSection.cs b/source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatSection.cs new file mode 100644 index 0000000000..c8c6f67801 --- /dev/null +++ b/source/Plugins/Formats.OpenBve/Enums/TrainDat/TrainDatSection.cs @@ -0,0 +1,37 @@ +namespace Formats.OpenBve +{ + /// The sections in a train.dat file + public enum TrainDatSection + { + /// Defines the acceleration characteristics of the train in each power notch + Acceleration, + /// Defines the brake co-efficients and other brake physics related values + Deceleration, + Performance = Deceleration, + /// Defines various delays associated with power and braking + Delay, + /// Defines the jerk values associated with power and braking + Move, + /// Defines the brake type and related settings + Brake, + /// Defines pressures for the train brake + Pressure, + /// Defines the handle types and number of notches + Handle, + /// Defines the location of the driver's eyes within the driver car + Cab, + Cockpit = Cab, + /// Defines the number of cars, and associated masses + Car, + /// Defines which built-in safety systems and other devices are present on the train + Device, + /// Defines the motor sound curves for P1 + Motor_P1, + /// Defines the motor sound curves for P2 + Motor_P2, + /// Defines the brake sound curves for P1 + Motor_B1, + /// Defines the brake sound curves for P2 + Motor_B2 + } +} diff --git a/source/Plugins/Formats.OpenBve/Formats.OpenBve.csproj b/source/Plugins/Formats.OpenBve/Formats.OpenBve.csproj index 710c56f37f..b6c84be2d9 100644 --- a/source/Plugins/Formats.OpenBve/Formats.OpenBve.csproj +++ b/source/Plugins/Formats.OpenBve/Formats.OpenBve.csproj @@ -58,6 +58,7 @@ + @@ -82,6 +83,9 @@ + + + diff --git a/source/Plugins/Train.OpenBve/Train.OpenBve.csproj b/source/Plugins/Train.OpenBve/Train.OpenBve.csproj index 2e17a7051f..a84a9b84b0 100644 --- a/source/Plugins/Train.OpenBve/Train.OpenBve.csproj +++ b/source/Plugins/Train.OpenBve/Train.OpenBve.csproj @@ -69,7 +69,6 @@ - diff --git a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs b/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs index d2c13cb541..5c398aa7e7 100644 --- a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs +++ b/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Drawing.Imaging; using System.Linq; using System.Text; +using Formats.OpenBve; using LibRender2.Trains; using OpenBveApi; using OpenBveApi.Interface; @@ -254,6 +256,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { Train.Specs.AveragesPressureDistribution = true; double[] powerDelayUp = { }, powerDelayDown = { }, brakeDelayUp = { }, brakeDelayDown = { }, locoBrakeDelayUp = { }, locoBrakeDelayDown = { }; double electricBrakeDelayUp = 0, electricBrakeDelayDown = 0; + int powerNotches = 0, brakeNotches = 0, locoBrakeNotches = 0, powerReduceSteps = -1, locoBrakeType = 0, driverPowerNotches = 0, driverBrakeNotches = 0; BVEMotorSoundTable[] Tables = new BVEMotorSoundTable[4]; for (int i = 0; i < 4; i++) { @@ -266,749 +269,419 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } // parse configuration double invfac = Lines.Length == 0 ? 0.1 : 0.1 / Lines.Length; - for (int i = 0; i < Lines.Length; i++) { - Plugin.CurrentProgress = Plugin.LastProgress + invfac * i; - if ((i & 7) == 0) { - System.Threading.Thread.Sleep(1); - if (Plugin.Cancel) return; - } - int n = 0; - switch (Lines[i].ToLowerInvariant()) { - case "#acceleration": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (string.IsNullOrEmpty(Lines[i])) + + + + TrainDatFile datFile = new TrainDatFile(FileName, Plugin.CurrentHost); + + while (datFile.RemainingSubBlocks > 0) + { + Block subBlock = datFile.ReadNextBlock(); + switch (subBlock.Key) + { + case TrainDatSection.Acceleration: + while (subBlock.RemainingDataValues > 0) + { + if (subBlock.GetNextDoubleArray(',', out double[] curveValues) && curveValues.Length >= 4) { - i++; - continue; - } - Array.Resize(ref AccelerationCurves, n + 1); - AccelerationCurves[n] = new BveAccelerationCurve(); - string t = Lines[i] + ","; - int m = 0; - while (true) { - int j = t.IndexOf(','); - if (j == -1) break; - string s = t.Substring(0, j).Trim(); - t = t.Substring(j + 1); - if (NumberFormats.TryParseDoubleVb6(s, out var a)) { - switch (m) { - case 0: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "a0 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); - } else { - AccelerationCurves[n].StageZeroAcceleration = a * 0.277777777777778; - } break; - case 1: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "a1 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); - } else { - AccelerationCurves[n].StageOneAcceleration = a * 0.277777777777778; - } break; - case 2: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v1 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); - } else { - AccelerationCurves[n].StageOneSpeed = a * 0.277777777777778; - } break; - case 3: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v2 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); - } else { - AccelerationCurves[n].StageTwoSpeed = a * 0.277777777777778; - if (AccelerationCurves[n].StageTwoSpeed < AccelerationCurves[n].StageOneSpeed) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v2 in section #ACCELERATION is expected to be greater than or equal to v1 at line " + (i + 1).ToString(Culture) + " in file " + FileName); - AccelerationCurves[n].StageTwoSpeed = AccelerationCurves[n].StageOneSpeed; - } - } break; - case 4: - { - if (currentFormat == TrainDatFormats.BVE1200000 || currentFormat == TrainDatFormats.BVE1210000 || currentFormat == TrainDatFormats.BVE1220000) { - if (a <= 0.0) { - AccelerationCurves[n].StageTwoExponent = 1.0; - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "e in section #ACCELERATION is expected to be positive at line " + (i + 1).ToString(Culture) + " in file " + FileName); - } else { - const double c = 4.439346232277577; - AccelerationCurves[n].StageTwoExponent = 1.0 - Math.Log(a) * AccelerationCurves[n].StageTwoSpeed * c; - if (AccelerationCurves[n].StageTwoExponent <= 0.0) { - AccelerationCurves[n].StageTwoExponent = 1.0; - } else if (AccelerationCurves[n].StageTwoExponent > 4.0) { - AccelerationCurves[n].StageTwoExponent = 4.0; - } - } - } else { - AccelerationCurves[n].StageTwoExponent = a; - if (AccelerationCurves[n].StageTwoExponent <= 0.0) { - AccelerationCurves[n].StageTwoExponent = 1.0; - } - } - } break; - } - } m++; - } i++; n++; - } i--; break; - case "#performance": - case "#deceleration": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) { - switch (n) { - case 0: - if (a < 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "BrakeDeceleration is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - BrakeDeceleration = a * 0.277777777777778; - } break; - case 1: - if (a < 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "CoefficientOfStaticFriction is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - if (Plugin.CurrentOptions.EnableBveTsHacks && (a > 0.1 || a < 1.0)) - { - break; - } - CoefficientOfStaticFriction = a; - } break; - case 3: - if (a < 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "CoefficientOfRollingResistance is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - CoefficientOfRollingResistance = a; - } break; - case 4: - if (a < 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "AerodynamicDragCoefficient is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - AerodynamicDragCoefficient = a; - } break; + Array.Resize(ref AccelerationCurves, AccelerationCurves.Length + 1); + BveAccelerationCurve curve = new BveAccelerationCurve(); + if (curveValues[0] > 0) + { + curve.StageZeroAcceleration = curveValues[0] * 0.277777777777778; } - } i++; n++; - } i--; break; - case "#delay": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) { - switch (n) + else { - case 0: - if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534) - { - powerDelayUp = Lines[i].Split( ',').Select(x => double.Parse(x, Culture)).ToArray(); - } - else - { - if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60) - { - break; - } - powerDelayUp = new[] {a}; - } - break; - case 1: - if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534) - { - powerDelayDown = Lines[i].Split(',').Select(x => double.Parse(x, Culture)).ToArray(); - } - else - { - if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60) - { - break; - } - powerDelayDown = new[] {a}; - } - break; - case 2: - if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534) - { - brakeDelayUp = Lines[i].Split(',').Select(x => double.Parse(x, Culture)).ToArray(); - } - else - { - if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60) - { - break; - } - brakeDelayUp = new[] {a}; - } - break; - case 3: - if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534) - { - brakeDelayDown = Lines[i].Split(',').Select(x => double.Parse(x, Culture)).ToArray(); - } - else - { - if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60) - { - break; - } - brakeDelayDown = new[] {a}; - } - break; - /* - * https://github.com/leezer3/OpenBVE/issues/737 - * OpenBVE appears to have overlooked these originally - * We can (hopefully) assume that any trains using the LocoBrake feature - * will have set the OPENBVE header, hence it's reasonable to assume that - * others will actually be genuine users - * - * Don't split per-notch, as this wll just cause more confusion - */ - case 4: - case 6: - switch (currentFormat) - { - case TrainDatFormats.openBVE: - if (myVersion >= 1830 && n == 4) - { - electricBrakeDelayUp = a; - } - else if (myVersion >= 1534) - { - locoBrakeDelayUp = Lines[i].Split(',').Select(x => double.Parse(x, Culture)).ToArray(); - } - else - { - if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60) - { - break; - } - locoBrakeDelayUp = new[] {a}; - } - break; - default: - electricBrakeDelayUp = a; - break; - } - break; - case 5: - case 7: - switch (currentFormat) - { - case TrainDatFormats.openBVE: - if (myVersion >= 1830 && n == 5) - { - electricBrakeDelayDown = a; - } - else if(myVersion >= 1534) - { - locoBrakeDelayDown = Lines[i].Split(',').Select(x => double.Parse(x, Culture)).ToArray(); - } - else - { - if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60) - { - break; - } - locoBrakeDelayDown = new[] {a}; - } - break; - default: - electricBrakeDelayDown = a; - break; - } - break; + //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "a0 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); } - } i++; n++; - } i--; break; - case "#move": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) { - switch (n) { - case 0: - if (a != 0) - { - JerkPowerUp = 0.01 * a; - } - else - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "JerkPowerUp is expected to be non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - break; - case 1: - if (a != 0) - { - JerkPowerDown = 0.01 * a; - } - else - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "JerkPowerDown is expected to be non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - break; - case 2: - if (a != 0) - { - JerkBrakeUp = 0.01 * a; - } - else - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "JerkBrakeUp is expected to be non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - break; - case 3: - if (a != 0) - { - JerkBrakeDown = 0.01 * a; - } - else - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "JerkBrakeDown is expected to be non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - break; - case 4: - if (a >= 0) - { - BrakeCylinderUp = 1000.0 * a; - } - else - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "BrakeCylinderUp is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - break; - case 5: - if (a >= 0) - { - BrakeCylinderDown = 1000.0 * a; - } - else - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "BrakeCylinderDown is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - break; + if (curveValues[1] > 0) + { + curve.StageOneAcceleration = curveValues[1] * 0.277777777777778; } - } i++; n++; - } i--; break; - case "#brake": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) - { - int b; - switch (n) + else { - case 0: - b = (int) Math.Round(a); - if (b >= 0 & b <= 2) - { - trainBrakeType = (BrakeSystemType) b; - } - else - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "The setting for BrakeType is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - trainBrakeType = BrakeSystemType.ElectromagneticStraightAirBrake; - } - break; - case 1: - b = (int) Math.Round(a); - if (b >= 0 & b <= 2) - { - ElectropneumaticType = (EletropneumaticBrakeType) b; - } - else - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "The setting for ElectropneumaticType is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - ElectropneumaticType = EletropneumaticBrakeType.None; - } - break; - case 2: - if (a < 0) - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "BrakeControlSpeed must be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName); - break; - } - if (a != 0 && trainBrakeType == BrakeSystemType.AutomaticAirBrake) - { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "BrakeControlSpeed will be ignored due to the current brake setup at line " + (i + 1).ToString(Culture) + " in " + FileName); - break; - } - BrakeControlSpeed = a * 0.277777777777778; //Convert to m/s - break; - case 3: - b = (int) Math.Round(a); - switch (b) - { - case 0: - //Not fitted - break; - case 1: - //Notched air brake - Train.Handles.HasLocoBrake = true; - locomotiveBrakeType = BrakeSystemType.ElectromagneticStraightAirBrake; - break; - case 2: - //Automatic air brake - Train.Handles.HasLocoBrake = true; - locomotiveBrakeType = BrakeSystemType.AutomaticAirBrake; - break; - } - break; + //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "a1 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); } - } i++; n++; - } i--; break; - case "#pressure": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) { - switch (n) { - case 0: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "BrakeCylinderServiceMaximumPressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - BrakeCylinderServiceMaximumPressure = a * 1000.0; - } break; - case 1: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "BrakeCylinderEmergencyMaximumPressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - BrakeCylinderEmergencyMaximumPressure = a * 1000.0; - } break; - case 2: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MainReservoirMinimumPressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - MainReservoirMinimumPressure = a * 1000.0; - } break; - case 3: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MainReservoirMaximumPressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - MainReservoirMaximumPressure = a * 1000.0; - } break; - case 4: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "BrakePipePressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - BrakePipePressure = a * 1000.0; - } break; + if (curveValues[2] > 0) + { + curve.StageOneSpeed = curveValues[2] * 0.277777777777778; } - } i++; n++; - } i--; break; - case "#handle": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseIntVb6(Lines[i], out var a)) { - switch (n) { - case 0: - switch (a) - { - case 0: - Train.Handles.HandleType = HandleType.TwinHandle; - break; - case 1: - Train.Handles.HandleType = HandleType.SingleHandle; - break; - case 2: - Train.Handles.HandleType = HandleType.InterlockedTwinHandle; - break; - case 3: - Train.Handles.HandleType = HandleType.InterlockedReverserHandle; - break; - default: - Train.Handles.HandleType = HandleType.TwinHandle; - break; - } - break; - case 1: - if (a > 0) - { - powerNotches = a; - } - else - { - powerNotches = 8; - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfPowerNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - break; - case 2: - if (a > 0) - { - brakeNotches = a; - } - else - { - brakeNotches = 8; - if (trainBrakeType != BrakeSystemType.AutomaticAirBrake) - { - /* - * NumberOfBrakeNotches is ignored when using the auto-air brake - * Whilst this value is invalid, it doesn't actually get used so get - * rid of the pointless error message it generates - */ - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfBrakeNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - } - break; - case 3: - powerReduceSteps = a; - break; - case 4: - if (a < 0 || a > 3) - { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "EbHandleBehaviour is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - break; - } - Train.Handles.EmergencyBrake.OtherHandlesBehaviour = (EbHandleBehaviour) a; - break; - case 5: - if (a >= 0) + else + { + //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v1 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); + } + if (curveValues[3] > 0) + { + curve.StageTwoSpeed = curveValues[3] * 0.277777777777778; + if (curve.StageTwoSpeed < curve.StageOneSpeed) + { + //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v2 in section #ACCELERATION is expected to be greater than or equal to v1 at line " + (i + 1).ToString(Culture) + " in file " + FileName); + curve.StageTwoSpeed = curve.StageOneSpeed; + } + } + else + { + //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v2 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); + } + if (curveValues[4] > 0) + { + if (datFile.Format > TrainDatFormats.BVE2000000) + { + if (curveValues[4] <= 0.0) { - - locoBrakeNotches = a; + curve.StageTwoExponent = 1.0; + // Plugin.CurrentHost.AddMessage(MessageType.Error, false, "e in section #ACCELERATION is expected to be positive at line " + (i + 1).ToString(Culture) + " in file " + FileName); } else { - locoBrakeNotches = 8; - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfLocoBrakeNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - - break; - case 6: - locoBrakeType = a; - break; - case 7: - if (currentFormat == TrainDatFormats.openBVE && myVersion >= 15311) - { - if (a > 0) + const double c = 4.439346232277577; + curve.StageTwoExponent = 1.0 - Math.Log(curveValues[4]) * curve.StageTwoSpeed * c; + if (curve.StageTwoExponent <= 0.0) { - driverPowerNotches = a; + curve.StageTwoExponent = 1.0; } - else + else if (curve.StageTwoExponent > 4.0) { - driverPowerNotches = 8; - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfDriverPowerNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); + curve.StageTwoExponent = 4.0; } } - break; - case 8: - if (currentFormat == TrainDatFormats.openBVE && myVersion >= 15311) + } + else + { + curve.StageTwoExponent = curveValues[4]; + if (curve.StageTwoExponent <= 0.0) { - if (a > 0) - { - driverBrakeNotches = a; - } - else - { - driverBrakeNotches = 8; - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfDriverBrakeNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); - } + curve.StageTwoExponent = 1.0; } - break; + } } - } i++; n++; - } i--; break; - case "#cockpit": - case "#cab": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) { - switch (n) { - case 0: Driver.X = 0.001 * a; break; - case 1: Driver.Y = 0.001 * a; break; - case 2: Driver.Z = 0.001 * a; break; - case 3: DriverCar = (int)Math.Round(a); break; + AccelerationCurves[AccelerationCurves.Length - 1] = curve; + } + else + { + //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Invalid acceleration curve entry at line " + (i + 1).ToString(Culture) + " in file " + FileName); + } + } + break; + case TrainDatSection.Performance: + if (subBlock.GetNextDouble(TrainDatKey.BrakeDeceleration, NumberRange.Positive, out double brakeDeceleration)) + { + BrakeDeceleration = brakeDeceleration * 0.277777777777778; + } + if (subBlock.GetNextDouble(TrainDatKey.CoefficientOfStaticFriction, NumberRange.Positive, out double staticFriction)) + { + if (Plugin.CurrentOptions.EnableBveTsHacks && (staticFriction > 0.1 || staticFriction < 1.0)) + { + break; + } + CoefficientOfStaticFriction = staticFriction; + } + if (subBlock.GetNextDouble(TrainDatKey.CoefficientOfRollingResistance, NumberRange.Positive, out double rollingResistance)) + { + CoefficientOfRollingResistance = rollingResistance; + } + + if (subBlock.GetNextDouble(TrainDatKey.CoefficientOfAerodynamicDrag, NumberRange.Positive, out double aerodynamicDrag)) + { + AerodynamicDragCoefficient = aerodynamicDrag; + } + break; + case TrainDatSection.Delay: + if (subBlock.GetNextDoubleArray(',', out powerDelayUp)) + { + if (datFile.Format != TrainDatFormats.openBVE || datFile.Version < 1534) + { + Array.Resize(ref powerDelayUp, 1); + if (Plugin.CurrentOptions.EnableBveTsHacks && powerDelayUp[0] > 60) + { + powerDelayUp[0] = 0; } - } i++; n++; - } i--; break; - case "#car": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) { - switch (n) { - case 0: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "MotorCarMass is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - MotorCarMass = a * 1000.0; - } break; - case 1: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfMotorCars is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - MotorCars = (int)Math.Round(a); - } break; - case 2: TrailerCarMass = a * 1000.0; break; - case 3: - if (a < 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfTrailerCars is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - TrailerCars = (int)Math.Round(a); - } break; - case 4: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "LengthOfACar is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - CarLength = a; - } break; - case 5: FrontCarIsMotorCar = a == 1.0; break; - case 6: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "WidthOfACar is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - CarWidth = a; - CarExposedFrontalArea = 0.65 * CarWidth * CarHeight; - CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight; - } break; - case 7: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "HeightOfACar is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - CarHeight = a; - CarExposedFrontalArea = 0.65 * CarWidth * CarHeight; - CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight; - } break; - case 8: CenterOfGravityHeight = a; break; - case 9: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "ExposedFrontalArea is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - CarExposedFrontalArea = a; - CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight; - } break; - case 10: - if (a <= 0.0) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "UnexposedFrontalArea is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName); - } else { - CarUnexposedFrontalArea = a; - } break; + } + } + if (subBlock.GetNextDoubleArray(',', out powerDelayDown)) + { + if (datFile.Format != TrainDatFormats.openBVE || datFile.Version < 1534) + { + Array.Resize(ref powerDelayDown, 1); + if (Plugin.CurrentOptions.EnableBveTsHacks && powerDelayDown[0] > 60) + { + powerDelayDown[0] = 0; } - } i++; n++; - } i--; break; - case "#device": - i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) { - switch (n) { - case 0: - if (a == 0.0) { - Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsSn; - } else if (a == 1.0) { - Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsSn; - Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsP; - } - break; - case 1: - if (a == 1.0 | a == 2.0) { - Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.Atc; - } - break; - case 2: - if (a == 1.0) { - Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.Eb; - } - break; - case 3: - Train.Specs.HasConstSpeed = a == 1.0; break; - case 4: - Train.Handles.HasHoldBrake = a == 1.0; break; - case 5: - int dt = (int) Math.Round(a); - if (dt < 4 && dt > -1) - { - ReAdhesionDevice = (ReadhesionDeviceType)dt; - } - else - { - ReAdhesionDevice = ReadhesionDeviceType.NotFitted; - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "ReAdhesionDeviceType is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - } - break; - case 7: - { - int b = (int)Math.Round(a); - if (b >= 0 & b <= 2) { - passAlarm = (PassAlarmType)b; - } else { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "PassAlarm is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - } break; - } - case 8: - { - int b = (int)Math.Round(a); - if (b >= 0 & b <= 2) { - Train.Specs.DoorOpenMode = (DoorMode)b; - } else { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "DoorOpenMode is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - } break; - } - case 9: - { - int b = (int)Math.Round(a); - if (b >= 0 & b <= 2) { - Train.Specs.DoorCloseMode = (DoorMode)b; - } else { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "DoorCloseMode is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - } break; - } - case 10: - { - if (a >= 0.0) { - DoorWidth = a; - } else { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "DoorWidth is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - } break; - } - case 11: - { - if (a >= 0.0) { - DoorTolerance = a; - } else { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "DoorMaxTolerance is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName); - } break; - } + } + } + if (subBlock.GetNextDoubleArray(',', out brakeDelayUp)) + { + if (datFile.Format != TrainDatFormats.openBVE || datFile.Version < 1534) + { + Array.Resize(ref brakeDelayUp, 1); + if (Plugin.CurrentOptions.EnableBveTsHacks && brakeDelayUp[0] > 60) + { + brakeDelayUp[0] = 0; } - } i++; n++; - } i--; break; - case "#motor_p1": - case "#motor_p2": - case "#motor_b1": - case "#motor_b2": + } + } + if (subBlock.GetNextDoubleArray(',', out brakeDelayDown)) { - int msi = 0; - switch (Lines[i].ToLowerInvariant()) { - case "#motor_p1": msi = BVEMotorSound.MotorP1; break; - case "#motor_p2": msi = BVEMotorSound.MotorP2; break; - case "#motor_b1": msi = BVEMotorSound.MotorB1; break; - case "#motor_b2": msi = BVEMotorSound.MotorB2; break; - } i++; - while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) { - int u = Tables[msi].Entries.Length; - if (n >= u) { - Array.Resize(ref Tables[msi].Entries, 2 * u); - for (int j = u; j < 2 * u; j++) { - Tables[msi].Entries[j].SoundIndex = -1; - Tables[msi].Entries[j].Pitch = 1.0f; - Tables[msi].Entries[j].Gain = 1.0f; - } + if (datFile.Format != TrainDatFormats.openBVE || datFile.Version < 1534) + { + Array.Resize(ref brakeDelayDown, 1); + if (Plugin.CurrentOptions.EnableBveTsHacks && brakeDelayDown[0] > 60) + { + brakeDelayDown[0] = 0; + } + } + } + /* + * https://github.com/leezer3/OpenBVE/issues/737 + * OpenBVE appears to have overlooked these originally + * We can (hopefully) assume that any trains using the LocoBrake feature + * will have set the OPENBVE header, hence it's reasonable to assume that + * others will actually be genuine users + * + * Don't split per-notch, as this wll just cause more confusion + */ + if (datFile.Format != TrainDatFormats.openBVE || datFile.Version >= 1830) + { + subBlock.GetNextDouble(TrainDatKey.ElectricBrakeDelayUp, NumberRange.Positive, out electricBrakeDelayUp); + subBlock.GetNextDouble(TrainDatKey.ElectricBrakeDelayDown, NumberRange.Positive, out electricBrakeDelayDown); + } + subBlock.GetNextDoubleArray(',', out locoBrakeDelayUp); + subBlock.GetNextDoubleArray(',', out locoBrakeDelayDown); + break; + case TrainDatSection.Move: + if (subBlock.GetNextDouble(TrainDatKey.JerkPowerUp, NumberRange.NonZero, out double jerkUp)) + { + JerkPowerUp = 0.01 * jerkUp; + } + if (subBlock.GetNextDouble(TrainDatKey.JerkPowerUp, NumberRange.NonZero, out double jerkDown)) + { + JerkPowerDown = 0.01 * jerkDown; + } + if (subBlock.GetNextDouble(TrainDatKey.JerkBrakeUp, NumberRange.NonZero, out jerkUp)) + { + JerkBrakeUp = 0.01 * jerkUp; + } + if (subBlock.GetNextDouble(TrainDatKey.JerkBrakeUp, NumberRange.NonZero, out jerkDown)) + { + JerkBrakeDown = 0.01 * jerkDown; + } + if (subBlock.GetNextDouble(TrainDatKey.BrakeCylinderUp, NumberRange.Positive, out double bcUp)) + { + BrakeCylinderUp = 1000.0 * bcUp; + } + if (subBlock.GetNextDouble(TrainDatKey.BrakeCylinderDown, NumberRange.Positive, out double bcDown)) + { + BrakeCylinderDown = 1000.0 * bcDown; + } + break; + case TrainDatSection.Brake: + subBlock.GetNextEnumValue(TrainDatKey.BrakeSystemType, out trainBrakeType); + subBlock.GetNextEnumValue(TrainDatKey.ElectropneumaticType, out ElectropneumaticType); + if (subBlock.GetNextDouble(TrainDatKey.BrakeControlSpeed, NumberRange.Positive, out double controlSpeed)) + { + if (controlSpeed > 0 && trainBrakeType == BrakeSystemType.AutomaticAirBrake) + { + //Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "BrakeControlSpeed will be ignored due to the current brake setup at line " + (i + 1).ToString(Culture) + " in " + FileName); + } + else + { + BrakeControlSpeed = controlSpeed * 0.277777777777778; //Convert to m/s + } + } + //if(subBlock.GetNextEnumValue(TrainDatKey.LocoBrakeType, out locoBrakeType)) + { + // 0 - none + // 1 - notched air + // 2 - automatic air + } + break; + case TrainDatSection.Pressure: + if (subBlock.GetNextDouble(TrainDatKey.BrakeCylinderServiceMaximumPressure, NumberRange.Positive, out double pressure)) + { + BrakeCylinderServiceMaximumPressure = pressure * 1000.0; + } + if (subBlock.GetNextDouble(TrainDatKey.BrakeCylinderEmergencyMaximumPressure, NumberRange.Positive, out pressure)) + { + BrakeCylinderEmergencyMaximumPressure = pressure * 1000.0; + } + if (subBlock.GetNextDouble(TrainDatKey.MainReservoirMinimumPressure, NumberRange.Positive, out pressure)) + { + MainReservoirMinimumPressure = pressure * 1000.0; + } + if (subBlock.GetNextDouble(TrainDatKey.MainReservoirMaximumPressure, NumberRange.Positive, out pressure)) + { + MainReservoirMaximumPressure = pressure * 1000.0; + } + if (subBlock.GetNextDouble(TrainDatKey.BrakePipePressure, NumberRange.Positive, out pressure)) + { + BrakePipePressure = pressure * 1000.0; + } + break; + case TrainDatSection.Handle: + subBlock.GetNextEnumValue(TrainDatKey.HandleType, out Train.Handles.HandleType); + if (subBlock.GetNextInt(TrainDatKey.NumberOfPowerNotches, NumberRange.Positive, out int power)) + { + powerNotches = power; + } + if (subBlock.GetNextInt(TrainDatKey.NumberOfBrakeNotches, NumberRange.NonNegative, out int brake)) + { + if (brake == 0) + { + brake = 8; + if (trainBrakeType != BrakeSystemType.AutomaticAirBrake) + { + //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfBrakeNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); } - string t = Lines[i] + ","; int m = 0; - while (true) { - int j = t.IndexOf(','); - if (j == -1) break; - string s = t.Substring(0, j).Trim(); - t = t.Substring(j + 1); - if (NumberFormats.TryParseDoubleVb6(s, out var a)) { - switch (m) { - case 0: - Tables[msi].Entries[n].SoundIndex = (int)Math.Round(a); - break; - case 1: - if (a < 0.0) a = 0.0; - Tables[msi].Entries[n].Pitch = (float)(0.01 * a); - break; - case 2: - if (a < 0.0) a = 0.0; - Tables[msi].Entries[n].Gain = (float)Math.Pow((0.0078125 * a), 0.25); - break; - } - } m++; - } i++; n++; } - if (n != 0) + brakeNotches = brake; + } + if (subBlock.GetNextInt(TrainDatKey.PowerReduceSteps, NumberRange.NonNegative, out int steps)) + { + powerReduceSteps = steps; + } + subBlock.GetNextEnumValue(TrainDatKey.EbHandleBehaviour, out Train.Handles.EmergencyBrake.OtherHandlesBehaviour); + if (subBlock.GetNextInt(TrainDatKey.LocoBrakeNotches, NumberRange.NonNegative, out int locoBrake)) + { + locoBrakeNotches = locoBrake; + } + subBlock.GetNextEnumValue(TrainDatKey.LocoBrakeType, out Train.Handles.LocoBrakeType); + if (datFile.Format == TrainDatFormats.openBVE && datFile.Version >= 15311) + { + if (subBlock.GetNextInt(TrainDatKey.DriverPowerNotches, NumberRange.Positive, out power)) + { + driverPowerNotches = power; + } + if (subBlock.GetNextInt(TrainDatKey.DriverBrakeNotches, NumberRange.Positive, out brake)) { - /* - * Handle duplicated section header: - * If no entries, don't resize - */ - Array.Resize(ref Tables[msi].Entries, n); + driverBrakeNotches = brake; } - i--; - } break; + } + break; + case TrainDatSection.Cab: + subBlock.GetNextDouble(TrainDatKey.DriverX, NumberRange.Any, out Driver.X); + subBlock.GetNextDouble(TrainDatKey.DriverY, NumberRange.Any, out Driver.Y); + subBlock.GetNextDouble(TrainDatKey.DriverZ, NumberRange.Any, out Driver.Z); + Driver *= 0.001; + break; + case TrainDatSection.Car: + if (subBlock.GetNextDouble(TrainDatKey.MotorCarMass, NumberRange.Positive, out double mass)) + { + MotorCarMass = mass * 1000.0; + } + if (subBlock.GetNextInt(TrainDatKey.NumberOfMotorCars, NumberRange.Positive, out int cars)) + { + MotorCars = cars; + } + if (subBlock.GetNextDouble(TrainDatKey.TrailerCarMass, NumberRange.Positive, out mass)) + { + TrailerCarMass = mass * 1000.0; + } + if (subBlock.GetNextInt(TrainDatKey.NumberOfTrailerCars, NumberRange.NonNegative, out cars)) + { + TrailerCars = cars; + } + if (subBlock.GetNextDouble(TrainDatKey.CarLength, NumberRange.Positive, out double length)) + { + CarLength = length; + } + if (subBlock.GetNextBool(TrainDatKey.FrontCarIsMotorCar, out bool frontMotor)) + { + FrontCarIsMotorCar = frontMotor; + } + if (subBlock.GetNextDouble(TrainDatKey.WidthOfCar, NumberRange.Positive, out double width)) + { + CarWidth = width; + } + if (subBlock.GetNextDouble(TrainDatKey.HeightOfCar, NumberRange.Positive, out double height)) + { + CarHeight = height; + } + CarExposedFrontalArea = 0.65 * CarWidth * CarHeight; + CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight; + + if (subBlock.GetNextDouble(TrainDatKey.ExposedFrontalArea, NumberRange.Positive, out double area)) + { + CarExposedFrontalArea = area; + CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight; + } + if (subBlock.GetNextDouble(TrainDatKey.UnexposedFrontalArea, NumberRange.Positive, out area)) + { + CarUnexposedFrontalArea = area; + } + break; + case TrainDatSection.Device: + subBlock.GetNextInt(TrainDatKey.DefaultSafetySystems, NumberRange.NonNegative, out int atsType); + switch (atsType) + { + case 0: + Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsSn; + break; + case 1: + Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsSn; + Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsP; + break; + } + + subBlock.GetNextInt(TrainDatKey.DefaultSafetySystems, NumberRange.NonNegative, out int atcType); + switch (atcType) + { + case 1: + case 2: + Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.Atc; + break; + } + + subBlock.GetNextBool(TrainDatKey.DefaultSafetySystems, out bool hasEb); + if (hasEb) + { + Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.Eb; + } + + subBlock.GetNextBool(TrainDatKey.DefaultSafetySystems, out Train.Specs.HasConstSpeed); + subBlock.GetNextBool(TrainDatKey.DefaultSafetySystems, out Train.Handles.HasHoldBrake); + subBlock.GetNextEnumValue(TrainDatKey.PassAlarm, out passAlarm); + subBlock.GetNextEnumValue(TrainDatKey.DoorOpenMode, out Train.Specs.DoorOpenMode); + subBlock.GetNextEnumValue(TrainDatKey.DoorCloseMode, out Train.Specs.DoorCloseMode); + if (subBlock.GetNextDouble(TrainDatKey.DoorWidth, NumberRange.Positive, out width)) + { + DoorWidth = width; + } + if (subBlock.GetNextDouble(TrainDatKey.DoorMaxTolerance, NumberRange.Positive, out double tolerance)) + { + DoorTolerance = tolerance; + } + break; + case TrainDatSection.Motor_P1: + case TrainDatSection.Motor_P2: + case TrainDatSection.Motor_B1: + case TrainDatSection.Motor_B2: + int msi = 0; + switch (subBlock.Key) + { + case TrainDatSection.Motor_P1: msi = BVEMotorSound.MotorP1; break; + case TrainDatSection.Motor_P2: msi = BVEMotorSound.MotorP2; break; + case TrainDatSection.Motor_B1: msi = BVEMotorSound.MotorB1; break; + case TrainDatSection.Motor_B2: msi = BVEMotorSound.MotorB2; break; + } + Array.Resize(ref Tables[msi].Entries, subBlock.RemainingDataValues); + for (int i = 0; i < Tables[msi].Entries.Length; i++) + { + subBlock.GetNextDoubleArray(',', out double[] entry); + Tables[msi].Entries[i].SoundIndex = (int)Math.Round(entry[0]); + + if (entry[1] < 0.0) entry[1] = 0.0; + Tables[msi].Entries[i].Pitch = (float)(0.01 * entry[1]); + if (entry[2] < 0.0) entry[2] = 0.0; + Tables[msi].Entries[i].Gain = (float)Math.Pow((0.0078125 * entry[2]), 0.25); + } + break; + + } } - + + if (TrailerCars > 0 & TrailerCarMass <= 0.0) { if (currentFormat < TrainDatFormats.openBVE && Plugin.CurrentOptions.EnableBveTsHacks && TrailerCars == 1 && TrailerCarMass == 0) { From 7e412888225f66d3bf59404283285f67f7c5bcb7 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 22 Jul 2026 11:19:57 +0100 Subject: [PATCH 2/4] Missing errors, minor changes --- .../Plugins/Formats.OpenBve/DAT/TrainDat.cs | 55 +++-- .../Plugins/Train.MsTs/Train/VehicleParser.cs | 6 +- .../Train.OpenBve/Train/BVE/TrainDatParser.cs | 228 +++--------------- .../Train/XML/TrainXmlParser.BrakeNode.cs | 3 +- .../Brake/AirBrake/Components/Reservoirs.cs | 5 +- source/TrainManager/Handles/CabHandles.cs | 64 +++++ 6 files changed, 150 insertions(+), 211 deletions(-) diff --git a/source/Plugins/Formats.OpenBve/DAT/TrainDat.cs b/source/Plugins/Formats.OpenBve/DAT/TrainDat.cs index 76d085c589..9ed770a12a 100644 --- a/source/Plugins/Formats.OpenBve/DAT/TrainDat.cs +++ b/source/Plugins/Formats.OpenBve/DAT/TrainDat.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using OpenBveApi.Interface; namespace Formats.OpenBve { @@ -36,7 +37,32 @@ public TrainDatFile(string myFile, HostInterface currentHost) : base(-1, default Format = ParseFormat(lines[i], out Version); } - + const int currentVersion = 18230; + + switch (Format) + { + case TrainDatFormats.openBVE when Version == -1: + currentHost.AddMessage(MessageType.Error, false, "The train.dat version is invalid in " + FileName); + break; + case TrainDatFormats.openBVE: + { + if (Version > currentVersion) + { + currentHost.AddMessage(MessageType.Warning, false, "The train.dat " + FileName + " with version " + Version + " was created with a newer version of openBVE. Please check for an update."); + } + break; + } + case TrainDatFormats.Unsupported: + currentHost.AddMessage(MessageType.Error, false, "The train.dat format " + Version + " is not supported in " + FileName); + break; + case TrainDatFormats.UnknownBVE: + currentHost.AddMessage(MessageType.Error, false, "The train.dat appears to have been created by an unknown BVE version in " + FileName + " - Please report this."); + break; + case TrainDatFormats.MissingHeader: + currentHost.AddMessage(MessageType.Error, false, "The train.dat appears be missing the required header in " + FileName + " - Please report this."); + break; + } + if (lines[i].Length > 0 && lines[i][0] == '#') { if (!Enum.TryParse(lines[i].Substring(1), true, out T1 currentSection)) @@ -72,21 +98,12 @@ public TrainDatFile(string myFile, HostInterface currentHost) : base(-1, default } } - public class TrainDatSection : Block where T1 : struct, Enum where T2 : struct, Enum - { - public TrainDatSection(List blockLines, T1 myKey, int startingLine, string myFile, HostInterface currentHost) : base(-1, myKey, myFile, currentHost) - { - for (int i = 0; i < blockLines.Count; i++) - { - rawValues.Enqueue(new KeyValuePair(startingLine + i, blockLines[i])); - } - } - } + /// Parse the format of the specified train.dat - /// The version line of the train.dat - /// The version of the specified OpenBVE train.dat - private static TrainDatFormats ParseFormat(string line, out int version) + /// The version line of the train.dat + /// The version of the specified OpenBVE train.dat + public static TrainDatFormats ParseFormat(string line, out int version) { version = -1; switch (line.ToLowerInvariant()) @@ -135,4 +152,14 @@ private static TrainDatFormats ParseFormat(string line, out int version) } } } + public class TrainDatSection : Block where T1 : struct, Enum where T2 : struct, Enum + { + public TrainDatSection(List blockLines, T1 myKey, int startingLine, string myFile, HostInterface currentHost) : base(-1, myKey, myFile, currentHost) + { + for (int i = 0; i < blockLines.Count; i++) + { + rawValues.Enqueue(new KeyValuePair(startingLine + i, blockLines[i])); + } + } + } } diff --git a/source/Plugins/Train.MsTs/Train/VehicleParser.cs b/source/Plugins/Train.MsTs/Train/VehicleParser.cs index eabbbc1e72..d37eca425c 100644 --- a/source/Plugins/Train.MsTs/Train/VehicleParser.cs +++ b/source/Plugins/Train.MsTs/Train/VehicleParser.cs @@ -377,8 +377,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r if (r < 0.1) r = 0.1; if (r > 1.0) r = 1.0; airBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * operatingPressure, 200000.0, 0.5, r); - airBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - airBrake.EqualizingReservoir.NormalPressure = 1.005 * operatingPressure; + airBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0, 1.005 * operatingPressure); airBrake.StraightAirPipe = new StraightAirPipe(300000.0, emergencyRate * emergencyVal, releaseRate * releaseVal); currentCar.CarBrake = airBrake; @@ -390,8 +389,7 @@ internal void Parse(string trainSetDirectory, string wagonName, bool isEngine, r vacuumBrake.MainReservoir = new MainReservoir(71110, 84660, 0.01, 0.075 / train.Cars.Length); // ~21in/hg - ~25in/hg vacuumBrake.BrakeCylinder = new BrakeCylinder(brakeCylinderMaximumPressure, brakeCylinderMaximumPressure * 1.1, 90000.0, 300000.0, 200000.0); vacuumBrake.AuxiliaryReservoir = new AuxiliaryReservoir(0.975 * brakeCylinderMaximumPressure, 200000.0, 0.5, 1.0); - vacuumBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - vacuumBrake.EqualizingReservoir.NormalPressure = 1.005 * (vacuumBrake.BrakeCylinder.EmergencyMaximumPressure + 0.75 * (vacuumBrake.MainReservoir.MinimumPressure - vacuumBrake.BrakeCylinder.EmergencyMaximumPressure)); + vacuumBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0, 1.005 * (vacuumBrake.BrakeCylinder.EmergencyMaximumPressure + 0.75 * (vacuumBrake.MainReservoir.MinimumPressure - vacuumBrake.BrakeCylinder.EmergencyMaximumPressure))); vacuumBrake.BrakePipe = new BrakePipe(brakeCylinderMaximumPressure, 10000000.0, 1500000.0, 5000000.0, false); currentCar.CarBrake = vacuumBrake; } diff --git a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs b/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs index 5c398aa7e7..5be01de48e 100644 --- a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs +++ b/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs @@ -86,73 +86,6 @@ private static string[] ReadFile(string fileName, Encoding encoding) return lines; } - /// Parse the format of the specified train.dat - /// The array of the specified train.dat - /// The version of the specified OpenBVE train.dat - private static TrainDatFormats ParseFormat(IReadOnlyList lines, out int version) - { - version = -1; - for (int i = 0; i < lines.Count; i++) - { - if (lines[i].Length <= 0) - { - continue; - } - - string t = lines[i].ToLowerInvariant(); - switch (t) - { - case "bve1200000": - return TrainDatFormats.BVE1200000; - case "bve1210000": - return TrainDatFormats.BVE1210000; - case "bve1220000": - return TrainDatFormats.BVE1220000; - case "bve2000000": - return TrainDatFormats.BVE2000000; - case "bve2060000": - return TrainDatFormats.BVE2060000; - case "openbve": - version = 0; - return TrainDatFormats.openBVE; - case "#acceleration": - case "#deceleration": - case "#delay": - case "#move": - case "#brake": - case "#pressure": - case "#handle": - case "#cab": - case "#car": - case "#device": - case "motor_p1": - case "motor_p2": - case "brake_p1": - case "brake_p2": - version = 0; - return TrainDatFormats.MissingHeader; - default: - if (t.ToLowerInvariant().StartsWith("openbve")) - { - string tt = t.Substring(7, t.Length - 7).Trim(); - if (!NumberFormats.TryParseIntVb6(tt, out version)) - { - version = -1; - } - return TrainDatFormats.openBVE; - } - - if (t.ToLowerInvariant().StartsWith("bve")) - { - return TrainDatFormats.UnknownBVE; - } - - return TrainDatFormats.Unsupported; - } - } - return TrainDatFormats.Unsupported; - } - /// /// Checks whether the parser can load the specified file. /// @@ -172,7 +105,7 @@ internal bool CanLoad(string fileName) return false; } - TrainDatFormats format = ParseFormat(lines, out _); + TrainDatFormats format = TrainDatFile.ParseFormat(lines[0], out _); if (format == TrainDatFormats.MissingHeader) { // Some NYCTA stuff seems to be missing the version header from their train.dat files @@ -187,35 +120,6 @@ internal bool CanLoad(string fileName) /// The text encoding to use /// The train internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { - System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; - - string[] Lines = ReadFile(FileName, Encoding); - - // Check version - const int currentVersion = 18230; - TrainDatFormats currentFormat = ParseFormat(Lines, out int myVersion); - string versionString = Lines.FirstOrDefault(x => x.Length > 0) ?? Lines[0]; - switch (currentFormat) - { - case TrainDatFormats.openBVE when myVersion == -1: - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "The train.dat version " + versionString + " is invalid in " + FileName); - break; - case TrainDatFormats.openBVE: - { - if (myVersion > currentVersion) - { - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "The train.dat " + FileName + " with version " + versionString + " was created with a newer version of openBVE. Please check for an update."); - } - break; - } - case TrainDatFormats.Unsupported: - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "The train.dat format " + versionString + " is not supported in " + FileName); - break; - case TrainDatFormats.UnknownBVE: - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "The train.dat format " + versionString + " appears to have been created by an unknown BVE version. in " + FileName + " - Please report this."); - break; - } - // initialize double BrakeCylinderServiceMaximumPressure = 440000.0; double BrakeCylinderEmergencyMaximumPressure = 440000.0; @@ -268,12 +172,9 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } } // parse configuration - double invfac = Lines.Length == 0 ? 0.1 : 0.1 / Lines.Length; - - - TrainDatFile datFile = new TrainDatFile(FileName, Plugin.CurrentHost); - + double invfac = datFile.RemainingSubBlocks == 0 ? 0.1 : 0.1 / datFile.RemainingSubBlocks; + int totalBlocks = datFile.RemainingSubBlocks; while (datFile.RemainingSubBlocks > 0) { Block subBlock = datFile.ReadNextBlock(); @@ -292,7 +193,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } else { - //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "a0 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "a0 in section #ACCELERATION is expected to be greater than zero for Curve " + AccelerationCurves.Length + " in file " + FileName); } if (curveValues[1] > 0) { @@ -300,7 +201,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } else { - //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "a1 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "a1 in section #ACCELERATION is expected to be greater than zero for Curve " + AccelerationCurves.Length + " in file " + FileName); } if (curveValues[2] > 0) { @@ -308,20 +209,20 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } else { - //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v1 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v1 in section #ACCELERATION is expected to be greater than zero for Curve " + AccelerationCurves.Length + " in file " + FileName); } if (curveValues[3] > 0) { curve.StageTwoSpeed = curveValues[3] * 0.277777777777778; if (curve.StageTwoSpeed < curve.StageOneSpeed) { - //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v2 in section #ACCELERATION is expected to be greater than or equal to v1 at line " + (i + 1).ToString(Culture) + " in file " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v2 in section #ACCELERATION is expected to be greater than or equal to v1 for Curve " + AccelerationCurves.Length + " in file " + FileName); curve.StageTwoSpeed = curve.StageOneSpeed; } } else { - //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v2 in section #ACCELERATION is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in file " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "v2 in section #ACCELERATION is expected to be greater than zero for Curve " + AccelerationCurves.Length + " in file " + FileName); } if (curveValues[4] > 0) { @@ -330,7 +231,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { if (curveValues[4] <= 0.0) { curve.StageTwoExponent = 1.0; - // Plugin.CurrentHost.AddMessage(MessageType.Error, false, "e in section #ACCELERATION is expected to be positive at line " + (i + 1).ToString(Culture) + " in file " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "e in section #ACCELERATION is expected to be positive for Curve " + AccelerationCurves.Length + " in file " + FileName); } else { @@ -359,7 +260,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } else { - //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Invalid acceleration curve entry at line " + (i + 1).ToString(Culture) + " in file " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Invalid acceleration curve entry for Curve " + AccelerationCurves.Length + " in file " + FileName); } } break; @@ -481,18 +382,29 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { { if (controlSpeed > 0 && trainBrakeType == BrakeSystemType.AutomaticAirBrake) { - //Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "BrakeControlSpeed will be ignored due to the current brake setup at line " + (i + 1).ToString(Culture) + " in " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "BrakeControlSpeed will be ignored due to the current brake setup in " + FileName); } else { BrakeControlSpeed = controlSpeed * 0.277777777777778; //Convert to m/s } } - //if(subBlock.GetNextEnumValue(TrainDatKey.LocoBrakeType, out locoBrakeType)) + subBlock.GetNextInt(TrainDatKey.LocoBrakeType, NumberRange.NonNegative, out locoBrakeType); + switch (locoBrakeType) { - // 0 - none - // 1 - notched air - // 2 - automatic air + case 0: + //Not fitted + break; + case 1: + //Notched air brake + Train.Handles.HasLocoBrake = true; + locomotiveBrakeType = BrakeSystemType.ElectromagneticStraightAirBrake; + break; + case 2: + //Automatic air brake + Train.Handles.HasLocoBrake = true; + locomotiveBrakeType = BrakeSystemType.AutomaticAirBrake; + break; } break; case TrainDatSection.Pressure: @@ -530,7 +442,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { brake = 8; if (trainBrakeType != BrakeSystemType.AutomaticAirBrake) { - //Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfBrakeNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfBrakeNotches is expected to be positive and non-zero in " + FileName); } } @@ -676,14 +588,13 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { Tables[msi].Entries[i].Gain = (float)Math.Pow((0.0078125 * entry[2]), 0.25); } break; - - } + Plugin.CurrentProgress = Plugin.LastProgress + invfac * (totalBlocks - datFile.RemainingSubBlocks); } - if (TrailerCars > 0 & TrailerCarMass <= 0.0) { - if (currentFormat < TrainDatFormats.openBVE && Plugin.CurrentOptions.EnableBveTsHacks && TrailerCars == 1 && TrailerCarMass == 0) + if (TrailerCars > 0 && TrailerCarMass == 0.0) { + if (datFile.Format < TrainDatFormats.openBVE && Plugin.CurrentOptions.EnableBveTsHacks && TrailerCars == 1 && TrailerCarMass == 0) { /* * Early BVE train editor versions appear to have been unable to create a train with no trailer cars, @@ -693,7 +604,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } else { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "TrailerCarMass is expected to be positive in " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "TrailerCarMass is expected to be non-zero in " + FileName); TrailerCarMass = 1.0; } @@ -711,7 +622,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } if (driverPowerNotches == 0) { - if (currentFormat == TrainDatFormats.openBVE && myVersion >= 15311) + if (datFile.Format == TrainDatFormats.openBVE && datFile.Version >= 15311) { Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfDriverPowerNotches was not set in " + FileName); } @@ -719,7 +630,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } if (driverBrakeNotches == 0) { - if (currentFormat == TrainDatFormats.openBVE && myVersion >= 15311) + if (datFile.Format == TrainDatFormats.openBVE && datFile.Version >= 15311) { Plugin.CurrentHost.AddMessage(MessageType.Error, false, "NumberOfDriverBrakeNotches was not set in " + FileName); } @@ -753,8 +664,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { Train.Handles.LocoBrakeType = (LocoBrakeType)locoBrakeType; Train.Handles.HoldBrake = new HoldBrakeHandle(Train); // apply data - if (MotorCars < 1) MotorCars = 1; - if (TrailerCars < 0) TrailerCars = 0; + MotorCars = Math.Max(MotorCars, 1); int Cars = MotorCars + TrailerCars; Train.Cars = new CarBase[Cars]; for (int i = 0; i < Train.Cars.Length; i++) @@ -763,7 +673,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { } double DistanceBetweenTheCars = 0.3; - if (DriverCar < 0 | DriverCar >= Cars) { + if (DriverCar >= Cars) { Plugin.CurrentHost.AddMessage(MessageType.Error, false, "DriverCar must point to an existing car in " + FileName); DriverCar = 0; @@ -824,7 +734,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { errors = true; } if (errors) { - Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Entry " + (i + 1).ToString(Culture) + " in the #ACCELERATION section is missing or invalid in " + FileName); + Plugin.CurrentHost.AddMessage(MessageType.Error, false, "Entry " + (i + 1) + " in the #ACCELERATION section is missing or invalid in " + FileName); } if (AccelerationCurves[i].MaximumAcceleration > MaximumAcceleration) { MaximumAcceleration = AccelerationCurves[i].MaximumAcceleration; @@ -932,8 +842,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { Train.Cars[i].CarBrake.MainReservoir = new MainReservoir(MainReservoirMinimumPressure, MainReservoirMaximumPressure, 0.01, (trainBrakeType == BrakeSystemType.AutomaticAirBrake ? 0.25 : 0.075) / Cars); Train.Cars[i].CarBrake.MainReservoir.Volume = 0.5; // Organization for Co-Operation between Railways specifies 340L to 680L main reservoir capacity for EMU, so let's pick something in the middle (in m³) - Train.Cars[i].CarBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0); - Train.Cars[i].CarBrake.EqualizingReservoir.NormalPressure = 1.005 * OperatingPressure; + Train.Cars[i].CarBrake.EqualizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0, 1.005 * OperatingPressure); Train.Cars[i].CarBrake.EqualizingReservoir.Volume = 0.015; // very small reservoir for observation, so guess at 15L Train.Cars[i].CarBrake.BrakePipe = new BrakePipe(OperatingPressure, 10000000.0, 1500000.0, 5000000.0, trainBrakeType == BrakeSystemType.ElectricCommandBrake); @@ -976,59 +885,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { Train.SafetySystems.PilotLamp = new PilotLamp(Train.Cars[DriverCar]); Train.SafetySystems.StationAdjust = new StationAdjustAlarm(Train); Train.SafetySystems.Headlights = new LightSource(Train, 1); - switch (Plugin.CurrentOptions.TrainStart) - { - // starting mode - case TrainStartMode.ServiceBrakesAts: - if (trainBrakeType == BrakeSystemType.AutomaticAirBrake) - { - Train.Handles.Brake.Driver = (int)AirBrakeHandleState.Service; - Train.Handles.Brake.Actual = (int)AirBrakeHandleState.Service; - } - else - { - int notch = (int)Math.Round(0.7 * Train.Handles.Brake.MaximumNotch); - Train.Handles.Brake.Driver = notch; - Train.Handles.Brake.Actual = notch; - } - Train.Handles.EmergencyBrake.Driver = false; - Train.Handles.EmergencyBrake.Safety = false; - Train.Handles.EmergencyBrake.Actual = false; - Train.Handles.Reverser.Driver = ReverserPosition.Forwards; - Train.Handles.Reverser.Actual = ReverserPosition.Forwards; - break; - case TrainStartMode.EmergencyBrakesAts: - if (trainBrakeType == BrakeSystemType.AutomaticAirBrake) - { - Train.Handles.Brake.Driver = (int)AirBrakeHandleState.Service; - Train.Handles.Brake.Actual = (int)AirBrakeHandleState.Service; - } - else - { - Train.Handles.Brake.Driver = Train.Handles.Brake.MaximumNotch; - Train.Handles.Brake.Actual = Train.Handles.Brake.MaximumNotch; - } - - Train.Handles.EmergencyBrake.Driver = true; - Train.Handles.EmergencyBrake.Safety = true; - Train.Handles.EmergencyBrake.Actual = true; - break; - default: - if (trainBrakeType == BrakeSystemType.AutomaticAirBrake) - { - Train.Handles.Brake.Driver = (int)AirBrakeHandleState.Service; - Train.Handles.Brake.Actual = (int)AirBrakeHandleState.Service; - } - else - { - Train.Handles.Brake.Driver = Train.Handles.Brake.MaximumNotch; - Train.Handles.Brake.Actual = Train.Handles.Brake.MaximumNotch; - } - Train.Handles.EmergencyBrake.Driver = true; - Train.Handles.EmergencyBrake.Safety = true; - Train.Handles.EmergencyBrake.Actual = true; - break; - } + Train.Handles.Setup(Plugin.CurrentOptions.TrainStart); // apply other attributes for all cars double AxleDistance = 0.4 * CarLength; for (int i = 0; i < Cars; i++) { @@ -1066,14 +923,7 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { Train.Cars[i].Specs.CriticalTopplingAngle = 0.5 * Math.PI - Math.Atan(2 * Train.Cars[i].Specs.CenterOfGravityHeight / Train.Cars[i].Width); } - if (Cars == 1) - { - Train.Cars[Train.Cars.Length - 1].BeaconReceiver.TriggerType = EventTriggerType.SingleCarTrain; - } - else - { - Train.Cars[Train.Cars.Length - 1].BeaconReceiver.TriggerType = EventTriggerType.TrainRear; - } + Train.Cars[Train.Cars.Length - 1].BeaconReceiver.TriggerType = Cars == 1 ? EventTriggerType.SingleCarTrain : EventTriggerType.TrainRear; Plugin.MotorSoundTables = Tables; diff --git a/source/Plugins/Train.OpenBve/Train/XML/TrainXmlParser.BrakeNode.cs b/source/Plugins/Train.OpenBve/Train/XML/TrainXmlParser.BrakeNode.cs index 5a195a2b28..c97fb4ce45 100644 --- a/source/Plugins/Train.OpenBve/Train/XML/TrainXmlParser.BrakeNode.cs +++ b/source/Plugins/Train.OpenBve/Train/XML/TrainXmlParser.BrakeNode.cs @@ -112,8 +112,7 @@ private void ParseBrakeBlock(Block block, int carI AirBrake airBrake = Train.Cars[carIndex].CarBrake as AirBrake; airBrake.Compressor = new Compressor(compressorRate, Train.Cars[carIndex].CarBrake.MainReservoir, Train.Cars[carIndex]); airBrake.StraightAirPipe = new StraightAirPipe(straightAirPipeServiceRate, straightAirPipeEmergencyRate, straightAirPipeReleaseRate); - Train.Cars[carIndex].CarBrake.EqualizingReservoir = new EqualizingReservoir(equalizingReservoirServiceRate, equalizingReservoirEmergencyRate, equalizingReservoirChargeRate); - Train.Cars[carIndex].CarBrake.EqualizingReservoir.NormalPressure = 1.005 * brakePipeNormalPressure; + Train.Cars[carIndex].CarBrake.EqualizingReservoir = new EqualizingReservoir(equalizingReservoirServiceRate, equalizingReservoirEmergencyRate, equalizingReservoirChargeRate, 1.005 * brakePipeNormalPressure); Train.Cars[carIndex].CarBrake.EqualizingReservoir.Volume = equalizingReservoirVolume; Train.Cars[carIndex].CarBrake.BrakePipe = new BrakePipe(brakePipeNormalPressure, brakePipeChargeRate, brakePipeServiceRate, brakePipeEmergencyRate, Train.Cars[0].CarBrake is ElectricCommandBrake); diff --git a/source/TrainManager/Brake/AirBrake/Components/Reservoirs.cs b/source/TrainManager/Brake/AirBrake/Components/Reservoirs.cs index bd119f63ca..c108da2a68 100644 --- a/source/TrainManager/Brake/AirBrake/Components/Reservoirs.cs +++ b/source/TrainManager/Brake/AirBrake/Components/Reservoirs.cs @@ -26,13 +26,14 @@ public class EqualizingReservoir : AbstractReservoir /// The rate when EB brakes are applied in Pa/s internal readonly double EmergencyRate; /// The normal pressure - public double NormalPressure; + public readonly double NormalPressure; /// The internal volume of the reservoir - public EqualizingReservoir(double serviceRate, double emergencyRate, double chargeRate) : base(chargeRate, 0.0) + public EqualizingReservoir(double serviceRate, double emergencyRate, double chargeRate, double normalPressure) : base(chargeRate, 0.0) { ServiceRate = serviceRate; EmergencyRate = emergencyRate; + NormalPressure = normalPressure; } /// Creates a dummy equalizing reservoir diff --git a/source/TrainManager/Handles/CabHandles.cs b/source/TrainManager/Handles/CabHandles.cs index 64c4675c33..1da127cbf4 100644 --- a/source/TrainManager/Handles/CabHandles.cs +++ b/source/TrainManager/Handles/CabHandles.cs @@ -1,4 +1,7 @@ using OpenBveApi.Interface; +using OpenBveApi.Trains; +using System; +using TrainManager.BrakeSystems; namespace TrainManager.Handles { @@ -26,6 +29,66 @@ public struct CabHandles /// The loco brake type public LocoBrakeType LocoBrakeType; + /// Called to setup the initial handle states + /// The selected train start mode + public void Setup(TrainStartMode trainStart) + { + switch (trainStart) + { + // starting mode + case TrainStartMode.ServiceBrakesAts: + if (Brake.baseTrain.Cars[Brake.baseTrain.DriverCar].CarBrake is AutomaticAirBrake) + { + Brake.Driver = (int)AirBrakeHandleState.Service; + Brake.Actual = (int)AirBrakeHandleState.Service; + } + else + { + int notch = (int)Math.Round(0.7 * Brake.MaximumNotch); + Brake.Driver = notch; + Brake.Actual = notch; + } + EmergencyBrake.Driver = false; + EmergencyBrake.Safety = false; + EmergencyBrake.Actual = false; + Reverser.Driver = ReverserPosition.Forwards; + Reverser.Actual = ReverserPosition.Forwards; + break; + case TrainStartMode.EmergencyBrakesAts: + if (Brake.baseTrain.Cars[Brake.baseTrain.DriverCar].CarBrake is AutomaticAirBrake) + { + Brake.Driver = (int)AirBrakeHandleState.Service; + Brake.Actual = (int)AirBrakeHandleState.Service; + } + else + { + Brake.Driver = Brake.MaximumNotch; + Brake.Actual = Brake.MaximumNotch; + } + + EmergencyBrake.Driver = true; + EmergencyBrake.Safety = true; + EmergencyBrake.Actual = true; + break; + default: + if (Brake.baseTrain.Cars[Brake.baseTrain.DriverCar].CarBrake is AutomaticAirBrake) + { + Brake.Driver = (int)AirBrakeHandleState.Service; + Brake.Actual = (int)AirBrakeHandleState.Service; + } + else + { + Brake.Driver = Brake.MaximumNotch; + Brake.Actual = Brake.MaximumNotch; + } + EmergencyBrake.Driver = true; + EmergencyBrake.Safety = true; + EmergencyBrake.Actual = true; + break; + } + } + + /// Called when a Control is pressed public void ControlDown(Control Control) { switch (Control.Command) @@ -369,6 +432,7 @@ public void ControlDown(Control Control) } } + /// Called when a control is released public void ControlUp(Control Control) { switch (Control.Command) From 38944e10ef4cbbc9f6a3bef29a1355c2236a2141 Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 22 Jul 2026 11:30:45 +0100 Subject: [PATCH 3/4] Minor bits --- source/Plugins/Train.OpenBve/Plugin.cs | 10 ++ .../Train.OpenBve/Train/BVE/TrainDatParser.cs | 95 +++++-------------- 2 files changed, 32 insertions(+), 73 deletions(-) diff --git a/source/Plugins/Train.OpenBve/Plugin.cs b/source/Plugins/Train.OpenBve/Plugin.cs index 346ff7ca16..1571c74a29 100644 --- a/source/Plugins/Train.OpenBve/Plugin.cs +++ b/source/Plugins/Train.OpenBve/Plugin.cs @@ -478,6 +478,16 @@ public override string GetDescription(string trainPath, Encoding encoding = null // another variant on readme descriptionFile = Path.CombineFile(trainPath, "read me.txt"); } + + if (!File.Exists(descriptionFile)) + { + // common pattern for UK BVE4 trains + string[] files = Directory.GetFiles(trainPath, "readme.bve4*.txt", SearchOption.TopDirectoryOnly); + if (files.Length > 0) + { + descriptionFile = files[0]; + } + } if (File.Exists(descriptionFile)) { if (encoding == null) diff --git a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs b/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs index 5be01de48e..3b8b129cbf 100644 --- a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs +++ b/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; -using System.Drawing.Imaging; -using System.Linq; +using System.IO; using System.Text; using Formats.OpenBve; using LibRender2.Trains; @@ -9,7 +7,6 @@ using OpenBveApi.Interface; using OpenBveApi.Math; using OpenBveApi.Routes; -using OpenBveApi.Trains; using TrainManager.BrakeSystems; using TrainManager.Car; using TrainManager.Handles; @@ -29,63 +26,6 @@ internal TrainDatParser(Plugin plugin) Plugin = plugin; } - /// - /// Read the file of the specified train.dat - /// - /// The file path of the specified train.dat - /// The text encoding to use - /// The array read from the specified train.dat - private static string[] ReadFile(string fileName, Encoding encoding) - { - //Create the array using the default compatibility train.dat - string[] lines = { "BVE2000000", "#CAR", "1", "1", "1", "0", "1", "1" }; - - // load file - try - { - lines = System.IO.File.ReadAllLines(fileName, encoding); - } - catch - { - //ignore and load default - } - if (lines.Length == 1 && encoding.Equals(Encoding.Unicode)) - { - /* - * Probably not unicode after all - * Stuff edited with BVE2 / BVE4 tools should either be ASCII or SHIFT_JIS - * both of which should read OK with ASCII for our purposes - */ - encoding = Encoding.ASCII; - lines = System.IO.File.ReadAllLines(fileName, encoding); - } - else if (lines.Length == 0) - { - //Catch zero-length train.dat files - throw new Exception("The train.dat file " + fileName + " is of zero length."); - } - - for (int i = 0; i < lines.Length; i++) - { - int j = lines[i].IndexOf(';'); - if (j >= 0) - { - lines[i] = lines[i].Substring(0, j).Trim(); - } - else - { - lines[i] = lines[i].Trim(); - } - if (lines[i].EndsWith(",")) - { - //File edited with MSExcel may have additional commas at the end of a line - lines[i] = lines[i].TrimEnd(','); - } - } - - return lines; - } - /// /// Checks whether the parser can load the specified file. /// @@ -94,25 +34,29 @@ private static string[] ReadFile(string fileName, Encoding encoding) internal bool CanLoad(string fileName) { Encoding encoding = TextEncoding.GetSystemEncodingFromFile(fileName); - - string[] lines; try { - lines = ReadFile(fileName, encoding); + string[] lines = File.ReadAllLines(fileName, encoding); + for (int i = 0; i < lines.Length; i++) + { + if (!string.IsNullOrEmpty(lines[i]) && !lines[i].StartsWith(";")) + { + TrainDatFormats format = TrainDatFile.ParseFormat(lines[i], out _); + if (format == TrainDatFormats.MissingHeader) + { + // Some NYCTA stuff seems to be missing the version header from their train.dat files + // absolute mess.... + Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "The train.dat file " + fileName + " has a missing version header."); + } + return format != TrainDatFormats.Unsupported; + } + } } catch { return false; } - - TrainDatFormats format = TrainDatFile.ParseFormat(lines[0], out _); - if (format == TrainDatFormats.MissingHeader) - { - // Some NYCTA stuff seems to be missing the version header from their train.dat files - // absolute mess.... - Plugin.CurrentHost.AddMessage(MessageType.Warning, false, "The train.dat file " + fileName + " has a missing version header."); - } - return format != TrainDatFormats.Unsupported; + return false; } /// Parses a BVE2 / BVE4 / openBVE train.dat file @@ -568,6 +512,11 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) { case TrainDatSection.Motor_P2: case TrainDatSection.Motor_B1: case TrainDatSection.Motor_B2: + if (subBlock.RemainingDataValues == 0) + { + // duplicated section header + break; + } int msi = 0; switch (subBlock.Key) { From 42dace8b5c16b225443b24b83a0df4ae38a3146a Mon Sep 17 00:00:00 2001 From: Christopher Lees Date: Wed, 22 Jul 2026 12:06:04 +0100 Subject: [PATCH 4/4] Change: Improve UKDT AI wheelslip response --- .../TrainManager/SafetySystems/Plugin/AI/PluginAI.UKDt.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/TrainManager/SafetySystems/Plugin/AI/PluginAI.UKDt.cs b/source/TrainManager/SafetySystems/Plugin/AI/PluginAI.UKDt.cs index 0f4a2d0991..1bfca4910d 100644 --- a/source/TrainManager/SafetySystems/Plugin/AI/PluginAI.UKDt.cs +++ b/source/TrainManager/SafetySystems/Plugin/AI/PluginAI.UKDt.cs @@ -116,10 +116,10 @@ internal override void Perform(AIData data) currentStep = 5; } - if (Plugin.Panel[51] == 1) + if (Plugin.Panel[51] == 1 || Plugin.Panel[55] == 1) { /* - * Over current has tripped + * Over current has tripped, or wheelslip light is lit * Let's back off to N and drop the max notch by 1 * * Repeat until we move off properly @@ -133,6 +133,7 @@ internal override void Perform(AIData data) data.Handles.PowerNotch = 0; data.Response = AIResponse.Long; overCurrentTrip = true; + nextPluginAction = TrainManagerBase.currentHost.InGameTime + 20; return; } data.Response = AIResponse.Long; @@ -142,9 +143,10 @@ internal override void Perform(AIData data) overCurrentTrip = false; if (overCurrentSpeed != double.MaxValue) { - if (Plugin.Train.CurrentSpeed < overCurrentSpeed + 10) + if (Plugin.Train.CurrentSpeed > 5 && Plugin.Train.CurrentSpeed < overCurrentSpeed + 10) { data.Handles.PowerNotch = overCurrentNotch; + data.Response = AIResponse.Short; } else {