From 8d9c08276a755d9dccd457a2401624c14ba550cb Mon Sep 17 00:00:00 2001 From: Marc Riera Date: Wed, 9 Oct 2024 22:54:28 +0200 Subject: [PATCH] First devices --- ...inPlugin.sln => OpenbveFcmbTrainPlugin.sln | 2 +- src/Devices/Deadman.cs | 111 ++++++++++++ src/Devices/Device.cs | 64 +++++++ src/Devices/Doors.cs | 167 ++++++++++++++++++ src/Devices/TrainStop.cs | 10 ++ ...ainPlugin.cs => OpenbveFcmbTrainPlugin.cs} | 36 ++-- ...n.csproj => OpenbveFcmbTrainPlugin.csproj} | 18 +- src/Train/Train.cs | 152 ++++++++++++++++ 8 files changed, 539 insertions(+), 21 deletions(-) rename OpenBveFcmbTrainPlugin.sln => OpenbveFcmbTrainPlugin.sln (82%) create mode 100644 src/Devices/Deadman.cs create mode 100644 src/Devices/Device.cs create mode 100644 src/Devices/Doors.cs create mode 100644 src/Devices/TrainStop.cs rename src/{OpenBveFcmbTrainPlugin.cs => OpenbveFcmbTrainPlugin.cs} (78%) rename src/{OpenBveFcmbTrainPlugin.csproj => OpenbveFcmbTrainPlugin.csproj} (70%) create mode 100644 src/Train/Train.cs diff --git a/OpenBveFcmbTrainPlugin.sln b/OpenbveFcmbTrainPlugin.sln similarity index 82% rename from OpenBveFcmbTrainPlugin.sln rename to OpenbveFcmbTrainPlugin.sln index b855c5f..97200f0 100644 --- a/OpenBveFcmbTrainPlugin.sln +++ b/OpenbveFcmbTrainPlugin.sln @@ -1,7 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenBveFcmbTrainPlugin", "src/OpenBveFcmbTrainPlugin.csproj", "{9197FFA4-D95A-410C-A705-4E7CD187546A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenbveFcmbTrainPlugin", "src/OpenbveFcmbTrainPlugin.csproj", "{9197FFA4-D95A-410C-A705-4E7CD187546A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Devices/Deadman.cs b/src/Devices/Deadman.cs new file mode 100644 index 0000000..a541693 --- /dev/null +++ b/src/Devices/Deadman.cs @@ -0,0 +1,111 @@ +using System; +using OpenBveApi.Runtime; + +namespace OpenbveFcmbTrainPlugin +{ + /// A deadman switch device to stop the train if the driver does not periodically acknowledge it. + internal class Deadman : Device + { + /// Represents the state of the device. + private enum DeviceStates + { + /// The device is inactive. + Inactive, + /// The device is active. + Active, + /// The device is in emergency state. + Emergency, + } + + /// The current state of the device. + private DeviceStates DeviceState; + + /// The counter. This starts at zero and counts up until the brake delay time is reached. + private double Counter; + + /// The delay time until the service brake is applied, in seconds. + private double BrakeDelay = 5.0; + + /// Whether the train must be completely stopped to release the brakes. + private bool FullReset = false; + + /// Is called when the device state should be updated. + /// The current train. + /// Whether the device should initialize. + /// The time elapsed since the previous call. + internal override void Update(Train train, bool init, Time elapsedTime) + { + if (train.VigilanceOverride) + { + // The train wants to override vigilance devices, so the device is inactive + DeviceState = DeviceStates.Inactive; + } + + switch (DeviceState) + { + case DeviceStates.Active: + // The device is active, update the counter every frame + Counter += elapsedTime.Seconds; + RequestedBrakeNotch = -1; + if (Counter > BrakeDelay) + { + // Switch to emergency mode when the counter reaches the brake delay time + DeviceState = DeviceStates.Emergency; + } + break; + case DeviceStates.Inactive: + // The device is being overridden, release the brakes + RequestedBrakeNotch = -1; + if (!train.VigilanceOverride) + { + // If the device is no longer overridden, it should be active again + DeviceState = DeviceStates.Active; + } + break; + case DeviceStates.Emergency: + // Apply the brakes when the device is in emergency mode + RequestedBrakeNotch = train.Specs.BrakeNotches; + break; + } + } + + /// 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) + { + if (pressed) + { + switch (key) + { + case VirtualKeys.S: + if (DeviceState == DeviceStates.Active || (DeviceState == DeviceStates.Emergency & !FullReset)) + { + // Reset the counter if the delay time has not been exceeded or if a full reset is not required + Counter = 0; + DeviceState = DeviceStates.Active; + } + else if (DeviceState == DeviceStates.Emergency & Math.Abs(train.State.Speed.KilometersPerHour) < 0.01) + { + // Reset the counter after the train has stopped + Counter = 0; + DeviceState = DeviceStates.Active; + } + break; + } + } + } + + /// Is called when the plugin should perform the AI. + /// The AI data. + /// The current train. + internal override void PerformAI(AIData data, Train train) + { + if (DeviceState != DeviceStates.Inactive) + { + KeyChange(VirtualKeys.S, true, train); + } + } + } +} \ No newline at end of file diff --git a/src/Devices/Device.cs b/src/Devices/Device.cs new file mode 100644 index 0000000..3bd0843 --- /dev/null +++ b/src/Devices/Device.cs @@ -0,0 +1,64 @@ +using OpenBveApi.Runtime; + +namespace OpenbveFcmbTrainPlugin +{ + /// Represents an abstract device. + internal abstract class Device + { + + /// The power notch requested by the device. + internal int RequestedPowerNotch = -1; + + /// The brake notch requested by the device. + internal int RequestedBrakeNotch = -1; + + /// The door interlock requested by the device. + internal DoorInterlockStates RequestedDoorInterlock; + + /// Is called when the device state should be updated. + /// The current train. + /// Whether the device should initialize. + /// The time elapsed since the previous call. + internal virtual void Update(Train train, bool init, Time elapsedTime) { } + + /// Is called when the driver changes the reverser. + /// The new reverser position. + internal virtual void SetReverser(int reverser) { } + + /// Is called when the driver changes the power notch. + /// The new power notch. + internal virtual void SetPower(int powerNotch) { } + + /// Is called when the driver changes the brake notch. + /// The new brake notch. + internal virtual void SetBrake(int brakeNotch) { } + + /// Is called when the state of a key changes. + /// The key. + /// Whether the key is pressed or released. + /// The current train. + internal virtual void KeyChange(VirtualKeys key, bool pressed, Train train) { } + + /// Is called when the state of the doors changes. + /// The old state of the doors. + /// The new state of the doors. + internal virtual void DoorChange(DoorStates oldState, DoorStates newState) { } + + /// Is called when a horn is played or when the music horn is stopped. + /// The type of horn. + internal virtual void HornBlow(HornTypes type) { } + + /// Is called to inform about signals. + /// The signal data. + internal virtual void SetSignal(SignalData[] signal) { } + + /// Is called when a beacon is passed. + /// The beacon data. + internal virtual void SetBeacon(BeaconData beacon) { } + + /// Is called when the plugin should perform the AI. + /// The AI data. + /// The current train. + internal virtual void PerformAI(AIData data, Train train) { } + } +} diff --git a/src/Devices/Doors.cs b/src/Devices/Doors.cs new file mode 100644 index 0000000..6d231dd --- /dev/null +++ b/src/Devices/Doors.cs @@ -0,0 +1,167 @@ +using System; +using OpenBveApi.Runtime; + +namespace OpenbveFcmbTrainPlugin +{ + /// A device controlling the door operation. + internal class Doors : Device + { + /// Whether the left doors are closing. + private bool LeftDoorsClosing; + + /// Whether the right doors are closing. + private bool RightDoorsClosing; + + /// Is called when the device state should be updated. + /// The current train. + /// Whether the device should initialize. + /// The time elapsed since the previous call. + internal override void Update(Train train, bool init, Time elapsedTime) + { + if (init) + { + // Set the selection state of the doors during initialization + switch (train.DoorState) + { + case DoorStates.None: + RequestedDoorInterlock = DoorInterlockStates.Locked; + break; + case DoorStates.Both: + RequestedDoorInterlock = DoorInterlockStates.Unlocked; + break; + case DoorStates.Left: + case DoorStates.Right: + RequestedDoorInterlock = (DoorInterlockStates)train.DoorState; + break; + } + } + + // Cut power when the doors are open + RequestedPowerNotch = (train.DoorState != DoorStates.None) ? 0 : -1; + + // Update panel variables for door selection state + train.Panel[50] = (RequestedDoorInterlock == DoorInterlockStates.Left || RequestedDoorInterlock == DoorInterlockStates.Unlocked) ? 1 : 0; + train.Panel[51] = (RequestedDoorInterlock == DoorInterlockStates.Right || RequestedDoorInterlock == DoorInterlockStates.Unlocked) ? 1 : 0; + } + + /// 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) + { + if (pressed) + { + switch (key) + { + case VirtualKeys.G: + // Change the selection state of the left doors + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + // Button active only if the doors are not open + if ((train.DoorState & DoorStates.Left) == 0 || LeftDoorsClosing) + { + switch (RequestedDoorInterlock) + { + case DoorInterlockStates.Locked: + RequestedDoorInterlock = DoorInterlockStates.Left; + break; + case DoorInterlockStates.Left: + RequestedDoorInterlock = DoorInterlockStates.Locked; + break; + case DoorInterlockStates.Right: + RequestedDoorInterlock = DoorInterlockStates.Unlocked; + break; + case DoorInterlockStates.Unlocked: + RequestedDoorInterlock = DoorInterlockStates.Right; + break; + } + } + } + break; + case VirtualKeys.H: + // Change the selection state of the right doors + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + // Button active only if the doors are not open + if ((train.DoorState & DoorStates.Right) == 0 || RightDoorsClosing) + { + switch (RequestedDoorInterlock) + { + case DoorInterlockStates.Locked: + RequestedDoorInterlock = DoorInterlockStates.Right; + break; + case DoorInterlockStates.Left: + RequestedDoorInterlock = DoorInterlockStates.Unlocked; + break; + case DoorInterlockStates.Right: + RequestedDoorInterlock = DoorInterlockStates.Locked; + break; + case DoorInterlockStates.Unlocked: + RequestedDoorInterlock = DoorInterlockStates.Left; + break; + } + } + } + break; + case VirtualKeys.LeftDoors: + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + // Unselect doors automatically when closing + if ((train.DoorState & DoorStates.Left) > 0 && (RequestedDoorInterlock == DoorInterlockStates.Left || RequestedDoorInterlock == DoorInterlockStates.Unlocked)) + { + LeftDoorsClosing = true; + RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Left ? DoorInterlockStates.Locked : DoorInterlockStates.Right; + } + } + break; + case VirtualKeys.RightDoors: + if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key]) + { + // Unselect doors automatically when closing + if ((train.DoorState & DoorStates.Right) > 0 && (RequestedDoorInterlock == DoorInterlockStates.Right || RequestedDoorInterlock == DoorInterlockStates.Unlocked)) + { + RightDoorsClosing = true; + RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Right ? DoorInterlockStates.Locked : DoorInterlockStates. Left; + } + } + break; + } + // TODO: OpenBVE does not trigger virtual buttons when the AI driver is active, needs changes to main program + } + } + + /// Is called when the state of the doors changes. + /// The old state of the doors. + /// The new state of the doors. + internal override void DoorChange(DoorStates oldState, DoorStates newState) + { + LeftDoorsClosing = false; + RightDoorsClosing = false; + } + + /// Is called when the device should perform the AI. + /// The AI data. + /// The current train. + internal override void PerformAI(AIData data, Train train) + { + //// Select the correct doors for the current station + //if (Stations.State != Stations.StationStates.Completed) + //{ + // if (((Stations.Current.OpenLeftDoors && !LeftDoorsSelected) || (!Stations.Current.OpenLeftDoors && LeftDoorsSelected)) && !LeftDoorsClosing) + // { + // // Select or unselect the doors on the left. + // KeyDown(VirtualKeys.G); + // data.Response = AIResponse.Short; + // } + // if (((Stations.Current.OpenRightDoors && !RightDoorsSelected) || (!Stations.Current.OpenRightDoors && RightDoorsSelected)) && !RightDoorsClosing) + // { + // // Select or unselect the doors on the right. + // KeyDown(VirtualKeys.H); + // data.Response = AIResponse.Short; + // } + //} + } + } +} + diff --git a/src/Devices/TrainStop.cs b/src/Devices/TrainStop.cs new file mode 100644 index 0000000..f2797cd --- /dev/null +++ b/src/Devices/TrainStop.cs @@ -0,0 +1,10 @@ +using System; + +namespace OpenbveFcmbTrainPlugin +{ + /// A train stop device to stop the train if a stop signal is passed. + internal class TrainStop : Device + { + + } +} diff --git a/src/OpenBveFcmbTrainPlugin.cs b/src/OpenbveFcmbTrainPlugin.cs similarity index 78% rename from src/OpenBveFcmbTrainPlugin.cs rename to src/OpenbveFcmbTrainPlugin.cs index 767f5e1..203e429 100644 --- a/src/OpenBveFcmbTrainPlugin.cs +++ b/src/OpenbveFcmbTrainPlugin.cs @@ -4,27 +4,25 @@ using OpenBveApi.Runtime; namespace OpenbveFcmbTrainPlugin { /// The interface to be implemented by the plugin. - public partial class OpenbveFcmbTrainPlugin : IRuntime + public class OpenbveFcmbTrainPlugin : IRuntime { + /// The train that is simulated by this plugin. + private Train Train; - /// Holds the aspect last reported to the plugin in the SetSignal call. - int LastAspect = -1; - - /// Holds the array of panel variables. - /// Be sure to initialize in the Load call. - public static int[] Panel; + /// Whether the train is initializing or reinitializing. + internal static bool Initializing = true; /// Remembers which of the virtual keys are currently pressed down. - public static bool[] KeysPressed = new bool[34]; - - /// Whether the plugin is initializing or reinitializing. - public static bool Initializing; + internal static bool[] KeysPressed = new bool[Enum.GetNames(typeof(VirtualKeys)).Length]; /// Is called when the plugin is loaded. /// The properties supplied to the plugin on loading. /// Whether the plugin was loaded successfully. public bool Load(LoadProperties properties) { + properties.AISupport = AISupport.Basic; + properties.Panel = new int[256]; + Train = new Train(properties.Panel); return true; } @@ -37,18 +35,24 @@ namespace OpenbveFcmbTrainPlugin /// The specifications of the train. public void SetVehicleSpecs(VehicleSpecs specs) { + Train.SetVehicleSpecs(specs); } /// Is called when the plugin should initialize or reinitialize. /// The mode of initialization. public void Initialize(InitializationModes mode) { + // On initialization, a variable is set so the first call to Elapse() can be used for initialization + Initializing = true; + Train.Initialize(mode); } /// Is called every frame. /// The data passed to the plugin. public void Elapse(ElapseData data) { + Train.Elapse(data); + Initializing = false; } /// Is called when the driver changes the reverser. @@ -73,12 +77,16 @@ namespace OpenbveFcmbTrainPlugin /// The virtual key that was pressed. public void KeyDown(VirtualKeys key) { + Train.KeyDown(key); + KeysPressed[(int)key] = true; } /// Is called when a virtual key is released. /// The virtual key that was released. public void KeyUp(VirtualKeys key) { + Train.KeyUp(key); + KeysPressed[(int)key] = false; } /// Is called when a horn is played or when the music horn is stopped. @@ -92,16 +100,13 @@ namespace OpenbveFcmbTrainPlugin /// The new state of the doors. public void DoorChange(DoorStates oldState, DoorStates newState) { + Train.DoorChange(oldState, newState); } /// Is called when the aspect in the current or in any of the upcoming sections changes, or when passing section boundaries. /// 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) { - int aspect = signal[0].Aspect; - if (aspect != LastAspect) - { - } } /// Is called when the train passes a beacon. @@ -114,6 +119,7 @@ namespace OpenbveFcmbTrainPlugin /// The AI data. public void PerformAI(AIData data) { + Train.PerformAI(data); } } diff --git a/src/OpenBveFcmbTrainPlugin.csproj b/src/OpenbveFcmbTrainPlugin.csproj similarity index 70% rename from src/OpenBveFcmbTrainPlugin.csproj rename to src/OpenbveFcmbTrainPlugin.csproj index 244f830..54c6b9b 100644 --- a/src/OpenBveFcmbTrainPlugin.csproj +++ b/src/OpenbveFcmbTrainPlugin.csproj @@ -5,8 +5,8 @@ AnyCPU {9197FFA4-D95A-410C-A705-4E7CD187546A} Library - OpenBveFcmbTrainPlugin - OpenBveFcmbTrainPlugin + OpenbveFcmbTrainPlugin + OpenbveFcmbTrainPlugin v4.7 @@ -29,13 +29,21 @@ - ..\..\..\..\lib\openbve\OpenBveApi.dll - False + ..\..\..\Documents\OpenBVE\OpenBveApi.dll - + + + + + + + + + + diff --git a/src/Train/Train.cs b/src/Train/Train.cs new file mode 100644 index 0000000..8c5726f --- /dev/null +++ b/src/Train/Train.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using OpenBveApi.Runtime; + +namespace OpenbveFcmbTrainPlugin +{ + /// Represents a train that is simulated by this plugin. + internal class Train + { + /// The train panel variables. + internal int[] Panel; + + /// The specifications of the train. + internal VehicleSpecs Specs { get; private set; } + + /// The current state of the train. + internal VehicleState State { get; private set; } + + /// The current state of the train doors. + internal DoorStates DoorState { get; private set; } + + /// The previous state of the train doors. + internal DoorStates PreviousDoorState { get; private set; } + + /// The latest initialization mode of the train. + internal InitializationModes InitializationMode { get; private set; } + + /// The devices equipped in the train. + private List Devices; + + /// Whether the train's vigilance device is overridden. + internal bool VigilanceOverride; + + /// Whether the train is using a continuous protection system instead of an intermittent protection system. + internal bool ContinuousProtection; + + /// Creates a new train with the device configuration provided. + /// The array of panel variables.(); + Devices.Add(new Doors()); + Devices.Add(new Deadman()); + Devices.Add(new TrainStop()); + } + + /// Is called when the train should initialize. + /// The initialization mode. + internal void Initialize(InitializationModes mode) + { + InitializationMode = mode; + } + + /// Is called after loading to inform the plugin about the specifications of the train. + /// The specifications of the train. + internal void SetVehicleSpecs(VehicleSpecs specs) + { + Specs = specs; + } + + /// Is called every frame. + /// The data. + internal void Elapse(ElapseData data) + { + State = data.Vehicle; + + // Reset data to be passed to the simulator + data.DoorInterlockState = DoorInterlockStates.Unlocked; + data.Handles.PowerNotch = data.Handles.PowerNotch; + data.Handles.BrakeNotch = data.Handles.BrakeNotch; + + // Retrieve data from all devices + foreach (Device dev in Devices) + { + dev.Update(this, OpenbveFcmbTrainPlugin.Initializing, data.ElapsedTime); + data.DoorInterlockState |= dev.RequestedDoorInterlock; + if (dev.RequestedPowerNotch != -1) data.Handles.PowerNotch = dev.RequestedPowerNotch; + if (dev.RequestedBrakeNotch != -1) data.Handles.BrakeNotch = dev.RequestedBrakeNotch; + } + data.DebugMessage = DoorState.ToString() + " " + data.DoorInterlockState.ToString(); + } + + /// Is called when the driver changes the reverser. + /// The new reverser position. + internal void SetReverser(int reverser) { } + + /// Is called when the driver changes the power notch. + /// The new power notch. + internal void SetPower(int powerNotch) { } + + /// Is called when the driver changes the brake notch. + /// The new brake notch. + internal void SetBrake(int brakeNotch) { } + + /// Is called when a key is pressed. + /// The key. + internal void KeyDown(VirtualKeys key) + { + foreach (Device dev in Devices) + { + dev.KeyChange(key, true, this); + } + } + + /// Is called when a key is released. + /// The key. + internal void KeyUp(VirtualKeys key) + { + foreach (Device dev in Devices) + { + dev.KeyChange(key, false, this); + } + } + + /// Is called when the state of the doors changes. + /// The old state of the doors. + /// The new state of the doors. + internal void DoorChange(DoorStates oldState, DoorStates newState) + { + PreviousDoorState = oldState; + DoorState = newState; + foreach (Device dev in Devices) + { + dev.DoorChange(oldState, newState); + } + } + + /// Is called when a horn is played or when the music horn is stopped. + /// The type of horn. + internal void HornBlow(HornTypes type) { } + + /// Is called to inform about signals. + /// The signal data. + internal void SetSignal(SignalData[] signal) { } + + /// Is called when a beacon is passed. + /// The beacon data. + internal void SetBeacon(BeaconData beacon) { } + + /// Is called when the plugin should perform the AI. + /// The AI data. + internal void PerformAI(AIData data) + { + foreach (Device dev in Devices) + { + dev.PerformAI(data, this); + } + } + } +}