Predict inventory slot interactions. (#6033)
This commit is contained in:
@@ -70,20 +70,6 @@ namespace Content.Client.Inventory
|
|||||||
_config.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
|
_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)
|
private void OnDidUnequip(EntityUid uid, ClientInventoryComponent component, DidUnequipEvent args)
|
||||||
{
|
{
|
||||||
UpdateComponentUISlot(uid, args.Slot, null, component);
|
UpdateComponentUISlot(uid, args.Slot, null, component);
|
||||||
@@ -213,17 +199,15 @@ namespace Content.Client.Inventory
|
|||||||
private void HandleSlotButtonPressed(EntityUid uid, string slot, ItemSlotButton button,
|
private void HandleSlotButtonPressed(EntityUid uid, string slot, ItemSlotButton button,
|
||||||
GUIBoundKeyEventArgs args)
|
GUIBoundKeyEventArgs args)
|
||||||
{
|
{
|
||||||
if (TryGetSlotEntity(uid, slot, out var itemUid))
|
if (TryGetSlotEntity(uid, slot, out var itemUid) && _itemSlotManager.OnButtonPressed(args, itemUid.Value))
|
||||||
{
|
|
||||||
if (!_itemSlotManager.OnButtonPressed(args, itemUid.Value) && args.Function == EngineKeyFunctions.UIClick)
|
|
||||||
{
|
|
||||||
RaiseNetworkEvent(new UseSlotNetworkMessage(uid, slot));
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (args.Function != EngineKeyFunctions.UIClick) return;
|
if (args.Function != EngineKeyFunctions.UIClick)
|
||||||
TryEquipActiveHandTo(uid, slot);
|
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,
|
private bool TryGetUIElements(EntityUid uid, [NotNullWhen(true)] out DefaultWindow? invWindow,
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
using Content.Server.Hands.Components;
|
|
||||||
using Content.Server.Interaction;
|
|
||||||
using Content.Server.Storage.Components;
|
using Content.Server.Storage.Components;
|
||||||
using Content.Server.Temperature.Systems;
|
using Content.Server.Temperature.Systems;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Inventory.Events;
|
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;
|
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
|
||||||
|
|
||||||
namespace Content.Server.Inventory
|
namespace Content.Server.Inventory
|
||||||
{
|
{
|
||||||
class ServerInventorySystem : InventorySystem
|
class ServerInventorySystem : InventorySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -25,27 +17,7 @@ namespace Content.Server.Inventory
|
|||||||
SubscribeLocalEvent<InventoryComponent, LowPressureEvent>(RelayInventoryEvent);
|
SubscribeLocalEvent<InventoryComponent, LowPressureEvent>(RelayInventoryEvent);
|
||||||
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
|
SubscribeLocalEvent<InventoryComponent, ModifyChangedTemperatureEvent>(RelayInventoryEvent);
|
||||||
|
|
||||||
SubscribeNetworkEvent<TryEquipNetworkMessage>(OnNetworkEquip);
|
|
||||||
SubscribeNetworkEvent<TryUnequipNetworkMessage>(OnNetworkUnequip);
|
|
||||||
SubscribeNetworkEvent<OpenSlotStorageNetworkMessage>(OnOpenSlotStorage);
|
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)
|
private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev)
|
||||||
@@ -55,15 +27,5 @@ namespace Content.Server.Inventory
|
|||||||
storageComponent.OpenStorageUI(ev.Uid);
|
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)
|
if (slot.Item != null)
|
||||||
hands.TryPutInAnyHand(slot.Item.Value);
|
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;
|
args.Handled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,12 +70,19 @@ namespace Content.Shared.Interaction
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public EntityCoordinates ClickLocation { get; }
|
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;
|
User = user;
|
||||||
Used = used;
|
Used = used;
|
||||||
Target = target;
|
Target = target;
|
||||||
ClickLocation = clickLocation;
|
ClickLocation = clickLocation;
|
||||||
|
Predicted = predicted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ namespace Content.Shared.Interaction
|
|||||||
/// Finds components with the InteractUsing interface and calls their function
|
/// Finds components with the InteractUsing interface and calls their function
|
||||||
/// NOTE: Does not have an InRangeUnobstructed check
|
/// NOTE: Does not have an InRangeUnobstructed check
|
||||||
/// </summary>
|
/// </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))
|
if (!_actionBlockerSystem.CanInteract(user))
|
||||||
return;
|
return;
|
||||||
@@ -541,7 +541,7 @@ namespace Content.Shared.Interaction
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// all interactions should only happen when in range / unobstructed, so no range check is needed
|
// 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);
|
RaiseLocalEvent(target, interactUsingEvent);
|
||||||
if (interactUsingEvent.Handled)
|
if (interactUsingEvent.Handled)
|
||||||
return;
|
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]
|
[NetSerializable, Serializable]
|
||||||
public class UseSlotNetworkMessage : EntityEventArgs
|
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 readonly string Slot;
|
||||||
|
|
||||||
public UseSlotNetworkMessage(EntityUid uid, string slot)
|
public UseSlotNetworkMessage(string slot)
|
||||||
{
|
{
|
||||||
Uid = uid;
|
|
||||||
Slot = slot;
|
Slot = slot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.Interaction.Helpers;
|
||||||
using Content.Shared.Inventory.Events;
|
using Content.Shared.Inventory.Events;
|
||||||
using Content.Shared.Item;
|
using Content.Shared.Item;
|
||||||
using Content.Shared.Movement.EntitySystems;
|
using Content.Shared.Movement.EntitySystems;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Strip.Components;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Inventory;
|
namespace Content.Shared.Inventory;
|
||||||
|
|
||||||
@@ -18,12 +23,16 @@ public abstract partial class InventorySystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
|
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
|
||||||
|
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
|
||||||
private void InitializeEquip()
|
private void InitializeEquip()
|
||||||
{
|
{
|
||||||
//these events ensure that the client also gets its proper events raised when getting its containerstate updated
|
//these events ensure that the client also gets its proper events raised when getting its containerstate updated
|
||||||
SubscribeLocalEvent<InventoryComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
SubscribeLocalEvent<InventoryComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
|
||||||
SubscribeLocalEvent<InventoryComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
SubscribeLocalEvent<InventoryComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
|
||||||
|
|
||||||
|
SubscribeAllEvent<UseSlotNetworkMessage>(OnUseSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEntRemoved(EntityUid uid, InventoryComponent component, EntRemovedFromContainerMessage args)
|
private void OnEntRemoved(EntityUid uid, InventoryComponent component, EntRemovedFromContainerMessage args)
|
||||||
@@ -50,50 +59,98 @@ public abstract partial class InventorySystem
|
|||||||
RaiseLocalEvent(args.Entity, gotEquippedEvent);
|
RaiseLocalEvent(args.Entity, gotEquippedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryEquipActiveHandTo(EntityUid uid, string slot, bool silent = false, bool force = false,
|
/// <summary>
|
||||||
InventoryComponent? component = null, SharedHandsComponent? hands = null)
|
/// 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))
|
if (eventArgs.SenderSession.AttachedEntity is not EntityUid { Valid: true } actor)
|
||||||
return false;
|
return;
|
||||||
|
|
||||||
if (!hands.TryGetActiveHeldEntity(out var heldEntity))
|
if (!TryComp(actor, out InventoryComponent? inventory) || !TryComp<SharedHandsComponent>(actor, out var hands))
|
||||||
return false;
|
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,
|
// un-equip to hands
|
||||||
InventoryComponent? inventory = null, SharedItemComponent? item = null) =>
|
if (itemUid != null)
|
||||||
TryEquip(uid, uid, itemUid, slot, silent, force, inventory, item);
|
{
|
||||||
|
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 (!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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryGetSlotContainer(target, slot, out var slotContainer, out var slotDefinition, inventory))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!force && !CanEquip(actor, target, itemUid, slot, out var reason, slotDefinition, inventory, item))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!slotContainer.Insert(itemUid))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!silent && item.EquipSound != null)
|
if(!silent && item.EquipSound != null && _gameTiming.IsFirstTimePredicted)
|
||||||
SoundSystem.Play(Filter.Pvs(target), item.EquipSound.GetSound(), target, AudioParams.Default.WithVolume(-2f));
|
{
|
||||||
|
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();
|
inventory.Dirty();
|
||||||
|
|
||||||
@@ -102,6 +159,28 @@ public abstract partial class InventorySystem
|
|||||||
return true;
|
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,
|
public bool CanEquip(EntityUid uid, EntityUid itemUid, string slot, [NotNullWhen(false)] out string? reason,
|
||||||
SlotDefinition? slotDefinition = null, InventoryComponent? inventory = null,
|
SlotDefinition? slotDefinition = null, InventoryComponent? inventory = null,
|
||||||
SharedItemComponent? item = null) =>
|
SharedItemComponent? item = null) =>
|
||||||
@@ -125,6 +204,12 @@ public abstract partial class InventorySystem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CanAccess(actor, target, itemUid))
|
||||||
|
{
|
||||||
|
reason = "interaction-system-user-interaction-cannot-reach";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var attemptEvent = new IsEquippingAttemptEvent(actor, target, itemUid, slotDefinition);
|
var attemptEvent = new IsEquippingAttemptEvent(actor, target, itemUid, slotDefinition);
|
||||||
RaiseLocalEvent(target, attemptEvent);
|
RaiseLocalEvent(target, attemptEvent);
|
||||||
if (attemptEvent.Cancelled)
|
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,
|
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);
|
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)
|
bool force = false, InventoryComponent? inventory = null)
|
||||||
{
|
{
|
||||||
removedItem = null;
|
removedItem = null;
|
||||||
if (!Resolve(target, ref inventory, false))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryGetSlotContainer(target, slot, out var slotContainer, out var slotDefinition, inventory))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +275,8 @@ public abstract partial class InventorySystem
|
|||||||
|
|
||||||
if (!force && !CanUnequip(actor, target, slot, out var reason, slotContainer, slotDefinition, inventory))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,6 +337,13 @@ public abstract partial class InventorySystem
|
|||||||
|
|
||||||
var itemUid = containerSlot.ContainedEntity.Value;
|
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);
|
var attemptEvent = new IsUnequippingAttemptEvent(actor, target, itemUid, slotDefinition);
|
||||||
RaiseLocalEvent(target, attemptEvent);
|
RaiseLocalEvent(target, attemptEvent);
|
||||||
if (attemptEvent.Cancelled)
|
if (attemptEvent.Cancelled)
|
||||||
|
|||||||
Reference in New Issue
Block a user