From 36078382e4a883c32fc93f104135478742abe6dd Mon Sep 17 00:00:00 2001 From: DamianX Date: Fri, 6 Sep 2019 10:05:02 +0200 Subject: [PATCH] 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 * ComponentReference ServerDoorComponent * Suggested name --- Content.Client/EntryPoint.cs | 1 + .../Wires/WiresBoundUserInterface.cs | 3 +- .../GameObjects/Components/Wires/WiresMenu.cs | 34 ++-- .../Components/Doors/AirlockComponent.cs | 191 ++++++++++++++++++ .../Components/Doors/ServerDoorComponent.cs | 39 +++- .../GameObjects/Components/WiresComponent.cs | 33 ++- .../Components/SharedWiresComponent.cs | 4 +- .../Entities/buildings/airlock_base.yml | 12 +- 8 files changed, 288 insertions(+), 29 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Doors/AirlockComponent.cs diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index c82ac92871..a21e35dd60 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -107,6 +107,7 @@ namespace Content.Client "Access", "AccessReader", "IdCardConsole", + "Airlock", }; foreach (var ignoreName in registerIgnore) diff --git a/Content.Client/GameObjects/Components/Wires/WiresBoundUserInterface.cs b/Content.Client/GameObjects/Components/Wires/WiresBoundUserInterface.cs index f6f9db605a..c88f87ef00 100644 --- a/Content.Client/GameObjects/Components/Wires/WiresBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Wires/WiresBoundUserInterface.cs @@ -25,8 +25,7 @@ namespace Content.Client.GameObjects.Components.Wires protected override void UpdateState(BoundUserInterfaceState state) { base.UpdateState(state); - var castState = (WiresBoundUserInterfaceState) state; - _menu.Populate(castState.WiresList); + _menu.Populate((WiresBoundUserInterfaceState) state); } public void PerformAction(Guid guid, WiresAction action) diff --git a/Content.Client/GameObjects/Components/Wires/WiresMenu.cs b/Content.Client/GameObjects/Components/Wires/WiresMenu.cs index b05bfa95b8..f2e516df26 100644 --- a/Content.Client/GameObjects/Components/Wires/WiresMenu.cs +++ b/Content.Client/GameObjects/Components/Wires/WiresMenu.cs @@ -16,26 +16,26 @@ namespace Content.Client.GameObjects.Components.Wires protected override Vector2? CustomSize => (300, 450); public WiresBoundUserInterface Owner { get; set; } - private readonly VBoxContainer _rows; + private readonly VBoxContainer _wiresContainer; public WiresMenu() { IoCManager.InjectDependencies(this); // TODO: Remove this and use DynamicTypeFactory? Title = _localizationManager.GetString("Wires"); - _rows = new VBoxContainer(); - Contents.AddChild(_rows); + _wiresContainer = new VBoxContainer(); + Contents.AddChild(_wiresContainer); } - public void Populate(List wiresList) + public void Populate(WiresBoundUserInterfaceState state) { - _rows.RemoveAllChildren(); - foreach (var entry in wiresList) + _wiresContainer.RemoveAllChildren(); + foreach (var wire in state.WiresList) { var container = new HBoxContainer(); var newLabel = new Label() { - Text = $"{_localizationManager.GetString(entry.Color.Name())}: ", - FontColorOverride = entry.Color, + Text = $"{_localizationManager.GetString(wire.Color.Name())}: ", + FontColorOverride = wire.Color, }; container.AddChild(newLabel); @@ -43,16 +43,26 @@ namespace Content.Client.GameObjects.Components.Wires { Text = _localizationManager.GetString("Pulse"), }; - newButton.OnPressed += _ => Owner.PerformAction(entry.Guid, WiresAction.Pulse); + newButton.OnPressed += _ => Owner.PerformAction(wire.Guid, WiresAction.Pulse); container.AddChild(newButton); 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); - _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); } } diff --git a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs new file mode 100644 index 0000000000..000d8b331e --- /dev/null +++ b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs @@ -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 + + /// + /// Duration for which power will be disabled after pulsing either power wire. + /// + private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0); + + private PowerDeviceComponent _powerDevice; + private WiresComponent _wires; + + private CancellationTokenSource _powerWiresPulsedTimerCancel; + + private bool _powerWiresPulsed; + /// + /// True if either power wire was pulsed in the last . + /// + 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(); + _wires = Owner.GetComponent(); + } + + 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 + { + /// + /// Pulsing turns off power for . + /// Cutting turns off power permanently if is also cut. + /// Mending restores power. + /// + 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() && !IsPowered()) + { + if (_state == DoorState.Closed) + { + Open(); + } + else if(_state == DoorState.Open) + { + Close(); + } + return true; + } + + return false; + } + } +} diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs index 60d9bf7197..a3928b7449 100644 --- a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs @@ -17,7 +17,7 @@ namespace Content.Server.GameObjects { public override string Name => "Door"; - private DoorState _state = DoorState.Closed; + protected DoorState _state = DoorState.Closed; private float OpenTimeCounter; @@ -45,7 +45,7 @@ namespace Content.Server.GameObjects base.OnRemove(); } - void IActivate.Activate(ActivateEventArgs eventArgs) + protected virtual void ActivateImpl(ActivateEventArgs eventArgs) { 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) { 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) { - if (Owner.TryGetComponent(out AccessReader accessReader)) + if (!CanOpen(user)) { - if (!accessReader.IsAllowed(user)) - { - Deny(); - return; - } + Deny(); + return; } Open(); } @@ -135,7 +152,7 @@ namespace Content.Server.GameObjects return true; } - public void Deny() + public virtual void Deny() { _appearance.SetData(DoorVisuals.VisualState, DoorVisualState.Deny); Timer.Spawn(DenyTime, () => @@ -145,7 +162,7 @@ namespace Content.Server.GameObjects } private const float AUTO_CLOSE_DELAY = 5; - public void OnUpdate(float frameTime) + public virtual void OnUpdate(float frameTime) { if (_state != DoorState.Open) { @@ -163,7 +180,7 @@ namespace Content.Server.GameObjects } } - private enum DoorState + protected enum DoorState { Closed, Open, diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index 03cf1cc41d..759991b0d0 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -1,5 +1,7 @@ using System; 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.VendingMachines; using Content.Server.GameObjects.EntitySystems; @@ -50,6 +52,11 @@ namespace Content.Server.GameObjects.Components /// public readonly List WiresList = new List(); + /// + /// Status messages are displayed at the bottom of the UI. + /// + private readonly Dictionary _statuses = new Dictionary(); + /// /// As seen on /vg/station. /// and . @@ -90,6 +97,17 @@ namespace Content.Server.GameObjects.Components UpdateUserInterface(); } + /// + /// Returns whether the wire associated with is cut. + /// + /// + 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 { /// @@ -233,7 +251,7 @@ namespace Content.Server.GameObjects.Components { 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) @@ -247,5 +265,18 @@ namespace Content.Server.GameObjects.Components { 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(); + } } } diff --git a/Content.Shared/GameObjects/Components/SharedWiresComponent.cs b/Content.Shared/GameObjects/Components/SharedWiresComponent.cs index 6011e73b20..eedbb93f12 100644 --- a/Content.Shared/GameObjects/Components/SharedWiresComponent.cs +++ b/Content.Shared/GameObjects/Components/SharedWiresComponent.cs @@ -35,10 +35,12 @@ namespace Content.Shared.GameObjects.Components public class WiresBoundUserInterfaceState : BoundUserInterfaceState { public readonly List WiresList; + public readonly List Statuses; - public WiresBoundUserInterfaceState(List wiresList) + public WiresBoundUserInterfaceState(List wiresList, List statuses) { WiresList = wiresList; + Statuses = statuses; } } diff --git a/Resources/Prototypes/Entities/buildings/airlock_base.yml b/Resources/Prototypes/Entities/buildings/airlock_base.yml index f4339c752b..bc05c70ba6 100644 --- a/Resources/Prototypes/Entities/buildings/airlock_base.yml +++ b/Resources/Prototypes/Entities/buildings/airlock_base.yml @@ -15,6 +15,8 @@ - state: closed_unlit shader: unshaded map: ["enum.DoorVisualLayers.BaseUnlit"] + - state: panel_open + map: ["enum.WiresVisualLayers.MaintenancePanel"] - type: Icon sprite: Buildings/airlock_basic.rsi @@ -25,13 +27,19 @@ - !type:PhysShapeAabb mask: 2 layer: 16 - - type: Door + - type: Airlock - type: Appearance visuals: - type: AirlockVisualizer2D open_sound: /Audio/machines/airlock_open.ogg close_sound: /Audio/machines/airlock_close.ogg deny_sound: /Audio/machines/airlock_deny.ogg - + - type: WiresVisualizer2D + - type: PowerDevice + - type: Wires + - type: UserInterface + interfaces: + - key: enum.WiresUiKey.Key + type: WiresBoundUserInterface placement: mode: SnapgridCenter