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;
}
}
}
}
}