From 1292adb001beb0285cd6303f64e8e9d9bc37fcfd Mon Sep 17 00:00:00 2001 From: Julian Giebel Date: Wed, 19 Aug 2020 15:50:06 +0200 Subject: [PATCH] Disposal routing (#1710) * Implement disposal tagger Implement disposal router Combine sprites to make conpipe-tagger sprite * Implement change requests * Remove nullable * Update DisposalHolderComponent.cs * Update DisposalHolderComponent.cs * Update Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs Co-authored-by: DrSmugleaf Co-authored-by: Julian Giebel Co-authored-by: DrSmugleaf --- .../DisposalRouterBoundUserInterface.cs | 69 ++ .../Disposal/DisposalRouterWindow.cs | 56 ++ .../DisposalTaggerBoundUserInterface.cs | 69 ++ .../Disposal/DisposalTaggerWindow.cs | 56 ++ Content.Client/IgnoredComponents.cs | 2 + .../Disposal/DisposalHolderComponent.cs | 7 + .../Disposal/DisposalRouterComponent.cs | 179 ++++ .../Disposal/DisposalTaggerComponent.cs | 150 ++++ .../Disposal/SharedDisposalRouterComponent.cs | 52 ++ .../Disposal/SharedDisposalTaggerComponent.cs | 53 ++ .../Entities/Constructible/disposal.yml | 82 ++ .../Power/disposal.rsi/conpipe-tagger.png | Bin 0 -> 2549 bytes .../Power/disposal.rsi/meta.json | 780 +++++++++++++++++- .../Power/disposal.rsi/pipe-tagger.png | Bin 2228 -> 2383 bytes 14 files changed, 1554 insertions(+), 1 deletion(-) create mode 100644 Content.Client/GameObjects/Components/Disposal/DisposalRouterBoundUserInterface.cs create mode 100644 Content.Client/GameObjects/Components/Disposal/DisposalRouterWindow.cs create mode 100644 Content.Client/GameObjects/Components/Disposal/DisposalTaggerBoundUserInterface.cs create mode 100644 Content.Client/GameObjects/Components/Disposal/DisposalTaggerWindow.cs create mode 100644 Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs create mode 100644 Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Disposal/SharedDisposalRouterComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Disposal/SharedDisposalTaggerComponent.cs create mode 100644 Resources/Textures/Constructible/Power/disposal.rsi/conpipe-tagger.png diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalRouterBoundUserInterface.cs b/Content.Client/GameObjects/Components/Disposal/DisposalRouterBoundUserInterface.cs new file mode 100644 index 0000000000..00f30c02ff --- /dev/null +++ b/Content.Client/GameObjects/Components/Disposal/DisposalRouterBoundUserInterface.cs @@ -0,0 +1,69 @@ +#nullable enable +using JetBrains.Annotations; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Localization; +using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent; + +namespace Content.Client.GameObjects.Components.Disposal +{ + /// + /// Initializes a and updates it when new server messages are received. + /// + [UsedImplicitly] + public class DisposalRouterBoundUserInterface : BoundUserInterface + { + private DisposalRouterWindow? _window; + + public DisposalRouterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _window = new DisposalRouterWindow + { + Title = Loc.GetString("Disposal Router"), + }; + + _window.OpenCentered(); + _window.OnClose += Close; + + _window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text); + + } + + private void ButtonPressed(UiAction action, string tag) + { + SendMessage(new UiActionMessage(action, tag)); + _window?.Close(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (!(state is DisposalRouterUserInterfaceState cast)) + { + return; + } + + _window?.UpdateState(cast); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _window?.Dispose(); + } + } + + + } + +} diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalRouterWindow.cs b/Content.Client/GameObjects/Components/Disposal/DisposalRouterWindow.cs new file mode 100644 index 0000000000..f9f850ae18 --- /dev/null +++ b/Content.Client/GameObjects/Components/Disposal/DisposalRouterWindow.cs @@ -0,0 +1,56 @@ +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using Content.Shared.GameObjects.Components.Disposal; +using Robust.Client.Graphics.Drawing; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent; + +namespace Content.Client.GameObjects.Components.Disposal +{ + /// + /// Client-side UI used to control a + /// + public class DisposalRouterWindow : SS14Window + { + public readonly LineEdit TagInput; + public readonly Button Confirm; + + protected override Vector2? CustomSize => (400, 80); + + private Regex _tagRegex; + + public DisposalRouterWindow() + { + _tagRegex = new Regex("^[a-zA-Z0-9, ]*$", RegexOptions.Compiled); + + Contents.AddChild(new VBoxContainer + { + Children = + { + new Label {Text = Loc.GetString("Tags:")}, + new Control {CustomMinimumSize = (0, 10)}, + new HBoxContainer + { + Children = + { + (TagInput = new LineEdit {SizeFlagsHorizontal = SizeFlags.Expand, CustomMinimumSize = (320, 0), + ToolTip = Loc.GetString("A comma separated list of tags"), IsValid = tags => _tagRegex.IsMatch(tags)}), + new Control {CustomMinimumSize = (10, 0)}, + (Confirm = new Button {Text = Loc.GetString("Confirm")}) + } + } + } + }); + } + + + public void UpdateState(DisposalRouterUserInterfaceState state) + { + TagInput.Text = state.Tags; + } + } +} diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalTaggerBoundUserInterface.cs b/Content.Client/GameObjects/Components/Disposal/DisposalTaggerBoundUserInterface.cs new file mode 100644 index 0000000000..273b8c06ec --- /dev/null +++ b/Content.Client/GameObjects/Components/Disposal/DisposalTaggerBoundUserInterface.cs @@ -0,0 +1,69 @@ +#nullable enable +using JetBrains.Annotations; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Localization; +using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent; + +namespace Content.Client.GameObjects.Components.Disposal +{ + /// + /// Initializes a and updates it when new server messages are received. + /// + [UsedImplicitly] + public class DisposalTaggerBoundUserInterface : BoundUserInterface + { + private DisposalTaggerWindow? _window; + + public DisposalTaggerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _window = new DisposalTaggerWindow + { + Title = Loc.GetString("Disposal Tagger"), + }; + + _window.OpenCentered(); + _window.OnClose += Close; + + _window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text); + + } + + private void ButtonPressed(UiAction action, string tag) + { + SendMessage(new UiActionMessage(action, tag)); + _window?.Close(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (!(state is DisposalTaggerUserInterfaceState cast)) + { + return; + } + + _window?.UpdateState(cast); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + _window?.Dispose(); + } + } + + + } + +} diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalTaggerWindow.cs b/Content.Client/GameObjects/Components/Disposal/DisposalTaggerWindow.cs new file mode 100644 index 0000000000..7fb6dc6937 --- /dev/null +++ b/Content.Client/GameObjects/Components/Disposal/DisposalTaggerWindow.cs @@ -0,0 +1,56 @@ +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using Content.Shared.GameObjects.Components.Disposal; +using Robust.Client.Graphics.Drawing; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent; + +namespace Content.Client.GameObjects.Components.Disposal +{ + /// + /// Client-side UI used to control a + /// + public class DisposalTaggerWindow : SS14Window + { + public readonly LineEdit TagInput; + public readonly Button Confirm; + + protected override Vector2? CustomSize => (400, 80); + + private Regex _tagRegex; + + public DisposalTaggerWindow() + { + _tagRegex = new Regex("^[a-zA-Z0-9 ]*$", RegexOptions.Compiled); + + Contents.AddChild(new VBoxContainer + { + Children = + { + new Label {Text = Loc.GetString("Tag:")}, + new Control {CustomMinimumSize = (0, 10)}, + new HBoxContainer + { + Children = + { + (TagInput = new LineEdit {SizeFlagsHorizontal = SizeFlags.Expand, CustomMinimumSize = (320, 0), + IsValid = tag => _tagRegex.IsMatch(tag)}), + new Control {CustomMinimumSize = (10, 0)}, + (Confirm = new Button {Text = Loc.GetString("Confirm")}) + } + } + } + }); + } + + + public void UpdateState(DisposalTaggerUserInterfaceState state) + { + TagInput.Text = state.Tag; + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index d4588e5c84..6e67d79995 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -142,6 +142,8 @@ "Listening", "Radio", "DisposalHolder", + "DisposalTagger", + "DisposalRouter", "DisposalTransit", "DisposalEntry", "DisposalJunction", diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs index ee39a57d76..44c1a1eab3 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Items.Storage; using Content.Shared.GameObjects.Components.Body; @@ -41,6 +42,12 @@ namespace Content.Server.GameObjects.Components.Disposal [ViewVariables] public IDisposalTubeComponent? NextTube { get; set; } + /// + /// A list of tags attached to the content, used for sorting + /// + [ViewVariables] + public HashSet Tags { get; set; } = new HashSet(); + private bool CanInsert(IEntity entity) { if (!_contents.CanInsert(entity)) diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs new file mode 100644 index 0000000000..1abf3a7b2d --- /dev/null +++ b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs @@ -0,0 +1,179 @@ +using Content.Server.Interfaces; +using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using System; +using System.Collections.Generic; +using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent; + +namespace Content.Server.GameObjects.Components.Disposal +{ + [RegisterComponent] + [ComponentReference(typeof(IActivate))] + [ComponentReference(typeof(IDisposalTubeComponent))] + public class DisposalRouterComponent : DisposalJunctionComponent, IActivate + { +#pragma warning disable 649 + [Dependency] private readonly IServerNotifyManager _notifyManager; +#pragma warning restore 649 + public override string Name => "DisposalRouter"; + + [ViewVariables] + private BoundUserInterface _userInterface; + + [ViewVariables] + private HashSet _tags; + + [ViewVariables] + public bool Anchored => + !Owner.TryGetComponent(out CollidableComponent collidable) || + collidable.Anchored; + + public override Direction NextDirection(DisposalHolderComponent holder) + { + var directions = ConnectableDirections(); + + if (holder.Tags.Overlaps(_tags)) + { + return directions[1]; + } + + return Owner.Transform.LocalRotation.GetDir(); + } + + + public override void Initialize() + { + base.Initialize(); + _userInterface = Owner.GetComponent() + .GetBoundUserInterface(DisposalRouterUiKey.Key); + _userInterface.OnReceiveMessage += OnUiReceiveMessage; + + _tags = new HashSet(); + + UpdateUserInterface(); + } + + /// + /// Handles ui messages from the client. For things such as button presses + /// which interact with the world and require server action. + /// + /// A user interface message from the client. + private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) + { + var msg = (UiActionMessage) obj.Message; + + if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity)) + return; + + if (msg.Action == UiAction.Ok) + { + _tags.Clear(); + foreach (var tag in msg.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries)) + { + _tags.Add(tag.Trim()); + } + } + + ClickSound(); + } + + /// + /// Checks whether the player entity is able to use the configuration interface of the pipe tagger. + /// + /// The player entity. + /// Returns true if the entity can use the configuration interface, and false if it cannot. + private bool PlayerCanUseDisposalTagger(IEntity playerEntity) + { + //Need player entity to check if they are still able to use the configuration interface + if (playerEntity == null) + return false; + if (!Anchored) + return false; + //Check if player can interact in their current state + if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity)) + return false; + + return true; + } + + /// + /// Gets component data to be used to update the user interface client-side. + /// + /// Returns a + private DisposalRouterUserInterfaceState GetUserInterfaceState() + { + if(_tags == null || _tags.Count <= 0) + { + return new DisposalRouterUserInterfaceState(""); + } + + var taglist = new System.Text.StringBuilder(); + + foreach (var tag in _tags) + { + taglist.Append(tag); + taglist.Append(", "); + } + + taglist.Remove(taglist.Length - 2, 2); + + return new DisposalRouterUserInterfaceState(taglist.ToString()); + } + + private void UpdateUserInterface() + { + var state = GetUserInterfaceState(); + _userInterface.SetState(state); + } + + private void ClickSound() + { + EntitySystem.Get().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f)); + } + + /// + /// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible. + /// + /// Data relevant to the event such as the actor which triggered it. + void IActivate.Activate(ActivateEventArgs args) + { + if (!args.User.TryGetComponent(out IActorComponent actor)) + { + return; + } + + if (!args.User.TryGetComponent(out IHandsComponent hands)) + { + _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, + Loc.GetString("You have no hands.")); + return; + } + + var activeHandEntity = hands.GetActiveHand?.Owner; + if (activeHandEntity == null) + { + UpdateUserInterface(); + _userInterface.Open(actor.playerSession); + } + } + + public override void OnRemove() + { + _userInterface.CloseAll(); + base.OnRemove(); + } + } +} diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs new file mode 100644 index 0000000000..f949e37090 --- /dev/null +++ b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs @@ -0,0 +1,150 @@ +using Content.Server.Interfaces; +using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent; + +namespace Content.Server.GameObjects.Components.Disposal +{ + [RegisterComponent] + [ComponentReference(typeof(IActivate))] + [ComponentReference(typeof(IDisposalTubeComponent))] + public class DisposalTaggerComponent : DisposalTransitComponent, IActivate + { +#pragma warning disable 649 + [Dependency] private readonly IServerNotifyManager _notifyManager; +#pragma warning restore 649 + public override string Name => "DisposalTagger"; + + [ViewVariables] + private BoundUserInterface _userInterface; + + [ViewVariables(VVAccess.ReadWrite)] + private string _tag = ""; + + [ViewVariables] + public bool Anchored => + !Owner.TryGetComponent(out CollidableComponent collidable) || + collidable.Anchored; + + public override Direction NextDirection(DisposalHolderComponent holder) + { + holder.Tags.Add(_tag); + return base.NextDirection(holder); + } + + + public override void Initialize() + { + base.Initialize(); + _userInterface = Owner.GetComponent() + .GetBoundUserInterface(DisposalTaggerUiKey.Key); + _userInterface.OnReceiveMessage += OnUiReceiveMessage; + + UpdateUserInterface(); + } + + /// + /// Handles ui messages from the client. For things such as button presses + /// which interact with the world and require server action. + /// + /// A user interface message from the client. + private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) + { + var msg = (UiActionMessage) obj.Message; + + if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity)) + return; + + if (msg.Action == UiAction.Ok) + { + _tag = msg.Tag; + } + + ClickSound(); + } + + /// + /// Checks whether the player entity is able to use the configuration interface of the pipe tagger. + /// + /// The player entity. + /// Returns true if the entity can use the configuration interface, and false if it cannot. + private bool PlayerCanUseDisposalTagger(IEntity playerEntity) + { + //Need player entity to check if they are still able to use the configuration interface + if (playerEntity == null) + return false; + if (!Anchored) + return false; + //Check if player can interact in their current state + if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity)) + return false; + + return true; + } + + /// + /// Gets component data to be used to update the user interface client-side. + /// + /// Returns a + private DisposalTaggerUserInterfaceState GetUserInterfaceState() + { + return new DisposalTaggerUserInterfaceState(_tag); + } + + private void UpdateUserInterface() + { + var state = GetUserInterfaceState(); + _userInterface.SetState(state); + } + + private void ClickSound() + { + EntitySystem.Get().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f)); + } + + /// + /// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible. + /// + /// Data relevant to the event such as the actor which triggered it. + void IActivate.Activate(ActivateEventArgs args) + { + if (!args.User.TryGetComponent(out IActorComponent actor)) + { + return; + } + + if (!args.User.TryGetComponent(out IHandsComponent hands)) + { + _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, + Loc.GetString("You have no hands.")); + return; + } + + var activeHandEntity = hands.GetActiveHand?.Owner; + if (activeHandEntity == null) + { + UpdateUserInterface(); + _userInterface.Open(actor.playerSession); + } + } + + public override void OnRemove() + { + base.OnRemove(); + _userInterface.CloseAll(); + } + } +} diff --git a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalRouterComponent.cs b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalRouterComponent.cs new file mode 100644 index 0000000000..c0323cbaa1 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalRouterComponent.cs @@ -0,0 +1,52 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Serialization; +using System; + +namespace Content.Shared.GameObjects.Components.Disposal +{ + public class SharedDisposalRouterComponent : Component + { + public override string Name => "DisposalRouter"; + + [Serializable, NetSerializable] + public class DisposalRouterUserInterfaceState : BoundUserInterfaceState + { + public readonly string Tags; + + public DisposalRouterUserInterfaceState(string tags) + { + Tags = tags; + } + } + + [Serializable, NetSerializable] + public class UiActionMessage : BoundUserInterfaceMessage + { + public readonly UiAction Action; + public readonly string Tags = ""; + + public UiActionMessage(UiAction action, string tags) + { + Action = action; + + if (Action == UiAction.Ok) + { + Tags = tags.Substring(0, Math.Min(tags.Length, 150)); + } + } + } + + [Serializable, NetSerializable] + public enum UiAction + { + Ok + } + + [Serializable, NetSerializable] + public enum DisposalRouterUiKey + { + Key + } + } +} diff --git a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalTaggerComponent.cs b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalTaggerComponent.cs new file mode 100644 index 0000000000..d12e9060e9 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalTaggerComponent.cs @@ -0,0 +1,53 @@ +#nullable enable +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Serialization; +using System; + +namespace Content.Shared.GameObjects.Components.Disposal +{ + public class SharedDisposalTaggerComponent : Component + { + public override string Name => "DisposalTagger"; + + [Serializable, NetSerializable] + public class DisposalTaggerUserInterfaceState : BoundUserInterfaceState + { + public readonly string Tag; + + public DisposalTaggerUserInterfaceState(string tag) + { + Tag = tag; + } + } + + [Serializable, NetSerializable] + public class UiActionMessage : BoundUserInterfaceMessage + { + public readonly UiAction Action; + public readonly string Tag = ""; + + public UiActionMessage(UiAction action, string tag) + { + Action = action; + + if (Action == UiAction.Ok) + { + Tag = tag; + } + } + } + + [Serializable, NetSerializable] + public enum UiAction + { + Ok + } + + [Serializable, NetSerializable] + public enum DisposalTaggerUiKey + { + Key + } + } +} diff --git a/Resources/Prototypes/Entities/Constructible/disposal.yml b/Resources/Prototypes/Entities/Constructible/disposal.yml index 5187ce2a88..1d85dd33a6 100644 --- a/Resources/Prototypes/Entities/Constructible/disposal.yml +++ b/Resources/Prototypes/Entities/Constructible/disposal.yml @@ -44,6 +44,31 @@ state_anchored: pipe-s state_broken: pipe-b +- type: entity + id: DisposalTagger + parent: DisposalPipeBase + name: disposal pipe tagger + description: A pipe that tags entities for routing + components: + - type: Sprite + drawdepth: BelowFloor + sprite: Constructible/Power/disposal.rsi + state: conpipe-tagger + - type: Icon + sprite: Constructible/Power/disposal.rsi + state: conpipe-tagger + - type: DisposalTagger + - type: Appearance + visuals: + - type: DisposalVisualizer + state_free: conpipe-tagger + state_anchored: pipe-tagger + state_broken: pipe-b + - type: UserInterface + interfaces: + - key: enum.DisposalTaggerUiKey.Key + type: DisposalTaggerBoundUserInterface + - type: entity id: DisposalTrunk parent: DisposalPipeBase @@ -131,6 +156,63 @@ - key: enum.DisposalUnitUiKey.Key type: DisposalUnitBoundUserInterface +- type: entity + id: DisposalRouter + parent: DisposalPipeBase + name: disposal router + description: A three-way router. Entities with matching tags get routed to the side + components: + - type: Sprite + drawdepth: BelowFloor + sprite: Constructible/Power/disposal.rsi + state: conpipe-j1s + - type: Icon + sprite: Constructible/Power/disposal.rsi + state: conpipe-j1s + - type: DisposalRouter + degrees: + - 0 + - -90 + - 180 + - type: Appearance + visuals: + - type: DisposalVisualizer + state_free: conpipe-j1s + state_anchored: pipe-j1s + state_broken: pipe-b + - type: Flippable + entity: DisposalRouterFlipped + - type: UserInterface + interfaces: + - key: enum.DisposalRouterUiKey.Key + type: DisposalRouterBoundUserInterface + +- type: entity + id: DisposalRouterFlipped + parent: DisposalRouter + name: flipped router junction + components: + - type: Sprite + drawdepth: BelowFloor + sprite: Constructible/Power/disposal.rsi + state: conpipe-j2s + - type: Icon + sprite: Constructible/Power/disposal.rsi + state: conpipe-j2s + - type: DisposalRouter + degrees: + - 0 + - 90 + - 180 + - type: Appearance + visuals: + - type: DisposalVisualizer + state_free: conpipe-j2s + state_anchored: pipe-j2s + state_broken: pipe-b + - type: Flippable + entity: DisposalRouter + - type: entity id: DisposalJunction parent: DisposalPipeBase diff --git a/Resources/Textures/Constructible/Power/disposal.rsi/conpipe-tagger.png b/Resources/Textures/Constructible/Power/disposal.rsi/conpipe-tagger.png new file mode 100644 index 0000000000000000000000000000000000000000..e1c4637244ab4b0390a785d54665da75ce7e54a4 GIT binary patch literal 2549 zcmVPx;uSrBfRCt{2noVyUMHHb3VjwXYY~wfy*wfR~J>6A%m=}AP z3Dc8IgtVETG?Jw%*T3qimwuk73fer_h(;Brrl$1kufI-jPY*&As|z72f0xk!TBEfF z;K$Wf-g@gTjvYJJP@|Fl8*jY9&aSSCHbT^NTyN3VTC=pg%-e6j-B4dP>cEK;Cm0wQ zU}<@o&h6XzVQGmyd-rlZlVSf;PjNky;g2IDWHK2Z>+9o(r6oGIZ^!q2PM$n@m&4xV zOE|ku!@8rRgIFwvX_{Ci-9#cm zEEYp+jcwcH9fzFbVB0pn=i>PRwrz9i(xqS3Hyd@}(xppcaNj=Nwfzxv;FHf~Q3aQr zy}?hnB%nbg;!sFl?xXx+{^ema^wgfeDDFgdv;I=OmtAdcO5o< zHjuJbUn?I3(qI?{r%t`Yv7<);SXo}e z&N+Pc`4_CM-Q?@bm+xuz%M1^v^uf{5ru5g>*GYf*RYU!TX8?dv;qPA1PWg`FE`!mhcA98l*uyX7SM{|KaM@t2I?dM^ZSBLqRKq zG(tDsP#V#+epo^qKuCjVEY8QD7R3{mRaIW@(7ygh5mMIp3HM5~`e6xe04WWGXxwne zwhj=b8EX+j+=YHfnmPlN-*|6zbR>~N(7NH5K2=TXhb8n{(LsRz#ol`5`8bCTR1dYI zV;k{!Jah*_A;f*t4{ZP;B}O#bu%4CNMkLx=18A-9n|}B+z=+gsxMzn2n{*k4&(-yo3t;Qqr<@OF*ljY5lN- zGJs)7RN!wOLPGG`-(Dem`v#H3!^J>FO3X-L(@LQc`qz zWtEp-ewjDleDj_hg5$@J>-XM!k6pdp7=}SC8pAXV3@IyNqEZSK1bBr4z8`@0SzY^y z%U7=4ds6iOJuTjF3n-FimD>XHiNqJw1(-lDWA#VzC$(FJ2@b zkF&VA*b;YGYt7i^O6v=H}**QZhX~jZ%u)*;x$JWPX00cs$N`*A}rX3#A%+ zgb)1HSerI&+O%oYrcIj%o~fxRol2$j@bIuce*AcgGn&PyZg_ZDr&1|BH8oY!{?ycz z9vmFhLqkI?*DfN4?Y6d?uabedhgy)`o=gkWT3gnT{^h?eUkOw6cRnSe6p z)((t~jp4d363xi|rz(}S)_nNkhfOWHzaDyqyz3wky!65#|Ni6?6q2#AF}`11touLd zbeaPP4j`1@a{YhK%n(l`nV+Apnt;s}AOz>monw4_oS@(mkH?Ev*P3@vpRW5R=hyfc zc<$L}FbtDK(jpp(U_|50&CRiU_wJhdLWrWa?<1wSFZv0~Vtjm@@4o%E)&K&Sn3zB* zg}we0zULwh$?1Q*U-wN;Bd%V(%16MF=bx>-=}ROMOiWDhWjbBg0CRJ596o#)0WH^; zhC$r2n4O*F(Jd2Dbq1U{bA}gRd=UgV8#nKGyHkFZQM{Yqi=!mC4CTq?FvgwbpWdDJ98dlF7+QzWDs}N_kaIaB_08bl3=PZMZ}d57+HL&kn0f zeEEh`_o^D;_U)UTI`s~^biF+}Iaz;Hz|71{G2Mo4xjqyR&6$~*s`9E8Ff%j5;lqbf zO3~B13w?*AVy&g`oOX*_3632-%74$#W7~Gk65Ptf#00MEf~MvAXV0F+b={gRSXlvD zYsSaN$vY0JbP*|}Wb(*F-MhF(zyAJy4m~|uc`)g^dGe0K`1p9;Y_X=e1#`Jv?HR*JkJ&*TcNX_-{7x{f>Cx&5y&J!^WT-PBMi;~Y} z(Lxe2BS-{Z-a!SW2hu`ND0t-ad8ClILh|*+FNjACe9t3&u_OK9g?+h9OZ(5w{XG!AO~{!Px5RDv5+u>71r~Ayd8%6q^Cq zFAl)1i?GEc$+qD+O%oY=E38CF3H$ZlyxZJ00000 LNkvXXu0mjf)SB-9 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Constructible/Power/disposal.rsi/meta.json b/Resources/Textures/Constructible/Power/disposal.rsi/meta.json index 96aadcfa66..f11b589795 100644 --- a/Resources/Textures/Constructible/Power/disposal.rsi/meta.json +++ b/Resources/Textures/Constructible/Power/disposal.rsi/meta.json @@ -1 +1,779 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/blob/bbe32606902c90f5290b57d905a3f31b84dc6d7d/icons/obj/pipes/disposal.dmi and modified by DrSmugleaf", "states": [{"name": "condisposal", "directions": 1, "delays": [[1.0]]}, {"name": "conpipe-c", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j1", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j1s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j2", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j2s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-t", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-y", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "disposal", "directions": 1, "delays": [[1.0]]}, {"name": "disposal-charging", "directions": 1, "delays": [[1.0]]}, {"name": "disposal-flush", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.5, 0.1, 0.1, 0.1]]}, {"name": "dispover-charge", "directions": 1, "delays": [[0.4, 0.4]]}, {"name": "dispover-full", "directions": 1, "delays": [[0.2, 0.2]]}, {"name": "dispover-handle", "directions": 1, "delays": [[1.0]]}, {"name": "dispover-ready", "directions": 1, "delays": [[1.0]]}, {"name": "intake", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "intake-closing", "directions": 4, "delays": [[0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "outlet", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "outlet-open", "directions": 4, "delays": [[0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1]]}, {"name": "pipe-b", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-bf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-c", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-cf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-d", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1f", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2f", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-t", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tagger", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tagger-partial", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-u", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-y", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-yf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} \ No newline at end of file +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/discordia-space/CEV-Eris/blob/bbe32606902c90f5290b57d905a3f31b84dc6d7d/icons/obj/pipes/disposal.dmi and modified by DrSmugleaf", + "states": [ + { + "name": "condisposal", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-c", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-j1", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-j1s", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-j2", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-j2s", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-s", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-t", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-tagger", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "conpipe-y", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "disposal", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "disposal-charging", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "disposal-flush", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.5, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "dispover-charge", + "directions": 1, + "delays": [ + [ + 0.4, + 0.4 + ] + ] + }, + { + "name": "dispover-full", + "directions": 1, + "delays": [ + [ + 0.2, + 0.2 + ] + ] + }, + { + "name": "dispover-handle", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "dispover-ready", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "intake", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "intake-closing", + "directions": 4, + "delays": [ + [ + 0.5, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.5, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.5, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.5, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "outlet", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "outlet-open", + "directions": 4, + "delays": [ + [ + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.1, + 1.5, + 0.1 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.1, + 1.5, + 0.1 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.1, + 1.5, + 0.1 + ], + [ + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.1, + 1.5, + 0.1 + ] + ] + }, + { + "name": "pipe-b", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-bf", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-c", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-cf", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-d", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-j1", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-j1f", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-j1s", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-j1sf", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-j2", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-j2f", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-j2s", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-j2sf", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-s", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-sf", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-t", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-tagger", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-tagger-partial", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-tf", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-u", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-y", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "pipe-yf", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Constructible/Power/disposal.rsi/pipe-tagger.png b/Resources/Textures/Constructible/Power/disposal.rsi/pipe-tagger.png index a8463fd0c1f57c834a4c27a9acdba9864a389a40..ce48830a50e8f73a57d173e8927f590c0bc92ddb 100644 GIT binary patch delta 2373 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