Add ore bag area pickups (#19358)

This commit is contained in:
metalgearsloth
2023-09-12 22:34:04 +10:00
committed by GitHub
parent 7d9550bc55
commit 7064f262b4
18 changed files with 256 additions and 83 deletions

View File

@@ -9,11 +9,11 @@ namespace Content.Client.Animations
{ {
public static class ReusableAnimations 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); IoCManager.Resolve(ref entMan);
if (entMan.Deleted(entity) || !initialPosition.IsValid(entMan)) if (entMan.Deleted(entity) || !initialCoords.IsValid(entMan))
return; return;
var metadata = entMan.GetComponent<MetaDataComponent>(entity); var metadata = entMan.GetComponent<MetaDataComponent>(entity);
@@ -21,7 +21,7 @@ namespace Content.Client.Animations
if (entMan.IsPaused(entity, metadata)) if (entMan.IsPaused(entity, metadata))
return; return;
var animatableClone = entMan.SpawnEntity("clientsideclone", initialPosition); var animatableClone = entMan.SpawnEntity("clientsideclone", initialCoords);
string val = entMan.GetComponent<MetaDataComponent>(entity).EntityName; string val = entMan.GetComponent<MetaDataComponent>(entity).EntityName;
entMan.System<MetaDataSystem>().SetEntityName(animatableClone, val); entMan.System<MetaDataSystem>().SetEntityName(animatableClone, val);
@@ -35,7 +35,8 @@ namespace Content.Client.Animations
sprite.Visible = true; sprite.Visible = true;
var animations = entMan.GetComponent<AnimationPlayerComponent>(animatableClone); var animations = entMan.GetComponent<AnimationPlayerComponent>(animatableClone);
animations.AnimationCompleted += (_) => { animations.AnimationCompleted += (_) =>
{
entMan.DeleteEntity(animatableClone); entMan.DeleteEntity(animatableClone);
}; };
@@ -55,7 +56,7 @@ namespace Content.Client.Animations
InterpolationMode = AnimationInterpolationMode.Linear, InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames = KeyFrames =
{ {
new AnimationTrackProperty.KeyFrame(initialPosition.Position, 0), new AnimationTrackProperty.KeyFrame(initialCoords.Position, 0),
new AnimationTrackProperty.KeyFrame(finalPosition, 0.125f) new AnimationTrackProperty.KeyFrame(finalPosition, 0.125f)
} }
}, },

View File

