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:
@@ -30,6 +30,8 @@ namespace Content.Client.Input
|
||||
common.AddFunction(ContentKeyFunctions.TakeScreenshot);
|
||||
common.AddFunction(ContentKeyFunctions.TakeScreenshotNoUI);
|
||||
common.AddFunction(ContentKeyFunctions.ToggleFullscreen);
|
||||
common.AddFunction(ContentKeyFunctions.MoveStoredItem);
|
||||
common.AddFunction(ContentKeyFunctions.RotateStoredItem);
|
||||
common.AddFunction(ContentKeyFunctions.Point);
|
||||
common.AddFunction(ContentKeyFunctions.ZoomOut);
|
||||
common.AddFunction(ContentKeyFunctions.ZoomIn);
|
||||
|
||||
@@ -97,6 +97,12 @@ namespace Content.Client.Options.UI.Tabs
|
||||
_deferCommands.Add(_inputManager.SaveToUserData);
|
||||
}
|
||||
|
||||
private void HandleStaticStorageUI(BaseButton.ButtonToggledEventArgs args)
|
||||
{
|
||||
_cfg.SetCVar(CCVars.StaticStorageUI, args.Pressed);
|
||||
_cfg.SaveToFile();
|
||||
}
|
||||
|
||||
public KeyRebindTab()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -175,6 +181,9 @@ namespace Content.Client.Options.UI.Tabs
|
||||
AddButton(ContentKeyFunctions.Drop);
|
||||
AddButton(ContentKeyFunctions.ExamineEntity);
|
||||
AddButton(ContentKeyFunctions.SwapHands);
|
||||
AddButton(ContentKeyFunctions.MoveStoredItem);
|
||||
AddButton(ContentKeyFunctions.RotateStoredItem);
|
||||
AddCheckBox("ui-options-static-storage-ui", _cfg.GetCVar(CCVars.StaticStorageUI), HandleStaticStorageUI);
|
||||
|
||||
AddHeader("ui-options-header-interaction-adv");
|
||||
AddButton(ContentKeyFunctions.SmartEquipBackpack);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ using Content.Client.UserInterface.Systems.Hands.Controls;
|
||||
using Content.Client.UserInterface.Systems.Hotbar.Widgets;
|
||||
using Content.Client.UserInterface.Systems.Inventory;
|
||||
using Content.Client.UserInterface.Systems.Inventory.Controls;
|
||||
using Content.Client.UserInterface.Systems.Storage;
|
||||
using Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
|
||||
@@ -13,6 +15,7 @@ public sealed class HotbarUIController : UIController
|
||||
{
|
||||
private InventoryUIController? _inventory;
|
||||
private HandsUIController? _hands;
|
||||
private StorageUIController? _storage;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -27,12 +30,14 @@ public sealed class HotbarUIController : UIController
|
||||
ReloadHotbar();
|
||||
}
|
||||
|
||||
public void Setup(HandsContainer handsContainer, ItemSlotButtonContainer inventoryBar, ItemStatusPanel handStatus)
|
||||
public void Setup(HandsContainer handsContainer, ItemSlotButtonContainer inventoryBar, ItemStatusPanel handStatus, StorageContainer storageContainer)
|
||||
{
|
||||
_inventory = UIManager.GetUIController<InventoryUIController>();
|
||||
_hands = UIManager.GetUIController<HandsUIController>();
|
||||
_storage = UIManager.GetUIController<StorageUIController>();
|
||||
_hands.RegisterHandContainer(handsContainer);
|
||||
_inventory.RegisterInventoryBarContainer(inventoryBar);
|
||||
_storage.RegisterStorageContainer(storageContainer);
|
||||
}
|
||||
|
||||
public void ReloadHotbar()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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"
|
||||
@@ -8,11 +9,21 @@
|
||||
VerticalAlignment="Bottom"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<inventory:ItemStatusPanel
|
||||
Name="StatusPanel"
|
||||
Visible="False"
|
||||
HorizontalAlignment="Center"
|
||||
/>
|
||||
<Control HorizontalAlignment="Center">
|
||||
<inventory:ItemStatusPanel
|
||||
Name="StatusPanel"
|
||||
Visible="False"
|
||||
HorizontalAlignment="Center"
|
||||
/>
|
||||
<BoxContainer Name="StorageContainer"
|
||||
Access="Public"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="10">
|
||||
<storage:StorageContainer
|
||||
Name="StoragePanel"
|
||||
Visible="False"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
<inventory:ItemSlotButtonContainer
|
||||
Name="InventoryHotbar"
|
||||
Access="Public"
|
||||
|
||||
@@ -13,7 +13,7 @@ public sealed partial class HotbarGui : UIWidget
|
||||
StatusPanel.Update(null);
|
||||
var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
|
||||
|
||||
hotbarController.Setup(HandContainer, InventoryHotbar, StatusPanel);
|
||||
hotbarController.Setup(HandContainer, InventoryHotbar, StatusPanel, StoragePanel);
|
||||
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
|
||||
public sealed class ItemGridPiece : Control
|
||||
{
|
||||
private readonly ItemSystem _itemSystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private readonly StorageUIController _storageController;
|
||||
|
||||
private readonly List<(Texture, Vector2)> _texturesPositions = new();
|
||||
|
||||
public readonly EntityUid Entity;
|
||||
public ItemStorageLocation Location;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPiecePressed;
|
||||
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPieceUnpressed;
|
||||
|
||||
#region Textures
|
||||
private readonly string _centerTexturePath = "Storage/piece_center";
|
||||
private Texture? _centerTexture;
|
||||
private readonly string _topTexturePath = "Storage/piece_top";
|
||||
private Texture? _topTexture;
|
||||
private readonly string _bottomTexturePath = "Storage/piece_bottom";
|
||||
private Texture? _bottomTexture;
|
||||
private readonly string _leftTexturePath = "Storage/piece_left";
|
||||
private Texture? _leftTexture;
|
||||
private readonly string _rightTexturePath = "Storage/piece_right";
|
||||
private Texture? _rightTexture;
|
||||
private readonly string _topLeftTexturePath = "Storage/piece_topLeft";
|
||||
private Texture? _topLeftTexture;
|
||||
private readonly string _topRightTexturePath = "Storage/piece_topRight";
|
||||
private Texture? _topRightTexture;
|
||||
private readonly string _bottomLeftTexturePath = "Storage/piece_bottomLeft";
|
||||
private Texture? _bottomLeftTexture;
|
||||
private readonly string _bottomRightTexturePath = "Storage/piece_bottomRight";
|
||||
private Texture? _bottomRightTexture;
|
||||
#endregion
|
||||
|
||||
public ItemGridPiece(Entity<ItemComponent> entity, ItemStorageLocation location, IEntityManager entityManager)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_itemSystem = entityManager.System<ItemSystem>();
|
||||
_spriteSystem = entityManager.System<SpriteSystem>();
|
||||
_storageController = UserInterfaceManager.GetUIController<StorageUIController>();
|
||||
|
||||
Entity = entity.Owner;
|
||||
Location = location;
|
||||
|
||||
Visible = true;
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
OnThemeUpdated();
|
||||
}
|
||||
|
||||
protected override void OnThemeUpdated()
|
||||
{
|
||||
base.OnThemeUpdated();
|
||||
|
||||
_centerTexture = Theme.ResolveTextureOrNull(_centerTexturePath)?.Texture;
|
||||
_topTexture = Theme.ResolveTextureOrNull(_topTexturePath)?.Texture;
|
||||
_bottomTexture = Theme.ResolveTextureOrNull(_bottomTexturePath)?.Texture;
|
||||
_leftTexture = Theme.ResolveTextureOrNull(_leftTexturePath)?.Texture;
|
||||
_rightTexture = Theme.ResolveTextureOrNull(_rightTexturePath)?.Texture;
|
||||
_topLeftTexture = Theme.ResolveTextureOrNull(_topLeftTexturePath)?.Texture;
|
||||
_topRightTexture = Theme.ResolveTextureOrNull(_topRightTexturePath)?.Texture;
|
||||
_bottomLeftTexture = Theme.ResolveTextureOrNull(_bottomLeftTexturePath)?.Texture;
|
||||
_bottomRightTexture = Theme.ResolveTextureOrNull(_bottomRightTexturePath)?.Texture;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (_storageController.IsDragging && _storageController.CurrentlyDragging == this)
|
||||
return;
|
||||
|
||||
var adjustedShape = _itemSystem.GetAdjustedItemShape((Entity, null), Location.Rotation, Vector2i.Zero);
|
||||
var boundingGrid = adjustedShape.GetBoundingBox();
|
||||
var size = _centerTexture!.Size * 2 * UIScale;
|
||||
|
||||
var hovering = !_storageController.IsDragging && UserInterfaceManager.CurrentlyHovered == this;
|
||||
//yeah, this coloring is kinda hardcoded. deal with it. B)
|
||||
Color? colorModulate = hovering ? null : Color.FromHex("#a8a8a8");
|
||||
|
||||
_texturesPositions.Clear();
|
||||
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
|
||||
{
|
||||
for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
|
||||
{
|
||||
if (!adjustedShape.Contains(x, y))
|
||||
continue;
|
||||
|
||||
var offset = size * 2 * new Vector2(x - boundingGrid.Left, y - boundingGrid.Bottom);
|
||||
var topLeft = PixelPosition + offset.Floored();
|
||||
|
||||
if (GetTexture(adjustedShape, new Vector2i(x, y), Direction.NorthEast) is {} neTexture)
|
||||
{
|
||||
var neOffset = new Vector2(size.X, 0);
|
||||
handle.DrawTextureRect(neTexture, new UIBox2(topLeft + neOffset, topLeft + neOffset + size), colorModulate);
|
||||
}
|
||||
if (GetTexture(adjustedShape, new Vector2i(x, y), Direction.NorthWest) is {} nwTexture)
|
||||
{
|
||||
_texturesPositions.Add((nwTexture, Position + offset / UIScale));
|
||||
handle.DrawTextureRect(nwTexture, new UIBox2(topLeft, topLeft + size), colorModulate);
|
||||
}
|
||||
if (GetTexture(adjustedShape, new Vector2i(x, y), Direction.SouthEast) is {} seTexture)
|
||||
{
|
||||
var seOffset = size;
|
||||
handle.DrawTextureRect(seTexture, new UIBox2(topLeft + seOffset, topLeft + seOffset + size), colorModulate);
|
||||
}
|
||||
if (GetTexture(adjustedShape, new Vector2i(x, y), Direction.SouthWest) is {} swTexture)
|
||||
{
|
||||
var swOffset = new Vector2(0, size.Y);
|
||||
handle.DrawTextureRect(swTexture, new UIBox2(topLeft + swOffset, topLeft + swOffset + size), colorModulate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// typically you'd divide by two, but since the textures are half a tile, this is done implicitly
|
||||
var iconOffset = new Vector2((boundingGrid.Width + 1) * size.X ,
|
||||
(boundingGrid.Height + 1) * size.Y);
|
||||
|
||||
_spriteSystem.ForceUpdate(Entity);
|
||||
handle.DrawEntity(Entity,
|
||||
PixelPosition + iconOffset,
|
||||
Vector2.One * 2 * UIScale,
|
||||
Angle.Zero,
|
||||
overrideDirection: Direction.South);
|
||||
}
|
||||
|
||||
protected override bool HasPoint(Vector2 point)
|
||||
{
|
||||
foreach (var (texture, position) in _texturesPositions)
|
||||
{
|
||||
if (!new Box2(position, position + texture.Size * 4).Contains(point))
|
||||
continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindDown(args);
|
||||
|
||||
OnPiecePressed?.Invoke(args, this);
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
|
||||
OnPieceUnpressed?.Invoke(args, this);
|
||||
}
|
||||
|
||||
private Texture? GetTexture(IReadOnlyList<Box2i> boxes, Vector2i position, Direction corner)
|
||||
{
|
||||
var top = !boxes.Contains(position - Vector2i.Up);
|
||||
var bottom = !boxes.Contains(position - Vector2i.Down);
|
||||
var left = !boxes.Contains(position + Vector2i.Left);
|
||||
var right = !boxes.Contains(position + Vector2i.Right);
|
||||
|
||||
switch (corner)
|
||||
{
|
||||
case Direction.NorthEast:
|
||||
if (top && right)
|
||||
return _topRightTexture;
|
||||
if (top)
|
||||
return _topTexture;
|
||||
if (right)
|
||||
return _rightTexture;
|
||||
return _centerTexture;
|
||||
case Direction.NorthWest:
|
||||
if (top && left)
|
||||
return _topLeftTexture;
|
||||
if (top)
|
||||
return _topTexture;
|
||||
if (left)
|
||||
return _leftTexture;
|
||||
return _centerTexture;
|
||||
case Direction.SouthEast:
|
||||
if (bottom && right)
|
||||
return _bottomRightTexture;
|
||||
if (bottom)
|
||||
return _bottomTexture;
|
||||
if (right)
|
||||
return _rightTexture;
|
||||
return _centerTexture;
|
||||
case Direction.SouthWest:
|
||||
if (bottom && left)
|
||||
return _bottomLeftTexture;
|
||||
if (bottom)
|
||||
return _bottomTexture;
|
||||
if (left)
|
||||
return _leftTexture;
|
||||
return _centerTexture;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector2 GetCenterOffset(Entity<ItemComponent?> entity, ItemStorageLocation location, IEntityManager entMan)
|
||||
{
|
||||
var boxSize = entMan.System<ItemSystem>().GetAdjustedItemShape(entity, location).GetBoundingBox().Size;
|
||||
var actualSize = new Vector2(boxSize.X + 1, boxSize.Y + 1);
|
||||
return actualSize * new Vector2i(8, 8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Client.Storage.Systems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
|
||||
public sealed class StorageContainer : BaseWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
private readonly StorageUIController _storageController;
|
||||
|
||||
public EntityUid? StorageEntity;
|
||||
|
||||
private readonly GridContainer _pieceGrid;
|
||||
private readonly GridContainer _backgroundGrid;
|
||||
private readonly GridContainer _sidebar;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPiecePressed;
|
||||
public event Action<GUIBoundKeyEventArgs, ItemGridPiece>? OnPieceUnpressed;
|
||||
|
||||
private readonly string _emptyTexturePath = "Storage/tile_empty";
|
||||
private Texture? _emptyTexture;
|
||||
private readonly string _blockedTexturePath = "Storage/tile_blocked";
|
||||
private Texture? _blockedTexture;
|
||||
private readonly string _exitTexturePath = "Storage/exit";
|
||||
private Texture? _exitTexture;
|
||||
private readonly string _backTexturePath = "Storage/back";
|
||||
private Texture? _backTexture;
|
||||
private readonly string _sidebarTopTexturePath = "Storage/sidebar_top";
|
||||
private Texture? _sidebarTopTexture;
|
||||
private readonly string _sidebarMiddleTexturePath = "Storage/sidebar_mid";
|
||||
private Texture? _sidebarMiddleTexture;
|
||||
private readonly string _sidebarBottomTexturePath = "Storage/sidebar_bottom";
|
||||
private Texture? _sidebarBottomTexture;
|
||||
private readonly string _sidebarFatTexturePath = "Storage/sidebar_fat";
|
||||
private Texture? _sidebarFatTexture;
|
||||
|
||||
public StorageContainer()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_storageController = UserInterfaceManager.GetUIController<StorageUIController>();
|
||||
|
||||
OnThemeUpdated();
|
||||
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
_sidebar = new GridContainer
|
||||
{
|
||||
HSeparationOverride = 0,
|
||||
VSeparationOverride = 0,
|
||||
Columns = 1
|
||||
};
|
||||
|
||||
_pieceGrid = new GridContainer
|
||||
{
|
||||
HSeparationOverride = 0,
|
||||
VSeparationOverride = 0
|
||||
};
|
||||
|
||||
_backgroundGrid = new GridContainer
|
||||
{
|
||||
HSeparationOverride = 0,
|
||||
VSeparationOverride = 0
|
||||
};
|
||||
|
||||
var container = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
_sidebar,
|
||||
new Control
|
||||
{
|
||||
Children =
|
||||
{
|
||||
_backgroundGrid,
|
||||
_pieceGrid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddChild(container);
|
||||
}
|
||||
|
||||
protected override void OnThemeUpdated()
|
||||
{
|
||||
base.OnThemeUpdated();
|
||||
|
||||
_emptyTexture = Theme.ResolveTextureOrNull(_emptyTexturePath)?.Texture;
|
||||
_blockedTexture = Theme.ResolveTextureOrNull(_blockedTexturePath)?.Texture;
|
||||
_exitTexture = Theme.ResolveTextureOrNull(_exitTexturePath)?.Texture;
|
||||
_backTexture = Theme.ResolveTextureOrNull(_backTexturePath)?.Texture;
|
||||
_sidebarTopTexture = Theme.ResolveTextureOrNull(_sidebarTopTexturePath)?.Texture;
|
||||
_sidebarMiddleTexture = Theme.ResolveTextureOrNull(_sidebarMiddleTexturePath)?.Texture;
|
||||
_sidebarBottomTexture = Theme.ResolveTextureOrNull(_sidebarBottomTexturePath)?.Texture;
|
||||
_sidebarFatTexture = Theme.ResolveTextureOrNull(_sidebarFatTexturePath)?.Texture;
|
||||
}
|
||||
|
||||
public void UpdateContainer(Entity<StorageComponent>? entity)
|
||||
{
|
||||
Visible = entity != null;
|
||||
StorageEntity = entity;
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
BuildGridRepresentation(entity.Value);
|
||||
}
|
||||
|
||||
private void BuildGridRepresentation(Entity<StorageComponent> entity)
|
||||
{
|
||||
var comp = entity.Comp;
|
||||
if (!comp.Grid.Any())
|
||||
return;
|
||||
|
||||
var boundingGrid = comp.Grid.GetBoundingBox();
|
||||
|
||||
_backgroundGrid.Children.Clear();
|
||||
_backgroundGrid.Rows = boundingGrid.Height + 1;
|
||||
_backgroundGrid.Columns = boundingGrid.Width + 1;
|
||||
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
|
||||
{
|
||||
for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
|
||||
{
|
||||
var texture = comp.Grid.Contains(x, y)
|
||||
? _emptyTexture
|
||||
: _blockedTexture;
|
||||
|
||||
_backgroundGrid.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
TextureScale = new Vector2(2, 2)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#region Sidebar
|
||||
_sidebar.Children.Clear();
|
||||
_sidebar.Rows = boundingGrid.Height + 1;
|
||||
var exitButton = new TextureButton
|
||||
{
|
||||
TextureNormal = _entity.System<StorageSystem>().OpenStorageAmount == 1
|
||||
?_exitTexture
|
||||
: _backTexture,
|
||||
Scale = new Vector2(2, 2),
|
||||
};
|
||||
exitButton.OnPressed += _ =>
|
||||
{
|
||||
Close();
|
||||
};
|
||||
var exitContainer = new BoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new TextureRect
|
||||
{
|
||||
Texture = boundingGrid.Height != 0
|
||||
? _sidebarTopTexture
|
||||
: _sidebarFatTexture,
|
||||
TextureScale = new Vector2(2, 2),
|
||||
Children =
|
||||
{
|
||||
exitButton
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
_sidebar.AddChild(exitContainer);
|
||||
for (var i = 0; i < boundingGrid.Height - 1; i++)
|
||||
{
|
||||
_sidebar.AddChild(new TextureRect
|
||||
{
|
||||
Texture = _sidebarMiddleTexture,
|
||||
TextureScale = new Vector2(2, 2),
|
||||
});
|
||||
}
|
||||
|
||||
if (boundingGrid.Height > 0)
|
||||
{
|
||||
_sidebar.AddChild(new TextureRect
|
||||
{
|
||||
Texture = _sidebarBottomTexture,
|
||||
TextureScale = new Vector2(2, 2),
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
BuildItemPieces();
|
||||
}
|
||||
|
||||
public void BuildItemPieces()
|
||||
{
|
||||
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComp))
|
||||
return;
|
||||
|
||||
if (!storageComp.Grid.Any())
|
||||
return;
|
||||
|
||||
var boundingGrid = storageComp.Grid.GetBoundingBox();
|
||||
var size = _emptyTexture!.Size * 2;
|
||||
|
||||
//todo. at some point, we may want to only rebuild the pieces that have actually received new data.
|
||||
|
||||
_pieceGrid.Children.Clear();
|
||||
_pieceGrid.Rows = boundingGrid.Height + 1;
|
||||
_pieceGrid.Columns = boundingGrid.Width + 1;
|
||||
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
|
||||
{
|
||||
for (var x = boundingGrid.Left; x <= boundingGrid.Right; x++)
|
||||
{
|
||||
var currentPosition = new Vector2i(x, y);
|
||||
var item = storageComp.StoredItems
|
||||
.Where(pair => pair.Value.Position == currentPosition)
|
||||
.FirstOrNull();
|
||||
|
||||
var control = new Control
|
||||
{
|
||||
MinSize = size
|
||||
};
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
var itemEnt = _entity.GetEntity(item.Value.Key);
|
||||
|
||||
if (_entity.TryGetComponent<ItemComponent>(itemEnt, out var itemEntComponent))
|
||||
{
|
||||
var gridPiece = new ItemGridPiece((itemEnt, itemEntComponent), item.Value.Value, _entity)
|
||||
{
|
||||
MinSize = size,
|
||||
};
|
||||
gridPiece.OnPiecePressed += OnPiecePressed;
|
||||
gridPiece.OnPieceUnpressed += OnPieceUnpressed;
|
||||
|
||||
control.AddChild(gridPiece);
|
||||
}
|
||||
}
|
||||
|
||||
_pieceGrid.AddChild(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!IsOpen)
|
||||
return;
|
||||
|
||||
var itemSystem = _entity.System<ItemSystem>();
|
||||
var storageSystem = _entity.System<StorageSystem>();
|
||||
var handsSystem = _entity.System<HandsSystem>();
|
||||
|
||||
foreach (var child in _backgroundGrid.Children)
|
||||
{
|
||||
child.ModulateSelfOverride = Color.FromHex("#222222");
|
||||
}
|
||||
|
||||
if (UserInterfaceManager.CurrentlyHovered is StorageContainer con && con != this)
|
||||
return;
|
||||
|
||||
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComponent))
|
||||
return;
|
||||
|
||||
EntityUid currentEnt;
|
||||
ItemStorageLocation currentLocation;
|
||||
var usingInHand = false;
|
||||
if (_storageController.IsDragging && _storageController.DraggingGhost is { } dragging)
|
||||
{
|
||||
currentEnt = dragging.Entity;
|
||||
currentLocation = dragging.Location;
|
||||
}
|
||||
else if (handsSystem.GetActiveHandEntity() is { } handEntity &&
|
||||
storageSystem.CanInsert(StorageEntity.Value, handEntity, out _, storageComp: storageComponent, ignoreLocation: true))
|
||||
{
|
||||
currentEnt = handEntity;
|
||||
currentLocation = new ItemStorageLocation(_storageController.DraggingRotation, Vector2i.Zero);
|
||||
usingInHand = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entity.TryGetComponent<ItemComponent>(currentEnt, out var itemComp))
|
||||
return;
|
||||
|
||||
var origin = GetMouseGridPieceLocation((currentEnt, itemComp), currentLocation);
|
||||
|
||||
var itemShape = itemSystem.GetAdjustedItemShape(
|
||||
(currentEnt, itemComp),
|
||||
currentLocation.Rotation,
|
||||
origin);
|
||||
var itemBounding = itemShape.GetBoundingBox();
|
||||
|
||||
var validLocation = storageSystem.ItemFitsInGridLocation(
|
||||
(currentEnt, itemComp),
|
||||
(StorageEntity.Value, storageComponent),
|
||||
origin,
|
||||
currentLocation.Rotation);
|
||||
|
||||
var validColor = usingInHand ? Color.Goldenrod : Color.Green;
|
||||
|
||||
for (var y = itemBounding.Bottom; y <= itemBounding.Top; y++)
|
||||
{
|
||||
for (var x = itemBounding.Left; x <= itemBounding.Right; x++)
|
||||
{
|
||||
if (TryGetBackgroundCell(x, y, out var cell) && itemShape.Contains(x, y))
|
||||
{
|
||||
cell.ModulateSelfOverride = validLocation ? validColor : Color.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
||||
{
|
||||
if (_storageController.StaticStorageUIEnabled)
|
||||
return DragMode.None;
|
||||
|
||||
if (_sidebar.SizeBox.Contains(relativeMousePos - _sidebar.Position))
|
||||
{
|
||||
return DragMode.Move;
|
||||
}
|
||||
|
||||
return DragMode.None;
|
||||
}
|
||||
|
||||
public Vector2i GetMouseGridPieceLocation(Entity<ItemComponent?> entity, ItemStorageLocation location)
|
||||
{
|
||||
var origin = Vector2i.Zero;
|
||||
|
||||
if (StorageEntity != null)
|
||||
origin = _entity.GetComponent<StorageComponent>(StorageEntity.Value).Grid.GetBoundingBox().BottomLeft;
|
||||
|
||||
var textureSize = (Vector2) _emptyTexture!.Size * 2;
|
||||
var position = ((UserInterfaceManager.MousePositionScaled.Position
|
||||
- _backgroundGrid.GlobalPosition
|
||||
- ItemGridPiece.GetCenterOffset(entity, location, _entity) * 2
|
||||
+ textureSize / 2f)
|
||||
/ textureSize).Floored() + origin;
|
||||
return position;
|
||||
}
|
||||
|
||||
public bool TryGetBackgroundCell(int x, int y, [NotNullWhen(true)] out Control? cell)
|
||||
{
|
||||
cell = null;
|
||||
|
||||
if (!_entity.TryGetComponent<StorageComponent>(StorageEntity, out var storageComponent))
|
||||
return false;
|
||||
var boundingBox = storageComponent.Grid.GetBoundingBox();
|
||||
x -= boundingBox.Left;
|
||||
y -= boundingBox.Bottom;
|
||||
|
||||
if (x < 0 ||
|
||||
x >= _backgroundGrid.Columns ||
|
||||
y < 0 ||
|
||||
y >= _backgroundGrid.Rows)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
cell = _backgroundGrid.GetChild(y * _backgroundGrid.Columns + x);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindDown(args);
|
||||
|
||||
if (!IsOpen)
|
||||
return;
|
||||
|
||||
var storageSystem = _entity.System<StorageSystem>();
|
||||
var handsSystem = _entity.System<HandsSystem>();
|
||||
|
||||
if (args.Function == ContentKeyFunctions.MoveStoredItem && StorageEntity != null)
|
||||
{
|
||||
if (handsSystem.GetActiveHandEntity() is { } handEntity &&
|
||||
storageSystem.CanInsert(StorageEntity.Value, handEntity, out _))
|
||||
{
|
||||
var pos = GetMouseGridPieceLocation((handEntity, null),
|
||||
new ItemStorageLocation(_storageController.DraggingRotation, Vector2i.Zero));
|
||||
|
||||
var insertLocation = new ItemStorageLocation(_storageController.DraggingRotation, pos);
|
||||
if (storageSystem.ItemFitsInGridLocation(
|
||||
(handEntity, null),
|
||||
(StorageEntity.Value, null),
|
||||
insertLocation))
|
||||
{
|
||||
_entity.RaisePredictiveEvent(new StorageInsertItemIntoLocationEvent(
|
||||
_entity.GetNetEntity(handEntity),
|
||||
_entity.GetNetEntity(StorageEntity.Value),
|
||||
insertLocation));
|
||||
args.Handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
|
||||
if (StorageEntity == null)
|
||||
return;
|
||||
|
||||
_entity.System<StorageSystem>().CloseStorageUI(StorageEntity.Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Client.Interaction;
|
||||
using Content.Client.Storage.Systems;
|
||||
using Content.Client.UserInterface.Systems.Hotbar.Widgets;
|
||||
using Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
using Content.Client.Verbs.UI;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Storage;
|
||||
|
||||
public sealed class StorageUIController : UIController, IOnSystemChanged<StorageSystem>
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
private readonly DragDropHelper<ItemGridPiece> _menuDragHelper;
|
||||
private StorageContainer? _container;
|
||||
|
||||
private Vector2? _lastContainerPosition;
|
||||
|
||||
private HotbarGui? Hotbar => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
|
||||
|
||||
public ItemGridPiece? DraggingGhost;
|
||||
public Angle DraggingRotation = Angle.Zero;
|
||||
public bool StaticStorageUIEnabled;
|
||||
|
||||
public bool IsDragging => _menuDragHelper.IsDragging;
|
||||
public ItemGridPiece? CurrentlyDragging => _menuDragHelper.Dragged;
|
||||
|
||||
public StorageUIController()
|
||||
{
|
||||
_menuDragHelper = new DragDropHelper<ItemGridPiece>(OnMenuBeginDrag, OnMenuContinueDrag, OnMenuEndDrag);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, 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)
|
||||
{
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
_container.UpdateContainer(nullEnt);
|
||||
|
||||
if (IsDragging)
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
if (nullEnt is not null)
|
||||
{
|
||||
if (_lastContainerPosition == null)
|
||||
{
|
||||
_container.OpenCenteredAt(new Vector2(0.5f, 0.75f));
|
||||
}
|
||||
else
|
||||
{
|
||||
_container.Open();
|
||||
|
||||
if (!StaticStorageUIEnabled)
|
||||
LayoutContainer.SetPosition(_container, _lastContainerPosition.Value);
|
||||
}
|
||||
|
||||
if (StaticStorageUIEnabled)
|
||||
{
|
||||
// we have to orphan it here because Open() sets the parent.
|
||||
_container.Orphan();
|
||||
Hotbar?.StorageContainer.AddChild(_container);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastContainerPosition = _container.GlobalPosition;
|
||||
_container.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStaticStorageChanged(bool obj)
|
||||
{
|
||||
if (StaticStorageUIEnabled == obj)
|
||||
return;
|
||||
|
||||
StaticStorageUIEnabled = obj;
|
||||
|
||||
if (_container == null)
|
||||
return;
|
||||
|
||||
if (!_container.IsOpen)
|
||||
return;
|
||||
|
||||
_container.Orphan();
|
||||
if (StaticStorageUIEnabled)
|
||||
{
|
||||
Hotbar?.StorageContainer.AddChild(_container);
|
||||
_lastContainerPosition = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ui.WindowRoot.AddChild(_container);
|
||||
}
|
||||
}
|
||||
|
||||
/// One might ask, Hey Emo, why are you parsing raw keyboard input just to rotate a rectangle?
|
||||
/// The answer is, that input bindings regarding mouse inputs are always intercepted by the UI,
|
||||
/// thus, if i want to be able to rotate my damn piece anywhere on the screen,
|
||||
/// I have to side-step all of the input handling. Cheers.
|
||||
private void OnMiddleMouse(KeyEventArgs keyEvent, KeyEventType type)
|
||||
{
|
||||
if (keyEvent.Handled)
|
||||
return;
|
||||
|
||||
if (type != KeyEventType.Down)
|
||||
return;
|
||||
|
||||
//todo there's gotta be a method for this in InputManager just expose it to content I BEG.
|
||||
if (!_input.TryGetKeyBinding(ContentKeyFunctions.RotateStoredItem, out var binding))
|
||||
return;
|
||||
if (binding.BaseKey != keyEvent.Key)
|
||||
return;
|
||||
|
||||
if (keyEvent.Shift &&
|
||||
!(binding.Mod1 == Keyboard.Key.Shift ||
|
||||
binding.Mod2 == Keyboard.Key.Shift ||
|
||||
binding.Mod3 == Keyboard.Key.Shift))
|
||||
return;
|
||||
|
||||
if (keyEvent.Alt &&
|
||||
!(binding.Mod1 == Keyboard.Key.Alt ||
|
||||
binding.Mod2 == Keyboard.Key.Alt ||
|
||||
binding.Mod3 == Keyboard.Key.Alt))
|
||||
return;
|
||||
|
||||
if (keyEvent.Control &&
|
||||
!(binding.Mod1 == Keyboard.Key.Control ||
|
||||
binding.Mod2 == Keyboard.Key.Control ||
|
||||
binding.Mod3 == Keyboard.Key.Control))
|
||||
return;
|
||||
|
||||
if (!IsDragging && _entity.System<HandsSystem>().GetActiveHandEntity() == null)
|
||||
return;
|
||||
|
||||
//clamp it to a cardinal.
|
||||
DraggingRotation = (DraggingRotation + Math.PI / 2f).GetCardinalDir().ToAngle();
|
||||
if (DraggingGhost != null)
|
||||
DraggingGhost.Location.Rotation = DraggingRotation;
|
||||
|
||||
if (IsDragging || (_container != null && UIManager.CurrentlyHovered == _container))
|
||||
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)
|
||||
{
|
||||
if (IsDragging || !_container?.IsOpen == true)
|
||||
return;
|
||||
|
||||
if (args.Function == ContentKeyFunctions.MoveStoredItem)
|
||||
{
|
||||
_menuDragHelper.MouseDown(control);
|
||||
_menuDragHelper.Update(0f);
|
||||
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ExamineEntity)
|
||||
{
|
||||
_entity.System<ExamineSystem>().DoExamine(control.Entity);
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == EngineKeyFunctions.UseSecondary)
|
||||
{
|
||||
UIManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(control.Entity);
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
|
||||
{
|
||||
_entity.EntityNetManager?.SendSystemNetworkMessage(
|
||||
new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: false));
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
|
||||
{
|
||||
_entity.RaisePredictiveEvent(new InteractInventorySlotEvent(_entity.GetNetEntity(control.Entity), altInteract: true));
|
||||
args.Handle();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPieceUnpressed(GUIBoundKeyEventArgs args, ItemGridPiece control)
|
||||
{
|
||||
if (_container?.StorageEntity is not { } storageEnt)
|
||||
return;
|
||||
|
||||
if (args.Function == ContentKeyFunctions.MoveStoredItem)
|
||||
{
|
||||
if (DraggingGhost is { } draggingGhost)
|
||||
{
|
||||
var position = _container.GetMouseGridPieceLocation(draggingGhost.Entity, draggingGhost.Location);
|
||||
_entity.RaisePredictiveEvent(new StorageSetItemLocationEvent(
|
||||
_entity.GetNetEntity(draggingGhost.Entity),
|
||||
_entity.GetNetEntity(storageEnt),
|
||||
new ItemStorageLocation(DraggingRotation, position)));
|
||||
_container?.BuildItemPieces();
|
||||
}
|
||||
else //if we just clicked, then take it out of the bag.
|
||||
{
|
||||
_entity.RaisePredictiveEvent(new StorageInteractWithItemEvent(
|
||||
_entity.GetNetEntity(control.Entity),
|
||||
_entity.GetNetEntity(storageEnt)));
|
||||
}
|
||||
_menuDragHelper.EndDrag();
|
||||
args.Handle();
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnMenuBeginDrag()
|
||||
{
|
||||
if (_menuDragHelper.Dragged is not { } dragged)
|
||||
return false;
|
||||
|
||||
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();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnMenuContinueDrag(float frameTime)
|
||||
{
|
||||
if (DraggingGhost == null)
|
||||
return false;
|
||||
SetDraggingRotation();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetDraggingRotation()
|
||||
{
|
||||
if (DraggingGhost == null)
|
||||
return;
|
||||
|
||||
var offset = ItemGridPiece.GetCenterOffset(
|
||||
(DraggingGhost.Entity, null),
|
||||
new ItemStorageLocation(DraggingRotation, Vector2i.Zero),
|
||||
_entity);
|
||||
|
||||
// I don't know why it divides the position by 2. Hope this helps! -emo
|
||||
LayoutContainer.SetPosition(DraggingGhost, UIManager.MousePositionScaled.Position / 2 - offset );
|
||||
}
|
||||
|
||||
private void OnMenuEndDrag()
|
||||
{
|
||||
if (DraggingGhost == null)
|
||||
return;
|
||||
DraggingGhost.Visible = false;
|
||||
DraggingGhost = null;
|
||||
DraggingRotation = Angle.Zero;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
_menuDragHelper.Update(args.DeltaSeconds);
|
||||
|
||||
if (!StaticStorageUIEnabled && _container?.Parent != null)
|
||||
_lastContainerPosition = _container.GlobalPosition;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user