Enable verbs for items in storage and prevent orphaned nested UIs (#5512)
This commit is contained in:
@@ -7,6 +7,7 @@ using Content.Server.CombatMode;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.Pulling;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
@@ -98,6 +99,27 @@ namespace Content.Server.Interaction
|
||||
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
@@ -188,9 +210,6 @@ namespace Content.Server.Interaction
|
||||
if (!EntityManager.TryGetEntity(uid, out var used))
|
||||
return false;
|
||||
|
||||
if (user.IsInContainer())
|
||||
return false;
|
||||
|
||||
InteractionActivate(user, used);
|
||||
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
|
||||
/// 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>
|
||||
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?
|
||||
if (!altInteract && user.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
|
||||
{
|
||||
|
||||
DoAttack(user, coordinates, false, clickedUid);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (!ValidateInteractAndFace(user, coordinates))
|
||||
@@ -307,7 +324,8 @@ namespace Content.Server.Interaction
|
||||
EntityManager.TryGetEntity(clickedUid, out var target);
|
||||
|
||||
// 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",
|
||||
$"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);
|
||||
|
||||
// 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",
|
||||
$"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.Players;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Storage.Components
|
||||
@@ -47,7 +48,8 @@ namespace Content.Server.Storage.Components
|
||||
|
||||
private const string LoggerName = "Storage";
|
||||
|
||||
private Container? _storage;
|
||||
public Container? Storage;
|
||||
|
||||
private readonly Dictionary<IEntity, int> _sizeCache = new();
|
||||
|
||||
[DataField("occludesLight")]
|
||||
@@ -77,7 +79,7 @@ namespace Content.Server.Storage.Components
|
||||
public SoundSpecifier StorageSoundCollection { get; set; } = new SoundCollectionSpecifier("storageRustle");
|
||||
|
||||
[ViewVariables]
|
||||
public override IReadOnlyList<IEntity>? StoredEntities => _storage?.ContainedEntities;
|
||||
public override IReadOnlyList<IEntity>? StoredEntities => Storage?.ContainedEntities;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool OccludesLight
|
||||
@@ -86,7 +88,7 @@ namespace Content.Server.Storage.Components
|
||||
set
|
||||
{
|
||||
_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;
|
||||
|
||||
if (_storage == null)
|
||||
if (Storage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var entity in _storage.ContainedEntities)
|
||||
foreach (var entity in Storage.ContainedEntities)
|
||||
{
|
||||
var item = entity.GetComponent<SharedItemComponent>();
|
||||
_storageUsed += item.Size;
|
||||
@@ -173,18 +175,18 @@ namespace Content.Server.Storage.Components
|
||||
/// <returns>true if the entity was inserted, false otherwise</returns>
|
||||
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)
|
||||
{
|
||||
EnsureInitialCalculated();
|
||||
return _storage?.Remove(entity) == true;
|
||||
return Storage?.Remove(entity) == true;
|
||||
}
|
||||
|
||||
public void HandleEntityMaybeInserted(EntInsertedIntoContainerMessage message)
|
||||
{
|
||||
if (message.Container != _storage)
|
||||
if (message.Container != Storage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -206,7 +208,7 @@ namespace Content.Server.Storage.Components
|
||||
|
||||
public void HandleEntityMaybeRemoved(EntRemovedFromContainerMessage message)
|
||||
{
|
||||
if (message.Container != _storage)
|
||||
if (message.Container != Storage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -324,9 +326,9 @@ namespace Content.Server.Storage.Components
|
||||
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;
|
||||
}
|
||||
@@ -381,10 +383,39 @@ namespace Content.Server.Storage.Components
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
CloseNestedInterfaces(session);
|
||||
|
||||
if (SubscribedSessions.Count == 0)
|
||||
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)
|
||||
{
|
||||
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();
|
||||
|
||||
// ReSharper disable once StringLiteralTypo
|
||||
_storage = Owner.EnsureContainer<Container>("storagebase");
|
||||
_storage.OccludesLight = _occludesLight;
|
||||
Storage = Owner.EnsureContainer<Container>("storagebase");
|
||||
Storage.OccludesLight = _occludesLight;
|
||||
UpdateStorageVisualization();
|
||||
}
|
||||
|
||||
@@ -437,7 +468,7 @@ namespace Content.Server.Storage.Components
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user