]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Revert engine reverties (#34968)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Sat, 8 Feb 2025 06:17:55 +0000 (17:17 +1100)
committerGitHub <noreply@github.com>
Sat, 8 Feb 2025 06:17:55 +0000 (17:17 +1100)
13 files changed:
Content.Client/Storage/StorageBoundUserInterface.cs
Content.Client/Storage/Systems/StorageSystem.cs
Content.Client/UserInterface/Systems/Hotbar/HotbarUIController.cs
Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml
Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml.cs
Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs
Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs [moved from Content.Client/UserInterface/Systems/Storage/Controls/StorageContainer.cs with 64% similarity]
Content.Client/UserInterface/Systems/Storage/StorageUIController.cs
Content.Client/Viewport/ScalingViewport.cs
Content.Shared/CCVar/CCVars.Interactions.cs
Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
Content.Shared/Storage/StorageComponent.cs
RobustToolbox

index b90977cbb4da14f6b48c42b260cd1b6f8f6dcc68..bacc90eabffcd9a3f628b4fbe950937cb2654684 100644 (file)
@@ -1,39 +1,80 @@
-using Content.Client.Storage.Systems;
+using Content.Client.UserInterface.Systems.Storage;
+using Content.Client.UserInterface.Systems.Storage.Controls;
 using Content.Shared.Storage;
 using JetBrains.Annotations;
+using Robust.Client.UserInterface;
 
 namespace Content.Client.Storage;
 
 [UsedImplicitly]
 public sealed class StorageBoundUserInterface : BoundUserInterface
 {
-    [Dependency] private readonly IEntityManager _entManager = default!;
-
-    private readonly StorageSystem _storage;
-
-    [Obsolete] public override bool DeferredClose => false;
+    private StorageWindow? _window;
 
     public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
     {
-        IoCManager.InjectDependencies(this);
-        _storage = _entManager.System<StorageSystem>();
     }
 
     protected override void Open()
     {
         base.Open();
 
-        if (_entManager.TryGetComponent<StorageComponent>(Owner, out var comp))
-            _storage.OpenStorageWindow((Owner, comp));
+        _window = IoCManager.Resolve<IUserInterfaceManager>()
+            .GetUIController<StorageUIController>()
+            .CreateStorageWindow(Owner);
+
+        if (EntMan.TryGetComponent(Owner, out StorageComponent? storage))
+        {
+            _window.UpdateContainer((Owner, storage));
+        }
+
+        _window.OnClose += Close;
+        _window.FlagDirty();
+    }
+
+    public void Refresh()
+    {
+        _window?.FlagDirty();
+    }
+
+    public void Reclaim()
+    {
+        if (_window == null)
+            return;
+
+        _window.OnClose -= Close;
+        _window.Orphan();
+        _window = null;
     }
 
     protected override void Dispose(bool disposing)
     {
         base.Dispose(disposing);
-        if (!disposing)
+
+        Reclaim();
+    }
+
+    public void Hide()
+    {
+        if (_window == null)
+            return;
+
+        _window.Visible = false;
+    }
+
+    public void Show()
+    {
+        if (_window == null)
             return;
 
-        _storage.CloseStorageWindow(Owner);
+        _window.Visible = true;
+    }
+
+    public void ReOpen()
+    {
+        _window?.Orphan();
+        _window = null;
+        Open();
     }
 }
 
index eea7b9ec797fc64de8ff39e3340d8ed4e160d3a9..ab4d9407b222575bf1016f185dbfa95a9dfb5d93 100644 (file)
@@ -4,7 +4,8 @@ using Content.Client.Animations;
 using Content.Shared.Hands;
 using Content.Shared.Storage;
 using Content.Shared.Storage.EntitySystems;
-using Robust.Shared.Collections;
+using Robust.Client.Player;
+using Robust.Shared.GameStates;
 using Robust.Shared.Map;
 using Robust.Shared.Timing;
 
@@ -13,114 +14,95 @@ namespace Content.Client.Storage.Systems;
 public sealed class StorageSystem : SharedStorageSystem
 {
     [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly IPlayerManager _player = default!;
     [Dependency] private readonly EntityPickupAnimationSystem _entityPickupAnimation = default!;
 
-    private readonly List<Entity<StorageComponent>> _openStorages = new();
-    public int OpenStorageAmount => _openStorages.Count;
-
-    public event Action<Entity<StorageComponent>>? StorageUpdated;
-    public event Action<Entity<StorageComponent>?>? StorageOrderChanged;
+    private Dictionary<EntityUid, ItemStorageLocation> _oldStoredItems = new();
 
     public override void Initialize()
     {
         base.Initialize();
 
-        SubscribeLocalEvent<StorageComponent, ComponentShutdown>(OnShutdown);
+        SubscribeLocalEvent<StorageComponent, ComponentHandleState>(OnStorageHandleState);
         SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
         SubscribeAllEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
     }
 
-    public override void UpdateUI(Entity<StorageComponent?> entity)
+    private void OnStorageHandleState(EntityUid uid, StorageComponent component, ref ComponentHandleState args)
     {
-        if (Resolve(entity.Owner, ref entity.Comp))
-            StorageUpdated?.Invoke((entity, entity.Comp));
-    }
+        if (args.Current is not StorageComponentState state)
+            return;
 
-    public void OpenStorageWindow(Entity<StorageComponent> entity)
-    {
-        if (_openStorages.Contains(entity))
-        {
-            if (_openStorages.LastOrDefault() == entity)
-            {
-                CloseStorageWindow((entity, entity.Comp));
-            }
-            else
-            {
-                var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
-                var reverseStorages = storages.Reverse();
+        component.Grid.Clear();
+        component.Grid.AddRange(state.Grid);
+        component.MaxItemSize = state.MaxItemSize;
+        component.Whitelist = state.Whitelist;
+        component.Blacklist = state.Blacklist;
 
-                foreach (var storageEnt in reverseStorages)
-                {
-                    if (storageEnt == entity)
-                        break;
+        _oldStoredItems.Clear();
 
-                    CloseStorageBoundUserInterface(storageEnt.Owner);
-                    _openStorages.Remove(entity);
-                }
-            }
-            return;
+        foreach (var item in component.StoredItems)
+        {
+            _oldStoredItems.Add(item.Key, item.Value);
         }
 
-        ClearNonParentStorages(entity);
-        _openStorages.Add(entity);
-        Entity<StorageComponent>? last = _openStorages.LastOrDefault();
-        StorageOrderChanged?.Invoke(last);
-    }
-
-    public void CloseStorageWindow(Entity<StorageComponent?> entity)
-    {
-        if (!Resolve(entity, ref entity.Comp, false))
-            return;
+        component.StoredItems.Clear();
 
-        if (!_openStorages.Contains((entity, entity.Comp)))
-            return;
+        foreach (var (nent, location) in state.StoredItems)
+        {
+            var ent = EnsureEntity<StorageComponent>(nent, uid);
+            component.StoredItems[ent] = location;
+        }
 
-        var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
-        var reverseStorages = storages.Reverse();
+        component.SavedLocations.Clear();
 
-        foreach (var storage in reverseStorages)
+        foreach (var loc in state.SavedLocations)
         {
-            CloseStorageBoundUserInterface(storage.Owner);
-            _openStorages.Remove(storage);
-            if (storage.Owner == entity.Owner)
-                break;
+            component.SavedLocations[loc.Key] = new(loc.Value);
         }
 
-        Entity<StorageComponent>? last = null;
-        if (_openStorages.Any())
-            last = _openStorages.LastOrDefault();
-        StorageOrderChanged?.Invoke(last);
-    }
+        var uiDirty = !component.StoredItems.SequenceEqual(_oldStoredItems);
 
-    private void ClearNonParentStorages(EntityUid uid)
-    {
-        var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
-        var reverseStorages = storages.Reverse();
-
-        foreach (var storage in reverseStorages)
+        if (uiDirty && UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
         {
-            if (storage.Comp.Container.Contains(uid))
-                break;
+            storageBui.Refresh();
+            // Make sure nesting still updated.
+            var player = _player.LocalEntity;
 
-            CloseStorageBoundUserInterface(storage.Owner);
-            _openStorages.Remove(storage);
+            if (NestedStorage && player != null && ContainerSystem.TryGetContainingContainer((uid, null, null), out var container) &&
+                UI.TryGetOpenUi<StorageBoundUserInterface>(container.Owner, StorageComponent.StorageUiKey.Key, out var containerBui))
+            {
+                containerBui.Hide();
+            }
+            else
+            {
+                storageBui.Show();
+            }
         }
     }
 
-    private void CloseStorageBoundUserInterface(Entity<UserInterfaceComponent?> entity)
+    public override void UpdateUI(Entity<StorageComponent?> entity)
     {
-        if (!Resolve(entity, ref entity.Comp, false))
-            return;
-
-        if (entity.Comp.ClientOpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui)
-            return;
+        if (UI.TryGetOpenUi<StorageBoundUserInterface>(entity.Owner, StorageComponent.StorageUiKey.Key, out var sBui))
+        {
+            sBui.Refresh();
+        }
+    }
 
-        bui.Close();
+    protected override void HideStorageWindow(EntityUid uid, EntityUid actor)
+    {
+        if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
+        {
+            storageBui.Hide();
+        }
     }
 
-    private void OnShutdown(Entity<StorageComponent> ent, ref ComponentShutdown args)
+    protected override void ShowStorageWindow(EntityUid uid, EntityUid actor)
     {
-        CloseStorageWindow((ent, ent.Comp));
+        if (UI.TryGetOpenUi<StorageBoundUserInterface>(uid, StorageComponent.StorageUiKey.Key, out var storageBui))
+        {
+            storageBui.Show();
+        }
     }
 
     /// <inheritdoc />
@@ -142,7 +124,7 @@ public sealed class StorageSystem : SharedStorageSystem
     {
         if (!_timing.IsFirstTimePredicted)
             return;
-        
+
         if (TransformSystem.InRange(finalCoords, initialCoords, 0.1f) ||
             !Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId))
         {
index 2f266cfdd6bf49b8b4c789251ece8c6a2fa9b443..b89115da86b9756afffb32efbbe32135d5fa619b 100644 (file)
@@ -31,13 +31,12 @@ public sealed class HotbarUIController : UIController
         ReloadHotbar();
     }
 
-    public void Setup(HandsContainer handsContainer, StorageContainer storageContainer)
+    public void Setup(HandsContainer handsContainer)
     {
         _inventory = UIManager.GetUIController<InventoryUIController>();
         _hands = UIManager.GetUIController<HandsUIController>();
         _storage = UIManager.GetUIController<StorageUIController>();
         _hands.RegisterHandContainer(handsContainer);
-        _storage.RegisterStorageContainer(storageContainer);
     }
 
     public void ReloadHotbar()
index 00ba1878b48d4e28b5a5da77ae80b730c842e2d3..153e02bd55856ea0f58945d488e2ac0a315f3390 100644 (file)
@@ -1,7 +1,6 @@
 <widgets:HotbarGui
     xmlns="https://spacestation14.io"
     xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Controls"
-    xmlns:storage="clr-namespace:Content.Client.UserInterface.Systems.Storage.Controls"
     xmlns:hands="clr-namespace:Content.Client.UserInterface.Systems.Hands.Controls"
     xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
     Name="HotbarInterface"
         <BoxContainer Name="StorageContainer"
                       Access="Public"
                       HorizontalAlignment="Center"
+                      HorizontalExpand="True"
                       Margin="10">
-            <storage:StorageContainer
-                Name="StoragePanel"
-                Visible="False"/>
         </BoxContainer>
         <BoxContainer Orientation="Horizontal" Name="Hotbar" HorizontalAlignment="Center">
             <inventory:ItemSlotButtonContainer
index 59d86c84b8536d5b6acd85850e61a2ffcdf8e045..b3650136e7ad2519a3fb63c5d730f3dccbad2875 100644 (file)
@@ -15,7 +15,7 @@ public sealed partial class HotbarGui : UIWidget
         StatusPanelLeft.SetSide(HandUILocation.Left);
         var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
 
-        hotbarController.Setup(HandContainer, StoragePanel);
+        hotbarController.Setup(HandContainer);
         LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
     }
 
index dd9986e4c6e69d70e1f8fc4a0b748978a6cd098b..f4c4158b5ceddc9388985fa0e45b04960b8a3520 100644 (file)
@@ -59,7 +59,7 @@ public sealed class ItemGridPiece : Control, IEntityControl
         Location = location;
 
         Visible = true;
-        MouseFilter = MouseFilterMode.Pass;
+        MouseFilter = MouseFilterMode.Stop;
 
         TooltipSupplier = SupplyTooltip;
 
@@ -105,8 +105,11 @@ public sealed class ItemGridPiece : Control, IEntityControl
             return;
         }
 
-        if (_storageController.IsDragging && _storageController.DraggingGhost?.Entity == Entity && _storageController.DraggingGhost != this)
+        if (_storageController.IsDragging && _storageController.DraggingGhost?.Entity == Entity &&
+            _storageController.DraggingGhost != this)
+        {
             return;
+        }
 
         var adjustedShape = _entityManager.System<ItemSystem>().GetAdjustedItemShape((Entity, itemComponent), Location.Rotation, Vector2i.Zero);
         var boundingGrid = adjustedShape.GetBoundingBox();
similarity index 64%
rename from Content.Client/UserInterface/Systems/Storage/Controls/StorageContainer.cs
rename to Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs
index a9d7e098265a5893bc327810e3f116ac3ffcb567..88b4c06d72c01664d7d7d938ada94653e409c271 100644 (file)
@@ -3,7 +3,9 @@ using System.Linq;
 using System.Numerics;
 using Content.Client.Hands.Systems;
 using Content.Client.Items.Systems;
+using Content.Client.Storage;
 using Content.Client.Storage.Systems;
+using Content.Shared.IdentityManagement;
 using Content.Shared.Input;
 using Content.Shared.Item;
 using Content.Shared.Storage;
@@ -11,12 +13,14 @@ using Robust.Client.Graphics;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Collections;
+using Robust.Shared.Containers;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
 namespace Content.Client.UserInterface.Systems.Storage.Controls;
 
-public sealed class StorageContainer : BaseWindow
+public sealed class StorageWindow : BaseWindow
 {
     [Dependency] private readonly IEntityManager _entity = default!;
     private readonly StorageUIController _storageController;
@@ -27,6 +31,20 @@ public sealed class StorageContainer : BaseWindow
     private readonly GridContainer _backgroundGrid;
     private readonly GridContainer _sidebar;
 
+    private Control _titleContainer;
+    private Label _titleLabel;
+
+    // Needs to be nullable in case a piece is in default spot.
+    private readonly Dictionary<EntityUid, (ItemStorageLocation? Loc, ItemGridPiece Control)> _pieces = new();
+    private readonly List<Control> _controlGrid = new();
+
+    private ValueList<EntityUid> _contained = new();
+    private ValueList<EntityUid> _toRemove = new();
+
+    private TextureButton? _backButton;
+
+    private bool _isDirty;
+
     public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPiecePressed;
     public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPieceUnpressed;
 
@@ -51,9 +69,10 @@ public sealed class StorageContainer : BaseWindow
     private readonly string _sidebarFatTexturePath = "Storage/sidebar_fat";
     private Texture? _sidebarFatTexture;
 
-    public StorageContainer()
+    public StorageWindow()
     {
         IoCManager.InjectDependencies(this);
+        Resizable = false;
 
         _storageController = UserInterfaceManager.GetUIController<StorageUIController>();
 
@@ -63,6 +82,7 @@ public sealed class StorageContainer : BaseWindow
 
         _sidebar = new GridContainer
         {
+            Name = "SideBar",
             HSeparationOverride = 0,
             VSeparationOverride = 0,
             Columns = 1
@@ -70,21 +90,48 @@ public sealed class StorageContainer : BaseWindow
 
         _pieceGrid = new GridContainer
         {
+            Name = "PieceGrid",
             HSeparationOverride = 0,
             VSeparationOverride = 0
         };
 
         _backgroundGrid = new GridContainer
         {
+            Name = "BackgroundGrid",
             HSeparationOverride = 0,
             VSeparationOverride = 0
         };
 
+        _titleLabel = new Label()
+        {
+            HorizontalExpand = true,
+            Name = "StorageLabel",
+            ClipText = true,
+            Text = "Dummy",
+            StyleClasses =
+            {
+                "FancyWindowTitle",
+            }
+        };
+
+        _titleContainer = new PanelContainer()
+        {
+            StyleClasses =
+            {
+                "WindowHeadingBackground"
+            },
+            Children =
+            {
+                _titleLabel
+            }
+        };
+
         var container = new BoxContainer
         {
             Orientation = BoxContainer.LayoutOrientation.Vertical,
             Children =
             {
+                _titleContainer,
                 new BoxContainer
                 {
                     Orientation = BoxContainer.LayoutOrientation.Horizontal,
@@ -130,12 +177,22 @@ public sealed class StorageContainer : BaseWindow
         if (entity == null)
             return;
 
+        if (UserInterfaceManager.GetUIController<StorageUIController>().WindowTitle)
+        {
+            _titleLabel.Text = Identity.Name(entity.Value, _entity);
+            _titleContainer.Visible = true;
+        }
+        else
+        {
+            _titleContainer.Visible = false;
+        }
+
         BuildGridRepresentation();
     }
 
     private void BuildGridRepresentation()
     {
-        if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || !comp.Grid.Any())
+        if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var comp) || comp.Grid.Count == 0)
             return;
 
         var boundingGrid = comp.Grid.GetBoundingBox();
@@ -144,12 +201,13 @@ public sealed class StorageContainer : BaseWindow
 
         #region Sidebar
         _sidebar.Children.Clear();
-        _sidebar.Rows = boundingGrid.Height + 1;
+        var rows = boundingGrid.Height + 1;
+        _sidebar.Rows = rows;
+
         var exitButton = new TextureButton
         {
-            TextureNormal = _entity.System<StorageSystem>().OpenStorageAmount == 1
-                ?_exitTexture
-                : _backTexture,
+            Name = "ExitButton",
+            TextureNormal = _exitTexture,
             Scale = new Vector2(2, 2),
         };
         exitButton.OnPressed += _ =>
@@ -165,8 +223,10 @@ public sealed class StorageContainer : BaseWindow
                 args.Handle();
             }
         };
+
         var exitContainer = new BoxContainer
         {
+            Name = "ExitContainer",
             Children =
             {
                 new TextureRect
@@ -182,28 +242,70 @@ public sealed class StorageContainer : BaseWindow
                 }
             }
         };
+
         _sidebar.AddChild(exitContainer);
-        for (var i = 0; i < boundingGrid.Height - 1; i++)
+        var offset = 2;
+
+        if (_entity.System<StorageSystem>().NestedStorage && rows > 0)
         {
-            _sidebar.AddChild(new TextureRect
+            _backButton = new TextureButton
             {
-                Texture = _sidebarMiddleTexture,
-                TextureScale = new Vector2(2, 2),
-            });
+                TextureNormal = _backTexture,
+                Scale = new Vector2(2, 2),
+            };
+            _backButton.OnPressed += _ =>
+            {
+                var containerSystem = _entity.System<SharedContainerSystem>();
+
+                if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
+                    _entity.TryGetComponent(container.Owner, out StorageComponent? storage))
+                {
+                    Close();
+
+                    if (_entity.System<SharedUserInterfaceSystem>()
+                        .TryGetOpenUi<StorageBoundUserInterface>(container.Owner,
+                            StorageComponent.StorageUiKey.Key,
+                            out var parentBui))
+                    {
+                        parentBui.Show();
+                    }
+                }
+            };
+
+            var backContainer = new BoxContainer
+            {
+                Name = "ExitContainer",
+                Children =
+                {
+                    new TextureRect
+                    {
+                        Texture = rows > 2 ? _sidebarMiddleTexture : _sidebarBottomTexture,
+                        TextureScale = new Vector2(2, 2),
+                        Children =
+                        {
+                            _backButton,
+                        }
+                    }
+                }
+            };
+
+            _sidebar.AddChild(backContainer);
         }
 
-        if (boundingGrid.Height > 0)
+        var fillerRows = rows - offset;
+
+        for (var i = 0; i < fillerRows; i++)
         {
             _sidebar.AddChild(new TextureRect
             {
-                Texture = _sidebarBottomTexture,
+                Texture = i != (fillerRows - 1) ? _sidebarMiddleTexture : _sidebarBottomTexture,
                 TextureScale = new Vector2(2, 2),
             });
         }
 
         #endregion
 
-        BuildItemPieces();
+        FlagDirty();
     }
 
     public void BuildBackground()
