From 7064f262b432c8c1613fbe5b59c6a8a6d735e568 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 12 Sep 2023 22:34:04 +1000 Subject: [PATCH] Add ore bag area pickups (#19358) --- .../Animations/ReusableAnimations.cs | 11 +- Content.Client/Hands/Systems/HandsSystem.cs | 26 ----- .../Storage/Systems/StorageSystem.cs | 35 ++++++ .../Interaction/InteractionTest.Helpers.cs | 4 +- .../EntitySystems/ChemMasterSystem.cs | 2 +- .../Construction/PartExchangerSystem.cs | 4 +- Content.Server/Hands/Systems/HandsSystem.cs | 16 --- .../EntitySystems/StorageSystem.Fill.cs | 2 +- .../Storage/EntitySystems/StorageSystem.cs | 10 ++ .../EntitySystems/SharedHandsSystem.Pickup.cs | 14 +-- .../Hands/EntitySystems/SharedHandsSystem.cs | 3 + Content.Shared/Hands/HandEvents.cs | 22 ++-- Content.Shared/Stacks/SharedStackSystem.cs | 26 +++-- .../Components/MagnetPickupComponent.cs | 36 ++++++ .../EntitySystems/MagnetPickupSystem.cs | 105 ++++++++++++++++++ .../EntitySystems/SharedStorageSystem.cs | 18 ++- .../Objects/Specific/Salvage/ore_bag.yml | 3 +- .../ServerInfo/Guidebook/Cargo/Salvage.xml | 4 +- 18 files changed, 257 insertions(+), 84 deletions(-) create mode 100644 Content.Shared/Storage/Components/MagnetPickupComponent.cs create mode 100644 Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs diff --git a/Content.Client/Animations/ReusableAnimations.cs b/Content.Client/Animations/ReusableAnimations.cs index 26115fa20e..3803af1de9 100644 --- a/Content.Client/Animations/ReusableAnimations.cs +++ b/Content.Client/Animations/ReusableAnimations.cs @@ -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(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(entity).EntityName; entMan.System().SetEntityName(animatableClone, val); @@ -35,7 +35,8 @@ namespace Content.Client.Animations sprite.Visible = true; var animations = entMan.GetComponent(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) } }, diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index 773ec1491f..1189f5a66e 100644 --- a/Content.Client/Hands/Systems/HandsSystem.cs +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -52,8 +52,6 @@ namespace Content.Client.Hands.Systems SubscribeLocalEvent(HandleComponentState); SubscribeLocalEvent(OnVisualsChanged); - SubscribeNetworkEvent(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)) diff --git a/Content.Client/Storage/Systems/StorageSystem.cs b/Content.Client/Storage/Systems/StorageSystem.cs index 725c79ffc6..7391e11b31 100644 --- a/Content.Client/Storage/Systems/StorageSystem.cs +++ b/Content.Client/Storage/Systems/StorageSystem.cs @@ -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(HandlePickupAnimation); SubscribeNetworkEvent(HandleAnimatingInsertingEntities); } @@ -25,6 +28,38 @@ public sealed class StorageSystem : SharedStorageSystem StorageUpdated?.Invoke(uid, component); } + /// + 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); + } + /// /// Animate the newly stored entities in flying towards this storage's position /// diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index 1442c0b670..453ad6b5ea 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -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); diff --git a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs index 5e1baa71bb..541fa4f3e7 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs @@ -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); diff --git a/Content.Server/Construction/PartExchangerSystem.cs b/Content.Server/Construction/PartExchangerSystem.cs index 3f73201965..4b543a0247 100644 --- a/Content.Server/Construction/PartExchangerSystem.cs +++ b/Content.Server/Construction/PartExchangerSystem.cs @@ -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); } } diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index 5a9afe0144..1cae95c78e 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -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); diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs b/Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs index 77c8458b91..e05a8f49ff 100644 --- a/Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs @@ -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."); diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.cs b/Content.Server/Storage/EntitySystems/StorageSystem.cs index da6a462752..b2d940ffe1 100644 --- a/Content.Server/Storage/EntitySystems/StorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.cs @@ -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); } + /// + 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); + } + /// /// If the user has nested-UIs open (e.g., PDA UI open when pda is in a backpack), close them. /// diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs index e62723df06..278470c4a2 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs @@ -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); } /// @@ -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); } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index 0d671759e8..af53b2625c 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -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? OnHandSetActive; diff --git a/Content.Shared/Hands/HandEvents.cs b/Content.Shared/Hands/HandEvents.cs index 29242b4a7e..059728ff4d 100644 --- a/Content.Shared/Hands/HandEvents.cs +++ b/Content.Shared/Hands/HandEvents.cs @@ -114,16 +114,24 @@ namespace Content.Shared.Hands } } + /// + /// Plays a clientside pickup animation by copying the specified entity. + /// [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) + /// + /// Entity to be copied for the clientside animation. + /// + 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; diff --git a/Content.Shared/Stacks/SharedStackSystem.cs b/Content.Shared/Stacks/SharedStackSystem.cs index 5e25c20701..99e3f7c6de 100644 --- a/Content.Shared/Stacks/SharedStackSystem.cs +++ b/Content.Shared/Stacks/SharedStackSystem.cs @@ -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; } /// diff --git a/Content.Shared/Storage/Components/MagnetPickupComponent.cs b/Content.Shared/Storage/Components/MagnetPickupComponent.cs new file mode 100644 index 0000000000..300055a62a --- /dev/null +++ b/Content.Shared/Storage/Components/MagnetPickupComponent.cs @@ -0,0 +1,36 @@ +using Content.Shared.Inventory; +using Content.Shared.Tag; +using Content.Shared.Whitelist; + +namespace Content.Server.Storage.Components; + +/// +/// Applies an ongoing pickup area around the attached entity. +/// +[RegisterComponent] +public sealed partial class MagnetPickupComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite), DataField("nextScan")] + public TimeSpan NextScan = TimeSpan.Zero; + + /// + /// What container slot the magnet needs to be in to work. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("slotFlags")] + public SlotFlags SlotFlags = SlotFlags.BELT; + + [ViewVariables(VVAccess.ReadWrite), DataField("range")] + public float Range = 1f; + + [ValidatePrototypeId] + private const string DefaultTag = "Ore"; + + [ViewVariables(VVAccess.ReadWrite), DataField("whitelist")] + public EntityWhitelist? Whitelist = new() + { + Tags = new List() + { + DefaultTag, + } + }; +} diff --git a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs new file mode 100644 index 0000000000..3a0132e5f2 --- /dev/null +++ b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs @@ -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; + +/// +/// +/// +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 _physicsQuery; + + public override void Initialize() + { + base.Initialize(); + _physicsQuery = GetEntityQuery(); + SubscribeLocalEvent(OnMagnetMapInit); + SubscribeLocalEvent(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(); + 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; + } + } + } +} diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index b35a4c3e25..6f1336a53b 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -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 /// /// true if the entity was inserted, false otherwise - 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; } + + /// + /// Plays a clientside pickup animation for the specified uid. + /// + public abstract void PlayPickupAnimation(EntityUid uid, EntityCoordinates initialCoordinates, + EntityCoordinates finalCoordinates, Angle initialRotation, EntityUid? user = null); } diff --git a/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml b/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml index f4e1ac7a9f..583aef374a 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Salvage/ore_bag.yml @@ -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 diff --git a/Resources/ServerInfo/Guidebook/Cargo/Salvage.xml b/Resources/ServerInfo/Guidebook/Cargo/Salvage.xml index 684eca10d5..5f97d01cbe 100644 --- a/Resources/ServerInfo/Guidebook/Cargo/Salvage.xml +++ b/Resources/ServerInfo/Guidebook/Cargo/Salvage.xml @@ -40,7 +40,9 @@ The crusher devices are your first and last line of defense against space fauna -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 -- 2.51.2