Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ItemSlotSystem popup Logic #28856

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
153 changes: 117 additions & 36 deletions Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public override void Initialize()
}

#region ComponentManagement

/// <summary>
/// Spawn in starting items for any item slots that should have one.
/// </summary>
Expand All @@ -70,7 +71,8 @@ private void OnMapInit(EntityUid uid, ItemSlotsComponent itemSlots, MapInitEvent
if (slot.HasItem || string.IsNullOrEmpty(slot.StartingItem))
continue;

var item = EntityManager.SpawnEntity(slot.StartingItem, EntityManager.GetComponent<TransformComponent>(uid).Coordinates);
var item = EntityManager.SpawnEntity(slot.StartingItem,
EntityManager.GetComponent<TransformComponent>(uid).Coordinates);
if (slot.ContainerSlot != null)
_containers.Insert(item, slot.ContainerSlot);
}
Expand Down Expand Up @@ -99,7 +101,8 @@ public void AddItemSlot(EntityUid uid, string id, ItemSlot slot, ItemSlotsCompon
if (itemSlots.Slots.TryGetValue(id, out var existing))
{
if (existing.Local)
Log.Error($"Duplicate item slot key. Entity: {EntityManager.GetComponent<MetaDataComponent>(uid).EntityName} ({uid}), key: {id}");
Log.Error(
$"Duplicate item slot key. Entity: {EntityManager.GetComponent<MetaDataComponent>(uid).EntityName} ({uid}), key: {id}");
else
// server state takes priority
slot.CopyFrom(existing);
Expand Down Expand Up @@ -134,7 +137,10 @@ public void RemoveItemSlot(EntityUid uid, ItemSlot slot, ItemSlotsComponent? ite
Dirty(uid, itemSlots);
}

public bool TryGetSlot(EntityUid uid, string slotId, [NotNullWhen(true)] out ItemSlot? itemSlot, ItemSlotsComponent? component = null)
public bool TryGetSlot(EntityUid uid,
string slotId,
[NotNullWhen(true)] out ItemSlot? itemSlot,
ItemSlotsComponent? component = null)
{
itemSlot = null;

Expand All @@ -143,9 +149,11 @@ public bool TryGetSlot(EntityUid uid, string slotId, [NotNullWhen(true)] out Ite

return component.Slots.TryGetValue(slotId, out itemSlot);
}

#endregion

#region Interactions

/// <summary>
/// Attempt to take an item from a slot, if any are set to EjectOnInteract.
/// </summary>
Expand Down Expand Up @@ -201,20 +209,50 @@ private void OnInteractUsing(EntityUid uid, ItemSlotsComponent itemSlots, Intera
if (!EntityManager.TryGetComponent(args.User, out HandsComponent? hands))
return;

if (itemSlots.Slots.Count == 0)
return;

// If any slot can be inserted into don't show popup.
// If any whitelist passes, but slot is locked, then show locked.
// If whitelist fails all, show whitelist fail.

// valid, insertable slots (if any)
var slots = new List<ItemSlot>();

string? whitelistFailPopup = null;
string? lockedFailPopup = null;
foreach (var slot in itemSlots.Slots.Values)
{
if (!slot.InsertOnInteract)
continue;

if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap, popup: args.User))
continue;
if (CanInsert(uid, args.Used, args.User, slot, slot.Swap))
{
slots.Add(slot);
}
else
{
var allowed = CanInsertWhitelist(args.Used, slot);
if (lockedFailPopup == null && slot.LockedFailPopup != null && allowed && slot.Locked)
lockedFailPopup = slot.LockedFailPopup;

slots.Add(slot);
if (whitelistFailPopup == null && slot.WhitelistFailPopup != null)
whitelistFailPopup = slot.WhitelistFailPopup;
}
}

if (slots.Count == 0)
{
// it's a bit weird that the popupMessage is stored with the item slots themselves, but in practice
// the popup messages will just all be the same, so it's probably fine.
//
// doing a check to make sure that they're all the same or something is probably frivolous
if (lockedFailPopup != null)
_popupSystem.PopupClient(Loc.GetString(lockedFailPopup), uid, args.User);
else if (whitelistFailPopup != null)
_popupSystem.PopupClient(Loc.GetString(whitelistFailPopup), uid, args.User);
return;
}

// Drop the held item onto the floor. Return if the user cannot drop.
if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands))
Expand All @@ -236,23 +274,31 @@ private void OnInteractUsing(EntityUid uid, ItemSlotsComponent itemSlots, Intera
return;
}
}

#endregion

#region Insert