@@ -52,8 +52,6 @@ namespace Content.Client.Hands.Systems
SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState); SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState);
SubscribeLocalEvent<HandsComponent, VisualsChangedEvent>(OnVisualsChanged); SubscribeLocalEvent<HandsComponent, VisualsChangedEvent>(OnVisualsChanged);
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
OnHandSetActive += OnHandActivated; OnHandSetActive += OnHandActivated;
} }
@@ -121,30 +119,6 @@ namespace Content.Client.Hands.Systems
} }
#endregion #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() public void ReloadHandButtons()
{ {
if (!TryGetPlayerHands(out var hands)) if (!TryGetPlayerHands(out var hands))

View File

@@ -1,6 +1,8 @@
using Content.Client.Animations; using Content.Client.Animations;
using Content.Shared.Hands;
using Content.Shared.Storage; using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems; using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Map;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Client.Storage.Systems; namespace Content.Client.Storage.Systems;
@@ -16,6 +18,7 @@ public sealed class StorageSystem : SharedStorageSystem
{ {
base.Initialize(); base.Initialize();
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
SubscribeNetworkEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities); SubscribeNetworkEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
} }
@@ -25,6 +28,38 @@ public sealed class StorageSystem : SharedStorageSystem
StorageUpdated?.Invoke(uid, component); 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> /// <summary>
/// Animate the newly stored entities in <paramref name="msg"/> flying towards this storage's position /// Animate the newly stored entities in <paramref name="msg"/> flying towards this storage's position
/// </summary> /// </summary>

View File

@@ -172,7 +172,7 @@ public abstract partial class InteractionTest
{ {
var playerEnt = SEntMan.GetEntity(Player); 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 // turn on welders
if (enableWelder && SEntMan.TryGetComponent(item, out welder) && !welder.Lit) if (enableWelder && SEntMan.TryGetComponent(item, out welder) && !welder.Lit)
@@ -213,7 +213,7 @@ public abstract partial class InteractionTest
await Server.WaitPost(() => 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); await RunTicks(1);

View File

@@ -201,7 +201,7 @@ namespace Content.Server.Chemistry.EntitySystems
for (var i = 0; i < message.Number; i++) for (var i = 0; i < message.Number; i++)
{ {
var item = Spawn(PillPrototypeId, Transform(container).Coordinates); 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); _labelSystem.Label(item, message.Label);
var itemSolution = _solutionContainerSystem.EnsureSolution(item, SharedChemMaster.PillSolutionName); var itemSolution = _solutionContainerSystem.EnsureSolution(item, SharedChemMaster.PillSolutionName);

View File

@@ -96,7 +96,7 @@ public sealed class PartExchangerSystem : EntitySystem
//put the unused parts back into rped. (this also does the "swapping") //put the unused parts back into rped. (this also does the "swapping")
foreach (var (unused, _) in machineParts) foreach (var (unused, _) in machineParts)
{ {
_storage.Insert(storageUid, unused, playSound: false); _storage.Insert(storageUid, unused, out _, playSound: false);
} }
_construction.RefreshParts(uid, machine); _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") //put the unused parts back into rped. (this also does the "swapping")
foreach (var (unused, _) in machineParts) foreach (var (unused, _) in machineParts)
{ {
_storage.Insert(storageEnt, unused, playSound: false); _storage.Insert(storageEnt, unused, out _, playSound: false);
} }
} }

View File

@@ -43,8 +43,6 @@ namespace Content.Server.Hands.Systems
[Dependency] private readonly PullingSystem _pullingSystem = default!; [Dependency] private readonly PullingSystem _pullingSystem = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly StorageSystem _storageSystem = default!; [Dependency] private readonly StorageSystem _storageSystem = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly IConfigurationManager _configuration = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -94,20 +92,6 @@ namespace Content.Server.Hands.Systems
args.Handled = true; // no shove/stun. 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) protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args)
{ {
base.HandleEntityRemoved(uid, hands, args); base.HandleEntityRemoved(uid, hands, args);

View File

@@ -31,7 +31,7 @@ public sealed partial class StorageSystem
if (entityStorageComp != null && EntityStorage.Insert(ent, uid)) if (entityStorageComp != null && EntityStorage.Insert(ent, uid))
continue; continue;
if (storageComp != null && Insert(uid, ent, storageComp: storageComp, playSound: false)) if (storageComp != null && Insert(uid, ent, out _, storageComp: storageComp, playSound: false))
continue; continue;
Log.Error($"Tried to StorageFill {item} inside {ToPrettyString(uid)} but can't."); Log.Error($"Tried to StorageFill {item} inside {ToPrettyString(uid)} but can't.");

View File

@@ -1,6 +1,7 @@
using Content.Server.Administration.Managers; using Content.Server.Administration.Managers;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Ghost; using Content.Shared.Ghost;
using Content.Shared.Hands;
using Content.Shared.Lock; using Content.Shared.Lock;
using Content.Shared.Storage; using Content.Shared.Storage;
using Content.Shared.Storage.Components; using Content.Shared.Storage.Components;
@@ -9,6 +10,7 @@ using Content.Shared.Timing;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Map;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Players; using Robust.Shared.Players;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -120,6 +122,14 @@ public sealed partial class StorageSystem : SharedStorageSystem
_uiSystem.OpenUi(bui, player.PlayerSession); _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> /// <summary>
/// If the user has nested-UIs open (e.g., PDA UI open when pda is in a backpack), close them. /// If the user has nested-UIs open (e.g., PDA UI open when pda is in a backpack), close them.
/// </summary> /// </summary>

View File

@@ -58,7 +58,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (hand == null) if (hand == null)
return false; return false;
return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item); return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
} }
/// <summary> /// <summary>
@@ -83,7 +83,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (!TryGetEmptyHand(uid, out var hand, handsComp)) if (!TryGetEmptyHand(uid, out var hand, handsComp))
return false; return false;
return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item); return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
} }
public bool TryPickup( public bool TryPickup(
@@ -91,7 +91,6 @@ public abstract partial class SharedHandsSystem : EntitySystem
EntityUid entity, EntityUid entity,
Hand hand, Hand hand,
bool checkActionBlocker = true, bool checkActionBlocker = true,
bool animateUser = false,
bool animate = true, bool animate = true,
HandsComponent? handsComp = null, HandsComponent? handsComp = null,
ItemComponent? item = 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. && MetaData(entity).VisibilityMask == MetaData(uid).VisibilityMask) // Don't animate aghost pickups.
{ {
var initialPosition = EntityCoordinates.FromMap(coordinateEntity, itemPos, EntityManager); 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); DoPickup(uid, hand, entity, handsComp);
@@ -199,7 +198,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
if (uid == null if (uid == null
|| !Resolve(uid.Value, ref handsComp, false) || !Resolve(uid.Value, ref handsComp, false)
|| !TryGetEmptyHand(uid.Value, out var hand, handsComp) || !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. // 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. // 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}"); _adminLogger.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}");
Dirty(hands); Dirty(uid, hands);
if (hand == hands.ActiveHand) if (hand == hands.ActiveHand)
RaiseLocalEvent(entity, new HandSelectedEvent(uid), false); RaiseLocalEvent(entity, new HandSelectedEvent(uid), false);
} }
public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, Angle initialAngle,
EntityUid? exclude);
} }