@@ -240,70 +342,127 @@ public sealed class StorageContainer : BaseWindow
         }
     }
 
+    public void Reclaim(ItemStorageLocation location, ItemGridPiece draggingGhost)
+    {
+        draggingGhost.OnPiecePressed += OnPiecePressed;
+        draggingGhost.OnPieceUnpressed += OnPieceUnpressed;
+        _pieces[draggingGhost.Entity] = (location, draggingGhost);
+        draggingGhost.Location = location;
+        var controlIndex = GetGridIndex(draggingGhost);
+        _controlGrid[controlIndex].AddChild(draggingGhost);
+    }
+
+    private int GetGridIndex(ItemGridPiece piece)
+    {
+        return piece.Location.Position.X + piece.Location.Position.Y * _pieceGrid.Columns;
+    }
+
+    public void FlagDirty()
+    {
+        _isDirty = true;
+    }
+
+    public void RemoveGrid(ItemGridPiece control)
+    {
+        control.Orphan();
+        _pieces.Remove(control.Entity);
+        control.OnPiecePressed -= OnPiecePressed;
+        control.OnPieceUnpressed -= OnPieceUnpressed;
+    }
+
     public void BuildItemPieces()
     {
         if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComp))
             return;
 
-        if (!storageComp.Grid.Any())
+        if (storageComp.Grid.Count == 0)
             return;
 
         var boundingGrid = storageComp.Grid.GetBoundingBox();
         var size = _emptyTexture!.Size * 2;
