First devices

main
Marc Riera 2024-10-09 22:54:28 +02:00
parent 51ef0ececc
commit 8d9c08276a
8 changed files with 539 additions and 21 deletions

View File

@ -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

111
src/Devices/Deadman.cs Normal file
View File

@ -0,0 +1,111 @@
using System;
using OpenBveApi.Runtime;
namespace OpenbveFcmbTrainPlugin
{
/// <summary>A deadman switch device to stop the train if the driver does not periodically acknowledge it.</summary>
internal class Deadman : Device
{
/// <summary>Represents the state of the device.</summary>
private enum DeviceStates
{
/// <summary>The device is inactive.</summary>
Inactive,
/// <summary>The device is active.</summary>
Active,
/// <summary>The device is in emergency state.</summary>
Emergency,
}
/// <summary>The current state of the device.</summary>
private DeviceStates DeviceState;
/// <summary>The counter. This starts at zero and counts up until the brake delay time is reached.</summary>
private double Counter;
/// <summary>The delay time until the service brake is applied, in seconds.</summary>
private double BrakeDelay = 5.0;
/// <summary>Whether the train must be completely stopped to release the brakes.</summary>
private bool FullReset = false;
/// <summary>Is called when the device state should be updated.</summary>
/// <param name="train">The current train.</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, 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;
}
}
/// <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)
{
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;
}
}
}
/// <summary>Is called when the plugin should perform the AI.</summary>
/// <param name="data">The AI data.</param>
/// <param name="train">The current train.</param>
internal override void PerformAI(AIData data, Train train)
{
if (DeviceState != DeviceStates.Inactive)
{
KeyChange(VirtualKeys.S, true, train);
}
}
}
}

64
src/Devices/Device.cs Normal file
View File

@ -0,0 +1,64 @@
using OpenBveApi.Runtime;
namespace OpenbveFcmbTrainPlugin
{
/// <summary>Represents an abstract device.</summary>
internal abstract class Device
{
/// <summary>The power notch requested by the device.</summary>
internal int RequestedPowerNotch = -1;
/// <summary>The brake notch requested by the device.</summary>
internal int RequestedBrakeNotch = -1;
/// <summary>The door interlock requested by the device.</summary>
internal DoorInterlockStates RequestedDoorInterlock;
/// <summary>Is called when the device state should be updated.</summary>
/// <param name="train">The current train.</param>
/// <param name="init">Whether the device should initialize.</param>
/// <param name="elapsedTime">The time elapsed since the previous call.</param>
internal virtual void Update(Train train, bool init, Time elapsedTime) { }
/// <summary>Is called when the driver changes the reverser.</summary>
/// <param name="reverser">The new reverser position.</param>
internal virtual void SetReverser(int reverser) { }
/// <summary>Is called when the driver changes the power notch.</summary>
/// <param name="powerNotch">The new power notch.</param>
internal virtual void SetPower(int powerNotch) { }
/// <summary>Is called when the driver changes the brake notch.</summary>
/// <param name="brakeNotch">The new brake notch.</param>
internal virtual void SetBrake(int brakeNotch) { }
/// <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 virtual void KeyChange(VirtualKeys key, bool pressed, Train train) { }
/// <summary>Is called when the state of the doors changes.</summary>
/// <param name="oldState">The old state of the doors.</param>
/// <param name="newState">The new state of the doors.</param>
internal virtual void DoorChange(DoorStates oldState, DoorStates newState) { }
/// <summary>Is called when a horn is played or when the music horn is stopped.</summary>
/// <param name="type">The type of horn.</param>
internal virtual void HornBlow(HornTypes type) { }
/// <summary>Is called to inform about signals.</summary>
/// <param name="signal">The signal data.</param>
internal virtual void SetSignal(SignalData[] signal) { }
/// <summary>Is called when a beacon is passed.</summary>
/// <param name="beacon">The beacon data.</param>
internal virtual void SetBeacon(BeaconData beacon) { }
/// <summary>Is called when the plugin should perform the AI.</summary>
/// <param name="data">The AI data.</param>
/// <param name="train">The current train.</param>
internal virtual void PerformAI(AIData data, Train train) { }
}
}

167
src/Devices/Doors.cs Normal file
View File

@ -0,0 +1,167 @@
using System;
using OpenBveApi.Runtime;
namespace OpenbveFcmbTrainPlugin
{
/// <summary>A device controlling the door operation.</summary>
internal class Doors : Device
{
/// <summary>Whether the left doors are closing.</summary>
private bool LeftDoorsClosing;
/// <summary>Whether the right doors are closing.</summary>
private bool RightDoorsClosing;
/// <summary>Is called when the device state should be updated.</summary>
/// <param name="train">The current train.</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, 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;
}
/// <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)
{
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
}
}
/// <summary>Is called when the state of the doors changes.</summary>
/// <param name="oldState">The old state of the doors.</param>
/// <param name="newState">The new state of the doors.</param>
internal override void DoorChange(DoorStates oldState, DoorStates newState)
{
LeftDoorsClosing = false;
RightDoorsClosing = false;
}
/// <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>
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;
// }
//}
}
}
}

