]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add ore bag area pickups (#19358)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Tue, 12 Sep 2023 12:34:04 +0000 (22:34 +1000)
committerGitHub <noreply@github.com>
Tue, 12 Sep 2023 12:34:04 +0000 (22:34 +1000)
18 files changed:
Content.Client/Animations/ReusableAnimations.cs
Content.Client/Hands/Systems/HandsSystem.cs
Content.Client/Storage/Systems/StorageSystem.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs
Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs
Content.Server/Construction/PartExchangerSystem.cs
Content.Server/Hands/Systems/HandsSystem.cs
Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs
Content.Server/Storage/EntitySystems/StorageSystem.cs
Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs
Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs
Content.Shared/Hands/HandEvents.cs
Content.Shared/Stacks/SharedStackSystem.cs
Content.Shared/Storage/Components/MagnetPickupComponent.cs [new file with mode: 0644]
Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs [new file with mode: 0644]
Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml
Resources/ServerInfo/Guidebook/Cargo/Salvage.xml

index 26115fa20e8011d3a7c362b9f1e2a33a440c8547..3803af1de92cec311a55c06c6e45d25edb30fa6e 100644 (file)
@@ -9,11 +9,11 @@ namespace Content.Client.Animations
 {
     public static class ReusableAnimations
     {
-        public static void AnimateEntityPickup(EntityUid entity, EntityCoordinates initialPosition, Vector2 finalPosition, Angle initialAngle, IEntityManager? entMan = null)
+        public static void AnimateEntityPickup(EntityUid entity, EntityCoordinates initialCoords, Vector2 finalPosition, Angle initialAngle, IEntityManager? entMan = null)
         {
             IoCManager.Resolve(ref entMan);
 
-            if (entMan.Deleted(entity) || !initialPosition.IsValid(entMan))
+            if (entMan.Deleted(entity) || !initialCoords.IsValid(entMan))
                 return;
 
             var metadata = entMan.GetComponent<MetaDataComponent>(entity);
@@ -21,7 +21,7 @@ namespace Content.Client.Animations
             if (entMan.IsPaused(entity, metadata))
                 return;
 
-            var animatableClone = entMan.SpawnEntity("clientsideclone", initialPosition);
+            var animatableClone = entMan.SpawnEntity("clientsideclone", initialCoords);
             string val = entMan.GetComponent<MetaDataComponent>(entity).EntityName;
             entMan.System<MetaDataSystem>().SetEntityName(animatableClone, val);
 
@@ -35,7 +35,8 @@ namespace Content.Client.Animations
             sprite.Visible = true;
 
             var animations = entMan.GetComponent<AnimationPlayerComponent>(animatableClone);
-            animations.AnimationCompleted += (_) => {
+            animations.AnimationCompleted += (_) =>
+            {
                 entMan.DeleteEntity(animatableClone);
             };
 
@@ -55,7 +56,7 @@ namespace Content.Client.Animations
                         InterpolationMode = AnimationInterpolationMode.Linear,
                         KeyFrames =
                         {
-                            new AnimationTrackProperty.KeyFrame(initialPosition.Position, 0),
+                            new AnimationTrackProperty.KeyFrame(initialCoords.Position, 0),
                             new AnimationTrackProperty.KeyFrame(finalPosition, 0.125f)
                         }
                     },
index 773ec1491f82520b1199831540c0a682bf6ea2d4..1189f5a66ec754eabc026b16a695dde9e7ac6c22 100644 (file)
@@ -52,8 +52,6 @@ namespace Content.Client.Hands.Systems
             SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState);
             SubscribeLocalEvent<HandsComponent, VisualsChangedEvent>(OnVisualsChanged);
 
-            SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
-
             OnHandSetActive += OnHandActivated;
         }
 
@@ -121,30 +119,6 @@ namespace Content.Client.Hands.Systems
         }
         #endregion
 
-        #region PickupAnimation
-        private void HandlePickupAnimation(PickupAnimationEvent msg)
-        {
-            PickupAnimation(GetEntity(msg.ItemUid), GetCoordinates(msg.InitialPosition), msg.FinalPosition, msg.InitialAngle);
-        }
-
-        public override void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, Angle initialAngle,
-            EntityUid? exclude)
-        {
-            PickupAnimation(item, initialPosition, finalPosition, initialAngle);
-        }
-
-        public void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, Angle initialAngle)
-        {
-            if (!_gameTiming.IsFirstTimePredicted)
-                return;
-
-            if (finalPosition.EqualsApprox(initialPosition.Position, tolerance: 0.1f))
-                return;
-
-            ReusableAnimations.AnimateEntityPickup(item, initialPosition, finalPosition, initialAngle);
-        }
-        #endregion
-
         public void ReloadHandButtons()
         {
             if (!TryGetPlayerHands(out var hands))
index 725c79ffc6623ca65d85d9060a94a4b507ab0cc0..7391e11b3160b55a65a78b72a962be0cfb648c60 100644 (file)
@@ -1,6 +1,8 @@
 using Content.Client.Animations;
+using Content.Shared.Hands;
 using Content.Shared.Storage;
 using Content.Shared.Storage.EntitySystems;
+using Robust.Shared.Map;
 using Robust.Shared.Timing;
 
 namespace Content.Client.Storage.Systems;
@@ -16,6 +18,7 @@ public sealed class StorageSystem : SharedStorageSystem
     {
         base.Initialize();
 
+        SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
         SubscribeNetworkEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
     }
 
@@ -25,6 +28,38 @@ public sealed class StorageSystem : SharedStorageSystem
         StorageUpdated?.Invoke(uid, component);
     }
 
