From 73e7b53edf1ca2d3e2957ce6a5d29b088082fc1f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 26 Jan 2022 15:26:53 +1100 Subject: [PATCH] Nerf hacking a smidge (#5940) --- .../WireHacking/WireHackingSystem.cs | 186 +++++++++++- Content.Server/WireHacking/WiresComponent.cs | 286 +++++++++--------- .../Structures/Doors/Airlocks/access.yml | 6 + .../Structures/Doors/Airlocks/shuttle.yml | 2 + .../Doors/Windoors/base_structurewindoors.yml | 4 +- .../Structures/Doors/Windoors/windoor.yml | 4 + 6 files changed, 331 insertions(+), 157 deletions(-) diff --git a/Content.Server/WireHacking/WireHackingSystem.cs b/Content.Server/WireHacking/WireHackingSystem.cs index e17c9e7308..781932ec9e 100644 --- a/Content.Server/WireHacking/WireHackingSystem.cs +++ b/Content.Server/WireHacking/WireHackingSystem.cs @@ -1,7 +1,16 @@ +using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using Content.Server.Tools; +using Content.Server.VendingMachines; +using Content.Shared.ActionBlocker; +using Content.Shared.Examine; using Content.Shared.GameTicking; +using Robust.Shared.Audio; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Player; +using Robust.Shared.Random; using Robust.Shared.ViewVariables; using static Content.Shared.Wires.SharedWiresComponent; @@ -9,6 +18,10 @@ namespace Content.Server.WireHacking { public class WireHackingSystem : EntitySystem { + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly ToolSystem _tools = default!; + [ViewVariables] private readonly Dictionary _layouts = new(); @@ -18,23 +31,184 @@ namespace Content.Server.WireHacking { base.Initialize(); + SubscribeLocalEvent(OnWiresStartup); + SubscribeLocalEvent(OnWiresMapInit); + SubscribeLocalEvent(OnWiresExamine); + + // Hacking DoAfters + SubscribeLocalEvent(OnWiresCut); + SubscribeLocalEvent(OnWiresMended); + SubscribeLocalEvent(OnWiresPulsed); + SubscribeLocalEvent(OnWiresCancelled); + SubscribeLocalEvent(Reset); } - public bool TryGetLayout(string id, [NotNullWhen(true)] out WireLayout? layout) + private void OnWiresCancelled(EntityUid uid, WiresComponent component, WiresComponent.WiresCancelledEvent args) { - return _layouts.TryGetValue(id, out layout); + component.PendingDoAfters.Remove(args.Wire.Id); } - public void AddLayout(string id, WireLayout layout) + private void HackingInteract(WiresComponent component, WiresComponent.Wire wire) { - _layouts.Add(id, layout); + component.PendingDoAfters.Remove(wire.Id); } - public void Reset(RoundRestartCleanupEvent ev) + private void OnWiresCut(EntityUid uid, WiresComponent component, WiresComponent.WiresCutEvent args) + { + HackingInteract(component, args.Wire); + + // Re-validate + // Deletion for user + wires should already be handled by do-after and tool is checked once at end in active-hand anyway. + if (!component.CanWiresInteract(args.User, out var tool)) return; + + if (!tool.Qualities.Contains(component.CuttingQuality)) return; + + var wire = args.Wire; + + _tools.PlayToolSound(args.Tool.Owner, args.Tool); + wire.IsCut = true; + component.UpdateUserInterface(); + + wire.Owner.WiresUpdate(new WiresUpdateEventArgs(wire.Identifier, WiresAction.Cut)); + } + + private void OnWiresMended(EntityUid uid, WiresComponent component, WiresComponent.WiresMendedEvent args) + { + HackingInteract(component, args.Wire); + + if (!component.CanWiresInteract(args.User, out var tool)) return; + + if (!tool.Qualities.Contains(component.CuttingQuality)) return; + + var wire = args.Wire; + + _tools.PlayToolSound(args.Tool.Owner, args.Tool); + wire.IsCut = false; + component.UpdateUserInterface(); + + wire.Owner.WiresUpdate(new WiresUpdateEventArgs(wire.Identifier, WiresAction.Mend)); + } + + private void OnWiresPulsed(EntityUid uid, WiresComponent component, WiresComponent.WiresPulsedEvent args) + { + HackingInteract(component, args.Wire); + + if (args.Wire.IsCut || !component.CanWiresInteract(args.User, out var tool)) return; + + if (!tool.Qualities.Contains(component.PulsingQuality)) return; + + var wire = args.Wire; + SoundSystem.Play(Filter.Pvs(uid), component.PulseSound.GetSound(), uid); + wire.Owner.WiresUpdate(new WiresUpdateEventArgs(wire.Identifier, WiresAction.Pulse)); + } + + private void OnWiresExamine(EntityUid uid, WiresComponent component, ExaminedEvent args) + { + args.PushMarkup(Loc.GetString(component.IsPanelOpen + ? "wires-component-on-examine-panel-open" + : "wires-component-on-examine-panel-closed")); + } + + private void OnWiresStartup(EntityUid uid, WiresComponent component, ComponentStartup args) + { + WireLayout? layout = null; + if (component.LayoutId != null) + { + _layouts.TryGetValue(component.LayoutId, out layout); + } + + foreach (var wiresProvider in EntityManager.GetComponents(uid)) + { + var builder = new WiresComponent.WiresBuilder(component, wiresProvider, layout); + wiresProvider.RegisterWires(builder); + } + + if (layout != null) + { + component.WiresList.Sort((a, b) => + { + var pA = layout.Specifications[a.Identifier].Position; + var pB = layout.Specifications[b.Identifier].Position; + + return pA.CompareTo(pB); + }); + } + else + { + _random.Shuffle(component.WiresList); + + if (component.LayoutId != null) + { + var dict = new Dictionary(); + for (var i = 0; i < component.WiresList.Count; i++) + { + var d = component.WiresList[i]; + dict.Add(d.Identifier, new WireLayout.WireData(d.Letter, d.Color, i)); + } + + _layouts.Add(component.LayoutId, new WireLayout(dict)); + } + } + + var id = 0; + foreach (var wire in component.WiresList) + { + wire.Id = ++id; + } + + component.UpdateUserInterface(); + } + + private void Reset(RoundRestartCleanupEvent ev) { _layouts.Clear(); } + + private void OnWiresMapInit(EntityUid uid, WiresComponent component, MapInitEvent args) + { + if (component.SerialNumber == null) + { + GenerateSerialNumber(component); + } + + if (component.WireSeed == 0) + { + component.WireSeed = _random.Next(1, int.MaxValue); + component.UpdateUserInterface(); + } + } + + private void GenerateSerialNumber(WiresComponent component) + { + Span data = stackalloc char[9]; + data[4] = '-'; + + if (_random.Prob(0.01f)) + { + for (var i = 0; i < 4; i++) + { + // Cyrillic Letters + data[i] = (char) _random.Next(0x0410, 0x0430); + } + } + else + { + for (var i = 0; i < 4; i++) + { + // Letters + data[i] = (char) _random.Next(0x41, 0x5B); + } + } + + for (var i = 5; i < 9; i++) + { + // Digits + data[i] = (char) _random.Next(0x30, 0x3A); + } + + component.SerialNumber = new string(data); + } } public sealed class WireLayout diff --git a/Content.Server/WireHacking/WiresComponent.cs b/Content.Server/WireHacking/WiresComponent.cs index 5740d27424..878ca2614a 100644 --- a/Content.Server/WireHacking/WiresComponent.cs +++ b/Content.Server/WireHacking/WiresComponent.cs @@ -1,13 +1,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; +using Content.Server.DoAfter; using Content.Server.Hands.Components; using Content.Server.Tools; using Content.Server.Tools.Components; using Content.Server.UserInterface; using Content.Server.VendingMachines; -using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; @@ -24,29 +25,37 @@ using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Server.WireHacking { [RegisterComponent] -#pragma warning disable 618 - public class WiresComponent : SharedWiresComponent, IInteractUsing, IExamine, IMapInit -#pragma warning restore 618 + public class WiresComponent : SharedWiresComponent, IInteractUsing { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IEntityManager _entities = default!; private bool _isPanelOpen; + [DataField("cuttingTime")] public float CuttingTime = 1f; + + [DataField("mendTime")] public float MendTime = 1f; + + [DataField("pulseTime")] public float PulseTime = 3f; + [DataField("screwingQuality", customTypeSerializer:typeof(PrototypeIdSerializer))] - private string _screwingQuality = "Screwing"; + public string ScrewingQuality = "Screwing"; [DataField("cuttingQuality", customTypeSerializer:typeof(PrototypeIdSerializer))] - private string _cuttingQuality = "Cutting"; + public string CuttingQuality = "Cutting"; [DataField("pulsingQuality", customTypeSerializer:typeof(PrototypeIdSerializer))] - private string _pulsingQuality = "Pulsing"; + public string PulsingQuality = "Pulsing"; + + /// + /// Make do_afters for hacking unique per wire so we can't spam a single wire. + /// + public HashSet PendingDoAfters = new(); /// /// Opening the maintenance panel (typically with a screwdriver) changes this. @@ -152,13 +161,12 @@ namespace Content.Server.WireHacking // We honestly don't care what it is or such but do care that it doesn't change between UI re-opens. [ViewVariables] [DataField("WireSeed")] - private int _wireSeed; + public int WireSeed; [ViewVariables] [DataField("LayoutId")] - private string? _layoutId = default; + public string? LayoutId = default; - [DataField("pulseSound")] - private SoundSpecifier _pulseSound = new SoundPathSpecifier("/Audio/Effects/multitool_pulse.ogg"); + [DataField("pulseSound")] public SoundSpecifier PulseSound = new SoundPathSpecifier("/Audio/Effects/multitool_pulse.ogg"); [DataField("screwdriverOpenSound")] private SoundSpecifier _screwdriverOpenSound = new SoundPathSpecifier("/Audio/Machines/screwdriveropen.ogg"); @@ -183,92 +191,6 @@ namespace Content.Server.WireHacking } } - private void GenerateSerialNumber() - { - var random = IoCManager.Resolve(); - Span data = stackalloc char[9]; - data[4] = '-'; - - if (random.Prob(0.01f)) - { - for (var i = 0; i < 4; i++) - { - // Cyrillic Letters - data[i] = (char) random.Next(0x0410, 0x0430); - } - } - else - { - for (var i = 0; i < 4; i++) - { - // Letters - data[i] = (char) random.Next(0x41, 0x5B); - } - } - - for (var i = 5; i < 9; i++) - { - // Digits - data[i] = (char) random.Next(0x30, 0x3A); - } - - SerialNumber = new string(data); - } - - protected override void Startup() - { - base.Startup(); - - - WireLayout? layout = null; - var hackingSystem = EntitySystem.Get(); - if (_layoutId != null) - { - hackingSystem.TryGetLayout(_layoutId, out layout); - } - - foreach (var wiresProvider in _entities.GetComponents(Owner)) - { - var builder = new WiresBuilder(this, wiresProvider, layout); - wiresProvider.RegisterWires(builder); - } - - if (layout != null) - { - WiresList.Sort((a, b) => - { - var pA = layout.Specifications[a.Identifier].Position; - var pB = layout.Specifications[b.Identifier].Position; - - return pA.CompareTo(pB); - }); - } - else - { - IoCManager.Resolve().Shuffle(WiresList); - - if (_layoutId != null) - { - var dict = new Dictionary(); - for (var i = 0; i < WiresList.Count; i++) - { - var d = WiresList[i]; - dict.Add(d.Identifier, new WireLayout.WireData(d.Letter, d.Color, i)); - } - - hackingSystem.AddLayout(_layoutId, new WireLayout(dict)); - } - } - - var id = 0; - foreach (var wire in WiresList) - { - wire.Id = ++id; - } - - UpdateUserInterface(); - } - /// /// Returns whether the wire associated with is cut. /// @@ -400,6 +322,31 @@ namespace Content.Server.WireHacking UserInterface?.CloseAll(); } + public bool CanWiresInteract(EntityUid user, [NotNullWhen(true)] out ToolComponent? tool) + { + tool = null; + + if (!_entities.TryGetComponent(user, out HandsComponent? handsComponent)) + { + Owner.PopupMessage(user, Loc.GetString("wires-component-ui-on-receive-message-no-hands")); + return false; + } + + if (!user.InRangeUnobstructed(Owner)) + { + Owner.PopupMessage(user, Loc.GetString("wires-component-ui-on-receive-message-cannot-reach")); + return false; + } + + if (handsComponent.GetActiveHand()?.HeldEntity is not { Valid: true } activeHandEntity || + !_entities.TryGetComponent(activeHandEntity, out tool)) + { + return false; + } + + return true; + } + private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { var message = serverMsg.Message; @@ -407,54 +354,74 @@ namespace Content.Server.WireHacking { case WiresActionMessage msg: var wire = WiresList.Find(x => x.Id == msg.Id); - if (wire == null || serverMsg.Session.AttachedEntity is not {} player) + if (wire == null || + serverMsg.Session.AttachedEntity is not {} player || + PendingDoAfters.Contains(wire.Id)) { return; } - if (!_entities.TryGetComponent(player, out HandsComponent? handsComponent)) - { - Owner.PopupMessage(player, Loc.GetString("wires-component-ui-on-receive-message-no-hands")); + if (!CanWiresInteract(player, out var tool)) return; - } - if (!player.InRangeUnobstructed(Owner)) - { - Owner.PopupMessage(player, Loc.GetString("wires-component-ui-on-receive-message-cannot-reach")); - return; - } - - ToolComponent? tool = null; - if (handsComponent.GetActiveHandItem?.Owner is EntityUid activeHandEntity) - _entities.TryGetComponent(activeHandEntity, out tool); - var toolSystem = EntitySystem.Get(); + var doAfterSystem = EntitySystem.Get(); switch (msg.Action) { case WiresAction.Cut: - if (tool == null || !tool.Qualities.Contains(_cuttingQuality)) + if (!tool.Qualities.Contains(CuttingQuality)) { player.PopupMessageCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters")); return; } - toolSystem.PlayToolSound(tool.Owner, tool); - wire.IsCut = true; - UpdateUserInterface(); + doAfterSystem.DoAfter( + new DoAfterEventArgs(player, CuttingTime, target: Owner) + { + TargetFinishedEvent = new WiresCutEvent + { + Wire = wire, + Tool = tool, + User = player, + }, + TargetCancelledEvent = new WiresCancelledEvent() + { + Wire = wire, + }, + NeedHand = true, + }); + + PendingDoAfters.Add(wire.Id); + break; case WiresAction.Mend: - if (tool == null || !tool.Qualities.Contains(_cuttingQuality)) + if (!tool.Qualities.Contains(CuttingQuality)) { player.PopupMessageCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters")); return; } - toolSystem.PlayToolSound(tool.Owner, tool); - wire.IsCut = false; - UpdateUserInterface(); + doAfterSystem.DoAfter( + new DoAfterEventArgs(player, MendTime, target: Owner) + { + TargetFinishedEvent = new WiresMendedEvent() + { + Wire = wire, + Tool = tool, + User = player, + }, + TargetCancelledEvent = new WiresCancelledEvent() + { + Wire = wire, + }, + NeedHand = true, + }); + + PendingDoAfters.Add(wire.Id); + break; case WiresAction.Pulse: - if (tool == null || !tool.Qualities.Contains(_pulsingQuality)) + if (!tool.Qualities.Contains(PulsingQuality)) { player.PopupMessageCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters")); return; @@ -466,16 +433,56 @@ namespace Content.Server.WireHacking return; } - SoundSystem.Play(Filter.Pvs(Owner), _pulseSound.GetSound(), Owner); + doAfterSystem.DoAfter( + new DoAfterEventArgs(player, PulseTime, target: Owner) + { + TargetFinishedEvent = new WiresPulsedEvent + { + Wire = wire, + Tool = tool, + User = player, + }, + TargetCancelledEvent = new WiresCancelledEvent() + { + Wire = wire, + }, + NeedHand = true, + }); + + PendingDoAfters.Add(wire.Id); + break; } - wire.Owner.WiresUpdate(new WiresUpdateEventArgs(wire.Identifier, msg.Action)); break; } } - private void UpdateUserInterface() + public sealed class WiresCancelledEvent : EntityEventArgs + { + public Wire Wire { get; init; } = default!; + } + + public abstract class WiresEvent : EntityEventArgs + { + public EntityUid User { get; init; } = default!; + public Wire Wire { get; init; } = default!; + public ToolComponent Tool { get; init; } = default!; + } + + public sealed class WiresCutEvent : WiresEvent + { + } + + public sealed class WiresMendedEvent : WiresEvent + { + } + + public sealed class WiresPulsedEvent : WiresEvent + { + } + + internal void UpdateUserInterface() { var clientList = new List(); foreach (var entry in WiresList) @@ -490,7 +497,7 @@ namespace Content.Server.WireHacking _statuses.Select(p => new StatusEntry(p.Key, p.Value)).ToArray(), BoardName, SerialNumber, - _wireSeed)); + WireSeed)); } async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) @@ -504,8 +511,8 @@ namespace Content.Server.WireHacking // opens the wires ui if using a tool with cutting or multitool quality on it if (IsPanelOpen && - (tool.Qualities.Contains(_cuttingQuality) || - tool.Qualities.Contains(_pulsingQuality))) + (tool.Qualities.Contains(CuttingQuality) || + tool.Qualities.Contains(PulsingQuality))) { if (_entities.TryGetComponent(eventArgs.User, out ActorComponent? actor)) { @@ -516,7 +523,7 @@ namespace Content.Server.WireHacking // screws the panel open if the tool can do so else if (await toolSystem.UseTool(tool.Owner, eventArgs.User, Owner, - 0f, WireHackingSystem.ScrewTime, _screwingQuality, toolComponent:tool)) + 0f, WireHackingSystem.ScrewTime, ScrewingQuality, toolComponent:tool)) { IsPanelOpen = !IsPanelOpen; if (IsPanelOpen) @@ -534,13 +541,6 @@ namespace Content.Server.WireHacking return false; } - void IExamine.Examine(FormattedMessage message, bool inDetailsRange) - { - message.AddMarkup(Loc.GetString(IsPanelOpen - ? "wires-component-on-examine-panel-open" - : "wires-component-on-examine-panel-closed")); - } - public void SetStatus(object statusIdentifier, object status) { if (_statuses.TryGetValue(statusIdentifier, out var storedMessage)) @@ -554,19 +554,5 @@ namespace Content.Server.WireHacking _statuses[statusIdentifier] = status; UpdateUserInterface(); } - - void IMapInit.MapInit() - { - if (SerialNumber == null) - { - GenerateSerialNumber(); - } - - if (_wireSeed == 0) - { - _wireSeed = IoCManager.Resolve().Next(1, int.MaxValue); - UpdateUserInterface(); - } - } } } diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml index 3d7e2fd4d5..c67bab684e 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml @@ -143,6 +143,8 @@ components: - type: AccessReader access: [["Command"]] + - type: Wires + LayoutId: AirlockCommand - type: entity parent: AirlockCommand @@ -199,6 +201,8 @@ components: - type: AccessReader access: [["Security"]] + - type: Wires + LayoutId: AirlockSecurity - type: entity parent: AirlockSecurity @@ -207,6 +211,8 @@ components: - type: AccessReader access: [["Armory"]] + - type: Wires + LayoutId: AirlockArmory - type: entity parent: AirlockSecurity diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml index 7bad50ddf1..673d358e8d 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml @@ -22,6 +22,8 @@ map: ["enum.DoorVisualLayers.BaseBolted"] - state: panel_open map: ["enum.WiresVisualLayers.MaintenancePanel"] + - type: Wires + LayoutId: Docking - type: Door bumpOpen: false closeTimeTwo: 0.4 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml index a04b87a06d..9b2ffd85d6 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml @@ -81,7 +81,7 @@ path: /Audio/Machines/airlock_deny.ogg - type: Wires BoardName: "Windoor Control" - LayoutId: Airlock + LayoutId: Windoors - type: UserInterface interfaces: - key: enum.WiresUiKey.Key @@ -126,6 +126,8 @@ - state: panel_open map: [ "enum.WiresVisualLayers.MaintenancePanel" ] visible: false + - type: Wires + LayoutId: Secure Windoors - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/windoor.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/windoor.yml index a1e66b0b79..256887c490 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/windoor.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/windoor.yml @@ -55,6 +55,8 @@ components: - type: AccessReader access: [["Command"]] + - type: Wires + LayoutId: WindoorCommand # Cargo windoor - type: entity @@ -73,6 +75,8 @@ components: - type: AccessReader access: [["Security"]] + - type: Wires + LayoutId: WindoorSecurity - type: entity parent: WindoorSecure