1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using LibRender2.Trains;
6 using OpenBveApi;
7 using OpenBveApi.Interface;
8 using OpenBveApi.Math;
9 using OpenBveApi.Routes;
10 using OpenBveApi.Trains;
11 using TrainManager.BrakeSystems;
12 using TrainManager.Car;
13 using TrainManager.Handles;
14 using TrainManager.Motor;
15 using TrainManager.Power;
16 using TrainManager.SafetySystems;
17 using TrainManager.Trains;
18 
19 namespace Train.OpenBve
20 {
21 	internal class TrainDatParser
22 	{
23 		internal readonly Plugin Plugin;
24 
TrainDatParser(Plugin plugin)25 		internal TrainDatParser(Plugin plugin)
26 		{
27 			Plugin = plugin;
28 		}
29 
30 		/// <summary>
31 		/// Read the file of the specified train.dat
32 		/// </summary>
33 		/// <param name="fileName">The file path of the specified train.dat</param>
34 		/// <param name="encoding">The text encoding to use</param>
35 		/// <returns>The array read from the specified train.dat</returns>
ReadFile(string fileName, Encoding encoding)36 		private static string[] ReadFile(string fileName, Encoding encoding)
37 		{
38 			//Create the array using the default compatibility train.dat
39 			string[] lines = { "BVE2000000", "#CAR", "1", "1", "1", "0", "1", "1" };
40 
41 			// load file
42 			try
43 			{
44 				lines = System.IO.File.ReadAllLines(fileName, encoding);
45 			}
46 			catch
47 			{
48 				//ignore and load default
49 			}
50 			if (lines.Length == 1 && encoding.Equals(Encoding.Unicode))
51 			{
52 				/*
53 				 * Probably not unicode after all
54 				 * Stuff edited with BVE2 / BVE4 tools should either be ASCII or SHIFT_JIS
55 				 * both of which should read OK with ASCII for our purposes
56 				 */
57 				encoding = Encoding.ASCII;
58 				lines = System.IO.File.ReadAllLines(fileName, encoding);
59 			}
60 			else if (lines.Length == 0)
61 			{
62 				//Catch zero-length train.dat files
63 				throw new Exception("The train.dat file " + fileName + " is of zero length.");
64 			}
65 
66 			for (int i = 0; i < lines.Length; i++)
67 			{
68 				int j = lines[i].IndexOf(';');
69 				if (j >= 0)
70 				{
71 					lines[i] = lines[i].Substring(0, j).Trim();
72 				}
73 				else
74 				{
75 					lines[i] = lines[i].Trim();
76 				}
77 				if (lines[i].EndsWith(","))
78 				{
79 					//File edited with MSExcel may have additional commas at the end of a line
80 					lines[i] = lines[i].TrimEnd(',');
81 				}
82 			}
83 
84 			return lines;
85 		}
86 
87 		/// <summary>Parse the format of the specified train.dat</summary>
88 		/// <param name="lines">The array of the specified train.dat</param>
89 		/// <param name="version">The version of the specified OpenBVE train.dat</param>
ParseFormat(IReadOnlyList<string> lines, out int version)90 		private static TrainDatFormats ParseFormat(IReadOnlyList<string> lines, out int version)
91 		{
92 			version = -1;
93 			for (int i = 0; i < lines.Count; i++)
94 			{
95 				if (lines[i].Length <= 0)
96 				{
97 					continue;
98 				}
99 
100 				string t = lines[i].ToLowerInvariant();
101 				switch (t)
102 				{
103 					case "bve1200000":
104 						return TrainDatFormats.BVE1200000;
105 					case "bve1210000":
106 						return TrainDatFormats.BVE1210000;
107 					case "bve1220000":
108 						return TrainDatFormats.BVE1220000;
109 					case "bve2000000":
110 						return TrainDatFormats.BVE2000000;
111 					case "bve2060000":
112 						return TrainDatFormats.BVE2060000;
113 					case "openbve":
114 						version = 0;
115 						return TrainDatFormats.openBVE;
116 					default:
117 						if (t.ToLowerInvariant().StartsWith("openbve"))
118 						{
119 							string tt = t.Substring(7, t.Length - 7).Trim();
120 							if (!NumberFormats.TryParseIntVb6(tt, out version))
121 							{
122 								version = -1;
123 							}
124 							return TrainDatFormats.openBVE;
125 						}
126 						else if (t.ToLowerInvariant().StartsWith("bve"))
127 						{
128 							return TrainDatFormats.UnknownBVE;
129 						}
130 						else
131 						{
132 							return TrainDatFormats.Unsupported;
133 						}
134 				}
135 			}
136 			return TrainDatFormats.Unsupported;
137 		}
138 
139 		/// <summary>
140 		/// Checks whether the parser can load the specified file.
141 		/// </summary>
142 		/// <param name="fileName">The file path of the specified train.dat</param>
143 		/// <returns>Whether the parser can load the specified file.</returns>
CanLoad(string fileName)144 		internal bool CanLoad(string fileName)
145 		{
146 			Encoding encoding = TextEncoding.GetSystemEncodingFromFile(fileName);
147 
148 			string[] lines;
149 			try
150 			{
151 				lines = ReadFile(fileName, encoding);
152 			}
153 			catch
154 			{
155 				return false;
156 			}
157 
158 			TrainDatFormats format = ParseFormat(lines, out _);
159 
160 			return format != TrainDatFormats.Unsupported;
161 		}
162 
163 		/// <summary>Parses a BVE2 / BVE4 / openBVE train.dat file</summary>
164 		/// <param name="FileName">The train.dat file to parse</param>
165 		/// <param name="Encoding">The text encoding to use</param>
166 		/// <param name="Train">The train</param>
Parse(string FileName, Encoding Encoding, TrainBase Train)167 		internal void Parse(string FileName, Encoding Encoding, TrainBase Train) {
168 			System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture;
169 
170 			string[] Lines = ReadFile(FileName, Encoding);
171 
172 			// Check version
173 			const int currentVersion = 17250;
174 			TrainDatFormats currentFormat = ParseFormat(Lines, out int myVersion);
175 			string versionString = Lines.FirstOrDefault(x => x.Length > 0) ?? Lines[0];
176 			switch (currentFormat)
177 			{
178 				case TrainDatFormats.openBVE when myVersion == -1:
179 					Plugin.currentHost.AddMessage(MessageType.Error, false, "The train.dat version " + versionString + " is invalid in " + FileName);
180 					break;
181 				case TrainDatFormats.openBVE:
182 				{
183 					if (myVersion > currentVersion)
184 					{
185 						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.");
186 					}
187 					break;
188 				}
189 				case TrainDatFormats.Unsupported:
190 					Plugin.currentHost.AddMessage(MessageType.Error, false, "The train.dat format " + versionString + " is not supported in " + FileName);
191 					break;
192 				case TrainDatFormats.UnknownBVE:
193 					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.");
194 					break;
195 			}
196 
197 			// initialize
198 			double BrakeCylinderServiceMaximumPressure = 440000.0;
199 			double BrakeCylinderEmergencyMaximumPressure = 440000.0;
200 			double MainReservoirMinimumPressure = 690000.0;
201 			double MainReservoirMaximumPressure = 780000.0;
202 			double BrakePipePressure = 0.0;
203 			BrakeSystemType trainBrakeType = BrakeSystemType.ElectromagneticStraightAirBrake;
204 			BrakeSystemType locomotiveBrakeType = BrakeSystemType.ElectromagneticStraightAirBrake;
205 			EletropneumaticBrakeType ElectropneumaticType = EletropneumaticBrakeType.None;
206 			double BrakeControlSpeed = 0.0;
207 			double BrakeDeceleration = 0.277777777777778;
208 			double JerkPowerUp = 10.0;
209 			double JerkPowerDown = 10.0;
210 			double JerkBrakeUp = 10.0;
211 			double JerkBrakeDown = 10.0;
212 			double BrakeCylinderUp = 300000.0;
213 			double BrakeCylinderDown = 200000.0;
214 			double CoefficientOfStaticFriction = 0.35;
215 			double CoefficientOfRollingResistance = 0.0025;
216 			double AerodynamicDragCoefficient = 1.1;
217 			BveAccelerationCurve[] AccelerationCurves = { };
218 			Vector3 Driver = new Vector3();
219 			int DriverCar = 0;
220 			double MotorCarMass = 1.0, TrailerCarMass = 1.0;
221 			int MotorCars = 0, TrailerCars = 0;
222 			double CarLength = 20.0;
223 			double CarWidth = 2.6;
224 			double CarHeight = 3.6;
225 			double CenterOfGravityHeight = 1.6;
226 			double CarExposedFrontalArea = 0.6 * CarWidth * CarHeight;
227 			double CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight;
228 			bool FrontCarIsMotorCar = true;
229 			double DoorWidth = 1000.0;
230 			double DoorTolerance = 0.0;
231 			ReadhesionDeviceType ReAdhesionDevice = ReadhesionDeviceType.TypeA;
232 			PassAlarmType passAlarm = PassAlarmType.None;
233 			Train.Handles.EmergencyBrake = new EmergencyHandle(Train);
234 			Train.Handles.HasLocoBrake = false;
235 			double[] powerDelayUp = { }, powerDelayDown = { }, brakeDelayUp = { }, brakeDelayDown = { }, locoBrakeDelayUp = { }, locoBrakeDelayDown = { };
236 			int powerNotches = 0, brakeNotches = 0, locoBrakeNotches = 0, powerReduceSteps = -1, locoBrakeType = 0, driverPowerNotches = 0, driverBrakeNotches = 0;
237 			BVEMotorSoundTable[] Tables = new BVEMotorSoundTable[4];
238 			for (int i = 0; i < 4; i++) {
239 				Tables[i].Entries = new BVEMotorSoundTableEntry[16];
240 				for (int j = 0; j < 16; j++) {
241 					Tables[i].Entries[j].SoundIndex = -1;
242 					Tables[i].Entries[j].Pitch = 1.0f;
243 					Tables[i].Entries[j].Gain = 1.0f;
244 				}
245 			}
246 			// parse configuration
247 			double invfac = Lines.Length == 0 ? 0.1 : 0.1 / Lines.Length;
248 			for (int i = 0; i < Lines.Length; i++) {
249 				Plugin.CurrentProgress = Plugin.LastProgress + invfac * i;
250 				if ((i & 7) == 0) {
251 					System.Threading.Thread.Sleep(1);
252 					if (Plugin.Cancel) return;
253 				}
254 				int n = 0;
255 				switch (Lines[i].ToLowerInvariant()) {
256 					case "#acceleration":
257 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
258 							Array.Resize(ref AccelerationCurves, n + 1);
259 							AccelerationCurves[n] = new BveAccelerationCurve();
260 							string t = Lines[i] + ",";
261 							int m = 0;
262 							while (true) {
263 								int j = t.IndexOf(',');
264 								if (j == -1) break;
265 								string s = t.Substring(0, j).Trim();
266 								t = t.Substring(j + 1);
267 								if (NumberFormats.TryParseDoubleVb6(s, out var a)) {
268 									switch (m) {
269 										case 0:
270 											if (a <= 0.0) {
271 												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);
272 											} else {
273 												AccelerationCurves[n].StageZeroAcceleration = a * 0.277777777777778;
274 											} break;
275 										case 1:
276 											if (a <= 0.0) {
277 												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);
278 											} else {
279 												AccelerationCurves[n].StageOneAcceleration = a * 0.277777777777778;
280 											} break;
281 										case 2:
282 											if (a <= 0.0) {
283 												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);
284 											} else {
285 												AccelerationCurves[n].StageOneSpeed = a * 0.277777777777778;
286 											} break;
287 										case 3:
288 											if (a <= 0.0) {
289 												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);
290 											} else {
291 												AccelerationCurves[n].StageTwoSpeed = a * 0.277777777777778;
292 												if (AccelerationCurves[n].StageTwoSpeed < AccelerationCurves[n].StageOneSpeed) {
293 													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);
294 													AccelerationCurves[n].StageTwoSpeed = AccelerationCurves[n].StageOneSpeed;
295 												}
296 											} break;
297 										case 4:
298 											{
299 												if (currentFormat == TrainDatFormats.BVE1200000 || currentFormat == TrainDatFormats.BVE1210000 || currentFormat == TrainDatFormats.BVE1220000) {
300 													if (a <= 0.0) {
301 														AccelerationCurves[n].StageTwoExponent = 1.0;
302 														Plugin.currentHost.AddMessage(MessageType.Error, false, "e in section #ACCELERATION is expected to be positive at line " + (i + 1).ToString(Culture) + " in file " + FileName);
303 													} else {
304 														const double c = 4.439346232277577;
305 														AccelerationCurves[n].StageTwoExponent = 1.0 - Math.Log(a) * AccelerationCurves[n].StageTwoSpeed * c;
306 														if (AccelerationCurves[n].StageTwoExponent <= 0.0) {
307 															AccelerationCurves[n].StageTwoExponent = 1.0;
308 														} else if (AccelerationCurves[n].StageTwoExponent > 4.0) {
309 															AccelerationCurves[n].StageTwoExponent = 4.0;
310 														}
311 													}
312 												} else {
313 													AccelerationCurves[n].StageTwoExponent = a;
314 													if (AccelerationCurves[n].StageTwoExponent <= 0.0) {
315 														AccelerationCurves[n].StageTwoExponent = 1.0;
316 													}
317 												}
318 											} break;
319 									}
320 								} m++;
321 							} i++; n++;
322 						} i--; break;
323 					case "#performance":
324 					case "#deceleration":
325 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
326 							if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) {
327 								switch (n) {
328 									case 0:
329 										if (a < 0.0) {
330 											Plugin.currentHost.AddMessage(MessageType.Error, false, "BrakeDeceleration is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName);
331 										} else {
332 											BrakeDeceleration = a * 0.277777777777778;
333 										} break;
334 									case 1:
335 										if (a < 0.0) {
336 											Plugin.currentHost.AddMessage(MessageType.Error, false, "CoefficientOfStaticFriction is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName);
337 										} else {
338 											if (Plugin.CurrentOptions.EnableBveTsHacks && (a > 0.1 || a < 1.0))
339 											{
340 												break;
341 											}
342 											CoefficientOfStaticFriction = a;
343 										} break;
344 									case 3:
345 										if (a < 0.0) {
346 											Plugin.currentHost.AddMessage(MessageType.Error, false, "CoefficientOfRollingResistance is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName);
347 										} else {
348 											CoefficientOfRollingResistance = a;
349 										} break;
350 									case 4:
351 										if (a < 0.0) {
352 											Plugin.currentHost.AddMessage(MessageType.Error, false, "AerodynamicDragCoefficient is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName);
353 										} else {
354 											AerodynamicDragCoefficient = a;
355 										} break;
356 								}
357 							} i++; n++;
358 						} i--; break;
359 					case "#delay":
360 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
361 							if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) {
362 								switch (n)
363 								{
364 									case 0:
365 										if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534)
366 										{
367 											powerDelayUp = Lines[i].Split( ',').Select(x => Double.Parse(x, Culture)).ToArray();
368 										}
369 										else
370 										{
371 											if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60)
372 											{
373 												break;
374 											}
375 											powerDelayUp = new[] {a};
376 										}
377 										break;
378 									case 1:
379 										if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534)
380 										{
381 											powerDelayDown = Lines[i].Split(',').Select(x => Double.Parse(x, Culture)).ToArray();
382 										}
383 										else
384 										{
385 											if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60)
386 											{
387 												break;
388 											}
389 											powerDelayDown = new[] {a};
390 										}
391 										break;
392 									case 2:
393 										if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534)
394 										{
395 											brakeDelayUp = Lines[i].Split(',').Select(x => Double.Parse(x, Culture)).ToArray();
396 										}
397 										else
398 										{
399 											if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60)
400 											{
401 												break;
402 											}
403 											brakeDelayUp = new[] {a};
404 										}
405 										break;
406 									case 3:
407 										if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534)
408 										{
409 											brakeDelayDown = Lines[i].Split(',').Select(x => Double.Parse(x, Culture)).ToArray();
410 										}
411 										else
412 										{
413 											if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60)
414 											{
415 												break;
416 											}
417 											brakeDelayDown = new[] {a};
418 										}
419 										break;
420 									case 4:
421 										if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534)
422 										{
423 											locoBrakeDelayUp = Lines[i].Split(',').Select(x => Double.Parse(x, Culture)).ToArray();
424 										}
425 										else
426 										{
427 											if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60)
428 											{
429 												break;
430 											}
431 											locoBrakeDelayUp = new[] {a};
432 										}
433 										break;
434 									case 5:
435 										if (currentFormat == TrainDatFormats.openBVE && myVersion >= 1534)
436 										{
437 											locoBrakeDelayDown = Lines[i].Split(',').Select(x => Double.Parse(x, Culture)).ToArray();
438 										}
439 										else
440 										{
441 											if (Plugin.CurrentOptions.EnableBveTsHacks && a > 60)
442 											{
443 												break;
444 											}
445 											locoBrakeDelayDown = new[] {a};
446 										}
447 										break;
448 								}
449 							} i++; n++;
450 						} i--; break;
451 					case "#move":
452 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
453 							if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) {
454 								switch (n) {
455 									case 0:
456 										if (a != 0)
457 										{
458 											JerkPowerUp = 0.01 * a;
459 										}
460 										else
461 										{
462 											Plugin.currentHost.AddMessage(MessageType.Error, false, "JerkPowerUp is expected to be non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
463 										}
464 										break;
465 									case 1:
466 										if (a != 0)
467 										{
468 											JerkPowerDown = 0.01 * a;
469 										}
470 										else
471 										{
472 											Plugin.currentHost.AddMessage(MessageType.Error, false, "JerkPowerDown is expected to be non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
473 										}
474 										break;
475 									case 2:
476 										if (a != 0)
477 										{
478 											JerkBrakeUp = 0.01 * a;
479 										}
480 										else
481 										{
482 											Plugin.currentHost.AddMessage(MessageType.Error, false, "JerkBrakeUp is expected to be non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
483 										}
484 										break;
485 									case 3:
486 										if (a != 0)
487 										{
488 											JerkBrakeDown = 0.01 * a;
489 										}
490 										else
491 										{
492 											Plugin.currentHost.AddMessage(MessageType.Error, false, "JerkBrakeDown is expected to be non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
493 										}
494 										break;
495 									case 4:
496 										if (a >= 0)
497 										{
498 											BrakeCylinderUp = 1000.0 * a;
499 										}
500 										else
501 										{
502 											Plugin.currentHost.AddMessage(MessageType.Error, false, "BrakeCylinderUp is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
503 										}
504 										break;
505 									case 5:
506 										if (a >= 0)
507 										{
508 											BrakeCylinderDown = 1000.0 * a;
509 										}
510 										else
511 										{
512 											Plugin.currentHost.AddMessage(MessageType.Error, false, "BrakeCylinderDown is expected to be greater than zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
513 										}
514 										break;
515 								}
516 							} i++; n++;
517 						} i--; break;
518 					case "#brake":
519 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
520 							if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a))
521 							{
522 								int b;
523 								switch (n)
524 								{
525 									case 0:
526 										b = (int) Math.Round(a);
527 										if (b >= 0 & b <= 2)
528 										{
529 											trainBrakeType = (BrakeSystemType) b;
530 										}
531 										else
532 										{
533 											Plugin.currentHost.AddMessage(MessageType.Error, false, "The setting for BrakeType is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
534 											trainBrakeType = BrakeSystemType.ElectromagneticStraightAirBrake;
535 										}
536 										break;
537 									case 1:
538 										b = (int) Math.Round(a);
539 										if (b >= 0 & b <= 2)
540 										{
541 											ElectropneumaticType = (EletropneumaticBrakeType) b;
542 										}
543 										else
544 										{
545 											Plugin.currentHost.AddMessage(MessageType.Error, false, "The setting for ElectropneumaticType is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
546 											ElectropneumaticType = EletropneumaticBrakeType.None;
547 										}
548 										break;
549 									case 2:
550 										if (a < 0)
551 										{
552 											Plugin.currentHost.AddMessage(MessageType.Error, false, "BrakeControlSpeed must be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName);
553 											break;
554 										}
555 										if (a != 0 && trainBrakeType == BrakeSystemType.AutomaticAirBrake)
556 										{
557 											Plugin.currentHost.AddMessage(MessageType.Warning, false, "BrakeControlSpeed will be ignored due to the current brake setup at line " + (i + 1).ToString(Culture) + " in " + FileName);
558 											break;
559 										}
560 										BrakeControlSpeed = a * 0.277777777777778; //Convert to m/s
561 										break;
562 									case 3:
563 										b = (int) Math.Round(a);
564 										switch (b)
565 										{
566 											case 0:
567 												//Not fitted
568 												break;
569 											case 1:
570 												//Notched air brake
571 												Train.Handles.HasLocoBrake = true;
572 												locomotiveBrakeType = BrakeSystemType.ElectromagneticStraightAirBrake;
573 												break;
574 											case 2:
575 												//Automatic air brake
576 												Train.Handles.HasLocoBrake = true;
577 												locomotiveBrakeType = BrakeSystemType.AutomaticAirBrake;
578 												break;
579 										}
580 										break;
581 								}
582 							} i++; n++;
583 						} i--; break;
584 					case "#pressure":
585 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
586 							if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) {
587 								switch (n) {
588 									case 0:
589 										if (a <= 0.0) {
590 											Plugin.currentHost.AddMessage(MessageType.Error, false, "BrakeCylinderServiceMaximumPressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
591 										} else {
592 											BrakeCylinderServiceMaximumPressure = a * 1000.0;
593 										} break;
594 									case 1:
595 										if (a <= 0.0) {
596 											Plugin.currentHost.AddMessage(MessageType.Error, false, "BrakeCylinderEmergencyMaximumPressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
597 										} else {
598 											BrakeCylinderEmergencyMaximumPressure = a * 1000.0;
599 										} break;
600 									case 2:
601 										if (a <= 0.0) {
602 											Plugin.currentHost.AddMessage(MessageType.Error, false, "MainReservoirMinimumPressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
603 										} else {
604 											MainReservoirMinimumPressure = a * 1000.0;
605 										} break;
606 									case 3:
607 										if (a <= 0.0) {
608 											Plugin.currentHost.AddMessage(MessageType.Error, false, "MainReservoirMaximumPressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
609 										} else {
610 											MainReservoirMaximumPressure = a * 1000.0;
611 										} break;
612 									case 4:
613 										if (a <= 0.0) {
614 											Plugin.currentHost.AddMessage(MessageType.Error, false, "BrakePipePressure is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
615 										} else {
616 											BrakePipePressure = a * 1000.0;
617 										} break;
618 								}
619 							} i++; n++;
620 						} i--; break;
621 					case "#handle":
622 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
623 							if (NumberFormats.TryParseIntVb6(Lines[i], out var a)) {
624 								switch (n) {
625 									case 0:
626 										switch (a)
627 										{
628 											case 0:
629 												Train.Handles.HandleType = HandleType.TwinHandle;
630 												break;
631 											case 1:
632 												Train.Handles.HandleType = HandleType.SingleHandle;
633 												break;
634 											case 2:
635 												Train.Handles.HandleType = HandleType.InterlockedTwinHandle;
636 												break;
637 											case 3:
638 												Train.Handles.HandleType = HandleType.InterlockedReverserHandle;
639 												break;
640 											default:
641 												Train.Handles.HandleType = HandleType.TwinHandle;
642 												break;
643 										}
644 										break;
645 									case 1:
646 										if (a > 0)
647 										{
648 											powerNotches = a;
649 										}
650 										else
651 										{
652 											powerNotches = 8;
653 											Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfPowerNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
654 										}
655 										break;
656 									case 2:
657 										if (a > 0)
658 										{
659 											brakeNotches = a;
660 										}
661 										else
662 										{
663 											brakeNotches = 8;
664 											if (trainBrakeType != BrakeSystemType.AutomaticAirBrake)
665 											{
666 												/*
667 												 * NumberOfBrakeNotches is ignored when using the auto-air brake
668 												 * Whilst this value is invalid, it doesn't actually get used so get
669 												 * rid of the pointless error message it generates
670 												 */
671 												Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfBrakeNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
672 											}
673 										}
674 										break;
675 									case 3:
676 										powerReduceSteps = a;
677 										break;
678 									case 4:
679 										if (a < 0 || a > 3)
680 										{
681 											Plugin.currentHost.AddMessage(MessageType.Error, false, "EbHandleBehaviour is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
682 											break;
683 										}
684 										Train.Handles.EmergencyBrake.OtherHandlesBehaviour = (EbHandleBehaviour) a;
685 										break;
686 									case 5:
687 										if (a >= 0)
688 										{
689 
690 											locoBrakeNotches = a;
691 										}
692 										else
693 										{
694 											locoBrakeNotches = 8;
695 											Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfLocoBrakeNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
696 										}
697 
698 										break;
699 									case 6:
700 										locoBrakeType = a;
701 										break;
702 									case 7:
703 										if (currentFormat == TrainDatFormats.openBVE && myVersion >= 15311)
704 										{
705 											if (a > 0)
706 											{
707 												driverPowerNotches = a;
708 											}
709 											else
710 											{
711 												driverPowerNotches = 8;
712 												Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfDriverPowerNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
713 											}
714 										}
715 										break;
716 									case 8:
717 										if (currentFormat == TrainDatFormats.openBVE && myVersion >= 15311)
718 										{
719 											if (a > 0)
720 											{
721 												driverBrakeNotches = a;
722 											}
723 											else
724 											{
725 												driverBrakeNotches = 8;
726 												Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfDriverBrakeNotches is expected to be positive and non-zero at line " + (i + 1).ToString(Culture) + " in " + FileName);
727 											}
728 										}
729 										break;
730 								}
731 							} i++; n++;
732 						} i--; break;
733 					case "#cockpit":
734 					case "#cab":
735 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
736 							if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) {
737 								switch (n) {
738 										case 0: Driver.X = 0.001 * a; break;
739 										case 1: Driver.Y = 0.001 * a; break;
740 										case 2: Driver.Z = 0.001 * a; break;
741 										case 3: DriverCar = (int)Math.Round(a); break;
742 								}
743 							} i++; n++;
744 						} i--; break;
745 					case "#car":
746 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
747 							if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) {
748 								switch (n) {
749 									case 0:
750 										if (a <= 0.0) {
751 											Plugin.currentHost.AddMessage(MessageType.Error, false, "MotorCarMass is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
752 										} else {
753 											MotorCarMass = a * 1000.0;
754 										} break;
755 									case 1:
756 										if (a <= 0.0) {
757 											Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfMotorCars is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
758 										} else {
759 											MotorCars = (int)Math.Round(a);
760 										} break;
761 									case 2: TrailerCarMass = a * 1000.0; break;
762 									case 3:
763 										if (a < 0.0) {
764 											Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfTrailerCars is expected to be non-negative at line " + (i + 1).ToString(Culture) + " in " + FileName);
765 										} else {
766 											TrailerCars = (int)Math.Round(a);
767 										} break;
768 									case 4:
769 										if (a <= 0.0) {
770 											Plugin.currentHost.AddMessage(MessageType.Error, false, "LengthOfACar is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
771 										} else {
772 											CarLength = a;
773 										} break;
774 									case 5: FrontCarIsMotorCar = a == 1.0; break;
775 									case 6:
776 										if (a <= 0.0) {
777 											Plugin.currentHost.AddMessage(MessageType.Error, false, "WidthOfACar is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
778 										} else {
779 											CarWidth = a;
780 											CarExposedFrontalArea = 0.65 * CarWidth * CarHeight;
781 											CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight;
782 										} break;
783 									case 7:
784 										if (a <= 0.0) {
785 											Plugin.currentHost.AddMessage(MessageType.Error, false, "HeightOfACar is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
786 										} else {
787 											CarHeight = a;
788 											CarExposedFrontalArea = 0.65 * CarWidth * CarHeight;
789 											CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight;
790 										} break;
791 									case 8: CenterOfGravityHeight = a; break;
792 									case 9:
793 										if (a <= 0.0) {
794 											Plugin.currentHost.AddMessage(MessageType.Error, false, "ExposedFrontalArea is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
795 										} else {
796 											CarExposedFrontalArea = a;
797 											CarUnexposedFrontalArea = 0.2 * CarWidth * CarHeight;
798 										} break;
799 									case 10:
800 										if (a <= 0.0) {
801 											Plugin.currentHost.AddMessage(MessageType.Error, false, "UnexposedFrontalArea is expected to be positive at line " + (i + 1).ToString(Culture) + " in " + FileName);
802 										} else {
803 											CarUnexposedFrontalArea = a;
804 										} break;
805 								}
806 							} i++; n++;
807 						} i--; break;
808 					case "#device":
809 						i++; while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
810 							if (NumberFormats.TryParseDoubleVb6(Lines[i], out var a)) {
811 								switch (n) {
812 									case 0:
813 										if (a == 0.0) {
814 											Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsSn;
815 										} else if (a == 1.0) {
816 											Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsSn;
817 											Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.AtsP;
818 										}
819 										break;
820 									case 1:
821 										if (a == 1.0 | a == 2.0) {
822 											Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.Atc;
823 										}
824 										break;
825 									case 2:
826 										if (a == 1.0) {
827 											Train.Specs.DefaultSafetySystems |= DefaultSafetySystems.Eb;
828 										}
829 										break;
830 									case 3:
831 										Train.Specs.HasConstSpeed = a == 1.0; break;
832 									case 4:
833 										Train.Handles.HasHoldBrake = a == 1.0; break;
834 									case 5:
835 										int dt = (int) Math.Round(a);
836 										if (dt < 4 && dt > -1)
837 										{
838 											ReAdhesionDevice = (ReadhesionDeviceType)dt;
839 										}
840 										else
841 										{
842 											ReAdhesionDevice = ReadhesionDeviceType.NotFitted;
843 											Plugin.currentHost.AddMessage(MessageType.Error, false, "ReAdhesionDeviceType is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
844 										}
845 										break;
846 									case 7:
847 										{
848 											int b = (int)Math.Round(a);
849 											if (b >= 0 & b <= 2) {
850 												passAlarm = (PassAlarmType)b;
851 											} else {
852 												Plugin.currentHost.AddMessage(MessageType.Error, false, "PassAlarm is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
853 											} break;
854 										}
855 									case 8:
856 										{
857 											int b = (int)Math.Round(a);
858 											if (b >= 0 & b <= 2) {
859 												Train.Specs.DoorOpenMode = (DoorMode)b;
860 											} else {
861 												Plugin.currentHost.AddMessage(MessageType.Error, false, "DoorOpenMode is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
862 											} break;
863 										}
864 									case 9:
865 										{
866 											int b = (int)Math.Round(a);
867 											if (b >= 0 & b <= 2) {
868 												Train.Specs.DoorCloseMode = (DoorMode)b;
869 											} else {
870 												Plugin.currentHost.AddMessage(MessageType.Error, false, "DoorCloseMode is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
871 											} break;
872 										}
873 									case 10:
874 										{
875 											if (a >= 0.0) {
876 												DoorWidth = a;
877 											} else {
878 												Plugin.currentHost.AddMessage(MessageType.Error, false, "DoorWidth is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
879 											} break;
880 										}
881 									case 11:
882 										{
883 											if (a >= 0.0) {
884 												DoorTolerance = a;
885 											} else {
886 												Plugin.currentHost.AddMessage(MessageType.Error, false, "DoorMaxTolerance is invalid at line " + (i + 1).ToString(Culture) + " in " + FileName);
887 											} break;
888 										}
889 								}
890 							} i++; n++;
891 						} i--; break;
892 					case "#motor_p1":
893 					case "#motor_p2":
894 					case "#motor_b1":
895 					case "#motor_b2":
896 						{
897 							int msi = 0;
898 							switch (Lines[i].ToLowerInvariant()) {
899 									case "#motor_p1": msi = BVEMotorSound.MotorP1; break;
900 									case "#motor_p2": msi = BVEMotorSound.MotorP2; break;
901 									case "#motor_b1": msi = BVEMotorSound.MotorB1; break;
902 									case "#motor_b2": msi = BVEMotorSound.MotorB2; break;
903 							} i++;
904 							while (i < Lines.Length && !Lines[i].StartsWith("#", StringComparison.Ordinal)) {
905 								int u = Tables[msi].Entries.Length;
906 								if (n >= u) {
907 									Array.Resize(ref Tables[msi].Entries, 2 * u);
908 									for (int j = u; j < 2 * u; j++) {
909 										Tables[msi].Entries[j].SoundIndex = -1;
910 										Tables[msi].Entries[j].Pitch = 1.0f;
911 										Tables[msi].Entries[j].Gain = 1.0f;
912 									}
913 								}
914 								string t = Lines[i] + ","; int m = 0;
915 								while (true) {
916 									int j = t.IndexOf(',');
917 									if (j == -1) break;
918 									string s = t.Substring(0, j).Trim();
919 									t = t.Substring(j + 1);
920 									if (NumberFormats.TryParseDoubleVb6(s, out var a)) {
921 										switch (m) {
922 											case 0:
923 												Tables[msi].Entries[n].SoundIndex = (int)Math.Round(a);
924 												break;
925 											case 1:
926 												if (a < 0.0) a = 0.0;
927 												Tables[msi].Entries[n].Pitch = (float)(0.01 * a);
928 												break;
929 											case 2:
930 												if (a < 0.0) a = 0.0;
931 												Tables[msi].Entries[n].Gain = (float)Math.Pow((0.0078125 * a), 0.25);
932 												break;
933 										}
934 									} m++;
935 								} i++; n++;
936 							}
937 
938 							if (n != 0)
939 							{
940 								/*
941 								 * Handle duplicated section header:
942 								 * If no entries, don't resize
943 								 */
944 								Array.Resize(ref Tables[msi].Entries, n);
945 							}
946 							i--;
947 						} break;
948 				}
949 			}
950 
951 			if (TrailerCars > 0 & TrailerCarMass <= 0.0) {
952 				if (currentFormat < TrainDatFormats.openBVE && Plugin.CurrentOptions.EnableBveTsHacks && TrailerCars == 1 && TrailerCarMass == 0)
953 				{
954 					/*
955 					 * Early BVE train editor versions appear to have been unable to create a train with no trailer cars,
956 					 * hence the use of a single zero-mass variety as a workaround e.g. EvA6
957 					 */
958 					TrailerCars = 0;
959 				}
960 				else
961 				{
962 					Plugin.currentHost.AddMessage(MessageType.Error, false, "TrailerCarMass is expected to be positive in " + FileName);
963 					TrailerCarMass = 1.0;
964 				}
965 
966 			}
967 
968 			if (powerNotches == 0)
969 			{
970 				Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfPowerNotches was not set in " + FileName);
971 				powerNotches = 8;
972 			}
973 			if (brakeNotches == 0)
974 			{
975 				Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfBrakeNotches was not set in " + FileName);
976 				brakeNotches = 8;
977 			}
978 			if (driverPowerNotches == 0)
979 			{
980 				if (currentFormat == TrainDatFormats.openBVE && myVersion >= 15311)
981 				{
982 					Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfDriverPowerNotches was not set in " + FileName);
983 				}
984 				driverPowerNotches = powerNotches;
985 			}
986 			if (driverBrakeNotches == 0)
987 			{
988 				if (currentFormat == TrainDatFormats.openBVE && myVersion >= 15311)
989 				{
990 					Plugin.currentHost.AddMessage(MessageType.Error, false, "NumberOfDriverBrakeNotches was not set in " + FileName);
991 				}
992 				driverBrakeNotches = brakeNotches;
993 			}
994 			Train.Handles.Reverser = new ReverserHandle(Train);
995 			Train.Handles.Power = new PowerHandle(powerNotches, driverPowerNotches, powerDelayUp, powerDelayDown, Train);
996 			if (powerReduceSteps != -1)
997 			{
998 				Train.Handles.Power.ReduceSteps = powerReduceSteps;
999 			}
1000 
1001 			if (trainBrakeType == BrakeSystemType.AutomaticAirBrake)
1002 			{
1003 				Train.Handles.Brake = new AirBrakeHandle(Train);
1004 			}
1005 			else
1006 			{
1007 				Train.Handles.Brake = new BrakeHandle(brakeNotches, driverBrakeNotches, Train.Handles.EmergencyBrake, brakeDelayUp, brakeDelayDown, Train);
1008 
1009 			}
1010 
1011 			if (locomotiveBrakeType == BrakeSystemType.AutomaticAirBrake)
1012 			{
1013 				Train.Handles.LocoBrake = new LocoAirBrakeHandle(Train);
1014 			}
1015 			else
1016 			{
1017 				Train.Handles.LocoBrake = new LocoBrakeHandle(locoBrakeNotches, Train.Handles.EmergencyBrake, locoBrakeDelayUp, locoBrakeDelayDown, Train);
1018 			}
1019 			Train.Handles.LocoBrakeType = (LocoBrakeType)locoBrakeType;
1020 			Train.Handles.HoldBrake = new HoldBrakeHandle(Train);
1021 			// apply data
1022 			if (MotorCars < 1) MotorCars = 1;
1023 			if (TrailerCars < 0) TrailerCars = 0;
1024 			int Cars = MotorCars + TrailerCars;
1025 			Train.Cars = new CarBase[Cars];
1026 			for (int i = 0; i < Train.Cars.Length; i++)
1027 			{
1028 				Train.Cars[i] = new CarBase(Train, i, CoefficientOfStaticFriction, CoefficientOfRollingResistance, AerodynamicDragCoefficient);
1029 			}
1030 			double DistanceBetweenTheCars = 0.3;
1031 
1032 			if (DriverCar < 0 | DriverCar >= Cars) {
1033 				Plugin.currentHost.AddMessage(MessageType.Error, false, "DriverCar must point to an existing car in " + FileName);
1034 				DriverCar = 0;
1035 
1036 			}
1037 			Train.DriverCar = DriverCar;
1038 			// brake system
1039 			double OperatingPressure;
1040 			if (BrakePipePressure <= 0.0) {
1041 				if (trainBrakeType == BrakeSystemType.AutomaticAirBrake) {
1042 					OperatingPressure = BrakeCylinderEmergencyMaximumPressure + 0.75 * (MainReservoirMinimumPressure - BrakeCylinderEmergencyMaximumPressure);
1043 					if (OperatingPressure > MainReservoirMinimumPressure) {
1044 						OperatingPressure = MainReservoirMinimumPressure;
1045 					}
1046 				} else {
1047 					if (BrakeCylinderEmergencyMaximumPressure < 480000.0 & MainReservoirMinimumPressure > 500000.0) {
1048 						OperatingPressure = 490000.0;
1049 					} else {
1050 						OperatingPressure = BrakeCylinderEmergencyMaximumPressure + 0.75 * (MainReservoirMinimumPressure - BrakeCylinderEmergencyMaximumPressure);
1051 					}
1052 				}
1053 			} else {
1054 				OperatingPressure = BrakePipePressure;
1055 			}
1056 			// acceleration curves
1057 			double MaximumAcceleration = 0.0;
1058 			if (AccelerationCurves.Length != Train.Handles.Power.MaximumNotch && !FileName.ToLowerInvariant().EndsWith("compatibility\\pretrain\\train.dat"))
1059 			{
1060 				//NOTE: The compatibility train.dat is only used to load some properties, hence this warning does not apply
1061 				Plugin.currentHost.AddMessage(MessageType.Warning, false, "The #ACCELERATION section defines " + AccelerationCurves.Length + " curves, but the #HANDLE section defines " + Train.Handles.Power.MaximumNotch + " power notches in " + FileName);
1062 			}
1063 
1064 			for (int i = 0; i < Math.Min(AccelerationCurves.Length, Train.Handles.Power.MaximumNotch); i++) {
1065 				bool errors = false;
1066 				if (AccelerationCurves[i].StageZeroAcceleration <= 0.0) {
1067 					AccelerationCurves[i].StageZeroAcceleration = 1.0;
1068 					errors = true;
1069 				}
1070 				if (AccelerationCurves[i].StageOneAcceleration <= 0.0) {
1071 					AccelerationCurves[i].StageOneAcceleration = 1.0;
1072 					errors = true;
1073 				}
1074 				if (AccelerationCurves[i].StageOneSpeed <= 0.0) {
1075 					AccelerationCurves[i].StageOneSpeed = 1.0;
1076 					errors = true;
1077 				}
1078 				if (AccelerationCurves[i].StageTwoSpeed <= 0.0) {
1079 					AccelerationCurves[i].StageTwoSpeed = 1.0;
1080 					errors = true;
1081 				}
1082 				if (AccelerationCurves[i].StageTwoExponent <= 0.0) {
1083 					AccelerationCurves[i].StageTwoExponent = 1.0;
1084 					errors = true;
1085 				}
1086 				if (AccelerationCurves[i].StageOneSpeed > AccelerationCurves[i].StageTwoSpeed) {
1087 					double x = 0.5 * (AccelerationCurves[i].StageOneSpeed + AccelerationCurves[i].StageTwoSpeed);
1088 					AccelerationCurves[i].StageOneSpeed = x;
1089 					AccelerationCurves[i].StageTwoSpeed = x;
1090 					errors = true;
1091 				}
1092 				if (errors) {
1093 					Plugin.currentHost.AddMessage(MessageType.Error, false, "Entry " + (i + 1).ToString(Culture) + " in the #ACCELERATION section is missing or invalid in " + FileName);
1094 				}
1095 				if (AccelerationCurves[i].MaximumAcceleration > MaximumAcceleration) {
1096 					MaximumAcceleration = AccelerationCurves[i].MaximumAcceleration;
1097 				}
1098 			}
1099 			// assign motor cars
1100 			if (MotorCars == 1) {
1101 				if (FrontCarIsMotorCar | TrailerCars == 0) {
1102 					Train.Cars[0].Specs.IsMotorCar = true;
1103 				} else {
1104 					Train.Cars[Cars - 1].Specs.IsMotorCar = true;
1105 				}
1106 			} else if (MotorCars == 2) {
1107 				if (FrontCarIsMotorCar | TrailerCars == 0) {
1108 					Train.Cars[0].Specs.IsMotorCar = true;
1109 					Train.Cars[Cars - 1].Specs.IsMotorCar = true;
1110 				} else if (TrailerCars == 1) {
1111 					Train.Cars[1].Specs.IsMotorCar = true;
1112 					Train.Cars[2].Specs.IsMotorCar = true;
1113 				} else {
1114 					int i = (int)Math.Ceiling(0.25 * (Cars - 1));
1115 					int j = (int)Math.Floor(0.75 * (Cars - 1));
1116 					Train.Cars[i].Specs.IsMotorCar = true;
1117 					Train.Cars[j].Specs.IsMotorCar = true;
1118 				}
1119 			} else if (MotorCars > 0) {
1120 				if (FrontCarIsMotorCar) {
1121 					Train.Cars[0].Specs.IsMotorCar = true;
1122 					double t = 1.0 + TrailerCars / (double)(MotorCars - 1);
1123 					double r = 0.0;
1124 					double x = 0.0;
1125 					while (true) {
1126 						double y = x + t - r;
1127 						x = Math.Ceiling(y);
1128 						r = x - y;
1129 						int i = (int)x;
1130 						if (i >= Cars) break;
1131 						Train.Cars[i].Specs.IsMotorCar = true;
1132 					}
1133 				} else {
1134 					Train.Cars[1].Specs.IsMotorCar = true;
1135 					double t = 1.0 + (TrailerCars - 1) / (double)(MotorCars - 1);
1136 					double r = 0.0;
1137 					double x = 1.0;
1138 					while (true) {
1139 						double y = x + t - r;
1140 						x = Math.Ceiling(y);
1141 						r = x - y;
1142 						int i = (int)x;
1143 						if (i >= Cars) break;
1144 						Train.Cars[i].Specs.IsMotorCar = true;
1145 					}
1146 				}
1147 			}
1148 			double MotorDeceleration = Math.Sqrt(MaximumAcceleration * BrakeDeceleration);
1149 			// apply brake-specific attributes for all cars
1150 			for (int i = 0; i < Cars; i++) {
1151 				AccelerationCurve[] DecelerationCurves =
1152 				{
1153 					new BveDecelerationCurve(BrakeDeceleration),
1154 				};
1155 				if (i == Train.DriverCar && Train.Handles.HasLocoBrake)
1156 				{
1157 					switch (locomotiveBrakeType)
1158 					{
1159 						case BrakeSystemType.AutomaticAirBrake:
1160 							Train.Cars[i].CarBrake = new AutomaticAirBrake(ElectropneumaticType, Train.Handles.EmergencyBrake, Train.Handles.Reverser, Train.Cars[i].Specs.IsMotorCar, BrakeControlSpeed, MotorDeceleration, DecelerationCurves);
1161 							break;
1162 						case BrakeSystemType.ElectricCommandBrake:
1163 							Train.Cars[i].CarBrake = new ElectricCommandBrake(ElectropneumaticType, Train.Handles.EmergencyBrake, Train.Handles.Reverser, Train.Cars[i].Specs.IsMotorCar, BrakeControlSpeed, MotorDeceleration, DecelerationCurves);
1164 							break;
1165 						case BrakeSystemType.ElectromagneticStraightAirBrake:
1166 							Train.Cars[i].CarBrake = new ElectromagneticStraightAirBrake(ElectropneumaticType, Train.Handles.EmergencyBrake, Train.Handles.Reverser, Train.Cars[i].Specs.IsMotorCar, BrakeControlSpeed, MotorDeceleration, DecelerationCurves);
1167 							break;
1168 					}
1169 				}
1170 				else
1171 				{
1172 					switch (trainBrakeType)
1173 					{
1174 						case BrakeSystemType.AutomaticAirBrake:
1175 							Train.Cars[i].CarBrake = new AutomaticAirBrake(ElectropneumaticType, Train.Handles.EmergencyBrake, Train.Handles.Reverser, Train.Cars[i].Specs.IsMotorCar, BrakeControlSpeed, MotorDeceleration, DecelerationCurves);
1176 							break;
1177 						case BrakeSystemType.ElectricCommandBrake:
1178 							Train.Cars[i].CarBrake = new ElectricCommandBrake(ElectropneumaticType, Train.Handles.EmergencyBrake, Train.Handles.Reverser, Train.Cars[i].Specs.IsMotorCar, BrakeControlSpeed, MotorDeceleration, DecelerationCurves);
1179 							break;
1180 						case BrakeSystemType.ElectromagneticStraightAirBrake:
1181 							Train.Cars[i].CarBrake = new ElectromagneticStraightAirBrake(ElectropneumaticType, Train.Handles.EmergencyBrake, Train.Handles.Reverser, Train.Cars[i].Specs.IsMotorCar, BrakeControlSpeed, MotorDeceleration, DecelerationCurves);
1182 							break;
1183 					}
1184 				}
1185 
1186 				if (Train.Cars[i].Specs.IsMotorCar || Train.IsPlayerTrain && i == Train.DriverCar || trainBrakeType == BrakeSystemType.ElectricCommandBrake)
1187 				{
1188 					Train.Cars[i].CarBrake.brakeType = BrakeType.Main;
1189 				}
1190 				else
1191 				{
1192 					Train.Cars[i].CarBrake.brakeType = BrakeType.Auxiliary;
1193 				}
1194 				Train.Cars[i].CarBrake.mainReservoir = new MainReservoir(MainReservoirMinimumPressure, MainReservoirMaximumPressure, 0.01, (trainBrakeType == BrakeSystemType.AutomaticAirBrake ? 0.25 : 0.075) / Cars);
1195 				Train.Cars[i].CarBrake.airCompressor = new Compressor(5000.0, Train.Cars[i].CarBrake.mainReservoir, Train.Cars[i]);
1196 				Train.Cars[i].CarBrake.equalizingReservoir = new EqualizingReservoir(50000.0, 250000.0, 200000.0);
1197 				Train.Cars[i].CarBrake.equalizingReservoir.NormalPressure = 1.005 * OperatingPressure;
1198 
1199 				Train.Cars[i].CarBrake.brakePipe = new BrakePipe(OperatingPressure, 10000000.0, 1500000.0, 5000000.0, trainBrakeType == BrakeSystemType.ElectricCommandBrake);
1200 				{
1201 					double r = 200000.0 / BrakeCylinderEmergencyMaximumPressure - 1.0;
1202 					if (r < 0.1) r = 0.1;
1203 					if (r > 1.0) r = 1.0;
1204 					Train.Cars[i].CarBrake.auxiliaryReservoir = new AuxiliaryReservoir(0.975 * OperatingPressure, 200000.0, 0.5, r);
1205 				}
1206 				Train.Cars[i].CarBrake.brakeCylinder = new BrakeCylinder(BrakeCylinderServiceMaximumPressure, BrakeCylinderEmergencyMaximumPressure, trainBrakeType == BrakeSystemType.AutomaticAirBrake ? BrakeCylinderUp : 0.3 * BrakeCylinderUp, BrakeCylinderUp, BrakeCylinderDown);
1207 				Train.Cars[i].CarBrake.straightAirPipe = new StraightAirPipe(300000.0, 400000.0, 200000.0);
1208 				Train.Cars[i].CarBrake.JerkUp = JerkBrakeUp;
1209 				Train.Cars[i].CarBrake.JerkDown = JerkBrakeDown;
1210 			}
1211 			if (Train.Handles.HasHoldBrake & Train.Handles.Brake.MaximumNotch > 1) {
1212 				Train.Handles.Brake.MaximumNotch--;
1213 			}
1214 			// apply train attributes
1215 			Train.Handles.Reverser.Driver = 0;
1216 			Train.Handles.Reverser.Actual = 0;
1217 			Train.Handles.Power.Driver = 0;
1218 			Train.Handles.Power.Safety = 0;
1219 			Train.Handles.Power.Actual = 0;
1220 			Train.Handles.Power.DelayedChanges = new HandleChange[] { };
1221 			Train.Handles.Brake.Driver = 0;
1222 			Train.Handles.Brake.Safety = 0;
1223 			Train.Handles.Brake.Actual = 0;
1224 			if (trainBrakeType == BrakeSystemType.AutomaticAirBrake) {
1225 				Train.Handles.HandleType = HandleType.TwinHandle;
1226 				Train.Handles.HasHoldBrake = false;
1227 			}
1228 			Train.SafetySystems.PassAlarm = new PassAlarm(passAlarm, Train.Cars[DriverCar]);
1229 			Train.SafetySystems.PilotLamp = new PilotLamp(Train.Cars[DriverCar]);
1230 			Train.SafetySystems.StationAdjust = new StationAdjustAlarm(Train);
1231 			switch (Plugin.CurrentOptions.TrainStart)
1232 			{
1233 				// starting mode
1234 				case TrainStartMode.ServiceBrakesAts:
1235 					for (int i = 0; i < Cars; i++) {
1236 						Train.Cars[i].CarBrake.brakeCylinder.CurrentPressure = Train.Cars[i].CarBrake.brakeCylinder.ServiceMaximumPressure;
1237 						Train.Cars[i].CarBrake.brakePipe.CurrentPressure = Train.Cars[i].CarBrake.brakePipe.NormalPressure;
1238 						Train.Cars[i].CarBrake.straightAirPipe.CurrentPressure = Train.Cars[i].CarBrake.brakeCylinder.ServiceMaximumPressure;
1239 						Train.Cars[i].CarBrake.equalizingReservoir.CurrentPressure = Train.Cars[i].CarBrake.equalizingReservoir.NormalPressure;
1240 					}
1241 
1242 					if (trainBrakeType == BrakeSystemType.AutomaticAirBrake)
1243 					{
1244 						Train.Handles.Brake.Driver = (int)AirBrakeHandleState.Service;
1245 						Train.Handles.Brake.Safety = (int)AirBrakeHandleState.Service;
1246 						Train.Handles.Brake.Actual = (int)AirBrakeHandleState.Service;
1247 					}
1248 					else
1249 					{
1250 						int notch = (int)Math.Round(0.7 * Train.Handles.Brake.MaximumNotch);
1251 						Train.Handles.Brake.Driver = notch;
1252 						Train.Handles.Brake.Safety = notch;
1253 						Train.Handles.Brake.Actual = notch;
1254 					}
1255 					Train.Handles.EmergencyBrake.Driver = false;
1256 					Train.Handles.EmergencyBrake.Safety = false;
1257 					Train.Handles.EmergencyBrake.Actual = false;
1258 					Train.Handles.Reverser.Driver = ReverserPosition.Forwards;
1259 					Train.Handles.Reverser.Actual = ReverserPosition.Forwards;
1260 					break;
1261 				case TrainStartMode.EmergencyBrakesAts:
1262 					for (int i = 0; i < Cars; i++) {
1263 						Train.Cars[i].CarBrake.brakeCylinder.CurrentPressure = Train.Cars[i].CarBrake.brakeCylinder.EmergencyMaximumPressure;
1264 						Train.Cars[i].CarBrake.brakePipe.CurrentPressure = 0.0;
1265 						Train.Cars[i].CarBrake.straightAirPipe.CurrentPressure = 0.0;
1266 						Train.Cars[i].CarBrake.equalizingReservoir.CurrentPressure = 0.0;
1267 					}
1268 
1269 					if (trainBrakeType == BrakeSystemType.AutomaticAirBrake)
1270 					{
1271 						Train.Handles.Brake.Driver = (int)AirBrakeHandleState.Service;
1272 						Train.Handles.Brake.Safety = (int)AirBrakeHandleState.Service;
1273 						Train.Handles.Brake.Actual = (int)AirBrakeHandleState.Service;
1274 					}
1275 					else
1276 					{
1277 						Train.Handles.Brake.Driver = Train.Handles.Brake.MaximumNotch;
1278 						Train.Handles.Brake.Safety = Train.Handles.Brake.MaximumNotch;
1279 						Train.Handles.Brake.Actual = Train.Handles.Brake.MaximumNotch;
1280 					}
1281 
1282 					Train.Handles.EmergencyBrake.Driver = true;
1283 					Train.Handles.EmergencyBrake.Safety = true;
1284 					Train.Handles.EmergencyBrake.Actual = true;
1285 					break;
1286 				default:
1287 					for (int i = 0; i < Cars; i++) {
1288 						Train.Cars[i].CarBrake.brakeCylinder.CurrentPressure = Train.Cars[i].CarBrake.brakeCylinder.EmergencyMaximumPressure;
1289 						Train.Cars[i].CarBrake.brakePipe.CurrentPressure = 0.0;
1290 						Train.Cars[i].CarBrake.straightAirPipe.CurrentPressure = 0.0;
1291 						Train.Cars[i].CarBrake.equalizingReservoir.CurrentPressure = 0.0;
1292 					}
1293 
1294 					if (trainBrakeType == BrakeSystemType.AutomaticAirBrake)
1295 					{
1296 						Train.Handles.Brake.Driver = (int)AirBrakeHandleState.Service;
1297 						Train.Handles.Brake.Safety = (int)AirBrakeHandleState.Service;
1298 						Train.Handles.Brake.Actual = (int)AirBrakeHandleState.Service;
1299 					}
1300 					else
1301 					{
1302 						Train.Handles.Brake.Driver = Train.Handles.Brake.MaximumNotch;
1303 						Train.Handles.Brake.Safety = Train.Handles.Brake.MaximumNotch;
1304 						Train.Handles.Brake.Actual = Train.Handles.Brake.MaximumNotch;
1305 					}
1306 					Train.Handles.EmergencyBrake.Driver = true;
1307 					Train.Handles.EmergencyBrake.Safety = true;
1308 					Train.Handles.EmergencyBrake.Actual = true;
1309 					break;
1310 			}
1311 			// apply other attributes for all cars
1312 			double AxleDistance = 0.4 * CarLength;
1313 			for (int i = 0; i < Cars; i++) {
1314 				if (Train.Cars.Length > 1)
1315 				{
1316 					Train.Cars[i].Coupler = new Coupler(0.9 * DistanceBetweenTheCars, 1.1 * DistanceBetweenTheCars, Train.Cars[i], i < Cars - 1 ? Train.Cars[i + 1] : null, Train);
1317 				}
1318 				else
1319 				{
1320 					Train.Cars[i].Coupler = new Coupler(0.9 * DistanceBetweenTheCars, 1.1 * DistanceBetweenTheCars, Train.Cars[i], null, Train);
1321 				}
1322 				if (i == DriverCar)
1323 				{
1324 					Train.Cars[i].Breaker = new Breaker(Train.Cars[i]);
1325 				}
1326 				Train.Cars[i].CurrentCarSection = -1;
1327 				Train.Cars[i].ChangeCarSection(CarSectionType.NotVisible);
1328 				Train.Cars[i].FrontBogie.ChangeSection(-1);
1329 				Train.Cars[i].RearBogie.ChangeSection(-1);
1330 				Train.Cars[i].Coupler.ChangeSection(-1);
1331 				Train.Cars[i].FrontAxle.Follower.TriggerType = i == 0 ? EventTriggerType.FrontCarFrontAxle : EventTriggerType.OtherCarFrontAxle;
1332 				Train.Cars[i].RearAxle.Follower.TriggerType = i == Cars - 1 ? EventTriggerType.RearCarRearAxle : EventTriggerType.OtherCarRearAxle;
1333 				Train.Cars[i].BeaconReceiver.TriggerType = i == 0 ? EventTriggerType.TrainFront : EventTriggerType.None;
1334 				Train.Cars[i].BeaconReceiverPosition = 0.5 * CarLength;
1335 				Train.Cars[i].FrontAxle.Follower.Car = Train.Cars[i];
1336 				Train.Cars[i].RearAxle.Follower.Car = Train.Cars[i];
1337 				Train.Cars[i].FrontAxle.Position = AxleDistance;
1338 				Train.Cars[i].RearAxle.Position = -AxleDistance;
1339 				Train.Cars[i].Specs.JerkPowerUp = JerkPowerUp;
1340 				Train.Cars[i].Specs.JerkPowerDown = JerkPowerDown;
1341 				Train.Cars[i].Specs.ExposedFrontalArea = CarExposedFrontalArea;
1342 				Train.Cars[i].Specs.UnexposedFrontalArea = CarUnexposedFrontalArea;
1343 				Train.Cars[i].Doors[0] = new Door(-1, DoorWidth, DoorTolerance);
1344 				Train.Cars[i].Doors[1] = new Door(1, DoorWidth, DoorTolerance);
1345 				Train.Cars[i].Specs.DoorOpenFrequency = 0.0;
1346 				Train.Cars[i].Specs.DoorCloseFrequency = 0.0;
1347 				Train.Cars[i].Specs.CenterOfGravityHeight = CenterOfGravityHeight;
1348 				Train.Cars[i].Width = CarWidth;
1349 				Train.Cars[i].Height = CarHeight;
1350 				Train.Cars[i].Length = CarLength;
1351 				Train.Cars[i].Specs.CriticalTopplingAngle = 0.5 * Math.PI - Math.Atan(2 * Train.Cars[i].Specs.CenterOfGravityHeight / Train.Cars[i].Width);
1352 			}
1353 
1354 			// assign motor/trailer-specific settings
1355 			for (int i = 0; i < Cars; i++) {
1356 				Train.Cars[i].ConstSpeed = new CarConstSpeed(Train.Cars[i]);
1357 				Train.Cars[i].HoldBrake = new CarHoldBrake(Train.Cars[i]);
1358 				Train.Cars[i].ReAdhesionDevice = new CarReAdhesionDevice(Train.Cars[i], ReAdhesionDevice);
1359 				if (Train.Cars[i].Specs.IsMotorCar) {
1360 					// motor car
1361 					Train.Cars[i].EmptyMass = MotorCarMass;
1362 					Train.Cars[i].CargoMass = 0;
1363 					Array.Resize(ref Train.Cars[i].Specs.AccelerationCurves, AccelerationCurves.Length);
1364 					for (int j = 0; j < AccelerationCurves.Length; j++)
1365 					{
1366 						Train.Cars[i].Specs.AccelerationCurves[j] = AccelerationCurves[j].Clone(1.0 + TrailerCars * TrailerCarMass / (MotorCars * MotorCarMass));
1367 					}
1368 					Train.Cars[i].Specs.AccelerationCurveMaximum = MaximumAcceleration;
1369 
1370 					// motor sound
1371 					Train.Cars[i].Sounds.Motor.SpeedConversionFactor = 18.0;
1372 					Train.Cars[i].Sounds.Motor.Tables = new BVEMotorSoundTable[4];
1373 					for (int j = 0; j < 4; j++) {
1374 						Train.Cars[i].Sounds.Motor.Tables[j].Entries = new BVEMotorSoundTableEntry[Tables[j].Entries.Length];
1375 						for (int k = 0; k < Tables[j].Entries.Length; k++) {
1376 							Train.Cars[i].Sounds.Motor.Tables[j].Entries[k] = Tables[j].Entries[k];
1377 						}
1378 					}
1379 				} else {
1380 					// trailer car
1381 					Train.Cars[i].EmptyMass = TrailerCarMass;
1382 					Train.Cars[i].CargoMass = 0;
1383 					Train.Cars[i].Specs.AccelerationCurves = new AccelerationCurve[] { };
1384 					Train.Cars[i].Specs.AccelerationCurveMaximum = 0.0;
1385 					Train.Cars[i].Sounds.Motor.SpeedConversionFactor = 18.0;
1386 					Train.Cars[i].Sounds.Motor.Tables = new BVEMotorSoundTable[4];
1387 					for (int j = 0; j < 4; j++) {
1388 						Train.Cars[i].Sounds.Motor.Tables[j].Entries = new BVEMotorSoundTableEntry[] { };
1389 					}
1390 				}
1391 			}
1392 			// driver
1393 
1394 			Train.Cars[Train.DriverCar].Driver.X = Driver.X;
1395 			Train.Cars[Train.DriverCar].Driver.Y = Driver.Y;
1396 			Train.Cars[Train.DriverCar].Driver.Z = 0.5 * CarLength + Driver.Z;
1397 			if (Train.IsPlayerTrain)
1398 			{
1399 				Train.Cars[DriverCar].HasInteriorView = true;
1400 			}
1401 
1402 			// finish
1403 
1404 		}
1405 
1406 	}
1407 }
1408