390 lines
20 KiB
C#
390 lines
20 KiB
C#
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>Whether the door closing sound is required to be played to close the doors.</summary>
|
|
private bool RequireDoorClosingSound;
|
|
|
|
/// <summary>The duration of the door closing sound, in seconds.</summary>
|
|
private double DoorClosingSoundDuration = 5;
|
|
|
|
/// <summary>The timeout after which the door closing sound needs to be played again, in seconds.</summary>
|
|
private double DoorClosingSoundTimeout = 15;
|
|
|
|
/// <summary>The counter for the door closing sound.</summary>
|
|
private double DoorClosingSoundCounter;
|
|
|
|
/// <summary>Whether to trigger a door unlock.</summary>
|
|
private bool TriggerDoorUnlock;
|
|
|
|
/// <summary>Whether to trigger a door lock.</summary>
|
|
private bool TriggerDoorLock;
|
|
|
|
/// <summary>Whether the AI should trigger the door closing sound.</summary>
|
|
private bool AiTriggerDoorClosingSound;
|
|
|
|
/// <summary>The timeout after which the AI needs to close doors if they become stuck, in seconds.</summary>
|
|
private double AiDoorStuckTimeout = 10;
|
|
|
|
/// <summary>The counter for the AI to close doors if they become stuck.</summary>
|
|
private double AiDoorStuckCounter;
|
|
|
|
/// <summary>The index of the door closing sound.</summary>
|
|
private int DoorClosingSoundIndex = 10;
|
|
|
|
/// <summary>The current time.</summary>
|
|
private Time CurrentTime = new Time(0);
|
|
|
|
/// <summary>The time when the doors last opened.</summary>
|
|
private Time DoorOpenTime = new Time(0);
|
|
|
|
|
|
/// <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)
|
|
{
|
|
// Update current time
|
|
CurrentTime = route.CurrentTime;
|
|
|
|
if (init)
|
|
{
|
|
if (!RequireDoorClosingSound)
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
// Set door closing counter to value higher than maximum where doors are allowed to close
|
|
DoorClosingSoundCounter = DoorClosingSoundDuration + DoorClosingSoundTimeout;
|
|
}
|
|
|
|
// Cut power when the doors are open
|
|
RequestedPowerNotch = (train.DoorState != DoorStates.None) ? 0 : -1;
|
|
|
|
// Update door closing sound counter
|
|
DoorClosingSoundCounter += elapsedTime.Seconds;
|
|
|
|
// Unlock doors for closing if the door closing sound has been played
|
|
if (RequireDoorClosingSound)
|
|
{
|
|
if (DoorClosingSoundCounter > DoorClosingSoundDuration && TriggerDoorUnlock)
|
|
{
|
|
switch (train.DoorState)
|
|
{
|
|
case DoorStates.Both:
|
|
RequestedDoorInterlock = DoorInterlockStates.Unlocked;
|
|
break;
|
|
case DoorStates.Left:
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Right ? DoorInterlockStates.Unlocked : DoorInterlockStates.Left;
|
|
break;
|
|
case DoorStates.Right:
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Left ? DoorInterlockStates.Unlocked : DoorInterlockStates.Right;
|
|
break;
|
|
}
|
|
TriggerDoorUnlock = false;
|
|
TriggerDoorLock = true;
|
|
}
|
|
else if (DoorClosingSoundCounter > DoorClosingSoundDuration + DoorClosingSoundTimeout && TriggerDoorLock)
|
|
{
|
|
switch (train.DoorState)
|
|
{
|
|
case DoorStates.Both:
|
|
RequestedDoorInterlock = DoorInterlockStates.Locked;
|
|
break;
|
|
case DoorStates.Left:
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Unlocked ? DoorInterlockStates.Right : DoorInterlockStates.Locked;
|
|
break;
|
|
case DoorStates.Right:
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Unlocked ? DoorInterlockStates.Left : DoorInterlockStates.Locked;
|
|
break;
|
|
}
|
|
TriggerDoorLock = false;
|
|
}
|
|
}
|
|
|
|
// Check if AI needs to trigger the door closing sound
|
|
if (train.DoorState != DoorStates.None && DoorClosingSoundCounter > DoorClosingSoundDuration + DoorClosingSoundTimeout)
|
|
{
|
|
if (route.CurrentStation.Type == StationType.Normal || route.CurrentStation.Type == StationType.RequestStop)
|
|
{
|
|
// The current station is an intermediate stop
|
|
if (route.CurrentStation.DepartureTime < 0)
|
|
{
|
|
// The current station has dwell time, not a specific departure time
|
|
// Calculate departure time from the time when the doors opened
|
|
AiTriggerDoorClosingSound |= CurrentTime.Seconds >= DoorOpenTime.Seconds + (route.CurrentStation.StopTime - DoorClosingSoundDuration);
|
|
}
|
|
else
|
|
{
|
|
// Use departure time - door closing sound duration
|
|
AiTriggerDoorClosingSound |= CurrentTime.Seconds >= route.CurrentStation.DepartureTime - DoorClosingSoundDuration;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AiTriggerDoorClosingSound = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AiTriggerDoorClosingSound = false;
|
|
}
|
|
|
|
// Update panel variables for door selection state
|
|
train.Panel[50] = (RequestedDoorInterlock == DoorInterlockStates.Left || RequestedDoorInterlock == DoorInterlockStates.Unlocked) || (((train.DoorState & DoorStates.Left) != 0) && !LeftDoorsClosing) ? 1 : 0;
|
|
train.Panel[51] = (RequestedDoorInterlock == DoorInterlockStates.Right || RequestedDoorInterlock == DoorInterlockStates.Unlocked) || (((train.DoorState & DoorStates.Right) != 0) && !RightDoorsClosing) ? 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.F:
|
|
if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
|
|
{
|
|
// Play the door closing sound and reset timeout
|
|
DoorClosingSoundCounter = 0;
|
|
TriggerDoorUnlock = true;
|
|
if (!SoundManager.Playing(DoorClosingSoundIndex))
|
|
{
|
|
SoundManager.Play(DoorClosingSoundIndex, 1, 1, false);
|
|
}
|
|
}
|
|
break;
|
|
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 && !LeftDoorsClosing && (RequestedDoorInterlock == DoorInterlockStates.Left || RequestedDoorInterlock == DoorInterlockStates.Unlocked))
|
|
{
|
|
LeftDoorsClosing = true;
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Left ? DoorInterlockStates.Locked : DoorInterlockStates.Right;
|
|
}
|
|
else
|
|
{
|
|
LeftDoorsClosing = false;
|
|
}
|
|
}
|
|
break;
|
|
case VirtualKeys.RightDoors:
|
|
if (!OpenbveFcmbTrainPlugin.KeysPressed[(int)key])
|
|
{
|
|
// Unselect doors automatically when closing
|
|
if ((train.DoorState & DoorStates.Right) > 0 && !RightDoorsClosing && (RequestedDoorInterlock == DoorInterlockStates.Right || RequestedDoorInterlock == DoorInterlockStates.Unlocked))
|
|
{
|
|
RightDoorsClosing = true;
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Right ? DoorInterlockStates.Locked : DoorInterlockStates.Left;
|
|
}
|
|
else
|
|
{
|
|
RightDoorsClosing = false;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
if ((oldState == DoorStates.Both || oldState == DoorStates.Left) && (newState == DoorStates.None || newState == DoorStates.Right))
|
|
{
|
|
LeftDoorsClosing = false;
|
|
// Unselect doors automatically when closed, just in case
|
|
if (RequestedDoorInterlock == DoorInterlockStates.Unlocked || RequestedDoorInterlock == DoorInterlockStates.Left)
|
|
{
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Left ? DoorInterlockStates.Locked : DoorInterlockStates.Right;
|
|
}
|
|
}
|
|
if ((oldState == DoorStates.Both || oldState == DoorStates.Right) && (newState == DoorStates.None || newState == DoorStates.Left))
|
|
{
|
|
RightDoorsClosing = false;
|
|
// Unselect doors automatically when closed, just in case
|
|
if (RequestedDoorInterlock == DoorInterlockStates.Unlocked || RequestedDoorInterlock == DoorInterlockStates.Right)
|
|
{
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Right ? DoorInterlockStates.Locked : DoorInterlockStates.Left;
|
|
}
|
|
}
|
|
if (RequireDoorClosingSound)
|
|
{
|
|
if ((oldState == DoorStates.None || oldState == DoorStates.Left) && (newState == DoorStates.Both || newState == DoorStates.Right))
|
|
{
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Left ? DoorInterlockStates.Left : DoorInterlockStates.Locked;
|
|
}
|
|
if ((oldState == DoorStates.None || oldState == DoorStates.Right) && (newState == DoorStates.Both || newState == DoorStates.Left))
|
|
{
|
|
RequestedDoorInterlock = RequestedDoorInterlock == DoorInterlockStates.Right ? DoorInterlockStates.Right : DoorInterlockStates.Locked;
|
|
}
|
|
}
|
|
|
|
// Set arrival time when doors open
|
|
if (oldState == DoorStates.None && newState != DoorStates.None)
|
|
{
|
|
DoorOpenTime = CurrentTime;
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
// Select doors to be opened on the current stop, at least 200 meters before the stop point.
|
|
if (route.CurrentStation.PlayerStops() && train.State.Location >= route.CurrentStation.StopPosition - 200 && route.StationState == Route.StationStates.Pending)
|
|
{
|
|
switch (RequestedDoorInterlock)
|
|
{
|
|
case DoorInterlockStates.Unlocked:
|
|
if (!route.CurrentStation.OpenLeftDoors)
|
|
{
|
|
KeyChange(VirtualKeys.G, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.G, false, train);
|
|
}
|
|
if (!route.CurrentStation.OpenRightDoors)
|
|
{
|
|
KeyChange(VirtualKeys.H, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.H, false, train);
|
|
}
|
|
break;
|
|
case DoorInterlockStates.Left:
|
|
if (!route.CurrentStation.OpenLeftDoors)
|
|
{
|
|
KeyChange(VirtualKeys.G, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.G, false, train);
|
|
}
|
|
if (route.CurrentStation.OpenRightDoors)
|
|
{
|
|
KeyChange(VirtualKeys.H, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.H, false, train);
|
|
}
|
|
break;
|
|
case DoorInterlockStates.Right:
|
|
if (route.CurrentStation.OpenLeftDoors)
|
|
{
|
|
KeyChange(VirtualKeys.G, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.G, false, train);
|
|
}
|
|
if (!route.CurrentStation.OpenRightDoors)
|
|
{
|
|
KeyChange(VirtualKeys.H, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.H, false, train);
|
|
}
|
|
break;
|
|
case DoorInterlockStates.Locked:
|
|
if (route.CurrentStation.OpenLeftDoors)
|
|
{
|
|
KeyChange(VirtualKeys.G, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.G, false, train);
|
|
}
|
|
if (route.CurrentStation.OpenRightDoors)
|
|
{
|
|
KeyChange(VirtualKeys.H, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.H, false, train);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (AiTriggerDoorClosingSound)
|
|
{
|
|
KeyChange(VirtualKeys.F, true, train);
|
|
data.Response = AIResponse.Short;
|
|
KeyChange(VirtualKeys.F, false, train);
|
|
AiTriggerDoorClosingSound = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|