Files
tbd-station-14/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs

461 lines
15 KiB
C#

#nullable enable
using System;
using System.Threading;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.VendingMachines;
using Content.Server.Interfaces.GameObjects.Components.Doors;
using Content.Shared.GameObjects.Components.Doors;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
using static Content.Shared.GameObjects.Components.SharedWiresComponent;
using static Content.Shared.GameObjects.Components.SharedWiresComponent.WiresAction;
namespace Content.Server.GameObjects.Components.Doors
{
/// <summary>
/// Companion component to ServerDoorComponent that handles airlock-specific behavior -- wires, requiring power to operate, bolts, and allowing automatic closing.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IDoorCheck))]
public class AirlockComponent : Component, IWires, IDoorCheck
{
public override string Name => "Airlock";
[ComponentDependency]
private readonly ServerDoorComponent? _doorComponent = null;
[ComponentDependency]
private readonly SharedAppearanceComponent? _appearanceComponent = null;
[ComponentDependency]
private readonly PowerReceiverComponent? _receiverComponent = null;
[ComponentDependency]
private readonly WiresComponent? _wiresComponent = null;
/// <summary>
/// Duration for which power will be disabled after pulsing either power wire.
/// </summary>
private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0);
private CancellationTokenSource _powerWiresPulsedTimerCancel = new();
private bool _powerWiresPulsed;
/// <summary>
/// True if either power wire was pulsed in the last <see cref="PowerWiresTimeout"/>.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private bool PowerWiresPulsed
{
get => _powerWiresPulsed;
set
{
_powerWiresPulsed = value;
UpdateWiresStatus();
UpdatePowerCutStatus();
}
}
private bool _boltsDown;
[ViewVariables(VVAccess.ReadWrite)]
public bool BoltsDown
{
get => _boltsDown;
set
{
_boltsDown = value;
UpdateBoltLightStatus();
}
}
private bool _boltLightsWirePulsed = true;
[ViewVariables(VVAccess.ReadWrite)]
private bool BoltLightsVisible
{
get => _boltLightsWirePulsed && BoltsDown && IsPowered()
&& _doorComponent != null && _doorComponent.State == SharedDoorComponent.DoorState.Closed;
set
{
_boltLightsWirePulsed = value;
UpdateBoltLightStatus();
}
}
private static readonly TimeSpan AutoCloseDelayFast = TimeSpan.FromSeconds(1);
[ViewVariables(VVAccess.ReadWrite)]
private bool _autoClose = true;
[ViewVariables(VVAccess.ReadWrite)]
private bool _normalCloseSpeed = true;
[ViewVariables(VVAccess.ReadWrite)]
private bool _safety = true;
public override void Initialize()
{
base.Initialize();
if (_receiverComponent != null && _appearanceComponent != null)
{
_appearanceComponent.SetData(DoorVisuals.Powered, _receiverComponent.Powered);
}
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
switch (message)
{
case PowerChangedMessage powerChanged:
PowerDeviceOnOnPowerStateChanged(powerChanged);
break;
}
}
void IDoorCheck.OnStateChange(SharedDoorComponent.DoorState doorState)
{
// Only show the maintenance panel if the airlock is closed
if (_wiresComponent != null)
{
_wiresComponent.IsPanelVisible = doorState != SharedDoorComponent.DoorState.Open;
}
// If the door is closed, we should look if the bolt was locked while closing
UpdateBoltLightStatus();
}
bool IDoorCheck.OpenCheck() => CanChangeState();
bool IDoorCheck.CloseCheck() => CanChangeState();
bool IDoorCheck.DenyCheck() => CanChangeState();
bool IDoorCheck.SafetyCheck() => _safety;
bool IDoorCheck.AutoCloseCheck() => _autoClose;
TimeSpan? IDoorCheck.GetCloseSpeed()
{
if (_normalCloseSpeed)
{
return null;
}
return AutoCloseDelayFast;
}
bool IDoorCheck.BlockActivate(ActivateEventArgs eventArgs)
{
if (_wiresComponent != null && _wiresComponent.IsPanelOpen &&
eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
_wiresComponent.OpenInterface(actor.playerSession);
return true;
}
return false;
}
bool IDoorCheck.CanPryCheck(InteractUsingEventArgs eventArgs)
{
if (IsBolted())
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("The airlock's bolts prevent it from being forced!"));
return false;
}
if (IsPowered())
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("The powered motors block your efforts!"));
return false;
}
return true;
}
private bool CanChangeState()
{
return IsPowered() && !IsBolted();
}
private bool IsBolted()
{
return _boltsDown;
}
private bool IsPowered()
{
return _receiverComponent == null || _receiverComponent.Powered;
}
private void UpdateBoltLightStatus()
{
if (_appearanceComponent != null)
{
_appearanceComponent.SetData(DoorVisuals.BoltLights, BoltLightsVisible);
}
}
private void UpdateWiresStatus()
{
if (_doorComponent == null)
{
return;
}
var powerLight = new StatusLightData(Color.Yellow, StatusLightState.On, "POWR");
if (PowerWiresPulsed)
{
powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR");
}
else if (_wiresComponent != null &&
_wiresComponent.IsWireCut(Wires.MainPower) &&
_wiresComponent.IsWireCut(Wires.BackupPower))
{
powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR");
}
var boltStatus =
new StatusLightData(Color.Red, BoltsDown ? StatusLightState.On : StatusLightState.Off, "BOLT");
var boltLightsStatus = new StatusLightData(Color.Lime,
_boltLightsWirePulsed ? StatusLightState.On : StatusLightState.Off, "BLTL");
var timingStatus =
new StatusLightData(Color.Orange, !_autoClose ? StatusLightState.Off :
!_normalCloseSpeed ? StatusLightState.BlinkingSlow :
StatusLightState.On,
"TIME");
var safetyStatus =
new StatusLightData(Color.Red, _safety ? StatusLightState.On : StatusLightState.Off, "SAFE");
if (_wiresComponent == null)
{
return;
}
_wiresComponent.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
_wiresComponent.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
_wiresComponent.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
_wiresComponent.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT"));
_wiresComponent.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus);
_wiresComponent.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus);
/*
_wires.SetStatus(6, powerLight);
_wires.SetStatus(7, powerLight);
_wires.SetStatus(8, powerLight);
_wires.SetStatus(9, powerLight);
_wires.SetStatus(10, powerLight);
_wires.SetStatus(11, powerLight);*/
}
private void UpdatePowerCutStatus()
{
if (_receiverComponent == null)
{
return;
}
if (PowerWiresPulsed)
{
_receiverComponent.PowerDisabled = true;
return;
}
if (_wiresComponent == null)
{
return;
}
_receiverComponent.PowerDisabled =
_wiresComponent.IsWireCut(Wires.MainPower) ||
_wiresComponent.IsWireCut(Wires.BackupPower);
}
private void PowerDeviceOnOnPowerStateChanged(PowerChangedMessage e)
{
if (_appearanceComponent != null)
{
_appearanceComponent.SetData(DoorVisuals.Powered, e.Powered);
}
// BoltLights also got out
UpdateBoltLightStatus();
}
private enum Wires
{
/// <summary>
/// Pulsing turns off power for <see cref="AirlockComponent.PowerWiresTimeout"/>.
/// Cutting turns off power permanently if <see cref="BackupPower"/> is also cut.
/// Mending restores power.
/// </summary>
MainPower,
/// <see cref="MainPower"/>
BackupPower,
/// <summary>
/// Pulsing causes for bolts to toggle (but only raise if power is on)
/// Cutting causes Bolts to drop
/// Mending does nothing
/// </summary>
Bolts,
/// <summary>
/// Pulsing causes light to toggle
/// Cutting causes light to go out
/// Mending causes them to go on again
/// </summary>
BoltLight,
// Placeholder for when AI is implemented
AIControl,
/// <summary>
/// Pulsing causes door to close faster
/// Cutting disables door timer, causing door to stop closing automatically
/// Mending restores door timer
/// </summary>
Timing,
/// <summary>
/// Pulsing toggles safety
/// Cutting disables safety
/// Mending enables safety
/// </summary>
Safety,
}
public void RegisterWires(WiresComponent.WiresBuilder builder)
{
builder.CreateWire(Wires.MainPower);
builder.CreateWire(Wires.BackupPower);
builder.CreateWire(Wires.Bolts);
builder.CreateWire(Wires.BoltLight);
builder.CreateWire(Wires.Timing);
builder.CreateWire(Wires.Safety);
/*
builder.CreateWire(6);
builder.CreateWire(7);
builder.CreateWire(8);
builder.CreateWire(9);
builder.CreateWire(10);
builder.CreateWire(11);*/
UpdateWiresStatus();
}
public void WiresUpdate(WiresUpdateEventArgs args)
{
if(_doorComponent == null)
{
return;
}
if (args.Action == Pulse)
{
switch (args.Identifier)
{
case Wires.MainPower:
case Wires.BackupPower:
PowerWiresPulsed = true;
_powerWiresPulsedTimerCancel.Cancel();
_powerWiresPulsedTimerCancel = new CancellationTokenSource();
Owner.SpawnTimer(PowerWiresTimeout,
() => PowerWiresPulsed = false,
_powerWiresPulsedTimerCancel.Token);
break;
case Wires.Bolts:
if (!BoltsDown)
{
SetBoltsWithAudio(true);
}
else
{
if (IsPowered()) // only raise again if powered
{
SetBoltsWithAudio(false);
}
}
break;
case Wires.BoltLight:
// we need to change the property here to set the appearance again
BoltLightsVisible = !_boltLightsWirePulsed;
break;
case Wires.Timing:
_normalCloseSpeed = !_normalCloseSpeed;
_doorComponent.RefreshAutoClose();
break;
case Wires.Safety:
_safety = !_safety;
break;
}
}
else if (args.Action == Mend)
{
switch (args.Identifier)
{
case Wires.MainPower:
case Wires.BackupPower:
// mending power wires instantly restores power
_powerWiresPulsedTimerCancel?.Cancel();
PowerWiresPulsed = false;
break;
case Wires.BoltLight:
BoltLightsVisible = true;
break;
case Wires.Timing:
_autoClose = true;
_doorComponent.RefreshAutoClose();
break;
case Wires.Safety:
_safety = true;
break;
}
}
else if (args.Action == Cut)
{
switch (args.Identifier)
{
case Wires.Bolts:
SetBoltsWithAudio(true);
break;
case Wires.BoltLight:
BoltLightsVisible = false;
break;
case Wires.Timing:
_autoClose = false;
_doorComponent.RefreshAutoClose();
break;
case Wires.Safety:
_safety = false;
break;
}
}
UpdateWiresStatus();
UpdatePowerCutStatus();
}
public void SetBoltsWithAudio(bool newBolts)
{
if (newBolts == BoltsDown)
{
return;
}
BoltsDown = newBolts;
EntitySystem.Get<AudioSystem>()
.PlayFromEntity(newBolts ? "/Audio/Machines/boltsdown.ogg" : "/Audio/Machines/boltsup.ogg", Owner);
}
}
}