From e971d6e07739907a9dda697ab4bc2cbdb9c0d885 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:17:55 +1100 Subject: [PATCH] Revert engine reverties (#34968) --- .../Storage/StorageBoundUserInterface.cs | 65 +++- .../Storage/Systems/StorageSystem.cs | 134 ++++---- .../Systems/Hotbar/HotbarUIController.cs | 3 +- .../Systems/Hotbar/Widgets/HotbarGui.xaml | 5 +- .../Systems/Hotbar/Widgets/HotbarGui.xaml.cs | 2 +- .../Systems/Storage/Controls/ItemGridPiece.cs | 7 +- .../{StorageContainer.cs => StorageWindow.cs} | 318 ++++++++++++++---- .../Systems/Storage/StorageUIController.cs | 314 +++++++++-------- Content.Client/Viewport/ScalingViewport.cs | 4 +- Content.Shared/CCVar/CCVars.Interactions.cs | 19 ++ .../EntitySystems/SharedStorageSystem.cs | 269 +++++++++++---- Content.Shared/Storage/StorageComponent.cs | 33 ++ RobustToolbox | 2 +- 13 files changed, 775 insertions(+), 400 deletions(-) rename Content.Client/UserInterface/Systems/Storage/Controls/{StorageContainer.cs => StorageWindow.cs} (64%) diff --git a/Content.Client/Storage/StorageBoundUserInterface.cs b/Content.Client/Storage/StorageBoundUserInterface.cs index b90977cbb4da..bacc90eabffc 100644 --- a/Content.Client/Storage/StorageBoundUserInterface.cs +++ b/Content.Client/Storage/StorageBoundUserInterface.cs @@ -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(); } protected override void Open() { base.Open(); - if (_entManager.TryGetComponent(Owner, out var comp)) - _storage.OpenStorageWindow((Owner, comp)); + _window = IoCManager.Resolve() + .GetUIController() + .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(); } } diff --git a/Content.Client/Storage/Systems/StorageSystem.cs b/Content.Client/Storage/Systems/StorageSystem.cs index eea7b9ec797f..ab4d9407b222 100644 --- a/Content.Client/Storage/Systems/StorageSystem.cs +++ b/Content.Client/Storage/Systems/StorageSystem.cs @@ -4,7 +4,8 @@ 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> _openStorages = new(); - public int OpenStorageAmount => _openStorages.Count; - - public event Action>? StorageUpdated; - public event Action?>? StorageOrderChanged; + private Dictionary _oldStoredItems = new(); public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnStorageHandleState); SubscribeNetworkEvent(HandlePickupAnimation); SubscribeAllEvent(HandleAnimatingInsertingEntities); } - public override void UpdateUI(Entity 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 entity) - { - if (_openStorages.Contains(entity)) - { - if (_openStorages.LastOrDefault() == entity) - { - CloseStorageWindow((entity, entity.Comp)); - } - else - { - var storages = new ValueList>(_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? last = _openStorages.LastOrDefault(); - StorageOrderChanged?.Invoke(last); - } - - public void CloseStorageWindow(Entity 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(nent, uid); + component.StoredItems[ent] = location; + } - var storages = new ValueList>(_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? 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>(_openStorages); - var reverseStorages = storages.Reverse(); - - foreach (var storage in reverseStorages) + if (uiDirty && UI.TryGetOpenUi(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(container.Owner, StorageComponent.StorageUiKey.Key, out var containerBui)) + { + containerBui.Hide(); + } + else + { + storageBui.Show(); + } } } - private void CloseStorageBoundUserInterface(Entity entity) + public override void UpdateUI(Entity entity) { - if (!Resolve(entity, ref entity.Comp, false)) - return; - - if (entity.Comp.ClientOpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui) - return; + if (UI.TryGetOpenUi(entity.Owner, StorageComponent.StorageUiKey.Key, out var sBui)) + { + sBui.Refresh(); + } + } - bui.Close(); + protected override void HideStorageWindow(EntityUid uid, EntityUid actor) + { + if (UI.TryGetOpenUi(uid, StorageComponent.StorageUiKey.Key, out var storageBui)) + { + storageBui.Hide(); + } } - private void OnShutdown(Entity ent, ref ComponentShutdown args) + protected override void ShowStorageWindow(EntityUid uid, EntityUid actor) { - CloseStorageWindow((ent, ent.Comp)); + if (UI.TryGetOpenUi(uid, StorageComponent.StorageUiKey.Key, out var storageBui)) + { + storageBui.Show(); + } } /// @@ -142,7 +124,7 @@ public void PickupAnimation(EntityUid item, EntityCoordinates initialCoords, Ent { if (!_timing.IsFirstTimePredicted) return; - + if (TransformSystem.InRange(finalCoords, initialCoords, 0.1f) || !Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId)) { diff --git a/Content.Client/UserInterface/Systems/Hotbar/HotbarUIController.cs b/Content.Client/UserInterface/Systems/Hotbar/HotbarUIController.cs index 2f266cfdd6bf..b89115da86b9 100644 --- a/Content.Client/UserInterface/Systems/Hotbar/HotbarUIController.cs +++ b/Content.Client/UserInterface/Systems/Hotbar/HotbarUIController.cs @@ -31,13 +31,12 @@ private void OnScreenLoad() ReloadHotbar(); } - public void Setup(HandsContainer handsContainer, StorageContainer storageContainer) + public void Setup(HandsContainer handsContainer) { _inventory = UIManager.GetUIController(); _hands = UIManager.GetUIController(); _storage = UIManager.GetUIController(); _hands.RegisterHandContainer(handsContainer); - _storage.RegisterStorageContainer(storageContainer); } public void ReloadHotbar() diff --git a/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml b/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml index 00ba1878b48d..153e02bd5585 100644 --- a/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml +++ b/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml @@ -1,7 +1,6 @@  - (); - hotbarController.Setup(HandContainer, StoragePanel); + hotbarController.Setup(HandContainer); LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin); } diff --git a/Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs b/Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs index dd9986e4c6e6..f4c4158b5ced 100644 --- a/Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs +++ b/Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs @@ -59,7 +59,7 @@ public ItemGridPiece(Entity entity, ItemStorageLocation location, Location = location; Visible = true; - MouseFilter = MouseFilterMode.Pass; + MouseFilter = MouseFilterMode.Stop; TooltipSupplier = SupplyTooltip; @@ -105,8 +105,11 @@ protected override void Draw(DrawingHandleScreen handle) 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().GetAdjustedItemShape((Entity, itemComponent), Location.Rotation, Vector2i.Zero); var boundingGrid = adjustedShape.GetBoundingBox(); diff --git a/Content.Client/UserInterface/Systems/Storage/Controls/StorageContainer.cs b/Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs similarity index 64% rename from Content.Client/UserInterface/Systems/Storage/Controls/StorageContainer.cs rename to Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs index a9d7e098265a..88b4c06d72c0 100644 --- a/Content.Client/UserInterface/Systems/Storage/Controls/StorageContainer.cs +++ b/Content.Client/UserInterface/Systems/Storage/Controls/StorageWindow.cs @@ -3,7 +3,9 @@ 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.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 _pieces = new(); + private readonly List _controlGrid = new(); + + private ValueList _contained = new(); + private ValueList _toRemove = new(); + + private TextureButton? _backButton; + + private bool _isDirty; + public event Action? OnPiecePressed; public event Action? 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(); @@ -63,6 +82,7 @@ public StorageContainer() _sidebar = new GridContainer { + Name = "SideBar", HSeparationOverride = 0, VSeparationOverride = 0, Columns = 1 @@ -70,21 +90,48 @@ public StorageContainer() _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 void UpdateContainer(Entity? entity) if (entity == null) return; + if (UserInterfaceManager.GetUIController().WindowTitle) + { + _titleLabel.Text = Identity.Name(entity.Value, _entity); + _titleContainer.Visible = true; + } + else + { + _titleContainer.Visible = false; + } + BuildGridRepresentation(); } private void BuildGridRepresentation() { - if (!_entity.TryGetComponent(StorageEntity, out var comp) || !comp.Grid.Any()) + if (!_entity.TryGetComponent(StorageEntity, out var comp) || comp.Grid.Count == 0) return; var boundingGrid = comp.Grid.GetBoundingBox(); @@ -144,12 +201,13 @@ private void BuildGridRepresentation() #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().OpenStorageAmount == 1 - ?_exitTexture - : _backTexture, + Name = "ExitButton", + TextureNormal = _exitTexture, Scale = new Vector2(2, 2), }; exitButton.OnPressed += _ => @@ -165,8 +223,10 @@ private void BuildGridRepresentation() args.Handle(); } }; + var exitContainer = new BoxContainer { + Name = "ExitContainer", Children = { new TextureRect @@ -182,28 +242,70 @@ private void BuildGridRepresentation() } } }; + _sidebar.AddChild(exitContainer); - for (var i = 0; i < boundingGrid.Height - 1; i++) + var offset = 2; + + if (_entity.System().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(); + + if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) && + _entity.TryGetComponent(container.Owner, out StorageComponent? storage)) + { + Close(); + + if (_entity.System() + .TryGetOpenUi(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 void BuildBackground() } } + 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(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(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(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 @@ protected override void FrameUpdate(FrameEventArgs args) if (!IsOpen) return; + if (_isDirty) + { + _isDirty = false; + BuildItemPieces(); + } + + var containerSystem = _entity.System(); + + if (_backButton != null) + { + if (StorageEntity != null && _entity.System().NestedStorage) + { + if (containerSystem.TryGetContainingContainer(StorageEntity.Value, out var container) && + _entity.HasComponent(container.Owner)) + { + _backButton.Visible = true; + } + else + { + _backButton.Visible = false; + } + } + // Hide the button. + else + { + _backButton.Visible = false; + } + } + var itemSystem = _entity.System(); var storageSystem = _entity.System(); var handsSystem = _entity.System(); @@ -324,7 +512,7 @@ protected override void FrameUpdate(FrameEventArgs args) 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(StorageEntity, out var storageComponent)) @@ -373,7 +561,7 @@ protected override void FrameUpdate(FrameEventArgs args) continue; float spot = 0; - var marked = new List(); + var marked = new ValueList(); foreach (var location in locations.Value) { @@ -500,14 +688,4 @@ protected override void KeyBindDown(GUIBoundKeyEventArgs args) } } } - - public override void Close() - { - base.Close(); - - if (StorageEntity == null) - return; - - _entity.System().CloseStorageWindow(StorageEntity.Value); - } } diff --git a/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs b/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs index 1e61ad983806..5c3f0479827c 100644 --- a/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs +++ b/Content.Client/UserInterface/Systems/Storage/StorageUIController.cs @@ -2,6 +2,7 @@ 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.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 { + /* + * 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 _menuDragHelper; - private StorageContainer? _container; - - private Vector2? _lastContainerPosition; - - private HotbarGui? Hotbar => UIManager.GetActiveUIWidgetOrNull(); - 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 _menuDragHelper.IsDragging; public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged; + public bool WindowTitle { get; private set; } = false; + public StorageUIController() { _menuDragHelper = new DragDropHelper(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag); @@ -52,106 +59,88 @@ public override void Initialize() { 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? 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(); - 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(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()?.StorageContainer.AddChild(window); } else { - _ui.WindowRoot.AddChild(_container); + window.OpenCenteredLeft(); } - if (_entity.TryGetComponent(_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 @@ private void OnMiddleMouse(KeyEventArgs keyEvent, KeyEventType type) binding.Mod3 == Keyboard.Key.Control)) return; - if (!IsDragging && _entity.System().GetActiveHandEntity() == null) + if (!IsDragging && EntityManager.System().GetActiveHandEntity() == null) return; //clamp it to a cardinal. @@ -198,43 +187,18 @@ private void OnMiddleMouse(KeyEventArgs keyEvent, KeyEventType type) 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 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 @@ private void OnPiecePressed(GUIBoundKeyEventArgs args, ItemGridPiece control) } 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().DoExamine(control.Entity); + EntityManager.System().DoExamine(control.Entity); args.Handle(); } else if (args.Function == EngineKeyFunctions.UseSecondary) @@ -262,62 +226,102 @@ private void OnPiecePressed(GUIBoundKeyEventArgs args, ItemGridPiece control) } 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(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(); - - 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 @@ private bool OnMenuBeginDrag() if (_menuDragHelper.Dragged is not { } dragged) return false; + DraggingGhost!.Orphan(); DraggingRotation = dragged.Location.Rotation; - DraggingGhost = new ItemGridPiece( - (dragged.Entity, _entity.GetComponent(dragged.Entity)), - dragged.Location, - _entity); - DraggingGhost.MouseFilter = Control.MouseFilterMode.Ignore; - DraggingGhost.Visible = true; - DraggingGhost.Orphan(); UIManager.PopupRoot.AddChild(DraggingGhost); SetDraggingRotation(); @@ -344,6 +342,7 @@ private bool OnMenuContinueDrag(float frameTime) { if (DraggingGhost == null) return false; + SetDraggingRotation(); return true; } @@ -356,7 +355,7 @@ private void SetDraggingRotation() 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 @@ private void OnMenuEndDrag() { 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; } } diff --git a/Content.Client/Viewport/ScalingViewport.cs b/Content.Client/Viewport/ScalingViewport.cs index ccd9636d7764..d9548d0f02b1 100644 --- a/Content.Client/Viewport/ScalingViewport.cs +++ b/Content.Client/Viewport/ScalingViewport.cs @@ -144,7 +144,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) _inputManager.ViewportKeyEvent(this, args); } - protected override void Draw(DrawingHandleScreen handle) + protected override void Draw(IRenderHandle handle) { EnsureViewportCreated(); @@ -170,7 +170,7 @@ protected override void Draw(DrawingHandleScreen handle) 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); } diff --git a/Content.Shared/CCVar/CCVars.Interactions.cs b/Content.Shared/CCVar/CCVars.Interactions.cs index c62f81be0ca3..fcefa73a96ae 100644 --- a/Content.Shared/CCVar/CCVars.Interactions.cs +++ b/Content.Shared/CCVar/CCVars.Interactions.cs @@ -51,4 +51,23 @@ public sealed partial class CCVars /// public static readonly CVarDef OpaqueStorageWindow = CVarDef.Create("control.opaque_storage_background", false, CVar.CLIENTONLY | CVar.ARCHIVE); + + /// + /// Whether or not the storage window has a title of the entity name. + /// + public static readonly CVarDef StorageWindowTitle = + CVarDef.Create("control.storage_window_title", false, CVar.CLIENTONLY | CVar.ARCHIVE); + + /// + /// How many storage windows are allowed to be open at once. + /// Recommended that you utilise this in conjunction with + /// + public static readonly CVarDef StorageLimit = + CVarDef.Create("control.storage_limit", 1, CVar.REPLICATED | CVar.SERVER); + + /// + /// Whether or not storage can be opened recursively. + /// + public static readonly CVarDef NestedStorage = + CVarDef.Create("control.nested_storage", true, CVar.REPLICATED | CVar.SERVER); } diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 123282fbdbee..7bf6d74c60b4 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -3,6 +3,7 @@ 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.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.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 _itemQuery; private EntityQuery _stackQuery; private EntityQuery _xformQuery; + private EntityQuery _userQuery; + + /// + /// Whether we're allowed to go up-down storage via UI. + /// + public bool NestedStorage = true; [ValidatePrototypeId] public const string DefaultStorageMaxItemSize = "Normal"; @@ -76,10 +88,15 @@ public abstract class SharedStorageSystem : EntitySystem private ItemSizePrototype _defaultStorageMaxItemSize = default!; + /// + /// Flag for whether we're checking for nested storage interactions. + /// + private bool _nestedCheck; + public bool CheckingCanInsert; - private List _entList = new(); - private HashSet _entSet = new(); + private readonly List _entList = new(); + private readonly HashSet _entSet = new(); private readonly List _sortedSizes = new(); private FrozenDictionary _nextSmallest = FrozenDictionary.Empty; @@ -87,6 +104,11 @@ public abstract class SharedStorageSystem : EntitySystem private const string QuickInsertUseDelayID = "quickInsert"; private const string OpenUiUseDelayID = "storage"; + /// + /// How many storage windows are allowed to be open at once. + /// + private int _openStorageLimit = -1; + protected readonly List CantFillReasons = []; /// @@ -97,8 +119,11 @@ public override void Initialize() _itemQuery = GetEntityQuery(); _stackQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); + _userQuery = GetEntityQuery(); _prototype.PrototypesReloaded += OnPrototypesReloaded; + Subs.CVar(_cfg, CCVars.StorageLimit, OnStorageLimitChanged, true); + Subs.BuiEvents(StorageComponent.StorageUiKey.Key, subs => { subs.Event(OnBoundUIClosed); @@ -108,7 +133,6 @@ public override void Initialize() SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent>(AddUiVerb); SubscribeLocalEvent(OnStorageGetState); - SubscribeLocalEvent(OnStorageHandleState); SubscribeLocalEvent(OnComponentInit, before: new[] { typeof(SharedContainerSystem) }); SubscribeLocalEvent>(AddTransferVerbs); SubscribeLocalEvent(OnInteractUsing, after: new[] { typeof(ItemSlotsSystem) }); @@ -116,6 +140,7 @@ public override void Initialize() SubscribeLocalEvent(OnImplantActivate); SubscribeLocalEvent(AfterInteract); SubscribeLocalEvent(OnDestroy); + SubscribeLocalEvent(OnBoundUIAttempt); SubscribeLocalEvent(OnBoundUIOpen); SubscribeLocalEvent(OnLockToggled); SubscribeLocalEvent(OnStackCountChanged); @@ -126,6 +151,8 @@ public override void Initialize() SubscribeLocalEvent(OnDoAfter); + SubscribeAllEvent(OnStorageNested); + SubscribeAllEvent(OnStorageTransfer); SubscribeAllEvent(OnInteractWithItem); SubscribeAllEvent(OnSetItemLocation); SubscribeAllEvent(OnInsertItemIntoLocation); @@ -138,12 +165,24 @@ public override void Initialize() .Bind(ContentKeyFunctions.OpenBelt, InputCmdHandler.FromDelegate(HandleOpenBelt, handle: false)) .Register(); + 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 entity, ref ComponentRemove args) { - _ui.CloseUi(entity.Owner, StorageComponent.StorageUiKey.Key); + UI.CloseUi(entity.Owner, StorageComponent.StorageUiKey.Key); } private void OnMapInit(Entity entity, ref MapInitEvent args) @@ -172,28 +211,6 @@ private void OnStorageGetState(EntityUid uid, StorageComponent component, ref Co }; } - 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(nent, uid); - component.StoredItems[ent] = location; - } - - component.SavedLocations = state.SavedLocations; - } - public override void Shutdown() { _prototype.PrototypesReloaded -= OnPrototypesReloaded; @@ -228,7 +245,7 @@ private void UpdatePrototypeCache() private void OnComponentInit(EntityUid uid, StorageComponent storageComp, ComponentInit args) { - storageComp.Container = _containerSystem.EnsureContainer(uid, StorageComponent.ContainerId); + storageComp.Container = ContainerSystem.EnsureContainer(uid, StorageComponent.ContainerId); UpdateAppearance((uid, storageComp, null)); } @@ -247,7 +264,7 @@ private void CloseNestedInterfaces(EntityUid uid, EntityUid actor, StorageCompon // close ui foreach (var entity in storageComp.Container.ContainedEntities) { - _ui.CloseUis(entity, actor); + UI.CloseUis(entity, actor); } } @@ -256,7 +273,7 @@ private void OnBoundUIClosed(EntityUid uid, StorageComponent storageComp, BoundU 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 @@ private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent< 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 @@ private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent< { if (uiOpen) { - _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User); + UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User); } else { @@ -315,16 +332,16 @@ public void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? sto 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 entity) {} @@ -384,18 +401,43 @@ private void OnActivate(EntityUid uid, StorageComponent storageComp, ActivateInW 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(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) + { + } + /// /// Specifically for storage implants. /// @@ -404,10 +446,10 @@ private void OnImplantActivate(EntityUid uid, StorageComponent storageComp, Open 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 @@ private void AfterInteract(EntityUid uid, StorageComponent storageComp, AfterInt 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 @@ private void OnDoAfter(EntityUid uid, StorageComponent component, AreaPickupDoAf 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 @@ private void OnDoAfter(EntityUid uid, StorageComponent component, AreaPickupDoAf 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 @@ private void OnDestroy(EntityUid uid, StorageComponent storageComp, DestructionE var coordinates = TransformSystem.GetMoverCoordinates(uid); // Being destroyed so need to recalculate. - _containerSystem.EmptyContainer(storageComp.Container, destination: coordinates); + ContainerSystem.EmptyContainer(storageComp.Container, destination: coordinates); } /// @@ -638,6 +680,54 @@ private void OnSetItemLocation(StorageSetItemLocationEvent msg, EntitySessionEve 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 @@ private void OnSaveItemLocation(StorageSaveItemLocationEvent msg, EntitySessionE SaveItemLocation(storage!, item.Owner); } - private void OnBoundUIOpen(EntityUid uid, StorageComponent storageComp, BoundUIOpenedEvent args) + private void OnBoundUIOpen(Entity 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 entity, ref EntInsertedIntoContainerMessage args) @@ -676,7 +803,7 @@ private void OnEntInserted(Entity entity, ref EntInsertedIntoC { 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 void UpdateAppearance(Entity ent 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 bool CanInsert( } CheckingCanInsert = true; - if (!_containerSystem.CanInsert(insertEnt, storageComp.Container)) + if (!ContainerSystem.CanInsert(insertEnt, storageComp.Container)) { CheckingCanInsert = false; reason = null; @@ -949,7 +1076,7 @@ public bool Insert( 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 bool Insert( // 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 bool TrySetItemStorageLocation(Entity itemEnt, Entity ent, Entity @@ -1357,16 +1486,16 @@ private void OnLockToggled(EntityUid uid, StorageComponent component, ref LockTo 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 @@ private void HandleToggleSlotUI(ICommonSession? session, string slot) 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 @@ private bool ValidateInput( // 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)) diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index 52547454a076..c59f7ab00e91 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -148,6 +148,19 @@ public enum StorageUiKey : byte } } + [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 @@ public StorageSetItemLocationEvent(NetEntity itemEnt, NetEntity storageEnt, Item } } + [Serializable, NetSerializable] + public sealed class StorageTransferItemEvent : EntityEventArgs + { + public readonly NetEntity ItemEnt; + + /// + /// Target storage to receive the transfer. + /// + 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 { diff --git a/RobustToolbox b/RobustToolbox index ee906af16e13..c4a5752c2aff 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit ee906af16e136c7e09930d88872ab9bba3137c8e +Subproject commit c4a5752c2affee874f0b9be3b07b86a55a91bf0a