using System; using OpenBveApi.Runtime; namespace Plugin { /// Represents ATS-Sx. internal class AtsSx : Device { // --- enumerations --- /// Represents different states of ATS-Sx. internal enum States { /// The system is disabled. Disabled = 0, /// The system is enabled, but currently suppressed. This will change to States.Initializing once the emergency brakes are released. Suppressed = 1, /// The system is initializing. This will change to States.Chime once the initialization is complete. Initializing = 2, /// The chime is ringing. Chime = 3, /// The system is operating normally. Normal = 4, /// The alarm is ringing. This will change to States.Emergency once the countdown runs out. Alarm = 5, /// The system applies the emergency brakes. Emergency = 6 } // --- members --- /// The underlying train. private readonly Train Train; /// The current state of the system. internal States State; /// The current alarm countdown. /// With States.Initializing, this counts down until the initialization is complete. /// With States.Alarm, this counts down until the emergency brakes are engaged. private double AlarmCountdown; /// The current speed check countdown. private double SpeedCheckCountdown; /// The distance traveled since the last IIYAMA-style Ps2 beacon. private double CompatibilityDistanceAccumulator; /// The location of the farthest known red signal, or System.Double.MinValue. internal double RedSignalLocation; // --- parameters --- /// The duration of the alarm until the emergency brakes are applied. internal readonly double DurationOfAlarm = 5.0; /// The duration of the initialization process. internal readonly double DurationOfInitialization = 3.0; // --- constructors --- /// Creates a new instance of this system with default parameters. /// The train. internal AtsSx(Train train) { this.Train = train; this.State = States.Disabled; this.AlarmCountdown = 0.0; this.SpeedCheckCountdown = 0.0; this.RedSignalLocation = 0.0; } // --- inherited functions --- /// Is called when the system should initialize. /// The initialization mode. internal override void Initialize(InitializationModes mode) { this.State = mode == InitializationModes.OffEmergency ? States.Suppressed : States.Normal; } /// Is called every frame. /// The data. /// Whether the device is blocked or will block subsequent devices. internal override void Elapse(ElapseData data, ref bool blocking) { // --- behavior --- if (this.State == States.Suppressed) { if (data.Handles.BrakeNotch <= this.Train.Specs.BrakeNotches) { this.AlarmCountdown = DurationOfInitialization; this.State = States.Initializing; } } if (this.State == States.Initializing) { this.AlarmCountdown -= data.ElapsedTime.Seconds; if (this.AlarmCountdown <= 0.0) { this.State = States.Chime; } else { data.Handles.BrakeNotch = this.Train.Specs.BrakeNotches + 1; this.Train.Sounds.AtsBell.Play(); } } if (blocking) { if (this.State != States.Disabled & this.State != States.Suppressed) { this.State = States.Normal; } } else { if (this.State == States.Chime) { this.Train.Sounds.AtsChime.Play(); } else if (this.State == States.Alarm) { this.Train.Sounds.AtsBell.Play(); this.AlarmCountdown -= data.ElapsedTime.Seconds; if (this.AlarmCountdown <= 0.0) { this.State = States.Emergency; } } else if (this.State == States.Emergency) { this.Train.Sounds.AtsBell.Play(); data.Handles.BrakeNotch = this.Train.Specs.BrakeNotches + 1; } if (this.SpeedCheckCountdown > 0.0 & data.ElapsedTime.Seconds > 0.0) { this.SpeedCheckCountdown -= data.ElapsedTime.Seconds; } if (this.CompatibilityDistanceAccumulator != 0.0) { this.CompatibilityDistanceAccumulator += data.Vehicle.Speed.MetersPerSecond * data.ElapsedTime.Seconds; if (this.CompatibilityDistanceAccumulator > 27.7) { this.CompatibilityDistanceAccumulator = 0.0; } } if (this.State != States.Disabled & (this.Train.Doors != DoorStates.None | data.Handles.BrakeNotch > 0)) { data.Handles.PowerNotch = 0; } } // --- panel --- if ((this.State == States.Chime | this.State == States.Normal) & !blocking) { this.Train.Panel[256] = 1; } if (this.State == States.Initializing | this.State == States.Alarm) { this.Train.Panel[257] = 1; this.Train.Panel[258] = 1; } else if (this.State == States.Emergency) { int value = (int)data.TotalTime.Milliseconds % 1000 < 500 ? 1 : 0; this.Train.Panel[257] = 2; this.Train.Panel[258] = value; } } /// Is called when the driver changes the reverser. /// The new reverser position. internal override void SetReverser(int reverser) { } /// Is called when the driver changes the power notch. /// The new power notch. internal override void SetPower(int powerNotch) { } /// Is called when the driver changes the brake notch. /// The new brake notch. internal override void SetBrake(int brakeNotch) { } /// Is called when a key is pressed. /// The key. internal override void KeyDown(VirtualKeys key) { switch (key) { case VirtualKeys.S: // --- acknowledge the alarm --- if (this.State == States.Alarm & this.Train.Handles.PowerNotch == 0 & this.Train.Handles.BrakeNotch >= this.Train.Specs.AtsNotch) { this.State = States.Chime; } break; case VirtualKeys.A1: // --- stop the chime --- if (this.State == States.Chime) { this.State = States.Normal; } break; case VirtualKeys.B1: // --- reset the system --- if (this.State == States.Emergency & this.Train.Handles.Reverser == 0 & this.Train.Handles.PowerNotch == 0 & this.Train.Handles.BrakeNotch == this.Train.Specs.BrakeNotches + 1) { this.State = States.Chime; } break; } } /// Is called when a key is released. /// The key. internal override void KeyUp(VirtualKeys key) { } /// Is called when a horn is played or when the music horn is stopped. /// The type of horn. internal override void HornBlow(HornTypes type) { } /// Is called to inform about signals. /// The signal data. internal override void SetSignal(SignalData[] signal) { if (this.RedSignalLocation != double.MinValue) { for (int i = 0; i < signal.Length; i++) { const double visibility = 200.0; if (signal[i].Distance < visibility) { double location = this.Train.State.Location + signal[i].Distance; if (Math.Abs(location - this.RedSignalLocation) < 50.0) { if (signal[i].Aspect != 0) { this.RedSignalLocation = double.MinValue; } } } } } } /// Is called when a beacon is passed. /// The beacon data. internal override void SetBeacon(BeaconData beacon) { if (this.State != States.Disabled & this.State != States.Initializing) { switch (beacon.Type) { case 0: // --- Sx long --- if (beacon.Signal.Aspect == 0) { if (this.State == States.Chime | this.State == States.Normal) { this.AlarmCountdown = DurationOfAlarm; this.State = States.Alarm; UpdateRedSignalLocation(beacon); } } break; case 1: // --- Sx immediate stop --- if (beacon.Signal.Aspect == 0) { if (this.State == States.Chime | this.State == States.Normal | this.State == States.Alarm) { this.State = States.Emergency; } } break; case 2: // --- accidental departure --- if (beacon.Signal.Aspect == 0 & (beacon.Optional == 0 | beacon.Optional >= this.Train.Specs.Cars)) { if (this.State == States.Chime | this.State == States.Normal | this.State == States.Alarm) { this.State = States.Emergency; } } break; } } } // --- private functions --- /// Updates the location of the farthest known red signal from the specified beacon. /// The beacon that holds the distance to a known red signal. private void UpdateRedSignalLocation(BeaconData beacon) { if (beacon.Signal.Distance < 1200.0) { double signalLocation = this.Train.State.Location + beacon.Signal.Distance; if (signalLocation > this.RedSignalLocation) { this.RedSignalLocation = signalLocation; } } } } }