using System.Diagnostics.CodeAnalysis; using Content.Shared.Database; namespace Content.Shared.Containers.ItemSlots; public sealed partial class ItemSlotsSystem { /// /// Checks whether an item can currently be ejected from a slot. /// /// /// Validation may raise on both the slot owner and the contained item, /// so callers must not treat this as a pure check. If is provided, a locked slot may /// also show its configured failure popup to that entity. /// public bool CanEject(EntityUid uid, ItemSlot slot, EntityUid? user, EntityUid? popup = null) { if (slot.Locked) { if (popup.HasValue && slot.LockedFailPopup.HasValue) _popupSystem.PopupEntity(Loc.GetString(slot.LockedFailPopup), uid, popup.Value); return false; } if (slot.ContainerSlot?.ContainedEntity is not { } item) return false; var ev = new ItemSlotEjectAttemptEvent(uid, item, user, slot); RaiseLocalEvent(uid, ref ev); RaiseLocalEvent(item, ref ev); if (ev.Cancelled) return false; return _containers.CanRemove(item, slot.ContainerSlot); } /// /// Ejects an item without performing validation. Returns false without producing success effects if the backing /// container does not remove the item. /// private bool Eject(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool excludeUserAudio = false) { if (slot.ContainerSlot == null || !_containers.Remove(item, slot.ContainerSlot)) return false; if (user != null) { _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} ejected {ToPrettyString(item)} from {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}"); } _audioSystem.PlayPredicted(slot.EjectSound, uid, excludeUserAudio ? user : null); return true; } /// /// Tries to eject an item from a slot. /// /// /// If is provided, the user's pickup action blocker must also allow the item to be /// picked up. /// /// True only if validation succeeds and the item was ejected. public bool TryEject(EntityUid uid, ItemSlot slot, EntityUid? user, [NotNullWhen(true)] out EntityUid? item, bool excludeUserAudio = false) { item = null; if (!CanEject(uid, slot, user)) return false; item = slot.Item; if (user != null && item != null && !_actionBlockerSystem.CanPickup(user.Value, item.Value, showPopup: true)) return false; return Eject(uid, slot, item!.Value, user, excludeUserAudio); } /// /// Tries to eject an item from a slot selected by ID. /// /// True only if the slot exists and the item was ejected. public bool TryEject(Entity ent, string id, EntityUid? user, [NotNullWhen(true)] out EntityUid? item, bool excludeUserAudio = false) { item = null; if (!Resolve(ent, ref ent.Comp)) return false; if (!ent.Comp.Slots.TryGetValue(id, out var slot)) return false; return TryEject(ent, slot, user, out item, excludeUserAudio); } /// /// Tries to eject an item and then place it in a user's hands or drop it. /// /// True if the item was ejected, even if it could not be placed in a hand. public bool TryEjectToHands(EntityUid uid, ItemSlot slot, EntityUid? user, bool excludeUserAudio = false) { if (!TryEject(uid, slot, user, out var item, excludeUserAudio)) return false; if (user != null) _handsSystem.PickupOrDrop(user.Value, item.Value); return true; } /// /// Unlocks every occupied slot and attempts to eject its item. /// public void EjectFromAllSlots(Entity entity) { EjectFromAllSlots(entity, _ => true); } /// /// Unlocks matching occupied slots and attempts to eject their items on the floor. /// private void EjectFromAllSlots(Entity entity, Func shouldEject) { foreach (var slot in entity.Comp.Slots.Values) { if (slot.HasItem && shouldEject(slot)) { SetLock((entity.Owner, entity.Comp), slot, false); TryEject(entity.Owner, slot, null, out _); } } } }