Dumpable component to use a doafter to empty storage into a disposal unit, placeable surface, or the ground (#7792)

This commit is contained in:
Rane
2022-05-03 23:00:22 -04:00
committed by GitHub
parent 40ae7cc285
commit cfd00e74ca
11 changed files with 240 additions and 72 deletions

View File

@@ -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();

View File

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

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

View File

@@ -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))

View File

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

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

View File

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

View File

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

View File

@@ -1,2 +1 @@
storage-component-transfer-verb = Transfer contents storage-component-transfer-verb = Transfer contents
storage-component-dispose-verb = Dispose of contents

View File

@@ -130,3 +130,4 @@
components: components:
- Produce - Produce
- Seed - Seed
- type: Dumpable

View File

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