View File

@@ -5,6 +5,7 @@ using Content.Shared.Administration.Logs;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
@@ -17,6 +18,8 @@ public abstract partial class SharedHandsSystem : EntitySystem
[Dependency] private readonly SharedContainerSystem _containerSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedItemSystem _items = default!; [Dependency] private readonly SharedItemSystem _items = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
protected event Action<HandsComponent?>? OnHandSetActive; protected event Action<HandsComponent?>? OnHandSetActive;

View File

@@ -114,16 +114,24 @@ namespace Content.Shared.Hands
} }
} }
/// <summary>
/// Plays a clientside pickup animation by copying the specified entity.
/// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class PickupAnimationEvent : EntityEventArgs public sealed class PickupAnimationEvent : EntityEventArgs
{ {
public NetEntity ItemUid { get; } /// <summary>
public NetCoordinates InitialPosition { get; } /// Entity to be copied for the clientside animation.
public Vector2 FinalPosition { get; } /// </summary>
public Angle InitialAngle { get; } public readonly NetEntity ItemUid;
public readonly NetCoordinates InitialPosition;
public readonly NetCoordinates FinalPosition;
public readonly Angle InitialAngle;
public PickupAnimationEvent(NetEntity itemUid, NetCoordinates initialPosition, public PickupAnimationEvent(NetEntity itemUid,
Vector2 finalPosition, Angle initialAngle) NetCoordinates initialPosition,
NetCoordinates finalPosition,
Angle initialAngle)
{ {
ItemUid = itemUid; ItemUid = itemUid;
FinalPosition = finalPosition; FinalPosition = finalPosition;

View File

@@ -1,9 +1,11 @@
using System.Numerics; using System.Numerics;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Hands;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Storage.EntitySystems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Physics.Systems; using Robust.Shared.Physics.Systems;
@@ -22,9 +24,10 @@ namespace Content.Shared.Stacks
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!; [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] protected readonly SharedHandsSystem Hands = default!; [Dependency] protected readonly SharedHandsSystem Hands = default!;
[Dependency] protected readonly SharedTransformSystem Xform = default!; [Dependency] protected readonly SharedTransformSystem Xform = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -56,6 +59,8 @@ namespace Content.Shared.Stacks
if (!TryComp(args.Used, out StackComponent? recipientStack)) if (!TryComp(args.Used, out StackComponent? recipientStack))
return; return;
var localRotation = Transform(args.Used).LocalRotation;
if (!TryMergeStacks(uid, args.Used, out var transfered, stack, recipientStack)) if (!TryMergeStacks(uid, args.Used, out var transfered, stack, recipientStack))
return; return;
@@ -67,10 +72,11 @@ namespace Content.Shared.Stacks
return; return;
var popupPos = args.ClickLocation; var popupPos = args.ClickLocation;
var userCoords = Transform(args.User).Coordinates;
if (!popupPos.IsValid(EntityManager)) if (!popupPos.IsValid(EntityManager))
{ {
popupPos = Transform(args.User).Coordinates; popupPos = userCoords;
} }
switch (transfered) switch (transfered)
@@ -90,16 +96,18 @@ namespace Content.Shared.Stacks
Popup.PopupCoordinates(Loc.GetString("comp-stack-already-full"), popupPos, Filter.Local(), false); Popup.PopupCoordinates(Loc.GetString("comp-stack-already-full"), popupPos, Filter.Local(), false);
break; break;
} }
_storage.PlayPickupAnimation(args.Used, popupPos, userCoords, localRotation, args.User);
} }
private bool TryMergeStacks( private bool TryMergeStacks(
EntityUid donor, EntityUid donor,
EntityUid recipient, EntityUid recipient,
out int transfered, out int transferred,
StackComponent? donorStack = null, StackComponent? donorStack = null,
StackComponent? recipientStack = null) StackComponent? recipientStack = null)
{ {
transfered = 0; transferred = 0;
if (donor == recipient) if (donor == recipient)
return false; return false;
@@ -109,10 +117,10 @@ namespace Content.Shared.Stacks
if (string.IsNullOrEmpty(recipientStack.StackTypeId) || !recipientStack.StackTypeId.Equals(donorStack.StackTypeId)) if (string.IsNullOrEmpty(recipientStack.StackTypeId) || !recipientStack.StackTypeId.Equals(donorStack.StackTypeId))
return false; return false;
transfered = Math.Min(donorStack.Count, GetAvailableSpace(recipientStack)); transferred = Math.Min(donorStack.Count, GetAvailableSpace(recipientStack));
SetCount(donor, donorStack.Count - transfered, donorStack); SetCount(donor, donorStack.Count - transferred, donorStack);
SetCount(recipient, recipientStack.Count + transfered, recipientStack); SetCount(recipient, recipientStack.Count + transferred, recipientStack);
return true; return transferred > 0;
} }
/// <summary> /// <summary>

