From 195c16d800fec28d1e96d8cb6e3b98cb610a13a7 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 27 May 2020 15:09:22 +0200 Subject: [PATCH] Wire hacking is now fancy. 1. new UI 2. wires are correctly randomized. 3. wires layouts are shared across machines in the same round. --- .../Wires/WiresBoundUserInterface.cs | 19 +- .../GameObjects/Components/Wires/WiresMenu.cs | 612 +++++++++++++++++- .../Components/Doors/AirlockComponent.cs | 53 +- .../GameObjects/Components/WiresComponent.cs | 321 +++++++-- .../EntitySystems/WireHackingSystem.cs | 52 ++ Content.Server/GameTicking/GameTicker.cs | 4 + .../Components/Doors/AirlockWireStatus.cs | 11 + .../Components/SharedWiresComponent.cs | 240 ++++++- Content.Tests/Shared/WireHackingTest.cs | 43 ++ .../Fonts/Boxfont-round/Boxfont Round.ttf | Bin 0 -> 41540 bytes Resources/Fonts/Boxfont-round/credits.txt | 2 + .../Entities/Buildings/airlock_base.yml | 2 + .../Entities/Buildings/vending_machines.yml | 2 + .../UserInterface/WireHacking/contact.svg | 71 ++ .../WireHacking/contact.svg.96dpi.png | Bin 0 -> 182 bytes .../WireHacking/contact.svg.96dpi.png.yml | 2 + .../WireHacking/light_off_base.svg | 66 ++ .../WireHacking/light_off_base.svg.96dpi.png | Bin 0 -> 212 bytes .../light_off_base.svg.96dpi.png.yml | 2 + .../WireHacking/light_on_base.svg | 66 ++ .../WireHacking/light_on_base.svg.96dpi.png | Bin 0 -> 345 bytes .../light_on_base.svg.96dpi.png.yml | 2 + .../UserInterface/WireHacking/wire_1.svg | 68 ++ .../WireHacking/wire_1.svg.96dpi.png | Bin 0 -> 554 bytes .../WireHacking/wire_1.svg.96dpi.png.yml | 2 + .../WireHacking/wire_1_copper.svg | 68 ++ .../WireHacking/wire_1_copper.svg.96dpi.png | Bin 0 -> 524 bytes .../wire_1_copper.svg.96dpi.png.yml | 2 + .../UserInterface/WireHacking/wire_1_cut.svg | 68 ++ .../WireHacking/wire_1_cut.svg.96dpi.png | Bin 0 -> 498 bytes .../WireHacking/wire_1_cut.svg.96dpi.png.yml | 2 + .../UserInterface/WireHacking/wire_2.svg | 68 ++ .../WireHacking/wire_2.svg.96dpi.png | Bin 0 -> 500 bytes .../WireHacking/wire_2.svg.96dpi.png.yml | 2 + .../WireHacking/wire_2_copper.svg | 68 ++ .../WireHacking/wire_2_copper.svg.96dpi.png | Bin 0 -> 451 bytes .../wire_2_copper.svg.96dpi.png.yml | 2 + .../UserInterface/WireHacking/wire_2_cut.svg | 68 ++ .../WireHacking/wire_2_cut.svg.96dpi.png | Bin 0 -> 444 bytes .../WireHacking/wire_2_cut.svg.96dpi.png.yml | 2 + 40 files changed, 1855 insertions(+), 135 deletions(-) create mode 100644 Content.Server/GameObjects/EntitySystems/WireHackingSystem.cs create mode 100644 Content.Shared/GameObjects/Components/Doors/AirlockWireStatus.cs create mode 100644 Content.Tests/Shared/WireHackingTest.cs create mode 100644 Resources/Fonts/Boxfont-round/Boxfont Round.ttf create mode 100644 Resources/Fonts/Boxfont-round/credits.txt create mode 100644 Resources/Textures/UserInterface/WireHacking/contact.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/contact.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/contact.svg.96dpi.png.yml create mode 100644 Resources/Textures/UserInterface/WireHacking/light_off_base.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/light_off_base.svg.96dpi.png.yml create mode 100644 Resources/Textures/UserInterface/WireHacking/light_on_base.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/light_on_base.svg.96dpi.png.yml create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1.svg.96dpi.png.yml create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1_copper.svg.96dpi.png.yml create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_1_cut.svg.96dpi.png.yml create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2.svg.96dpi.png.yml create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2_copper.svg.96dpi.png.yml create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png create mode 100644 Resources/Textures/UserInterface/WireHacking/wire_2_cut.svg.96dpi.png.yml 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 0000000000000000000000000000000000000000..bcd299e1f60b07a425363e437e814a0cb3157fdd GIT binary patch literal 41540 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 XjZ>HR_Vsu7JoRMLqz9LG9PR)AXzwHt literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..83f478582477cc56e298b676b7b8115dff6c19a7 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP?58zi(`mI@7pUIc@HRvFb4{6 zG?rA1mX~c3dU~p!*=A?akNeYY8EpbNV>0@$GhAYDG3oCVjYw!Te~`VHvF&T3)hhkD YQ?)i{^wyox0vgBQ>FVdQ&MBb@02JsrWdHyG literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..53c014cd0e9b114ac6e530d74130f488a8ed21f4 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0Tm^Bx;TbNOzoX;kheiW#92Gt zT=-Ey+mWR%> + + + + + + + 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 0000000000000000000000000000000000000000..15e585ce1b265590c955099400f73a4af07fc93c GIT binary patch literal 345 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-6_7sn8b-nCOt`!XAfFdTO~ z*(tO`!HVJ62ccO?4u+ocEo@FjPMHjT3WhRs7Hlxg+46d4RQ{9a-wPKsSh3|bu*5K> z-(DR4Kyn7-W`RTQn)`HjhlZDZUgY;rckaUQNC@cyVP>Zv*irWzDt~n zl0BYls&BRW>RHPPN*6*Vy!8;=u}WhP-z({YD9;~tH|!7p=sK`$QGsxytLwvH75*yS zXDgITBNuKAf2Pa&YDts9-(9OKv%frjr%-9S>bq@9u=ne1|2q|TX6xlH=WMZGqWq~x o{Zq!fdPeS|^fS&J^WWac{~0;M(p6xEC(y?Xp00i_>zopr0A>J*#{d8T literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f2b8a9aad99580227fe558e6058a9ff40f148df6 GIT binary patch literal 554 zcmV+_0@eMAP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10k=s+ zK~zYI&6d3@hG7)O&rQB548F2a@-Zq&8I{Q!Cw9LCN+RYir~J8WzOO9|IhxgI z`Kx00E1)=9L5o=5N+~@@0e51(8&F^gI1=miX0{HfZ{vFaJ;1AT5<5Ue1TyOY0G1+v zB49lWWIO^X2euu+EzlT&)C0#3WIiY09nuRt3&0D|9ED6dRZK(yHNd?9tbAy)PUV!+ zwdqmz*+OKjmnwD$0Q8$#pIF+@xh>EHoH`G2ESI?u*mTTifNJX-VA3%IjN~#mI!6bv z5b1r|G4Do7F9U9b`4Q-h>^|h|KI3_Qz6bq|E(~ZDnuCvTNR%HG&{!_yI|iT>KEt1YeUTgb%B(c*T{aX*m8^g56ex_lg!~g&Q07*qoM6N<$f{jw*qW}N^ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eb32069a805b20e99626ce57aaf25a032e7cec74 GIT binary patch literal 524 zcmV+n0`vWeP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10hvie zK~zYI)s??1g<%xO&vTLFF84~xV8J9|ut;>pU{N-IgGsV5nG6QIto{IlQWmAKh?GGT z8D#KdaqH^3d<^$1H@Yv+nK;X-_j$gb^F8nTyyr3d(*f)PyIye%<+w}Qmr7WhR6g`n zuLCaRV9EoSmiP>)jTJWnw*pN5toROCkoW)yvEmHyC_v6reNp1AXt4lS0t|b^8Hw*> zdapc44Q5s}v#gnoMmvy;k1ucoWIccyU{8>BuZs>KFUYtTGAqC}koEvlz`h_WUKc$; z5hww@)%I1L2O)$LGg~sVU}nuO7jhE+4*qTc1HgRrl&Sz6>C^Uk#7)2}PyiY}o+pj< zuaVRKy%0jd0~k@>iO_roaIXHlJ>oWrFTSq*wuL$64Nvt6;0<^Nx?;tx8nOU0p6Y87 zZ+dR2PT)l|q1{t`S=Cpg#R8lPFz68nz4_8le_pk7P;cc?RtzDOq8-S^gIZ|uI50Ci zRvz*Kww1?{096C%R|}_JKw5yOqb%`6{%j4NDZ0spP^KjIz7QKhFa8>jLB O0000 + + + + + + + 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 0000000000000000000000000000000000000000..6a0b2be48886464f6a10bc6e45c3cd05809033a2 GIT binary patch literal 498 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10e?wE zK~zYI?UuhQ#ZeT%&-bC89}}fVUKS}~k}L+9O>ZwV1gi#o>VZ3evLTrC2<8mBV@USBU5o=2MQ{)FdL`?1;2p3G zw7THBr5pqEK#Ln}L?c20WPxFz`L}|#>d}sY3t$^q;_Cr&3HFo=R;rEvXaO=JGOY}q zc`B!XBjX#9^9WW9_z6gd;0*9;2sS;{w+y%lw1wa<;7x&JV8l~>)~LP_gsXyG1+Dk07*qoM6N<$f?LJW{Qv*} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eab2baeaa59df4ed1d2543f1f26b37f7aa2ae776 GIT binary patch literal 500 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10f9+G zK~zYI#g@-2MNt%mzk_%}exyv06h(|A49GvNlmTU+kdmi_LXo7z8_1(96SKQ^_Diyd zt98G#?mheLd%%>WZB=XAdE1=WO8psN$Y{^4049xgodQUj0D6u0CYxY6NOkrbfPnoh zV8LkTrIe>EV9sa*5uhW`n^}OH2>@?dfZMVraGwRN8m(SR`S=kaX&9I{fJ3*E zORx?Uqo@DQF1~=BEWi!cvr>v}E(e%4nmZd+fTS*9%=n^NSxL|nq`J!jIs;#&g(H9h zUso3J2voWa7*7F8DWAZt@hxT(fWJsAfA3HVuxqq#{KX>;fENA>b)aGN+*tqqknTn{ ql1%{cVl;dChyYPTr2*;sf4~=2nUm#X20pw10000 + + + + + + + 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 0000000000000000000000000000000000000000..25c27dfa6ef1519710c36c2905f737a5bb83d26c GIT binary patch literal 451 zcmV;!0X+VRP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Z>Uq zK~zYI-ImQR1VI#qtC-AKArg_W7-Au2cmwPu-e?|6yZ}oJ8?5{+{KP_RCc#8{J`1N- zs&2O{M4Zj(PTl%)Z`bXr3c(PP6*FNCHqp-kfN4Dry%{XZ@4bMo{LTy5ke_=2efimU zGdQ&XSpg9ks*mP8fE7(PKVS&u5_)p8ehZM6@X`xdlDkW2)P)w{S>E;nuH~yxu5_5 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f48b0306497397fdf5cb5428b0a6176a4c94b4e2 GIT binary patch literal 444 zcmV;t0YmpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10ZBik`Zu3w~k!4&zV*9Yk)E1y;K3r8t)+m07fN^_Y9D<8nn90 z0@eb+#ZLfjNtZtXT$fT>S-_IC8sUv}L@Sq_b>;6Vo6!EI>`ty2&1<0Fo9Z4IBS73s`YMN#|KW^tkLcIKZUwko1%V zxG!_7l=7YhxDg$2mm|Qd@oK>m40000