+    /// <inheritdoc />
+    public override void PlayPickupAnimation(EntityUid uid, EntityCoordinates initialCoordinates, EntityCoordinates finalCoordinates,
+        Angle initialRotation, EntityUid? user = null)
+    {
+        if (!_timing.IsFirstTimePredicted)
+            return;
+
+        PickupAnimation(uid, initialCoordinates, finalCoordinates, initialRotation);
+    }
+
+    private void HandlePickupAnimation(PickupAnimationEvent msg)
+    {
+        PickupAnimation(GetEntity(msg.ItemUid), GetCoordinates(msg.InitialPosition), GetCoordinates(msg.FinalPosition), msg.InitialAngle);
+    }
+
+    public void PickupAnimation(EntityUid item, EntityCoordinates initialCoords, EntityCoordinates finalCoords, Angle initialAngle)
+    {
+        if (!_timing.IsFirstTimePredicted)
+            return;
+
+        if (finalCoords.InRange(EntityManager, _transform, initialCoords, 0.1f) ||
+            !Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId))
+        {
+            return;
+        }
+
+        var finalMapPos = finalCoords.ToMapPos(EntityManager, _transform);
+        var finalPos = _transform.GetInvWorldMatrix(initialCoords.EntityId).Transform(finalMapPos);
+
+        ReusableAnimations.AnimateEntityPickup(item, initialCoords, finalPos, initialAngle);
+    }
+
     /// <summary>
     /// Animate the newly stored entities in <paramref name="msg"/> flying towards this storage's position
     /// </summary>
index 1442c0b6702fcf2fdb7c3e15a314f62b62a4db19..453ad6b5ea16de9881439f139348f219f614f4ad 100644 (file)
@@ -172,7 +172,7 @@ public abstract partial class InteractionTest
         {
             var playerEnt = SEntMan.GetEntity(Player);
 
-            Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, false, Hands));
+            Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, Hands));
 
             // turn on welders
             if (enableWelder && SEntMan.TryGetComponent(item, out welder) && !welder.Lit)
@@ -213,7 +213,7 @@ public abstract partial class InteractionTest
 
         await Server.WaitPost(() =>
         {
-            Assert.That(HandSys.TryPickup(SEntMan.GetEntity(Player), uid.Value, Hands.ActiveHand, false, false, false, Hands, item));
+            Assert.That(HandSys.TryPickup(SEntMan.GetEntity(Player), uid.Value, Hands.ActiveHand, false, false, Hands, item));
         });
 
         await RunTicks(1);
