diff --git a/Content.Client/Stack/StackSystem.cs b/Content.Client/Stack/StackSystem.cs index ab6c68a11e..97eef22b93 100644 --- a/Content.Client/Stack/StackSystem.cs +++ b/Content.Client/Stack/StackSystem.cs @@ -1,23 +1,28 @@ using Content.Shared.Stacks; using JetBrains.Annotations; -using Robust.Shared.GameObjects; namespace Content.Client.Stack { [UsedImplicitly] public sealed class StackSystem : SharedStackSystem { - public override void Initialize() + public override void SetCount(EntityUid uid, int amount, SharedStackComponent? component = null) { - base.Initialize(); + if (!Resolve(uid, ref component)) + return; - SubscribeLocalEvent(OnStackCountChanged); - } + base.SetCount(uid, amount, component); + + // TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call. + if (component.Count <= 0) + { + Transform(uid).DetachParentToNull(); + return; + } - private void OnStackCountChanged(EntityUid uid, StackComponent component, StackCountChangedEvent args) - { // Dirty the UI now that the stack count has changed. - component.UiUpdateNeeded = true; + if (component is StackComponent clientComp) + clientComp.UiUpdateNeeded = true; } } } diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 80f99afb70..72c9d14967 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -14,6 +14,7 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory; using Content.Shared.Popups; +using Content.Shared.Stacks; using Robust.Shared.Containers; using Robust.Shared.Players; using Robust.Shared.Timing; @@ -336,8 +337,12 @@ namespace Content.Server.Construction } } - if (await Construct(user, "item_construction", constructionGraph, edge, targetNode) is {Valid: true} item) - _handsSystem.PickupOrDrop(user, item); + if (await Construct(user, "item_construction", constructionGraph, edge, targetNode) is not { Valid: true } item) + return; + + // Just in case this is a stack, attempt to merge it. If it isn't a stack, this will just normally pick up + // or drop the item as normal. + _stackSystem.TryMergeToHands(item, user); } // LEGACY CODE. See warning at the top of the file! diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index ec0906f36a..1431be8aa4 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -1,17 +1,7 @@ -using System; -using Content.Server.Hands.Components; -using Content.Server.Popups; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction; -using Content.Shared.Item; using Content.Shared.Stacks; using Content.Shared.Verbs; using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -25,8 +15,6 @@ namespace Content.Server.Stack public sealed class StackSystem : SharedStackSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 }; @@ -34,10 +22,21 @@ namespace Content.Server.Stack { base.Initialize(); - SubscribeLocalEvent(OnStackInteractUsing); SubscribeLocalEvent>(OnStackAlternativeInteract); } + public override void SetCount(EntityUid uid, int amount, SharedStackComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + base.SetCount(uid, amount, component); + + // Queue delete stack if count reaches zero. + if (component.Count <= 0) + QueueDel(uid); + } + /// /// Try to split this stack into two. Returns a non-null if successful. /// @@ -83,51 +82,6 @@ namespace Content.Server.Stack return entity; } - private void OnStackInteractUsing(EntityUid uid, StackComponent stack, InteractUsingEvent args) - { - if (args.Handled) - return; - - if (!TryComp(args.Used, out var otherStack)) - return; - - if (!otherStack.StackTypeId.Equals(stack.StackTypeId)) - return; - - var toTransfer = Math.Min(stack.Count, otherStack.AvailableSpace); - SetCount(uid, stack.Count - toTransfer, stack); - SetCount(args.Used, otherStack.Count + toTransfer, otherStack); - - var popupPos = args.ClickLocation; - - if (!popupPos.IsValid(EntityManager)) - { - popupPos = Transform(args.User).Coordinates; - } - - var filter = Filter.Entities(args.User); - - switch (toTransfer) - { - case > 0: - _popupSystem.PopupCoordinates($"+{toTransfer}", popupPos, filter); - - if (otherStack.AvailableSpace == 0) - { - _popupSystem.PopupCoordinates(Loc.GetString("comp-stack-becomes-full"), - popupPos.Offset(new Vector2(0, -0.5f)) , filter); - } - - break; - - case 0 when otherStack.AvailableSpace == 0: - _popupSystem.PopupCoordinates(Loc.GetString("comp-stack-already-full"), popupPos, filter); - break; - } - - args.Handled = true; - } - private void OnStackAlternativeInteract(EntityUid uid, StackComponent stack, GetVerbsEvent args) { if (!args.CanAccess || !args.CanInteract) @@ -175,16 +129,16 @@ namespace Content.Server.Stack if (amount <= 0) { - _popupSystem.PopupCursor(Loc.GetString("comp-stack-split-too-small"), Filter.Entities(userUid)); + PopupSystem.PopupCursor(Loc.GetString("comp-stack-split-too-small"), Filter.Entities(userUid)); return; } if (Split(uid, amount, userTransform.Coordinates, stack) is not {} split) return; - _handsSystem.PickupOrDrop(userUid, split); + HandsSystem.PickupOrDrop(userUid, split); - _popupSystem.PopupCursor(Loc.GetString("comp-stack-split"), Filter.Entities(userUid)); + PopupSystem.PopupCursor(Loc.GetString("comp-stack-split"), Filter.Entities(userUid)); } } } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs index f642b3860c..6ed191320a 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs @@ -104,7 +104,7 @@ public abstract partial class SharedHandsSystem : EntitySystem } /// - /// Puts an item any hand, preferring the active hand, or puts it on the floor. + /// Puts an item into any hand, preferring the active hand, or puts it on the floor. /// public void PickupOrDrop(EntityUid? uid, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false, SharedHandsComponent? handsComp = null, SharedItemComponent? item = null) { diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index 5a6636fc85..ae2a5bd863 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -118,7 +118,7 @@ public abstract partial class SharedHandsSystem : EntitySystem } /// - /// Enumerate over hands, with the active hand being first. + /// Enumerate over held items, starting with the item in the currently active hand (if there is one). /// public IEnumerable EnumerateHeld(EntityUid uid, SharedHandsComponent? handsComp = null) { diff --git a/Content.Shared/Stacks/SharedStackSystem.cs b/Content.Shared/Stacks/SharedStackSystem.cs index 0daf9a2eb6..d35deb255e 100644 --- a/Content.Shared/Stacks/SharedStackSystem.cs +++ b/Content.Shared/Stacks/SharedStackSystem.cs @@ -1,15 +1,22 @@ using Content.Shared.Examine; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Popups; using JetBrains.Annotations; -using Robust.Shared.GameObjects; using Robust.Shared.GameStates; -using Robust.Shared.Localization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Player; +using Robust.Shared.Timing; namespace Content.Shared.Stacks { [UsedImplicitly] public abstract class SharedStackSystem : EntitySystem { + [Dependency] protected readonly SharedPopupSystem PopupSystem = default!; + [Dependency] protected readonly SharedHandsSystem HandsSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + public override void Initialize() { base.Initialize(); @@ -18,9 +25,109 @@ namespace Content.Shared.Stacks SubscribeLocalEvent(OnStackHandleState); SubscribeLocalEvent(OnStackStarted); SubscribeLocalEvent(OnStackExamined); + SubscribeLocalEvent(OnStackInteractUsing); } - public void SetCount(EntityUid uid, int amount, SharedStackComponent? component = null) + private void OnStackInteractUsing(EntityUid uid, SharedStackComponent stack, InteractUsingEvent args) + { + if (args.Handled) + return; + + if (!TryComp(args.Used, out SharedStackComponent? recipientStack)) + return; + + if (!TryMergeStacks(uid, args.Used, out var transfered, stack, recipientStack)) + return; + + args.Handled = true; + + // interaction is done, the rest is just generating a pop-up + + if (!_gameTiming.IsFirstTimePredicted) + return; + + var popupPos = args.ClickLocation; + + if (!popupPos.IsValid(EntityManager)) + { + popupPos = Transform(args.User).Coordinates; + } + + switch (transfered) + { + case > 0: + PopupSystem.PopupCoordinates($"+{transfered}", popupPos, Filter.Local()); + + if (recipientStack.AvailableSpace == 0) + { + PopupSystem.PopupCoordinates(Loc.GetString("comp-stack-becomes-full"), + popupPos.Offset(new Vector2(0, -0.5f)), Filter.Local()); + } + + break; + + case 0 when recipientStack.AvailableSpace == 0: + PopupSystem.PopupCoordinates(Loc.GetString("comp-stack-already-full"), popupPos, Filter.Local()); + break; + } + } + + private bool TryMergeStacks( + EntityUid donor, + EntityUid recipient, + out int transfered, + SharedStackComponent? donorStack = null, + SharedStackComponent? recipientStack = null) + { + transfered = 0; + if (!Resolve(recipient, ref recipientStack, false) || !Resolve(donor, ref donorStack, false)) + return false; + + if (!recipientStack.StackTypeId.Equals(donorStack.StackTypeId)) + return false; + + transfered = Math.Min(donorStack.Count, recipientStack.AvailableSpace); + SetCount(donor, donorStack.Count - transfered, donorStack); + SetCount(recipient, recipientStack.Count + transfered, recipientStack); + return true; + } + + /// + /// If the given item is a stack, this attempts to find a matching stack in the users hand, and merge with that. + /// + /// + /// If the interaction fails to fully merge the stack, or if this is just not a stack, it will instead try + /// to place it in the user's hand normally. + /// + public void TryMergeToHands( + EntityUid item, + EntityUid user, + SharedStackComponent? itemStack = null, + SharedHandsComponent? hands = null) + { + if (!Resolve(user, ref hands, false)) + return; + + if (!Resolve(item, ref itemStack, false)) + { + // This isn't even a stack. Just try to pickup as normal. + HandsSystem.PickupOrDrop(user, item, handsComp: hands); + return; + } + + // This is shit code until hands get fixed and give an easy way to enumerate over items, starting with the currently active item. + foreach (var held in HandsSystem.EnumerateHeld(user, hands)) + { + TryMergeStacks(item, held, out _, donorStack: itemStack); + + if (itemStack.Count == 0) + return; + } + + HandsSystem.PickupOrDrop(user, item, handsComp: hands); + } + + public virtual void SetCount(EntityUid uid, int amount, SharedStackComponent? component = null) { if (!Resolve(uid, ref component)) return; @@ -44,11 +151,7 @@ namespace Content.Shared.Stacks } component.Count = amount; - component.Dirty(); - - // Queue delete stack if count reaches zero. - if(component.Count <= 0) - QueueDel(uid); + Dirty(component); // Change appearance data. if (TryComp(uid, out AppearanceComponent? appearance))