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:
Nemanja
2023-12-04 18:04:39 -05:00
committed by GitHub
parent 4221ed2d4b
commit cc8984d096
99 changed files with 2014 additions and 619 deletions

View File

@@ -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);
}
}

View File

@@ -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]);
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}