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