diff --git a/src/Devices/AtcDimetronic.cs b/src/Devices/AtcDimetronic.cs
new file mode 100644
index 0000000..3f834f8
--- /dev/null
+++ b/src/Devices/AtcDimetronic.cs
@@ -0,0 +1,410 @@
+using OpenBveApi.Runtime;
+
+namespace OpenbveFcmbTrainPlugin
+{
+ /// A device simulating ATC by Dimetronic.
+ internal class AtcDimetronic : Device
+ {
+ /// Represents the state of the device.
+ private enum DeviceStates
+ {
+ /// The device is being overriden.
+ Override,
+ /// The device has been initialized and no driving mode is selected.
+ NoMode,
+ /// The device is in YARD (M+25) driving mode.
+ YARD,
+ /// The device is in ATP (M+ATP) driving mode.
+ ATP,
+ /// The device is in ATO mode.
+ ATO,
+ }
+
+ /// The current state of the device.
+ private DeviceStates DeviceState;
+
+ /// Represents the state of the ATC control.
+ private enum AtcControlStates
+ {
+ /// The brakes are released.
+ Released,
+ /// The power is cut.
+ NoPower,
+ /// The emergency brake is applied.
+ BrakeEmergency,
+ }
+
+ /// The current state of the ATC control.
+ private AtcControlStates AtcControlState;
+
+ /// Represents the state of the track.
+ private enum TrackStates
+ {
+ Unprotected,
+ Enable,
+ ATC,
+ Disable
+ }
+
+ /// The current state of the track.
+ private TrackStates TrackState;
+
+ /// The maximum speed in YARD mode, in km/h.
+ private double YardMaximumSpeed;
+
+ /// Represents an ATC speed code.
+ private class SpeedCode
+ {
+ internal Speed CurrentLimit { get; private set; }
+ internal Speed WarningOn { get; private set; }
+ internal Speed WarningOff { get; private set; }
+ internal Speed TargetLimit { get; private set; }
+
+ internal SpeedCode(Speed limit, Speed target)
+ {
+ CurrentLimit = limit;
+ TargetLimit = target;
+ if (limit == target)
+ {
+ // Constant speed
+ WarningOn = new Speed(limit.MetersPerSecond - 3 / 3.6);
+ WarningOff = new Speed(limit.MetersPerSecond - 5 / 3.6);
+ }
+ else
+ {
+ // Reduce speed
+ WarningOn = target;
+ WarningOff = new Speed(target.MetersPerSecond - 2 / 3.6);
+ }
+ }
+ }
+
+ /// The current speed code received by the train.
+ private SpeedCode CurrentSpeedCode;
+
+ /// The ideal power notch for the AI.
+ private int AiPowerNotch;
+
+ /// The ideal brake notch for the AI.
+ private int AiBrakeNotch;
+
+ /// Creates an instance of the Bombardier ATC device.
+ /// The maximum speed in YARD mode, in km/h.
+ internal AtcDimetronic(double yardMaxSpeed)
+ {
+ YardMaximumSpeed = yardMaxSpeed;
+ }
+
+ /// Is called when the device state should be updated.
+ /// The current train.
+ /// The current route.
+ /// Whether the device should initialize.
+ /// The time elapsed since the previous call.
+ internal override void Update(Train train, Route route, bool init, Time elapsedTime)
+ {
+ if (init)
+ {
+ // Initialize device on start
+ DeviceState = DeviceStates.NoMode;
+ }
+
+ // Update ATC movement permission
+ UpdateSpeedCode();
+
+ double speed = train.State.Speed.KilometersPerHour;
+ bool stopped = speed < 0.05;
+
+ // Speed limit enforcement
+ if (DeviceState == DeviceStates.YARD || DeviceState == DeviceStates.ATP || DeviceState == DeviceStates.ATO)
+ {
+ if (AtcControlState == AtcControlStates.Released && speed > CurrentSpeedCode.WarningOn.KilometersPerHour)
+ {
+ // Cut power above warning on threshold
+ AtcControlState = AtcControlStates.NoPower;
+ }
+ if (AtcControlState == AtcControlStates.NoPower && speed < CurrentSpeedCode.WarningOff.KilometersPerHour)
+ {
+ // Stop cutting power below warning off threshold
+ AtcControlState = AtcControlStates.Released;
+ }
+ if (speed > CurrentSpeedCode.CurrentLimit.KilometersPerHour)
+ {
+ // Unselect driving mode to apply emergency brake if overspeeding
+ DeviceState = DeviceStates.NoMode;
+ }
+ }
+
+ // Brake application
+ switch (AtcControlState)
+ {
+ case AtcControlStates.Released:
+ RequestedBrakeNotch = -1;
+ RequestedPowerNotch = -1;
+ break;
+ case AtcControlStates.NoPower:
+ RequestedBrakeNotch = -1;
+ RequestedPowerNotch = 0;
+ break;
+ case AtcControlStates.BrakeEmergency:
+ RequestedBrakeNotch = train.Specs.BrakeNotches + 1;
+ RequestedPowerNotch = 0;
+ break;
+ }
+
+ switch (DeviceState)
+ {
+ // ATC device is disabled (also called "Special Mode")
+ case DeviceStates.Override:
+ train.ContinuousProtection = false;
+ train.VigilanceOverride = false;
+ // Release control of the brakes and power
+ AtcControlState = AtcControlStates.Released;
+ break;
+ // ATC device is initialized and a driving mode is required
+ case DeviceStates.NoMode:
+ train.ContinuousProtection = false;
+ train.VigilanceOverride = false;
+ // Apply service/emergency brake while no driving mode is selected
+ AtcControlState = AtcControlStates.BrakeEmergency;
+ // ATC movement permission
+ CurrentSpeedCode = new SpeedCode(new Speed(0), new Speed(0));
+ break;
+ // ATC device is in YARD (M+25) driving mode
+ case DeviceStates.YARD:
+ train.ContinuousProtection = false;
+ train.VigilanceOverride = false;
+ // Apply brake if any door opens
+ if (train.DoorState != DoorStates.None)
+ {
+ RequestedBrakeNotch = stopped ? train.Specs.BrakeNotches + 1 : train.ServiceBrakeNotch;
+ }
+ // If the train is not moving, brakes are released
+ if (stopped)
+ {
+ AtcControlState = AtcControlStates.Released;
+ }
+ break;
+ // ATC device is in ATP (M+ATP) driving mode
+ case DeviceStates.ATP:
+ train.ContinuousProtection = true;
+ train.VigilanceOverride = false;
+ break;
+ // ATC device is in ATO driving mode
+ case DeviceStates.ATO:
+ train.ContinuousProtection = true;
+ train.VigilanceOverride = true;
+ break;
+ }
+
+ // Panel indicators
+ train.Panel[25] = DeviceState == DeviceStates.Override ? 1 : 0;
+ train.Panel[26] = DeviceState == DeviceStates.YARD ? 1 : 0;
+ train.Panel[27] = DeviceState == DeviceStates.ATP ? 1 : 0;
+ train.Panel[34] = (int)CurrentSpeedCode.CurrentLimit.KilometersPerHour * 1000;
+ }
+
+
+ /// Is called when the state of a key changes.
+ /// The key.
+ /// Whether the key is pressed or released.
+ /// The current train.
+ internal override void KeyChange(VirtualKeys key, bool pressed, Train train)
+ {
+ double speed = train.State.Speed.KilometersPerHour;
+ bool stopped = speed < 0.05;
+
+ if (pressed)
+ {
+ switch (key)
+ {
+ case VirtualKeys.I:
+ // YARD (M+25) mode selection button
+ if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
+ {
+ if (DeviceState == DeviceStates.NoMode && stopped)
+ {
+ DeviceState = DeviceStates.YARD;
+ }
+ }
+ break;
+ case VirtualKeys.J:
+ // ATP (M+ATP) mode selection button
+ if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
+ {
+ }
+ break;
+ case VirtualKeys.K:
+ // ATO mode selection button
+ if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
+ {
+ }
+ break;
+ case VirtualKeys.L:
+ // ATO start button
+ if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
+ {
+ }
+ break;
+ case VirtualKeys.B1:
+ // ATC alarm acknowledge
+ if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
+ {
+ }
+ break;
+ case VirtualKeys.C1:
+ // Override toggle
+ if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
+ {
+ switch (DeviceState)
+ {
+ case DeviceStates.NoMode:
+ case DeviceStates.YARD:
+ // Override device if possible
+ if (stopped && train.PhysicalHandles.PowerNotch == 0)
+ {
+ DeviceState = DeviceStates.Override;
+ }
+ break;
+ case DeviceStates.Override:
+ // Disable override if possible
+ if (stopped && train.PhysicalHandles.PowerNotch == 0)
+ {
+ DeviceState = DeviceStates.NoMode;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ /// Is called to inform about signals.
+ /// The signal data.
+ internal override void SetSignal(SignalData[] signal)
+ {
+
+ }
+
+ /// Is called when a beacon is passed.
+ /// The beacon data.
+ internal override void SetBeacon(BeaconData beacon)
+ {
+ int type = beacon.Type;
+ switch (type)
+ {
+ case -16777215:
+ {
+ if (!(beacon.Optional >= 0 & beacon.Optional <= 3))
+ {
+ break;
+ }
+ TrackState = (TrackStates)beacon.Optional;
+ return;
+ }
+ }
+ }
+
+ /// Is called when the device should perform the AI.
+ /// The AI data.
+ /// The current train.
+ /// The current route.
+ internal override void PerformAI(AIData data, Train train, Route route)
+ {
+ // Set AI notches to what the AI is doing right now
+ AiBrakeNotch = data.Handles.BrakeNotch;
+ AiPowerNotch = data.Handles.PowerNotch;
+
+ double speed = train.State.Speed.KilometersPerHour;
+ bool stopped = speed < 0.05;
+
+ switch (DeviceState)
+ {
+ case DeviceStates.NoMode:
+ // Apply brake
+ data.Handles.BrakeNotch = train.Specs.BrakeNotches;
+ data.Response = AIResponse.Short;
+ // If the train is stopped, select YARD (M+25) mode
+ if (stopped)
+ {
+ KeyChange(VirtualKeys.I, true, train);
+ data.Response = AIResponse.Short;
+ KeyChange(VirtualKeys.I, false, train);
+ }
+ break;
+ case DeviceStates.YARD:
+ case DeviceStates.ATP:
+ // Calculate ideal notch for AI when approaching the speed limit
+ if (speed > CurrentSpeedCode.WarningOn.KilometersPerHour - 2 && train.Acceleration > 0.1)
+ {
+ if (data.Handles.PowerNotch > 0)
+ {
+ AiPowerNotch -= 1;
+ }
+ else if (train.Specs.HasHoldBrake)
+ {
+ data.Handles.HoldBrake = true;
+ }
+ else
+ {
+ AiBrakeNotch += 1;
+ }
+ }
+ else if (speed < CurrentSpeedCode.WarningOff.KilometersPerHour - 2 && train.Acceleration < 0.25)
+ {
+ if (data.Handles.BrakeNotch > 0)
+ {
+ AiBrakeNotch -= 1;
+ }
+ else
+ {
+ AiPowerNotch += 1;
+ }
+ }
+ // If still far from next station, take control of AI handles
+ if (speed / (route.CurrentStation.StopPosition - train.State.Location) < 0.2)
+ {
+ if (speed > CurrentSpeedCode.CurrentLimit.KilometersPerHour - 5)
+ {
+ data.Handles.PowerNotch = AiPowerNotch;
+ data.Handles.BrakeNotch = AiBrakeNotch;
+ data.Response = AIResponse.Medium;
+ }
+ }
+ break;
+ }
+ }
+
+ /// Updates the data for the current speed code.
+ internal void UpdateSpeedCode()
+ {
+ Speed zero = new Speed(0);
+ Speed yard = new Speed(YardMaximumSpeed / 3.6);
+
+ switch (DeviceState)
+ {
+ case DeviceStates.Override:
+ case DeviceStates.NoMode:
+ if (CurrentSpeedCode == null || CurrentSpeedCode.CurrentLimit != zero || CurrentSpeedCode.TargetLimit != zero)
+ {
+ CurrentSpeedCode = new SpeedCode(zero, zero);
+ }
+ break;
+ case DeviceStates.YARD:
+ if (CurrentSpeedCode == null || CurrentSpeedCode.CurrentLimit != yard || CurrentSpeedCode.TargetLimit != yard)
+ {
+ CurrentSpeedCode = new SpeedCode(yard, yard);
+ }
+ break;
+ case DeviceStates.ATP:
+ case DeviceStates.ATO:
+ if (CurrentSpeedCode == null || CurrentSpeedCode.CurrentLimit != zero || CurrentSpeedCode.TargetLimit != zero)
+ {
+ CurrentSpeedCode = new SpeedCode(zero, zero);
+ }
+ break;
+ }
+ }
+
+ }
+}
diff --git a/src/OpenbveFcmbTrainPlugin.cs b/src/OpenbveFcmbTrainPlugin.cs
index a0ded43..218f572 100644
--- a/src/OpenbveFcmbTrainPlugin.cs
+++ b/src/OpenbveFcmbTrainPlugin.cs
@@ -118,6 +118,7 @@ namespace OpenbveFcmbTrainPlugin
/// The signal array is guaranteed to have at least one element. When accessing elements other than index 0, you must check the bounds of the array first.
public void SetSignal(SignalData[] signal)
{
+ Train.SetSignal(signal);
}
/// Is called when the train passes a beacon.
diff --git a/src/OpenbveFcmbTrainPlugin.csproj b/src/OpenbveFcmbTrainPlugin.csproj
index c15573a..b9f9dea 100644
--- a/src/OpenbveFcmbTrainPlugin.csproj
+++ b/src/OpenbveFcmbTrainPlugin.csproj
@@ -45,6 +45,7 @@
+
diff --git a/src/Train/Train.cs b/src/Train/Train.cs
index 1809cf7..08f4560 100644
--- a/src/Train/Train.cs
+++ b/src/Train/Train.cs
@@ -69,7 +69,8 @@ namespace OpenbveFcmbTrainPlugin
Devices.Add(new Doors(false, 5, 15, 10));
Devices.Add(new Deadman(5.0, false));
Devices.Add(new TrainStop());
- Devices.Add(new AtcBombardier(30, 60, 20, 500));
+ //Devices.Add(new AtcBombardier(30, 60, 20, 500));
+ Devices.Add(new AtcDimetronic(25));
}
/// Is called when the train should initialize.
@@ -183,7 +184,13 @@ namespace OpenbveFcmbTrainPlugin
/// Is called to inform about signals.
/// The signal data.
- internal void SetSignal(SignalData[] signal) { }
+ internal void SetSignal(SignalData[] signal)
+ {
+ foreach (Device dev in Devices)
+ {
+ dev.SetSignal(signal);
+ }
+ }
/// Is called when a beacon is passed.
/// The beacon data.