| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- using System.Diagnostics.CodeAnalysis;
- using Content.Shared.Armor;
- using Content.Shared.Clothing.Components;
- using Content.Shared.DoAfter;
- using Content.Shared.Hands;
- using Content.Shared.Hands.Components;
- using Content.Shared.Hands.EntitySystems;
- using Content.Shared.Interaction;
- using Content.Shared.Inventory.Events;
- using Content.Shared.Item;
- using Content.Shared.Movement.Systems;
- using Content.Shared.Popups;
- using Content.Shared.Strip;
- using Content.Shared.Strip.Components;
- using Content.Shared.Whitelist;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Containers;
- using Robust.Shared.Timing;
- using Robust.Shared.Utility;
- namespace Content.Shared.Inventory;
- public abstract partial class InventorySystem
- {
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
- [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
- [Dependency] private readonly SharedItemSystem _item = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
- [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
- [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
- [Dependency] private readonly SharedStrippableSystem _strippable = default!;
- [ValidatePrototypeId<ItemSizePrototype>]
- private const string PocketableItemSize = "Small";
- private void InitializeEquip()
- {
- //these events ensure that the client also gets its proper events raised when getting its containerstate updated
- SubscribeLocalEvent<InventoryComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
- SubscribeLocalEvent<InventoryComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
- SubscribeAllEvent<UseSlotNetworkMessage>(OnUseSlot);
- }
- private void OnEntRemoved(EntityUid uid, InventoryComponent component, EntRemovedFromContainerMessage args)
- {
- if (!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component))
- return;
- var unequippedEvent = new DidUnequipEvent(uid, args.Entity, slotDef);
- RaiseLocalEvent(uid, unequippedEvent, true);
- var gotUnequippedEvent = new GotUnequippedEvent(uid, args.Entity, slotDef);
- RaiseLocalEvent(args.Entity, gotUnequippedEvent, true);
- }
- private void OnEntInserted(EntityUid uid, InventoryComponent component, EntInsertedIntoContainerMessage args)
- {
- if (!TryGetSlot(uid, args.Container.ID, out var slotDef, inventory: component))
- return;
- var equippedEvent = new DidEquipEvent(uid, args.Entity, slotDef);
- RaiseLocalEvent(uid, equippedEvent, true);
- var gotEquippedEvent = new GotEquippedEvent(uid, args.Entity, slotDef);
- RaiseLocalEvent(args.Entity, gotEquippedEvent, true);
- }
- /// <summary>
- /// Will attempt to equip or unequip an item to/from the clicked slot. If the user clicked on an occupied slot
- /// with some entity, will instead attempt to interact with this entity.
- /// </summary>
- private void OnUseSlot(UseSlotNetworkMessage ev, EntitySessionEventArgs eventArgs)
- {
- if (eventArgs.SenderSession.AttachedEntity is not { Valid: true } actor)
- return;
- if (!TryComp(actor, out InventoryComponent? inventory) || !TryComp<HandsComponent>(actor, out var hands))
- return;
- var held = hands.ActiveHandEntity;
- TryGetSlotEntity(actor, ev.Slot, out var itemUid, inventory);
- // attempt to perform some interaction
- if (held != null && itemUid != null)
- {
- _interactionSystem.InteractUsing(actor, held.Value, itemUid.Value,
- Transform(itemUid.Value).Coordinates);
- return;
- }
- // unequip the item.
- if (itemUid != null)
- {
- if (!TryUnequip(actor, ev.Slot, out var item, predicted: true, inventory: inventory, checkDoafter: true))
- return;
- _handsSystem.PickupOrDrop(actor, item.Value);
- return;
- }
- // finally, just try to equip the held item.
- if (held == null)
- return;
- // before we drop the item, check that it can be equipped in the first place.
- if (!CanEquip(actor, held.Value, ev.Slot, out var reason))
- {
- _popup.PopupCursor(Loc.GetString(reason));
- return;
- }
- if (!_handsSystem.CanDropHeld(actor, hands.ActiveHand!, checkActionBlocker: false))
- return;
- RaiseLocalEvent(held.Value, new HandDeselectedEvent(actor));
- TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory, force: true, checkDoafter: true);
- }
- public bool TryEquip(EntityUid uid, EntityUid itemUid, string slot, bool silent = false, bool force = false, bool predicted = false,
- InventoryComponent? inventory = null, ClothingComponent? clothing = null, bool checkDoafter = false) =>
- TryEquip(uid, uid, itemUid, slot, silent, force, predicted, inventory, clothing, checkDoafter);
- public bool TryEquip(EntityUid actor, EntityUid target, EntityUid itemUid, string slot, bool silent = false, bool force = false, bool predicted = false,
- InventoryComponent? inventory = null, ClothingComponent? clothing = null, bool checkDoafter = false)
- {
- if (!Resolve(target, ref inventory, false))
- {
- if(!silent)
- _popup.PopupCursor(Loc.GetString("inventory-component-can-equip-cannot"));
- return false;
- }
- // Not required to have, since pockets can take any item.
- // CanEquip will still check, so we don't have to worry about it.
- Resolve(itemUid, ref clothing, false);
- if (!TryGetSlotContainer(target, slot, out var slotContainer, out var slotDefinition, inventory))
- {
- if(!silent)
- _popup.PopupCursor(Loc.GetString("inventory-component-can-equip-cannot"));
- return false;
- }
- if (!force && !CanEquip(actor, target, itemUid, slot, out var reason, slotDefinition, inventory, clothing))
- {
- if(!silent)
- _popup.PopupCursor(Loc.GetString(reason));
- return false;
- }
- if (checkDoafter &&
- clothing != null &&
- clothing.EquipDelay > TimeSpan.Zero &&
- (clothing.Slots & slotDefinition.SlotFlags) != 0 &&
- _containerSystem.CanInsert(itemUid, slotContainer))
- {
- var args = new DoAfterArgs(
- EntityManager,
- actor,
- clothing.EquipDelay,
- new ClothingEquipDoAfterEvent(slot),
- itemUid,
- target,
- itemUid)
- {
- BreakOnMove = true,
- NeedHand = true,
- };
- _doAfter.TryStartDoAfter(args);
- return false;
- }
- if (!_containerSystem.Insert(itemUid, slotContainer))
- {
- if(!silent)
- _popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"));
- return false;
- }
- if (!silent && clothing != null)
- {
- _audio.PlayPredicted(clothing.EquipSound, target, actor);
- }
- Dirty(target, inventory);
- _movementSpeed.RefreshMovementSpeedModifiers(target);
- return true;
- }
- public bool CanAccess(EntityUid actor, EntityUid target, EntityUid itemUid)
- {
- // if the item is something like a hardsuit helmet, it may be contained within the hardsuit.
- // in that case, we check accesibility for the owner-entity instead.
- if (TryComp(itemUid, out AttachedClothingComponent? attachedComp))
- itemUid = attachedComp.AttachedUid;
- // Can the actor reach the target?
- if (actor != target && !(_interactionSystem.InRangeUnobstructed(actor, target) && _containerSystem.IsInSameOrParentContainer(actor, target)))
- return false;
- // Can the actor reach the item?
- if (_interactionSystem.InRangeAndAccessible(actor, itemUid))
- return true;
- // Is the actor currently stripping the target? Here we could check if the actor has the stripping UI open, but
- // that requires server/client specific code.
- // Uhhh TODO, fix this. This doesn't even fucking check if the target item is IN the targets inventory.
- return actor != target &&
- HasComp<StrippableComponent>(target) &&
- HasComp<StrippingComponent>(actor) &&
- HasComp<HandsComponent>(actor);
- }
- public bool CanEquip(EntityUid uid, EntityUid itemUid, string slot, [NotNullWhen(false)] out string? reason,
- SlotDefinition? slotDefinition = null, InventoryComponent? inventory = null,
- ClothingComponent? clothing = null, ItemComponent? item = null) =>
- CanEquip(uid, uid, itemUid, slot, out reason, slotDefinition, inventory, clothing, item);
- public bool CanEquip(EntityUid actor, EntityUid target, EntityUid itemUid, string slot, [NotNullWhen(false)] out string? reason, SlotDefinition? slotDefinition = null,
- InventoryComponent? inventory = null, ClothingComponent? clothing = null, ItemComponent? item = null)
- {
- reason = "inventory-component-can-equip-cannot";
- if (!Resolve(target, ref inventory, false))
- return false;
- Resolve(itemUid, ref clothing, ref item, false);
- if (slotDefinition == null && !TryGetSlot(target, slot, out slotDefinition, inventory: inventory))
- return false;
- DebugTools.Assert(slotDefinition.Name == slot);
- if (slotDefinition.DependsOn != null)
- {
- if (!TryGetSlotEntity(target, slotDefinition.DependsOn, out EntityUid? slotEntity, inventory))
- return false;
- if (slotDefinition.DependsOnComponents is { } componentRegistry)
- {
- foreach (var (_, entry) in componentRegistry)
- {
- if (!HasComp(slotEntity, entry.Component.GetType()))
- return false;
- if (TryComp<AllowSuitStorageComponent>(slotEntity, out var comp) &&
- _whitelistSystem.IsWhitelistFailOrNull(comp.Whitelist, itemUid))
- return false;
- }
- }
- }
- var fittingInPocket = slotDefinition.SlotFlags.HasFlag(SlotFlags.POCKET) &&
- item != null &&
- _item.GetSizePrototype(item.Size) <= _item.GetSizePrototype(PocketableItemSize);
- if (clothing == null && !fittingInPocket
- || clothing != null && !clothing.Slots.HasFlag(slotDefinition.SlotFlags) && !fittingInPocket)
- {
- reason = "inventory-component-can-equip-does-not-fit";
- return false;
- }
- if (!CanAccess(actor, target, itemUid))
- {
- reason = "interaction-system-user-interaction-cannot-reach";
- return false;
- }
- if (_whitelistSystem.IsWhitelistFail(slotDefinition.Whitelist, itemUid) ||
- _whitelistSystem.IsBlacklistPass(slotDefinition.Blacklist, itemUid))
- {
- reason = "inventory-component-can-equip-does-not-fit";
- return false;
- }
- var attemptEvent = new IsEquippingAttemptEvent(actor, target, itemUid, slotDefinition);
- RaiseLocalEvent(target, attemptEvent, true);
- if (attemptEvent.Cancelled)
- {
- reason = attemptEvent.Reason ?? reason;
- return false;
- }
- if (actor != target)
- {
- //reuse the event. this is gucci, right?
- attemptEvent.Reason = null;
- RaiseLocalEvent(actor, attemptEvent, true);
- if (attemptEvent.Cancelled)
- {
- reason = attemptEvent.Reason ?? reason;
- return false;
- }
- }
- var itemAttemptEvent = new BeingEquippedAttemptEvent(actor, target, itemUid, slotDefinition);
- RaiseLocalEvent(itemUid, itemAttemptEvent, true);
- if (itemAttemptEvent.Cancelled)
- {
- reason = itemAttemptEvent.Reason ?? reason;
- return false;
- }
- return true;
- }
- public bool TryUnequip(
- EntityUid uid,
- string slot,
- bool silent = false,
- bool force = false,
- bool predicted = false,
- InventoryComponent? inventory = null,
- ClothingComponent? clothing = null,
- bool reparent = true,
- bool checkDoafter = false)
- {
- return TryUnequip(uid, uid, slot, silent, force, predicted, inventory, clothing, reparent, checkDoafter);
- }
- public bool TryUnequip(
- EntityUid actor,
- EntityUid target,
- string slot,
- bool silent = false,
- bool force = false,
- bool predicted = false,
- InventoryComponent? inventory = null,
- ClothingComponent? clothing = null,
- bool reparent = true,
- bool checkDoafter = false)
- {
- return TryUnequip(actor, target, slot, out _, silent, force, predicted, inventory, clothing, reparent, checkDoafter);
- }
- public bool TryUnequip(
- EntityUid uid,
- string slot,
- [NotNullWhen(true)] out EntityUid? removedItem,
- bool silent = false,
- bool force = false,
- bool predicted = false,
- InventoryComponent? inventory = null,
- ClothingComponent? clothing = null,
- bool reparent = true,
- bool checkDoafter = false)
- {
- return TryUnequip(uid, uid, slot, out removedItem, silent, force, predicted, inventory, clothing, reparent, checkDoafter);
- }
- public bool TryUnequip(
- EntityUid actor,
- EntityUid target,
- string slot,
- [NotNullWhen(true)] out EntityUid? removedItem,
- bool silent = false,
- bool force = false,
- bool predicted = false,
- InventoryComponent? inventory = null,
- ClothingComponent? clothing = null,
- bool reparent = true,
- bool checkDoafter = false)
- {
- var itemsDropped = 0;
- return TryUnequip(actor, target, slot, out removedItem, ref itemsDropped,
- silent, force, predicted, inventory, clothing, reparent, checkDoafter);
- }
- private bool TryUnequip(
- EntityUid actor,
- EntityUid target,
- string slot,
- [NotNullWhen(true)] out EntityUid? removedItem,
- ref int itemsDropped,
- bool silent = false,
- bool force = false,
- bool predicted = false,
- InventoryComponent? inventory = null,
- ClothingComponent? clothing = null,
- bool reparent = true,
- bool checkDoafter = false)
- {
- removedItem = null;
- if (TerminatingOrDeleted(target))
- return false;
- if (!Resolve(target, ref inventory, false))
- {
- if(!silent)
- _popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"));
- return false;
- }
- if (!TryGetSlotContainer(target, slot, out var slotContainer, out var slotDefinition, inventory))
- {
- if(!silent)
- _popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"));
- return false;
- }
- removedItem = slotContainer.ContainedEntity;
- if (!removedItem.HasValue || TerminatingOrDeleted(removedItem.Value))
- return false;
- if (!force && !CanUnequip(actor, target, slot, out var reason, slotContainer, slotDefinition, inventory))
- {
- if(!silent)
- _popup.PopupCursor(Loc.GetString(reason));
- return false;
- }
- //we need to do this to make sure we are 100% removing this entity, since we are now dropping dependant slots
- if (!force && !_containerSystem.CanRemove(removedItem.Value, slotContainer))
- return false;
- if (checkDoafter &&
- Resolve(removedItem.Value, ref clothing, false) &&
- (clothing.Slots & slotDefinition.SlotFlags) != 0 &&
- clothing.UnequipDelay > TimeSpan.Zero)
- {
- var args = new DoAfterArgs(
- EntityManager,
- actor,
- clothing.UnequipDelay,
- new ClothingUnequipDoAfterEvent(slot),
- removedItem.Value,
- target,
- removedItem.Value)
- {
- BreakOnMove = true,
- NeedHand = true,
- };
- _doAfter.TryStartDoAfter(args);
- return false;
- }
- if (!_containerSystem.Remove(removedItem.Value, slotContainer, force: force, reparent: reparent))
- return false;
- // this is in order to keep track of whether this is the first instance of a recursion call
- var firstRun = itemsDropped == 0;
- ++itemsDropped;
- foreach (var slotDef in inventory.Slots)
- {
- if (slotDef != slotDefinition && slotDef.DependsOn == slotDefinition.Name)
- {
- //this recursive call might be risky
- TryUnequip(actor, target, slotDef.Name, out _, ref itemsDropped, true, true, predicted, inventory, reparent: reparent);
- }
- }
- // we check if any items were dropped, and make a popup if they were.
- // the reason we check for > 1 is because the first item is always the one we are trying to unequip,
- // whereas we only want to notify for extra dropped items.
- if (!silent && _gameTiming.IsFirstTimePredicted && firstRun && itemsDropped > 1)
- _popup.PopupClient(Loc.GetString("inventory-component-dropped-from-unequip", ("items", itemsDropped - 1)), target, target);
- // TODO: Inventory needs a hot cleanup hoo boy
- // Check if something else (AKA toggleable) dumped it into a container.
- if (!_containerSystem.IsEntityInContainer(removedItem.Value))
- _transform.DropNextTo(removedItem.Value, target);
- if (!silent && Resolve(removedItem.Value, ref clothing, false) && clothing.UnequipSound != null)
- {
- _audio.PlayPredicted(clothing.UnequipSound, target, actor);
- }
- Dirty(target, inventory);
- _movementSpeed.RefreshMovementSpeedModifiers(target);
- return true;
- }
- public bool CanUnequip(EntityUid uid, string slot, [NotNullWhen(false)] out string? reason,
- ContainerSlot? containerSlot = null, SlotDefinition? slotDefinition = null,
- InventoryComponent? inventory = null) =>
- CanUnequip(uid, uid, slot, out reason, containerSlot, slotDefinition, inventory);
- public bool CanUnequip(EntityUid actor, EntityUid target, string slot, [NotNullWhen(false)] out string? reason, ContainerSlot? containerSlot = null, SlotDefinition? slotDefinition = null, InventoryComponent? inventory = null)
- {
- reason = "inventory-component-can-unequip-cannot";
- if (!Resolve(target, ref inventory, false))
- return false;
- if ((containerSlot == null || slotDefinition == null) && !TryGetSlotContainer(target, slot, out containerSlot, out slotDefinition, inventory))
- return false;
- if (containerSlot.ContainedEntity is not { } itemUid)
- return false;
- if (!_containerSystem.CanRemove(itemUid, containerSlot))
- return false;
- // make sure the user can actually reach the target
- if (!CanAccess(actor, target, itemUid))
- {
- reason = "interaction-system-user-interaction-cannot-reach";
- return false;
- }
- var attemptEvent = new IsUnequippingAttemptEvent(actor, target, itemUid, slotDefinition);
- RaiseLocalEvent(target, attemptEvent, true);
- if (attemptEvent.Cancelled)
- {
- reason = attemptEvent.Reason ?? reason;
- return false;
- }
- if (actor != target)
- {
- //reuse the event. this is gucci, right?
- attemptEvent.Reason = null;
- RaiseLocalEvent(actor, attemptEvent, true);
- if (attemptEvent.Cancelled)
- {
- reason = attemptEvent.Reason ?? reason;
- return false;
- }
- }
- var itemAttemptEvent = new BeingUnequippedAttemptEvent(actor, target, itemUid, slotDefinition);
- RaiseLocalEvent(itemUid, itemAttemptEvent, true);
- if (itemAttemptEvent.Cancelled)
- {
- reason = attemptEvent.Reason ?? reason;
- return false;
- }
- return true;
- }
- public bool TryGetSlotEntity(EntityUid uid, string slot, [NotNullWhen(true)] out EntityUid? entityUid, InventoryComponent? inventoryComponent = null, ContainerManagerComponent? containerManagerComponent = null)
- {
- entityUid = null;
- if (!Resolve(uid, ref inventoryComponent, ref containerManagerComponent, false)
- || !TryGetSlotContainer(uid, slot, out var container, out _, inventoryComponent, containerManagerComponent))
- return false;
- entityUid = container.ContainedEntity;
- return entityUid != null;
- }
- }
|