-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();
}
}
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;
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 />
{
if (!_timing.IsFirstTimePredicted)
return;
-
+
if (TransformSystem.InRange(finalCoords, initialCoords, 0.1f) ||
!Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId))
{
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()
<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
StatusPanelLeft.SetSide(HandUILocation.Left);
var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
- hotbarController.Setup(HandContainer, StoragePanel);
+ hotbarController.Setup(HandContainer);
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
}
Location = location;
Visible = true;
- MouseFilter = MouseFilterMode.Pass;
+ MouseFilter = MouseFilterMode.Stop;
TooltipSupplier = SupplyTooltip;
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();
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;
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;
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;
private readonly string _sidebarFatTexturePath = "Storage/sidebar_fat";
private Texture? _sidebarFatTexture;
- public StorageContainer()
+ public StorageWindow()
{
IoCManager.InjectDependencies(this);
+ Resizable = false;
_storageController = UserInterfaceManager.GetUIController<StorageUIController>();
_sidebar = new GridContainer
{
+ Name = "SideBar",
HSeparationOverride = 0,
VSeparationOverride = 0,
Columns = 1
_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,
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();
#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 += _ =>
args.Handle();
}
};
+
var exitContainer = new BoxContainer
{
+ Name = "ExitContainer",
Children =
{
new TextureRect
}
}
};
+
_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()
}
}
+ 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);
}
}
}
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>();
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))
continue;
float spot = 0;
- var marked = new List<Control>();
+ var marked = new ValueList<Control>();
foreach (var location in locations.Value)
{
}
}
}
-
- public override void Close()
- {
- base.Close();
-
- if (StorageEntity == null)
- return;
-
- _entity.System<StorageSystem>().CloseStorageWindow(StorageEntity.Value);
- }
}
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;
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;
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;
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);
{
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?
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.
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);
}
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)
}
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();
}
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();
{
if (DraggingGhost == null)
return false;
+
SetDraggingRotation();
return true;
}
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 );
{
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;
}
}
_inputManager.ViewportKeyEvent(this, args);
}
- protected override void Draw(DrawingHandleScreen handle)
+ protected override void Draw(IRenderHandle handle)
{
EnsureViewportCreated();
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);
}
/// </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);
}
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;
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;
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";
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;
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 />
_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);
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) });
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);
SubscribeLocalEvent<StorageComponent, AreaPickupDoAfterEvent>(OnDoAfter);
+ SubscribeAllEvent<OpenNestedStorageEvent>(OnStorageNested);
+ SubscribeAllEvent<StorageTransferItemEvent>(OnStorageTransfer);
SubscribeAllEvent<StorageInteractWithItemEvent>(OnInteractWithItem);
SubscribeAllEvent<StorageSetItemLocationEvent>(OnSetItemLocation);
SubscribeAllEvent<StorageInsertItemIntoLocationEvent>(OnInsertItemIntoLocation);
.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)
};
}
- 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;
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));
}
// close ui
foreach (var entity in storageComp.Container.ContainedEntities)
{
- _ui.CloseUis(entity, actor);
+ UI.CloseUis(entity, actor);
}
}
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);
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()
{
{
if (uiOpen)
{
- _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
+ UI.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User);
}
else
{
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) {}
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>
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);
if (args.Target is not { Valid: true } target)
return;
- if (_containerSystem.IsEntityInContainer(target)
+ if (ContainerSystem.IsEntityInContainer(target)
|| target == args.User
|| !_itemQuery.HasComponent(target))
{
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))
{
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)
var coordinates = TransformSystem.GetMoverCoordinates(uid);
// Being destroyed so need to recalculate.
- _containerSystem.EmptyContainer(storageComp.Container, destination: coordinates);
+ ContainerSystem.EmptyContainer(storageComp.Container, destination: coordinates);
}
/// <summary>
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))
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)
{
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;
}
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);
}
CheckingCanInsert = true;
- if (!_containerSystem.CanInsert(insertEnt, storageComp.Container))
+ if (!ContainerSystem.CanInsert(insertEnt, storageComp.Container))
{
CheckingCanInsert = false;
reason = null;
if (!stackAutomatically || !_stackQuery.TryGetComponent(insertEnt, out var insertStack))
{
- if (!_containerSystem.Insert(insertEnt, storageComp.Container))
+ if (!ContainerSystem.Insert(insertEnt, storageComp.Container))
return false;
if (playSound)
// Still stackable remaining
if (insertStack.Count > 0
- && !_containerSystem.Insert(insertEnt, storageComp.Container)
+ && !ContainerSystem.Insert(insertEnt, storageComp.Container)
&& toInsertCount == insertStack.Count)
{
// Failed to insert anything.
return false;
storageEnt.Comp.StoredItems[itemEnt] = location;
+ UpdateUI(storageEnt);
Dirty(storageEnt, storageEnt.Comp);
return true;
}
}
Dirty(ent, ent.Comp);
+ UpdateUI((ent.Owner, ent.Comp));
}
/// <summary>
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);
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);
}
}
// 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))
}
}
+ [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
{
}
}
+ [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
{
-Subproject commit ee906af16e136c7e09930d88872ab9bba3137c8e
+Subproject commit c4a5752c2affee874f0b9be3b07b86a55a91bf0a