diff --git a/Content.Client/GameObjects/Components/Wires/WiresBoundUserInterface.cs b/Content.Client/GameObjects/Components/Wires/WiresBoundUserInterface.cs index 7f472ed585..03c345c6b1 100644 --- a/Content.Client/GameObjects/Components/Wires/WiresBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Wires/WiresBoundUserInterface.cs @@ -1,17 +1,11 @@ -using System; using Robust.Client.GameObjects.Components.UserInterface; using Robust.Shared.GameObjects.Components.UserInterface; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using static Content.Shared.GameObjects.Components.SharedWiresComponent; namespace Content.Client.GameObjects.Components.Wires { public class WiresBoundUserInterface : BoundUserInterface { -#pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _localizationManager; -#pragma warning restore 649 public WiresBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { } @@ -21,7 +15,7 @@ namespace Content.Client.GameObjects.Components.Wires protected override void Open() { base.Open(); - _menu = new WiresMenu(_localizationManager) {Owner = this}; + _menu = new WiresMenu(this); _menu.OnClose += Close; _menu.OpenCentered(); @@ -33,9 +27,16 @@ namespace Content.Client.GameObjects.Components.Wires _menu.Populate((WiresBoundUserInterfaceState) state); } - public void PerformAction(Guid guid, WiresAction action) + public void PerformAction(int id, WiresAction action) { - SendMessage(new WiresActionMessage(guid, action)); + SendMessage(new WiresActionMessage(id, action)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + _menu.Close(); } } } diff --git a/Content.Client/GameObjects/Components/Wires/WiresMenu.cs b/Content.Client/GameObjects/Components/Wires/WiresMenu.cs index 18d3661f9f..f4d78223cd 100644 --- a/Content.Client/GameObjects/Components/Wires/WiresMenu.cs +++ b/Content.Client/GameObjects/Components/Wires/WiresMenu.cs @@ -1,66 +1,618 @@ +using System; +using Content.Client.Animations; +using Content.Client.GameObjects.EntitySystems; +using Content.Client.UserInterface.Stylesheets; +using Content.Client.Utility; +using Content.Shared.GameObjects.Components; +using Robust.Client.Animations; +using Robust.Client.Graphics; +using Robust.Client.Graphics.Drawing; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Animations; +using Robust.Shared.Input; +using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; using static Content.Shared.GameObjects.Components.SharedWiresComponent; namespace Content.Client.GameObjects.Components.Wires { - public class WiresMenu : SS14Window + public class WiresMenu : BaseWindow { - private readonly ILocalizationManager _localizationManager; - protected override Vector2? CustomSize => (300, 150); - public WiresBoundUserInterface Owner { get; set; } + public WiresBoundUserInterface Owner { get; } - private readonly VBoxContainer _wiresContainer; + private readonly Control _wiresHBox; + private readonly Control _topContainer; + private readonly Control _statusContainer; - public WiresMenu(ILocalizationManager localizationManager) + private readonly Label _nameLabel; + private readonly Label _serialLabel; + + public TextureButton CloseButton { get; set; } + + public WiresMenu(WiresBoundUserInterface owner) { - _localizationManager = localizationManager; - Title = _localizationManager.GetString("Wires"); - _wiresContainer = new VBoxContainer(); - Contents.AddChild(_wiresContainer); + var resourceCache = IoCManager.Resolve(); + + Owner = owner; + var rootContainer = new LayoutContainer {Name = "WireRoot"}; + AddChild(rootContainer); + + MouseFilter = MouseFilterMode.Stop; + + var panelTex = resourceCache.GetTexture("/Nano/button.svg.96dpi.png"); + var back = new StyleBoxTexture + { + Texture = panelTex, + Modulate = Color.FromHex("#25252A"), + }; + back.SetPatchMargin(StyleBox.Margin.All, 10); + + var topPanel = new PanelContainer + { + PanelOverride = back, + MouseFilter = MouseFilterMode.Pass + }; + var bottomWrap = new LayoutContainer + { + Name = "BottomWrap" + }; + var bottomPanel = new PanelContainer + { + PanelOverride = back, + MouseFilter = MouseFilterMode.Pass + }; + + var shadow = new HBoxContainer + { + Children = + { + new PanelContainer + { + CustomMinimumSize = (2, 0), + PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")} + }, + new PanelContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + MouseFilter = MouseFilterMode.Stop, + Name = "Shadow", + PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Black.WithAlpha(0.5f)} + }, + new PanelContainer + { + CustomMinimumSize = (2, 0), + PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")} + }, + } + }; + + var wrappingHBox = new HBoxContainer(); + _wiresHBox = new HBoxContainer {SeparationOverride = 4, SizeFlagsVertical = SizeFlags.ShrinkEnd}; + + wrappingHBox.AddChild(new Control {CustomMinimumSize = (20, 0)}); + wrappingHBox.AddChild(_wiresHBox); + wrappingHBox.AddChild(new Control {CustomMinimumSize = (20, 0)}); + + bottomWrap.AddChild(bottomPanel); + + LayoutContainer.SetAnchorPreset(bottomPanel, LayoutContainer.LayoutPreset.BottomWide); + LayoutContainer.SetMarginTop(bottomPanel, -55); + + bottomWrap.AddChild(shadow); + + LayoutContainer.SetAnchorPreset(shadow, LayoutContainer.LayoutPreset.BottomWide); + LayoutContainer.SetMarginBottom(shadow, -55); + LayoutContainer.SetMarginTop(shadow, -80); + LayoutContainer.SetMarginLeft(shadow, 12); + LayoutContainer.SetMarginRight(shadow, -12); + + bottomWrap.AddChild(wrappingHBox); + LayoutContainer.SetAnchorPreset(wrappingHBox, LayoutContainer.LayoutPreset.Wide); + LayoutContainer.SetMarginBottom(wrappingHBox, -4); + + rootContainer.AddChild(topPanel); + rootContainer.AddChild(bottomWrap); + + LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide); + LayoutContainer.SetMarginBottom(topPanel, -80); + + LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide); + LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both); + + var topContainerWrap = new VBoxContainer + { + Children = + { + (_topContainer = new VBoxContainer()), + new Control {CustomMinimumSize = (0, 110)} + } + }; + + rootContainer.AddChild(topContainerWrap); + + LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide); + + var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13); + var fontSmall = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 10); + + Button helpButton; + var topRow = new MarginContainer + { + MarginLeftOverride = 4, + MarginTopOverride = 2, + MarginRightOverride = 12, + MarginBottomOverride = 2, + Children = + { + new HBoxContainer + { + Children = + { + (_nameLabel = new Label + { + Text = Loc.GetString("Wires"), + FontOverride = font, + FontColorOverride = StyleNano.NanoGold, + SizeFlagsVertical = SizeFlags.ShrinkCenter + }), + new Control + { + CustomMinimumSize = (8, 0), + }, + (_serialLabel = new Label + { + Text = Loc.GetString("DEAD-BEEF"), + FontOverride = fontSmall, + FontColorOverride = Color.Gray, + SizeFlagsVertical = SizeFlags.ShrinkCenter + }), + new Control + { + CustomMinimumSize = (20, 0), + SizeFlagsHorizontal = SizeFlags.Expand + }, + (helpButton = new Button {Text = "?"}), + new Control + { + CustomMinimumSize = (2, 0), + }, + (CloseButton = new TextureButton + { + StyleClasses = {SS14Window.StyleClassWindowCloseButton}, + SizeFlagsVertical = SizeFlags.ShrinkCenter + }) + } + } + } + }; + + helpButton.OnPressed += a => + { + var popup = new HelpPopup(); + UserInterfaceManager.ModalRoot.AddChild(popup); + + popup.Open(UIBox2.FromDimensions(a.Event.PointerLocation.Position, (400, 200))); + }; + + var middle = new PanelContainer + { + PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202025")}, + Children = + { + new HBoxContainer + { + Children = + { + new MarginContainer + { + MarginLeftOverride = 8, + MarginRightOverride = 8, + MarginTopOverride = 4, + MarginBottomOverride = 4, + Children = + { + (_statusContainer = new GridContainer + { + // TODO: automatically change columns count. + Columns = 3 + }) + } + } + } + } + } + }; + + _topContainer.AddChild(topRow); + _topContainer.AddChild(new PanelContainer + { + CustomMinimumSize = (0, 2), + PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")} + }); + _topContainer.AddChild(middle); + _topContainer.AddChild(new PanelContainer + { + CustomMinimumSize = (0, 2), + PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")} + }); + CloseButton.OnPressed += _ => Close(); + LayoutContainer.SetSize(this, (300, 200)); } + public void Populate(WiresBoundUserInterfaceState state) { - _wiresContainer.RemoveAllChildren(); + _nameLabel.Text = state.BoardName; + _serialLabel.Text = state.SerialNumber; + + _wiresHBox.RemoveAllChildren(); + var random = new Random(state.WireSeed); foreach (var wire in state.WiresList) { - var container = new HBoxContainer(); - var newLabel = new Label() + var mirror = random.Next(2) == 0; + var flip = random.Next(2) == 0; + var type = random.Next(2); + var control = new WireControl(wire.Color, wire.Letter, wire.IsCut, flip, mirror, type) { - Text = $"{_localizationManager.GetString(wire.Color.Name())}: ", - FontColorOverride = wire.Color, + SizeFlagsVertical = SizeFlags.ShrinkEnd }; - container.AddChild(newLabel); + _wiresHBox.AddChild(control); - var newButton = new Button() + control.WireClicked += () => { - Text = _localizationManager.GetString("Pulse"), + Owner.PerformAction(wire.Id, wire.IsCut ? WiresAction.Mend : WiresAction.Cut); }; - newButton.OnPressed += _ => Owner.PerformAction(wire.Guid, WiresAction.Pulse); - container.AddChild(newButton); - newButton = new Button() + control.ContactsClicked += () => { - Text = wire.IsCut ? _localizationManager.GetString("Mend") : _localizationManager.GetString("Cut"), + Owner.PerformAction(wire.Id, WiresAction.Pulse); }; - newButton.OnPressed += _ => Owner.PerformAction(wire.Guid, wire.IsCut ? WiresAction.Mend : WiresAction.Cut); - container.AddChild(newButton); - _wiresContainer.AddChild(container); } + + _statusContainer.RemoveAllChildren(); foreach (var status in state.Statuses) { - var container = new HBoxContainer(); - container.AddChild(new Label + if (status.Value is StatusLightData statusLightData) { - Text = status - }); - _wiresContainer.AddChild(container); + _statusContainer.AddChild(new StatusLight(statusLightData)); + } + else + { + _statusContainer.AddChild(new Label + { + Text = status.ToString() + }); + } } } + protected override DragMode GetDragModeFor(Vector2 relativeMousePos) + { + return DragMode.Move; + } + + protected override bool HasPoint(Vector2 point) + { + // This makes it so our base window won't count for hit tests, + // but we will still receive mouse events coming in from Pass mouse filter mode. + // So basically, it perfectly shells out the hit tests to the panels we have! + return false; + } + + private sealed class WireControl : Control + { + private const string TextureContact = "/Textures/UserInterface/WireHacking/contact.svg.96dpi.png"; + + public event Action WireClicked; + public event Action ContactsClicked; + + public WireControl(WireColor color, WireLetter letter, bool isCut, bool flip, bool mirror, int type) + { + SizeFlagsHorizontal = SizeFlags.ShrinkCenter; + MouseFilter = MouseFilterMode.Stop; + + var resourceCache = IoCManager.Resolve(); + + var layout = new LayoutContainer(); + AddChild(layout); + + var greek = new Label + { + Text = letter.Letter().ToString(), + SizeFlagsVertical = SizeFlags.ShrinkEnd, + SizeFlagsHorizontal = SizeFlags.ShrinkCenter, + Align = Label.AlignMode.Center, + FontOverride = resourceCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 12), + FontColorOverride = Color.Gray, + ToolTip = letter.Name(), + MouseFilter = MouseFilterMode.Stop + }; + + layout.AddChild(greek); + LayoutContainer.SetAnchorPreset(greek, LayoutContainer.LayoutPreset.BottomWide); + LayoutContainer.SetGrowVertical(greek, LayoutContainer.GrowDirection.Begin); + LayoutContainer.SetGrowHorizontal(greek, LayoutContainer.GrowDirection.Both); + + var contactTexture = resourceCache.GetTexture(TextureContact); + var contact1 = new TextureRect + { + Texture = contactTexture, + Modulate = Color.FromHex("#E1CA76") + }; + + layout.AddChild(contact1); + LayoutContainer.SetPosition(contact1, (0, 0)); + + var contact2 = new TextureRect + { + Texture = contactTexture, + Modulate = Color.FromHex("#E1CA76") + }; + + layout.AddChild(contact2); + LayoutContainer.SetPosition(contact2, (0, 60)); + + var wire = new WireRender(color, isCut, flip, mirror, type); + + layout.AddChild(wire); + LayoutContainer.SetPosition(wire, (2, 16)); + + ToolTip = color.Name(); + } + + protected override Vector2 CalculateMinimumSize() + { + return (20, 102); + } + + + protected override void KeyBindDown(GUIBoundKeyEventArgs args) + { + base.KeyBindDown(args); + + if (args.Function != EngineKeyFunctions.UIClick) + { + return; + } + + if (args.RelativePixelPosition.Y > 20 && args.RelativePixelPosition.Y < 60) + { + WireClicked?.Invoke(); + } + else + { + ContactsClicked?.Invoke(); + } + } + + protected override bool HasPoint(Vector2 point) + { + return base.HasPoint(point) && point.Y <= 80; + } + + private sealed class WireRender : Control + { + private readonly WireColor _color; + private readonly bool _isCut; + private readonly bool _flip; + private readonly bool _mirror; + private readonly int _type; + + private static readonly string[] TextureNormal = + { + "/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png", + "/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png" + }; + + private static readonly string[] TextureCut = + { + "/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png", + "/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png", + }; + + private static readonly string[] TextureCopper = + { + "/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png", + "/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png" + }; + + public WireRender(WireColor color, bool isCut, bool flip, bool mirror, int type) + { + _color = color; + _isCut = isCut; + _flip = flip; + _mirror = mirror; + _type = type; + } + + protected override Vector2 CalculateMinimumSize() + { + return (16, 50); + } + + protected override void Draw(DrawingHandleScreen handle) + { + var resC = IoCManager.Resolve(); + + var colorValue = _color.ColorValue(); + var tex = resC.GetTexture(_isCut ? TextureCut[_type] : TextureNormal[_type]); + + var l = 0f; + var r = tex.Width + l; + var t = 0f; + var b = tex.Height + t; + + if (_flip) + { + (t, b) = (b, t); + } + + if (_mirror) + { + (l, r) = (r, l); + } + + l *= UIScale; + r *= UIScale; + t *= UIScale; + b *= UIScale; + + var rect = new UIBox2(l, t, r, b); + if (_isCut) + { + var copper = Color.Orange; + var copperTex = resC.GetTexture(TextureCopper[_type]); + handle.DrawTextureRect(copperTex, rect, copper); + } + + handle.DrawTextureRect(tex, rect, colorValue); + } + } + } + + private sealed class StatusLight : Control + { + private static readonly Animation _blinkingFast = new Animation + { + Length = TimeSpan.FromSeconds(0.2), + AnimationTracks = + { + new AnimationTrackControlProperty + { + Property = nameof(Control.Modulate), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Color.White, 0f), + new AnimationTrackProperty.KeyFrame(Color.Transparent, 0.1f), + new AnimationTrackProperty.KeyFrame(Color.White, 0.1f) + } + } + } + }; + + private static readonly Animation _blinkingSlow = new Animation + { + Length = TimeSpan.FromSeconds(0.8), + AnimationTracks = + { + new AnimationTrackControlProperty + { + Property = nameof(Control.Modulate), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(Color.White, 0f), + new AnimationTrackProperty.KeyFrame(Color.White, 0.3f), + new AnimationTrackProperty.KeyFrame(Color.Transparent, 0.1f), + new AnimationTrackProperty.KeyFrame(Color.Transparent, 0.3f), + new AnimationTrackProperty.KeyFrame(Color.White, 0.1f), + } + } + } + }; + + public StatusLight(StatusLightData data) + { + var resC = IoCManager.Resolve(); + var hsv = Color.ToHsv(data.Color); + hsv.Z /= 2; + var dimColor = Color.FromHsv(hsv); + TextureRect activeLight; + + var lightContainer = new Control + { + Children = + { + new TextureRect + { + Texture = resC.GetTexture( + "/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png"), + Stretch = TextureRect.StretchMode.KeepCentered, + ModulateSelfOverride = dimColor + }, + (activeLight = new TextureRect + { + ModulateSelfOverride = data.Color.WithAlpha(0.4f), + Stretch = TextureRect.StretchMode.KeepCentered, + Texture = + resC.GetTexture("/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png"), + }) + } + }; + + Animation animation = null; + + switch (data.State) + { + case StatusLightState.Off: + activeLight.Visible = false; + break; + case StatusLightState.On: + break; + case StatusLightState.BlinkingFast: + animation = _blinkingFast; + break; + case StatusLightState.BlinkingSlow: + animation = _blinkingSlow; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (animation != null) + { + activeLight.PlayAnimation(animation, "blink"); + + activeLight.AnimationCompleted += s => + { + if (s == "blink") + { + activeLight.PlayAnimation(animation, s); + } + }; + } + + var font = IoCManager.Resolve().GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 12); + + var hBox = new HBoxContainer {SeparationOverride = 4}; + hBox.AddChild(new Label + { + Text = data.Text, + FontOverride = font, + FontColorOverride = Color.FromHex("#A1A6AE"), + SizeFlagsVertical = SizeFlags.ShrinkCenter, + }); + hBox.AddChild(lightContainer); + hBox.AddChild(new Control {CustomMinimumSize = (6, 0)}); + AddChild(hBox); + } + } + + private sealed class HelpPopup : Popup + { + private const string Text = "Click on the gold contacts with a multitool in hand to pulse their wire.\n" + + "Click on the wires with a pair of wirecutters in hand to cut/mend them.\n\n" + + "The lights at the top show the state of the machine, " + + "messing with wires will probably do stuff to them.\n" + + "Wire layouts are different each round, " + + "but consistent between machines of the same type."; + + public HelpPopup() + { + var label = new RichTextLabel(); + label.SetMessage(Text); + AddChild(new PanelContainer + { + StyleClasses = {ExamineSystem.StyleClassEntityTooltip}, + Children = {label} + }); + } + } } } diff --git a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs index 82c19dcc01..c6d280ff3f 100644 --- a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs @@ -5,7 +5,6 @@ using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.Components.VendingMachines; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; -using Content.Server.Utility; using Content.Shared.GameObjects.Components.Doors; using Content.Shared.GameObjects.Components.Interactable; using Robust.Server.GameObjects; @@ -13,6 +12,8 @@ using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Maths; +using static Content.Shared.GameObjects.Components.SharedWiresComponent; using static Content.Shared.GameObjects.Components.SharedWiresComponent.WiresAction; using Timer = Robust.Shared.Timers.Timer; @@ -40,6 +41,7 @@ namespace Content.Server.GameObjects.Components.Doors private CancellationTokenSource _powerWiresPulsedTimerCancel; private bool _powerWiresPulsed; + /// /// True if either power wire was pulsed in the last . /// @@ -56,16 +58,30 @@ namespace Content.Server.GameObjects.Components.Doors private void UpdateWiresStatus() { - var powerMessage = "A yellow light is on."; + var powerLight = new StatusLightData(Color.Yellow, StatusLightState.On, "POWR"); 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."; + powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR"); } - _wires.SetStatus(WiresStatus.PowerIndicator, _localizationMgr.GetString(powerMessage)); + else if (_wires.IsWireCut(Wires.MainPower) && + _wires.IsWireCut(Wires.BackupPower)) + { + powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR"); + } + + _wires.SetStatus(AirlockWireStatus.PowerIndicator, powerLight); + _wires.SetStatus(1, new StatusLightData(Color.Red, StatusLightState.Off, "BOLT")); + _wires.SetStatus(2, new StatusLightData(Color.Lime, StatusLightState.On, "BLTL")); + _wires.SetStatus(3, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT")); + _wires.SetStatus(4, new StatusLightData(Color.Orange, StatusLightState.Off, "TIME")); + _wires.SetStatus(5, new StatusLightData(Color.Red, StatusLightState.Off, "SAFE")); + /* + _wires.SetStatus(6, powerLight); + _wires.SetStatus(7, powerLight); + _wires.SetStatus(8, powerLight); + _wires.SetStatus(9, powerLight); + _wires.SetStatus(10, powerLight); + _wires.SetStatus(11, powerLight);*/ } private void UpdatePowerCutStatus() @@ -125,19 +141,26 @@ namespace Content.Server.GameObjects.Components.Doors /// Mending restores power. /// MainPower, + /// BackupPower, } - private enum WiresStatus - { - PowerIndicator, - } - public void RegisterWires(WiresComponent.WiresBuilder builder) { builder.CreateWire(Wires.MainPower); builder.CreateWire(Wires.BackupPower); + builder.CreateWire(1); + builder.CreateWire(2); + builder.CreateWire(3); + builder.CreateWire(4); + /*builder.CreateWire(5); + builder.CreateWire(6); + builder.CreateWire(7); + builder.CreateWire(8); + builder.CreateWire(9); + builder.CreateWire(10); + builder.CreateWire(11);*/ UpdateWiresStatus(); } @@ -192,6 +215,7 @@ namespace Content.Server.GameObjects.Components.Doors { return; } + base.Deny(); } @@ -216,11 +240,10 @@ namespace Content.Server.GameObjects.Components.Doors if (State == DoorState.Closed) Open(); - else if(State == DoorState.Open) + else if (State == DoorState.Open) Close(); return true; - } } } diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index 1d42032427..48892c7855 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -6,43 +6,43 @@ using Content.Server.GameObjects.Components.VendingMachines; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; -using Content.Server.Utility; 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.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; -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.Serialization; using Robust.Shared.Utility; -using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components { [RegisterComponent] - public class WiresComponent : SharedWiresComponent, IInteractUsing, IExamine + public class WiresComponent : SharedWiresComponent, IInteractUsing, IExamine, IMapInit { #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. /// + [ViewVariables] public bool IsPanelOpen { get => _isPanelOpen; @@ -52,15 +52,18 @@ namespace Content.Server.GameObjects.Components { 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 /// + [ViewVariables] public bool IsPanelVisible { get => _isPanelVisible; @@ -70,11 +73,34 @@ namespace Content.Server.GameObjects.Components { return; } + _isPanelVisible = value; UpdateAppearance(); } } + [ViewVariables(VVAccess.ReadWrite)] + public string BoardName + { + get => _boardName; + set + { + _boardName = value; + UpdateUserInterface(); + } + } + + [ViewVariables(VVAccess.ReadWrite)] + public string SerialNumber + { + get => _serialNumber; + set + { + _serialNumber = value; + UpdateUserInterface(); + } + } + private void UpdateAppearance() { _appearance.SetData(WiresVisuals.MaintenancePanelState, IsPanelOpen && IsPanelVisible); @@ -88,26 +114,27 @@ namespace Content.Server.GameObjects.Components /// /// Status messages are displayed at the bottom of the UI. /// - private readonly Dictionary _statuses = new Dictionary(); + private readonly Dictionary _statuses = new Dictionary(); /// - /// and . + /// 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, - }; + private readonly List _availableColors = + new List((WireColor[]) Enum.GetValues(typeof(WireColor))); + + private readonly List _availableLetters = + new List((WireLetter[]) Enum.GetValues(typeof(WireLetter))); + + private string _boardName; + + private string _serialNumber; + + // Used to generate wire appearance randomization client side. + // We honestly don't care what it is or such but do care that it doesn't change between UI re-opens. + [ViewVariables] + private int _wireSeed; + [ViewVariables] + private string _layoutId; public override void Initialize() { @@ -120,16 +147,89 @@ namespace Content.Server.GameObjects.Components _userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage; } + 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 Owner.GetAllComponents()) { - var builder = new WiresBuilder(this, wiresProvider); + 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(); } @@ -140,39 +240,53 @@ namespace Content.Server.GameObjects.Components public bool IsWireCut(object identifier) { var wire = WiresList.Find(x => x.Identifier.Equals(identifier)); - if(wire == null) throw new ArgumentException(); + 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; + public IWires Owner { get; } + /// /// Whether the wire is cut. /// - public bool IsCut; - public Wire(Guid guid, object identifier, Color color, IWires owner, bool isCut) + public bool IsCut { get; set; } + + /// + /// Used in client-server communication to identify a wire without telling the client what the wire does. + /// + [ViewVariables] + public int Id { get; set; } + + /// + /// The color of the wire. + /// + [ViewVariables] + public WireColor Color { get; } + + /// + /// The greek letter shown below the wire. + /// + [ViewVariables] + public WireLetter Letter { get; } + + /// + /// Registered by components implementing IWires, used to identify which wire the client interacted with. + /// + [ViewVariables] + public object Identifier { get; } + + public Wire(IWires owner, bool isCut, WireColor color, WireLetter letter, object identifier) { - Guid = guid; - Identifier = identifier; - Color = color; Owner = owner; IsCut = isCut; + Color = color; + Letter = letter; + Identifier = identifier; } } @@ -183,24 +297,42 @@ namespace Content.Server.GameObjects.Components { [NotNull] private readonly WiresComponent _wires; [NotNull] private readonly IWires _owner; + [CanBeNull] private readonly WireLayout _layout; - public WiresBuilder(WiresComponent wires, IWires owner) + public WiresBuilder(WiresComponent wires, IWires owner, WireLayout layout) { _wires = wires; _owner = owner; + _layout = layout; } - public void CreateWire(object identifier, Color? color = null, bool isCut = false) + public void CreateWire(object identifier, (WireColor, WireLetter)? appearance = null, bool isCut = false) { - if (!color.HasValue) + WireLetter letter; + WireColor color; + if (!appearance.HasValue) { - color = _wires.AssignColor(); + if (_layout != null && _layout.Specifications.TryGetValue(identifier, out var specification)) + { + color = specification.Color; + letter = specification.Letter; + _wires._availableColors.Remove(color); + _wires._availableLetters.Remove(letter); + } + else + { + (color, letter) = _wires.AssignAppearance(); + } } else { - _wires._availableColors.Remove(color.Value); + (color, letter) = appearance.Value; + _wires._availableColors.Remove(color); + _wires._availableLetters.Remove(letter); } - _wires.WiresList.Add(new Wire(Guid.NewGuid(), identifier, color.Value, _owner, isCut)); + + // TODO: ENSURE NO RANDOM OVERLAP. + _wires.WiresList.Add(new Wire(_owner, isCut, color, letter, identifier)); } } @@ -208,13 +340,12 @@ namespace Content.Server.GameObjects.Components /// Picks a color from and removes it from the list. /// /// The picked color. - private Color AssignColor() + private (WireColor, WireLetter) AssignAppearance() { - if(_availableColors.Count == 0) - { - return Color.Black; - } - return _random.PickAndTake(_availableColors); + var color = _availableColors.Count == 0 ? WireColor.Red : _random.PickAndTake(_availableColors); + var letter = _availableLetters.Count == 0 ? WireLetter.α : _random.PickAndTake(_availableLetters); + + return (color, letter); } /// @@ -231,30 +362,41 @@ namespace Content.Server.GameObjects.Components switch (message) { case WiresActionMessage msg: - var wire = WiresList.Find(x => x.Guid == msg.Guid); + var wire = WiresList.Find(x => x.Id == msg.Id); + if (wire == null) + { + return; + } + var player = serverMsg.Session.AttachedEntity; if (!player.TryGetComponent(out IHandsComponent handsComponent)) { - _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You have no hands.")); + _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, + Loc.GetString("You have no hands.")); return; } if (!EntitySystem.Get().InRangeUnobstructed(player.Transform.MapPosition, Owner.Transform.MapPosition, ignoredEnt: Owner)) { - _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, _localizationManager.GetString("You can't reach there!")); + _notifyManager.PopupMessage(Owner.Transform.GridPosition, player, + Loc.GetString("You can't reach there!")); return; } var activeHandEntity = handsComponent.GetActiveHand?.Owner; - activeHandEntity.TryGetComponent(out var tool); + ToolComponent tool = null; + activeHandEntity?.TryGetComponent(out 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!")); + _notifyManager.PopupMessageCursor(player, + Loc.GetString("You need to hold a wirecutter in your hand!")); return; } + tool.PlayUseSound(); wire.IsCut = true; UpdateUserInterface(); @@ -262,9 +404,11 @@ namespace Content.Server.GameObjects.Components 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!")); + _notifyManager.PopupMessageCursor(player, + Loc.GetString("You need to hold a wirecutter in your hand!")); return; } + tool.PlayUseSound(); wire.IsCut = false; UpdateUserInterface(); @@ -272,17 +416,22 @@ namespace Content.Server.GameObjects.Components 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!")); + _notifyManager.PopupMessageCursor(player, + Loc.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!")); + _notifyManager.PopupMessageCursor(player, + Loc.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; } @@ -293,9 +442,27 @@ namespace Content.Server.GameObjects.Components var clientList = new List(); foreach (var entry in WiresList) { - clientList.Add(new ClientWire(entry.Guid, entry.Color, entry.IsCut)); + clientList.Add(new ClientWire(entry.Id, entry.IsCut, entry.Color, + entry.Letter)); } - _userInterface.SetState(new WiresBoundUserInterfaceState(clientList, _statuses.Values.ToList())); + + _userInterface.SetState( + new WiresBoundUserInterfaceState( + clientList.ToArray(), + _statuses.Select(p => new StatusEntry(p.Key, p.Value)).ToArray(), + BoardName, + SerialNumber, + _wireSeed)); + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _boardName, "BoardName", "Wires"); + serializer.DataField(ref _serialNumber, "SerialNumber", null); + serializer.DataField(ref _wireSeed, "WireSeed", 0); + serializer.DataField(ref _layoutId, "LayoutId", null); } bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) @@ -307,7 +474,8 @@ namespace Content.Server.GameObjects.Components IsPanelOpen = !IsPanelOpen; EntitySystem.Get() - .Play(IsPanelOpen ? "/Audio/machines/screwdriveropen.ogg" : "/Audio/machines/screwdriverclose.ogg", Owner); + .Play(IsPanelOpen ? "/Audio/machines/screwdriveropen.ogg" : "/Audio/machines/screwdriverclose.ogg", + Owner); return true; } @@ -320,17 +488,32 @@ namespace Content.Server.GameObjects.Components : "The [color=lightgray]maintenance panel[/color] is [color=darkred]closed[/color].")); } - public void SetStatus(object statusIdentifier, string newMessage) + public void SetStatus(object statusIdentifier, object status) { if (_statuses.TryGetValue(statusIdentifier, out var storedMessage)) { - if (storedMessage == newMessage) + if (storedMessage == status) { return; } } - _statuses[statusIdentifier] = newMessage; + + _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/Content.Server/GameObjects/EntitySystems/WireHackingSystem.cs b/Content.Server/GameObjects/EntitySystems/WireHackingSystem.cs new file mode 100644 index 0000000000..f13ae1c0a4 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/WireHackingSystem.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.ViewVariables; +using static Content.Shared.GameObjects.Components.SharedWiresComponent; + +namespace Content.Server.GameObjects.EntitySystems +{ + public class WireHackingSystem : EntitySystem + { + [ViewVariables] private readonly Dictionary _layouts = + new Dictionary(); + + public bool TryGetLayout(string id, out WireLayout layout) + { + return _layouts.TryGetValue(id, out layout); + } + + public void AddLayout(string id, WireLayout layout) + { + _layouts.Add(id, layout); + } + + public void ResetLayouts() + { + _layouts.Clear(); + } + } + + public sealed class WireLayout + { + [ViewVariables] public IReadOnlyDictionary Specifications { get; } + + public WireLayout(IReadOnlyDictionary specifications) + { + Specifications = specifications; + } + + public sealed class WireData + { + public WireLetter Letter { get; } + public WireColor Color { get; } + public int Position { get; } + + public WireData(WireLetter letter, WireColor color, int position) + { + Letter = letter; + Color = color; + Position = position; + } + } + } +} diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index adce89e947..9f3b8cc536 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -7,6 +7,7 @@ using Content.Server.GameObjects.Components.Access; using Content.Server.GameObjects.Components.Markers; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Observer; +using Content.Server.GameObjects.EntitySystems; using Content.Server.GameTicking.GamePresets; using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; @@ -26,6 +27,7 @@ using Robust.Server.ServerStatus; using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; @@ -469,6 +471,8 @@ namespace Content.Server.GameTicking _spawnedPositions.Clear(); _manifest.Clear(); + + EntitySystem.Get().ResetLayouts(); } private void _preRoundSetup() diff --git a/Content.Shared/GameObjects/Components/Doors/AirlockWireStatus.cs b/Content.Shared/GameObjects/Components/Doors/AirlockWireStatus.cs new file mode 100644 index 0000000000..f09d67beca --- /dev/null +++ b/Content.Shared/GameObjects/Components/Doors/AirlockWireStatus.cs @@ -0,0 +1,11 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Doors +{ + [Serializable, NetSerializable] + public enum AirlockWireStatus + { + PowerIndicator, + } +} diff --git a/Content.Shared/GameObjects/Components/SharedWiresComponent.cs b/Content.Shared/GameObjects/Components/SharedWiresComponent.cs index eedbb93f12..9275b79a50 100644 --- a/Content.Shared/GameObjects/Components/SharedWiresComponent.cs +++ b/Content.Shared/GameObjects/Components/SharedWiresComponent.cs @@ -1,9 +1,12 @@ using System; -using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Serialization; +using static Content.Shared.GameObjects.Components.SharedWiresComponent; namespace Content.Shared.GameObjects.Components { @@ -31,44 +34,257 @@ namespace Content.Shared.GameObjects.Components Pulse, } + [Serializable, NetSerializable] + public enum StatusLightState + { + Off, + On, + BlinkingFast, + BlinkingSlow + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + [PublicAPI] + [Serializable, NetSerializable] + public enum WireLetter : byte + { + α, + β, + γ, + δ, + ε, + ζ, + η, + θ, + ι, + κ, + λ, + μ, + ν, + ξ, + ο, + π, + ρ, + σ, + τ, + υ, + φ, + χ, + ψ, + ω + } + + [PublicAPI] + [Serializable, NetSerializable] + public enum WireColor : byte + { + Red, + Blue, + Green, + Orange, + Brown, + Gold, + Gray, + Cyan, + Navy, + Purple, + Pink, + Fuchsia + } + + [Serializable, NetSerializable] + public struct StatusLightData + { + public StatusLightData(Color color, StatusLightState state, string text) + { + Color = color; + State = state; + Text = text; + } + + public Color Color { get; } + public StatusLightState State { get; } + public string Text { get; } + + public override string ToString() + { + return $"Color: {Color}, State: {State}, Text: {Text}"; + } + } + [Serializable, NetSerializable] public class WiresBoundUserInterfaceState : BoundUserInterfaceState { - public readonly List WiresList; - public readonly List Statuses; + public string BoardName { get; } + public string SerialNumber { get; } + public ClientWire[] WiresList { get; } + public StatusEntry[] Statuses { get; } + public int WireSeed { get; } - public WiresBoundUserInterfaceState(List wiresList, List statuses) + public WiresBoundUserInterfaceState(ClientWire[] wiresList, StatusEntry[] statuses, string boardName, string serialNumber, int wireSeed) { + BoardName = boardName; + SerialNumber = serialNumber; + WireSeed = wireSeed; WiresList = wiresList; Statuses = statuses; } } + [Serializable, NetSerializable] + public struct StatusEntry + { + public readonly object Key; + public readonly object Value; + + public StatusEntry(object key, object value) + { + Key = key; + Value = value; + } + + public override string ToString() + { + return $"{Key}, {Value}"; + } + } + + [Serializable, NetSerializable] public class ClientWire { - public Guid Guid; - public Color Color; + public int Id; public bool IsCut; + public WireColor Color; + public WireLetter Letter; - public ClientWire(Guid guid, Color color, bool isCut) + public ClientWire(int id, bool isCut, WireColor color, WireLetter letter) { - Guid = guid; - Color = color; + Id = id; IsCut = isCut; + Letter = letter; + Color = color; } } [Serializable, NetSerializable] public class WiresActionMessage : BoundUserInterfaceMessage { - public readonly Guid Guid; + public readonly int Id; public readonly WiresAction Action; - public WiresActionMessage(Guid guid, WiresAction action) + + public WiresActionMessage(int id, WiresAction action) { - Guid = guid; + Id = id; Action = action; } } } + + public static class HackingWiresExt + { + public static string Name(this WireColor color) + { + return Loc.GetString(color switch + { + WireColor.Red => "Red", + WireColor.Blue => "Blue", + WireColor.Green => "Green", + WireColor.Orange => "Orange", + WireColor.Brown => "Brown", + WireColor.Gold => "Gold", + WireColor.Gray => "Gray", + WireColor.Cyan => "Cyan", + WireColor.Navy => "Navy", + WireColor.Purple => "Purple", + WireColor.Pink => "Pink", + WireColor.Fuchsia => "Fuchsia", + _ => throw new InvalidOperationException() + }); + } + + public static Color ColorValue(this WireColor color) + { + return color switch + { + WireColor.Red => Color.Red, + WireColor.Blue => Color.Blue, + WireColor.Green => Color.Green, + WireColor.Orange => Color.Orange, + WireColor.Brown => Color.Brown, + WireColor.Gold => Color.Gold, + WireColor.Gray => Color.Gray, + WireColor.Cyan => Color.Cyan, + WireColor.Navy => Color.Navy, + WireColor.Purple => Color.Purple, + WireColor.Pink => Color.Pink, + WireColor.Fuchsia => Color.Fuchsia, + _ => throw new InvalidOperationException() + }; + } + + public static string Name(this WireLetter letter) + { + return Loc.GetString(letter switch + { + WireLetter.α => "Alpha", + WireLetter.β => "Beta", + WireLetter.γ => "Gamma", + WireLetter.δ => "Delta", + WireLetter.ε => "Epsilon", + WireLetter.ζ => "Zeta", + WireLetter.η => "Eta", + WireLetter.θ => "Theta", + WireLetter.ι => "Iota", + WireLetter.κ => "Kappa", + WireLetter.λ => "Lambda", + WireLetter.μ => "Mu", + WireLetter.ν => "Nu", + WireLetter.ξ => "Xi", + WireLetter.ο => "Omicron", + WireLetter.π => "Pi", + WireLetter.ρ => "Rho", + WireLetter.σ => "Sigma", + WireLetter.τ => "Tau", + WireLetter.υ => "Upsilon", + WireLetter.φ => "Phi", + WireLetter.χ => "Chi", + WireLetter.ψ => "Psi", + WireLetter.ω => "Omega", + _ => throw new InvalidOperationException() + }); + } + + public static char Letter(this WireLetter letter) + { + return letter switch + { + WireLetter.α => 'α', + WireLetter.β => 'β', + WireLetter.γ => 'γ', + WireLetter.δ => 'δ', + WireLetter.ε => 'ε', + WireLetter.ζ => 'ζ', + WireLetter.η => 'η', + WireLetter.θ => 'θ', + WireLetter.ι => 'ι', + WireLetter.κ => 'κ', + WireLetter.λ => 'λ', + WireLetter.μ => 'μ', + WireLetter.ν => 'ν', + WireLetter.ξ => 'ξ', + WireLetter.ο => 'ο', + WireLetter.π => 'π', + WireLetter.ρ => 'ρ', + WireLetter.σ => 'σ', + WireLetter.τ => 'τ', + WireLetter.υ => 'υ', + WireLetter.φ => 'φ', + WireLetter.χ => 'χ', + WireLetter.ψ => 'ψ', + WireLetter.ω => 'ω', + _ => throw new InvalidOperationException() + }; + } + } } diff --git a/Content.Tests/Shared/WireHackingTest.cs b/Content.Tests/Shared/WireHackingTest.cs new file mode 100644 index 0000000000..cbb37fbe30 --- /dev/null +++ b/Content.Tests/Shared/WireHackingTest.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.Components; +using NUnit.Framework; +using Robust.UnitTesting; +using static Content.Shared.GameObjects.Components.SharedWiresComponent; + +namespace Content.Tests.Shared +{ + // Making sure nobody forgets to set values for these wire colors/letters. + // Also a thinly veiled excuse to bloat the test count. + + [TestFixture] + public class WireHackingTest : RobustUnitTest + { + public static IEnumerable ColorValues = (WireColor[]) Enum.GetValues(typeof(WireColor)); + public static IEnumerable LetterValues = (WireLetter[]) Enum.GetValues(typeof(WireLetter)); + + [Test] + public void TestColorNameExists([ValueSource(nameof(ColorValues))] WireColor color) + { + Assert.DoesNotThrow(() => color.Name()); + } + + [Test] + public void TestColorValueExists([ValueSource(nameof(ColorValues))] WireColor color) + { + Assert.DoesNotThrow(() => color.ColorValue()); + } + + [Test] + public void TestLetterNameExists([ValueSource(nameof(LetterValues))] WireLetter letter) + { + Assert.DoesNotThrow(() => letter.Name()); + } + + [Test] + public void TestLetterLetterExists([ValueSource(nameof(LetterValues))] WireLetter letter) + { + Assert.DoesNotThrow(() => letter.Letter()); + } + } +} diff --git a/Resources/Fonts/Boxfont-round/Boxfont Round.ttf b/Resources/Fonts/Boxfont-round/Boxfont Round.ttf new file mode 100644 index 0000000000..bcd299e1f6 Binary files /dev/null and b/Resources/Fonts/Boxfont-round/Boxfont Round.ttf differ diff --git a/Resources/Fonts/Boxfont-round/credits.txt b/Resources/Fonts/Boxfont-round/credits.txt new file mode 100644 index 0000000000..13c84cdfea --- /dev/null +++ b/Resources/Fonts/Boxfont-round/credits.txt @@ -0,0 +1,2 @@ + https://fontlibrary.org/en/font/boxfont-round + License: CC-0 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Buildings/airlock_base.yml b/Resources/Prototypes/Entities/Buildings/airlock_base.yml index 1609b374ed..d2b224e15f 100644 --- a/Resources/Prototypes/Entities/Buildings/airlock_base.yml +++ b/Resources/Prototypes/Entities/Buildings/airlock_base.yml @@ -42,6 +42,8 @@ - type: WiresVisualizer2D - type: PowerDevice - type: Wires + BoardName: "Airlock Control" + LayoutId: Airlock - type: UserInterface interfaces: - key: enum.WiresUiKey.Key diff --git a/Resources/Prototypes/Entities/Buildings/vending_machines.yml b/Resources/Prototypes/Entities/Buildings/vending_machines.yml index c32f92b4dd..0a3a589467 100644 --- a/Resources/Prototypes/Entities/Buildings/vending_machines.yml +++ b/Resources/Prototypes/Entities/Buildings/vending_machines.yml @@ -44,6 +44,8 @@ - type: PowerDevice priority: Low - type: Wires + BoardName: "Vending Machine" + LayoutId: Vending - type: Anchorable - type: entity diff --git a/Resources/Textures/UserInterface/WireHacking/contact.svg b/Resources/Textures/UserInterface/WireHacking/contact.svg new file mode 100644 index 0000000000..09ffb629a5 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/contact.svg @@ -0,0 +1,71 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/contact.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/contact.svg.96dpi.png new file mode 100644 index 0000000000..83f4785824 Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/contact.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/contact.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/contact.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/contact.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/UserInterface/WireHacking/light_off_base.svg b/Resources/Textures/UserInterface/WireHacking/light_off_base.svg new file mode 100644 index 0000000000..51d4f69e49 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/light_off_base.svg @@ -0,0 +1,66 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png new file mode 100644 index 0000000000..53c014cd0e Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/UserInterface/WireHacking/light_on_base.svg b/Resources/Textures/UserInterface/WireHacking/light_on_base.svg new file mode 100644 index 0000000000..ea1d6da98d --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/light_on_base.svg @@ -0,0 +1,66 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png new file mode 100644 index 0000000000..15e585ce1b Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1.svg b/Resources/Textures/UserInterface/WireHacking/wire_1.svg new file mode 100644 index 0000000000..231bad6a78 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_1.svg @@ -0,0 +1,68 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png new file mode 100644 index 0000000000..f2b8a9aad9 Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg b/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg new file mode 100644 index 0000000000..0400b76b8c --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg @@ -0,0 +1,68 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png new file mode 100644 index 0000000000..eb32069a80 Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg b/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg new file mode 100644 index 0000000000..257897a634 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg @@ -0,0 +1,68 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png new file mode 100644 index 0000000000..6a0b2be488 Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2.svg b/Resources/Textures/UserInterface/WireHacking/wire_2.svg new file mode 100644 index 0000000000..b46ca98349 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_2.svg @@ -0,0 +1,68 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png new file mode 100644 index 0000000000..eab2baeaa5 Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg b/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg new file mode 100644 index 0000000000..68737e7245 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg @@ -0,0 +1,68 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png new file mode 100644 index 0000000000..25c27dfa6e Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg b/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg new file mode 100644 index 0000000000..d667b357d0 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg @@ -0,0 +1,68 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png b/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png new file mode 100644 index 0000000000..f48b030649 Binary files /dev/null and b/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png differ diff --git a/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png.yml b/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true