using System.Diagnostics.CodeAnalysis; using Content.Shared.Popups; using Content.Shared.Storage.Components; using Content.Shared.Storage.Events; using JetBrains.Annotations; using Robust.Shared.Prototypes; namespace Content.Shared.Storage.EntitySystems; public sealed partial class EntityProviderSystem { /// /// Attempts to insert an entity back into the entityStorage of the provider. /// They will be saved as an entity and are prioritized over spawning new entities of their kind. /// /// The entity providing the entityProvider storage. /// The entity attempted to be put into the provider. /// The user attempting to insert the entity into the provider. Leave null to avoid popups. /// Returns true if it was inserted successfully, otherwise false. [PublicAPI] public bool TryInsertIntoProvider(Entity provider, EntityUid target, EntityUid? user = null) { if (_whitelist.IsWhitelistFail(provider.Comp.Whitelist, target)) return false; // This event allows for a deeper check than a whitelist/blacklist. var ev = new EntityProviderInsertCheckEvent(); RaiseLocalEvent(target, ref ev); if (ev.FailureMessage != null) { _popup.PopupEntity(ev.FailureMessage, provider, user, PopupType.Medium); return false; } var meta = MetaData(target); if (meta.EntityPrototype == null) return false; if (!provider.Comp.EntityCounter.TryAdd(meta.EntityPrototype, 1)) provider.Comp.EntityCounter[meta.EntityPrototype]++; _container.Insert(target, provider.Comp.Container); if (user.HasValue) // This ensures not causing multiple sounds when refilled via storage. _audio.PlayPredicted(provider.Comp.SingularTransferSound, provider, user.Value); Dirty(provider); return true; } /// /// Try to get an entity from the provider and spawn it. /// /// The entity providing the entityProvider storage. /// The entity prototype ID to be spawned. /// The uid of the spawned entity. /// Returns true when it was able to spawn it, otherwise false. [PublicAPI] public bool TryGetEntity(Entity provider, EntProtoId protoId, [NotNullWhen(true)] out EntityUid? entity) { entity = null; if (!TryGetEntities(provider, protoId, out var entities, 1)) return false; entity = entities[0]; return true; } /// /// Try to get a list of entities of the same kind from the provider and spawn them. /// /// The entity providing the entityProvider storage. /// The entity prototype ID to be spawned. /// The uid list of the spawned entities. /// The amount of entities to spawn. If null, it'll spawn all of them. /// Returns true when it was able to spawn them, otherwise false. [PublicAPI] public bool TryGetEntities( Entity provider, EntProtoId protoId, [NotNullWhen(true)] out List? entities, int? requestedAmount = null ) { entities = null; if (requestedAmount <= 0 || !Resolve(provider, ref provider.Comp) || !provider.Comp.EntityCounter.TryGetValue(protoId, out var amountInEntityProvider)) return false; requestedAmount = requestedAmount == null ? amountInEntityProvider : Math.Min(requestedAmount.Value, amountInEntityProvider); // Prioritize already spawned entities before spawning new ones. entities = new List(GetEntitiesFromContainer(provider, protoId, requestedAmount)); requestedAmount -= entities.Count; // We don't need to spawn already spawned ones, so reduce the amount. amountInEntityProvider -= entities.Count; while (requestedAmount > 0) { var spawned = PredictedSpawnInContainerOrDrop(protoId, provider, ContainerId); entities.Add(spawned); amountInEntityProvider--; requestedAmount--; } if (amountInEntityProvider == 0) provider.Comp.EntityCounter.Remove(protoId); else provider.Comp.EntityCounter[protoId] = amountInEntityProvider; Dirty(provider); if (provider.Comp.DeleteIfEmpty && provider.Comp.EntityCounter.Count == 0) PredictedQueueDel(provider); return true; } /// /// Returns a readonly Dictionary containing the EntProtoIds and their corresponding stored amounts. /// This cannot be used for editing, only for knowing what and how much is stored. /// /// The entity providing the entityProvider storage. /// The dictionary containing the stored entities. /// Returns true if the provider has one, otherwise false. [PublicAPI] public bool TryGetEntityCounter(Entity provider, [NotNullWhen(true)] out IReadOnlyDictionary? entityCounter) { entityCounter = null; if (!Resolve(provider, ref provider.Comp)) return false; entityCounter = provider.Comp.EntityCounter; return true; } /// /// Attempts to spawn all entities of a kind, and then eject them from the provider. /// /// The entity providing the entityProvider storage. /// The entity prototype ID to be spawned. /// The uid list of the spawned and ejected entities. /// The amount of entities to spawn and eject. If null, it'll spawn all of them. /// The user ejecting the items. /// Returns true when the entities were spawned and ejected, otherwise false. [PublicAPI] public bool TryEjectEntities( Entity provider, EntProtoId protoId, [NotNullWhen(true)] out List? entities, int? requestedAmount = null, EntityUid? user = null ) { entities = null; if (!Resolve(provider, ref provider.Comp) || !TryGetEntities(provider, protoId, out entities, requestedAmount)) return false; if (entities.Count == 0) { var message = Loc.GetString("comp-entity-provider-no-ejected"); _popup.PopupEntity(message, provider, user, PopupType.Medium); return false; } foreach (var entity in entities) { _container.Remove(entity, provider.Comp.Container); } if (!ProtoMan.Resolve(protoId, out var prototype)) return true; _audio.PlayPredicted(provider.Comp.PluralTransferSound, provider, user); return true; } }