using System.Linq;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Storage.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Shared.Storage.EntitySystems;
///
/// System for managing providing entities from storage.
///
///
public sealed partial class EntityProviderSystem : EntitySystem
{
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private SharedContainerSystem _container = default!;
[Dependency] private SharedPopupSystem _popup = default!;
[Dependency] private EntityWhitelistSystem _whitelist = default!;
[Dependency] private EntityQuery _providerQuery;
[Dependency] private EntityQuery _storageQuery;
private const string ContainerId = "entity-provider";
/// Initialize container on component.
[SubscribeLocalEvent]
private void OnInit(Entity provider, ref ComponentInit args)
{
provider.Comp.Container = _container.EnsureContainer(provider, ContainerId);
}
[SubscribeLocalEvent]
public void OnAfterInteract(Entity provider, ref AfterInteractEvent args)
{
if (args.Handled || !provider.Comp.CanTransfer || args.Target == null)
return;
if (TryInsertIntoProvider(provider, args.Target.Value, args.User))
{
args.Handled = true;
return;
}
if (_providerQuery.TryComp(args.Target, out var targetComp))
args.Handled = TryFillOtherProvider(provider, (args.Target.Value, targetComp), args.User);
}
[SubscribeLocalEvent]
public void OnInteractUsing(Entity provider, ref InteractUsingEvent args)
{
if (args.Handled || !provider.Comp.CanReceive)
return;
if (TryInsertIntoProvider(provider, args.Used, args.User))
{
args.Handled = true;
return;
}
if (_storageQuery.TryComp(args.Used, out var storage))
args.Handled = TryFillFromStorage(provider, (args.Used, storage), args.User);
}
///
/// Fill the entityStorage of a provider with the entityStorage of another provider.
///
/// The provider to refill the target.
/// The provider whose entityStorage is to be refilled.
/// The user who attempts to refill the provider with the other. Leave null to avoid popups.
/// Returns true if it was able to insert at least one entity, otherwise false.
private bool TryFillOtherProvider(Entity provider, Entity refillTarget, EntityUid? user = null)
{
var success = false;
List toRemove = [];
if (!provider.Comp.CanTransfer)
{
_popup.PopupEntity(Loc.GetString("comp-entity-provider-cannot-transfer", ("provider", provider)), provider, user);
return false;
}
if (!refillTarget.Comp.CanReceive)
{
_popup.PopupEntity(Loc.GetString("comp-entity-provider-cannot-receive", ("refillTarget", refillTarget)), refillTarget, user);
return false;
}
foreach (var (entProtoId, count) in provider.Comp.EntityCounter)
{
if (_whitelist.IsWhitelistFail(refillTarget.Comp.Whitelist, entProtoId))
continue;
if (!refillTarget.Comp.EntityCounter.TryAdd(entProtoId, count))
refillTarget.Comp.EntityCounter[entProtoId] += count;
// Move all spawned entities over to the new provider.
var existingEntities = GetEntitiesFromContainer(provider.AsNullable(), entProtoId)
.ToArray();
foreach (var spawnedEntity in existingEntities)
{
_container.Insert(spawnedEntity, refillTarget.Comp.Container);
}
success = true;
toRemove.Add(entProtoId);
}
foreach (var removedEntProtoId in toRemove)
{
provider.Comp.EntityCounter.Remove(removedEntProtoId);
}
if (provider.Comp is { DeleteIfEmpty: true, EntityCounter.Count: 0 })
PredictedQueueDel(provider);
Dirty(provider);
Dirty(refillTarget);
if (!success)
return success;
// The refilling provider dictates the sound happening at the refillTarget.
_audio.PlayPredicted(provider.Comp.PluralTransferSound, refillTarget, user);
return success;
}
///
/// Refill an entityProvider with entities inside a storage.
///
/// The provider to refill.
/// The storage whose contents will refill the provider.
/// The user who attempts to refill the provider with the storage. Leave null to avoid popups.
/// Returns true if it was able to insert at least one entity, otherwise false.
private bool TryFillFromStorage(Entity refillTarget, Entity storage, EntityUid? user = null)
{
if (!Resolve(storage, ref storage.Comp))
return false;
var storedEntities = storage.Comp.Container.ContainedEntities.ToArray();
var insertionSuccess = false;
foreach (var ent in storedEntities)
{
if (TryInsertIntoProvider(refillTarget, ent)) // Not passing along the user to avoid the popup.
insertionSuccess = true;
}
// show some message if success
if (!insertionSuccess || user == null)
return insertionSuccess;
_audio.PlayPredicted(refillTarget.Comp.PluralTransferSound, refillTarget, user);
return insertionSuccess;
}
///
/// Attempts to get only the spawned entities of a certain prototype from the container.
///
/// The entityProvider with the container.
/// The entity prototype to check for.
/// The amount of entities to retrieve. If null, it'll retrieve all of them.
/// Returns a list of all currently spawned entities of that prototype. It will NOT spawn more to reach .
private IEnumerable GetEntitiesFromContainer(Entity provider, EntProtoId protoId, int? requestedAmount = null)
{
if (requestedAmount <= 0 || !Resolve(provider, ref provider.Comp))
yield break;
var containedEntities = provider.Comp.Container.ContainedEntities;
var count = 0;
foreach (var containedEntity in containedEntities)
{
var meta = MetaData(containedEntity).EntityPrototype;
if (meta != null && meta.ID == protoId)
{
yield return containedEntity;
count++;
}
// Check if we have enough.
if (count == requestedAmount)
yield break;
}
}
}