-        var containedEntities = storageComp.Container.ContainedEntities.Reverse().ToArray();
+        _contained.Clear();
+        _contained.AddRange(storageComp.Container.ContainedEntities.Reverse());
 
-        //todo. at some point, we may want to only rebuild the pieces that have actually received new data.
-
-        _pieceGrid.RemoveAllChildren();
-        _pieceGrid.Rows = boundingGrid.Height + 1;
-        _pieceGrid.Columns = boundingGrid.Width + 1;
-        for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
+        // Build the grid representation
+        if (_pieceGrid.Rows - 1 != boundingGrid.Height || _pieceGrid.Columns - 1 != boundingGrid.Width)
         {
-            for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
+            _pieceGrid.Rows = boundingGrid.Height + 1;
+            _pieceGrid.Columns = boundingGrid.Width + 1;
+            _controlGrid.Clear();
+
+            for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
             {
-                var control = new Control
+                for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
                 {
-                    MinSize = size
-                };
+                    var control = new Control
+                    {
+                        MinSize = size
+                    };
 
-                var currentPosition = new Vector2i(x, y);
+                    _controlGrid.Add(control);
+                    _pieceGrid.AddChild(control);
+                }
+            }
+        }
+
+        _toRemove.Clear();
 
-                foreach (var (itemEnt, itemPos) in storageComp.StoredItems)
+        // Remove entities no longer relevant / Update existing ones
+        foreach (var (ent, data) in _pieces)
+        {
+            if (storageComp.StoredItems.TryGetValue(ent, out var updated))
+            {
+                if (data.Loc.Equals(updated))
                 {
-                    if (itemPos.Position != currentPosition)
-                        continue;
+                    DebugTools.Assert(data.Control.Location == updated);
+                    continue;
+                }
 
-                    if (_entity.TryGetComponent<ItemComponent>(itemEnt, out var itemEntComponent))
-                    {
-                        ItemGridPiece gridPiece;
+                // Update
+                data.Control.Location = updated;
+                var index = GetGridIndex(data.Control);
+                data.Control.Orphan();
+                _controlGrid[index].AddChild(data.Control);
+                _pieces[ent] = (updated, data.Control);
+                continue;
+            }
 
-                        if (_storageController.CurrentlyDragging?.Entity is { } dragging
-                            && dragging == itemEnt)
-                        {
-                            _storageController.CurrentlyDragging.Orphan();
-                            gridPiece = _storageController.CurrentlyDragging;
-                        }
-                        else
-                        {
-                            gridPiece = new ItemGridPiece((itemEnt, itemEntComponent), itemPos, _entity)
-                            {
-                                MinSize = size,
-                                Marked = Array.IndexOf(containedEntities, itemEnt) switch
-                                {
-                                    0 => ItemGridPieceMarks.First,
-                                    1 => ItemGridPieceMarks.Second,
-                                    _ => null,
-                                }
-                            };
-                            gridPiece.OnPiecePressed += OnPiecePressed;
-                            gridPiece.OnPieceUnpressed += OnPieceUnpressed;
-                        }
+            _toRemove.Add(ent);
+        }
+
+        foreach (var ent in _toRemove)
+        {
+            _pieces.Remove(ent, out var data);
+            data.Control.Orphan();
+        }
+
+        // Add new ones
+        foreach (var (ent, loc) in storageComp.StoredItems)
+        {
+            if (_pieces.TryGetValue(ent, out var existing))
+            {
+                DebugTools.Assert(existing.Loc == loc);
+                continue;
+            }
 
-                        control.AddChild(gridPiece);
+            if (_entity.TryGetComponent<ItemComponent>(ent, out var itemEntComponent))
+            {
+                var gridPiece = new ItemGridPiece((ent, itemEntComponent), loc, _entity)
+                {
+                    MinSize = size,
+                    Marked = _contained.IndexOf(ent) switch
+                    {
+                        0 => ItemGridPieceMarks.First,
+                        1 => ItemGridPieceMarks.Second,
+                        _ => null,
                     }
-                }
+                };
+                gridPiece.OnPiecePressed += OnPiecePressed;
+                gridPiece.OnPieceUnpressed += OnPieceUnpressed;
+                var controlIndex = loc.Position.X + loc.Position.Y * (boundingGrid.Width + 1);
 
-                _pieceGrid.AddChild(control);
+                _controlGrid[controlIndex].AddChild(gridPiece);
+                _pieces[ent] = (loc, gridPiece);
             }
         }
     }
@@ -315,6 +474,35 @@ public sealed class StorageContainer : BaseWindow
         if (!IsOpen)
             return;
 
+        if (_isDirty)
+        {
+            _isDirty = false;
+            BuildItemPieces();
+        }
+
+        var containerSystem = _entity.System<SharedContainerSystem>();
+
+        if (_backButton != null)
+        {
+            if (StorageEntity != null && _entity.System<StorageSystem>().NestedStorage)
+            {
+                if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) &&
+                    _entity.HasComponent<StorageComponent>(container.Owner))
+                {
+                    _backButton.Visible = true;
+                }
+                else
+                {
+                    _backButton.Visible = false;
+                }
+            }
+            // Hide the button.
+            else
+            {
+                _backButton.Visible = false;
+            }
+        }
+
         var itemSystem = _entity.System<ItemSystem>();
         var storageSystem = _entity.System<StorageSystem>();
         var handsSystem = _entity.System<HandsSystem>();
@@ -324,7 +512,7 @@ public sealed class StorageContainer : BaseWindow
             child.ModulateSelfOverride = Color.FromHex("#222222");
         }
 
