using System.Diagnostics.CodeAnalysis; using Content.Shared.Database; using Content.Shared.Hands.Components; namespace Content.Shared.Containers.ItemSlots; public sealed partial class ItemSlotsSystem { /// /// Inserts an item without performing validation. Returns false without producing success effects if the /// backing container rejects the item. /// private bool Insert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool excludeUserAudio = false) { if (slot.ContainerSlot == null || !_containers.Insert(item, slot.ContainerSlot)) return false; if (user != null) { _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} inserted {ToPrettyString(item)} into {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}"); } _audioSystem.PlayPredicted(slot.InsertSound, uid, excludeUserAudio ? user : null); return true; } /// /// Checks whether an item can currently be inserted into a slot. Unless is true, this /// returns false if the slot is occupied. /// /// /// Validation may raise on both the slot owner and the candidate item, /// so callers must not treat this as a pure check. /// public bool CanInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool swap = false) { if (slot.ContainerSlot == null) return false; if (slot.HasItem && (!swap || swap && !CanEject(uid, slot, user))) return false; if (!CanInsertWhitelist(item, slot)) return false; if (slot.Locked) return false; var ev = new ItemSlotInsertAttemptEvent(uid, item, user, slot); RaiseLocalEvent(uid, ref ev); RaiseLocalEvent(item, ref ev); if (ev.Cancelled) return false; return _containers.CanInsert(item, slot.ContainerSlot, assumeEmpty: swap); } private bool CanInsertWhitelist(EntityUid item, ItemSlot slot) { if (_whitelistSystem.IsWhitelistFail(slot.Whitelist, item) || _whitelistSystem.IsWhitelistPass(slot.Blacklist, item)) return false; return true; } /// /// Tries to insert an item into a slot selected by ID. /// /// True only if the slot exists and the item was inserted. public bool TryInsert(Entity ent, string id, EntityUid item, EntityUid? user, bool excludeUserAudio = false) { if (!Resolve(ent, ref ent.Comp)) return false; if (!ent.Comp.Slots.TryGetValue(id, out var slot)) return false; return TryInsert(ent, slot, item, user, excludeUserAudio: excludeUserAudio); } /// /// Tries to insert an item into a specific slot. /// /// True only if validation succeeds and the item was inserted. public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool excludeUserAudio = false) { if (!CanInsert(uid, slot, item, user)) return false; return Insert(uid, slot, item, user, excludeUserAudio: excludeUserAudio); } /// /// Tries to insert the item in a user's active hand into a specific slot. /// /// /// If insertion fails after the item is dropped, the item remains dropped. /// /// True only if the held item was dropped and inserted. public bool TryInsertFromHand(EntityUid uid, ItemSlot slot, Entity user, bool excludeUserAudio = false) { if (!Resolve(user, ref user.Comp, false)) return false; if (!_handsSystem.TryGetActiveItem(user, out var held)) return false; if (!CanInsert(uid, slot, held.Value, user)) return false; if (!_handsSystem.TryDrop(user, user.Comp.ActiveHandId!)) return false; return Insert(uid, slot, held.Value, user, excludeUserAudio: excludeUserAudio); } /// /// Tries to insert an item into any compatible empty slot. /// /// The entity that has the item slots. /// The item to be inserted. /// The entity performing the interaction. /// /// If true, will exclude the user when playing sound. Does nothing client-side. /// Useful for predicted interactions. /// /// True only if a compatible slot was found and the item was inserted. public bool TryInsertEmpty(Entity ent, EntityUid item, EntityUid? user, bool excludeUserAudio = false) { if (!Resolve(ent, ref ent.Comp, false)) return false; if (!TryGetAvailableSlot(ent, item, user, out var itemSlot, emptyOnly: true)) return false; if (user != null && !_handsSystem.TryDrop(user.Value, item)) return false; return Insert(ent, itemSlot, item, user, excludeUserAudio: excludeUserAudio); } /// /// Tries to get a slot that the can be inserted into. /// /// Entity that is being inserted into. /// Entity being inserted into . /// Entity inserting into . /// The ItemSlot on to insert into. /// If true, occupied slots are skipped before validation. /// True when a compatible slot is found. Otherwise, false. public bool TryGetAvailableSlot(Entity ent, EntityUid item, Entity? userEnt, [NotNullWhen(true)] out ItemSlot? itemSlot, bool emptyOnly = false) { itemSlot = null; if (userEnt is { } user && Resolve(user, ref user.Comp) && _handsSystem.IsHolding(user, item)) { if (!_handsSystem.CanDrop(user, item)) return false; } if (!Resolve(ent, ref ent.Comp, false)) return false; var slots = new List(); foreach (var slot in ent.Comp.Slots.Values) { if (emptyOnly && slot.ContainerSlot?.ContainedEntity != null) continue; if (CanInsert(ent, slot, item, userEnt)) slots.Add(slot); } if (slots.Count == 0) return false; slots.Sort(SortEmpty); itemSlot = slots[0]; return true; } private static int SortEmpty(ItemSlot a, ItemSlot b) { var aEmpty = !a.HasItem; var bEmpty = !b.HasItem; if (aEmpty != bEmpty) return aEmpty ? -1 : 1; return a.Priority.CompareTo(b.Priority); } }