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)