-        if (UserInterfaceManager.CurrentlyHovered is StorageContainer con && con != this)
+        if (UserInterfaceManager.CurrentlyHovered is StorageWindow con && con != this)
             return;
 
         if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComponent))
@@ -373,7 +561,7 @@ public sealed class StorageContainer : BaseWindow
                 continue;
 
             float spot = 0;
-            var marked = new List<Control>();
+            var marked = new ValueList<Control>();
 
             foreach (var location in locations.Value)
             {
@@ -500,14 +688,4 @@ public sealed class StorageContainer : BaseWindow
             }
         }
     }
-
-    public override void Close()
-    {
-        base.Close();
-
-        if (StorageEntity == null)
-            return;
-
-        _entity.System<StorageSystem>().CloseStorageWindow(StorageEntity.Value);
-    }
 }
index 1e61ad9838066fb24d8bea4d0100dc48d07b7e49..5c3f0479827c41e3b2cded24dda7b254731fb4c3 100644 (file)
@@ -2,6 +2,7 @@ using System.Numerics;
 using Content.Client.Examine;
 using Content.Client.Hands.Systems;
 using Content.Client.Interaction;
+using Content.Client.Storage;
 using Content.Client.Storage.Systems;
 using Content.Client.UserInterface.Systems.Hotbar.Widgets;
 using Content.Client.UserInterface.Systems.Storage.Controls;
@@ -9,9 +10,9 @@ using Content.Client.Verbs.UI;
 using Content.Shared.CCVar;
 using Content.Shared.Input;
 using Content.Shared.Interaction;
-using Content.Shared.Item;
 using Content.Shared.Storage;
 using Robust.Client.Input;
+using Robust.Client.Player;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controllers;
 using Robust.Client.UserInterface.Controls;
@@ -23,19 +24,23 @@ namespace Content.Client.UserInterface.Systems.Storage;
 
 public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>
 {
+    /*
+     * Things are a bit over the shop but essentially
+     * - Clicking into storagewindow is handled via storagewindow
+     * - Clicking out of it is via ItemGridPiece
+     * - Dragging around is handled here
+     * - Drawing is handled via ItemGridPiece
+     * - StorageSystem handles any sim stuff around open windows.
+     */
+
     [Dependency] private readonly IConfigurationManager _configuration = default!;
-    [Dependency] private readonly IEntityManager _entity = default!;
     [Dependency] private readonly IInputManager _input = default!;
-    [Dependency] private readonly IUserInterfaceManager _ui = default!;
+    [Dependency] private readonly IPlayerManager _player = default!;
+    [UISystemDependency] private readonly StorageSystem _storage = default!;
 
     private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
-    private StorageContainer? _container;
-
-    private Vector2? _lastContainerPosition;
-
-    private HotbarGui? Hotbar => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
 
-    public ItemGridPiece? DraggingGhost;
+    public ItemGridPiece? DraggingGhost => _menuDragHelper.Dragged;
     public Angle DraggingRotation = Angle.Zero;
     public bool StaticStorageUIEnabled;
     public bool OpaqueStorageWindow;
@@ -43,6 +48,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
     public bool IsDragging => _menuDragHelper.IsDragging;
     public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged;
 
+    public bool WindowTitle { get; private set; } = false;
+
     public StorageUIController()
     {
         _menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag);
@@ -52,106 +59,88 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
     {
         base.Initialize();
 
+        UIManager.OnScreenChanged += OnScreenChange;
+
         _configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
         _configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, true);
+        _configuration.OnValueChanged(CCVars.StorageWindowTitle, OnStorageWindowTitle, true);
     }
 
-    public void OnSystemLoaded(StorageSystem system)
-    {
-        _input.FirstChanceOnKeyEvent += OnMiddleMouse;
-        system.StorageUpdated += OnStorageUpdated;
-        system.StorageOrderChanged += OnStorageOrderChanged;
-    }
-
-    public void OnSystemUnloaded(StorageSystem system)
-    {
-        _input.FirstChanceOnKeyEvent -= OnMiddleMouse;
-        system.StorageUpdated -= OnStorageUpdated;
-        system.StorageOrderChanged -= OnStorageOrderChanged;
-    }
-
-    private void OnStorageOrderChanged(Entity<StorageComponent>? nullEnt)
+    private void OnScreenChange((UIScreen? Old, UIScreen? New) obj)
     {
-        if (_container == null)
+        // Handle reconnects with hotbargui.
+
+        // Essentially HotbarGui / the screen gets loaded AFTER gamestates at the moment (because clientgameticker manually changes it via event)
+        // and changing this may be a massive change.
+        // So instead we'll just manually reload it for now.
+        if (!StaticStorageUIEnabled ||
+            obj.New == null ||
+            !EntityManager.TryGetComponent(_player.LocalEntity, out UserInterfaceUserComponent? userComp))
+        {
             return;
+        }
 
-        if (IsDragging)
-            _menuDragHelper.EndDrag();
-
-        _container.UpdateContainer(nullEnt);
+        // UISystemDependency not injected at this point so do it the old fashion way, I love ordering issues.
+        var uiSystem = EntityManager.System<SharedUserInterfaceSystem>();
 
-        if (nullEnt is not null)
+        foreach (var bui in uiSystem.GetActorUis((_player.LocalEntity.Value, userComp)))
         {
-            // center it if we knock it off screen somehow.
-            if (!StaticStorageUIEnabled &&
-                (_lastContainerPosition == null ||
-                _lastContainerPosition.Value.X < 0 ||
-                _lastContainerPosition.Value.Y < 0 ||
-                _lastContainerPosition.Value.X > _ui.WindowRoot.Width ||
-                _lastContainerPosition.Value.Y > _ui.WindowRoot.Height))
-            {
-                _container.OpenCenteredAt(new Vector2(0.5f, 0.75f));
-            }
-            else
-            {
-                _container.Open();
+            if (!uiSystem.TryGetOpenUi<StorageBoundUserInterface>(bui.Entity, StorageComponent.StorageUiKey.Key, out var storageBui))
+                continue;
 
-                var pos = !StaticStorageUIEnabled && _lastContainerPosition != null
-                    ? _lastContainerPosition.Value
-                    : Vector2.Zero;
+            storageBui.ReOpen();
+        }
+    }
 
-                LayoutContainer.SetPosition(_container, pos);
-            }
+    private void OnStorageWindowTitle(bool obj)
+    {
+        WindowTitle = obj;
+    }
 
-            if (StaticStorageUIEnabled)
-            {
-                // we have to orphan it here because Open() sets the parent.
-                _container.Orphan();
-                Hotbar?.StorageContainer.AddChild(_container);
-            }
-            _lastContainerPosition = _container.GlobalPosition;
-        }
-        else
-        {
-            _lastContainerPosition = _container.GlobalPosition;
-            _container.Close();
-        }
+    private void OnOpaqueWindowChanged(bool obj)
+    {
+        OpaqueStorageWindow = obj;
     }
 
     private void OnStaticStorageChanged(bool obj)
     {
-        if (StaticStorageUIEnabled == obj)
-            return;
-
         StaticStorageUIEnabled = obj;
-        _lastContainerPosition = null;
+    }
 
-        if (_container == null)
-            return;
+    public StorageWindow CreateStorageWindow(EntityUid uid)
+    {
+        var window = new StorageWindow();
+        window.MouseFilter = Control.MouseFilterMode.Pass;
 
-        if (!_container.IsOpen)
-            return;
+        window.OnPiecePressed += (args, piece) =>
+        {
+            OnPiecePressed(args, window, piece);
+        };
+        window.OnPieceUnpressed += (args, piece) =>
+        {
+            OnPieceUnpressed(args, window, piece);
+        };
 
-        _container.Orphan();
         if (StaticStorageUIEnabled)
         {
-            Hotbar?.StorageContainer.AddChild(_container);
+            UIManager.GetActiveUIWidgetOrNull<HotbarGui>()?.StorageContainer.AddChild(window);
         }
         else
         {
-            _ui.WindowRoot.AddChild(_container);
+            window.OpenCenteredLeft();
         }
 
-        if (_entity.TryGetComponent<StorageComponent>(_container.StorageEntity, out var comp))
-            OnStorageOrderChanged((_container.StorageEntity.Value, comp));
+        return window;
     }
 
-    private void OnOpaqueWindowChanged(bool obj)
+    public void OnSystemLoaded(StorageSystem system)
     {
-        if (OpaqueStorageWindow == obj)
-            return;
-        OpaqueStorageWindow = obj;
-        _container?.BuildBackground();
+        _input.FirstChanceOnKeyEvent += OnMiddleMouse;
+    }
+
+    public void OnSystemUnloaded(StorageSystem system)
+    {
+        _input.FirstChanceOnKeyEvent -= OnMiddleMouse;
     }
 
     /// One might ask, Hey Emo, why are you parsing raw keyboard input just to rotate a rectangle?
@@ -190,7 +179,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
               binding.Mod3 == Keyboard.Key.Control))
             return;
 
-        if (!IsDragging && _entity.System<HandsSystem>().GetActiveHandEntity() == null)
+        if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
             return;
 
         //clamp it to a cardinal.
@@ -198,43 +187,18 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
         if (DraggingGhost != null)
             DraggingGhost.Location.Rotation = DraggingRotation;
 
-        if (IsDragging || (_container != null && UIManager.CurrentlyHovered == _container))
+        if (IsDragging || UIManager.CurrentlyHovered is StorageWindow)
             keyEvent.Handle();
     }
 