index 5e1baa71bbab8d6b935fdc70fb6f70f8628cf1d6..541fa4f3e7315c90ded496bc82660c78f6f1d3b2 100644 (file)
@@ -201,7 +201,7 @@ namespace Content.Server.Chemistry.EntitySystems
             for (var i = 0; i < message.Number; i++)
             {
                 var item = Spawn(PillPrototypeId, Transform(container).Coordinates);
-                _storageSystem.Insert(container, item, user, storage);
+                _storageSystem.Insert(container, item, out _, user: user, storage);
                 _labelSystem.Label(item, message.Label);
 
                 var itemSolution = _solutionContainerSystem.EnsureSolution(item, SharedChemMaster.PillSolutionName);
index 3f732019651ace4bef49009a6900f46eb3a689dd..4b543a0247a738c223e3975d4aa11c2b2e7c278f 100644 (file)
@@ -96,7 +96,7 @@ public sealed class PartExchangerSystem : EntitySystem
         //put the unused parts back into rped. (this also does the "swapping")
         foreach (var (unused, _) in machineParts)
         {
-            _storage.Insert(storageUid, unused, playSound: false);
+            _storage.Insert(storageUid, unused, out _, playSound: false);
         }
         _construction.RefreshParts(uid, machine);
     }
@@ -146,7 +146,7 @@ public sealed class PartExchangerSystem : EntitySystem
         //put the unused parts back into rped. (this also does the "swapping")
         foreach (var (unused, _) in machineParts)
         {
-            _storage.Insert(storageEnt, unused, playSound: false);
+            _storage.Insert(storageEnt, unused, out _, playSound: false);
         }
     }
 
index 5a9afe0144426669d48e780b3878b848c93f0dec..1cae95c78eb7d2739baa655ec558410b1c9a72e6 100644 (file)
@@ -43,8 +43,6 @@ namespace Content.Server.Hands.Systems
         [Dependency] private readonly PullingSystem _pullingSystem = default!;
         [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
         [Dependency] private readonly StorageSystem _storageSystem = default!;
-        [Dependency] private readonly ISharedPlayerManager _player = default!;
-        [Dependency] private readonly IConfigurationManager _configuration = default!;
 
         public override void Initialize()
         {
@@ -94,20 +92,6 @@ namespace Content.Server.Hands.Systems
             args.Handled = true; // no shove/stun.
         }
 
-        public override void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, Angle initialAngle,
-            EntityUid? exclude)
-        {
-            if (finalPosition.EqualsApprox(initialPosition.Position, tolerance: 0.1f))
-                return;
-
-            var filter = Filter.Pvs(item, entityManager: EntityManager, playerManager: _player, cfgManager: _configuration);
-
-            if (exclude != null)
-                filter = filter.RemoveWhereAttachedEntity(entity => entity == exclude);
-
-            RaiseNetworkEvent(new PickupAnimationEvent(GetNetEntity(item), GetNetCoordinates(initialPosition), finalPosition, initialAngle), filter);
-        }
-
         protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args)
         {
             base.HandleEntityRemoved(uid, hands, args);
index 77c8458b91a5c438565d25fd39eb44ffd210e244..e05a8f49ff0bfbd698bb76e4e7ef5935259b3b61 100644 (file)
@@ -31,7 +31,7 @@ public sealed partial class StorageSystem
             if (entityStorageComp != null && EntityStorage.Insert(ent, uid))
                 continue;
 
-            if (storageComp != null && Insert(uid, ent, storageComp: storageComp, playSound: false))
+            if (storageComp != null && Insert(uid, ent, out _, storageComp: storageComp, playSound: false))
                 continue;
 
             Log.Error($"Tried to StorageFill {item} inside {ToPrettyString(uid)} but can't.");
index da6a4627520696fc4366f4c465c2b32247c2155c..b2d940ffe1cf600ad96073f2dec603d6874ad88b 100644 (file)
@@ -1,6 +1,7 @@
 using Content.Server.Administration.Managers;
 using Content.Shared.Administration;
 using Content.Shared.Ghost;
+using Content.Shared.Hands;
 using Content.Shared.Lock;
 using Content.Shared.Storage;
 using Content.Shared.Storage.Components;
@@ -9,6 +10,7 @@ using Content.Shared.Timing;
 using Content.Shared.Verbs;
 using Robust.Server.GameObjects;
 using Robust.Server.Player;
+using Robust.Shared.Map;
 using Robust.Shared.Player;
 using Robust.Shared.Players;
 using Robust.Shared.Utility;
@@ -120,6 +122,14 @@ public sealed partial class StorageSystem : SharedStorageSystem
             _uiSystem.OpenUi(bui, player.PlayerSession);
     }
 
