diff --git a/assets/Controls/Default.controls b/assets/Controls/Default.controls
index afd1daad89..feca026dee 100644
--- a/assets/Controls/Default.controls
+++ b/assets/Controls/Default.controls
@@ -104,6 +104,8 @@ DECREASE_CUTOFF, keyboard, U, 2
ROUTE_INFORMATION, keyboard, F9, 0
SHOW_EVENTS, keyboard, E, 3
DEBUG_ATS, keyboard, F10, 2
-ACCESSIBILITY_CURRENT_SPEED, keyboard, s, 3
-ACCESSIBILITY_NEXT_SIGNAL, keyboard, a, 3
-ACCESSIBILITY_NEXT_STATION, keyboard, t, 3
\ No newline at end of file
+ACCESSIBILITY_CURRENT_SPEED, keyboard, S, 3
+ACCESSIBILITY_NEXT_SIGNAL, keyboard, A, 3
+ACCESSIBILITY_NEXT_STATION, keyboard, T, 3
+UNCOUPLE_REAR, keyboard, Semicolon, 2
+UNCOUPLE_FRONT, keyboard, Semicolon, 3
\ No newline at end of file
diff --git a/assets/Languages/en-US.xlf b/assets/Languages/en-US.xlf
index d93f69b1d2..f70e380ac9 100755
--- a/assets/Languages/en-US.xlf
+++ b/assets/Languages/en-US.xlf
@@ -1579,6 +1579,18 @@
Mouse grab: off
+
+ Please switch to exterior view to uncouple.
+
+
+ Unable to uncouple this car.
+
+
+ Uncoupling the rear of car number
+
+
+ Uncoupling the front of car number
+
diff --git a/source/ObjectViewer/Trains/NearestTrain.cs b/source/ObjectViewer/Trains/NearestTrain.cs
index 909a580fd1..ae16bdd36a 100644
--- a/source/ObjectViewer/Trains/NearestTrain.cs
+++ b/source/ObjectViewer/Trains/NearestTrain.cs
@@ -45,8 +45,6 @@ static NearestTrain()
private static TrainBase CreateDummyTrain()
{
TrainBase train = new TrainBase(TrainState.Available);
-
- train.Handles.Reverser = new ReverserHandle(train);
train.Handles.Power = new PowerHandle(Specs.PowerNotches, Specs.PowerNotches, new double[] { }, new double[] { }, train);
if (Specs.IsAirBrake)
{
@@ -57,7 +55,6 @@ private static TrainBase CreateDummyTrain()
train.Handles.Brake = new BrakeHandle(Specs.BrakeNotches, Specs.BrakeNotches, null, new double[] { }, new double[] { }, train);
train.Handles.HasHoldBrake = Specs.HasHoldBrake;
}
- train.Handles.EmergencyBrake = new EmergencyHandle(train);
train.Handles.HoldBrake = new HoldBrakeHandle(train);
train.Specs.HasConstSpeed = Specs.HasConstSpeed;
@@ -65,7 +62,6 @@ private static TrainBase CreateDummyTrain()
for (int i = 0; i < train.Cars.Length; i++)
{
train.Cars[i] = new CarBase(train, i);
- train.Cars[i].Specs = new CarPhysics();
if (Specs.IsAirBrake)
{
diff --git a/source/OpenBVE/System/Host.cs b/source/OpenBVE/System/Host.cs
index 77d37a5103..ff1dc7401e 100644
--- a/source/OpenBVE/System/Host.cs
+++ b/source/OpenBVE/System/Host.cs
@@ -576,6 +576,41 @@ public override AbstractTrain[] Trains
}
}
+ public override void AddTrain(AbstractTrain ReferenceTrain, AbstractTrain NewTrain, bool Preccedes)
+ {
+ Array.Resize(ref Program.TrainManager.Trains, Program.TrainManager.Trains.Length + 1);
+ int trainIndex = -1;
+ // find index of train within trainmanager array
+ for (int i = 0; i < Program.TrainManager.Trains.Length; i++)
+ {
+ if (Program.TrainManager.Trains[i] == ReferenceTrain)
+ {
+ trainIndex = i;
+ break;
+ }
+ }
+
+ if (Preccedes && trainIndex > 0)
+ {
+ trainIndex--;
+ }
+
+ if (trainIndex == -1)
+ {
+ Program.TrainManager.Trains[Program.TrainManager.Trains.Length - 1] = (TrainBase)NewTrain;
+ }
+ else
+ {
+ for (int i = Program.TrainManager.Trains.Length - 2; i > trainIndex; i--)
+ {
+ Program.TrainManager.Trains[i + 1] = Program.TrainManager.Trains[i];
+ }
+
+ Program.TrainManager.Trains[trainIndex + 1] = (TrainBase)NewTrain;
+ }
+
+ }
+
public override AbstractTrain ClosestTrain(AbstractTrain Train)
{
TrainBase baseTrain = Train as TrainBase;
diff --git a/source/OpenBVE/System/Input/ProcessControls.Digital.cs b/source/OpenBVE/System/Input/ProcessControls.Digital.cs
index 4266f68943..e7a2581bf2 100644
--- a/source/OpenBVE/System/Input/ProcessControls.Digital.cs
+++ b/source/OpenBVE/System/Input/ProcessControls.Digital.cs
@@ -990,6 +990,45 @@ private static void ProcessDigitalControl(double TimeElapsed, ref Control Contro
}
}
+ break;
+ case Translations.Command.UncoupleFront:
+ if (Program.Renderer.Camera.CurrentMode != CameraViewMode.Exterior)
+ {
+ MessageManager.AddMessage(
+ Translations.GetInterfaceString("notification_switchexterior_uncouple"),
+ MessageDependency.None, GameMode.Expert,
+ MessageColor.White, Program.CurrentRoute.SecondsSinceMidnight + 5.0, null);
+ return;
+ }
+
+ if (TrainManager.PlayerTrain.CameraCar == 0)
+ {
+ MessageManager.AddMessage(
+ Translations.GetInterfaceString("notification_unable_uncouple"),
+ MessageDependency.None, GameMode.Expert,
+ MessageColor.White, Program.CurrentRoute.SecondsSinceMidnight + 5.0, null);
+ return;
+ }
+ MessageManager.AddMessage(
+ Translations.GetInterfaceString("notification_exterior_uncouplefront") + " " +TrainManager.PlayerTrain.CameraCar,
+ MessageDependency.None, GameMode.Expert,
+ MessageColor.White, Program.CurrentRoute.SecondsSinceMidnight + 5.0, null);
+ TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.CameraCar].Uncouple(true, false);
+ break;
+ case Translations.Command.UncoupleRear:
+ if (Program.Renderer.Camera.CurrentMode != CameraViewMode.Exterior)
+ {
+ MessageManager.AddMessage(
+ Translations.GetInterfaceString("notification_switchexterior_uncouple"),
+ MessageDependency.None, GameMode.Expert,
+ MessageColor.White, Program.CurrentRoute.SecondsSinceMidnight + 5.0, null);
+ return;
+ }
+ MessageManager.AddMessage(
+ Translations.GetInterfaceString("notification_exterior_uncouplerear") + " " + TrainManager.PlayerTrain.CameraCar,
+ MessageDependency.None, GameMode.Expert,
+ MessageColor.White, Program.CurrentRoute.SecondsSinceMidnight + 5.0, null);
+ TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.CameraCar].Uncouple(false, true);
break;
case Translations.Command.TimetableToggle:
// option: timetable
diff --git a/source/OpenBveApi/Interface/Input/Commands.CommandInfo.cs b/source/OpenBveApi/Interface/Input/Commands.CommandInfo.cs
index a7740c4897..d20db3d6ef 100644
--- a/source/OpenBveApi/Interface/Input/Commands.CommandInfo.cs
+++ b/source/OpenBveApi/Interface/Input/Commands.CommandInfo.cs
@@ -156,6 +156,8 @@ public static CommandInfo TryGetInfo(this CommandInfo[] commandInfos, Command Va
new CommandInfo(Command.DeviceConstSpeed, CommandType.Digital, "DEVICE_CONSTSPEED"),
new CommandInfo(Command.PlayMicSounds, CommandType.Digital, "PLAY_MIC_SOUNDS"),
new CommandInfo(Command.Sanders, CommandType.Digital, "SANDERS"),
+ new CommandInfo(Command.UncoupleFront, CommandType.Digital, "UNCOUPLE_FRONT"),
+ new CommandInfo(Command.UncoupleRear, CommandType.Digital, "UNCOUPLE_REAR"),
//We only want to mark these as obsolete for new users of the API
#pragma warning disable 618
diff --git a/source/OpenBveApi/Interface/Input/Commands.cs b/source/OpenBveApi/Interface/Input/Commands.cs
index 93028af92c..43180e230a 100644
--- a/source/OpenBveApi/Interface/Input/Commands.cs
+++ b/source/OpenBveApi/Interface/Input/Commands.cs
@@ -307,7 +307,11 @@ public enum Command
* Added in 1.8.4.3
*/
/// Toggles the sanders if fitted
- Sanders
+ Sanders,
+ /// Uncouples the front coupling of a car
+ UncoupleFront,
+ /// Uncouples the rear coupling of a car
+ UncoupleRear
}
/// Defines the possible command types
diff --git a/source/OpenBveApi/Objects/ObjectTypes/AnimatedWorldObject.StateSound.cs b/source/OpenBveApi/Objects/ObjectTypes/AnimatedWorldObject.StateSound.cs
index 9fda553bb0..41bc47964c 100644
--- a/source/OpenBveApi/Objects/ObjectTypes/AnimatedWorldObject.StateSound.cs
+++ b/source/OpenBveApi/Objects/ObjectTypes/AnimatedWorldObject.StateSound.cs
@@ -47,7 +47,7 @@ public override void Update(AbstractTrain NearestTrain, double TimeElapsed, bool
{
double timeDelta = Object.SecondsSinceLastUpdate + TimeElapsed;
Object.SecondsSinceLastUpdate = 0.0;
- Object.Update(NearestTrain, NearestTrain == null ? 0 : NearestTrain.DriverCar, TrackPosition, Position, Direction, Up, Side, true, true, timeDelta, true);
+ Object.Update(NearestTrain, NearestTrain?.DriverCar ?? 0, TrackPosition, Position, Direction, Up, Side, true, true, timeDelta, true);
if (this.Object.CurrentState != this.lastState && currentHost.SimulationState != SimulationState.Loading)
{
if (SingleBuffer)
diff --git a/source/OpenBveApi/System/Hosts.cs b/source/OpenBveApi/System/Hosts.cs
index a588a58bfd..0b1323266b 100644
--- a/source/OpenBveApi/System/Hosts.cs
+++ b/source/OpenBveApi/System/Hosts.cs
@@ -704,6 +704,15 @@ public virtual AbstractTrain ClosestTrain(double TrackPosition)
return null;
}
+ /// Adds a new train
+ /// The reference train, or a null reference to add the train at the end of the queue
+ /// The new train
+ /// Whether this train preceeds or follows the reference train
+ public virtual void AddTrain(AbstractTrain ReferenceTrain, AbstractTrain NewTrain, bool Preceedes)
+ {
+
+ }
+
/*
* Used for interop with the 32-bit plugin host
*/
diff --git a/source/OpenBveApi/Train/AbstractCar.cs b/source/OpenBveApi/Train/AbstractCar.cs
index 6c443fbcc8..412e0e27b7 100644
--- a/source/OpenBveApi/Train/AbstractCar.cs
+++ b/source/OpenBveApi/Train/AbstractCar.cs
@@ -1,3 +1,4 @@
+using System;
using OpenBveApi.Math;
namespace OpenBveApi.Trains
@@ -72,6 +73,10 @@ public virtual int Index
// A single car is by itself a train, hence index zero
return 0;
}
+ set
+ {
+ throw new NotSupportedException("Cannot set the index of a single car");
+ }
}
/// Call this method to reverse (flip) the car
@@ -85,5 +90,11 @@ public virtual void OpenDoors(bool Left, bool Right)
{
}
+
+ /// Uncouples the car
+ public virtual void Uncouple(bool Front, bool Rear)
+ {
+
+ }
}
}
diff --git a/source/Plugins/Train.OpenBve/Sound/SoundCfg.Xml.cs b/source/Plugins/Train.OpenBve/Sound/SoundCfg.Xml.cs
index be9438d817..dcd802b6a6 100644
--- a/source/Plugins/Train.OpenBve/Sound/SoundCfg.Xml.cs
+++ b/source/Plugins/Train.OpenBve/Sound/SoundCfg.Xml.cs
@@ -51,6 +51,8 @@ internal void Parse(string fileName, ref TrainBase Train, ref CarBase car, bool
Vector3 right = new Vector3(1.3, 0.0, 0.0);
//Positioned at the front of the car, centered X and Y
Vector3 front = new Vector3(0.0, 0.0, 0.5 * car.Length);
+ //Positioned at the rear of the car centered X and Y
+ Vector3 rear = new Vector3(0.0, 0.0, -0.5 * car.Length);
//Positioned at the position of the panel / 3D cab (Remember that the panel is just an object in the world...)
Vector3 panel = new Vector3(car.Driver.X, car.Driver.Y, car.Driver.Z + 1.0);
@@ -581,6 +583,29 @@ internal void Parse(string fileName, ref TrainBase Train, ref CarBase car, bool
}
}
break;
+ case "coupler":
+ if (!c.ChildNodes.OfType().Any())
+ {
+ Plugin.currentHost.AddMessage(MessageType.Error, false, "An empty list of brake handle sounds was defined in in XML file " + fileName);
+ break;
+ }
+ if (!isDriverCar)
+ {
+ break;
+ }
+ foreach (XmlNode cc in c.ChildNodes)
+ {
+ switch (cc.Name.ToLowerInvariant())
+ {
+ case "uncouple":
+ ParseNode(cc, out car.Coupler.UncoupleSound, rear, SoundCfgParser.smallRadius);
+ break;
+ default:
+ Plugin.currentHost.AddMessage(MessageType.Error, false, "Declaration " + cc.Name + " is unsupported in a " + c.Name + " node.");
+ break;
+ }
+ }
+ break;
}
}
}
diff --git a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs b/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs
index b4ce2ea677..cee04714b8 100644
--- a/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs
+++ b/source/Plugins/Train.OpenBve/Train/BVE/TrainDatParser.cs
@@ -230,7 +230,6 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) {
double DoorTolerance = 0.0;
ReadhesionDeviceType ReAdhesionDevice = ReadhesionDeviceType.TypeA;
PassAlarmType passAlarm = PassAlarmType.None;
- Train.Handles.EmergencyBrake = new EmergencyHandle(Train);
Train.Handles.HasLocoBrake = false;
double[] powerDelayUp = { }, powerDelayDown = { }, brakeDelayUp = { }, brakeDelayDown = { }, locoBrakeDelayUp = { }, locoBrakeDelayDown = { };
double electricBrakeDelayUp = 0, electricBrakeDelayDown = 0;
@@ -1027,7 +1026,6 @@ internal void Parse(string FileName, Encoding Encoding, TrainBase Train) {
}
driverBrakeNotches = brakeNotches;
}
- Train.Handles.Reverser = new ReverserHandle(Train);
Train.Handles.Power = new PowerHandle(powerNotches, driverPowerNotches, powerDelayUp, powerDelayDown, Train);
if (powerReduceSteps != -1)
{
diff --git a/source/RouteViewer/TrainManagerR.cs b/source/RouteViewer/TrainManagerR.cs
index 9f358a7d18..edff5eb5f9 100644
--- a/source/RouteViewer/TrainManagerR.cs
+++ b/source/RouteViewer/TrainManagerR.cs
@@ -26,8 +26,6 @@ public TrainManager(HostInterface host, BaseRenderer renderer, BaseOptions optio
internal class Train : TrainBase {
internal Train() : base(TrainState.Pending)
{
- Handles.Reverser = new ReverserHandle(this);
- Handles.EmergencyBrake = new EmergencyHandle(this);
Handles.Power = new PowerHandle(8, 8, new double[] {}, new double[] {}, this);
Handles.Brake = new BrakeHandle(8, 8, null, new double[] {}, new double[] {}, this);
Handles.HoldBrake = new HoldBrakeHandle(this);
diff --git a/source/TrainEditor2/Simulation/TrainManager/Car/Car.cs b/source/TrainEditor2/Simulation/TrainManager/Car/Car.cs
index a4fd64f356..1de9016b0f 100644
--- a/source/TrainEditor2/Simulation/TrainManager/Car/Car.cs
+++ b/source/TrainEditor2/Simulation/TrainManager/Car/Car.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using OpenBveApi.Math;
-using OpenBveApi.Trains;
using SoundManager;
using TrainEditor2.Models.Sounds;
using TrainManager.Car;
@@ -20,7 +19,6 @@ internal class Car : CarBase
public Car() : base(null, 0)
{
Sounds = new CarSounds();
- Specs = new CarPhysics();
Specs.IsMotorCar = true;
}
diff --git a/source/TrainManager/Car/Bogie/Bogie.cs b/source/TrainManager/Car/Bogie/Bogie.cs
index 8cf3c51b0f..ce0ad88747 100644
--- a/source/TrainManager/Car/Bogie/Bogie.cs
+++ b/source/TrainManager/Car/Bogie/Bogie.cs
@@ -39,20 +39,14 @@ public class Bogie
/// Whether the bogie is the rear bogie
private readonly bool Rear;
-
- /// Holds a reference to the base train
- // We don't want this to be read-only if we ever manage to uncouple cars...
- // ReSharper disable once FieldCanBeMadeReadOnly.Local
- private AbstractTrain baseTrain;
-
- public Bogie(AbstractTrain train, CarBase car, bool IsRear)
+
+ public Bogie(CarBase car, bool IsRear)
{
- baseTrain = train;
baseCar = car;
Rear = IsRear;
CarSections = new CarSection[] { };
- FrontAxle = new Axle(TrainManagerBase.currentHost, train, car);
- RearAxle = new Axle(TrainManagerBase.currentHost, train, car);
+ FrontAxle = new Axle(TrainManagerBase.currentHost, car.baseTrain, car);
+ RearAxle = new Axle(TrainManagerBase.currentHost, car.baseTrain, car);
}
public void UpdateObjects(double TimeElapsed, bool ForceUpdate)
@@ -200,7 +194,7 @@ private void UpdateSectionElement(int SectionIndex, int ElementIndex, Vector3 Po
updatefunctions = true;
}
- CarSections[SectionIndex].Groups[0].Elements[ElementIndex].Update(baseTrain, baseCar.Index, FrontAxle.Follower.TrackPosition - FrontAxle.Position, p, Direction, Up, Side, updatefunctions, Show, timeDelta, true);
+ CarSections[SectionIndex].Groups[0].Elements[ElementIndex].Update(baseCar.baseTrain, baseCar.Index, FrontAxle.Follower.TrackPosition - FrontAxle.Position, p, Direction, Up, Side, updatefunctions, Show, timeDelta, true);
}
}
diff --git a/source/TrainManager/Car/CarBase.cs b/source/TrainManager/Car/CarBase.cs
index 48679f5924..afa58d44f0 100644
--- a/source/TrainManager/Car/CarBase.cs
+++ b/source/TrainManager/Car/CarBase.cs
@@ -15,6 +15,7 @@
using TrainManager.BrakeSystems;
using TrainManager.Car.Systems;
using TrainManager.Cargo;
+using TrainManager.Handles;
using TrainManager.Power;
using TrainManager.Trains;
@@ -36,7 +37,7 @@ public class CarBase : AbstractCar
/// The horns attached to this car
public Horn[] Horns;
/// Contains the physics properties for the car
- public CarPhysics Specs;
+ public readonly CarPhysics Specs;
/// The car brake for this car
public CarBrake CarBrake;
/// The car sections (objects) attached to the car
@@ -86,20 +87,22 @@ public class CarBase : AbstractCar
/// The cargo carried by the car
public CargoBase Cargo;
+ private int trainCarIndex;
+
public CarBase(TrainBase train, int index, double CoefficientOfFriction, double CoefficientOfRollingResistance, double AerodynamicDragCoefficient)
{
Specs = new CarPhysics();
Brightness = new Brightness(this);
baseTrain = train;
- Index = index;
+ trainCarIndex = index;
CarSections = new CarSection[] { };
FrontAxle = new Axle(TrainManagerBase.currentHost, train, this, CoefficientOfFriction, CoefficientOfRollingResistance, AerodynamicDragCoefficient);
FrontAxle.Follower.TriggerType = index == 0 ? EventTriggerType.FrontCarFrontAxle : EventTriggerType.OtherCarFrontAxle;
RearAxle = new Axle(TrainManagerBase.currentHost, train, this, CoefficientOfFriction, CoefficientOfRollingResistance, AerodynamicDragCoefficient);
RearAxle.Follower.TriggerType = index == baseTrain.Cars.Length - 1 ? EventTriggerType.RearCarRearAxle : EventTriggerType.OtherCarRearAxle;
BeaconReceiver = new TrackFollower(TrainManagerBase.currentHost, train);
- FrontBogie = new Bogie(train, this, false);
- RearBogie = new Bogie(train, this, true);
+ FrontBogie = new Bogie(this, false);
+ RearBogie = new Bogie(this, true);
Doors = new Door[2];
Horns = new[]
{
@@ -118,13 +121,13 @@ public CarBase(TrainBase train, int index, double CoefficientOfFriction, double
public CarBase(TrainBase train, int index)
{
baseTrain = train;
- Index = index;
+ trainCarIndex = index;
CarSections = new CarSection[] { };
FrontAxle = new Axle(TrainManagerBase.currentHost, train, this);
RearAxle = new Axle(TrainManagerBase.currentHost, train, this);
BeaconReceiver = new TrackFollower(TrainManagerBase.currentHost, train);
- FrontBogie = new Bogie(train, this, false);
- RearBogie = new Bogie(train, this, true);
+ FrontBogie = new Bogie(this, false);
+ RearBogie = new Bogie(this, true);
Doors = new Door[2];
Horns = new[]
{
@@ -134,6 +137,7 @@ public CarBase(TrainBase train, int index)
};
Brightness = new Brightness(this);
Cargo = new Passengers(this);
+ Specs = new CarPhysics();
}
/// Moves the car
@@ -239,7 +243,8 @@ public override void CreateWorldCoordinates(Vector3 Car, out Vector3 Position, o
/// Backing property for the index of the car within the train
public override int Index
{
- get;
+ get => trainCarIndex;
+ set => trainCarIndex = value;
}
public override void Reverse(bool flipInterior = false)
@@ -347,6 +352,120 @@ public override void OpenDoors(bool Left, bool Right)
}
}
+ public override void Uncouple(bool Front, bool Rear)
+ {
+ if (!Front && !Rear)
+ {
+ return;
+ }
+ // Create new train
+ TrainBase newTrain = new TrainBase(TrainState.Available);
+ newTrain.Handles.Power = new PowerHandle(0, 0, new double[0], new double[0], newTrain)
+ {
+ DelayedChanges = new HandleChange[0]
+ };
+ newTrain.Handles.Brake = new BrakeHandle(0, 0, newTrain.Handles.EmergencyBrake, new double[0], new double[0], newTrain)
+ {
+ DelayedChanges = new HandleChange[0]
+ };
+ newTrain.Handles.HoldBrake = new HoldBrakeHandle(newTrain);
+ if (Front)
+ {
+ int totalPreceedingCars = trainCarIndex;
+ newTrain.Cars = new CarBase[trainCarIndex];
+ for (int i = 0; i < totalPreceedingCars; i++)
+ {
+ newTrain.Cars[i] = baseTrain.Cars[i];
+ }
+
+ for (int i = totalPreceedingCars; i < baseTrain.Cars.Length; i++)
+ {
+ baseTrain.Cars[i - totalPreceedingCars] = baseTrain.Cars[i];
+ baseTrain.Cars[i].Index = i - totalPreceedingCars;
+ }
+ Array.Resize(ref baseTrain.Cars, baseTrain.Cars.Length - totalPreceedingCars);
+ TrainManagerBase.currentHost.AddTrain(baseTrain, newTrain, false);
+
+ if (baseTrain.DriverCar - totalPreceedingCars >= 0)
+ {
+ baseTrain.DriverCar -= totalPreceedingCars;
+ }
+ }
+
+ if (Rear)
+ {
+ int totalFollowingCars = baseTrain.Cars.Length - (Index + 1);
+ if (totalFollowingCars > 0)
+ {
+ newTrain.Cars = new CarBase[totalFollowingCars];
+ // Move following cars to new train
+ for (int i = 0; i < totalFollowingCars; i++)
+ {
+ newTrain.Cars[i] = baseTrain.Cars[Index + i + 1];
+ newTrain.Cars[i].baseTrain = newTrain;
+ newTrain.Cars[i].Index = i;
+ }
+ for (int i = 0; i < newTrain.Cars.Length; i++)
+ {
+ /*
+ * Make visible if not part of player train
+ * Otherwise uncoupling from cab then changing to exterior, they will still be hidden
+ *
+ * Need to do this after everything has been done in case objects refer to other bits
+ */
+ newTrain.Cars[i].ChangeCarSection(CarSectionType.Exterior);
+ newTrain.Cars[i].FrontBogie.ChangeSection(0);
+ newTrain.Cars[i].RearBogie.ChangeSection(0);
+ newTrain.Cars[i].Coupler.ChangeSection(0);
+ }
+ Array.Resize(ref baseTrain.Cars, baseTrain.Cars.Length - totalFollowingCars);
+ baseTrain.Cars[baseTrain.Cars.Length - 1].Coupler.connectedCar = baseTrain.Cars[baseTrain.Cars.Length - 1];
+ }
+ else
+ {
+ return;
+ }
+ Coupler.UncoupleSound.Play(this, false);
+ TrainManagerBase.currentHost.AddTrain(baseTrain, newTrain, true);
+ }
+
+ if (baseTrain.DriverCar >= baseTrain.Cars.Length)
+ {
+ /*
+ * The driver car is no longer in the train
+ *
+ * Look for a car with an interior view to substitute
+ * If not found, this will stop at Car 0
+ */
+
+ for (int i = baseTrain.Cars.Length; i > 0; i--)
+ {
+ baseTrain.DriverCar = i - 1;
+ if (!baseTrain.Cars[i - 1].HasInteriorView)
+ {
+ /*
+ * Set the eye position to something vaguely sensible, rather than leaving it on the rails
+ * Whilst there will be no cab, at least it's a bit more usable like this
+ */
+ baseTrain.Cars[i - 1].InteriorCamera = new CameraAlignment()
+ {
+ Position = new Vector3(0, 2, 0.5 * Length)
+ };
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ if (baseTrain.CameraCar >= baseTrain.Cars.Length)
+ {
+ baseTrain.CameraCar = baseTrain.DriverCar;
+ }
+
+ }
+
/// Returns the combination of door states what encountered at the specified car in a train.
/// Whether to include left doors.
/// Whether to include right doors.
diff --git a/source/TrainManager/Car/Coupler/Coupler.cs b/source/TrainManager/Car/Coupler/Coupler.cs
index 6e0914ef83..40a1b567d6 100644
--- a/source/TrainManager/Car/Coupler/Coupler.cs
+++ b/source/TrainManager/Car/Coupler/Coupler.cs
@@ -3,6 +3,7 @@
using OpenBveApi.Math;
using OpenBveApi.Objects;
using OpenBveApi.Trains;
+using SoundManager;
namespace TrainManager.Car
{
@@ -22,6 +23,9 @@ public class Coupler : AbstractCoupler
/// This is the REAR car when travelling in the notional forwards direction
internal CarBase connectedCar;
+ /// The sound played when this coupler is uncoupled
+ public CarSound UncoupleSound;
+
internal AbstractTrain baseTrain;
public Coupler(double minimumDistance, double maximumDistance, CarBase frontCar, CarBase rearCar, AbstractTrain train)
@@ -34,6 +38,7 @@ public Coupler(double minimumDistance, double maximumDistance, CarBase frontCar,
CarSections = new CarSection[] { };
baseTrain = train;
ChangeSection(-1);
+ UncoupleSound = new CarSound();
}
public void UpdateObjects(double TimeElapsed, bool ForceUpdate)
diff --git a/source/TrainManager/Train/TrainBase.cs b/source/TrainManager/Train/TrainBase.cs
index 30c5e020f8..bce8ab3547 100644
--- a/source/TrainManager/Train/TrainBase.cs
+++ b/source/TrainManager/Train/TrainBase.cs
@@ -110,6 +110,8 @@ public TrainBase(TrainState state)
Specs.DoorOpenMode = DoorMode.AutomaticManualOverride;
Specs.DoorCloseMode = DoorMode.AutomaticManualOverride;
DriverBody = new DriverBody(this);
+ Handles.Reverser = new ReverserHandle(this);
+ Handles.EmergencyBrake = new EmergencyHandle(this);
}
/// Called once when the simulation loads to initalize the train
@@ -514,8 +516,7 @@ private void UpdatePhysicsAndControls(double TimeElapsed)
{
breaker = Handles.Reverser.Actual != 0 & Handles.Power.Safety >= 1 & Handles.Brake.Safety == 0 & !Handles.EmergencyBrake.Safety & !Handles.HoldBrake.Actual;
}
-
- Cars[DriverCar].Breaker.Update(breaker);
+ Cars[DriverCar].Breaker?.Update(breaker);
}
// signals
if (CurrentSectionLimit == 0.0)