-    private void OnStorageUpdated(Entity<StorageComponent> uid)
-    {
-        if (_container?.StorageEntity != uid)
-            return;
-
-        _container.BuildItemPieces();
-    }
-
-    public void RegisterStorageContainer(StorageContainer container)
-    {
-        if (_container != null)
-        {
-            container.OnPiecePressed -= OnPiecePressed;
-            container.OnPieceUnpressed -= OnPieceUnpressed;
-        }
-
-        _container = container;
-        container.OnPiecePressed += OnPiecePressed;
-        container.OnPieceUnpressed += OnPieceUnpressed;
-
-        if (!StaticStorageUIEnabled)
-            _container.Orphan();
-    }
-
-    private void OnPiecePressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
+    private void OnPiecePressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
     {
-        if (IsDragging || !_container?.IsOpen == true)
+        if (IsDragging || !window.IsOpen)
             return;
 
         if (args.Function == ContentKeyFunctions.MoveStoredItem)
         {
             DraggingRotation = control.Location.Rotation;
-
             _menuDragHelper.MouseDown(control);
             _menuDragHelper.Update(0f);
 
@@ -242,17 +206,17 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
         }
         else if (args.Function == ContentKeyFunctions.SaveItemLocation)
         {
-            if (_container?.StorageEntity is not {} storage)
+            if (window.StorageEntity is not {} storage)
                 return;
 
-            _entity.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
-                _entity.GetNetEntity(control.Entity),
-                _entity.GetNetEntity(storage)));
+            EntityManager.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
+                EntityManager.GetNetEntity(control.Entity),
+                EntityManager.GetNetEntity(storage)));
             args.Handle();
         }
         else if (args.Function == ContentKeyFunctions.ExamineEntity)
         {
-            _entity.System<ExamineSystem>().DoExamine(control.Entity);
+            EntityManager.System<ExamineSystem>().DoExamine(control.Entity);
             args.Handle();
         }
         else if (args.Function == EngineKeyFunctions.UseSecondary)
@@ -262,62 +226,102 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
         }
         else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
         {
-            _entity.RaisePredictiveEvent(
-                new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: false));
+            EntityManager.RaisePredictiveEvent(
+                new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: false));
             args.Handle();
         }
         else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
         {
-            _entity.RaisePredictiveEvent(new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: true));
+            EntityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(EntityManager.GetNetEntity(control.Entity), altInteract: true));
             args.Handle();
         }
+
+        window.FlagDirty();
     }
 
-    private void OnPieceUnpressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
+    private void OnPieceUnpressed(GUIBoundKeyEventArgs args, StorageWindow window, ItemGridPiece control)
     {
         if (args.Function != ContentKeyFunctions.MoveStoredItem)
             return;
 
-        if (_container?.StorageEntity is not { } storageEnt|| !_entity.TryGetComponent<StorageComponent>(storageEnt, out var storageComp))
+        // Want to get the control under the dragged control.
+        // This means we can drag the original control around (and not hide the original).
+        control.MouseFilter = Control.MouseFilterMode.Ignore;
+        var targetControl = UIManager.MouseGetControl(args.PointerLocation);
+        var targetStorage = targetControl as StorageWindow;
+        control.MouseFilter = Control.MouseFilterMode.Pass;
+
+        var localPlayer = _player.LocalEntity;
+        window.RemoveGrid(control);
+        window.FlagDirty();
+
+        // If we tried to drag it on top of another grid piece then cancel out.
+        if (targetControl is ItemGridPiece || window.StorageEntity is not { } sourceStorage || localPlayer == null)
+        {
+            window.Reclaim(control.Location, control);
+            args.Handle();
+            _menuDragHelper.EndDrag();
             return;
+        }
 
-        if (DraggingGhost is { } draggingGhost)
+        if (_menuDragHelper.IsDragging && DraggingGhost is { } draggingGhost)
         {
             var dragEnt = draggingGhost.Entity;
             var dragLoc = draggingGhost.Location;
-            var itemSys = _entity.System<SharedItemSystem>();
-
-            var position = _container.GetMouseGridPieceLocation(dragEnt, dragLoc);
-            var itemBounding = itemSys.GetAdjustedItemShape(dragEnt, dragLoc).GetBoundingBox();
-            var gridBounding = storageComp.Grid.GetBoundingBox();
-
-            // The extended bounding box for if this is out of the window is the grid bounding box dimensions combined
-            // with the item shape bounding box dimensions. Plus 1 on the left for the sidebar. This makes it so that.
-            // dropping an item on the floor requires dragging it all the way out of the window.
-            var left = gridBounding.Left - itemBounding.Width - 1;
-            var bottom = gridBounding.Bottom - itemBounding.Height;
-            var top = gridBounding.Top;
-            var right = gridBounding.Right;
-            var lenientBounding = new Box2i(left, bottom, right, top);
-
-            if (lenientBounding.Contains(position))
+
+            // Dragging in the same storage
+            // The existing ItemGridPiece just stops rendering but still exists so check if it's hovered.
+            if (targetStorage == window)
             {
-                _entity.RaisePredictiveEvent(new StorageSetItemLocationEvent(
-                    _entity.GetNetEntity(draggingGhost.Entity),
-                    _entity.GetNetEntity(storageEnt),
-                    new ItemStorageLocation(DraggingRotation, position)));
+                var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
+                var newLocation = new ItemStorageLocation(DraggingRotation, position);
+
+                EntityManager.RaisePredictiveEvent(new StorageSetItemLocationEvent(
+                    EntityManager.GetNetEntity(draggingGhost.Entity),
+                    EntityManager.GetNetEntity(sourceStorage),
+                    newLocation));
+
+                window.Reclaim(newLocation, control);
+            }
+            // Dragging to new storage
+            else if (targetStorage?.StorageEntity != null && targetStorage != window)
+            {
+                var position = targetStorage.GetMouseGridPieceLocation(dragEnt, dragLoc);
+                var newLocation = new ItemStorageLocation(DraggingRotation, position);
+
+                // Check it fits and we can move to hand (no free transfers).
+                if (_storage.ItemFitsInGridLocation(
+                        (dragEnt, null),
+                        (targetStorage.StorageEntity.Value, null),
+                        newLocation))
+                {
+                    // Can drop and move.
+                    EntityManager.RaisePredictiveEvent(new StorageTransferItemEvent(
+                        EntityManager.GetNetEntity(dragEnt),
+                        EntityManager.GetNetEntity(targetStorage.StorageEntity.Value),
+                        newLocation));
+
+                    targetStorage.Reclaim(newLocation, control);
+                    DraggingRotation = Angle.Zero;
+                }
+                else
+                {
+                    // Cancel it (rather than dropping).
+                    window.Reclaim(dragLoc, control);
+                }
             }
 
-            _menuDragHelper.EndDrag();
-            _container?.BuildItemPieces();
+            targetStorage?.FlagDirty();
         }
-        else //if we just clicked, then take it out of the bag.
+        // If we just clicked, then take it out of the bag.
+        else
         {
-            _menuDragHelper.EndDrag();
-            _entity.RaisePredictiveEvent(new StorageInteractWithItemEvent(
-                _entity.GetNetEntity(control.Entity),
-                _entity.GetNetEntity(storageEnt)));
+            EntityManager.RaisePredictiveEvent(new StorageInteractWithItemEvent(
+                EntityManager.GetNetEntity(control.Entity),
+                EntityManager.GetNetEntity(sourceStorage)));
         }
+
+        _menuDragHelper.EndDrag();
         args.Handle();
     }
 
@@ -326,14 +330,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
         if (_menuDragHelper.Dragged is not { } dragged)
             return false;
 
+        DraggingGhost!.Orphan();
         DraggingRotation = dragged.Location.Rotation;
-        DraggingGhost = new ItemGridPiece(
-            (dragged.Entity, _entity.GetComponent<ItemComponent>(dragged.Entity)),
-            dragged.Location,
-            _entity);
-        DraggingGhost.MouseFilter = Control.MouseFilterMode.Ignore;
-        DraggingGhost.Visible = true;
-        DraggingGhost.Orphan();
 
         UIManager.PopupRoot.AddChild(DraggingGhost);
         SetDraggingRotation();
@@ -344,6 +342,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
     {
         if (DraggingGhost == null)
             return false;
+
         SetDraggingRotation();
         return true;
     }
@@ -356,7 +355,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
         var offset = ItemGridPiece.GetCenterOffset(
             (DraggingGhost.Entity, null),
             new ItemStorageLocation(DraggingRotation, Vector2i.Zero),
-            _entity);
+            EntityManager);
 
         // I don't know why it divides the position by 2. Hope this helps! -emo
         LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset );
@@ -366,18 +365,13 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
     {
         if (DraggingGhost == null)
             return;
-        DraggingGhost.Visible = false;
-        DraggingGhost = null;
+
         DraggingRotation = Angle.Zero;
     }
 
     public override void FrameUpdate(FrameEventArgs args)
     {
         base.FrameUpdate(args);
-
         _menuDragHelper.Update(args.DeltaSeconds);
-
-        if (!StaticStorageUIEnabled && _container?.Parent != null && _lastContainerPosition != null)
-            _lastContainerPosition = _container.GlobalPosition;
     }
 }
