Enable verbs for items in storage and prevent orphaned nested UIs (#5512)
This commit is contained in:
@@ -6,8 +6,6 @@ using Content.Client.Verbs;
|
|||||||
using Content.Client.Viewport;
|
using Content.Client.Viewport;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Content.Shared.Interaction.Helpers;
|
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.Input;
|
using Robust.Client.Input;
|
||||||
@@ -121,8 +119,12 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do some other server-side interaction?
|
// do some other server-side interaction?
|
||||||
if (args.Function == EngineKeyFunctions.Use || args.Function == ContentKeyFunctions.AltActivateItemInWorld || args.Function == ContentKeyFunctions.Point ||
|
if (args.Function == EngineKeyFunctions.Use ||
|
||||||
args.Function == ContentKeyFunctions.TryPullObject || args.Function == ContentKeyFunctions.MovePulledObject)
|
args.Function == ContentKeyFunctions.ActivateItemInWorld ||
|
||||||
|
args.Function == ContentKeyFunctions.AltActivateItemInWorld ||
|
||||||
|
args.Function == ContentKeyFunctions.Point ||
|
||||||
|
args.Function == ContentKeyFunctions.TryPullObject ||
|
||||||
|
args.Function == ContentKeyFunctions.MovePulledObject)
|
||||||
{
|
{
|
||||||
var inputSys = _systemManager.GetEntitySystem<InputSystem>();
|
var inputSys = _systemManager.GetEntitySystem<InputSystem>();
|
||||||
|
|
||||||
@@ -142,11 +144,6 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
args.Handle();
|
args.Handle();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_itemSlotManager.OnButtonPressed(args, entity))
|
|
||||||
{
|
|
||||||
_verbSystem.CloseAllMenus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HandleOpenEntityMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
private bool HandleOpenEntityMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
|
using Content.Client.Storage;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Client.Interactable
|
namespace Content.Client.Interactable
|
||||||
{
|
{
|
||||||
public sealed class InteractionSystem : SharedInteractionSystem
|
public sealed class InteractionSystem : SharedInteractionSystem
|
||||||
{
|
{
|
||||||
|
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||||
|
{
|
||||||
|
if (!EntityManager.TryGetEntity(target, out var entity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!entity.TryGetContainer(out var container))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!EntityManager.TryGetComponent(container.Owner.Uid, out ClientStorageComponent storage))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
|
||||||
|
return storage.UIOpen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace Content.Client.Storage
|
|||||||
private int StorageSizeUsed;
|
private int StorageSizeUsed;
|
||||||
private int StorageCapacityMax;
|
private int StorageCapacityMax;
|
||||||
private StorageWindow? _window;
|
private StorageWindow? _window;
|
||||||
|
public bool UIOpen => _window?.IsOpen ?? false;
|
||||||
|
|
||||||
public override IReadOnlyList<IEntity> StoredEntities => _storedEntities;
|
public override IReadOnlyList<IEntity> StoredEntities => _storedEntities;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Content.Server.CombatMode;
|
|||||||
using Content.Server.Hands.Components;
|
using Content.Server.Hands.Components;
|
||||||
using Content.Server.Items;
|
using Content.Server.Items;
|
||||||
using Content.Server.Pulling;
|
using Content.Server.Pulling;
|
||||||
|
using Content.Server.Storage.Components;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
@@ -98,6 +99,27 @@ namespace Content.Server.Interaction
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||||
|
{
|
||||||
|
if (!EntityManager.TryGetEntity(target, out var entity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!entity.TryGetContainer(out var container))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!EntityManager.TryGetComponent(container.Owner.Uid, out ServerStorageComponent storage))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (storage.Storage?.ID != container.ID)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!EntityManager.TryGetComponent(user, out ActorComponent actor))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
|
||||||
|
return storage.SubscribedSessions.Contains(actor.PlayerSession);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -188,9 +210,6 @@ namespace Content.Server.Interaction
|
|||||||
if (!EntityManager.TryGetEntity(uid, out var used))
|
if (!EntityManager.TryGetEntity(uid, out var used))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (user.IsInContainer())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
InteractionActivate(user, used);
|
InteractionActivate(user, used);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -286,15 +305,13 @@ namespace Content.Server.Interaction
|
|||||||
/// <param name="altInteract">Whether to use default or alternative interactions (usually as a result of
|
/// <param name="altInteract">Whether to use default or alternative interactions (usually as a result of
|
||||||
/// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat
|
/// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat
|
||||||
/// interaction. Having an item in the active hand also disables alternative interactions.</param>
|
/// interaction. Having an item in the active hand also disables alternative interactions.</param>
|
||||||
public async void UserInteraction(IEntity user, EntityCoordinates coordinates, EntityUid clickedUid, bool altInteract = false )
|
public async void UserInteraction(IEntity user, EntityCoordinates coordinates, EntityUid clickedUid, bool altInteract = false)
|
||||||
{
|
{
|
||||||
// TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms?
|
// TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms?
|
||||||
if (!altInteract && user.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
|
if (!altInteract && user.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
|
||||||
{
|
{
|
||||||
|
|
||||||
DoAttack(user, coordinates, false, clickedUid);
|
DoAttack(user, coordinates, false, clickedUid);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ValidateInteractAndFace(user, coordinates))
|
if (!ValidateInteractAndFace(user, coordinates))
|
||||||
@@ -307,7 +324,8 @@ namespace Content.Server.Interaction
|
|||||||
EntityManager.TryGetEntity(clickedUid, out var target);
|
EntityManager.TryGetEntity(clickedUid, out var target);
|
||||||
|
|
||||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||||
if (target != null && !user.IsInSameOrParentContainer(target))
|
// This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
|
||||||
|
if (target != null && !user.IsInSameOrParentContainer(target) && !CanAccessViaStorage(user.Uid, target.Uid))
|
||||||
{
|
{
|
||||||
Logger.WarningS("system.interaction",
|
Logger.WarningS("system.interaction",
|
||||||
$"User entity named {user.Name} clicked on object {target.Name} that isn't the parent, child, or in the same container");
|
$"User entity named {user.Name} clicked on object {target.Name} that isn't the parent, child, or in the same container");
|
||||||
@@ -449,7 +467,7 @@ namespace Content.Server.Interaction
|
|||||||
EntityManager.TryGetEntity(targetUid, out targetEnt);
|
EntityManager.TryGetEntity(targetUid, out targetEnt);
|
||||||
|
|
||||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||||
if (targetEnt != null && !user.IsInSameOrParentContainer(targetEnt))
|
if (targetEnt != null && !user.IsInSameOrParentContainer(targetEnt) && !CanAccessViaStorage(user.Uid, targetEnt.Uid))
|
||||||
{
|
{
|
||||||
Logger.WarningS("system.interaction",
|
Logger.WarningS("system.interaction",
|
||||||
$"User entity named {user.Name} clicked on object {targetEnt.Name} that isn't the parent, child, or in the same container");
|
$"User entity named {user.Name} clicked on object {targetEnt.Name} that isn't the parent, child, or in the same container");
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ using Robust.Shared.Network;
|
|||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Players;
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
using Robust.Shared.Serialization.Manager.Attributes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.Storage.Components
|
namespace Content.Server.Storage.Components
|
||||||
@@ -47,7 +48,8 @@ namespace Content.Server.Storage.Components
|
|||||||
|
|
||||||
private const string LoggerName = "Storage";
|
private const string LoggerName = "Storage";
|
||||||
|
|
||||||
private Container? _storage;
|
public Container? Storage;
|
||||||
|
|
||||||
private readonly Dictionary<IEntity, int> _sizeCache = new();
|
private readonly Dictionary<IEntity, int> _sizeCache = new();
|
||||||
|
|
||||||
[DataField("occludesLight")]
|
[DataField("occludesLight")]
|
||||||
@@ -77,7 +79,7 @@ namespace Content.Server.Storage.Components
|
|||||||
public SoundSpecifier StorageSoundCollection { get; set; } = new SoundCollectionSpecifier("storageRustle");
|
public SoundSpecifier StorageSoundCollection { get; set; } = new SoundCollectionSpecifier("storageRustle");
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public override IReadOnlyList<IEntity>? StoredEntities => _storage?.ContainedEntities;
|
public override IReadOnlyList<IEntity>? StoredEntities => Storage?.ContainedEntities;
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
public bool OccludesLight
|
public bool OccludesLight
|
||||||
@@ -86,7 +88,7 @@ namespace Content.Server.Storage.Components
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
_occludesLight = value;
|
_occludesLight = value;
|
||||||
if (_storage != null) _storage.OccludesLight = value;
|
if (Storage != null) Storage.OccludesLight = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,12 +122,12 @@ namespace Content.Server.Storage.Components
|
|||||||
{
|
{
|
||||||
_storageUsed = 0;
|
_storageUsed = 0;
|
||||||
|
|
||||||
if (_storage == null)
|
if (Storage == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entity in _storage.ContainedEntities)
|
foreach (var entity in Storage.ContainedEntities)
|
||||||
{
|
{
|
||||||
var item = entity.GetComponent<SharedItemComponent>();
|
var item = entity.GetComponent<SharedItemComponent>();
|
||||||
_storageUsed += item.Size;
|
_storageUsed += item.Size;
|
||||||
@@ -173,18 +175,18 @@ namespace Content.Server.Storage.Components
|
|||||||
/// <returns>true if the entity was inserted, false otherwise</returns>
|
/// <returns>true if the entity was inserted, false otherwise</returns>
|
||||||
public bool Insert(IEntity entity)
|
public bool Insert(IEntity entity)
|
||||||
{
|
{
|
||||||
return CanInsert(entity) && _storage?.Insert(entity) == true;
|
return CanInsert(entity) && Storage?.Insert(entity) == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Remove(IEntity entity)
|
public override bool Remove(IEntity entity)
|
||||||
{
|
{
|
||||||
EnsureInitialCalculated();
|
EnsureInitialCalculated();
|
||||||
return _storage?.Remove(entity) == true;
|
return Storage?.Remove(entity) == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleEntityMaybeInserted(EntInsertedIntoContainerMessage message)
|
public void HandleEntityMaybeInserted(EntInsertedIntoContainerMessage message)
|
||||||
{
|
{
|
||||||
if (message.Container != _storage)
|
if (message.Container != Storage)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -206,7 +208,7 @@ namespace Content.Server.Storage.Components
|
|||||||
|
|
||||||
public void HandleEntityMaybeRemoved(EntRemovedFromContainerMessage message)
|
public void HandleEntityMaybeRemoved(EntRemovedFromContainerMessage message)
|
||||||
{
|
{
|
||||||
if (message.Container != _storage)
|
if (message.Container != Storage)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -324,9 +326,9 @@ namespace Content.Server.Storage.Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_storage == null)
|
if (Storage == null)
|
||||||
{
|
{
|
||||||
Logger.WarningS(LoggerName, $"{nameof(UpdateClientInventory)} called with null {nameof(_storage)}");
|
Logger.WarningS(LoggerName, $"{nameof(UpdateClientInventory)} called with null {nameof(Storage)}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -381,10 +383,39 @@ namespace Content.Server.Storage.Components
|
|||||||
#pragma warning restore 618
|
#pragma warning restore 618
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CloseNestedInterfaces(session);
|
||||||
|
|
||||||
if (SubscribedSessions.Count == 0)
|
if (SubscribedSessions.Count == 0)
|
||||||
UpdateStorageVisualization();
|
UpdateStorageVisualization();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the user has nested-UIs open (e.g., PDA UI open when pda is in a backpack), close them.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session"></param>
|
||||||
|
public void CloseNestedInterfaces(IPlayerSession session)
|
||||||
|
{
|
||||||
|
if (StoredEntities == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var entity in StoredEntities)
|
||||||
|
{
|
||||||
|
if (_entityManager.TryGetComponent(entity.Uid, out ServerStorageComponent storageComponent))
|
||||||
|
{
|
||||||
|
DebugTools.Assert(storageComponent != this, $"Storage component contains itself!? Entity: {OwnerUid}");
|
||||||
|
storageComponent.UnsubscribeSession(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_entityManager.TryGetComponent(entity.Uid, out ServerUserInterfaceComponent uiComponent))
|
||||||
|
{
|
||||||
|
foreach (var ui in uiComponent.Interfaces)
|
||||||
|
{
|
||||||
|
ui.Close(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void HandlePlayerSessionChangeEvent(object? obj, SessionStatusEventArgs sessionStatus)
|
private void HandlePlayerSessionChangeEvent(object? obj, SessionStatusEventArgs sessionStatus)
|
||||||
{
|
{
|
||||||
Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) handled a status change in player session (UID {sessionStatus.Session.AttachedEntityUid}).");
|
Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) handled a status change in player session (UID {sessionStatus.Session.AttachedEntityUid}).");
|
||||||
@@ -400,8 +431,8 @@ namespace Content.Server.Storage.Components
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
// ReSharper disable once StringLiteralTypo
|
// ReSharper disable once StringLiteralTypo
|
||||||
_storage = Owner.EnsureContainer<Container>("storagebase");
|
Storage = Owner.EnsureContainer<Container>("storagebase");
|
||||||
_storage.OccludesLight = _occludesLight;
|
Storage.OccludesLight = _occludesLight;
|
||||||
UpdateStorageVisualization();
|
UpdateStorageVisualization();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,7 +468,7 @@ namespace Content.Server.Storage.Components
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Owner.EntityManager.TryGetEntity(remove.EntityUid, out var entity) || _storage?.Contains(entity) == false)
|
if (!Owner.EntityManager.TryGetEntity(remove.EntityUid, out var entity) || Storage?.Contains(entity) == false)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ using Content.Shared.Throwing;
|
|||||||
using Content.Shared.Timing;
|
using Content.Shared.Timing;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
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.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
@@ -33,7 +35,6 @@ namespace Content.Shared.Interaction
|
|||||||
public abstract class SharedInteractionSystem : EntitySystem
|
public abstract class SharedInteractionSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
|
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||||
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
|
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
|
||||||
[Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!;
|
[Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!;
|
||||||
@@ -454,6 +455,11 @@ namespace Content.Shared.Interaction
|
|||||||
if (!InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true))
|
if (!InRangeUnobstructed(user, used, ignoreInsideBlocker: true, popup: true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||||
|
// This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
|
||||||
|
if (!user.IsInSameOrParentContainer(used) && !CanAccessViaStorage(user.Uid, used.Uid))
|
||||||
|
return;
|
||||||
|
|
||||||
var activateMsg = new ActivateInWorldEvent(user, used);
|
var activateMsg = new ActivateInWorldEvent(user, used);
|
||||||
RaiseLocalEvent(used.Uid, activateMsg);
|
RaiseLocalEvent(used.Uid, activateMsg);
|
||||||
if (activateMsg.Handled)
|
if (activateMsg.Handled)
|
||||||
@@ -529,17 +535,9 @@ namespace Content.Shared.Interaction
|
|||||||
public void AltInteract(IEntity user, IEntity target)
|
public void AltInteract(IEntity user, IEntity target)
|
||||||
{
|
{
|
||||||
// Get list of alt-interact verbs
|
// Get list of alt-interact verbs
|
||||||
GetAlternativeVerbsEvent getVerbEvent = new(user, target);
|
var verbs = _verbSystem.GetLocalVerbs(target, user, VerbType.Alternative)[VerbType.Alternative];
|
||||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
if (verbs.Any())
|
||||||
|
_verbSystem.ExecuteVerb(verbs.First(), user.Uid, target.Uid);
|
||||||
foreach (var verb in getVerbEvent.Verbs)
|
|
||||||
{
|
|
||||||
if (verb.Disabled)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
_verbSystem.ExecuteVerb(verb, user.Uid, target.Uid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -733,6 +731,13 @@ namespace Content.Shared.Interaction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This
|
||||||
|
/// checks if the user can access the item in these situations.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ namespace Content.Shared.Item
|
|||||||
verb.Act = () => args.Hands.TryPutInActiveHandOrAny(args.Target);
|
verb.Act = () => args.Hands.TryPutInActiveHandOrAny(args.Target);
|
||||||
verb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png";
|
verb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png";
|
||||||
|
|
||||||
// if the item already in the user's inventory, change the text
|
// if the item already in a container (that is not the same as the user's), then change the text.
|
||||||
if (args.Target.TryGetContainer(out var container) && container.Owner == args.User)
|
// this occurs when the item is in their inventory or in an open backpack
|
||||||
|
args.User.TryGetContainer(out var userContainer);
|
||||||
|
if (args.Target.TryGetContainer(out var container) && container != userContainer)
|
||||||
verb.Text = Loc.GetString("pick-up-verb-get-data-text-inventory");
|
verb.Text = Loc.GetString("pick-up-verb-get-data-text-inventory");
|
||||||
else
|
else
|
||||||
verb.Text = Loc.GetString("pick-up-verb-get-data-text");
|
verb.Text = Loc.GetString("pick-up-verb-get-data-text");
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.ActionBlocker;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Hands.Components;
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
@@ -10,6 +13,8 @@ namespace Content.Shared.Verbs
|
|||||||
public abstract class SharedVerbSystem : EntitySystem
|
public abstract class SharedVerbSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
|
[Dependency] private readonly SharedAdminLogSystem _logSystem = default!;
|
||||||
|
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||||
|
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This
|
/// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This
|
||||||
@@ -19,30 +24,61 @@ namespace Content.Shared.Verbs
|
|||||||
{
|
{
|
||||||
Dictionary<VerbType, SortedSet<Verb>> verbs = new();
|
Dictionary<VerbType, SortedSet<Verb>> verbs = new();
|
||||||
|
|
||||||
|
// accessibility checks
|
||||||
|
bool canAccess = false;
|
||||||
|
if (force || target == user)
|
||||||
|
canAccess = true;
|
||||||
|
else if (_interactionSystem.InRangeUnobstructed(user, target, ignoreInsideBlocker: true))
|
||||||
|
{
|
||||||
|
if (user.IsInSameOrParentContainer(target))
|
||||||
|
canAccess = true;
|
||||||
|
else
|
||||||
|
// the item might be in a backpack that the user has open
|
||||||
|
canAccess = _interactionSystem.CanAccessViaStorage(user.Uid, target.Uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A large number of verbs need to check action blockers. Instead of repeatedly having each system individually
|
||||||
|
// call ActionBlocker checks, just cache it for the verb request.
|
||||||
|
var canInteract = force || _actionBlockerSystem.CanInteract(user.Uid);
|
||||||
|
|
||||||
|
IEntity? @using = null;
|
||||||
|
if (user.TryGetComponent(out SharedHandsComponent? hands) && (force || _actionBlockerSystem.CanUse(user.Uid)))
|
||||||
|
{
|
||||||
|
hands.TryGetActiveHeldEntity(out @using);
|
||||||
|
|
||||||
|
// Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
|
||||||
|
// This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
|
||||||
|
// their sprite.
|
||||||
|
if (@using != null && @using.TryGetComponent<HandVirtualItemComponent>(out var pull))
|
||||||
|
{
|
||||||
|
@using = IoCManager.Resolve<IEntityManager>().GetEntity(pull.BlockingEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((verbTypes & VerbType.Interaction) == VerbType.Interaction)
|
if ((verbTypes & VerbType.Interaction) == VerbType.Interaction)
|
||||||
{
|
{
|
||||||
GetInteractionVerbsEvent getVerbEvent = new(user, target, force);
|
GetInteractionVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess);
|
||||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
RaiseLocalEvent(target.Uid, getVerbEvent);
|
||||||
verbs.Add(VerbType.Interaction, getVerbEvent.Verbs);
|
verbs.Add(VerbType.Interaction, getVerbEvent.Verbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((verbTypes & VerbType.Activation) == VerbType.Activation)
|
if ((verbTypes & VerbType.Activation) == VerbType.Activation)
|
||||||
{
|
{
|
||||||
GetActivationVerbsEvent getVerbEvent = new(user, target, force);
|
GetActivationVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess);
|
||||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
RaiseLocalEvent(target.Uid, getVerbEvent);
|
||||||
verbs.Add(VerbType.Activation, getVerbEvent.Verbs);
|
verbs.Add(VerbType.Activation, getVerbEvent.Verbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((verbTypes & VerbType.Alternative) == VerbType.Alternative)
|
if ((verbTypes & VerbType.Alternative) == VerbType.Alternative)
|
||||||
{
|
{
|
||||||
GetAlternativeVerbsEvent getVerbEvent = new(user, target, force);
|
GetAlternativeVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess);
|
||||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
RaiseLocalEvent(target.Uid, getVerbEvent);
|
||||||
verbs.Add(VerbType.Alternative, getVerbEvent.Verbs);
|
verbs.Add(VerbType.Alternative, getVerbEvent.Verbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((verbTypes & VerbType.Other) == VerbType.Other)
|
if ((verbTypes & VerbType.Other) == VerbType.Other)
|
||||||
{
|
{
|
||||||
GetOtherVerbsEvent getVerbEvent = new(user, target, force);
|
GetOtherVerbsEvent getVerbEvent = new(user, target, @using, hands, canInteract, canAccess);
|
||||||
RaiseLocalEvent(target.Uid, getVerbEvent);
|
RaiseLocalEvent(target.Uid, getVerbEvent);
|
||||||
verbs.Add(VerbType.Other, getVerbEvent.Verbs);
|
verbs.Add(VerbType.Other, getVerbEvent.Verbs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ using Content.Shared.Hands.Components;
|
|||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using System.Collections.Immutable;
|
|
||||||
|
|
||||||
namespace Content.Shared.Verbs
|
namespace Content.Shared.Verbs
|
||||||
{
|
{
|
||||||
@@ -17,10 +15,18 @@ namespace Content.Shared.Verbs
|
|||||||
public readonly EntityUid EntityUid;
|
public readonly EntityUid EntityUid;
|
||||||
public readonly VerbType Type;
|
public readonly VerbType Type;
|
||||||
|
|
||||||
public RequestServerVerbsEvent(EntityUid entityUid, VerbType type)
|
/// <summary>
|
||||||
|
/// If the target item is inside of some storage (e.g., backpack), this is the entity that owns that item
|
||||||
|
/// slot. Needed for validating that the user can access the target item.
|
||||||
|
/// </summary>
|
||||||
|
public readonly EntityUid? SlotOwner;
|
||||||
|
|
||||||
|
|
||||||
|
public RequestServerVerbsEvent(EntityUid entityUid, VerbType type, EntityUid? slotOwner = null)
|
||||||
{
|
{
|
||||||
EntityUid = entityUid;
|
EntityUid = entityUid;
|
||||||
Type = type;
|
Type = type;
|
||||||
|
SlotOwner = slotOwner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +81,9 @@ namespace Content.Shared.Verbs
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class GetInteractionVerbsEvent : GetVerbsEvent
|
public class GetInteractionVerbsEvent : GetVerbsEvent
|
||||||
{
|
{
|
||||||
public GetInteractionVerbsEvent(IEntity user, IEntity target, bool force=false) : base(user, target, force) { }
|
public GetInteractionVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands,
|
||||||
|
bool canInteract, bool canAccess) : base(user, target, @using, hands, canInteract, canAccess) { }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -89,7 +97,8 @@ namespace Content.Shared.Verbs
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class GetActivationVerbsEvent : GetVerbsEvent
|
public class GetActivationVerbsEvent : GetVerbsEvent
|
||||||
{
|
{
|
||||||
public GetActivationVerbsEvent(IEntity user, IEntity target, bool force=false) : base(user, target, force) { }
|
public GetActivationVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands,
|
||||||
|
bool canInteract, bool canAccess) : base(user, target, @using, hands, canInteract, canAccess) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -101,7 +110,8 @@ namespace Content.Shared.Verbs
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class GetAlternativeVerbsEvent : GetVerbsEvent
|
public class GetAlternativeVerbsEvent : GetVerbsEvent
|
||||||
{
|
{
|
||||||
public GetAlternativeVerbsEvent(IEntity user, IEntity target, bool force=false) : base(user, target, force) { }
|
public GetAlternativeVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands,
|
||||||
|
bool canInteract, bool canAccess) : base(user, target, @using, hands, canInteract, canAccess) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -113,7 +123,8 @@ namespace Content.Shared.Verbs
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class GetOtherVerbsEvent : GetVerbsEvent
|
public class GetOtherVerbsEvent : GetVerbsEvent
|
||||||
{
|
{
|
||||||
public GetOtherVerbsEvent(IEntity user, IEntity target, bool force=false) : base(user, target, force) { }
|
public GetOtherVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands,
|
||||||
|
bool canInteract, bool canAccess) : base(user, target, @using, hands, canInteract, canAccess) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -124,7 +135,7 @@ namespace Content.Shared.Verbs
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event output. Set of verbs that can be executed.
|
/// Event output. Set of verbs that can be executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SortedSet<Verb> Verbs = new();
|
public readonly SortedSet<Verb> Verbs = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can the user physically access the target?
|
/// Can the user physically access the target?
|
||||||
@@ -133,17 +144,17 @@ namespace Content.Shared.Verbs
|
|||||||
/// This is a combination of <see cref="ContainerHelpers.IsInSameOrParentContainer"/> and
|
/// This is a combination of <see cref="ContainerHelpers.IsInSameOrParentContainer"/> and
|
||||||
/// <see cref="SharedInteractionSystem.InRangeUnobstructed"/>.
|
/// <see cref="SharedInteractionSystem.InRangeUnobstructed"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public bool CanAccess;
|
public readonly bool CanAccess = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity being targeted for the verb.
|
/// The entity being targeted for the verb.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEntity Target;
|
public readonly IEntity Target;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity that will be "performing" the verb.
|
/// The entity that will be "performing" the verb.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEntity User;
|
public readonly IEntity User;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can the user physically interact?
|
/// Can the user physically interact?
|
||||||
@@ -153,7 +164,7 @@ namespace Content.Shared.Verbs
|
|||||||
/// to check this, it prevents it from having to be repeatedly called by each individual system that might
|
/// to check this, it prevents it from having to be repeatedly called by each individual system that might
|
||||||
/// contribute a verb.
|
/// contribute a verb.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public bool CanInteract;
|
public readonly bool CanInteract;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The User's hand component.
|
/// The User's hand component.
|
||||||
@@ -161,7 +172,7 @@ namespace Content.Shared.Verbs
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This may be null if the user has no hands.
|
/// This may be null if the user has no hands.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public SharedHandsComponent? Hands;
|
public readonly SharedHandsComponent? Hands;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity currently being held by the active hand.
|
/// The entity currently being held by the active hand.
|
||||||
@@ -170,34 +181,21 @@ namespace Content.Shared.Verbs
|
|||||||
/// This is only ever not null when <see cref="ActionBlockerSystem.CanUse(EntityUid)"/> is true and the user
|
/// This is only ever not null when <see cref="ActionBlockerSystem.CanUse(EntityUid)"/> is true and the user
|
||||||
/// has hands.
|
/// has hands.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IEntity? Using;
|
public readonly IEntity? Using;
|
||||||
|
|
||||||
public GetVerbsEvent(IEntity user, IEntity target, bool force=false)
|
// for eventual removal of IEntity.
|
||||||
|
public EntityUid UserUid => User.Uid;
|
||||||
|
public EntityUid TargetUid => Target.Uid;
|
||||||
|
public EntityUid? UsingUid => Using?.Uid;
|
||||||
|
|
||||||
|
public GetVerbsEvent(IEntity user, IEntity target, IEntity? @using, SharedHandsComponent? hands, bool canInteract, bool canAccess)
|
||||||
{
|
{
|
||||||
User = user;
|
User = user;
|
||||||
Target = target;
|
Target = target;
|
||||||
|
Using = @using;
|
||||||
CanAccess = force || (Target == User) || user.IsInSameOrParentContainer(target) &&
|
Hands = hands;
|
||||||
EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(user, target, ignoreInsideBlocker: true);
|
CanAccess = canAccess;
|
||||||
|
CanInteract = canInteract;
|
||||||
// A large number of verbs need to check action blockers. Instead of repeatedly having each system individually
|
|
||||||
// call ActionBlocker checks, just cache it for the verb request.
|
|
||||||
var actionBlockerSystem = EntitySystem.Get<ActionBlockerSystem>();
|
|
||||||
CanInteract = force || actionBlockerSystem.CanInteract(user.Uid);
|
|
||||||
|
|
||||||
if (!user.TryGetComponent(out Hands) ||
|
|
||||||
!actionBlockerSystem.CanUse(user.Uid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Hands.TryGetActiveHeldEntity(out Using);
|
|
||||||
|
|
||||||
// Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
|
|
||||||
// This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
|
|
||||||
// their sprite.
|
|
||||||
if (Using != null && Using.TryGetComponent<HandVirtualItemComponent>(out var pull))
|
|
||||||
{
|
|
||||||
Using = IoCManager.Resolve<IEntityManager>().GetEntity(pull.BlockingEntity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user