+    /// <inheritdoc />
+    public override void PlayPickupAnimation(EntityUid uid, EntityCoordinates initialCoordinates, EntityCoordinates finalCoordinates,
+        Angle initialRotation, EntityUid? user = null)
+    {
+        var filter = Filter.Pvs(uid).RemoveWhereAttachedEntity(e => e == user);
+        RaiseNetworkEvent(new PickupAnimationEvent(GetNetEntity(uid), GetNetCoordinates(initialCoordinates), GetNetCoordinates(finalCoordinates), initialRotation), filter);
+    }
+
     /// <summary>
     ///     If the user has nested-UIs open (e.g., PDA UI open when pda is in a backpack), close them.
     /// </summary>
index e62723df06e8f88d6f5c66be5686d8eaf0a43b22..278470c4a2c2f97df83a8512b37e28a6bd1b4482 100644 (file)
@@ -58,7 +58,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
         if (hand == null)
             return false;
 
-        return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item);
+        return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
     }
 
     /// <summary>
@@ -83,7 +83,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
         if (!TryGetEmptyHand(uid, out var hand, handsComp))
             return false;
 
-        return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item);
+        return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
     }
 
     public bool TryPickup(
@@ -91,7 +91,6 @@ public abstract partial class SharedHandsSystem : EntitySystem
         EntityUid entity,
         Hand hand,
         bool checkActionBlocker = true,
-        bool animateUser = false,
         bool animate = true,
         HandsComponent? handsComp = null,
         ItemComponent? item = null)
@@ -117,7 +116,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
                 && MetaData(entity).VisibilityMask == MetaData(uid).VisibilityMask) // Don't animate aghost pickups.
             {
                 var initialPosition = EntityCoordinates.FromMap(coordinateEntity, itemPos, EntityManager);
-                PickupAnimation(entity, initialPosition, xform.LocalPosition, itemXform.LocalRotation, animateUser ? null : uid);
+                _storage.PlayPickupAnimation(entity, initialPosition, xform.Coordinates, itemXform.LocalRotation, uid);
             }
         }
         DoPickup(uid, hand, entity, handsComp);
@@ -199,7 +198,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
         if (uid == null
             || !Resolve(uid.Value, ref handsComp, false)
             || !TryGetEmptyHand(uid.Value, out var hand, handsComp)
-            || !TryPickup(uid.Value, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item))
+            || !TryPickup(uid.Value, entity, hand, checkActionBlocker, animate, handsComp, item))
         {
             // TODO make this check upwards for any container, and parent to that.
             // Currently this just checks the direct parent, so items can still teleport through containers.
@@ -227,12 +226,9 @@ public abstract partial class SharedHandsSystem : EntitySystem
 
         _adminLogger.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}");
 
-        Dirty(hands);
+        Dirty(uid, hands);
 
         if (hand == hands.ActiveHand)
             RaiseLocalEvent(entity, new HandSelectedEvent(uid), false);
     }
-
-    public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, Angle initialAngle,
-        EntityUid? exclude);
 }
