1 using System; 2 using OpenBveApi.Runtime; 3 4 namespace Plugin { 5 /// <summary>Represents ATS-Sx.</summary> 6 internal class AtsSx : Device { 7 8 // --- enumerations --- 9 10 /// <summary>Represents different states of ATS-Sx.</summary> 11 internal enum States { 12 /// <summary>The system is disabled.</summary> 13 Disabled = 0, 14 /// <summary>The system is enabled, but currently suppressed. This will change to States.Initializing once the emergency brakes are released.</summary> 15 Suppressed = 1, 16 /// <summary>The system is initializing. This will change to States.Chime once the initialization is complete.</summary> 17 Initializing = 2, 18 /// <summary>The chime is ringing.</summary> 19 Chime = 3, 20 /// <summary>The system is operating normally.</summary> 21 Normal = 4, 22 /// <summary>The alarm is ringing. This will change to States.Emergency once the countdown runs out.</summary> 23 Alarm = 5, 24 /// <summary>The system applies the emergency brakes.</summary> 25 Emergency = 6 26 } 27 28 29 // --- members --- 30 31 /// <summary>The underlying train.</summary> 32 private readonly Train Train; 33 34 /// <summary>The current state of the system.</summary> 35 internal States State; 36 37 /// <summary>The current alarm countdown.</summary> 38 /// <remarks>With States.Initializing, this counts down until the initialization is complete.</remarks> 39 /// <remarks>With States.Alarm, this counts down until the emergency brakes are engaged.</remarks> 40 private double AlarmCountdown; 41 42 /// <summary>The current speed check countdown.</summary> 43 private double SpeedCheckCountdown; 44 45 /// <summary>The distance traveled since the last IIYAMA-style Ps2 beacon.</summary> 46 private double CompatibilityDistanceAccumulator; 47 48 /// <summary>The location of the farthest known red signal, or System.Double.MinValue.</summary> 49 internal double RedSignalLocation; 50 51 52 // --- parameters --- 53 54 /// <summary>The duration of the alarm until the emergency brakes are applied.</summary> 55 internal readonly double DurationOfAlarm = 5.0; 56 57 /// <summary>The duration of the initialization process.</summary> 58 internal readonly double DurationOfInitialization = 3.0; 59 60 // --- constructors --- 61 62 /// <summary>Creates a new instance of this system with default parameters.</summary> 63 /// <param name="train">The train.</param> AtsSx(Train train)64 internal AtsSx(Train train) { 65 this.Train = train; 66 this.State = States.Disabled; 67 this.AlarmCountdown = 0.0; 68 this.SpeedCheckCountdown = 0.0; 69 this.RedSignalLocation = 0.0; 70 } 71 72 73 // --- inherited functions --- 74 75 /// <summary>Is called when the system should initialize.</summary> 76 /// <param name="mode">The initialization mode.</param> Initialize(InitializationModes mode)77 internal override void Initialize(InitializationModes mode) 78 { 79 this.State = mode == InitializationModes.OffEmergency ? States.Suppressed : States.Normal; 80 } 81 82 /// <summary>Is called every frame.</summary> 83 /// <param name="data">The data.</param> 84 /// <param name="blocking">Whether the device is blocked or will block subsequent devices.</param> Elapse(ElapseData data, ref bool blocking)85 internal override void Elapse(ElapseData data, ref bool blocking) { 86 // --- behavior --- 87 if (this.State == States.Suppressed) { 88 if (data.Handles.BrakeNotch <= this.Train.Specs.BrakeNotches) { 89 this.AlarmCountdown = DurationOfInitialization; 90 this.State = States.Initializing; 91 } 92 } 93 if (this.State == States.Initializing) { 94 this.AlarmCountdown -= data.ElapsedTime.Seconds; 95 if (this.AlarmCountdown <= 0.0) { 96 this.State = States.Chime; 97 } else { 98 data.Handles.BrakeNotch = this.Train.Specs.BrakeNotches + 1; 99 this.Train.Sounds.AtsBell.Play(); 100 } 101 } 102 if (blocking) { 103 if (this.State != States.Disabled & this.State != States.Suppressed) { 104 this.State = States.Normal; 105 } 106 } else { 107 if (this.State == States.Chime) { 108 this.Train.Sounds.AtsChime.Play(); 109 } else if (this.State == States.Alarm) { 110 this.Train.Sounds.AtsBell.Play(); 111 this.AlarmCountdown -= data.ElapsedTime.Seconds; 112 if (this.AlarmCountdown <= 0.0) { 113 this.State = States.Emergency; 114 } 115 } else if (this.State == States.Emergency) { 116 this.Train.Sounds.AtsBell.Play(); 117 data.Handles.BrakeNotch = this.Train.Specs.BrakeNotches + 1; 118 } 119 if (this.SpeedCheckCountdown > 0.0 & data.ElapsedTime.Seconds > 0.0) { 120 this.SpeedCheckCountdown -= data.ElapsedTime.Seconds; 121 } 122 if (this.CompatibilityDistanceAccumulator != 0.0) { 123 this.CompatibilityDistanceAccumulator += data.Vehicle.Speed.MetersPerSecond * data.ElapsedTime.Seconds; 124 if (this.CompatibilityDistanceAccumulator > 27.7) { 125 this.CompatibilityDistanceAccumulator = 0.0; 126 } 127 } 128 if (this.State != States.Disabled & (this.Train.Doors != DoorStates.None | data.Handles.BrakeNotch > 0)) { 129 data.Handles.PowerNotch = 0; 130 } 131 } 132 // --- panel --- 133 if ((this.State == States.Chime | this.State == States.Normal) & !blocking) { 134 this.Train.Panel[256] = 1; 135 } 136 if (this.State == States.Initializing | this.State == States.Alarm) { 137 this.Train.Panel[257] = 1; 138 this.Train.Panel[258] = 1; 139 } else if (this.State == States.Emergency) { 140 int value = (int)data.TotalTime.Milliseconds % 1000 < 500 ? 1 : 0; 141 this.Train.Panel[257] = 2; 142 this.Train.Panel[258] = value; 143 } 144 } 145 146 /// <summary>Is called when the driver changes the reverser.</summary> 147 /// <param name="reverser">The new reverser position.</param> SetReverser(int reverser)148 internal override void SetReverser(int reverser) { 149 } 150 151 /// <summary>Is called when the driver changes the power notch.</summary> 152 /// <param name="powerNotch">The new power notch.</param> SetPower(int powerNotch)153 internal override void SetPower(int powerNotch) { 154 } 155 156 /// <summary>Is called when the driver changes the brake notch.</summary> 157 /// <param name="brakeNotch">The new brake notch.</param> SetBrake(int brakeNotch)158 internal override void SetBrake(int brakeNotch) { 159 } 160 161 /// <summary>Is called when a key is pressed.</summary> 162 /// <param name="key">The key.</param> KeyDown(VirtualKeys key)163 internal override void KeyDown(VirtualKeys key) { 164 switch (key) { 165 case VirtualKeys.S: 166 // --- acknowledge the alarm --- 167 if (this.State == States.Alarm & this.Train.Handles.PowerNotch == 0 & this.Train.Handles.BrakeNotch >= this.Train.Specs.AtsNotch) { 168 this.State = States.Chime; 169 } 170 break; 171 case VirtualKeys.A1: 172 // --- stop the chime --- 173 if (this.State == States.Chime) { 174 this.State = States.Normal; 175 } 176 break; 177 case VirtualKeys.B1: 178 // --- reset the system --- 179 if (this.State == States.Emergency & this.Train.Handles.Reverser == 0 & this.Train.Handles.PowerNotch == 0 & this.Train.Handles.BrakeNotch == this.Train.Specs.BrakeNotches + 1) { 180 this.State = States.Chime; 181 } 182 break; 183 } 184 } 185 186 /// <summary>Is called when a key is released.</summary> 187 /// <param name="key">The key.</param> KeyUp(VirtualKeys key)188 internal override void KeyUp(VirtualKeys key) { 189 } 190 191 /// <summary>Is called when a horn is played or when the music horn is stopped.</summary> 192 /// <param name="type">The type of horn.</param> HornBlow(HornTypes type)193 internal override void HornBlow(HornTypes type) { 194 } 195 196 /// <summary>Is called to inform about signals.</summary> 197 /// <param name="signal">The signal data.</param> SetSignal(SignalData[] signal)198 internal override void SetSignal(SignalData[] signal) { 199 if (this.RedSignalLocation != double.MinValue) { 200 for (int i = 0; i < signal.Length; i++) { 201 const double visibility = 200.0; 202 if (signal[i].Distance < visibility) { 203 double location = this.Train.State.Location + signal[i].Distance; 204 if (Math.Abs(location - this.RedSignalLocation) < 50.0) { 205 if (signal[i].Aspect != 0) { 206 this.RedSignalLocation = double.MinValue; 207 } 208 } 209 } 210 } 211 } 212 } 213 214 /// <summary>Is called when a beacon is passed.</summary> 215 /// <param name="beacon">The beacon data.</param> SetBeacon(BeaconData beacon)216 internal override void SetBeacon(BeaconData beacon) { 217 if (this.State != States.Disabled & this.State != States.Initializing) { 218 switch (beacon.Type) { 219 case 0: 220 // --- Sx long --- 221 if (beacon.Signal.Aspect == 0) { 222 if (this.State == States.Chime | this.State == States.Normal) { 223 this.AlarmCountdown = DurationOfAlarm; 224 this.State = States.Alarm; 225 UpdateRedSignalLocation(beacon); 226 } 227 } 228 break; 229 case 1: 230 // --- Sx immediate stop --- 231 if (beacon.Signal.Aspect == 0) { 232 if (this.State == States.Chime | this.State == States.Normal | this.State == States.Alarm) { 233 this.State = States.Emergency; 234 } 235 } 236 break; 237 case 2: 238 // --- accidental departure --- 239 if (beacon.Signal.Aspect == 0 & (beacon.Optional == 0 | beacon.Optional >= this.Train.Specs.Cars)) { 240 if (this.State == States.Chime | this.State == States.Normal | this.State == States.Alarm) { 241 this.State = States.Emergency; 242 } 243 } 244 break; 245 } 246 } 247 } 248 249 250 // --- private functions --- 251 252 /// <summary>Updates the location of the farthest known red signal from the specified beacon.</summary> 253 /// <param name="beacon">The beacon that holds the distance to a known red signal.</param> UpdateRedSignalLocation(BeaconData beacon)254 private void UpdateRedSignalLocation(BeaconData beacon) { 255 if (beacon.Signal.Distance < 1200.0) { 256 double signalLocation = this.Train.State.Location + beacon.Signal.Distance; 257 if (signalLocation > this.RedSignalLocation) { 258 this.RedSignalLocation = signalLocation; 259 } 260 } 261 } 262 263 } 264 } 265