index ccd9636d776413af1b3ed2ec02d33f847b102f34..d9548d0f02b17524a0c16e6f7dace9f6038ab93d 100644 (file)
@@ -144,7 +144,7 @@ namespace Content.Client.Viewport
             _inputManager.ViewportKeyEvent(this, args);
         }
 
-        protected override void Draw(DrawingHandleScreen handle)
+        protected override void Draw(IRenderHandle handle)
         {
             EnsureViewportCreated();
 
@@ -170,7 +170,7 @@ namespace Content.Client.Viewport
             var drawBox = GetDrawBox();
             var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
             _viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
-            handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
+            handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
             _viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
         }
 
index c62f81be0ca38f2137d7ffa8ff68d7459dbf9344..fcefa73a96aeec46947513994d64f6c9387c3b22 100644 (file)
@@ -51,4 +51,23 @@ public sealed partial class CCVars
     /// </summary>
     public static readonly CVarDef<bool> OpaqueStorageWindow =
         CVarDef.Create("control.opaque_storage_background", false, CVar.CLIENTONLY | CVar.ARCHIVE);
+
+    /// <summary>
+    /// Whether or not the storage window has a title of the entity name.
+    /// </summary>
+    public static readonly CVarDef<bool> StorageWindowTitle =
+        CVarDef.Create("control.storage_window_title", false, CVar.CLIENTONLY | CVar.ARCHIVE);
+
+    /// <summary>
+    /// How many storage windows are allowed to be open at once.
+    /// Recommended that you utilise this in conjunction with <see cref="StaticStorageUI"/>
+    /// </summary>
+    public static readonly CVarDef<int> StorageLimit =
+        CVarDef.Create("control.storage_limit", 1, CVar.REPLICATED | CVar.SERVER);
+
+    /// <summary>
+    /// Whether or not storage can be opened recursively.
+    /// </summary>
+    public static readonly CVarDef<bool> NestedStorage =
+        CVarDef.Create("control.nested_storage", true, CVar.REPLICATED | CVar.SERVER);
 }
index 123282fbdbee44d19ff132374144308123c6a242..7bf6d74c60b4d52ebc7de8772aeeda0c49de04f5 100644 (file)
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using Content.Shared.ActionBlocker;
 using Content.Shared.Administration.Logs;
+using Content.Shared.CCVar;
 using Content.Shared.Containers.ItemSlots;
 using Content.Shared.Database;
 using Content.Shared.Destructible;
@@ -27,6 +28,7 @@ using Content.Shared.Verbs;
 using Content.Shared.Whitelist;
 using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
+using Robust.Shared.Configuration;
 using Robust.Shared.Containers;
 using Robust.Shared.GameStates;
 using Robust.Shared.Input.Binding;
@@ -35,36 +37,46 @@ using Robust.Shared.Player;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
 namespace Content.Shared.Storage.EntitySystems;
 
 public abstract class SharedStorageSystem : EntitySystem
 {
-    [Dependency] private readonly IPrototypeManager _prototype = default!;
+    [Dependency] private   readonly IConfigurationManager _cfg = default!;
+    [Dependency] protected readonly IGameTiming Timing = default!;
+    [Dependency] private   readonly IPrototypeManager _prototype = default!;
     [Dependency] protected readonly IRobustRandom Random = default!;
+    [Dependency] private   readonly ISharedAdminLogManager _adminLog = default!;
+
     [Dependency] protected readonly ActionBlockerSystem ActionBlocker = default!;
-    [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
-    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private   readonly EntityLookupSystem _entityLookupSystem = default!;
+    [Dependency] private   readonly EntityWhitelistSystem _whitelistSystem = default!;
+    [Dependency] private   readonly InventorySystem _inventory = default!;
+    [Dependency] private   readonly SharedAppearanceSystem _appearance = default!;
     [Dependency] protected readonly SharedAudioSystem Audio = default!;
-    [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
-    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+    [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
+    [Dependency] private   readonly SharedDoAfterSystem _doAfterSystem = default!;
     [Dependency] protected readonly SharedEntityStorageSystem EntityStorage = default!;
-    [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
-    [Dependency] private readonly InventorySystem _inventory = default!;
+    [Dependency] private   readonly SharedInteractionSystem _interactionSystem = default!;
     [Dependency] protected readonly SharedItemSystem ItemSystem = default!;
-    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
-    [Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!;
-    [Dependency] private readonly SharedStackSystem _stack = default!;
+    [Dependency] private   readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private   readonly SharedHandsSystem _sharedHandsSystem = default!;
+    [Dependency] private   readonly SharedStackSystem _stack = default!;
     [Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
-    [Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
+    [Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
     [Dependency] protected readonly UseDelaySystem UseDelay = default!;
-    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
-    [Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
 
     private EntityQuery<ItemComponent> _itemQuery;
     private EntityQuery<StackComponent> _stackQuery;
     private EntityQuery<TransformComponent> _xformQuery;
+    private EntityQuery<UserInterfaceUserComponent> _userQuery;
+
+    /// <summary>
+    /// Whether we're allowed to go up-down storage via UI.
+    /// </summary>
+    public bool NestedStorage = true;
 
     [ValidatePrototypeId<ItemSizePrototype>]
     public const string DefaultStorageMaxItemSize = "Normal";
@@ -76,10 +88,15 @@ public abstract class SharedStorageSystem : EntitySystem
 
     private ItemSizePrototype _defaultStorageMaxItemSize = default!;
 
+    /// <summary>
+    /// Flag for whether we're checking for nested storage interactions.
+    /// </summary>
+    private bool _nestedCheck;
+
     public bool CheckingCanInsert;
 
-    private List<EntityUid> _entList = new();
-    private HashSet<EntityUid> _entSet = new();
+    private readonly List<EntityUid> _entList = new();
+    private readonly HashSet<EntityUid> _entSet = new();
 
     private readonly List<ItemSizePrototype> _sortedSizes = new();
     private FrozenDictionary<string, ItemSizePrototype> _nextSmallest = FrozenDictionary<string, ItemSizePrototype>.Empty;
@@ -87,6 +104,11 @@ public abstract class SharedStorageSystem : EntitySystem
     private const string QuickInsertUseDelayID = "quickInsert";
     private const string OpenUiUseDelayID = "storage";
 
+    /// <summary>
+    /// How many storage windows are allowed to be open at once.
+    /// </summary>
+    private int _openStorageLimit = -1;
+
     protected readonly List<string> CantFillReasons = [];
 
     /// <inheritdoc />
@@ -97,8 +119,11 @@ public abstract class SharedStorageSystem : EntitySystem
         _itemQuery = GetEntityQuery<ItemComponent>();
         _stackQuery = GetEntityQuery<StackComponent>();
         _xformQuery = GetEntityQuery<TransformComponent>();
+        _userQuery = GetEntityQuery<UserInterfaceUserComponent>();
         _prototype.PrototypesReloaded += OnPrototypesReloaded;
 
+        Subs.CVar(_cfg, CCVars.StorageLimit, OnStorageLimitChanged, true);
+
         Subs.BuiEvents<StorageComponent>(StorageComponent.StorageUiKey.Key, subs =>
         {
             subs.Event<BoundUIClosedEvent>(OnBoundUIClosed);
@@ -108,7 +133,6 @@ public abstract class SharedStorageSystem : EntitySystem
         SubscribeLocalEvent<StorageComponent, MapInitEvent>(OnMapInit);
         SubscribeLocalEvent<StorageComponent, GetVerbsEvent<ActivationVerb>>(AddUiVerb);
         SubscribeLocalEvent<StorageComponent, ComponentGetState>(OnStorageGetState);
-        SubscribeLocalEvent<StorageComponent, ComponentHandleState>(OnStorageHandleState);
         SubscribeLocalEvent<StorageComponent, ComponentInit>(OnComponentInit, before: new[] { typeof(SharedContainerSystem) });
         SubscribeLocalEvent<StorageComponent, GetVerbsEvent<UtilityVerb>>(AddTransferVerbs);
         SubscribeLocalEvent<StorageComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ItemSlotsSystem) });
@@ -116,6 +140,7 @@ public abstract class SharedStorageSystem : EntitySystem
         SubscribeLocalEvent<StorageComponent, OpenStorageImplantEvent>(OnImplantActivate);
         SubscribeLocalEvent<StorageComponent, AfterInteractEvent>(AfterInteract);
         SubscribeLocalEvent<StorageComponent, DestructionEventArgs>(OnDestroy);
+        SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundUIAttempt);
         SubscribeLocalEvent<StorageComponent, BoundUIOpenedEvent>(OnBoundUIOpen);
         SubscribeLocalEvent<StorageComponent, LockToggledEvent>(OnLockToggled);
         SubscribeLocalEvent<MetaDataComponent, StackCountChangedEvent>(OnStackCountChanged);
@@ -126,6 +151,8 @@ public abstract class SharedStorageSystem : EntitySystem
 
         SubscribeLocalEvent<StorageComponent, AreaPickupDoAfterEvent>(OnDoAfter);
 
+        SubscribeAllEvent<OpenNestedStorageEvent>(OnStorageNested);
+        SubscribeAllEvent<StorageTransferItemEvent>(OnStorageTransfer);
         SubscribeAllEvent<StorageInteractWithItemEvent>(OnInteractWithItem);
         SubscribeAllEvent<StorageSetItemLocationEvent>(OnSetItemLocation);
         SubscribeAllEvent<StorageInsertItemIntoLocationEvent>(OnInsertItemIntoLocation);
@@ -138,12 +165,24 @@ public abstract class SharedStorageSystem : EntitySystem
             .Bind(ContentKeyFunctions.OpenBelt, InputCmdHandler.FromDelegate(HandleOpenBelt, handle: false))
             .Register<SharedStorageSystem>();
 
+        Subs.CVar(_cfg, CCVars.NestedStorage, OnNestedStorageCvar, true);
+
         UpdatePrototypeCache();
     }
 
+    private void OnNestedStorageCvar(bool obj)
+    {
+        NestedStorage = obj;
+    }
+
+    private void OnStorageLimitChanged(int obj)
+    {
+        _openStorageLimit = obj;
+    }
+
     private void OnRemove(Entity<StorageComponent> entity, ref ComponentRemove args)
     {
-        _ui.CloseUi(entity.Owner, StorageComponent.StorageUiKey.Key);
+        UI.CloseUi(entity.Owner, StorageComponent.StorageUiKey.Key);
     }
 
     private void OnMapInit(Entity<StorageComponent> entity, ref MapInitEvent args)
@@ -172,28 +211,6 @@ public abstract class SharedStorageSystem : EntitySystem
         };
     }
 
-    private void OnStorageHandleState(EntityUid uid, StorageComponent component, ref ComponentHandleState args)
-    {
-        if (args.Current is not StorageComponentState state)
-            return;
-
-        component.Grid.Clear();
-        component.Grid.AddRange(state.Grid);
-        component.MaxItemSize = state.MaxItemSize;
-        component.Whitelist = state.Whitelist;
-        component.Blacklist = state.Blacklist;
-
-        component.StoredItems.Clear();
-
-        foreach (var (nent, location) in state.StoredItems)
-        {
-            var ent = EnsureEntity<StorageComponent>(nent, uid);
-            component.StoredItems[ent] = location;
-        }
-
-        component.SavedLocations = state.SavedLocations;
-    }
-
     public override void Shutdown()
     {
         _prototype.PrototypesReloaded -= OnPrototypesReloaded;
@@ -228,7 +245,7 @@ public abstract class SharedStorageSystem : EntitySystem
 
     private void OnComponentInit(EntityUid uid, StorageComponent storageComp, ComponentInit args)
     {
-        storageComp.Container = _containerSystem.EnsureContainer<Container>(uid, StorageComponent.ContainerId);
+        storageComp.Container = ContainerSystem.EnsureContainer<Container>(uid, StorageComponent.ContainerId);
         UpdateAppearance((uid, storageComp, null));
     }
 
@@ -247,7 +264,7 @@ public abstract class SharedStorageSystem : EntitySystem
         // close ui
         foreach (var entity in storageComp.Container.ContainedEntities)
         {
-            _ui.CloseUis(entity, actor);
+            UI.CloseUis(entity, actor);
         }
     }
 
@@ -256,7 +273,7 @@ public abstract class SharedStorageSystem : EntitySystem
         CloseNestedInterfaces(uid, args.Actor, storageComp);
 
         // If UI is closed for everyone
-        if (!_ui.IsUiOpen(uid, args.UiKey))
+        if (!UI.IsUiOpen(uid, args.UiKey))
         {
             UpdateAppearance((uid, storageComp, null));
             Audio.PlayPredicted(storageComp.StorageCloseSound, uid, args.Actor);
@@ -269,7 +286,7 @@ public abstract class SharedStorageSystem : EntitySystem
             return;
 
         // Does this player currently have the storage UI open?
-        var uiOpen = _ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User);
+        var uiOpen = UI.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User);
 
         ActivationVerb verb = new()
         {
@@ -277,7 +294,7 @@ public abstract class SharedStorageSystem : EntitySystem
             {
                 if (uiOpen)
                 {
-                    _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
+                    UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
                 }
                 else
                 {
@@ -315,16 +332,16 @@ public abstract class SharedStorageSystem : EntitySystem
         if (!CanInteract(entity, (uid, storageComp), silent: silent))
             return;
 
+        if (!UI.TryOpenUi(uid, StorageComponent.StorageUiKey.Key, entity))
+            return;
+
         if (!silent)
         {
-            if (!_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key))
-                Audio.PlayPredicted(storageComp.StorageOpenSound, uid, entity);
+            Audio.PlayPredicted(storageComp.StorageOpenSound, uid, entity);
 
             if (useDelay != null)
                 UseDelay.TryResetDelay((uid, useDelay), id: OpenUiUseDelayID);
         }
-
-        _ui.OpenUi(uid, StorageComponent.StorageUiKey.Key, entity);
     }
 
     public virtual void UpdateUI(Entity<StorageComponent?> entity) {}
@@ -384,18 +401,43 @@ public abstract class SharedStorageSystem : EntitySystem
             return;
 
         // Toggle
-        if (_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User))
+        if (UI.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User))
         {
-            _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
+            UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
         }
         else
         {
-            OpenStorageUI(uid, args.User, storageComp, false);
+            // Handle recursively opening nested storages.
+            if (ContainerSystem.TryGetContainingContainer((args.Target, null, null), out var container) &&
+                UI.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, args.User))
+            {
+                _nestedCheck = true;
+                HideStorageWindow(container.Owner, args.User);
+                OpenStorageUI(uid, args.User, storageComp, silent: true);
+                _nestedCheck = false;
+            }
+            else
+            {
+                // If you need something more sophisticated for multi-UI you'll need to code some smarter
+                // interactions.
+                if (_openStorageLimit == 1)
+                    UI.CloseUserUis<StorageComponent.StorageUiKey>(args.User);
+
+                OpenStorageUI(uid, args.User, storageComp, silent: false);
+            }
         }
 
         args.Handled = true;
     }
 
+    protected virtual void HideStorageWindow(EntityUid uid, EntityUid actor)
+    {
+    }
+
+    protected virtual void ShowStorageWindow(EntityUid uid, EntityUid actor)
+    {
+    }
+
     /// <summary>
     /// Specifically for storage implants.
     /// </summary>
@@ -404,10 +446,10 @@ public abstract class SharedStorageSystem : EntitySystem
         if (args.Handled)
             return;
 
-        var uiOpen = _ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.Performer);
+        var uiOpen = UI.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.Performer);
 
         if (uiOpen)
