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