10
src/Devices/TrainStop.cs Normal file
View File

@ -0,0 +1,10 @@
using System;
namespace OpenbveFcmbTrainPlugin
{
/// <summary>A train stop device to stop the train if a stop signal is passed.</summary>
internal class TrainStop : Device
{
}
}

View File

@ -4,27 +4,25 @@ using OpenBveApi.Runtime;
namespace OpenbveFcmbTrainPlugin
{
/// <summary>The interface to be implemented by the plugin.</summary>
public partial class OpenbveFcmbTrainPlugin : IRuntime
public class OpenbveFcmbTrainPlugin : IRuntime
{
/// <summary>The train that is simulated by this plugin.</summary>
private Train Train;
/// <summary>Holds the aspect last reported to the plugin in the SetSignal call.</summary>
int LastAspect = -1;
/// <summary>Holds the array of panel variables.</summary>
/// <remarks>Be sure to initialize in the Load call.</remarks>
public static int[] Panel;
/// <summary>Whether the train is initializing or reinitializing.</summary>
internal static bool Initializing = true;
/// <summary>Remembers which of the virtual keys are currently pressed down.</summary>
public static bool[] KeysPressed = new bool[34];
/// <summary>Whether the plugin is initializing or reinitializing.</summary>
public static bool Initializing;
internal static bool[] KeysPressed = new bool[Enum.GetNames(typeof(VirtualKeys)).Length];
/// <summary>Is called when the plugin is loaded.</summary>
/// <param name="properties">The properties supplied to the plugin on loading.</param>
/// <returns>Whether the plugin was loaded successfully.</returns>
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
/// <param name="specs">The specifications of the train.</param>
public void SetVehicleSpecs(VehicleSpecs specs)
{
Train.SetVehicleSpecs(specs);
}
/// <summary>Is called when the plugin should initialize or reinitialize.</summary>
/// <param name="mode">The mode of initialization.</param>
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);
}
/// <summary>Is called every frame.</summary>
/// <param name="data">The data passed to the plugin.</param>
public void Elapse(ElapseData data)
{
Train.Elapse(data);
Initializing = false;
}
/// <summary>Is called when the driver changes the reverser.</summary>
@ -73,12 +77,16 @@ namespace OpenbveFcmbTrainPlugin
/// <param name="key">The virtual key that was pressed.</param>
public void KeyDown(VirtualKeys key)
{
Train.KeyDown(key);
KeysPressed[(int)key] = true;
}
/// <summary>Is called when a virtual key is released.</summary>
/// <param name="key">The virtual key that was released.</param>
public void KeyUp(VirtualKeys key)
{
Train.KeyUp(key);
KeysPressed[(int)key] = false;
}
/// <summary>Is called when a horn is played or when the music horn is stopped.</summary>
@ -92,16 +100,13 @@ namespace OpenbveFcmbTrainPlugin
/// <param name="newState">The new state of the doors.</param>
public void DoorChange(DoorStates oldState, DoorStates newState)
{
Train.DoorChange(oldState, newState);
}
/// <summary>Is called when the aspect in the current or in any of the upcoming sections changes, or when passing section boundaries.</summary>
/// <remarks>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.</remarks>
public void SetSignal(SignalData[] signal)
{
int aspect = signal[0].Aspect;
if (aspect != LastAspect)
{
}
}
/// <summary>Is called when the train passes a beacon.</summary>
@ -114,6 +119,7 @@ namespace OpenbveFcmbTrainPlugin
/// <param name="data">The AI data.</param>
public void PerformAI(AIData data)
{
Train.PerformAI(data);
}
}

View File

@ -5,8 +5,8 @@
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9197FFA4-D95A-410C-A705-4E7CD187546A}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>OpenBveFcmbTrainPlugin</RootNamespace>
<AssemblyName>OpenBveFcmbTrainPlugin</AssemblyName>
<RootNamespace>OpenbveFcmbTrainPlugin</RootNamespace>
<AssemblyName>OpenbveFcmbTrainPlugin</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -29,13 +29,21 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="OpenBveApi">
<HintPath>..\..\..\..\lib\openbve\OpenBveApi.dll</HintPath>
<Private>False</Private>
<HintPath>..\..\..\Documents\OpenBVE\OpenBveApi.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="OpenBveFcmbTrainPlugin.cs" />
<Compile Include="OpenbveFcmbTrainPlugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Devices\Device.cs" />
<Compile Include="Devices\Doors.cs" />
<Compile Include="Train\Train.cs" />
<Compile Include="Devices\TrainStop.cs" />
<Compile Include="Devices\Deadman.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Devices\" />
<Folder Include="Train\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

