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