index 0d671759e822fc3e90dacd6d788e69850586432a..af53b2625c005b3c113f58dc89bdca8d648acab8 100644 (file)
@@ -5,6 +5,7 @@ using Content.Shared.Administration.Logs;
 using Content.Shared.Hands.Components;
 using Content.Shared.Interaction;
 using Content.Shared.Item;
+using Content.Shared.Storage.EntitySystems;
 using Robust.Shared.Containers;
 using Robust.Shared.Input.Binding;
 
@@ -17,6 +18,8 @@ public abstract partial class SharedHandsSystem : EntitySystem
     [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
     [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
     [Dependency] private readonly SharedItemSystem _items = default!;
+    [Dependency] private readonly SharedStorageSystem _storage = default!;
+    [Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
 
     protected event Action<HandsComponent?>? OnHandSetActive;
 
index 29242b4a7ee97975423b91a789e848fa817a026c..059728ff4dd09a81d62345d5aa95de3ee9aa9d34 100644 (file)
@@ -114,16 +114,24 @@ namespace Content.Shared.Hands
         }
     }
 
+    /// <summary>
+    /// Plays a clientside pickup animation by copying the specified entity.
+    /// </summary>
     [Serializable, NetSerializable]
     public sealed class PickupAnimationEvent : EntityEventArgs
     {
-        public NetEntity ItemUid { get; }
-        public NetCoordinates InitialPosition { get; }
-        public Vector2 FinalPosition { get; }
-        public Angle InitialAngle { get; }
-
-        public PickupAnimationEvent(NetEntity itemUid, NetCoordinates initialPosition,
-            Vector2 finalPosition, Angle initialAngle)
+        /// <summary>
+        /// Entity to be copied for the clientside animation.
+        /// </summary>
+        public readonly NetEntity ItemUid;
+        public readonly NetCoordinates InitialPosition;
+        public readonly NetCoordinates FinalPosition;
+        public readonly Angle InitialAngle;
+
+        public PickupAnimationEvent(NetEntity itemUid,
+            NetCoordinates initialPosition,
+            NetCoordinates finalPosition,
+            Angle initialAngle)
         {
             ItemUid = itemUid;
             FinalPosition = finalPosition;
index 5e25c2070167d0b643683931d8d00d9ae13ff2be..99e3f7c6de4b20c9105e6c9ace1c311a2260df9c 100644 (file)
@@ -1,9 +1,11 @@
 using System.Numerics;
 using Content.Shared.Examine;
+using Content.Shared.Hands;
 using Content.Shared.Hands.Components;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
+using Content.Shared.Storage.EntitySystems;
 using JetBrains.Annotations;
 using Robust.Shared.GameStates;
 using Robust.Shared.Physics.Systems;
@@ -22,9 +24,10 @@ namespace Content.Shared.Stacks
         [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
         [Dependency] protected readonly SharedHandsSystem Hands = default!;
         [Dependency] protected readonly SharedTransformSystem Xform = default!;
-        [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
-        [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+        [Dependency] private   readonly EntityLookupSystem _entityLookup = default!;
+        [Dependency] private   readonly SharedPhysicsSystem _physics = default!;
         [Dependency] protected readonly SharedPopupSystem Popup = default!;
+        [Dependency] private readonly SharedStorageSystem _storage = default!;
 
         public override void Initialize()
         {
@@ -56,6 +59,8 @@ namespace Content.Shared.Stacks
             if (!TryComp(args.Used, out StackComponent? recipientStack))
                 return;
 
+            var localRotation = Transform(args.Used).LocalRotation;
+
             if (!TryMergeStacks(uid, args.Used, out var transfered, stack, recipientStack))
                 return;
 
@@ -67,10 +72,11 @@ namespace Content.Shared.Stacks
                 return;
 
             var popupPos = args.ClickLocation;
+            var userCoords = Transform(args.User).Coordinates;
 
             if (!popupPos.IsValid(EntityManager))
             {
-                popupPos = Transform(args.User).Coordinates;
+                popupPos = userCoords;
             }
 
             switch (transfered)
@@ -90,16 +96,18 @@ namespace Content.Shared.Stacks
                     Popup.PopupCoordinates(Loc.GetString("comp-stack-already-full"), popupPos, Filter.Local(), false);
                     break;
             }
+
+            _storage.PlayPickupAnimation(args.Used, popupPos, userCoords, localRotation, args.User);
         }
 
         private bool TryMergeStacks(
             EntityUid donor,
             EntityUid recipient,
-            out int transfered,
+            out int transferred,
             StackComponent? donorStack = null,
             StackComponent? recipientStack = null)
         {
-            transfered = 0;
+            transferred = 0;
             if (donor == recipient)
                 return false;
 
@@ -109,10 +117,10 @@ namespace Content.Shared.Stacks
             if (string.IsNullOrEmpty(recipientStack.StackTypeId) || !recipientStack.StackTypeId.Equals(donorStack.StackTypeId))
                 return false;
 
-            transfered = Math.Min(donorStack.Count, GetAvailableSpace(recipientStack));
-            SetCount(donor, donorStack.Count - transfered, donorStack);
-            SetCount(recipient, recipientStack.Count + transfered, recipientStack);
-            return true;
+            transferred = Math.Min(donorStack.Count, GetAvailableSpace(recipientStack));
+            SetCount(donor, donorStack.Count - transferred, donorStack);
+            SetCount(recipient, recipientStack.Count + transferred, recipientStack);
+            return transferred > 0;
         }
 
         /// <summary>
diff --git a/Content.Shared/Storage/Components/MagnetPickupComponent.cs b/Content.Shared/Storage/Components/MagnetPickupComponent.cs
new file mode 100644 (file)
index 0000000..300055a
--- /dev/null
@@ -0,0 +1,36 @@
+using Content.Shared.Inventory;
+using Content.Shared.Tag;
+using Content.Shared.Whitelist;
+
+namespace Content.Server.Storage.Components;
+
+/// <summary>
+/// Applies an ongoing pickup area around the attached entity.
+/// </summary>
+[RegisterComponent]
+public sealed partial class MagnetPickupComponent : Component
+{
+    [ViewVariables(VVAccess.ReadWrite), DataField("nextScan")]
+    public TimeSpan NextScan = TimeSpan.Zero;
+
+    /// <summary>
+    /// What container slot the magnet needs to be in to work.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("slotFlags")]
+    public SlotFlags SlotFlags = SlotFlags.BELT;
+
+    [ViewVariables(VVAccess.ReadWrite), DataField("range")]
+    public float Range = 1f;
+
+    [ValidatePrototypeId<TagPrototype>]
+    private const string DefaultTag = "Ore";
+
+    [ViewVariables(VVAccess.ReadWrite), DataField("whitelist")]
+    public EntityWhitelist? Whitelist = new()
+    {
+        Tags = new List<string>()
+        {
+            DefaultTag,
+        }
+    };
+}
diff --git a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs
new file mode 100644 (file)
index 0000000..3a0132e
--- /dev/null
@@ -0,0 +1,105 @@
+using Content.Server.Storage.Components;
+using Content.Shared.Hands;
+using Content.Shared.Inventory;
+using Content.Shared.Stacks;
+using Robust.Shared.Map;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Player;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Storage.EntitySystems;
+
+/// <summary>
+/// <see cref="MagnetPickupComponent"/>
+/// </summary>
+public sealed class MagnetPickupSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly EntityLookupSystem _lookup = default!;
+    [Dependency] private readonly InventorySystem _inventory = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SharedStorageSystem _storage = default!;
+
+    private static readonly TimeSpan ScanDelay = TimeSpan.FromSeconds(1);
+
+    private EntityQuery<PhysicsComponent> _physicsQuery;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        _physicsQuery = GetEntityQuery<PhysicsComponent>();
+        SubscribeLocalEvent<MagnetPickupComponent, MapInitEvent>(OnMagnetMapInit);
+        SubscribeLocalEvent<MagnetPickupComponent, EntityUnpausedEvent>(OnMagnetUnpaused);
+    }
+
+    private void OnMagnetUnpaused(EntityUid uid, MagnetPickupComponent component, ref EntityUnpausedEvent args)
+    {
+        component.NextScan += args.PausedTime;
+    }
+
+    private void OnMagnetMapInit(EntityUid uid, MagnetPickupComponent component, MapInitEvent args)
+    {
+        component.NextScan = _timing.CurTime;
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+        var query = EntityQueryEnumerator<MagnetPickupComponent, StorageComponent, TransformComponent>();
+        var currentTime = _timing.CurTime;
+
+        while (query.MoveNext(out var uid, out var comp, out var storage, out var xform))
+        {
+            if (comp.NextScan < currentTime)
+                continue;
+
+            comp.NextScan += ScanDelay;
+
+            // No space
+            if (storage.StorageUsed >= storage.StorageCapacityMax)
+                continue;
+
+            if (!_inventory.TryGetContainingSlot(uid, out var slotDef))
+                continue;
+
+            if ((slotDef.SlotFlags & comp.SlotFlags) == 0x0)
+                continue;
+
+            var parentUid = xform.ParentUid;
+            var playedSound = false;
+            var finalCoords = xform.Coordinates;
+            var moverCoords = _transform.GetMoverCoordinates(uid, xform);
+
+            foreach (var near in _lookup.GetEntitiesInRange(uid, comp.Range, LookupFlags.Dynamic | LookupFlags.Sundries))
+            {
+                if (comp.Whitelist?.IsValid(near, EntityManager) == false)
+                    continue;
+
+                if (!_physicsQuery.TryGetComponent(near, out var physics) || physics.BodyStatus != BodyStatus.OnGround)
+                    continue;
+
+                if (near == parentUid)
+                    continue;
+
+                // TODO: Probably move this to storage somewhere when it gets cleaned up
+                // TODO: This sucks but you need to fix a lot of stuff to make it better
+                // the problem is that stack pickups delete the original entity, which is fine, but due to
+                // game state handling we can't show a lerp animation for it.
+                var nearXform = Transform(near);
+                var nearMap = nearXform.MapPosition;
+                var nearCoords = EntityCoordinates.FromMap(moverCoords.EntityId, nearMap, _transform, EntityManager);
+
+                if (!_storage.Insert(uid, near, out var stacked, storageComp: storage, playSound: !playedSound))
+                    continue;
+
+                // Play pickup animation for either the stack entity or the original entity.
+                if (stacked != null)
+                    _storage.PlayPickupAnimation(stacked.Value, nearCoords, finalCoords, nearXform.LocalRotation);
+                else
+                    _storage.PlayPickupAnimation(near, nearCoords, finalCoords, nearXform.LocalRotation);
+
+                playedSound = true;
+            }
+        }
+    }
+}
index b35a4c3e2596dd5c25bca6f2c7430dcb68cf54ba..6f1336a53b45baaf52af6ddb27efa860aa0b02a1 100644 (file)
@@ -4,6 +4,7 @@ using Content.Shared.CombatMode;
 using Content.Shared.Containers.ItemSlots;
 using Content.Shared.Destructible;
 using Content.Shared.DoAfter;
+using Content.Shared.Hands;
 using Content.Shared.Hands.Components;
 using Content.Shared.Hands.EntitySystems;
 using Content.Shared.Implants.Components;
@@ -37,7 +38,7 @@ public abstract class SharedStorageSystem : EntitySystem
     [Dependency] private   readonly SharedAppearanceSystem _appearance = default!;
     [Dependency] protected readonly SharedAudioSystem Audio = default!;
     [Dependency] private   readonly SharedCombatModeSystem _combatMode = default!;
-    [Dependency] private   readonly SharedTransformSystem _transform = default!;
+    [Dependency] protected   readonly SharedTransformSystem _transform = default!;
     [Dependency] private   readonly SharedStackSystem _stack = default!;
     [Dependency] protected readonly UseDelaySystem UseDelay = default!;
 
@@ -435,7 +436,7 @@ public abstract class SharedStorageSystem : EntitySystem
 
         foreach (var entity in entities.ToArray())
         {
-            Insert(target, entity, user, targetComp, playSound: false);
+            Insert(target, entity, out _, user: user, targetComp, playSound: false);
         }
 
         Audio.PlayPredicted(sourceComp.StorageInsertSound, target, user);
@@ -495,8 +496,10 @@ public abstract class SharedStorageSystem : EntitySystem
     ///     Inserts into the storage container
     /// </summary>
     /// <returns>true if the entity was inserted, false otherwise</returns>
-    public bool Insert(EntityUid uid, EntityUid insertEnt, EntityUid? user = null, StorageComponent? storageComp = null, bool playSound = true)
+    public bool Insert(EntityUid uid, EntityUid insertEnt, out EntityUid? stackedEntity, EntityUid? user = null, StorageComponent? storageComp = null, bool playSound = true)
     {
+        stackedEntity = null;
+
         if (!Resolve(uid, ref storageComp) || !CanInsert(uid, insertEnt, out _, storageComp))
             return false;
 
@@ -522,6 +525,7 @@ public abstract class SharedStorageSystem : EntitySystem
                 if (!_stack.TryAdd(insertEnt, ent, insertStack, containedStack))
                     continue;
 
+                stackedEntity = ent;
                 var remaining = insertStack.Count;
                 toInsertCount -= toInsertCount - remaining;
 
@@ -596,11 +600,17 @@ public abstract class SharedStorageSystem : EntitySystem
         if (!Resolve(uid, ref storageComp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid))
             return false;
 
-        if (!Insert(uid, toInsert, player, storageComp))
+        if (!Insert(uid, toInsert, out _, user: player, storageComp))
         {
             _popupSystem.PopupClient(Loc.GetString("comp-storage-cant-insert"), uid, player);
             return false;
         }
         return true;
     }
+
+    /// <summary>
+    /// Plays a clientside pickup animation for the specified uid.
+    /// </summary>
+    public abstract void PlayPickupAnimation(EntityUid uid, EntityCoordinates initialCoordinates,
+        EntityCoordinates finalCoordinates, Angle initialRotation, EntityUid? user = null);
 }
index f4e1ac7a9f007ba93507d8c70d0c6e8241259e3e..583aef374a7493ad0da61e547c5f025552930e55 100644 (file)
@@ -2,8 +2,9 @@
   name: ore bag
   id: OreBag
   parent: BaseStorageItem
-  description: A robust bag for salvage specialists and miners alike to carry large amounts of ore.
+  description: A robust bag for salvage specialists and miners alike to carry large amounts of ore. Magnetises any nearby ores when attached to a belt.
   components:
+  - type: MagnetPickup
   - type: Sprite
     sprite: Objects/Specific/Mining/ore_bag.rsi
     state: icon
index 684eca10d55f73d28a1286994ae88f067df78c56..5f97d01cbe4a5d2c6968ceeda9006e8220355719 100644 (file)
@@ -40,7 +40,9 @@ The crusher devices are your first and last line of defense against space fauna
 <GuideEntityEmbed Entity="OreBag"/>
 <GuideEntityEmbed Entity="ClothingBeltUtilityFilled"/>
 </Box>
-Mining equipment and a full utility belt are needed to be able to plunder the full value of a salvage. The mining to quickly gather ore for the ore processor to make usefull, and the tools of the utility belt for breaking things apart and moving high value objects out.
+The pickaxe and mining drill are both useful for mining rocks or breaking structures quickly.
+The ore bag magnetises nearby ore and automatically picks it up if equipped to a belt slot.
+The utility belt can be useful for holding miscellaneous items when not occupied by your ore bag.
 
 ## How to make money as a salvager
 <Box>