-            _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.Performer);
+            UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.Performer);
         else
             OpenStorageUI(uid, args.Performer, storageComp, false);
 
@@ -474,7 +516,7 @@ public abstract class SharedStorageSystem : EntitySystem
             if (args.Target is not { Valid: true } target)
                 return;
 
-            if (_containerSystem.IsEntityInContainer(target)
+            if (ContainerSystem.IsEntityInContainer(target)
                 || target == args.User
                 || !_itemQuery.HasComponent(target))
             {
@@ -525,7 +567,7 @@ public abstract class SharedStorageSystem : EntitySystem
             var entity = GetEntity(args.Entities[i]);
 
             // Check again, situation may have changed for some entities, but we'll still pick up any that are valid
-            if (_containerSystem.IsEntityInContainer(entity)
+            if (ContainerSystem.IsEntityInContainer(entity)
                 || entity == args.Args.User
                 || !_itemQuery.HasComponent(entity))
             {
@@ -570,7 +612,7 @@ public abstract class SharedStorageSystem : EntitySystem
 
     private void OnReclaimed(EntityUid uid, StorageComponent storageComp, GotReclaimedEvent args)
     {
-        _containerSystem.EmptyContainer(storageComp.Container, destination: args.ReclaimerCoordinates);
+        ContainerSystem.EmptyContainer(storageComp.Container, destination: args.ReclaimerCoordinates);
     }
 
     private void OnDestroy(EntityUid uid, StorageComponent storageComp, DestructionEventArgs args)
@@ -578,7 +620,7 @@ public abstract class SharedStorageSystem : EntitySystem
         var coordinates = TransformSystem.GetMoverCoordinates(uid);
 
         // Being destroyed so need to recalculate.
-        _containerSystem.EmptyContainer(storageComp.Container, destination: coordinates);
+        ContainerSystem.EmptyContainer(storageComp.Container, destination: coordinates);
     }
 
     /// <summary>
@@ -638,6 +680,54 @@ public abstract class SharedStorageSystem : EntitySystem
         TrySetItemStorageLocation(item!, storage!, msg.Location);
     }
 
+    private void OnStorageNested(OpenNestedStorageEvent msg, EntitySessionEventArgs args)
+    {
+        if (!NestedStorage)
+            return;
+
+        if (!TryGetEntity(msg.InteractedItemUid, out var itemEnt))
+            return;
+
+        _nestedCheck = true;
+
+        var result = ValidateInput(args,
+            msg.StorageUid,
+            msg.InteractedItemUid,
+            out var player,
+            out var storage,
+            out var item);
+
+        if (!result)
+        {
+            _nestedCheck = false;
+            return;
+        }
+
+        HideStorageWindow(storage.Owner, player.Owner);
+        OpenStorageUI(item.Owner, player.Owner, silent: true);
+        _nestedCheck = false;
+    }
+
+    private void OnStorageTransfer(StorageTransferItemEvent msg, EntitySessionEventArgs args)
+    {
+        if (!TryGetEntity(msg.ItemEnt, out var itemEnt))
+            return;
+
+        var localPlayer = args.SenderSession.AttachedEntity;
+
+        if (!TryComp(localPlayer, out HandsComponent? handsComp) || !_sharedHandsSystem.TryPickup(localPlayer.Value, itemEnt.Value, handsComp: handsComp, animate: false))
+            return;
+
+        if (!ValidateInput(args, msg.StorageEnt, msg.ItemEnt, out var player, out var storage, out var item, held: true))
+            return;
+
+        _adminLog.Add(
+            LogType.Storage,
+            LogImpact.Low,
+            $"{ToPrettyString(player):player} is inserting {ToPrettyString(item):item} into {ToPrettyString(storage):storage}");
+        InsertAt(storage!, item!, msg.Location, out _, player, stackAutomatically: false);
+    }
+
     private void OnInsertItemIntoLocation(StorageInsertItemIntoLocationEvent msg, EntitySessionEventArgs args)
     {
         if (!ValidateInput(args, msg.StorageEnt, msg.ItemEnt, out var player, out var storage, out var item, held: true))
@@ -658,9 +748,46 @@ public abstract class SharedStorageSystem : EntitySystem
         SaveItemLocation(storage!, item.Owner);
     }
 
-    private void OnBoundUIOpen(EntityUid uid, StorageComponent storageComp, BoundUIOpenedEvent args)
+    private void OnBoundUIOpen(Entity<StorageComponent> ent, ref BoundUIOpenedEvent args)
     {
-        UpdateAppearance((uid, storageComp, null));
+        UpdateAppearance((ent.Owner, ent.Comp, null));
+    }
+
+    private void OnBoundUIAttempt(BoundUserInterfaceMessageAttempt args)
+    {
+        if (args.UiKey is not StorageComponent.StorageUiKey.Key ||
+            _openStorageLimit == -1 ||
+            _nestedCheck ||
+            args.Message is not OpenBoundInterfaceMessage)
+            return;
+
+        var uid = args.Target;
+        var actor = args.Actor;
+        var count = 0;
+
+        if (_userQuery.TryComp(actor, out var userComp))
+        {
+            foreach (var (ui, keys) in userComp.OpenInterfaces)
+            {
+                if (ui == uid)
+                    continue;
+
+                foreach (var key in keys)
+                {
+                    if (key is not StorageComponent.StorageUiKey)
+                        continue;
+
+                    count++;
+
+                    if (count >= _openStorageLimit)
+                    {
+                        args.Cancel();
+                    }
+
+                    break;
+                }
+            }
+        }
     }
 
     private void OnEntInserted(Entity<StorageComponent> entity, ref EntInsertedIntoContainerMessage args)
@@ -676,7 +803,7 @@ public abstract class SharedStorageSystem : EntitySystem
         {
             if (!TryGetAvailableGridSpace((entity.Owner, entity.Comp), (args.Entity, null), out var location))
             {
-                _containerSystem.Remove(args.Entity, args.Container, force: true);
+                ContainerSystem.Remove(args.Entity, args.Container, force: true);
                 return;
             }
 
@@ -738,7 +865,7 @@ public abstract class SharedStorageSystem : EntitySystem
         var capacity = storage.Grid.GetArea();
         var used = GetCumulativeItemAreas((uid, storage));
 
-        var isOpen = _ui.IsUiOpen(entity.Owner, StorageComponent.StorageUiKey.Key);
+        var isOpen = UI.IsUiOpen(entity.Owner, StorageComponent.StorageUiKey.Key);
 
         _appearance.SetData(uid, StorageVisuals.StorageUsed, used, appearance);
         _appearance.SetData(uid, StorageVisuals.Capacity, capacity, appearance);
@@ -848,7 +975,7 @@ public abstract class SharedStorageSystem : EntitySystem
         }
 
         CheckingCanInsert = true;
-        if (!_containerSystem.CanInsert(insertEnt, storageComp.Container))
+        if (!ContainerSystem.CanInsert(insertEnt, storageComp.Container))
         {
             CheckingCanInsert = false;
             reason = null;
@@ -949,7 +1076,7 @@ public abstract class SharedStorageSystem : EntitySystem
 
         if (!stackAutomatically || !_stackQuery.TryGetComponent(insertEnt, out var insertStack))
         {
-            if (!_containerSystem.Insert(insertEnt, storageComp.Container))
+            if (!ContainerSystem.Insert(insertEnt, storageComp.Container))
                 return false;
 
             if (playSound)
@@ -975,7 +1102,7 @@ public abstract class SharedStorageSystem : EntitySystem
 
         // Still stackable remaining
         if (insertStack.Count > 0
-            && !_containerSystem.Insert(insertEnt, storageComp.Container)
+            && !ContainerSystem.Insert(insertEnt, storageComp.Container)
             && toInsertCount == insertStack.Count)
         {
             // Failed to insert anything.
@@ -1054,6 +1181,7 @@ public abstract class SharedStorageSystem : EntitySystem
             return false;
 
         storageEnt.Comp.StoredItems[itemEnt] = location;
+        UpdateUI(storageEnt);
         Dirty(storageEnt, storageEnt.Comp);
         return true;
     }
@@ -1186,6 +1314,7 @@ public abstract class SharedStorageSystem : EntitySystem
         }
 
         Dirty(ent, ent.Comp);
+        UpdateUI((ent.Owner, ent.Comp));
     }
 
     /// <summary>
@@ -1357,16 +1486,16 @@ public abstract class SharedStorageSystem : EntitySystem
             return;
 
         // Gets everyone looking at the UI
-        foreach (var actor in _ui.GetActors(uid, StorageComponent.StorageUiKey.Key).ToList())
+        foreach (var actor in UI.GetActors(uid, StorageComponent.StorageUiKey.Key).ToList())
         {
             if (!CanInteract(actor, (uid, component)))
-                _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor);
+                UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor);
         }
     }
 
     private void OnStackCountChanged(EntityUid uid, MetaDataComponent component, StackCountChangedEvent args)
     {
-        if (_containerSystem.TryGetContainingContainer((uid, null, component), out var container) &&
+        if (ContainerSystem.TryGetContainingContainer((uid, null, component), out var container) &&
             container.ID == StorageComponent.ContainerId)
         {
             UpdateAppearance(container.Owner);
@@ -1398,13 +1527,13 @@ public abstract class SharedStorageSystem : EntitySystem
         if (!ActionBlocker.CanInteract(playerEnt, storageEnt))
             return;
 
-        if (!_ui.IsUiOpen(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt))
+        if (!UI.IsUiOpen(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt))
         {
             OpenStorageUI(storageEnt.Value, playerEnt, silent: false);
         }
         else
         {
-            _ui.CloseUi(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt);
+            UI.CloseUi(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt);
         }
     }
 
@@ -1459,7 +1588,7 @@ public abstract class SharedStorageSystem : EntitySystem
         // TODO STORAGE use BUI events
         // This would automatically validate that the UI is open & that the user can interact.
         // However, we still need to manually validate that items being used are in the users hands or in the storage.
-        if (!_ui.IsUiOpen(storageUid.Value, StorageComponent.StorageUiKey.Key, playerUid))
+        if (!UI.IsUiOpen(storageUid.Value, StorageComponent.StorageUiKey.Key, playerUid))
             return false;
 
         if (!ActionBlocker.CanInteract(playerUid, storageUid))
index 52547454a076dc458fabb5628f5120833a32a1df..c59f7ab00e914af6f034ff77f84e9b750e524667 100644 (file)
@@ -148,6 +148,19 @@ namespace Content.Shared.Storage
         }
     }
 
