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