/// <summary>
/// Insert an item into a slot. This does not perform checks, so make sure to also use <see
/// cref="CanInsert"/> or just use <see cref="TryInsert"/> instead.
/// </summary>
/// <param name="excludeUserAudio">If true, will exclude the user when playing sound. Does nothing client-side.
/// Useful for predicted interactions</param>
private void Insert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool excludeUserAudio = false)
private void Insert(EntityUid uid,
ItemSlot slot,
EntityUid item,
EntityUid? user,
bool excludeUserAudio = false)
{
bool? inserted = slot.ContainerSlot != null ? _containers.Insert(item, slot.ContainerSlot) : null;
// ContainerSlot automatically raises a directed EntInsertedIntoContainerMessage

// Logging
if (inserted != null && inserted.Value && user != null)
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} inserted {ToPrettyString(item)} into {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}");
_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);
}
Expand All @@ -261,46 +307,53 @@ private void Insert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? use
/// Check whether a given item can be inserted into a slot. Unless otherwise specified, this will return
/// false if the slot is already filled.
/// </summary>
/// <remarks>
/// If a popup entity is given, and if the item slot is set to generate a popup message when it fails to
/// pass the whitelist or due to slot being locked, then this will generate an appropriate popup.
/// </remarks>
public bool CanInsert(EntityUid uid, EntityUid usedUid, EntityUid? user, ItemSlot slot, bool swap = false, EntityUid? popup = null)
public bool CanInsert(EntityUid uid,
EntityUid usedUid,
EntityUid? user,
ItemSlot slot,
bool swap = false)
{
if (slot.ContainerSlot == null)
return false;

if (_whitelistSystem.IsWhitelistFail(slot.Whitelist, usedUid) || _whitelistSystem.IsBlacklistPass(slot.Blacklist, usedUid))
{
if (popup.HasValue && slot.WhitelistFailPopup.HasValue)
_popupSystem.PopupClient(Loc.GetString(slot.WhitelistFailPopup), uid, popup.Value);
if (slot.HasItem && (!swap || swap && !CanEject(uid, user, slot)))
return false;
}

if (slot.Locked)
{
if (popup.HasValue && slot.LockedFailPopup.HasValue)
_popupSystem.PopupClient(Loc.GetString(slot.LockedFailPopup), uid, popup.Value);
if (!CanInsertWhitelist(usedUid, slot))
return false;
}

if (slot.HasItem && (!swap || (swap && !CanEject(uid, user, slot))))
if (slot.Locked)
return false;

var ev = new ItemSlotInsertAttemptEvent(uid, usedUid, user, slot);
RaiseLocalEvent(uid, ref ev);
RaiseLocalEvent(usedUid, ref ev);
if (ev.Cancelled)
{
return false;
}

return _containers.CanInsert(usedUid, slot.ContainerSlot, assumeEmpty: swap);
}

private bool CanInsertWhitelist(EntityUid usedUid, ItemSlot slot)
{
if (_whitelistSystem.IsWhitelistFail(slot.Whitelist, usedUid)
|| _whitelistSystem.IsBlacklistPass(slot.Blacklist, usedUid))
return false;
return true;
}