+    [Serializable, NetSerializable]
+    public sealed class OpenNestedStorageEvent : EntityEventArgs
+    {
+        public readonly NetEntity InteractedItemUid;
+        public readonly NetEntity StorageUid;
+
+        public OpenNestedStorageEvent(NetEntity interactedItemUid, NetEntity storageUid)
+        {
+            InteractedItemUid = interactedItemUid;
+            StorageUid = storageUid;
+        }
+    }
+
     [Serializable, NetSerializable]
     public sealed class StorageInteractWithItemEvent : EntityEventArgs
     {
@@ -179,6 +192,26 @@ namespace Content.Shared.Storage
         }
     }
 
+    [Serializable, NetSerializable]
+    public sealed class StorageTransferItemEvent : EntityEventArgs
+    {
+        public readonly NetEntity ItemEnt;
+
+        /// <summary>
+        /// Target storage to receive the transfer.
+        /// </summary>
+        public readonly NetEntity StorageEnt;
+
+        public readonly ItemStorageLocation Location;
+
+        public StorageTransferItemEvent(NetEntity itemEnt, NetEntity storageEnt, ItemStorageLocation location)
+        {
+            ItemEnt = itemEnt;
+            StorageEnt = storageEnt;
+            Location = location;
+        }
+    }
+
     [Serializable, NetSerializable]
     public sealed class StorageInsertItemIntoLocationEvent : EntityEventArgs
     {
index ee906af16e136c7e09930d88872ab9bba3137c8e..c4a5752c2affee874f0b9be3b07b86a55a91bf0a 160000 (submodule)
@@ -1 +1 @@
-Subproject commit ee906af16e136c7e09930d88872ab9bba3137c8e
+Subproject commit c4a5752c2affee874f0b9be3b07b86a55a91bf0a