Dumpable component to use a doafter to empty storage into a disposal unit, placeable surface, or the ground (#7792)
This commit is contained in:
@@ -8,7 +8,7 @@ namespace Content.Client.Storage
|
|||||||
/// Client version of item storage containers, contains a UI which displays stored entities and their size
|
/// Client version of item storage containers, contains a UI which displays stored entities and their size
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed class ClientStorageComponent : SharedStorageComponent, IDraggable
|
public sealed class ClientStorageComponent : SharedStorageComponent
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
private List<EntityUid> _storedEntities = new();
|
private List<EntityUid> _storedEntities = new();
|
||||||
|
|||||||
@@ -162,6 +162,17 @@ namespace Content.Server.Disposal.Unit.EntitySystems
|
|||||||
AfterInsert(unit, toInsert);
|
AfterInsert(unit, toInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DoInsertDisposalUnit(EntityUid unit, EntityUid toInsert, DisposalUnitComponent? disposal = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(unit, ref disposal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!disposal.Container.Insert(toInsert))
|
||||||
|
return;
|
||||||
|
|
||||||
|
AfterInsert(disposal, toInsert);
|
||||||
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|||||||
193
Content.Server/Storage/EntitySystems/DumpableSystem.cs
Normal file
193
Content.Server/Storage/EntitySystems/DumpableSystem.cs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Server.Storage.Components;
|
||||||
|
using Content.Shared.Storage.Components;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Content.Server.Disposal.Unit.Components;
|
||||||
|
using Content.Server.Disposal.Unit.EntitySystems;
|
||||||
|
using Content.Server.DoAfter;
|
||||||
|
using Content.Shared.Placeable;
|
||||||
|
using Content.Server.Hands.Systems;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
|
namespace Content.Server.Storage.EntitySystems
|
||||||
|
{
|
||||||
|
public sealed class DumpableSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||||
|
[Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!;
|
||||||
|
|
||||||
|
[Dependency] private readonly HandsSystem _handsSystem = default!;
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<DumpableComponent, AfterInteractEvent>(OnAfterInteract);
|
||||||
|
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<AlternativeVerb>>(AddDumpVerb);
|
||||||
|
SubscribeLocalEvent<DumpableComponent, GetVerbsEvent<UtilityVerb>>(AddUtilityVerbs);
|
||||||
|
SubscribeLocalEvent<DumpCompletedEvent>(OnDumpCompleted);
|
||||||
|
SubscribeLocalEvent<DumpCancelledEvent>(OnDumpCancelled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAfterInteract(EntityUid uid, DumpableComponent component, AfterInteractEvent args)
|
||||||
|
{
|
||||||
|
if (!args.CanReach)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<ServerStorageComponent>(args.Used, out var storage))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (storage.StoredEntities == null || storage.StoredEntities.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (HasComp<DisposalUnitComponent>(args.Target) || HasComp<PlaceableSurfaceComponent>(args.Target))
|
||||||
|
{
|
||||||
|
StartDoAfter(uid, args.Target.Value, args.User, component, storage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void AddDumpVerb(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent<AlternativeVerb> args)
|
||||||
|
{
|
||||||
|
if (!args.CanAccess || !args.CanInteract)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<ServerStorageComponent>(uid, out var storage) || storage.StoredEntities == null || storage.StoredEntities.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AlternativeVerb verb = new()
|
||||||
|
{
|
||||||
|
Act = () =>
|
||||||
|
{
|
||||||
|
StartDoAfter(uid, null, args.User, dumpable, storage, 0.6f);
|
||||||
|
},
|
||||||
|
Text = Loc.GetString("dump-verb-name"),
|
||||||
|
IconTexture = "/Textures/Interface/VerbIcons/drop.svg.192dpi.png",
|
||||||
|
};
|
||||||
|
args.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddUtilityVerbs(EntityUid uid, DumpableComponent dumpable, GetVerbsEvent<UtilityVerb> args)
|
||||||
|
{
|
||||||
|
if (!args.CanAccess || !args.CanInteract)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<ServerStorageComponent>(uid, out var storage) || storage.StoredEntities == null || storage.StoredEntities.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (HasComp<DisposalUnitComponent>(args.Target))
|
||||||
|
{
|
||||||
|
UtilityVerb verb = new()
|
||||||
|
{
|
||||||
|
Act = () =>
|
||||||
|
{
|
||||||
|
StartDoAfter(uid, args.Target, args.User, dumpable, storage);
|
||||||
|
},
|
||||||
|
Text = Loc.GetString("dump-disposal-verb-name", ("unit", args.Target)),
|
||||||
|
IconEntity = uid
|
||||||
|
};
|
||||||
|
args.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasComp<PlaceableSurfaceComponent>(args.Target))
|
||||||
|
{
|
||||||
|
UtilityVerb verb = new()
|
||||||
|
{
|
||||||
|
Act = () =>
|
||||||
|
{
|
||||||
|
StartDoAfter(uid, args.Target, args.User, dumpable, storage);
|
||||||
|
},
|
||||||
|
Text = Loc.GetString("dump-placeable-verb-name", ("surface", args.Target)),
|
||||||
|
IconEntity = uid
|
||||||
|
};
|
||||||
|
args.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDoAfter(EntityUid storageUid, EntityUid? targetUid, EntityUid userUid, DumpableComponent dumpable, ServerStorageComponent storage, float multiplier = 1)
|
||||||
|
{
|
||||||
|
if (dumpable.CancelToken != null)
|
||||||
|
{
|
||||||
|
dumpable.CancelToken.Cancel();
|
||||||
|
dumpable.CancelToken = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storage.StoredEntities == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float delay = storage.StoredEntities.Count * (float) dumpable.DelayPerItem.TotalSeconds * multiplier;
|
||||||
|
|
||||||
|
dumpable.CancelToken = new CancellationTokenSource();
|
||||||
|
_doAfterSystem.DoAfter(new DoAfterEventArgs(userUid, delay, dumpable.CancelToken.Token, target: targetUid)
|
||||||
|
{
|
||||||
|
BroadcastFinishedEvent = new DumpCompletedEvent(userUid, targetUid, storage.StoredEntities),
|
||||||
|
BroadcastCancelledEvent = new DumpCancelledEvent(dumpable.Owner),
|
||||||
|
BreakOnTargetMove = true,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
BreakOnStun = true,
|
||||||
|
NeedHand = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnDumpCompleted(DumpCompletedEvent args)
|
||||||
|
{
|
||||||
|
Queue<EntityUid> dumpQueue = new();
|
||||||
|
foreach (var entity in args.StoredEntities)
|
||||||
|
{
|
||||||
|
dumpQueue.Enqueue(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp<DisposalUnitComponent>(args.Target, out var disposal))
|
||||||
|
{
|
||||||
|
foreach (var entity in dumpQueue)
|
||||||
|
{
|
||||||
|
_disposalUnitSystem.DoInsertDisposalUnit(args.Target.Value, entity);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entity in dumpQueue)
|
||||||
|
{
|
||||||
|
Transform(entity).AttachParentToContainerOrGrid(EntityManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasComp<PlaceableSurfaceComponent>(args.Target))
|
||||||
|
{
|
||||||
|
foreach (var entity in dumpQueue)
|
||||||
|
{
|
||||||
|
Transform(entity).LocalPosition = Transform(args.Target.Value).LocalPosition;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDumpCancelled(DumpCancelledEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<DumpableComponent>(args.Uid, out var dumpable))
|
||||||
|
dumpable.CancelToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DumpCancelledEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public readonly EntityUid Uid;
|
||||||
|
public DumpCancelledEvent(EntityUid uid)
|
||||||
|
{
|
||||||
|
Uid = uid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DumpCompletedEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public EntityUid User { get; }
|
||||||
|
public EntityUid? Target { get; }
|
||||||
|
public IReadOnlyList<EntityUid> StoredEntities { get; }
|
||||||
|
|
||||||
|
public DumpCompletedEvent(EntityUid user, EntityUid? target, IReadOnlyList<EntityUid> storedEntities)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
Target = target;
|
||||||
|
StoredEntities = storedEntities;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -175,19 +175,6 @@ namespace Content.Server.Storage.EntitySystems
|
|||||||
|
|
||||||
args.Verbs.Add(verb);
|
args.Verbs.Add(verb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the target is a disposal unit, add a verb to transfer storage into the unit (e.g., empty a trash bag).
|
|
||||||
if (!TryComp(args.Target, out DisposalUnitComponent? disposal))
|
|
||||||
return;
|
|
||||||
|
|
||||||
UtilityVerb dispose = new()
|
|
||||||
{
|
|
||||||
Text = Loc.GetString("storage-component-dispose-verb"),
|
|
||||||
IconEntity = args.Using,
|
|
||||||
Act = () => DisposeEntities(args.User, uid, args.Target, component, lockComponent, disposal)
|
|
||||||
};
|
|
||||||
|
|
||||||
args.Verbs.Add(dispose);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -446,35 +433,6 @@ namespace Content.Server.Storage.EntitySystems
|
|||||||
UpdateStorageUI(source, sourceComp);
|
UpdateStorageUI(source, sourceComp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Move entities from storage into a disposal unit.
|
|
||||||
/// </summary>
|
|
||||||
public void DisposeEntities(EntityUid user, EntityUid source, EntityUid target,
|
|
||||||
ServerStorageComponent? sourceComp = null, LockComponent? sourceLock = null,
|
|
||||||
DisposalUnitComponent? disposalComp = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(source, ref sourceComp) || !Resolve(target, ref disposalComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var entities = sourceComp.Storage?.ContainedEntities;
|
|
||||||
if (entities == null || entities.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Resolve(source, ref sourceLock, false) && sourceLock.Locked)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var entity in entities.ToList())
|
|
||||||
{
|
|
||||||
if (_disposalSystem.CanInsert(disposalComp, entity)
|
|
||||||
&& disposalComp.Container.Insert(entity))
|
|
||||||
{
|
|
||||||
_disposalSystem.AfterInsert(disposalComp, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RecalculateStorageUsed(sourceComp);
|
|
||||||
UpdateStorageUI(source, sourceComp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleRemoveEntity(EntityUid uid, EntityUid player, EntityUid itemToRemove, ServerStorageComponent? storageComp = null)
|
public void HandleRemoveEntity(EntityUid uid, EntityUid player, EntityUid itemToRemove, ServerStorageComponent? storageComp = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref storageComp))
|
if (!Resolve(uid, ref storageComp))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Storage.Components;
|
||||||
using Content.Shared.Hands.EntitySystems;
|
using Content.Shared.Hands.EntitySystems;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
@@ -52,6 +52,11 @@ namespace Content.Shared.Placeable
|
|||||||
if (!surface.IsPlaceable)
|
if (!surface.IsPlaceable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// 99% of the time they want to dump the stuff inside on the table, they can manually place with q if they really need to.
|
||||||
|
// Just causes prediction CBT otherwise.
|
||||||
|
if (HasComp<DumpableComponent>(args.Used))
|
||||||
|
return;
|
||||||
|
|
||||||
if (!_handsSystem.TryDrop(args.User, args.Used))
|
if (!_handsSystem.TryDrop(args.User, args.Used))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
23
Content.Shared/Storage/Components/DumpableComponent.cs
Normal file
23
Content.Shared/Storage/Components/DumpableComponent.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Content.Shared.Storage.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lets you dump this container on the ground using a verb,
|
||||||
|
/// or when interacting with it on a disposal unit or placeable surface.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class DumpableComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How long each item adds to the doafter.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("delayPerItem")]
|
||||||
|
public TimeSpan DelayPerItem = TimeSpan.FromSeconds(0.2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancellation token for the doafter.
|
||||||
|
/// <summary>
|
||||||
|
public CancellationTokenSource? CancelToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ using Robust.Shared.Serialization;
|
|||||||
namespace Content.Shared.Storage
|
namespace Content.Shared.Storage
|
||||||
{
|
{
|
||||||
[NetworkedComponent()]
|
[NetworkedComponent()]
|
||||||
public abstract class SharedStorageComponent : Component, IDraggable
|
public abstract class SharedStorageComponent : Component
|
||||||
{
|
{
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class StorageBoundUserInterfaceState : BoundUserInterfaceState
|
public sealed class StorageBoundUserInterfaceState : BoundUserInterfaceState
|
||||||
@@ -56,32 +56,6 @@ namespace Content.Shared.Storage
|
|||||||
/// <param name="entity">The entity to remove</param>
|
/// <param name="entity">The entity to remove</param>
|
||||||
/// <returns>True if no longer in storage, false otherwise</returns>
|
/// <returns>True if no longer in storage, false otherwise</returns>
|
||||||
public abstract bool Remove(EntityUid entity);
|
public abstract bool Remove(EntityUid entity);
|
||||||
|
|
||||||
bool IDraggable.CanDrop(CanDropEvent args)
|
|
||||||
{
|
|
||||||
return _entMan.TryGetComponent(args.Target, out PlaceableSurfaceComponent? placeable) &&
|
|
||||||
placeable.IsPlaceable;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IDraggable.Drop(DragDropEvent eventArgs)
|
|
||||||
{
|
|
||||||
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User, eventArgs.Target))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var storedEntities = StoredEntities?.ToArray();
|
|
||||||
|
|
||||||
if (storedEntities == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// empty everything out
|
|
||||||
foreach (var storedEntity in storedEntities)
|
|
||||||
{
|
|
||||||
if (Remove(storedEntity))
|
|
||||||
_entMan.GetComponent<TransformComponent>(storedEntity).WorldPosition = eventArgs.DropLocation.Position;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dump-verb-name = Dump out on ground
|
||||||
|
dump-disposal-verb-name = Dump out into {$unit}
|
||||||
|
dump-placeable-verb-name = Dump out onto {$surface}
|
||||||
@@ -1,2 +1 @@
|
|||||||
storage-component-transfer-verb = Transfer contents
|
storage-component-transfer-verb = Transfer contents
|
||||||
storage-component-dispose-verb = Dispose of contents
|
|
||||||
@@ -130,3 +130,4 @@
|
|||||||
components:
|
components:
|
||||||
- Produce
|
- Produce
|
||||||
- Seed
|
- Seed
|
||||||
|
- type: Dumpable
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
- type: StorageFillVisualizer
|
- type: StorageFillVisualizer
|
||||||
maxFillLevels: 4
|
maxFillLevels: 4
|
||||||
fillBaseName: icon
|
fillBaseName: icon
|
||||||
|
- type: Dumpable
|
||||||
- type: Clothing
|
- type: Clothing
|
||||||
Slots: [belt]
|
Slots: [belt]
|
||||||
sprite: Objects/Specific/Janitorial/trashbag.rsi
|
sprite: Objects/Specific/Janitorial/trashbag.rsi
|
||||||
|
|||||||
Reference in New Issue
Block a user