Grid Inventory (#21931)
* Grid Inventory * oh boy we keep cracking on * auto insertion is kinda working? gross, too! * pieces and proper layouts * fix the sprites * mousing over grid pieces... finally * dragging deez nuts all over the screen * eek! * dragging is 90% less horrendous * auto-rotating * flatten * Rotation at last * fix rotation and change keybind for removing items. * rebinding and keybinding * wow! look at that! configurable with a button! cool! * dragging is a bit cooler, eh? * hover insert, my beloved * add some grids for storage, fix 1x1 storages, fix multiple inputs at once * el navigation * oh yeah some stuff i forgor * more fixes and QOL stuff * the griddening * the last of it (yippee) * sloth review :)
This commit is contained in:
@@ -1,129 +1,37 @@
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.Storage.UI;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.Verbs.UI;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Client.Storage.Systems;
|
||||
using Content.Shared.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using static Content.Shared.Storage.StorageComponent;
|
||||
|
||||
namespace Content.Client.Storage
|
||||
namespace Content.Client.Storage;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
|
||||
private readonly StorageSystem _storage;
|
||||
|
||||
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private StorageWindow? _window;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_storage = _entManager.System<StorageSystem>();
|
||||
}
|
||||
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
if (_entManager.TryGetComponent<StorageComponent>(Owner, out var comp))
|
||||
_storage.OpenStorageUI(Owner, comp);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_window == null)
|
||||
{
|
||||
// TODO: This is a bit of a mess but storagecomponent got moved to shared and cleaned up a bit.
|
||||
var controller = IoCManager.Resolve<IUserInterfaceManager>().GetUIController<StorageUIController>();
|
||||
_window = controller.EnsureStorageWindow(Owner);
|
||||
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
|
||||
_window.EntityList.GenerateItem += _window.GenerateButton;
|
||||
_window.EntityList.ItemPressed += InteractWithItem;
|
||||
_window.StorageContainerButton.OnPressed += TouchedContainerButton;
|
||||
|
||||
_window.OnClose += Close;
|
||||
|
||||
if (EntMan.TryGetComponent<StorageComponent>(Owner, out var storageComp))
|
||||
{
|
||||
BuildEntityList(Owner, storageComp);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_window.Open();
|
||||
}
|
||||
}
|
||||
|
||||
public void BuildEntityList(EntityUid uid, StorageComponent component)
|
||||
{
|
||||
_window?.BuildEntityList(uid, component);
|
||||
}
|
||||
|
||||
public void InteractWithItem(BaseButton.ButtonEventArgs? args, ListData? cData)
|
||||
{
|
||||
if (args == null || cData is not EntityListData { Uid: var entity })
|
||||
return;
|
||||
|
||||
if (args.Event.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
SendPredictedMessage(new StorageInteractWithItemEvent(_entManager.GetNetEntity(entity)));
|
||||
}
|
||||
else if (EntMan.EntityExists(entity))
|
||||
{
|
||||
OnButtonPressed(args.Event, entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnButtonPressed(GUIBoundKeyEventArgs args, EntityUid entity)
|
||||
{
|
||||
if (args.Function == ContentKeyFunctions.ExamineEntity)
|
||||
{
|
||||
EntMan.System<ExamineSystem>()
|
||||
.DoExamine(entity);
|
||||
}
|
||||
else if (args.Function == EngineKeyFunctions.UseSecondary)
|
||||
{
|
||||
IoCManager.Resolve<IUserInterfaceManager>().GetUIController<VerbMenuUIController>().OpenVerbMenu(entity);
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
|
||||
{
|
||||
EntMan.EntityNetManager?.SendSystemNetworkMessage(
|
||||
new InteractInventorySlotEvent(EntMan.GetNetEntity(entity), altInteract: false));
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
||||
{
|
||||
EntMan.RaisePredictiveEvent(new InteractInventorySlotEvent(EntMan.GetNetEntity(entity), altInteract: true));
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
public void TouchedContainerButton(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
SendPredictedMessage(new StorageInsertItemMessage());
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_window != null)
|
||||
{
|
||||
_window.Orphan();
|
||||
_window.EntityList.GenerateItem -= _window.GenerateButton;
|
||||
_window.EntityList.ItemPressed -= InteractWithItem;
|
||||
_window.StorageContainerButton.OnPressed -= TouchedContainerButton;
|
||||
_window.OnClose -= Close;
|
||||
_window = null;
|
||||
}
|
||||
}
|
||||
_storage.CloseStorageUI(Owner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
using Content.Client.Animations;
|
||||
using System.Linq;
|
||||
using Content.Client.Animations;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Storage.Systems;
|
||||
|
||||
// TODO kill this is all horrid.
|
||||
public sealed class StorageSystem : SharedStorageSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly EntityPickupAnimationSystem _entityPickupAnimation = default!;
|
||||
|
||||
public event Action<EntityUid, StorageComponent>? StorageUpdated;
|
||||
private readonly List<Entity<StorageComponent>> _openStorages = new();
|
||||
public int OpenStorageAmount => _openStorages.Count;
|
||||
|
||||
public event Action<Entity<StorageComponent>>? StorageUpdated;
|
||||
public event Action<Entity<StorageComponent>?>? StorageOrderChanged;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StorageComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
|
||||
SubscribeNetworkEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
|
||||
}
|
||||
@@ -26,7 +32,74 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
public override void UpdateUI(Entity<StorageComponent?> entity)
|
||||
{
|
||||
if (Resolve(entity.Owner, ref entity.Comp))
|
||||
StorageUpdated?.Invoke(entity.Owner, entity.Comp);
|
||||
StorageUpdated?.Invoke((entity, entity.Comp));
|
||||
}
|
||||
|
||||
public void OpenStorageUI(EntityUid uid, StorageComponent component)
|
||||
{
|
||||
if (_openStorages.Contains((uid, component)))
|
||||
return;
|
||||
|
||||
ClearNonParentStorages(uid);
|
||||
_openStorages.Add((uid, component));
|
||||
Entity<StorageComponent>? last = _openStorages.LastOrDefault();
|
||||
StorageOrderChanged?.Invoke(last);
|
||||
}
|
||||
|
||||
public void CloseStorageUI(Entity<StorageComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return;
|
||||
|
||||
if (!_openStorages.Contains((entity, entity.Comp)))
|
||||
return;
|
||||
|
||||
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
|
||||
var reverseStorages = storages.Reverse();
|
||||
|
||||
foreach (var storage in reverseStorages)
|
||||
{
|
||||
CloseStorageBoundUserInterface(storage.Owner);
|
||||
_openStorages.Remove(storage);
|
||||
if (storage.Owner == entity.Owner)
|
||||
break;
|
||||
}
|
||||
|
||||
Entity<StorageComponent>? last = null;
|
||||
if (_openStorages.Any())
|
||||
last = _openStorages.LastOrDefault();
|
||||
StorageOrderChanged?.Invoke(last);
|
||||
}
|
||||
|
||||
private void ClearNonParentStorages(EntityUid uid)
|
||||
{
|
||||
var storages = new ValueList<Entity<StorageComponent>>(_openStorages);
|
||||
var reverseStorages = storages.Reverse();
|
||||
|
||||
foreach (var storage in reverseStorages)
|
||||
{
|
||||
if (storage.Comp.Container.Contains(uid))
|
||||
break;
|
||||
|
||||
CloseStorageBoundUserInterface(storage.Owner);
|
||||
_openStorages.Remove(storage);
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseStorageBoundUserInterface(Entity<UserInterfaceComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
if (entity.Comp.OpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui)
|
||||
return;
|
||||
|
||||
bui.Close();
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<StorageComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
CloseStorageUI((ent, ent.Comp));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -49,14 +122,14 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
if (finalCoords.InRange(EntityManager, _transform, initialCoords, 0.1f) ||
|
||||
if (finalCoords.InRange(EntityManager, TransformSystem, initialCoords, 0.1f) ||
|
||||
!Exists(initialCoords.EntityId) || !Exists(finalCoords.EntityId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var finalMapPos = finalCoords.ToMapPos(EntityManager, _transform);
|
||||
var finalPos = _transform.GetInvWorldMatrix(initialCoords.EntityId).Transform(finalMapPos);
|
||||
var finalMapPos = finalCoords.ToMapPos(EntityManager, TransformSystem);
|
||||
var finalPos = TransformSystem.GetInvWorldMatrix(initialCoords.EntityId).Transform(finalMapPos);
|
||||
|
||||
_entityPickupAnimation.AnimateEntityPickup(item, initialCoords, finalPos, initialAngle);
|
||||
}
|
||||
@@ -74,7 +147,7 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
var entity = GetEntity(msg.StoredEntities[i]);
|
||||
|
||||
var initialPosition = msg.EntityPositions[i];
|
||||
if (EntityManager.EntityExists(entity) && transformComp != null)
|
||||
if (Exists(entity) && transformComp != null)
|
||||
{
|
||||
_entityPickupAnimation.AnimateEntityPickup(entity, GetCoordinates(initialPosition), transformComp.LocalPosition, msg.EntityAngles[i]);
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Storage.Systems;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
|
||||
namespace Content.Client.Storage.UI;
|
||||
|
||||
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>, IOnStateExited<GameplayState>
|
||||
{
|
||||
// This is mainly to keep legacy functionality for now.
|
||||
private readonly Dictionary<EntityUid, StorageWindow> _storageWindows = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
EntityManager.EventBus.SubscribeLocalEvent<StorageComponent, ComponentShutdown>(OnStorageShutdown);
|
||||
}
|
||||
public StorageWindow EnsureStorageWindow(EntityUid uid)
|
||||
{
|
||||
if (_storageWindows.TryGetValue(uid, out var window))
|
||||
{
|
||||
UIManager.WindowRoot.AddChild(window);
|
||||
return window;
|
||||
}
|
||||
|
||||
window = new StorageWindow(EntityManager);
|
||||
_storageWindows[uid] = window;
|
||||
window.OpenCenteredLeft();
|
||||
return window;
|
||||
}
|
||||
|
||||
private void OnStorageShutdown(EntityUid uid, StorageComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!_storageWindows.TryGetValue(uid, out var window))
|
||||
return;
|
||||
|
||||
_storageWindows.Remove(uid);
|
||||
window.Dispose();
|
||||
}
|
||||
|
||||
private void OnStorageUpdate(EntityUid uid, StorageComponent component)
|
||||
{
|
||||
if (EntityManager.TryGetComponent<UserInterfaceComponent>(uid, out var uiComp) &&
|
||||
uiComp.OpenInterfaces.TryGetValue(StorageComponent.StorageUiKey.Key, out var bui))
|
||||
{
|
||||
var storageBui = (StorageBoundUserInterface) bui;
|
||||
|
||||
storageBui.BuildEntityList(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSystemLoaded(StorageSystem system)
|
||||
{
|
||||
system.StorageUpdated += OnStorageUpdate;
|
||||
}
|
||||
|
||||
public void OnSystemUnloaded(StorageSystem system)
|
||||
{
|
||||
system.StorageUpdated -= OnStorageUpdate;
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
foreach (var window in _storageWindows.Values)
|
||||
{
|
||||
window.Dispose();
|
||||
}
|
||||
|
||||
_storageWindows.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Client.Message;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
|
||||
namespace Content.Client.Storage.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// GUI class for client storage component
|
||||
/// </summary>
|
||||
public sealed class StorageWindow : FancyWindow
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
private readonly SharedStorageSystem _storage;
|
||||
private readonly ItemSystem _item;
|
||||
|
||||
private readonly RichTextLabel _information;
|
||||
public readonly ContainerButton StorageContainerButton;
|
||||
public readonly ListContainer EntityList;
|
||||
private readonly StyleBoxFlat _hoveredBox = new() { BackgroundColor = Color.Black.WithAlpha(0.35f) };
|
||||
private readonly StyleBoxFlat _unHoveredBox = new() { BackgroundColor = Color.Black.WithAlpha(0.0f) };
|
||||
|
||||
public StorageWindow(IEntityManager entityManager)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_storage = _entityManager.System<SharedStorageSystem>();
|
||||
_item = _entityManager.System<ItemSystem>();
|
||||
SetSize = new Vector2(240, 320);
|
||||
Title = Loc.GetString("comp-storage-window-title");
|
||||
RectClipContent = true;
|
||||
|
||||
StorageContainerButton = new ContainerButton
|
||||
{
|
||||
Name = "StorageContainerButton",
|
||||
MouseFilter = MouseFilterMode.Pass,
|
||||
};
|
||||
|
||||
ContentsContainer.AddChild(StorageContainerButton);
|
||||
|
||||
var innerContainerButton = new PanelContainer
|
||||
{
|
||||
PanelOverride = _unHoveredBox,
|
||||
};
|
||||
|
||||
StorageContainerButton.AddChild(innerContainerButton);
|
||||
|
||||
Control vBox = new BoxContainer()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MouseFilter = MouseFilterMode.Ignore,
|
||||
Margin = new Thickness(5),
|
||||
};
|
||||
|
||||
StorageContainerButton.AddChild(vBox);
|
||||
|
||||
_information = new RichTextLabel
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center
|
||||
};
|
||||
_information.SetMessage(Loc.GetString("comp-storage-window-weight",
|
||||
("weight", 0),
|
||||
("maxWeight", 0),
|
||||
("size", _item.GetItemSizeLocale(SharedStorageSystem.DefaultStorageMaxItemSize))));
|
||||
|
||||
vBox.AddChild(_information);
|
||||
|
||||
EntityList = new ListContainer
|
||||
{
|
||||
Name = "EntityListContainer",
|
||||
};
|
||||
|
||||
vBox.AddChild(EntityList);
|
||||
|
||||
EntityList.OnMouseEntered += _ =>
|
||||
{
|
||||
innerContainerButton.PanelOverride = _hoveredBox;
|
||||
};
|
||||
|
||||
EntityList.OnMouseExited += _ =>
|
||||
{
|
||||
innerContainerButton.PanelOverride = _unHoveredBox;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops through stored entities creating buttons for each, updates information labels
|
||||
/// </summary>
|
||||
public void BuildEntityList(EntityUid entity, StorageComponent component)
|
||||
{
|
||||
var storedCount = component.Container.ContainedEntities.Count;
|
||||
var list = new List<EntityListData>(storedCount);
|
||||
|
||||
foreach (var uid in component.Container.ContainedEntities)
|
||||
{
|
||||
list.Add(new EntityListData(uid));
|
||||
}
|
||||
|
||||
EntityList.PopulateList(list);
|
||||
|
||||
SetStorageInformation((entity, component));
|
||||
}
|
||||
|
||||
private void SetStorageInformation(Entity<StorageComponent> uid)
|
||||
{
|
||||
//todo: text is the straight agenda. What about anything else?
|
||||
if (uid.Comp.MaxSlots == null)
|
||||
{
|
||||
_information.SetMarkup(Loc.GetString("comp-storage-window-weight",
|
||||
("weight", _storage.GetCumulativeItemSizes(uid, uid.Comp)),
|
||||
("maxWeight", uid.Comp.MaxTotalWeight),
|
||||
("size", _item.GetItemSizeLocale(_storage.GetMaxItemSize((uid, uid.Comp))))));
|
||||
}
|
||||
else
|
||||
{
|
||||
_information.SetMarkup(Loc.GetString("comp-storage-window-slots",
|
||||
("itemCount", uid.Comp.Container.ContainedEntities.Count),
|
||||
("maxCount", uid.Comp.MaxSlots),
|
||||
("size", _item.GetItemSizeLocale(_storage.GetMaxItemSize((uid, uid.Comp))))));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Button created for each entity that represents that item in the storage UI, with a texture, and name and size label
|
||||
/// </summary>
|
||||
public void GenerateButton(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not EntityListData {Uid: var entity}
|
||||
|| !_entityManager.EntityExists(entity))
|
||||
return;
|
||||
|
||||
_entityManager.TryGetComponent(entity, out StackComponent? stack);
|
||||
_entityManager.TryGetComponent(entity, out ItemComponent? item);
|
||||
var count = stack?.Count ?? 1;
|
||||
|
||||
var spriteView = new SpriteView
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SetSize = new Vector2(32.0f, 32.0f),
|
||||
OverrideDirection = Direction.South,
|
||||
};
|
||||
spriteView.SetEntity(entity);
|
||||
button.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 2,
|
||||
Children =
|
||||
{
|
||||
spriteView,
|
||||
new Label
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
ClipText = true,
|
||||
Text = _entityManager.GetComponent<MetaDataComponent>(Identity.Entity(entity, _entityManager)).EntityName +
|
||||
(count > 1 ? $" x {count}" : string.Empty)
|
||||
},
|
||||
new Label
|
||||
{
|
||||
Align = Label.AlignMode.Right,
|
||||
Text = item?.Size != null
|
||||
? $"{_item.GetItemSizeWeight(item.Size)}"
|
||||
: Loc.GetString("comp-storage-no-item-size")
|
||||
}
|
||||
}
|
||||
});
|
||||
button.StyleClasses.Add(StyleNano.StyleClassStorageButton);
|
||||
button.EnableAllKeybinds = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user