using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.VendingMachines; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Interactable; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Random; using Robust.Shared.Utility; namespace Content.Server.GameObjects.Components { [RegisterComponent] public class WiresComponent : SharedWiresComponent, IAttackBy, IExamine { #pragma warning disable 649 [Dependency] private readonly IRobustRandom _random; [Dependency] private readonly IServerNotifyManager _notifyManager; [Dependency] private readonly ILocalizationManager _localizationManager; #pragma warning restore 649 private AudioSystem _audioSystem; private AppearanceComponent _appearance; private BoundUserInterface _userInterface; private bool _isPanelOpen; /// /// Opening the maintenance panel (typically with a screwdriver) changes this. /// public bool IsPanelOpen { get => _isPanelOpen; private set { if (_isPanelOpen == value) { return; } _isPanelOpen = value; UpdateAppearance(); } } private bool _isPanelVisible = true; /// /// Components can set this to prevent the maintenance panel overlay from showing even if it's open /// public bool IsPanelVisible { get => _isPanelVisible; set { if (_isPanelVisible == value) { return; } _isPanelVisible = value; UpdateAppearance(); } } private void UpdateAppearance() { _appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen && IsPanelVisible); } /// /// Contains all registered wires. /// public readonly List WiresList = new List(); /// /// Status messages are displayed at the bottom of the UI. /// private readonly Dictionary _statuses = new Dictionary(); /// /// and . /// private readonly List _availableColors = new List() { Color.Red, Color.Blue, Color.Green, Color.Orange, Color.Brown, Color.Gold, Color.Gray, Color.Cyan, Color.Navy, Color.Purple, Color.Pink, Color.Fuchsia, }; public override void Initialize() { base.Initialize(); _audioSystem = IoCManager.Resolve().GetEntitySystem(); _appearance = Owner.GetComponent(); _appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen); _userInterface = Owner.GetComponent() .GetBoundUserInterface(WiresUiKey.Key); _userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; } protected override void Startup() { base.Startup(); foreach (var wiresProvider in Owner.GetAllComponents()) { var builder = new WiresBuilder(this, wiresProvider); wiresProvider.RegisterWires(builder); } 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 { /// /// Used in client-server communication to identify a wire without telling the client what the wire does. /// public readonly Guid Guid; /// /// Registered by components implementing IWires, used to identify which wire the client interacted with. /// public readonly object Identifier; /// /// The color of the wire. It needs to have a corresponding entry in . /// public readonly Color Color; /// /// The component that registered the wire. /// public readonly IWires Owner; /// /// Whether the wire is cut. /// public bool IsCut; public Wire(Guid guid, object identifier, Color color, IWires owner, bool isCut) { Guid = guid; Identifier = identifier; Color = color; Owner = owner; IsCut = isCut; } } /// /// Used by . /// public class WiresBuilder { [NotNull] private readonly WiresComponent _wires; [NotNull] private readonly IWires _owner; public WiresBuilder(WiresComponent wires, IWires owner) { _wires = wires; _owner = owner; } public void CreateWire(object identifier, Color? color = null, bool isCut = false) { if (!color.HasValue) { color = _wires.AssignColor(); } else { _wires._availableColors.Remove(color.Value); } _wires.WiresList.Add(new Wire(Guid.NewGuid(), identifier, color.Value, _owner, isCut)); } } /// /// Picks a color from and removes it from the list. /// /// The picked color. private Color AssignColor() { if(_availableColors.Count == 0) { return Color.Black; } return _random.PickAndTake(_availableColors); } /// /// Call this from other components to open the wires UI. /// public void OpenInterface(IPlayerSession session) { _userInterface.Open(session); } private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { var message = serverMsg.Message; switch (message) { case WiresActionMessage msg: var wire = WiresList.Find(x => x.Guid == msg.Guid); var player = serverMsg.Session.AttachedEntity; if (!player.TryGetComponent(out IHandsComponent handsComponent)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You have no hands.")); return; } var interactionSystem = IoCManager.Resolve().GetEntitySystem(); if (!interactionSystem.InRangeUnobstructed(player.Transform.MapPosition, Owner.Transform.WorldPosition, ignoredEnt: Owner)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You can't reach there!")); return; } var activeHandEntity = handsComponent.GetActiveHand?.Owner; activeHandEntity.TryGetComponent(out var tool); switch (msg.Action) { case WiresAction.Cut: if (tool == null || !tool.HasQuality(ToolQuality.Cutting)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You need to hold a wirecutter in your hand!")); return; } tool.PlayUseSound(); wire.IsCut = true; UpdateUserInterface(); break; case WiresAction.Mend: if (tool == null || !tool.HasQuality(ToolQuality.Cutting)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You need to hold a wirecutter in your hand!")); return; } tool.PlayUseSound(); wire.IsCut = false; UpdateUserInterface(); break; case WiresAction.Pulse: if (tool == null || !tool.HasQuality(ToolQuality.Multitool)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You need to hold a multitool in your hand!")); return; } if (wire.IsCut) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You can't pulse a wire that's been cut!")); return; } _audioSystem.Play("/Audio/effects/multitool_pulse.ogg", Owner); break; } wire.Owner.WiresUpdate(new WiresUpdateEventArgs(wire.Identifier, msg.Action)); break; } } private void UpdateUserInterface() { var clientList = new List(); foreach (var entry in WiresList) { clientList.Add(new ClientWire(entry.Guid, entry.Color, entry.IsCut)); } _userInterface.SetState(new WiresBoundUserInterfaceState(clientList, _statuses.Values.ToList())); } bool IAttackBy.AttackBy(AttackByEventArgs eventArgs) { if (!eventArgs.AttackWith.TryGetComponent(out var tool)) return false; if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Screwing)) return false; IsPanelOpen = !IsPanelOpen; IoCManager.Resolve() .GetEntitySystem() .Play(IsPanelOpen ? "/Audio/machines/screwdriveropen.ogg" : "/Audio/machines/screwdriverclose.ogg", Owner); return true; } void IExamine.Examine(FormattedMessage message) { var loc = IoCManager.Resolve(); message.AddMarkup(loc.GetString(IsPanelOpen ? "The [color=lightgray]maintenance panel[/color] is [color=darkgreen]open[/color]." : "The [color=lightgray]maintenance panel[/color] is [color=darkred]closed[/color].")); } public void SetStatus(object statusIdentifier, string newMessage) { if (_statuses.TryGetValue(statusIdentifier, out var storedMessage)) { if (storedMessage == newMessage) { return; } } _statuses[statusIdentifier] = newMessage; UpdateUserInterface(); } } }