Airlock hacking (#329)
* Airlock hacking * Added status text * Whoops don't need this * Update Content.Server/GameObjects/Components/Doors/AirlockComponent.cs Co-Authored-By: Pieter-Jan Briers <pieterjan.briers@gmail.com> * ComponentReference ServerDoorComponent * Suggested name
This commit is contained in:
committed by
Pieter-Jan Briers
parent
34f4731c9b
commit
36078382e4
@@ -107,6 +107,7 @@ namespace Content.Client
|
|||||||
"Access",
|
"Access",
|
||||||
"AccessReader",
|
"AccessReader",
|
||||||
"IdCardConsole",
|
"IdCardConsole",
|
||||||
|
"Airlock",
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var ignoreName in registerIgnore)
|
foreach (var ignoreName in registerIgnore)
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
|||||||
protected override void UpdateState(BoundUserInterfaceState state)
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
base.UpdateState(state);
|
base.UpdateState(state);
|
||||||
var castState = (WiresBoundUserInterfaceState) state;
|
_menu.Populate((WiresBoundUserInterfaceState) state);
|
||||||
_menu.Populate(castState.WiresList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PerformAction(Guid guid, WiresAction action)
|
public void PerformAction(Guid guid, WiresAction action)
|
||||||
|
|||||||
@@ -16,26 +16,26 @@ namespace Content.Client.GameObjects.Components.Wires
|
|||||||
protected override Vector2? CustomSize => (300, 450);
|
protected override Vector2? CustomSize => (300, 450);
|
||||||
public WiresBoundUserInterface Owner { get; set; }
|
public WiresBoundUserInterface Owner { get; set; }
|
||||||
|
|
||||||
private readonly VBoxContainer _rows;
|
private readonly VBoxContainer _wiresContainer;
|
||||||
|
|
||||||
public WiresMenu()
|
public WiresMenu()
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this); // TODO: Remove this and use DynamicTypeFactory?
|
IoCManager.InjectDependencies(this); // TODO: Remove this and use DynamicTypeFactory?
|
||||||
Title = _localizationManager.GetString("Wires");
|
Title = _localizationManager.GetString("Wires");
|
||||||
_rows = new VBoxContainer();
|
_wiresContainer = new VBoxContainer();
|
||||||
Contents.AddChild(_rows);
|
Contents.AddChild(_wiresContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Populate(List<ClientWire> wiresList)
|
public void Populate(WiresBoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
_rows.RemoveAllChildren();
|
_wiresContainer.RemoveAllChildren();
|
||||||
foreach (var entry in wiresList)
|
foreach (var wire in state.WiresList)
|
||||||
{
|
{
|
||||||
var container = new HBoxContainer();
|
var container = new HBoxContainer();
|
||||||
var newLabel = new Label()
|
var newLabel = new Label()
|
||||||
{
|
{
|
||||||
Text = $"{_localizationManager.GetString(entry.Color.Name())}: ",
|
Text = $"{_localizationManager.GetString(wire.Color.Name())}: ",
|
||||||
FontColorOverride = entry.Color,
|
FontColorOverride = wire.Color,
|
||||||
};
|
};
|
||||||
container.AddChild(newLabel);
|
container.AddChild(newLabel);
|
||||||
|
|
||||||
@@ -43,16 +43,26 @@ namespace Content.Client.GameObjects.Components.Wires
|
|||||||
{
|
{
|
||||||
Text = _localizationManager.GetString("Pulse"),
|
Text = _localizationManager.GetString("Pulse"),
|
||||||
};
|
};
|
||||||
newButton.OnPressed += _ => Owner.PerformAction(entry.Guid, WiresAction.Pulse);
|
newButton.OnPressed += _ => Owner.PerformAction(wire.Guid, WiresAction.Pulse);
|
||||||
container.AddChild(newButton);
|
container.AddChild(newButton);
|
||||||
|
|
||||||
newButton = new Button()
|
newButton = new Button()
|
||||||
{
|
{
|
||||||
Text = entry.IsCut ? _localizationManager.GetString("Mend") : _localizationManager.GetString("Cut"),
|
Text = wire.IsCut ? _localizationManager.GetString("Mend") : _localizationManager.GetString("Cut"),
|
||||||
};
|
};
|
||||||
newButton.OnPressed += _ => Owner.PerformAction(entry.Guid, entry.IsCut ? WiresAction.Mend : WiresAction.Cut);
|
newButton.OnPressed += _ => Owner.PerformAction(wire.Guid, wire.IsCut ? WiresAction.Mend : WiresAction.Cut);
|
||||||
container.AddChild(newButton);
|
container.AddChild(newButton);
|
||||||
_rows.AddChild(container);
|
_wiresContainer.AddChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var status in state.Statuses)
|
||||||
|
{
|
||||||
|
var container = new HBoxContainer();
|
||||||
|
container.AddChild(new Label
|
||||||
|
{
|
||||||
|
Text = status
|
||||||
|
});
|
||||||
|
_wiresContainer.AddChild(container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
191
Content.Server/GameObjects/Components/Doors/AirlockComponent.cs
Normal file
191
Content.Server/GameObjects/Components/Doors/AirlockComponent.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Content.Server.GameObjects.Components.Interactable.Tools;
|
||||||
|
using Content.Server.GameObjects.Components.Power;
|
||||||
|
using Content.Server.GameObjects.Components.VendingMachines;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using static Content.Shared.GameObjects.Components.SharedWiresComponent.WiresAction;
|
||||||
|
using Timer = Robust.Shared.Timers.Timer;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Doors
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IActivate))]
|
||||||
|
[ComponentReference(typeof(ServerDoorComponent))]
|
||||||
|
public class AirlockComponent : ServerDoorComponent, IWires, IAttackBy
|
||||||
|
{
|
||||||
|
public override string Name => "Airlock";
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly ILocalizationManager _localizationMgr;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duration for which power will be disabled after pulsing either power wire.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0);
|
||||||
|
|
||||||
|
private PowerDeviceComponent _powerDevice;
|
||||||
|
private WiresComponent _wires;
|
||||||
|
|
||||||
|
private CancellationTokenSource _powerWiresPulsedTimerCancel;
|
||||||
|
|
||||||
|
private bool _powerWiresPulsed;
|
||||||
|
/// <summary>
|
||||||
|
/// True if either power wire was pulsed in the last <see cref="PowerWiresTimeout"/>.
|
||||||
|
/// </summary>
|
||||||
|
private bool PowerWiresPulsed
|
||||||
|
{
|
||||||
|
get => _powerWiresPulsed;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_powerWiresPulsed = value;
|
||||||
|
UpdateWiresStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWiresStatus()
|
||||||
|
{
|
||||||
|
var powerMessage = "A yellow light is on.";
|
||||||
|
if (_powerWiresPulsed)
|
||||||
|
{
|
||||||
|
powerMessage = "A yellow light is blinking rapidly.";
|
||||||
|
} else if (_wires.IsWireCut(Wires.MainPower) &&
|
||||||
|
_wires.IsWireCut(Wires.BackupPower))
|
||||||
|
{
|
||||||
|
powerMessage = "A red light is on.";
|
||||||
|
}
|
||||||
|
_wires.SetStatus(WiresStatus.PowerIndicator, _localizationMgr.GetString(powerMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
|
||||||
|
_wires = Owner.GetComponent<WiresComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ActivateImpl(ActivateEventArgs args)
|
||||||
|
{
|
||||||
|
if (_wires.IsOpen)
|
||||||
|
{
|
||||||
|
if (args.User.TryGetComponent(out IActorComponent actor))
|
||||||
|
{
|
||||||
|
_wires.OpenInterface(actor.playerSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.ActivateImpl(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum WiresStatus
|
||||||
|
{
|
||||||
|
PowerIndicator,
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterWires(WiresComponent.WiresBuilder builder)
|
||||||
|
{
|
||||||
|
builder.CreateWire(Wires.MainPower);
|
||||||
|
builder.CreateWire(Wires.BackupPower);
|
||||||
|
UpdateWiresStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WiresUpdate(WiresUpdateEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Action == Pulse)
|
||||||
|
{
|
||||||
|
switch (args.Identifier)
|
||||||
|
{
|
||||||
|
case Wires.MainPower:
|
||||||
|
case Wires.BackupPower:
|
||||||
|
PowerWiresPulsed = true;
|
||||||
|
_powerWiresPulsedTimerCancel?.Cancel();
|
||||||
|
_powerWiresPulsedTimerCancel = new CancellationTokenSource();
|
||||||
|
Timer.Spawn(PowerWiresTimeout,
|
||||||
|
() => PowerWiresPulsed = false,
|
||||||
|
_powerWiresPulsedTimerCancel.Token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Action == Mend)
|
||||||
|
{
|
||||||
|
switch (args.Identifier)
|
||||||
|
{
|
||||||
|
case Wires.MainPower:
|
||||||
|
case Wires.BackupPower:
|
||||||
|
// mending power wires instantly restores power
|
||||||
|
_powerWiresPulsedTimerCancel?.Cancel();
|
||||||
|
PowerWiresPulsed = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateWiresStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanOpen()
|
||||||
|
{
|
||||||
|
return IsPowered();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Deny()
|
||||||
|
{
|
||||||
|
if (!IsPowered())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
base.Deny();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPowered()
|
||||||
|
{
|
||||||
|
if (PowerWiresPulsed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_wires.IsWireCut(Wires.MainPower) &&
|
||||||
|
_wires.IsWireCut(Wires.BackupPower))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _powerDevice.Powered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AttackBy(AttackByEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs.AttackWith.HasComponent<CrowbarComponent>() && !IsPowered())
|
||||||
|
{
|
||||||
|
if (_state == DoorState.Closed)
|
||||||
|
{
|
||||||
|
Open();
|
||||||
|
}
|
||||||
|
else if(_state == DoorState.Open)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ namespace Content.Server.GameObjects
|
|||||||
{
|
{
|
||||||
public override string Name => "Door";
|
public override string Name => "Door";
|
||||||
|
|
||||||
private DoorState _state = DoorState.Closed;
|
protected DoorState _state = DoorState.Closed;
|
||||||
|
|
||||||
private float OpenTimeCounter;
|
private float OpenTimeCounter;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ namespace Content.Server.GameObjects
|
|||||||
base.OnRemove();
|
base.OnRemove();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
protected virtual void ActivateImpl(ActivateEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (_state == DoorState.Open)
|
if (_state == DoorState.Open)
|
||||||
{
|
{
|
||||||
@@ -57,6 +57,11 @@ namespace Content.Server.GameObjects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
ActivateImpl(eventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||||
{
|
{
|
||||||
base.HandleMessage(message, netChannel, component);
|
base.HandleMessage(message, netChannel, component);
|
||||||
@@ -80,15 +85,27 @@ namespace Content.Server.GameObjects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual bool CanOpen()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanOpen(IEntity user)
|
||||||
|
{
|
||||||
|
if (!CanOpen()) return false;
|
||||||
|
if (!Owner.TryGetComponent(out AccessReader accessReader))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return accessReader.IsAllowed(user);
|
||||||
|
}
|
||||||
|
|
||||||
public void TryOpen(IEntity user)
|
public void TryOpen(IEntity user)
|
||||||
{
|
{
|
||||||
if (Owner.TryGetComponent(out AccessReader accessReader))
|
if (!CanOpen(user))
|
||||||
{
|
{
|
||||||
if (!accessReader.IsAllowed(user))
|
Deny();
|
||||||
{
|
return;
|
||||||
Deny();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Open();
|
Open();
|
||||||
}
|
}
|
||||||
@@ -135,7 +152,7 @@ namespace Content.Server.GameObjects
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deny()
|
public virtual void Deny()
|
||||||
{
|
{
|
||||||
_appearance.SetData(DoorVisuals.VisualState, DoorVisualState.Deny);
|
_appearance.SetData(DoorVisuals.VisualState, DoorVisualState.Deny);
|
||||||
Timer.Spawn(DenyTime, () =>
|
Timer.Spawn(DenyTime, () =>
|
||||||
@@ -145,7 +162,7 @@ namespace Content.Server.GameObjects
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const float AUTO_CLOSE_DELAY = 5;
|
private const float AUTO_CLOSE_DELAY = 5;
|
||||||
public void OnUpdate(float frameTime)
|
public virtual void OnUpdate(float frameTime)
|
||||||
{
|
{
|
||||||
if (_state != DoorState.Open)
|
if (_state != DoorState.Open)
|
||||||
{
|
{
|
||||||
@@ -163,7 +180,7 @@ namespace Content.Server.GameObjects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum DoorState
|
protected enum DoorState
|
||||||
{
|
{
|
||||||
Closed,
|
Closed,
|
||||||
Open,
|
Open,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Doors;
|
||||||
using Content.Server.GameObjects.Components.Interactable.Tools;
|
using Content.Server.GameObjects.Components.Interactable.Tools;
|
||||||
using Content.Server.GameObjects.Components.VendingMachines;
|
using Content.Server.GameObjects.Components.VendingMachines;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
@@ -50,6 +52,11 @@ namespace Content.Server.GameObjects.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly List<Wire> WiresList = new List<Wire>();
|
public readonly List<Wire> WiresList = new List<Wire>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status messages are displayed at the bottom of the UI.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<object, string> _statuses = new Dictionary<object, string>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// As seen on /vg/station.
|
/// As seen on /vg/station.
|
||||||
/// <see cref="AssignColor"/> and <see cref="WiresBuilder.CreateWire"/>.
|
/// <see cref="AssignColor"/> and <see cref="WiresBuilder.CreateWire"/>.
|
||||||
@@ -90,6 +97,17 @@ namespace Content.Server.GameObjects.Components
|
|||||||
UpdateUserInterface();
|
UpdateUserInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the wire associated with <see cref="identifier"/> is cut.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
|
public bool IsWireCut(object identifier)
|
||||||
|
{
|
||||||
|
var wire = WiresList.Find(x => x.Identifier.Equals(identifier));
|
||||||
|
if(wire == null) throw new ArgumentException();
|
||||||
|
return wire.IsCut;
|
||||||
|
}
|
||||||
|
|
||||||
public class Wire
|
public class Wire
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -233,7 +251,7 @@ namespace Content.Server.GameObjects.Components
|
|||||||
{
|
{
|
||||||
clientList.Add(new ClientWire(entry.Guid, entry.Color, entry.IsCut));
|
clientList.Add(new ClientWire(entry.Guid, entry.Color, entry.IsCut));
|
||||||
}
|
}
|
||||||
_userInterface.SetState(new WiresBoundUserInterfaceState(clientList));
|
_userInterface.SetState(new WiresBoundUserInterfaceState(clientList, _statuses.Values.ToList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IAttackBy.AttackBy(AttackByEventArgs eventArgs)
|
bool IAttackBy.AttackBy(AttackByEventArgs eventArgs)
|
||||||
@@ -247,5 +265,18 @@ namespace Content.Server.GameObjects.Components
|
|||||||
{
|
{
|
||||||
message.AddText($"The maintenance panel is {(IsOpen ? "open" : "closed")}.");
|
message.AddText($"The maintenance panel is {(IsOpen ? "open" : "closed")}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetStatus(object statusIdentifier, string newMessage)
|
||||||
|
{
|
||||||
|
if (_statuses.TryGetValue(statusIdentifier, out var storedMessage))
|
||||||
|
{
|
||||||
|
if (storedMessage == newMessage)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_statuses[statusIdentifier] = newMessage;
|
||||||
|
UpdateUserInterface();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,12 @@ namespace Content.Shared.GameObjects.Components
|
|||||||
public class WiresBoundUserInterfaceState : BoundUserInterfaceState
|
public class WiresBoundUserInterfaceState : BoundUserInterfaceState
|
||||||
{
|
{
|
||||||
public readonly List<ClientWire> WiresList;
|
public readonly List<ClientWire> WiresList;
|
||||||
|
public readonly List<string> Statuses;
|
||||||
|
|
||||||
public WiresBoundUserInterfaceState(List<ClientWire> wiresList)
|
public WiresBoundUserInterfaceState(List<ClientWire> wiresList, List<string> statuses)
|
||||||
{
|
{
|
||||||
WiresList = wiresList;
|
WiresList = wiresList;
|
||||||
|
Statuses = statuses;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
- state: closed_unlit
|
- state: closed_unlit
|
||||||
shader: unshaded
|
shader: unshaded
|
||||||
map: ["enum.DoorVisualLayers.BaseUnlit"]
|
map: ["enum.DoorVisualLayers.BaseUnlit"]
|
||||||
|
- state: panel_open
|
||||||
|
map: ["enum.WiresVisualLayers.MaintenancePanel"]
|
||||||
|
|
||||||
- type: Icon
|
- type: Icon
|
||||||
sprite: Buildings/airlock_basic.rsi
|
sprite: Buildings/airlock_basic.rsi
|
||||||
@@ -25,13 +27,19 @@
|
|||||||
- !type:PhysShapeAabb
|
- !type:PhysShapeAabb
|
||||||
mask: 2
|
mask: 2
|
||||||
layer: 16
|
layer: 16
|
||||||
- type: Door
|
- type: Airlock
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
visuals:
|
visuals:
|
||||||
- type: AirlockVisualizer2D
|
- type: AirlockVisualizer2D
|
||||||
open_sound: /Audio/machines/airlock_open.ogg
|
open_sound: /Audio/machines/airlock_open.ogg
|
||||||
close_sound: /Audio/machines/airlock_close.ogg
|
close_sound: /Audio/machines/airlock_close.ogg
|
||||||
deny_sound: /Audio/machines/airlock_deny.ogg
|
deny_sound: /Audio/machines/airlock_deny.ogg
|
||||||
|
- type: WiresVisualizer2D
|
||||||
|
- type: PowerDevice
|
||||||
|
- type: Wires
|
||||||
|
- type: UserInterface
|
||||||
|
interfaces:
|
||||||
|
- key: enum.WiresUiKey.Key
|
||||||
|
type: WiresBoundUserInterface
|
||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
|
|||||||
Reference in New Issue
Block a user