CatStation/Content.Shared/Inventory/InventorySystem.Equip.cs
Dmitry 25c5e59248
Upstream (#3172)
Signed-off-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Co-authored-by: Samuka-C <47865393+Samuka-C@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Co-authored-by: Partmedia <kevinz5000@gmail.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: themias <89101928+themias@users.noreply.github.com>
Co-authored-by: Victor Shen <71985089+Vexerot@users.noreply.github.com>
Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: Milon <milonpl.git@proton.me>
Co-authored-by: Kirus59 <145689588+Kirus59@users.noreply.github.com>
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: Stomf <5dorkydorks@gmail.com>
Co-authored-by: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com>
Co-authored-by: PraxisMapper <praxismapper@gmail.com>
Co-authored-by: EmoGarbage404 <retron404@gmail.com>
Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
Co-authored-by: TytosB <54259736+TytosB@users.noreply.github.com>
Co-authored-by: abadaba695 <spacestation13thingy@gmail.com>
Co-authored-by: kosticia <kosticia46@gmail.com>
Co-authored-by: Thinbug <101073555+Thinbug0@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com>
Co-authored-by: ActiveMammmoth <140334666+ActiveMammmoth@users.noreply.github.com>
Co-authored-by: Myra <vasilis@pikachu.systems>
Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com>
Co-authored-by: Gentleman-Bird <dcgreen406@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: BIGZi0348 <svalker0348@gmail.com>
Co-authored-by: LaCumbiaDelCoronavirus <90893484+LaCumbiaDelCoronavirus@users.noreply.github.com>
Co-authored-by: imatsoup <93290208+imatsoup@users.noreply.github.com>
Co-authored-by: Matthew Herber <32679887+happyrobot33@users.noreply.github.com>
Co-authored-by: Ertanic <36124833+Ertanic@users.noreply.github.com>
Co-authored-by: MissKay1994 <15877268+MissKay1994@users.noreply.github.com>
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
Co-authored-by: eoineoineoin <helloworld@eoinrul.es>
Co-authored-by: Tiniest Shark <head.rebel@yahoo.com>
Co-authored-by: nikitosych <boriszyn@gmail.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com>
Co-authored-by: YoungThug <ramialanbagy@gmail.com>
Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com>
Co-authored-by: Vladislav Suchkov <20380250+murolem@users.noreply.github.com>
Co-authored-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: Unkn0wn_Gh0st <shadowstalkermll@gmail.com>
Co-authored-by: 3nderall <101940324+3nderall@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: J <billsmith116@gmail.com>
Co-authored-by: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: youtissoum <51883137+youtissoum@users.noreply.github.com>
Co-authored-by: Minemoder5000 <minemoder50000@gmail.com>
Co-authored-by: Spanky <scott@wearejacob.com>
Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: brainfood1183 <113240905+brainfood1183@users.noreply.github.com>
Co-authored-by: Deerstop <edainturner@gmail.com>
Co-authored-by: B_Kirill <153602297+B-Kirill@users.noreply.github.com>
Co-authored-by: archee1 <archee3@hotmail.co.uk>
Co-authored-by: Cojoke <83733158+Cojoke-dot@users.noreply.github.com>
Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com>
Co-authored-by: poklj <compgeek223@gmail.com>
Co-authored-by: Krunklehorn <42424291+Krunklehorn@users.noreply.github.com>
Co-authored-by: OnyxTheBrave <131422822+OnyxTheBrave@users.noreply.github.com>
Co-authored-by: UpAndLeaves <92269094+Alpha-Two@users.noreply.github.com>
Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com>
Co-authored-by: Zalycon <84675130+Zalycon@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com>
Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: Dmitry <57028746+dimm00n@users.noreply.github.com>
2025-05-24 11:00:17 +03:00

571 lines
22 KiB
C#

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, triggerHandContact: 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, triggerHandContact: 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, bool triggerHandContact = false) =>
TryEquip(uid, uid, itemUid, slot, silent, force, predicted, inventory, clothing, checkDoafter, triggerHandContact);
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, bool triggerHandContact = 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);
}
// If new gloves are equipped, trigger OnContactInteraction for held items
if (triggerHandContact && !((slotDefinition.SlotFlags & SlotFlags.GLOVES) == 0))
TriggerHandContactInteraction(target);
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(actor, attemptEvent, true);
if (attemptEvent.Cancelled)
{
reason = attemptEvent.Reason ?? reason;
return false;
}
var targetAttemptEvent = new IsEquippingTargetAttemptEvent(actor, target, itemUid, slotDefinition);
RaiseLocalEvent(target, targetAttemptEvent, true);
if (targetAttemptEvent.Cancelled)
{
reason = targetAttemptEvent.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,
bool triggerHandContact = false)
{
return TryUnequip(uid, uid, slot, silent, force, predicted, inventory, clothing, reparent, checkDoafter, triggerHandContact);
}
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,
bool triggerHandContact = false)
{
return TryUnequip(actor, target, slot, out _, silent, force, predicted, inventory, clothing, reparent, checkDoafter, triggerHandContact);
}
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,
bool triggerHandContact = false)
{
return TryUnequip(uid, uid, slot, out removedItem, silent, force, predicted, inventory, clothing, reparent, checkDoafter, triggerHandContact);
}
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,
bool triggerHandContact = 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,
bool triggerHandContact = 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);
}
// If gloves are unequipped, OnContactInteraction should trigger for held items
if (triggerHandContact && !((slotDefinition.SlotFlags & SlotFlags.GLOVES) == 0))
TriggerHandContactInteraction(target);
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(actor, attemptEvent, true);
if (attemptEvent.Cancelled)
{
reason = attemptEvent.Reason ?? reason;
return false;
}
var targetAttemptEvent = new IsUnequippingTargetAttemptEvent(actor, target, itemUid, slotDefinition);
RaiseLocalEvent(target, targetAttemptEvent, true);
if (targetAttemptEvent.Cancelled)
{
reason = targetAttemptEvent.Reason ?? reason;
return false;
}
var itemAttemptEvent = new BeingUnequippedAttemptEvent(actor, target, itemUid, slotDefinition);
RaiseLocalEvent(itemUid, itemAttemptEvent, true);
if (itemAttemptEvent.Cancelled)
{
reason = itemAttemptEvent.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;
}
public void TriggerHandContactInteraction(EntityUid uid)
{
foreach (var item in _handsSystem.EnumerateHeld(uid))
{
_interactionSystem.DoContactInteraction(uid, item);
}
}
}