Add a component that inserts the held item when no interaction happens on the stored item (#29823)

* Insert when held item has no interaction with stored item

* Decouple inserting on failure

* Add component that stores the used entity when no interaction happened

* Add prediction
This commit is contained in:
ShadowCommander
2024-08-10 19:29:44 -07:00
committed by GitHub
parent d7c3ba8409
commit 2f77d0d4f3
5 changed files with 93 additions and 25 deletions

View File

@@ -918,11 +918,21 @@ namespace Content.Shared.Interaction
} }
/// <summary> /// <summary>
/// Uses a item/object on an entity /// Uses an item/object on an entity
/// 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 void InteractUsing( /// <param name="user">User doing the interaction.</param>
/// <param name="used">Item being used on the <paramref name="target"/>.</param>
/// <param name="target">Entity getting interacted with by the <paramref name="user"/> using the
/// <paramref name="used"/> entity.</param>
/// <param name="clickLocation">The location that the <paramref name="user"/> clicked.</param>
/// <param name="checkCanInteract">Whether to check that the <paramref name="user"/> can interact with the
/// <paramref name="target"/>.</param>
/// <param name="checkCanUse">Whether to check that the <paramref name="user"/> can use the
/// <paramref name="used"/> entity.</param>
/// <returns>True if the interaction was handled. Otherwise, false.</returns>
public bool InteractUsing(
EntityUid user, EntityUid user,
EntityUid used, EntityUid used,
EntityUid target, EntityUid target,
@@ -931,10 +941,10 @@ namespace Content.Shared.Interaction
bool checkCanUse = true) bool checkCanUse = true)
{ {
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target)) if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target))
return; return false;
if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user, used)) if (checkCanUse && !_actionBlockerSystem.CanUseHeldEntity(user, used))
return; return false;
_adminLogger.Add( _adminLogger.Add(
LogType.InteractUsing, LogType.InteractUsing,
@@ -942,7 +952,7 @@ namespace Content.Shared.Interaction
$"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target} using {ToPrettyString(used):used}"); $"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target} using {ToPrettyString(used):used}");
if (RangedInteractDoBefore(user, used, target, clickLocation, true)) if (RangedInteractDoBefore(user, used, target, clickLocation, true))
return; return true;
// 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);
@@ -951,15 +961,24 @@ namespace Content.Shared.Interaction
DoContactInteraction(user, target, interactUsingEvent); DoContactInteraction(user, target, interactUsingEvent);
// Contact interactions are currently only used for forensics, so we don't raise used -> target // Contact interactions are currently only used for forensics, so we don't raise used -> target
if (interactUsingEvent.Handled) if (interactUsingEvent.Handled)
return; return true;
InteractDoAfter(user, used, target, clickLocation, canReach: true); if (InteractDoAfter(user, used, target, clickLocation, canReach: true))
return true;
return false;
} }
/// <summary> /// <summary>
/// Used when clicking on an entity resulted in no other interaction. Used for low-priority interactions. /// Used when clicking on an entity resulted in no other interaction. Used for low-priority interactions.
/// </summary> /// </summary>
public void InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach) /// <param name="user"><inheritdoc cref="InteractUsing"/></param>
/// <param name="used"><inheritdoc cref="InteractUsing"/></param>
/// <param name="target"><inheritdoc cref="InteractUsing"/></param>
/// <param name="clickLocation"><inheritdoc cref="InteractUsing"/></param>
/// <param name="canReach">Whether the <paramref name="user"/> is in range of the <paramref name="target"/>.
/// </param>
/// <returns>True if the interaction was handled. Otherwise, false.</returns>
public bool InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach)
{ {
if (target is { Valid: false }) if (target is { Valid: false })
target = null; target = null;
@@ -974,10 +993,10 @@ namespace Content.Shared.Interaction
} }
if (afterInteractEvent.Handled) if (afterInteractEvent.Handled)
return; return true;
if (target == null) if (target == null)
return; return false;
var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach); var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(target.Value, afterInteractUsingEvent); RaiseLocalEvent(target.Value, afterInteractUsingEvent);
@@ -988,6 +1007,10 @@ namespace Content.Shared.Interaction
DoContactInteraction(user, target, afterInteractUsingEvent); DoContactInteraction(user, target, afterInteractUsingEvent);
// Contact interactions are currently only used for forensics, so we don't raise used -> target // Contact interactions are currently only used for forensics, so we don't raise used -> target
} }
if (afterInteractUsingEvent.Handled)
return true;
return false;
} }
#region ActivateItemInWorld #region ActivateItemInWorld

View File

@@ -0,0 +1,8 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Storage.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class StoreAfterFailedInteractComponent : Component
{
}

View File