View File

@@ -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,
}
};
}

View File

@@ -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;
}
}
}
}

View File

@@ -4,6 +4,7 @@ using Content.Shared.CombatMode;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Content.Shared.Destructible; using Content.Shared.Destructible;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Hands;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Implants.Components; using Content.Shared.Implants.Components;
@@ -37,7 +38,7 @@ public abstract class SharedStorageSystem : EntitySystem
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!;
[Dependency] private readonly SharedCombatModeSystem _combatMode = 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] private readonly SharedStackSystem _stack = default!;
[Dependency] protected readonly UseDelaySystem UseDelay = default!; [Dependency] protected readonly UseDelaySystem UseDelay = default!;
@@ -435,7 +436,7 @@ public abstract class SharedStorageSystem : EntitySystem
foreach (var entity in entities.ToArray()) 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); Audio.PlayPredicted(sourceComp.StorageInsertSound, target, user);
@@ -495,8 +496,10 @@ public abstract class SharedStorageSystem : EntitySystem
/// Inserts into the storage container /// Inserts into the storage container
/// </summary> /// </summary>
/// <returns>true if the entity was inserted, false otherwise</returns> /// <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)) if (!Resolve(uid, ref storageComp) || !CanInsert(uid, insertEnt, out _, storageComp))
return false; return false;
@@ -522,6 +525,7 @@ public abstract class SharedStorageSystem : EntitySystem
if (!_stack.TryAdd(insertEnt, ent, insertStack, containedStack)) if (!_stack.TryAdd(insertEnt, ent, insertStack, containedStack))
continue; continue;
stackedEntity = ent;
var remaining = insertStack.Count; var remaining = insertStack.Count;
toInsertCount -= toInsertCount - remaining; toInsertCount -= toInsertCount - remaining;
@@ -596,11 +600,17 @@ public abstract class SharedStorageSystem : EntitySystem
if (!Resolve(uid, ref storageComp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid)) if (!Resolve(uid, ref storageComp) || !_sharedInteractionSystem.InRangeUnobstructed(player, uid))
return false; 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); _popupSystem.PopupClient(Loc.GetString("comp-storage-cant-insert"), uid, player);
return false; return false;
} }
return true; 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);
} }

View File

@@ -2,8 +2,9 @@
name: ore bag name: ore bag
id: OreBag id: OreBag
parent: BaseStorageItem 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: components:
- type: MagnetPickup
- type: Sprite - type: Sprite
sprite: Objects/Specific/Mining/ore_bag.rsi sprite: Objects/Specific/Mining/ore_bag.rsi
state: icon state: icon

View File

@@ -40,7 +40,9 @@ The crusher devices are your first and last line of defense against space fauna
<GuideEntityEmbed Entity="OreBag"/> <GuideEntityEmbed Entity="OreBag"/>
<GuideEntityEmbed Entity="ClothingBeltUtilityFilled"/> <GuideEntityEmbed Entity="ClothingBeltUtilityFilled"/>
</Box> </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 ## How to make money as a salvager
<Box> <Box>