using System.Diagnostics.CodeAnalysis; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Popups; using Robust.Shared.Containers; using Robust.Shared.Network; 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 INetManager _netManager = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [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!; [ValidatePrototypeId] private const string VirtualItem = "VirtualItem"; public override void Initialize() { SubscribeLocalEvent(OnAfterAutoHandleState); SubscribeLocalEvent(OnBeingEquippedAttempt); SubscribeLocalEvent(OnBeingUnequippedAttempt); SubscribeLocalEvent(OnBeforeRangedInteract); } /// /// 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; } #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) { virtualItem = null; if (!_handsSystem.TryGetEmptyHand(user, out var empty)) { if (!dropOthers) return false; foreach (var hand in _handsSystem.EnumerateHands(user)) { if (hand.HeldEntity is not { } 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) { // Client can't currently predict deleting networked entities so we use this workaround, another // problem can popup when the hands leave PVS for example and this avoids that too if (_netManager.IsClient) return; foreach (var hand in _handsSystem.EnumerateHands(user)) { if (TryComp(hand.HeldEntity, out VirtualItemComponent? virt) && virt.BlockingEntity == matching) { DeleteVirtualItem((hand.HeldEntity.Value, 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) { // Client can't currently predict deleting networked entities so we use this workaround, another // problem can popup when the hands leave PVS for example and this avoids that too if (_netManager.IsClient) return; 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) { if (_netManager.IsClient) { virtualItem = null; return false; } var pos = Transform(user).Coordinates; virtualItem = Spawn(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; _transformSystem.DetachParentToNull(item, Transform(item)); if (_netManager.IsServer) QueueDel(item); } }