1 using System; 2 using System.Globalization; 3 using System.Linq; 4 using LibRender2.Trains; 5 using OpenBveApi; 6 using OpenBveApi.Colors; 7 using OpenBveApi.Interface; 8 using OpenBveApi.Runtime; 9 using OpenBveApi.Trains; 10 using RouteManager2.MessageManager; 11 using RouteManager2.SignalManager; 12 using RouteManager2.Stations; 13 using SoundManager; 14 using TrainManager.BrakeSystems; 15 using TrainManager.Car; 16 using TrainManager.Handles; 17 using TrainManager.SafetySystems; 18 19 namespace TrainManager.Trains 20 { 21 /* 22 * TEMPORARY NAME AND CLASS TO ALLOW FOR MOVE IN PARTS 23 */ 24 public partial class TrainBase : AbstractTrain 25 { 26 /// <summary>Contains information on the specifications of the train</summary> 27 public TrainSpecs Specs; 28 /// <summary>The cab handles</summary> 29 public CabHandles Handles; 30 /// <summary>Holds the passengers</summary> 31 public TrainPassengers Passengers; 32 /// <summary>Holds the safety systems for the train</summary> 33 public TrainSafetySystems SafetySystems; 34 /// <summary>Holds the cars</summary> 35 public CarBase[] Cars; 36 /// <summary>The index of the car which the camera is currently anchored to</summary> 37 public int CameraCar; 38 /// <summary>Coefficient of friction used for braking</summary> 39 public const double CoefficientOfGroundFriction = 0.5; 40 /// <summary>The speed difference in m/s above which derailments etc. will occur</summary> 41 public double CriticalCollisionSpeedDifference = 8.0; 42 /// <summary>The time of the last station arrival in seconds since midnight</summary> 43 public double StationArrivalTime; 44 /// <summary>The time of the last station departure in seconds since midnight</summary> 45 public double StationDepartureTime; 46 /// <summary>Whether the station departure sound has been triggered</summary> 47 public bool StationDepartureSoundPlayed; 48 /// <summary>The adjust distance to the station stop point</summary> 49 public double StationDistanceToStopPoint; 50 /// <summary>The plugin used by this train.</summary> 51 public Plugin Plugin; 52 /// <summary>The driver body</summary> 53 public DriverBody DriverBody; 54 /// <summary>Whether the train has currently derailed</summary> 55 public bool Derailed; 56 /// <summary>Stores the previous route speed limit</summary> 57 private double previousRouteLimit; 58 /// <summary>Internal timer used for updates</summary> 59 private double InternalTimerTimeElapsed; 60 /// <inheritdoc/> 61 public override bool IsPlayerTrain => this == TrainManagerBase.PlayerTrain; 62 63 private bool currentlyOverspeed; 64 65 /// <inheritdoc/> 66 public override int NumberOfCars => this.Cars.Length; 67 TrainBase(TrainState state)68 public TrainBase(TrainState state) 69 { 70 State = state; 71 Destination = TrainManagerBase.CurrentOptions.InitialDestination; 72 Station = -1; 73 RouteLimits = new[] { double.PositiveInfinity }; 74 CurrentRouteLimit = double.PositiveInfinity; 75 CurrentSectionLimit = double.PositiveInfinity; 76 Cars = new CarBase[] { }; 77 78 Specs.DoorOpenMode = DoorMode.AutomaticManualOverride; 79 Specs.DoorCloseMode = DoorMode.AutomaticManualOverride; 80 } 81 82 /// <summary>Called once when the simulation loads to initalize the train</summary> Initialize()83 public virtual void Initialize() 84 { 85 for (int i = 0; i < Cars.Length; i++) 86 { 87 Cars[i].Initialize(); 88 } 89 90 Update(0.0); 91 } 92 93 /// <summary>Synchronizes the entire train after a period of infrequent updates</summary> Synchronize()94 public void Synchronize() 95 { 96 for (int i = 0; i < Cars.Length; i++) 97 { 98 Cars[i].Syncronize(); 99 } 100 } 101 102 /// <summary>Updates the objects for all cars in this train</summary> 103 /// <param name="TimeElapsed">The time elapsed</param> 104 /// <param name="ForceUpdate">Whether this is a forced update</param> UpdateObjects(double TimeElapsed, bool ForceUpdate)105 public void UpdateObjects(double TimeElapsed, bool ForceUpdate) 106 { 107 if (TrainManagerBase.currentHost.SimulationState == SimulationState.Running) 108 { 109 for (int i = 0; i < Cars.Length; i++) 110 { 111 Cars[i].UpdateObjects(TimeElapsed, ForceUpdate, true); 112 Cars[i].FrontBogie.UpdateObjects(TimeElapsed, ForceUpdate); 113 Cars[i].RearBogie.UpdateObjects(TimeElapsed, ForceUpdate); 114 if (i == DriverCar && Cars[i].Windscreen != null) 115 { 116 Cars[i].Windscreen.Update(TimeElapsed); 117 } 118 119 Cars[i].Coupler.UpdateObjects(TimeElapsed, ForceUpdate); 120 } 121 } 122 } 123 124 /// <summary>Performs a forced update on all objects attached to the driver car</summary> 125 /// <remarks>This function ignores damping of needles etc.</remarks> UpdateCabObjects()126 public void UpdateCabObjects() 127 { 128 Cars[this.DriverCar].UpdateObjects(0.0, true, false); 129 } 130 131 /// <summary>Places the cars</summary> 132 /// <param name="TrackPosition">The track position of the front car</param> PlaceCars(double TrackPosition)133 public void PlaceCars(double TrackPosition) 134 { 135 for (int i = 0; i < Cars.Length; i++) 136 { 137 //Front axle track position 138 Cars[i].FrontAxle.Follower.TrackPosition = TrackPosition - 0.5 * Cars[i].Length + Cars[i].FrontAxle.Position; 139 //Bogie for front axle 140 Cars[i].FrontBogie.FrontAxle.Follower.TrackPosition = Cars[i].FrontAxle.Follower.TrackPosition - 0.5 * Cars[i].FrontBogie.Length + Cars[i].FrontBogie.FrontAxle.Position; 141 Cars[i].FrontBogie.RearAxle.Follower.TrackPosition = Cars[i].FrontAxle.Follower.TrackPosition - 0.5 * Cars[i].FrontBogie.Length + Cars[i].FrontBogie.RearAxle.Position; 142 //Rear axle track position 143 Cars[i].RearAxle.Follower.TrackPosition = TrackPosition - 0.5 * Cars[i].Length + Cars[i].RearAxle.Position; 144 //Bogie for rear axle 145 Cars[i].RearBogie.FrontAxle.Follower.TrackPosition = Cars[i].RearAxle.Follower.TrackPosition - 0.5 * Cars[i].RearBogie.Length + Cars[i].RearBogie.FrontAxle.Position; 146 Cars[i].RearBogie.RearAxle.Follower.TrackPosition = Cars[i].RearAxle.Follower.TrackPosition - 0.5 * Cars[i].RearBogie.Length + Cars[i].RearBogie.RearAxle.Position; 147 //Beacon reciever (AWS, ATC etc.) 148 Cars[i].BeaconReceiver.TrackPosition = TrackPosition - 0.5 * Cars[i].Length + Cars[i].BeaconReceiverPosition; 149 TrackPosition -= Cars[i].Length; 150 if (i < Cars.Length - 1) 151 { 152 TrackPosition -= 0.5 * (Cars[i].Coupler.MinimumDistanceBetweenCars + Cars[i].Coupler.MaximumDistanceBetweenCars); 153 } 154 } 155 } 156 157 /// <summary>Disposes of the train</summary> Dispose()158 public override void Dispose() 159 { 160 State = TrainState.Disposed; 161 for (int i = 0; i < Cars.Length; i++) 162 { 163 Cars[i].ChangeCarSection(CarSectionType.NotVisible); 164 Cars[i].FrontBogie.ChangeSection(-1); 165 Cars[i].RearBogie.ChangeSection(-1); 166 Cars[i].Coupler.ChangeSection(-1); 167 } 168 169 TrainManagerBase.currentHost.StopAllSounds(this); 170 171 for (int i = 0; i < TrainManagerBase.CurrentRoute.Sections.Length; i++) 172 { 173 TrainManagerBase.CurrentRoute.Sections[i].Leave(this); 174 } 175 176 if (TrainManagerBase.CurrentRoute.Sections.Length != 0) 177 { 178 TrainManagerBase.CurrentRoute.UpdateAllSections(); 179 } 180 } 181 182 /// <inheritdoc/> UpdateBeacon(int transponderType, int sectionIndex, int optional)183 public override void UpdateBeacon(int transponderType, int sectionIndex, int optional) 184 { 185 if (Plugin != null) 186 { 187 Plugin.UpdateBeacon(transponderType, sectionIndex, optional); 188 } 189 } 190 191 /// <inheritdoc/> Update(double TimeElapsed)192 public override void Update(double TimeElapsed) 193 { 194 if (State == TrainState.Pending) 195 { 196 // pending train 197 bool forceIntroduction = !IsPlayerTrain && TrainManagerBase.currentHost.SimulationState != SimulationState.MinimalisticSimulation; 198 double time = 0.0; 199 if (!forceIntroduction) 200 { 201 for (int i = 0; i < TrainManagerBase.CurrentRoute.Stations.Length; i++) 202 { 203 if (TrainManagerBase.CurrentRoute.Stations[i].StopMode == StationStopMode.AllStop | TrainManagerBase.CurrentRoute.Stations[i].StopMode == StationStopMode.PlayerPass) 204 { 205 if (TrainManagerBase.CurrentRoute.Stations[i].ArrivalTime >= 0.0) 206 { 207 time = TrainManagerBase.CurrentRoute.Stations[i].ArrivalTime; 208 } 209 else if (TrainManagerBase.CurrentRoute.Stations[i].DepartureTime >= 0.0) 210 { 211 time = TrainManagerBase.CurrentRoute.Stations[i].DepartureTime - TrainManagerBase.CurrentRoute.Stations[i].StopTime; 212 } 213 214 break; 215 } 216 } 217 218 time -= TimetableDelta; 219 } 220 221 if (TrainManagerBase.CurrentRoute.SecondsSinceMidnight >= time | forceIntroduction) 222 { 223 bool introduce = true; 224 if (!forceIntroduction) 225 { 226 if (CurrentSectionIndex >= 0 && TrainManagerBase.CurrentRoute.Sections.Length > CurrentSectionIndex) 227 { 228 if (!TrainManagerBase.CurrentRoute.Sections[CurrentSectionIndex].IsFree()) 229 { 230 introduce = false; 231 } 232 } 233 } 234 235 if (this == TrainManagerBase.PlayerTrain && TrainManagerBase.currentHost.SimulationState != SimulationState.Loading) 236 { 237 /* Loading has finished, but we still have an AI train in the current section 238 * This may be caused by an iffy RunInterval value, or simply by having no sections * 239 * 240 * We must introduce the player's train as otherwise the cab and loop sounds are missing 241 * NOTE: In this case, the signalling cannot prevent the player from colliding with 242 * the AI train 243 */ 244 245 introduce = true; 246 } 247 248 if (introduce) 249 { 250 // train is introduced 251 State = TrainState.Available; 252 for (int j = 0; j < Cars.Length; j++) 253 { 254 if (Cars[j].CarSections.Length != 0) 255 { 256 if (j == DriverCar && IsPlayerTrain && TrainManagerBase.CurrentOptions.InitialViewpoint == 0) 257 { 258 Cars[j].ChangeCarSection(CarSectionType.Interior); 259 } 260 else 261 { 262 /* 263 * HACK: Load in exterior mode first to ensure everything is cached 264 * before switching immediately to not visible 265 * https://github.com/leezer3/OpenBVE/issues/226 266 * Stuff like the R142A really needs to downsize the textures supplied, 267 * but we have no control over external factors.... 268 */ 269 Cars[j].ChangeCarSection(CarSectionType.Exterior); 270 if (IsPlayerTrain && TrainManagerBase.CurrentOptions.InitialViewpoint == 0) 271 { 272 Cars[j].ChangeCarSection(CarSectionType.NotVisible, true); 273 } 274 } 275 276 } 277 278 Cars[j].FrontBogie.ChangeSection(!IsPlayerTrain ? 0 : -1); 279 Cars[j].RearBogie.ChangeSection(!IsPlayerTrain ? 0 : -1); 280 Cars[j].Coupler.ChangeSection(!IsPlayerTrain ? 0 : -1); 281 282 if (Cars[j].Specs.IsMotorCar && Cars[j].Sounds.Loop != null) 283 { 284 Cars[j].Sounds.Loop.Play(Cars[j], true); 285 } 286 } 287 } 288 } 289 } 290 else if (State == TrainState.Available) 291 { 292 // available train 293 UpdatePhysicsAndControls(TimeElapsed); 294 if (CurrentSpeed > CurrentRouteLimit) 295 { 296 if (!currentlyOverspeed || previousRouteLimit != CurrentRouteLimit || TrainManagerBase.CurrentOptions.GameMode == GameMode.Arcade) 297 { 298 /* 299 * HACK: If the limit has changed, or we are in arcade mode, notify the player 300 * This conforms to the original behaviour, but doesn't need to raise the message from the event. 301 */ 302 TrainManagerBase.currentHost.AddMessage(Translations.GetInterfaceString("message_route_overspeed"), MessageDependency.RouteLimit, GameMode.Normal, MessageColor.Orange, double.PositiveInfinity, null); 303 } 304 currentlyOverspeed = true; 305 } 306 else 307 { 308 currentlyOverspeed = false; 309 } 310 311 if (TrainManagerBase.CurrentOptions.Accessibility) 312 { 313 if (previousRouteLimit != CurrentRouteLimit) 314 { 315 //Show for 10s and announce the current speed limit if screen reader present 316 TrainManagerBase.currentHost.AddMessage(Translations.GetInterfaceString("message_route_newlimit"), MessageDependency.AccessibilityHelper, GameMode.Normal, MessageColor.White, TrainManagerBase.currentHost.InGameTime + 10.0, null); 317 } 318 319 Section nextSection = TrainManagerBase.CurrentRoute.NextSection(FrontCarTrackPosition()); 320 if (nextSection != null) 321 { 322 //If we find an appropriate signal, and the distance to it is less than 500m, announce if screen reader is present 323 //Aspect announce to be triggered via a separate keybind 324 double tPos = nextSection.TrackPosition - FrontCarTrackPosition(); 325 if (!nextSection.AccessibilityAnnounced && tPos < 500) 326 { 327 string s = Translations.GetInterfaceString("message_route_nextsection").Replace("[distance]", $"{tPos:0.0}") + "m"; 328 TrainManagerBase.currentHost.AddMessage(s, MessageDependency.AccessibilityHelper, GameMode.Normal, MessageColor.White, TrainManagerBase.currentHost.InGameTime + 10.0, null); 329 nextSection.AccessibilityAnnounced = true; 330 } 331 } 332 RouteStation nextStation = TrainManagerBase.CurrentRoute.NextStation(FrontCarTrackPosition()); 333 if (nextStation != null) 334 { 335 //If we find an appropriate signal, and the distance to it is less than 500m, announce if screen reader is present 336 //Aspect announce to be triggered via a separate keybind 337 double tPos = nextStation.DefaultTrackPosition - FrontCarTrackPosition(); 338 if (!nextStation.AccessibilityAnnounced && tPos < 500) 339 { 340 string s = Translations.GetInterfaceString("message_route_nextstation").Replace("[distance]", $"{tPos:0.0}") + "m".Replace("[name]", nextStation.Name); 341 TrainManagerBase.currentHost.AddMessage(s, MessageDependency.AccessibilityHelper, GameMode.Normal, MessageColor.White, TrainManagerBase.currentHost.InGameTime + 10.0, null); 342 nextStation.AccessibilityAnnounced = true; 343 } 344 } 345 } 346 previousRouteLimit = CurrentRouteLimit; 347 if (TrainManagerBase.CurrentOptions.GameMode == GameMode.Arcade) 348 { 349 if (CurrentSectionLimit == 0.0) 350 { 351 TrainManagerBase.currentHost.AddMessage(Translations.GetInterfaceString("message_signal_stop"), MessageDependency.PassedRedSignal, GameMode.Normal, MessageColor.Red, double.PositiveInfinity, null); 352 } 353 else if (CurrentSpeed > CurrentSectionLimit) 354 { 355 TrainManagerBase.currentHost.AddMessage(Translations.GetInterfaceString("message_signal_overspeed"), MessageDependency.SectionLimit, GameMode.Normal, MessageColor.Orange, double.PositiveInfinity, null); 356 } 357 } 358 359 if (AI != null) 360 { 361 AI.Trigger(TimeElapsed); 362 } 363 } 364 else if (State == TrainState.Bogus) 365 { 366 // bogus train 367 if (AI != null) 368 { 369 AI.Trigger(TimeElapsed); 370 } 371 } 372 373 //Trigger point sounds if appropriate 374 for (int i = 0; i < Cars.Length; i++) 375 { 376 CarSound c = null; 377 if (Cars[i].FrontAxle.PointSoundTriggered) 378 { 379 Cars[i].FrontAxle.PointSoundTriggered = false; 380 int bufferIndex = Cars[i].FrontAxle.RunIndex; 381 if (bufferIndex > Cars[i].FrontAxle.PointSounds.Length - 1) 382 { 383 //If the switch sound does not exist, return zero 384 //Required to handle legacy trains which don't have idx specific run sounds defined 385 bufferIndex = 0; 386 } 387 388 if (Cars[i].FrontAxle.PointSounds == null || Cars[i].FrontAxle.PointSounds.Length == 0) 389 { 390 //No point sounds defined at all 391 continue; 392 } 393 394 c = (CarSound) Cars[i].FrontAxle.PointSounds[bufferIndex]; 395 if (c.Buffer == null) 396 { 397 c = (CarSound) Cars[i].FrontAxle.PointSounds[0]; 398 } 399 } 400 401 if (c != null) 402 { 403 double spd = Math.Abs(CurrentSpeed); 404 double pitch = spd / 12.5; 405 double gain = pitch < 0.5 ? 2.0 * pitch : 1.0; 406 if (pitch > 0.2 && gain > 0.2) 407 { 408 c.Play(pitch, gain, Cars[i], false); 409 } 410 } 411 } 412 } 413 414 /// <summary>Updates the physics and controls for this train</summary> 415 /// <param name="TimeElapsed">The time elapsed</param> UpdatePhysicsAndControls(double TimeElapsed)416 private void UpdatePhysicsAndControls(double TimeElapsed) 417 { 418 if (TimeElapsed == 0.0 || TimeElapsed > 1000) 419 { 420 //HACK: The physics engine really does not like update times above 1000ms 421 //This works around a bug experienced when jumping to a station on a steep hill 422 //causing exessive acceleration 423 return; 424 } 425 426 // move cars 427 for (int i = 0; i < Cars.Length; i++) 428 { 429 Cars[i].Move(Cars[i].CurrentSpeed * TimeElapsed); 430 if (State == TrainState.Disposed) 431 { 432 return; 433 } 434 } 435 436 // update station and doors 437 UpdateStation(TimeElapsed); 438 UpdateDoors(TimeElapsed); 439 // delayed handles 440 if (Plugin == null) 441 { 442 Handles.Power.Safety = Handles.Power.Driver; 443 Handles.Brake.Safety = Handles.Brake.Driver; 444 Handles.EmergencyBrake.Safety = Handles.EmergencyBrake.Driver; 445 } 446 447 Handles.Power.Update(); 448 Handles.Brake.Update(); 449 Handles.Brake.Update(); 450 Handles.EmergencyBrake.Update(); 451 Handles.HoldBrake.Actual = Handles.HoldBrake.Driver; 452 // update speeds 453 UpdateSpeeds(TimeElapsed); 454 // Update Run and Motor sounds 455 for (int i = 0; i < Cars.Length; i++) 456 { 457 Cars[i].UpdateRunSounds(TimeElapsed); 458 Cars[i].UpdateMotorSounds(TimeElapsed); 459 } 460 461 // safety system 462 if (TrainManagerBase.currentHost.SimulationState != SimulationState.MinimalisticSimulation | !IsPlayerTrain) 463 { 464 UpdateSafetySystem(); 465 } 466 467 { 468 // breaker sound 469 bool breaker; 470 if (Cars[DriverCar].CarBrake is AutomaticAirBrake) 471 { 472 breaker = Handles.Reverser.Actual != 0 & Handles.Power.Safety >= 1 & Handles.Brake.Safety == (int) AirBrakeHandleState.Release & !Handles.EmergencyBrake.Safety & !Handles.HoldBrake.Actual; 473 } 474 else 475 { 476 breaker = Handles.Reverser.Actual != 0 & Handles.Power.Safety >= 1 & Handles.Brake.Safety == 0 & !Handles.EmergencyBrake.Safety & !Handles.HoldBrake.Actual; 477 } 478 479 Cars[DriverCar].Breaker.Update(breaker); 480 } 481 // passengers 482 Passengers.Update(Specs.CurrentAverageAcceleration, TimeElapsed); 483 // signals 484 if (CurrentSectionLimit == 0.0) 485 { 486 if (Handles.EmergencyBrake.Driver & CurrentSpeed > -0.03 & CurrentSpeed < 0.03) 487 { 488 CurrentSectionLimit = 6.94444444444444; 489 if (IsPlayerTrain) 490 { 491 string s = Translations.GetInterfaceString("message_signal_proceed"); 492 double a = (3.6 * CurrentSectionLimit) * TrainManagerBase.CurrentOptions.SpeedConversionFactor; 493 s = s.Replace("[speed]", a.ToString("0", CultureInfo.InvariantCulture)); 494 s = s.Replace("[unit]", TrainManagerBase.CurrentOptions.UnitOfSpeed); 495 TrainManagerBase.currentHost.AddMessage(s, MessageDependency.None, GameMode.Normal, MessageColor.Red, TrainManagerBase.currentHost.InGameTime + 5.0, null); 496 } 497 } 498 } 499 500 // infrequent updates 501 InternalTimerTimeElapsed += TimeElapsed; 502 if (InternalTimerTimeElapsed > 10.0) 503 { 504 InternalTimerTimeElapsed -= 10.0; 505 Synchronize(); 506 } 507 } 508 UpdateSpeeds(double TimeElapsed)509 private void UpdateSpeeds(double TimeElapsed) 510 { 511 if (TrainManagerBase.currentHost.SimulationState == SimulationState.MinimalisticSimulation & IsPlayerTrain) 512 { 513 // hold the position of the player's train during startup 514 for (int i = 0; i < Cars.Length; i++) 515 { 516 Cars[i].CurrentSpeed = 0.0; 517 Cars[i].Specs.MotorAcceleration = 0.0; 518 } 519 520 return; 521 } 522 523 // update brake system 524 UpdateBrakeSystem(TimeElapsed, out var DecelerationDueToBrake, out var DecelerationDueToMotor); 525 // calculate new car speeds 526 double[] NewSpeeds = new double[Cars.Length]; 527 for (int i = 0; i < Cars.Length; i++) 528 { 529 Cars[i].UpdateSpeed(TimeElapsed, DecelerationDueToMotor[i], DecelerationDueToBrake[i], out NewSpeeds[i]); 530 } 531 532 // calculate center of mass position 533 double[] CenterOfCarPositions = new double[Cars.Length]; 534 double CenterOfMassPosition = 0.0; 535 double TrainMass = 0.0; 536 for (int i = 0; i < Cars.Length; i++) 537 { 538 double pr = Cars[i].RearAxle.Follower.TrackPosition - Cars[i].RearAxle.Position; 539 double pf = Cars[i].FrontAxle.Follower.TrackPosition - Cars[i].FrontAxle.Position; 540 CenterOfCarPositions[i] = 0.5 * (pr + pf); 541 CenterOfMassPosition += CenterOfCarPositions[i] * Cars[i].CurrentMass; 542 TrainMass += Cars[i].CurrentMass; 543 } 544 545 if (TrainMass != 0.0) 546 { 547 CenterOfMassPosition /= TrainMass; 548 } 549 550 { 551 // coupler 552 // determine closest cars 553 int p = -1; // primary car index 554 int s = -1; // secondary car index 555 { 556 double PrimaryDistance = double.MaxValue; 557 for (int i = 0; i < Cars.Length; i++) 558 { 559 double d = Math.Abs(CenterOfCarPositions[i] - CenterOfMassPosition); 560 if (d < PrimaryDistance) 561 { 562 PrimaryDistance = d; 563 p = i; 564 } 565 } 566 567 double SecondDistance = Double.MaxValue; 568 for (int i = p - 1; i <= p + 1; i++) 569 { 570 if (i >= 0 & i < Cars.Length & i != p) 571 { 572 double d = Math.Abs(CenterOfCarPositions[i] - CenterOfMassPosition); 573 if (d < SecondDistance) 574 { 575 SecondDistance = d; 576 s = i; 577 } 578 } 579 } 580 581 if (s >= 0 && PrimaryDistance <= 0.25 * (PrimaryDistance + SecondDistance)) 582 { 583 s = -1; 584 } 585 } 586 // coupler 587 bool[] CouplerCollision = new bool[Cars.Length - 1]; 588 int cf, cr; 589 if (s >= 0) 590 { 591 // use two cars as center of mass 592 if (p > s) 593 { 594 int t = p; 595 p = s; 596 s = t; 597 } 598 599 double min = Cars[p].Coupler.MinimumDistanceBetweenCars; 600 double max = Cars[p].Coupler.MaximumDistanceBetweenCars; 601 double d = CenterOfCarPositions[p] - CenterOfCarPositions[s] - 0.5 * (Cars[p].Length + Cars[s].Length); 602 if (d < min) 603 { 604 double t = (min - d) / (Cars[p].CurrentMass + Cars[s].CurrentMass); 605 double tp = t * Cars[s].CurrentMass; 606 double ts = t * Cars[p].CurrentMass; 607 Cars[p].UpdateTrackFollowers(tp, false, false); 608 Cars[s].UpdateTrackFollowers(-ts, false, false); 609 CenterOfCarPositions[p] += tp; 610 CenterOfCarPositions[s] -= ts; 611 CouplerCollision[p] = true; 612 } 613 else if (d > max & !Cars[p].Derailed & !Cars[s].Derailed) 614 { 615 double t = (d - max) / (Cars[p].CurrentMass + Cars[s].CurrentMass); 616 double tp = t * Cars[s].CurrentMass; 617 double ts = t * Cars[p].CurrentMass; 618 619 Cars[p].UpdateTrackFollowers(-tp, false, false); 620 Cars[s].UpdateTrackFollowers(ts, false, false); 621 CenterOfCarPositions[p] -= tp; 622 CenterOfCarPositions[s] += ts; 623 CouplerCollision[p] = true; 624 } 625 626 cf = p; 627 cr = s; 628 } 629 else 630 { 631 // use one car as center of mass 632 cf = p; 633 cr = p; 634 } 635 636 // front cars 637 for (int i = cf - 1; i >= 0; i--) 638 { 639 double min = Cars[i].Coupler.MinimumDistanceBetweenCars; 640 double max = Cars[i].Coupler.MaximumDistanceBetweenCars; 641 double d = CenterOfCarPositions[i] - CenterOfCarPositions[i + 1] - 0.5 * (Cars[i].Length + Cars[i + 1].Length); 642 if (d < min) 643 { 644 double t = min - d + 0.0001; 645 Cars[i].UpdateTrackFollowers(t, false, false); 646 CenterOfCarPositions[i] += t; 647 CouplerCollision[i] = true; 648 } 649 else if (d > max & !Cars[i].Derailed & !Cars[i + 1].Derailed) 650 { 651 double t = d - max + 0.0001; 652 Cars[i].UpdateTrackFollowers(-t, false, false); 653 CenterOfCarPositions[i] -= t; 654 CouplerCollision[i] = true; 655 } 656 } 657 658 // rear cars 659 for (int i = cr + 1; i < Cars.Length; i++) 660 { 661 double min = Cars[i - 1].Coupler.MinimumDistanceBetweenCars; 662 double max = Cars[i - 1].Coupler.MaximumDistanceBetweenCars; 663 double d = CenterOfCarPositions[i - 1] - CenterOfCarPositions[i] - 0.5 * (Cars[i].Length + Cars[i - 1].Length); 664 if (d < min) 665 { 666 double t = min - d + 0.0001; 667 Cars[i].UpdateTrackFollowers(-t, false, false); 668 CenterOfCarPositions[i] -= t; 669 CouplerCollision[i - 1] = true; 670 } 671 else if (d > max & !Cars[i].Derailed & !Cars[i - 1].Derailed) 672 { 673 double t = d - max + 0.0001; 674 Cars[i].UpdateTrackFollowers(t, false, false); 675 676 CenterOfCarPositions[i] += t; 677 CouplerCollision[i - 1] = true; 678 } 679 } 680 681 // update speeds 682 for (int i = 0; i < Cars.Length - 1; i++) 683 { 684 if (CouplerCollision[i]) 685 { 686 int j; 687 for (j = i + 1; j < Cars.Length - 1; j++) 688 { 689 if (!CouplerCollision[j]) 690 { 691 break; 692 } 693 } 694 695 double v = 0.0; 696 double m = 0.0; 697 for (int k = i; k <= j; k++) 698 { 699 v += NewSpeeds[k] * Cars[k].CurrentMass; 700 m += Cars[k].CurrentMass; 701 } 702 703 if (m != 0.0) 704 { 705 v /= m; 706 } 707 708 for (int k = i; k <= j; k++) 709 { 710 if (TrainManagerBase.CurrentOptions.Derailments && Math.Abs(v - NewSpeeds[k]) > 0.5 * CriticalCollisionSpeedDifference) 711 { 712 Derail(k, TimeElapsed); 713 } 714 715 NewSpeeds[k] = v; 716 } 717 718 i = j - 1; 719 } 720 } 721 } 722 // update average data 723 CurrentSpeed = 0.0; 724 Specs.CurrentAverageAcceleration = 0.0; 725 double invtime = TimeElapsed != 0.0 ? 1.0 / TimeElapsed : 1.0; 726 for (int i = 0; i < Cars.Length; i++) 727 { 728 Cars[i].Specs.Acceleration = (NewSpeeds[i] - Cars[i].CurrentSpeed) * invtime; 729 Cars[i].CurrentSpeed = NewSpeeds[i]; 730 CurrentSpeed += NewSpeeds[i]; 731 Specs.CurrentAverageAcceleration += Cars[i].Specs.Acceleration; 732 } 733 734 double invcarlen = 1.0 / Cars.Length; 735 CurrentSpeed *= invcarlen; 736 Specs.CurrentAverageAcceleration *= invcarlen; 737 } 738 739 /// <summary>Updates the safety system plugin for this train</summary> UpdateSafetySystem()740 internal void UpdateSafetySystem() 741 { 742 if (Plugin != null) 743 { 744 SignalData[] data = new SignalData[16]; 745 int count = 0; 746 int start = CurrentSectionIndex >= 0 ? CurrentSectionIndex : 0; 747 for (int i = start; i < TrainManagerBase.CurrentRoute.Sections.Length; i++) 748 { 749 SignalData signal = TrainManagerBase.CurrentRoute.Sections[i].GetPluginSignal(this); 750 if (data.Length == count) 751 { 752 Array.Resize(ref data, data.Length << 1); 753 } 754 755 data[count] = signal; 756 count++; 757 if (signal.Aspect == 0 | count == 16) 758 { 759 break; 760 } 761 } 762 763 Array.Resize(ref data, count); 764 Plugin.UpdateSignals(data); 765 Plugin.LastSection = CurrentSectionIndex; 766 Plugin.UpdatePlugin(); 767 } 768 } 769 770 771 /// <summary>Call this method to derail a car</summary> 772 /// <param name="CarIndex">The car index to derail</param> 773 /// <param name="ElapsedTime">The elapsed time for this frame (Used for logging)</param> Derail(int CarIndex, double ElapsedTime)774 public override void Derail(int CarIndex, double ElapsedTime) 775 { 776 this.Cars[CarIndex].Derailed = true; 777 this.Derailed = true; 778 if (TrainManagerBase.CurrentOptions.GenerateDebugLogging) 779 { 780 TrainManagerBase.currentHost.AddMessage(MessageType.Information, false, "Car " + CarIndex + " derailed. Current simulation time: " + TrainManagerBase.CurrentRoute.SecondsSinceMidnight + " Current frame time: " + ElapsedTime); 781 } 782 } 783 784 /// <inheritdoc/> Derail(AbstractCar Car, double ElapsedTime)785 public override void Derail(AbstractCar Car, double ElapsedTime) 786 { 787 if (this.Cars.Contains(Car)) 788 { 789 var c = Car as CarBase; 790 // ReSharper disable once PossibleNullReferenceException 791 c.Derailed = true; 792 this.Derailed = true; 793 if (TrainManagerBase.CurrentOptions.GenerateDebugLogging) 794 { 795 TrainManagerBase.currentHost.AddMessage(MessageType.Information, false, "Car " + c.Index + " derailed. Current simulation time: " + TrainManagerBase.CurrentRoute.SecondsSinceMidnight + " Current frame time: " + ElapsedTime); 796 } 797 } 798 } 799 800 /// <inheritdoc/> Reverse()801 public override void Reverse() 802 { 803 double trackPosition = Cars[0].TrackPosition; 804 Cars = Cars.Reverse().ToArray(); 805 for (int i = 0; i < Cars.Length; i++) 806 { 807 Cars[i].Reverse(); 808 } 809 PlaceCars(trackPosition); 810 DriverCar = Cars.Length - 1 - DriverCar; 811 UpdateCabObjects(); 812 } 813 814 815 816 /// <inheritdoc/> FrontCarTrackPosition()817 public override double FrontCarTrackPosition() 818 { 819 return Cars[0].FrontAxle.Follower.TrackPosition - Cars[0].FrontAxle.Position + 0.5 * Cars[0].Length; 820 } 821 822 /// <inheritdoc/> RearCarTrackPosition()823 public override double RearCarTrackPosition() 824 { 825 return Cars[Cars.Length - 1].RearAxle.Follower.TrackPosition - Cars[Cars.Length - 1].RearAxle.Position - 0.5 * Cars[Cars.Length - 1].Length; 826 } 827 828 /// <inheritdoc/> SectionChange()829 public override void SectionChange() 830 { 831 if (CurrentSectionLimit == 0.0 && TrainManagerBase.currentHost.SimulationState != SimulationState.MinimalisticSimulation) 832 { 833 TrainManagerBase.currentHost.AddMessage(Translations.GetInterfaceString("message_signal_stop"), MessageDependency.PassedRedSignal, GameMode.Normal, MessageColor.Red, double.PositiveInfinity, null); 834 } 835 else if (CurrentSpeed > CurrentSectionLimit) 836 { 837 TrainManagerBase.currentHost.AddMessage(Translations.GetInterfaceString("message_signal_overspeed"), MessageDependency.SectionLimit, GameMode.Normal, MessageColor.Orange, double.PositiveInfinity, null); 838 } 839 } 840 841 Jump(int stationIndex)842 public override void Jump(int stationIndex) 843 { 844 if (IsPlayerTrain) 845 { 846 for (int i = 0; i < TrainManagerBase.CurrentRoute.Sections.Length; i++) 847 { 848 TrainManagerBase.CurrentRoute.Sections[i].AccessibilityAnnounced = false; 849 } 850 } 851 SafetySystems.PassAlarm.Halt(); 852 int currentTrackElement = Cars[0].FrontAxle.Follower.LastTrackElement; 853 StationState = TrainStopState.Jumping; 854 int stopIndex = TrainManagerBase.CurrentRoute.Stations[stationIndex].GetStopIndex(NumberOfCars); 855 if (stopIndex >= 0) 856 { 857 if (IsPlayerTrain) 858 { 859 if (Plugin != null) 860 { 861 Plugin.BeginJump((InitializationModes) TrainManagerBase.CurrentOptions.TrainStart); 862 } 863 } 864 865 for (int h = 0; h < Cars.Length; h++) 866 { 867 Cars[h].CurrentSpeed = 0.0; 868 } 869 870 double d = TrainManagerBase.CurrentRoute.Stations[stationIndex].Stops[stopIndex].TrackPosition - Cars[0].FrontAxle.Follower.TrackPosition + Cars[0].FrontAxle.Position - 0.5 * Cars[0].Length; 871 if (IsPlayerTrain) 872 { 873 SoundsBase.SuppressSoundEvents = true; 874 } 875 876 while (d != 0.0) 877 { 878 double x; 879 if (Math.Abs(d) > 1.0) 880 { 881 x = Math.Sign(d); 882 } 883 else 884 { 885 x = d; 886 } 887 888 for (int h = 0; h < Cars.Length; h++) 889 { 890 Cars[h].Move(x); 891 } 892 893 if (Math.Abs(d) >= 1.0) 894 { 895 d -= x; 896 } 897 else 898 { 899 break; 900 } 901 } 902 903 if (IsPlayerTrain) 904 { 905 SoundsBase.SuppressSoundEvents = false; 906 } 907 908 if (Handles.EmergencyBrake.Driver) 909 { 910 Handles.Power.ApplyState(0, false); 911 } 912 else 913 { 914 Handles.Brake.ApplyState(Handles.Brake.MaximumNotch, false); 915 Handles.Power.ApplyState(0, false); 916 if (Handles.Brake is AirBrakeHandle) 917 { 918 Handles.Brake.ApplyState(AirBrakeHandleState.Service); 919 } 920 921 } 922 923 if (TrainManagerBase.CurrentRoute.Sections.Length > 0) 924 { 925 TrainManagerBase.CurrentRoute.UpdateAllSections(); 926 } 927 928 if (IsPlayerTrain) 929 { 930 if (TrainManagerBase.CurrentRoute.Stations[stationIndex].ArrivalTime >= 0.0) 931 { 932 TrainManagerBase.CurrentRoute.SecondsSinceMidnight = TrainManagerBase.CurrentRoute.Stations[stationIndex].ArrivalTime; 933 } 934 else if (TrainManagerBase.CurrentRoute.Stations[stationIndex].DepartureTime >= 0.0) 935 { 936 TrainManagerBase.CurrentRoute.SecondsSinceMidnight = TrainManagerBase.CurrentRoute.Stations[stationIndex].DepartureTime - TrainManagerBase.CurrentRoute.Stations[stationIndex].StopTime; 937 } 938 } 939 940 for (int i = 0; i < Cars.Length; i++) 941 { 942 Cars[i].Doors[0].AnticipatedOpen = TrainManagerBase.CurrentRoute.Stations[stationIndex].OpenLeftDoors; 943 Cars[i].Doors[1].AnticipatedOpen = TrainManagerBase.CurrentRoute.Stations[stationIndex].OpenRightDoors; 944 } 945 if (IsPlayerTrain) 946 { 947 if (Plugin != null) 948 { 949 Plugin.EndJump(); 950 } 951 } 952 953 StationState = TrainStopState.Pending; 954 if (IsPlayerTrain) 955 { 956 LastStation = stationIndex; 957 } 958 959 int newTrackElement = Cars[0].FrontAxle.Follower.LastTrackElement; 960 if (newTrackElement < currentTrackElement) 961 { 962 for (int i = newTrackElement; i < currentTrackElement; i++) 963 { 964 for (int j = 0; j < TrainManagerBase.currentHost.Tracks[0].Elements[i].Events.Length; j++) 965 { 966 TrainManagerBase.currentHost.Tracks[0].Elements[i].Events[j].Reset(); 967 } 968 969 } 970 } 971 TrainManagerBase.currentHost.ProcessJump(this, stationIndex); 972 } 973 } 974 } 975 } 976