/// <summary>
/// Tries to insert item into a specific slot.
/// </summary>
/// <returns>False if failed to insert item</returns>
public bool TryInsert(EntityUid uid, string id, EntityUid item, EntityUid? user, ItemSlotsComponent? itemSlots = null, bool excludeUserAudio = false)
public bool TryInsert(EntityUid uid,
string id,
EntityUid item,
EntityUid? user,
ItemSlotsComponent? itemSlots = null,
bool excludeUserAudio = false)
{
if (!Resolve(uid, ref itemSlots))
return false;
Expand All @@ -315,7 +368,11 @@ public bool TryInsert(EntityUid uid, string id, EntityUid item, EntityUid? user,
/// Tries to insert item into a specific slot.
/// </summary>
/// <returns>False if failed to insert item</returns>
public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user, bool excludeUserAudio = false)
public bool TryInsert(EntityUid uid,
ItemSlot slot,
EntityUid item,
EntityUid? user,
bool excludeUserAudio = false)
{
if (!CanInsert(uid, item, user, slot))
return false;
Expand All @@ -329,7 +386,11 @@ public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? u
/// Does not check action blockers.
/// </summary>
/// <returns>False if failed to insert item</returns>
public bool TryInsertFromHand(EntityUid uid, ItemSlot slot, EntityUid user, HandsComponent? hands = null, bool excludeUserAudio = false)
public bool TryInsertFromHand(EntityUid uid,
ItemSlot slot,
EntityUid user,
HandsComponent? hands = null,
bool excludeUserAudio = false)
{
if (!Resolve(user, ref hands, false))
return false;
Expand Down Expand Up @@ -443,6 +504,7 @@ private static int SortEmpty(ItemSlot a, ItemSlot b)

return 1;
}

#endregion

#region Eject
Expand All @@ -462,7 +524,7 @@ public bool CanEject(EntityUid uid, EntityUid? user, ItemSlot slot, EntityUid? p
return false;
}

if (slot.ContainerSlot?.ContainedEntity is not {} item)
if (slot.ContainerSlot?.ContainedEntity is not { } item)
return false;

var ev = new ItemSlotEjectAttemptEvent(uid, item, user, slot);
Expand All @@ -487,7 +549,9 @@ private void Eject(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user

// Logging
if (ejected != null && ejected.Value && user != null)
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} ejected {ToPrettyString(item)} from {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}");
_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);
}
Expand All @@ -496,7 +560,11 @@ private void Eject(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user
/// Try to eject an item from a slot.
/// </summary>
/// <returns>False if item slot is locked or has no item inserted</returns>
public bool TryEject(EntityUid uid, ItemSlot slot, EntityUid? user, [NotNullWhen(true)] out EntityUid? item, bool excludeUserAudio = false)
public bool TryEject(EntityUid uid,
ItemSlot slot,
EntityUid? user,
[NotNullWhen(true)] out EntityUid? item,
bool excludeUserAudio = false)
{
item = null;

Expand All @@ -518,8 +586,12 @@ public bool TryEject(EntityUid uid, ItemSlot slot, EntityUid? user, [NotNullWhen
/// Try to eject item from a slot.
/// </summary>
/// <returns>False if the id is not valid, the item slot is locked, or it has no item inserted</returns>
public bool TryEject(EntityUid uid, string id, EntityUid? user,
[NotNullWhen(true)] out EntityUid? item, ItemSlotsComponent? itemSlots = null, bool excludeUserAudio = false)
public bool TryEject(EntityUid uid,
string id,
EntityUid? user,
[NotNullWhen(true)] out EntityUid? item,
ItemSlotsComponent? itemSlots = null,
bool excludeUserAudio = false)
{
item = null;

Expand Down Expand Up @@ -550,12 +622,16 @@ public bool TryEjectToHands(EntityUid uid, ItemSlot slot, EntityUid? user, bool

return true;
}

#endregion

#region Verbs
private void AddAlternativeVerbs(EntityUid uid, ItemSlotsComponent itemSlots, GetVerbsEvent<AlternativeVerb> args)

private void AddAlternativeVerbs(EntityUid uid,
ItemSlotsComponent itemSlots,
GetVerbsEvent<AlternativeVerb> args)
{
if (args.Hands == null || !args.CanAccess ||!args.CanInteract)
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
{
return;
}
Expand Down Expand Up @@ -649,7 +725,9 @@ private void AddAlternativeVerbs(EntityUid uid, ItemSlotsComponent itemSlots, Ge
}
}

private void AddInteractionVerbsVerbs(EntityUid uid, ItemSlotsComponent itemSlots, GetVerbsEvent<InteractionVerb> args)
private void AddInteractionVerbsVerbs(EntityUid uid,
ItemSlotsComponent itemSlots,
GetVerbsEvent<InteractionVerb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
return;
Expand Down Expand Up @@ -708,7 +786,7 @@ private void AddInteractionVerbsVerbs(EntityUid uid, ItemSlotsComponent itemSlot
new SpriteSpecifier.Texture(
new ResPath("/Textures/Interface/VerbIcons/insert.svg.192dpi.png"));
}
else if(slot.EjectOnInteract)
else if (slot.EjectOnInteract)
{
// Inserting/ejecting is a primary interaction for this entity. Instead of using the insert
// category, we will use a single "Place <item>" verb.
Expand All @@ -727,9 +805,11 @@ private void AddInteractionVerbsVerbs(EntityUid uid, ItemSlotsComponent itemSlot
args.Verbs.Add(insertVerb);
}
}

#endregion

#region BUIs

private void HandleButtonPressed(EntityUid uid, ItemSlotsComponent component, ItemSlotButtonPressedEvent args)
{
if (!component.Slots.TryGetValue(args.SlotId, out var slot))
Expand All @@ -740,6 +820,7 @@ private void HandleButtonPressed(EntityUid uid, ItemSlotsComponent component, It
else if (args.TryInsert && !slot.HasItem)
TryInsertFromHand(uid, slot, args.Actor);
}

#endregion

/// <summary>
Expand Down
Loading