152
src/Train/Train.cs Normal file
View File

@ -0,0 +1,152 @@
using System.Collections.Generic;
using OpenBveApi.Runtime;
namespace OpenbveFcmbTrainPlugin
{
/// <summary>Represents a train that is simulated by this plugin.</summary>
internal class Train
{
/// <summary>The train panel variables.</summary>
internal int[] Panel;
/// <summary>The specifications of the train.</summary>
internal VehicleSpecs Specs { get; private set; }
/// <summary>The current state of the train.</summary>
internal VehicleState State { get; private set; }
/// <summary>The current state of the train doors.</summary>
internal DoorStates DoorState { get; private set; }
/// <summary>The previous state of the train doors.</summary>
internal DoorStates PreviousDoorState { get; private set; }
/// <summary>The latest initialization mode of the train.</summary>
internal InitializationModes InitializationMode { get; private set; }
/// <summary>The devices equipped in the train.</summary>
private List<Device> Devices;
/// <summary>Whether the train's vigilance device is overridden.</summary>
internal bool VigilanceOverride;
/// <summary>Whether the train is using a continuous protection system instead of an intermittent protection system.</summary>
internal bool ContinuousProtection;
/// <summary>Creates a new train with the device configuration provided.</summary>
/// <param name="panel">The array of panel variables.</pa
internal Train(int[] panel)
{
Specs = new VehicleSpecs(0, BrakeTypes.ElectromagneticStraightAirBrake, 0, false, 0);
State = new VehicleState(0.0, new Speed(0.0), 0.0, 0.0, 0.0, 0.0, 0.0);
Panel = panel;
Devices = new List<Device>();
Devices.Add(new Doors());
Devices.Add(new Deadman());
Devices.Add(new TrainStop());
}
/// <summary>Is called when the train should initialize.</summary>
/// <param name="mode">The initialization mode.</param>
internal void Initialize(InitializationModes mode)
{
InitializationMode = mode;
}
/// <summary>Is called after loading to inform the plugin about the specifications of the train.</summary>
/// <param name="specs">The specifications of the train.</param>
internal void SetVehicleSpecs(VehicleSpecs specs)
{
Specs = specs;
}
/// <summary>Is called every frame.</summary>
/// <param name="data">The data.</param>
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();
}
/// <summary>Is called when the driver changes the reverser.</summary>
/// <param name="reverser">The new reverser position.</param>
internal void SetReverser(int reverser) { }
/// <summary>Is called when the driver changes the power notch.</summary>
/// <param name="powerNotch">The new power notch.</param>
internal void SetPower(int powerNotch) { }
/// <summary>Is called when the driver changes the brake notch.</summary>
/// <param name="brakeNotch">The new brake notch.</param>
internal void SetBrake(int brakeNotch) { }
/// <summary>Is called when a key is pressed.</summary>
/// <param name="key">The key.</param>
internal void KeyDown(VirtualKeys key)
{
foreach (Device dev in Devices)
{
dev.KeyChange(key, true, this);
}
}
/// <summary>Is called when a key is released.</summary>
/// <param name="key">The key.</param>
internal void KeyUp(VirtualKeys key)
{
foreach (Device dev in Devices)
{
dev.KeyChange(key, false, this);
}
}
/// <summary>Is called when the state of the doors changes.</summary>
/// <param name="oldState">The old state of the doors.</param>
/// <param name="newState">The new state of the doors.</param>
internal void DoorChange(DoorStates oldState, DoorStates newState)
{
PreviousDoorState = oldState;
DoorState = newState;
foreach (Device dev in Devices)
{
dev.DoorChange(oldState, newState);
}
}
/// <summary>Is called when a horn is played or when the music horn is stopped.</summary>
/// <param name="type">The type of horn.</param>
internal void HornBlow(HornTypes type) { }
/// <summary>Is called to inform about signals.</summary>
/// <param name="signal">The signal data.</param>
internal void SetSignal(SignalData[] signal) { }
/// <summary>Is called when a beacon is passed.</summary>
/// <param name="beacon">The beacon data.</param>
internal void SetBeacon(BeaconData beacon) { }
/// <summary>Is called when the plugin should perform the AI.</summary>
/// <param name="data">The AI data.</param>
internal void PerformAI(AIData data)
{
foreach (Device dev in Devices)
{
dev.PerformAI(data, this);
}
}
}
}