@@ -22,6 +22,7 @@ using Content.Shared.Popups;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Storage.Components; using Content.Shared.Storage.Components;
using Content.Shared.Timing; using Content.Shared.Timing;
using Content.Shared.Storage.Events;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -366,7 +367,7 @@ public abstract class SharedStorageSystem : EntitySystem
if (HasComp<PlaceableSurfaceComponent>(uid)) if (HasComp<PlaceableSurfaceComponent>(uid))
return; return;
PlayerInsertHeldEntity(uid, args.User, storageComp); PlayerInsertHeldEntity((uid, storageComp), args.User);
// Always handle it, even if insertion fails. // Always handle it, even if insertion fails.
// We don't want to trigger any AfterInteract logic here. // We don't want to trigger any AfterInteract logic here.
// Example issue would be placing wires if item doesn't fit in backpack. // Example issue would be placing wires if item doesn't fit in backpack.
@@ -606,7 +607,15 @@ public abstract class SharedStorageSystem : EntitySystem
$"{ToPrettyString(player):player} is interacting with {ToPrettyString(item):item} while it is stored in {ToPrettyString(storage):storage} using {ToPrettyString(player.Comp.ActiveHandEntity):used}"); $"{ToPrettyString(player):player} is interacting with {ToPrettyString(item):item} while it is stored in {ToPrettyString(storage):storage} using {ToPrettyString(player.Comp.ActiveHandEntity):used}");
// Else, interact using the held item // Else, interact using the held item
_interactionSystem.InteractUsing(player, player.Comp.ActiveHandEntity.Value, item, Transform(item).Coordinates, checkCanInteract: false); if (_interactionSystem.InteractUsing(player,
player.Comp.ActiveHandEntity.Value,
item,
Transform(item).Coordinates,
checkCanInteract: false))
return;
var failedEv = new StorageInsertFailedEvent((storage, storage.Comp), (player, player.Comp));
RaiseLocalEvent(storage, ref failedEv);
} }
private void OnSetItemLocation(StorageSetItemLocationEvent msg, EntitySessionEventArgs args) private void OnSetItemLocation(StorageSetItemLocationEvent msg, EntitySessionEventArgs args)
@@ -988,35 +997,36 @@ public abstract class SharedStorageSystem : EntitySystem
/// <summary> /// <summary>
/// Inserts an entity into storage from the player's active hand /// Inserts an entity into storage from the player's active hand
/// </summary> /// </summary>
/// <param name="uid"></param> /// <param name="ent">The storage entity and component to insert into.</param>
/// <param name="player">The player to insert an entity from</param> /// <param name="player">The player and hands component to insert the held entity from.</param>
/// <param name="storageComp"></param> /// <returns>True if inserted, otherwise false.</returns>
/// <returns>true if inserted, false otherwise</returns> public bool PlayerInsertHeldEntity(Entity<StorageComponent?> ent, Entity<HandsComponent?> player)
public bool PlayerInsertHeldEntity(EntityUid uid, EntityUid player, StorageComponent? storageComp = null)
{ {
if (!Resolve(uid, ref storageComp) || !TryComp(player, out HandsComponent? hands) || hands.ActiveHandEntity == null) if (!Resolve(ent.Owner, ref ent.Comp)
|| !Resolve(player.Owner, ref player.Comp)
|| player.Comp.ActiveHandEntity == null)
return false; return false;
var toInsert = hands.ActiveHandEntity; var toInsert = player.Comp.ActiveHandEntity;
if (!CanInsert(uid, toInsert.Value, out var reason, storageComp)) if (!CanInsert(ent, toInsert.Value, out var reason, ent.Comp))
{ {
_popupSystem.PopupClient(Loc.GetString(reason ?? "comp-storage-cant-insert"), uid, player); _popupSystem.PopupClient(Loc.GetString(reason ?? "comp-storage-cant-insert"), ent, player);
return false; return false;
} }
if (!_sharedHandsSystem.CanDrop(player, toInsert.Value, hands)) if (!_sharedHandsSystem.CanDrop(player, toInsert.Value, player.Comp))
{ {
_popupSystem.PopupClient(Loc.GetString("comp-storage-cant-drop", ("entity", toInsert.Value)), uid, player); _popupSystem.PopupClient(Loc.GetString("comp-storage-cant-drop", ("entity", toInsert.Value)), ent, player);
return false; return false;
} }
return PlayerInsertEntityInWorld((uid, storageComp), player, toInsert.Value); return PlayerInsertEntityInWorld((ent, ent.Comp), player, toInsert.Value);
} }
/// <summary> /// <summary>
/// Inserts an Entity (<paramref name="toInsert"/>) in the world into storage, informing <paramref name="player"/> if it fails. /// Inserts an Entity (<paramref name="toInsert"/>) in the world into storage, informing <paramref name="player"/> if it fails.
/// <paramref name="toInsert"/> is *NOT* held, see <see cref="PlayerInsertHeldEntity(EntityUid,EntityUid,StorageComponent)"/>. /// <paramref name="toInsert"/> is *NOT* held, see <see cref="PlayerInsertHeldEntity(Entity{StorageComponent?},Entity{HandsComponent?})"/>.
/// </summary> /// </summary>
/// <param name="uid"></param> /// <param name="uid"></param>
/// <param name="player">The player to insert an entity with</param> /// <param name="player">The player to insert an entity with</param>

View File

@@ -0,0 +1,21 @@
using Content.Shared.Storage.Components;
using Content.Shared.Storage.Events;
namespace Content.Shared.Storage.EntitySystems;
public sealed class StoreAfterFailedInteractSystem : EntitySystem
{
[Dependency] private readonly SharedStorageSystem _storage = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StoreAfterFailedInteractComponent, StorageInsertFailedEvent>(OnStorageInsertFailed);
}
private void OnStorageInsertFailed(Entity<StoreAfterFailedInteractComponent> ent, ref StorageInsertFailedEvent args)
{
_storage.PlayerInsertHeldEntity(args.Storage, args.Player);
}
}

View File

@@ -0,0 +1,6 @@
using Content.Shared.Hands.Components;
namespace Content.Shared.Storage.Events;
[ByRefEvent]
public record struct StorageInsertFailedEvent(Entity<StorageComponent?> Storage, Entity<HandsComponent?> Player);