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; /// Whether the door closing sound is required to be played to close the doors. private bool RequireDoorClosingSound; /// The duration of the door closing sound, in seconds. private double DoorClosingSoundDuration = 5; /// The timeout after which the door closing sound needs to be played again, in seconds. private double DoorClosingSoundTimeout = 15; /// The counter for the door closing sound. private double DoorClosingSoundCounter; /// Whether to trigger a door unlock. private bool TriggerDoorUnlock; /// Whether to trigger a door lock. private bool TriggerDoorLock; /// Whether the AI should trigger the door closing sound. private bool AiTriggerDoorClosingSound; /// The timeout after which the AI needs to close doors if they become stuck, in seconds. private double AiDoorStuckTimeout = 10; /// The counter for the AI to close doors if they become stuck. private double AiDoorStuckCounter; /// The index of the door closing sound. private int DoorClosingSoundIndex = 10; /// The current time. private Time CurrentTime = new Time(0); /// The time when the doors last opened. private Time DoorOpenTime = new Time(0); /// 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) { // 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; } /// 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.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 } } /// 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) { 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; } } /// 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) { // 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; } } } }