Predict inventory slot interactions. (#6033)
This commit is contained in:
@@ -70,20 +70,6 @@ namespace Content.Client.Inventory
|
||||
_config.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
|
||||
}
|
||||
|
||||
public override bool TryEquip(EntityUid actor, EntityUid target, EntityUid itemUid, string slot, bool silent = false, bool force = false,
|
||||
InventoryComponent? inventory = null, SharedItemComponent? item = null)
|
||||
{
|
||||
if(!target.IsClientSide() && !actor.IsClientSide() && !itemUid.IsClientSide()) RaiseNetworkEvent(new TryEquipNetworkMessage(actor, target, itemUid, slot, silent, force));
|
||||
return base.TryEquip(actor, target, itemUid, slot, silent, force, inventory, item);
|
||||
}
|
||||
|
||||
public override bool TryUnequip(EntityUid actor, EntityUid target, string slot, [NotNullWhen(true)] out EntityUid? removedItem, bool silent = false, bool force = false,
|
||||
InventoryComponent? inventory = null)
|
||||
{
|
||||
if(!target.IsClientSide() && !actor.IsClientSide()) RaiseNetworkEvent(new TryUnequipNetworkMessage(actor, target, slot, silent, force));
|
||||
return base.TryUnequip(actor, target, slot, out removedItem, silent, force, inventory);
|
||||
}
|
||||
|
||||
private void OnDidUnequip(EntityUid uid, ClientInventoryComponent component, DidUnequipEvent args)
|
||||
{
|
||||
UpdateComponentUISlot(uid, args.Slot, null, component);
|
||||
@@ -213,17 +199,15 @@ namespace Content.Client.Inventory
|
||||
private void HandleSlotButtonPressed(EntityUid uid, string slot, ItemSlotButton button,
|
||||
GUIBoundKeyEventArgs args)
|
||||
{
|
||||
if (TryGetSlotEntity(uid, slot, out var itemUid))
|
||||
{
|
||||
if (!_itemSlotManager.OnButtonPressed(args, itemUid.Value) && args.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
RaiseNetworkEvent(new UseSlotNetworkMessage(uid, slot));
|
||||
}
|
||||
if (TryGetSlotEntity(uid, slot, out var itemUid) && _itemSlotManager.OnButtonPressed(args, itemUid.Value))
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function != EngineKeyFunctions.UIClick) return;
|
||||
TryEquipActiveHandTo(uid, slot);
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
// only raise event if either itemUid is not null, or the user is holding something
|
||||
if (itemUid != null || TryComp(uid, out SharedHandsComponent? hands) && hands.TryGetActiveHeldEntity(out _))
|
||||
EntityManager.RaisePredictiveEvent(new UseSlotNetworkMessage(slot));
|
||||
}
|
||||
|
||||
private bool TryGetUIElements(EntityUid uid, [NotNullWhen(true)] out DefaultWindow? invWindow,
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Temperature.Systems;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
|
||||
|
||||
namespace Content.Server.Inventory
|
||||
{
|
||||
class ServerInventorySystem : InventorySystem
|
||||
{
|
||||
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -25,27 +17,7 @@ namespace Content.Server.Inventory
|
||||
SubscribeLocalEvent<InventoryComponent, LowPressureEvent>(RelayInventoryEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
|
||||
|
||||
SubscribeNetworkEvent<TryEquipNetworkMessage>(OnNetworkEquip);
|
||||
SubscribeNetworkEvent<TryUnequipNetworkMessage>(OnNetworkUnequip);
|
||||
SubscribeNetworkEvent<OpenSlotStorageNetworkMessage>(OnOpenSlotStorage);
|
||||
SubscribeNetworkEvent<UseSlotNetworkMessage>(OnUseSlot);
|
||||
}
|
||||
|
||||
private void OnUseSlot(UseSlotNetworkMessage ev)
|
||||
{
|
||||
if (!TryComp<HandsComponent>(ev.Uid, out var hands) || !TryGetSlotEntity(ev.Uid, ev.Slot, out var itemUid))
|
||||
return;
|
||||
|
||||
var activeHand = hands.GetActiveHandItem;
|
||||
if (activeHand != null)
|
||||
{
|
||||
_interactionSystem.InteractUsing(ev.Uid, activeHand.Owner, itemUid.Value,
|
||||
new EntityCoordinates());
|
||||
}
|
||||
else if (TryUnequip(ev.Uid, ev.Slot))
|
||||
{
|
||||
hands.PutInHand(itemUid.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev)
|
||||
@@ -55,15 +27,5 @@ namespace Content.Server.Inventory
|
||||
storageComponent.OpenStorageUI(ev.Uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkUnequip(TryUnequipNetworkMessage ev)
|
||||
{
|
||||
TryUnequip(ev.Actor, ev.Target, ev.Slot, ev.Silent, ev.Force);
|
||||
}
|
||||
|
||||
private void OnNetworkEquip(TryEquipNetworkMessage ev)
|
||||
{
|
||||
TryEquip(ev.Actor, ev.Target, ev.ItemUid, ev.Slot, ev.Silent, ev.Force);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (slot.Item != null)
|
||||
hands.TryPutInAnyHand(slot.Item.Value);
|
||||
|
||||
Insert(uid, slot, args.Used, args.User);
|
||||
Insert(uid, slot, args.Used, args.User, excludeUserAudio: args.Predicted);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,12 +70,19 @@ namespace Content.Shared.Interaction
|
||||
/// </summary>
|
||||
public EntityCoordinates ClickLocation { get; }
|
||||
|
||||
public InteractUsingEvent(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation)
|
||||
/// <summary>
|
||||
/// If true, this prediction is also being predicted client-side. So care has to be taken to avoid audio
|
||||
/// duplication.
|
||||
/// </summary>
|
||||
public bool Predicted { get; }
|
||||
|
||||
public InteractUsingEvent(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation, bool predicted = false)
|
||||
{
|
||||
User = user;
|
||||
Used = used;
|
||||
Target = target;
|
||||
ClickLocation = clickLocation;
|
||||
Predicted = predicted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +532,7 @@ namespace Content.Shared.Interaction
|
||||
/// Finds components with the InteractUsing interface and calls their function
|
||||
/// NOTE: Does not have an InRangeUnobstructed check
|
||||
/// </summary>
|
||||
public async Task InteractUsing(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation)
|
||||
public async Task InteractUsing(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation, bool predicted = false)
|
||||
{
|
||||
if (!_actionBlockerSystem.CanInteract(user))
|
||||
return;
|
||||
@@ -541,7 +541,7 @@ namespace Content.Shared.Interaction
|
||||
return;
|
||||
|
||||
// all interactions should only happen when in range / unobstructed, so no range check is needed
|
||||
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
|
||||
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation, predicted);
|
||||
RaiseLocalEvent(target, interactUsingEvent);
|
||||
if (interactUsingEvent.Handled)
|
||||
return;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Inventory.Events;
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public class TryEquipNetworkMessage : EntityEventArgs
|
||||
{
|
||||
public readonly EntityUid Actor;
|
||||
public readonly EntityUid Target;
|
||||
public readonly EntityUid ItemUid;
|
||||
public readonly string Slot;
|
||||
public readonly bool Silent;
|
||||
public readonly bool Force;
|
||||
|
||||
public TryEquipNetworkMessage(EntityUid actor, EntityUid target, EntityUid itemUid, string slot, bool silent, bool force)
|
||||
{
|
||||
Actor = actor;
|
||||
Target = target;
|
||||
ItemUid = itemUid;
|
||||
Slot = slot;
|
||||
Silent = silent;
|
||||
Force = force;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Inventory.Events;
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public class TryUnequipNetworkMessage : EntityEventArgs
|
||||
{
|
||||
public readonly EntityUid Actor;
|
||||
public readonly EntityUid Target;
|
||||
public readonly string Slot;
|
||||
public readonly bool Silent;
|
||||
public readonly bool Force;
|
||||
|
||||
public TryUnequipNetworkMessage(EntityUid actor, EntityUid target, string slot, bool silent, bool force)
|
||||
{
|
||||
Actor = actor;
|
||||
Target = target;
|
||||
Slot = slot;
|
||||
Silent = silent;
|
||||
Force = force;
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,12 @@ namespace Content.Shared.Inventory.Events;
|
||||
[NetSerializable, Serializable]
|
||||
public class UseSlotNetworkMessage : EntityEventArgs
|
||||
{
|
||||
public readonly EntityUid Uid;
|
||||
// The slot-owner is implicitly the client that is sending this message.
|
||||
// Otherwise clients could start forcefully undressing other clients.
|
||||
public readonly string Slot;
|
||||
|
||||
public UseSlotNetworkMessage(EntityUid uid, string slot)
|
||||
public UseSlotNetworkMessage(string slot)
|
||||
{
|
||||
Uid = uid;
|
||||
Slot = slot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Movement.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Strip.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Inventory;
|
||||
|
||||
@@ -18,12 +23,16 @@ 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 IGameTiming _gameTiming = default!;
|
||||
|
||||
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)
|
||||
@@ -50,50 +59,98 @@ public abstract partial class InventorySystem
|
||||
RaiseLocalEvent(args.Entity, gotEquippedEvent);
|
||||
}
|
||||
|
||||
public bool TryEquipActiveHandTo(EntityUid uid, string slot, bool silent = false, bool force = false,
|
||||
InventoryComponent? component = null, SharedHandsComponent? hands = null)
|
||||
/// <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 (!Resolve(uid, ref component, false) || !Resolve(uid, ref hands, false))
|
||||
return false;
|
||||
if (eventArgs.SenderSession.AttachedEntity is not EntityUid { Valid: true } actor)
|
||||
return;
|
||||
|
||||
if (!hands.TryGetActiveHeldEntity(out var heldEntity))
|
||||
return false;
|
||||
if (!TryComp(actor, out InventoryComponent? inventory) || !TryComp<SharedHandsComponent>(actor, out var hands))
|
||||
return;
|
||||
|
||||
return TryEquip(uid, heldEntity.Value, slot, silent, force, component);
|
||||
hands.TryGetActiveHeldEntity(out var held);
|
||||
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,
|
||||
new EntityCoordinates(), predicted: true);
|
||||
return;
|
||||
}
|
||||
|
||||
public bool TryEquip(EntityUid uid, EntityUid itemUid, string slot, bool silent = false, bool force = false,
|
||||
InventoryComponent? inventory = null, SharedItemComponent? item = null) =>
|
||||
TryEquip(uid, uid, itemUid, slot, silent, force, inventory, item);
|
||||
// un-equip to hands
|
||||
if (itemUid != null)
|
||||
{
|
||||
if (hands.CanPickupEntityToActiveHand(itemUid.Value) && TryUnequip(actor, ev.Slot, inventory: inventory))
|
||||
hands.PutInHand(itemUid.Value, false);
|
||||
return;
|
||||
}
|
||||
|
||||
public virtual bool TryEquip(EntityUid actor, EntityUid target, EntityUid itemUid, string slot, bool silent = false, bool force = false, InventoryComponent? inventory = null, SharedItemComponent? item = null)
|
||||
// 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))
|
||||
{
|
||||
if (_gameTiming.IsFirstTimePredicted)
|
||||
_popup.PopupCursor(Loc.GetString(reason), Filter.Local());
|
||||
return;
|
||||
}
|
||||
|
||||
if (hands.TryDropNoInteraction())
|
||||
TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory);
|
||||
}
|
||||
|
||||
public bool TryEquip(EntityUid uid, EntityUid itemUid, string slot, bool silent = false, bool force = false, bool predicted = false,
|
||||
InventoryComponent? inventory = null, SharedItemComponent? item = null) =>
|
||||
TryEquip(uid, uid, itemUid, slot, silent, force, predicted, inventory, item);
|
||||
|
||||
public bool TryEquip(EntityUid actor, EntityUid target, EntityUid itemUid, string slot, bool silent = false, bool force = false, bool predicted = false,
|
||||
InventoryComponent? inventory = null, SharedItemComponent? item = null)
|
||||
{
|
||||
if (!Resolve(target, ref inventory, false) || !Resolve(itemUid, ref item, false))
|
||||
{
|
||||
if(!silent) _popup.PopupCursor(Loc.GetString("inventory-component-can-equip-cannot"), Filter.Local());
|
||||
if(!silent && _gameTiming.IsFirstTimePredicted)
|
||||
_popup.PopupCursor(Loc.GetString("inventory-component-can-equip-cannot"), Filter.Local());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetSlotContainer(target, slot, out var slotContainer, out var slotDefinition, inventory))
|
||||
{
|
||||
if(!silent) _popup.PopupCursor(Loc.GetString("inventory-component-can-equip-cannot"), Filter.Local());
|
||||
if(!silent && _gameTiming.IsFirstTimePredicted)
|
||||
_popup.PopupCursor(Loc.GetString("inventory-component-can-equip-cannot"), Filter.Local());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!force && !CanEquip(actor, target, itemUid, slot, out var reason, slotDefinition, inventory, item))
|
||||
{
|
||||
if(!silent) _popup.PopupCursor(Loc.GetString(reason), Filter.Local());
|
||||
if(!silent && _gameTiming.IsFirstTimePredicted)
|
||||
_popup.PopupCursor(Loc.GetString(reason), Filter.Local());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!slotContainer.Insert(itemUid))
|
||||
{
|
||||
if(!silent) _popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"), Filter.Local());
|
||||
if(!silent && _gameTiming.IsFirstTimePredicted)
|
||||
_popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"), Filter.Local());
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!silent && item.EquipSound != null)
|
||||
SoundSystem.Play(Filter.Pvs(target), item.EquipSound.GetSound(), target, AudioParams.Default.WithVolume(-2f));
|
||||
if(!silent && item.EquipSound != null && _gameTiming.IsFirstTimePredicted)
|
||||
{
|
||||
var filter = Filter.Pvs(target);
|
||||
|
||||
// don't play double audio for predicted interactions
|
||||
if (predicted)
|
||||
filter.RemoveWhereAttachedEntity(entity => entity == actor);
|
||||
|
||||
SoundSystem.Play(filter, item.EquipSound.GetSound(), target, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
|
||||
inventory.Dirty();
|
||||
|
||||
@@ -102,6 +159,28 @@ public abstract partial class InventorySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanAccess(EntityUid actor, EntityUid target, EntityUid itemUid)
|
||||
{
|
||||
// Can the actor reach the target?
|
||||
if (actor != target && !( actor.InRangeUnobstructed(target) && actor.IsInSameOrParentContainer(target)))
|
||||
return false;
|
||||
|
||||
// Can the actor reach the item?
|
||||
if (actor.InRangeUnobstructed(itemUid) && actor.IsInSameOrParentContainer(itemUid))
|
||||
return true;
|
||||
|
||||
// Is the item in an open storage UI, i.e., is the user quick-equipping from an open backpack?
|
||||
if (_interactionSystem.CanAccessViaStorage(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. so lets just check if they **could** open the stripping UI.
|
||||
// Note that this doesn't check that the item is equipped by the target, as this is done elsewhere.
|
||||
return actor != target
|
||||
&& TryComp(target, out SharedStrippableComponent? strip)
|
||||
&& strip.CanBeStripped(actor);
|
||||
}
|
||||
|
||||
public bool CanEquip(EntityUid uid, EntityUid itemUid, string slot, [NotNullWhen(false)] out string? reason,
|
||||
SlotDefinition? slotDefinition = null, InventoryComponent? inventory = null,
|
||||
SharedItemComponent? item = null) =>
|
||||
@@ -125,6 +204,12 @@ public abstract partial class InventorySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanAccess(actor, target, itemUid))
|
||||
{
|
||||
reason = "interaction-system-user-interaction-cannot-reach";
|
||||
return false;
|
||||
}
|
||||
|
||||
var attemptEvent = new IsEquippingAttemptEvent(actor, target, itemUid, slotDefinition);
|
||||
RaiseLocalEvent(target, attemptEvent);
|
||||
if (attemptEvent.Cancelled)
|
||||
@@ -166,19 +251,21 @@ public abstract partial class InventorySystem
|
||||
public bool TryUnequip(EntityUid uid, string slot, [NotNullWhen(true)] out EntityUid? removedItem, bool silent = false, bool force = false,
|
||||
InventoryComponent? inventory = null) => TryUnequip(uid, uid, slot, out removedItem, silent, force, inventory);
|
||||
|
||||
public virtual bool TryUnequip(EntityUid actor, EntityUid target, string slot, [NotNullWhen(true)] out EntityUid? removedItem, bool silent = false,
|
||||
public bool TryUnequip(EntityUid actor, EntityUid target, string slot, [NotNullWhen(true)] out EntityUid? removedItem, bool silent = false,
|
||||
bool force = false, InventoryComponent? inventory = null)
|
||||
{
|
||||
removedItem = null;
|
||||
if (!Resolve(target, ref inventory, false))
|
||||
{
|
||||
if(!silent) _popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"), Filter.Local());
|
||||
if(!silent && _gameTiming.IsFirstTimePredicted)
|
||||
_popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"), Filter.Local());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetSlotContainer(target, slot, out var slotContainer, out var slotDefinition, inventory))
|
||||
{
|
||||
if(!silent) _popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"), Filter.Local());
|
||||
if(!silent && _gameTiming.IsFirstTimePredicted)
|
||||
_popup.PopupCursor(Loc.GetString("inventory-component-can-unequip-cannot"), Filter.Local());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -188,7 +275,8 @@ public abstract partial class InventorySystem
|
||||
|
||||
if (!force && !CanUnequip(actor, target, slot, out var reason, slotContainer, slotDefinition, inventory))
|
||||
{
|
||||
if(!silent) _popup.PopupCursor(Loc.GetString(reason), Filter.Local());
|
||||
if(!silent && _gameTiming.IsFirstTimePredicted)
|
||||
_popup.PopupCursor(Loc.GetString(reason), Filter.Local());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -249,6 +337,13 @@ public abstract partial class InventorySystem
|
||||
|
||||
var itemUid = containerSlot.ContainedEntity.Value;
|
||||
|
||||
// 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);
|
||||
if (attemptEvent.Cancelled)
|
||||
|
||||
Reference in New Issue
Block a user