using System.Diagnostics.CodeAnalysis; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Popups; using Robust.Shared.Containers; using Robust.Shared.Prototypes; namespace Content.Shared.Inventory.VirtualItem; /// /// In charge of managing virtual items. /// Virtual items are used to block a /// or a with a non-existent item that /// is a visual copy of another for whatever use /// /// /// The slot visuals are managed by /// and , see the /// references there for more information /// public abstract class SharedVirtualItemSystem : EntitySystem { [Dependency] private readonly SharedContainerSystem _containerSystem = default!; [Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; private static readonly EntProtoId VirtualItem = "VirtualItem"; public override void Initialize() { SubscribeLocalEvent(OnAfterAutoHandleState); SubscribeLocalEvent(OnBeingEquippedAttempt); SubscribeLocalEvent(OnBeingUnequippedAttempt); SubscribeLocalEvent(OnBeforeRangedInteract); SubscribeLocalEvent(OnGettingInteractedWithAttemptEvent); SubscribeLocalEvent(OnGetUsedEntity); } /// /// Updates the GUI buttons with the new entity. /// private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args) { if (_containerSystem.IsEntityInContainer(ent)) _itemSystem.VisualsChanged(ent); } private void OnBeingEquippedAttempt(Entity ent, ref BeingEquippedAttemptEvent args) { // No interactions with a virtual item, please. args.Cancel(); } private void OnBeingUnequippedAttempt(Entity ent, ref BeingUnequippedAttemptEvent args) { // No interactions with a virtual item, please. args.Cancel(); } private void OnBeforeRangedInteract(Entity ent, ref BeforeRangedInteractEvent args) { // No interactions with a virtual item, please. args.Handled = true; } private void OnGettingInteractedWithAttemptEvent(Entity ent, ref GettingInteractedWithAttemptEvent args) { // No interactions with a virtual item, please. args.Cancelled = true; } private void OnGetUsedEntity(Entity ent, ref GetUsedEntityEvent args) { if (args.Handled) return; // if the user is holding the real item the virtual item points to, // we allow them to use it in the interaction foreach (var held in _handsSystem.EnumerateHeld(args.User)) { if (held == ent.Comp.BlockingEntity) { args.Used = ent.Comp.BlockingEntity; return; } } } #region Hands /// /// Spawns a virtual item in a empty hand /// /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity /// Whether or not to try and drop other items to make space public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, bool dropOthers = false) { return TrySpawnVirtualItemInHand(blockingEnt, user, out _, dropOthers); } /// public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, string? empty = null) { virtualItem = null; if (empty == null && !_handsSystem.TryGetEmptyHand(user, out empty)) { if (!dropOthers) return false; foreach (var hand in _handsSystem.EnumerateHands(user)) { if (!_handsSystem.TryGetHeldItem(user, hand, out var held)) continue; if (held == blockingEnt) continue; if (!_handsSystem.TryDrop(user, hand)) continue; if (!TerminatingOrDeleted(held)) _popup.PopupClient(Loc.GetString("virtual-item-dropped-other", ("dropped", held)), user, user); empty = hand; break; } } if (empty == null) return false; if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem)) return false; _handsSystem.DoPickup(user, empty, virtualItem.Value); return true; } /// /// Scan the user's hands until we find the virtual entity, if the /// virtual entity is a copy of the matching entity, delete it /// public void DeleteInHandsMatching(EntityUid user, EntityUid matching) { foreach (var held in _handsSystem.EnumerateHeld(user)) { if (TryComp(held, out VirtualItemComponent? virt) && virt.BlockingEntity == matching) { DeleteVirtualItem((held, virt), user); } } } #endregion #region Inventory /// /// Spawns a virtual item inside a inventory slot /// /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity /// The slot to which we will insert the virtual entity (could be the "shoes" slot, for example) /// Whether or not to force an equip public bool TrySpawnVirtualItemInInventory(EntityUid blockingEnt, EntityUid user, string slot, bool force = false) { return TrySpawnVirtualItemInInventory(blockingEnt, user, slot, force, out _); } /// public bool TrySpawnVirtualItemInInventory(EntityUid blockingEnt, EntityUid user, string slot, bool force, [NotNullWhen(true)] out EntityUid? virtualItem) { if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem)) return false; _inventorySystem.TryEquip(user, virtualItem.Value, slot, force: force); return true; } /// /// Scan the user's inventory slots until we find a virtual entity, when /// that's done check if the found virtual entity is a copy of our matching entity, /// if it is, delete it /// /// The entity that we want to delete the virtual entity from /// The entity that made the virtual entity /// Set this param if you have the name of the slot, it avoids unnecessary queries public void DeleteInSlotMatching(EntityUid user, EntityUid matching, string? slotName = null) { if (slotName != null) { if (!_inventorySystem.TryGetSlotEntity(user, slotName, out var slotEnt)) return; if (TryComp(slotEnt, out VirtualItemComponent? virt) && virt.BlockingEntity == matching) DeleteVirtualItem((slotEnt.Value, virt), user); return; } if (!_inventorySystem.TryGetSlots(user, out var slotDefinitions)) return; foreach (var slot in slotDefinitions) { if (!_inventorySystem.TryGetSlotEntity(user, slot.Name, out var slotEnt)) continue; if (TryComp(slotEnt, out VirtualItemComponent? virt) && virt.BlockingEntity == matching) DeleteVirtualItem((slotEnt.Value, virt), user); } } #endregion /// /// Spawns a virtual item and setups the component without any special handling /// /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity /// The virtual item, if spawned public bool TrySpawnVirtualItem(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem) { var pos = Transform(user).Coordinates; virtualItem = PredictedSpawnAttachedTo(VirtualItem, pos); var virtualItemComp = Comp(virtualItem.Value); virtualItemComp.BlockingEntity = blockingEnt; Dirty(virtualItem.Value, virtualItemComp); return true; } /// /// Queues a deletion for a virtual item and notifies the blocking entity and user. /// public void DeleteVirtualItem(Entity item, EntityUid user) { var userEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user); RaiseLocalEvent(user, userEv); var targEv = new VirtualItemDeletedEvent(item.Comp.BlockingEntity, user); RaiseLocalEvent(item.Comp.BlockingEntity, targEv); if (TerminatingOrDeleted(item)) return; PredictedQueueDel(item.Owner); } }