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:
DamianX
2019-09-06 10:05:02 +02:00
committed by Pieter-Jan Briers
parent 34f4731c9b
commit 36078382e4
8 changed files with 288 additions and 29 deletions

View File

@@ -107,6 +107,7 @@ namespace Content.Client
"Access", "Access",
"AccessReader", "AccessReader",
"IdCardConsole", "IdCardConsole",
"Airlock",
}; };
foreach (var ignoreName in registerIgnore) foreach (var ignoreName in registerIgnore)

View File

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

View File

@@ -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);
} }
} }

View 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;
}
}
}

View File

@@ -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,16 +85,28 @@ 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(); Deny();
return; 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,

View File

@@ -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();
}
} }
} }

View File

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

View File

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