Predict inventory slot interactions. (#6033)

This commit is contained in:
Leon Friedrich
2022-01-30 13:50:10 +13:00
committed by GitHub
parent c465715273
commit 47e597ca47
9 changed files with 138 additions and 140 deletions

View File

@@ -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);
public bool TryEquip(EntityUid uid, EntityUid itemUid, string slot, bool silent = false, bool force = false,
// attempt to perform some interaction
if (held != null && itemUid != null)
{
_interactionSystem.InteractUsing(actor, held.Value, itemUid.Value,
new EntityCoordinates(), predicted: true);
return;
}
// un-equip to hands
if (itemUid != null)
{
if (hands.CanPickupEntityToActiveHand(itemUid.Value) && TryUnequip(actor, ev.Slot, inventory: inventory))
hands.PutInHand(itemUid.Value, false);
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))
{
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, inventory, item);
TryEquip(uid, uid, itemUid, slot, silent, force, predicted, inventory, item);
public virtual bool TryEquip(EntityUid actor, EntityUid target, EntityUid itemUid, string slot, bool silent = false, bool force = false, InventoryComponent? inventory = null, SharedItemComponent? item = null)
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)