630 lines
28 KiB
C#
630 lines
28 KiB
C#
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using OpenBveApi.Runtime;
|
|
|
|
namespace OpenbveFcmbTrainPlugin
|
|
{
|
|
/// <summary>A device simulating ATC by Dimetronic.</summary>
|
|
internal class AtcDimetronic : Device
|
|
{
|
|
/// <summary>Represents the state of the device.</summary>
|
|
private enum DeviceStates
|
|
{
|
|
/// <summary>The device is being overriden.</summary>
|
|
Override,
|
|
/// <summary>The device has been initialized and no driving mode is selected.</summary>
|
|
NoMode,
|
|
/// <summary>The device is in YARD (M+25) driving mode.</summary>
|
|
YARD,
|
|
/// <summary>The device is in ATP (M+ATP) driving mode.</summary>
|
|
ATP,
|
|
/// <summary>The device is in ATO mode.</summary>
|
|
ATO,
|
|
}
|
|
|
|
/// <summary>The current state of the device.</summary>
|
|
private DeviceStates DeviceState;
|
|
|
|
/// <summary>Represents the state of the ATC control.</summary>
|
|
private enum AtcControlStates
|
|
{
|
|
/// <summary>The brakes are released.</summary>
|
|
Released,
|
|
/// <summary>The power is cut.</summary>
|
|
NoPower,
|
|
/// <summary>The power is cut and an alarm plays.</summary>
|
|
NoPowerAlarm,
|
|
/// <summary>The emergency brake is applied.</summary>
|
|
BrakeEmergency,
|
|
}
|
|
|
|
/// <summary>The current state of the ATC control.</summary>
|
|
private AtcControlStates AtcControlState;
|
|
|
|
/// <summary>Represents the state of the track.</summary>
|
|
private enum TrackStates
|
|
{
|
|
Unprotected,
|
|
Enable,
|
|
ATC,
|
|
Disable
|
|
}
|
|
|
|
/// <summary>The current state of the track.</summary>
|
|
private TrackStates TrackState;
|
|
|
|
/// <summary>The current position of the train.</summary>
|
|
private double TrainLocation;
|
|
|
|
/// <summary>The position of the train used to detect runback.</summary>
|
|
private double RunbackLocation;
|
|
|
|
/// <summary>The position of the train used to detect rollforward.</summary>
|
|
private double RollforwardLocation;
|
|
|
|
/// <summary>Whether rollforward should be detected or not.</summary>
|
|
private bool RollforwardTrigger;
|
|
|
|
/// <summary>The length of the train.</summary>
|
|
private readonly double TrainLength;
|
|
|
|
/// <summary>The maximum speed in YARD mode.</summary>
|
|
private readonly Speed YardMaximumSpeed;
|
|
|
|
/// <summary>The distance after which the runback protection is triggered.</summary>
|
|
private readonly double RunbackDistance;
|
|
|
|
/// <summary>The distance after which the rollforward protection is triggered.</summary>
|
|
private readonly double RollforwardDistance;
|
|
|
|
/// <summary>Whether ATO is available or not on the train.</summary>
|
|
private readonly bool AtoAvailable;
|
|
|
|
/// <summary>The index of the alarm sound.</summary>
|
|
private readonly int AlarmSoundIndex;
|
|
|
|
/// <summary>Represents an ATC speed code.</summary>
|
|
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 double TargetDistance { get; private set; }
|
|
|
|
internal SpeedCode(Speed limit, Speed target, double distance)
|
|
{
|
|
CurrentLimit = limit;
|
|
TargetLimit = target;
|
|
TargetDistance = distance;
|
|
if (limit == target)
|
|
{
|
|
// Constant speed
|
|
WarningOn = new Speed((limit.KilometersPerHour - 3) / 3.6);
|
|
WarningOff = new Speed((limit.KilometersPerHour - 5) / 3.6);
|
|
}
|
|
else
|
|
{
|
|
// Reduce speed
|
|
WarningOn = target;
|
|
WarningOff = new Speed((target.KilometersPerHour - 2) / 3.6);
|
|
}
|
|
}
|
|
|
|
internal SpeedCode(Speed limit, Speed target) : this(limit, target, 0)
|
|
{
|
|
}
|
|
|
|
/// <summary>Updates the ATC speed code.</summary>
|
|
/// <param name="distance">The distance to the end of the signalling block.</param>
|
|
internal void Update(double distance)
|
|
{
|
|
TargetDistance = distance;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>The current speed code received by the train.</summary>
|
|
private SpeedCode CurrentSpeedCode;
|
|
|
|
/// <summary>Represents a signal code.</summary>
|
|
internal class SignalCode
|
|
{
|
|
internal int Aspect;
|
|
internal Speed Limit;
|
|
internal Speed Target;
|
|
internal Speed EntryTarget;
|
|
|
|
internal SignalCode(int aspect, string data)
|
|
{
|
|
Aspect = aspect;
|
|
Limit = new Speed(0);
|
|
Target = new Speed(0);
|
|
EntryTarget = new Speed(0);
|
|
if (data.Contains("/"))
|
|
{
|
|
string[] speeds = data.Split('/');
|
|
if (speeds.Length > 1)
|
|
{
|
|
string a = speeds[0];
|
|
string b = speeds[1];
|
|
if (double.TryParse(a, NumberStyles.Float, CultureInfo.InvariantCulture, out double an) && double.TryParse(b, NumberStyles.Float, CultureInfo.InvariantCulture, out double bn))
|
|
{
|
|
if (an >= bn && an >= 0)
|
|
{
|
|
Limit = new Speed(an / 3.6);
|
|
}
|
|
if (bn <= an && bn >= 0)
|
|
{
|
|
Target = new Speed(bn / 3.6);
|
|
}
|
|
if (speeds.Length > 2)
|
|
{
|
|
if (double.TryParse(speeds[2], NumberStyles.Float, CultureInfo.InvariantCulture, out double cn))
|
|
{
|
|
EntryTarget = new Speed(cn / 3.6);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>The list of signal codes recognised by the device.</summary>
|
|
private readonly List<SignalCode> SignalCodes;
|
|
|
|
/// <summary>The aspect of the current signal in the route.</summary>
|
|
private int CurrentSignalAspect;
|
|
|
|
/// <summary>The start position of the current signal.</summary>
|
|
private double CurrentSignalStartLocation;
|
|
|
|
/// <summary>The end position of the current signal.</summary>
|
|
private double CurrentSignalEndLocation;
|
|
|
|
/// <summary>The ideal power notch for the AI.</summary>
|
|
private int AiPowerNotch;
|
|
|
|
/// <summary>The ideal brake notch for the AI.</summary>
|
|
private int AiBrakeNotch;
|
|
|
|
/// <summary>Creates an instance of the Bombardier ATC device.</summary>
|
|
/// <param name="trainLength">The length of the train, in meters.</param>
|
|
/// <param name="yardMaxSpeed">The maximum speed in YARD mode, in km/h.</param>
|
|
/// <param name="runbackDistance">The distance after which the runback protection is triggered, in meters.</param>
|
|
/// <param name="rollforwardDistance">The distance after which the rollforward protection is triggered, in meters.</param>
|
|
/// <param name="signalCodes">The list of signal codes recognised by the device.</param>
|
|
/// <param name="alarmSoundIndex">The index of the alarm sound.</param>
|
|
/// <param name="atoAvailable">Whether ATO is available or not.</param>
|
|
internal AtcDimetronic(double trainLength, Speed yardMaxSpeed, double runbackDistance, double rollforwardDistance, List<SignalCode> signalCodes, int alarmSoundIndex, bool atoAvailable)
|
|
{
|
|
TrainLength = trainLength;
|
|
YardMaximumSpeed = yardMaxSpeed;
|
|
RunbackDistance = runbackDistance;
|
|
RollforwardDistance = rollforwardDistance;
|
|
SignalCodes = signalCodes;
|
|
AlarmSoundIndex = alarmSoundIndex;
|
|
AtoAvailable = atoAvailable;
|
|
}
|
|
|
|
/// <summary>Is called when the device state should be updated.</summary>
|
|
/// <param name="train">The current train.</param>
|
|
/// <param name="route">The current route.</param>
|
|
/// <param name="init">Whether the device should initialize.</param>
|
|
/// <param name="elapsedTime">The time elapsed since the previous call.</param>
|
|
internal override void Update(Train train, Route route, bool init, Time elapsedTime)
|
|
{
|
|
if (init)
|
|
{
|
|
// Initialize device on start
|
|
DeviceState = DeviceStates.NoMode;
|
|
// Reset runback and rollforward detection
|
|
RunbackLocation = train.State.Location;
|
|
RollforwardLocation = train.State.Location;
|
|
}
|
|
|
|
// Update train location
|
|
TrainLocation = train.State.Location;
|
|
|
|
double speed = train.State.Speed.KilometersPerHour;
|
|
bool stopped = speed < 0.05;
|
|
|
|
if (DeviceState == DeviceStates.YARD || DeviceState == DeviceStates.ATP || DeviceState == DeviceStates.ATO)
|
|
{
|
|
// Speed limit enforcement
|
|
if (AtcControlState == AtcControlStates.Released && speed > CurrentSpeedCode.WarningOn.KilometersPerHour)
|
|
{
|
|
// Cut power above warning on threshold and play alarm
|
|
AtcControlState = AtcControlStates.NoPowerAlarm;
|
|
}
|
|
if ((AtcControlState == AtcControlStates.NoPower || AtcControlState == AtcControlStates.NoPowerAlarm) && 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;
|
|
}
|
|
|
|
// Runback protection
|
|
if ((train.PhysicalHandles.Reverser == 1 && TrainLocation > RunbackLocation) || (train.PhysicalHandles.Reverser == -1 && TrainLocation < RunbackLocation))
|
|
{
|
|
// The train is moving in the correct direction, update location
|
|
RunbackLocation = TrainLocation;
|
|
}
|
|
if ((train.PhysicalHandles.Reverser >= 0 && TrainLocation < RunbackLocation - RunbackDistance) || (train.PhysicalHandles.Reverser <= 0 && TrainLocation > RunbackLocation + RunbackDistance))
|
|
{
|
|
// The train is moving in the incorrect direction, reset driving mode
|
|
DeviceState = DeviceStates.NoMode;
|
|
}
|
|
|
|
// Rollforward protection
|
|
if (train.PhysicalHandles.BrakeNotch > 0 || train.PhysicalHandles.PowerNotch > 0)
|
|
{
|
|
// The train has a power/brake notch applied, movement is expected
|
|
RollforwardTrigger = false;
|
|
RollforwardLocation = TrainLocation;
|
|
}
|
|
else if (stopped)
|
|
{
|
|
// The train is stopped but without power or brakes, start checking for unintended rolling
|
|
RollforwardTrigger = true;
|
|
}
|
|
if (RollforwardTrigger && (TrainLocation > RollforwardLocation + RollforwardDistance || TrainLocation < RollforwardLocation - RollforwardDistance))
|
|
{
|
|
// The train is rolling unintendedly, reset driving mode
|
|
DeviceState = DeviceStates.NoMode;
|
|
}
|
|
}
|
|
|
|
// Brake application
|
|
switch (AtcControlState)
|
|
{
|
|
case AtcControlStates.Released:
|
|
RequestedBrakeNotch = -1;
|
|
RequestedPowerNotch = -1;
|
|
break;
|
|
case AtcControlStates.NoPower:
|
|
case AtcControlStates.NoPowerAlarm:
|
|
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;
|
|
// Update ATC speed code
|
|
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;
|
|
CurrentSpeedCode = GetSpeedCodeFromAspect();
|
|
break;
|
|
// ATC device is in ATO driving mode
|
|
case DeviceStates.ATO:
|
|
train.ContinuousProtection = true;
|
|
train.VigilanceOverride = true;
|
|
CurrentSpeedCode = GetSpeedCodeFromAspect();
|
|
break;
|
|
}
|
|
|
|
// Panel indicators
|
|
train.Panel[11] = TrackState > TrackStates.Unprotected && train.PhysicalHandles.Reverser == 1 ? 1 : 0;
|
|
train.Panel[12] = AtcControlState == AtcControlStates.NoPower || AtcControlState == AtcControlStates.NoPowerAlarm ? 1 : 0;
|
|
train.Panel[13] = (int)DeviceState;
|
|
train.Panel[14] = (int)CurrentSpeedCode.CurrentLimit.KilometersPerHour * 1000;
|
|
train.Panel[15] = (int)CurrentSpeedCode.TargetLimit.KilometersPerHour * 1000;
|
|
train.Panel[16] = (int)DeviceState > 1 ? 1 : 0;
|
|
|
|
// Play alarm sound
|
|
if (AtcControlState == AtcControlStates.NoPowerAlarm)
|
|
{
|
|
SoundManager.Play(AlarmSoundIndex, 1.0, 1.0, true);
|
|
}
|
|
// Stop alarm sound
|
|
if (AtcControlState != AtcControlStates.NoPowerAlarm && SoundManager.Playing(AlarmSoundIndex))
|
|
{
|
|
SoundManager.Stop(AlarmSoundIndex);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>Is called when the state of a key changes.</summary>
|
|
/// <param name="key">The key.</param>
|
|
/// <param name="pressed">Whether the key is pressed or released.</param>
|
|
/// <param name="train">The current train.</param>
|
|
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])
|
|
{
|
|
// Allow change anytime the train is stopped or from ATP mode below YARD speed limit
|
|
if ((DeviceState >= DeviceStates.NoMode && stopped) || (DeviceState == DeviceStates.ATP && train.State.Speed.KilometersPerHour < YardMaximumSpeed.KilometersPerHour))
|
|
{
|
|
if (train.PhysicalHandles.Reverser == 1 || train.PhysicalHandles.Reverser == -1)
|
|
{
|
|
DeviceState = DeviceStates.YARD;
|
|
// Update ATC speed code
|
|
CurrentSpeedCode = new SpeedCode(YardMaximumSpeed, YardMaximumSpeed);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case VirtualKeys.J:
|
|
// ATP (M+ATP) mode selection button
|
|
if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
|
|
{
|
|
// Allow change anytime the train is stopped or from ATO mode when running
|
|
if ((DeviceState >= DeviceStates.NoMode && DeviceState <= DeviceStates.ATO && stopped) || DeviceState == DeviceStates.ATO)
|
|
{
|
|
if (train.PhysicalHandles.Reverser == 1 && TrackState > TrackStates.Unprotected)
|
|
{
|
|
DeviceState = DeviceStates.ATP;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case VirtualKeys.K:
|
|
// ATO mode selection button
|
|
if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
|
|
{
|
|
// Allow change when the train is stopped in ATP mode
|
|
if (DeviceState == DeviceStates.ATP && stopped && AtoAvailable)
|
|
{
|
|
if (train.PhysicalHandles.Reverser == 1 && TrackState > TrackStates.Unprotected)
|
|
{
|
|
DeviceState = DeviceStates.ATO;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case VirtualKeys.L:
|
|
// ATO start button
|
|
if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
|
|
{
|
|
}
|
|
break;
|
|
case VirtualKeys.B1:
|
|
// ATC alarm acknowledge
|
|
if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
|
|
{
|
|
if (AtcControlState == AtcControlStates.NoPowerAlarm)
|
|
{
|
|
AtcControlState = AtcControlStates.NoPower;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
CurrentSpeedCode = new SpeedCode(new Speed(0), new Speed(0));
|
|
DeviceState = DeviceStates.Override;
|
|
}
|
|
break;
|
|
case DeviceStates.Override:
|
|
// Disable override if possible
|
|
if (stopped && train.PhysicalHandles.PowerNotch == 0)
|
|
{
|
|
DeviceState = DeviceStates.NoMode;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Is called to inform about signals.</summary>
|
|
/// <param name="signal">The signal data.</param>
|
|
internal override void SetSignal(SignalData[] signal)
|
|
{
|
|
CurrentSignalAspect = signal[0].Aspect;
|
|
CurrentSignalStartLocation = signal[0].Distance + TrainLocation;
|
|
CurrentSignalEndLocation = signal.Length > 1 ? signal[1].Distance + TrainLocation : double.MaxValue;
|
|
}
|
|
|
|
/// <summary>Is called when a beacon is passed.</summary>
|
|
/// <param name="beacon">The beacon data.</param>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Is called when the device should perform the AI.</summary>
|
|
/// <param name="data">The AI data.</param>
|
|
/// <param name="train">The current train.</param>
|
|
/// <param name="route">The current route.</param>
|
|
internal override void PerformAI(AIData data, Train train, Route route)
|
|
{
|
|
double speed = train.State.Speed.KilometersPerHour;
|
|
bool stopped = speed < 0.05;
|
|
|
|
// Set reverser to forward
|
|
if (data.Handles.Reverser != 1)
|
|
{
|
|
data.Handles.Reverser = 1;
|
|
data.Response = AIResponse.Short;
|
|
}
|
|
|
|
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:
|
|
// Select ATP (M+ATP) mode if the train is stopped and receiving ATC codes from the track
|
|
if (stopped)
|
|
{
|
|
switch (TrackState)
|
|
{
|
|
case TrackStates.Enable:
|
|
case TrackStates.ATC:
|
|
KeyChange(VirtualKeys.J, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.J, false, train);
|
|
break;
|
|
}
|
|
}
|
|
CalculateAiNotches(data, train, route);
|
|
break;
|
|
case DeviceStates.ATP:
|
|
// Select YARD (M+25) mode if the train is stopped and not receiving ATC codes from the track or a mode change is required
|
|
if (stopped)
|
|
{
|
|
switch (TrackState)
|
|
{
|
|
case TrackStates.Disable:
|
|
case TrackStates.Unprotected:
|
|
KeyChange(VirtualKeys.I, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.I, false, train);
|
|
break;
|
|
}
|
|
}
|
|
CalculateAiNotches(data, train, route);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>Calculates the ideal notches for the AI driver.</summary>
|
|
/// <param name="data">The AI data.</param>
|
|
/// <param name="train">The current train.</param>
|
|
/// <param name="route">The current route.</param>
|
|
private void CalculateAiNotches(AIData data, Train train, Route route)
|
|
{
|
|
double speed = train.State.Speed.KilometersPerHour;
|
|
bool stopped = speed < 0.05;
|
|
|
|
// Set AI notches to what the AI is doing right now
|
|
AiBrakeNotch = data.Handles.BrakeNotch;
|
|
AiPowerNotch = data.Handles.PowerNotch;
|
|
|
|
// 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
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Generates the corresponding speed code from a signal aspect.</summary>
|
|
private SpeedCode GetSpeedCodeFromAspect()
|
|
{
|
|
foreach (SignalCode signal in SignalCodes)
|
|
{
|
|
if (signal.Aspect == CurrentSignalAspect)
|
|
{
|
|
if (TrainLocation - TrainLength < CurrentSignalStartLocation && signal.EntryTarget.MetersPerSecond > 0)
|
|
{
|
|
// The train has not fully entered the signal section, use the entry target speed
|
|
return new SpeedCode(signal.Limit, signal.EntryTarget, CurrentSignalEndLocation);
|
|
}
|
|
return new SpeedCode(signal.Limit, signal.Target, CurrentSignalEndLocation);
|
|
}
|
|
}
|
|
return new SpeedCode(new Speed(0), new Speed(0));
|
|
}
|
|
}
|
|
}
|