+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.UserInterface.Systems.Inventory.Controls;
+using Content.Shared.Hands.Components;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.UserInterface.Systems.Hands.Controls;
public sealed class HandsContainer : ItemSlotUIContainer<HandButton>
{
private readonly GridContainer _grid;
- public int ColumnLimit { get => _grid.Columns; set => _grid.Columns = value; }
- public int MaxButtonCount { get; set; } = 0;
+ private readonly List<HandButton> _orderedButtons = new();
+ public HandsComponent? PlayerHandsComponent;
- public int MaxButtonsPerRow { get; set; }= 6;
+ public int ColumnLimit { get; set; } = 6;
/// <summary>
/// Indexer. This is used to reference a HandsContainer from the
_grid.ExpandBackwards = true;
}
- public override HandButton? AddButton(HandButton newButton)
+ protected override void AddButton(HandButton newButton)
{
- if (MaxButtonCount > 0)
- {
- if (ButtonCount >= MaxButtonCount)
- return null;
+ _orderedButtons.Add(newButton);
- _grid.AddChild(newButton);
- }
- else
+ _grid.RemoveAllChildren();
+ var enumerable = PlayerHandsComponent?.SortedHands is { } sortedHands
+ ? _orderedButtons.OrderBy(it => sortedHands.IndexOf(it.SlotName))
+ : _orderedButtons.OrderBy(it => it.HandLocation);
+ foreach (var button in enumerable)
{
- _grid.AddChild(newButton);
+ _grid.AddChild(button);
}
- _grid.Columns = Math.Min(_grid.ChildCount, MaxButtonsPerRow);
- return base.AddButton(newButton);
+ _grid.Columns = Math.Min(_grid.ChildCount, ColumnLimit);
}
- public override void RemoveButton(string handName)
+ protected override void RemoveButton(HandButton button)
{
- var button = GetButton(handName);
- if (button == null)
- return;
- base.RemoveButton(button);
+ _orderedButtons.Remove(button);
_grid.RemoveChild(button);
}
- public bool TryGetLastButton(out HandButton? control)
+ public override void ClearButtons()
{
- if (Buttons.Count == 0)
- {
- control = null;
- return false;
- }
-
- control = Buttons.Values.Last();
- return true;
- }
-
- public bool TryRemoveLastHand(out HandButton? control)
- {
- var success = TryGetLastButton(out control);
- if (control != null)
- RemoveButton(control);
- return success;
- }
-
- public void Clear()
- {
- ClearButtons();
- _grid.RemoveAllChildren();
+ base.ClearButtons();
+ _orderedButtons.Clear();
}
public IEnumerable<HandButton> GetButtons()
yield return hand;
}
}
-
- public bool IsFull => (MaxButtonCount != 0 && ButtonCount >= MaxButtonCount);
-
- public int ButtonCount => _grid.ChildCount;
}
using Content.Shared.Input;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Timing;
-using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
[UISystemDependency] private readonly HandsSystem _handsSystem = default!;
[UISystemDependency] private readonly UseDelaySystem _useDelay = default!;
- private readonly List<HandsContainer> _handsContainers = new();
- private readonly Dictionary<string, int> _handContainerIndices = new();
- private readonly Dictionary<string, HandButton> _handLookup = new();
private HandsComponent? _playerHandsComponent;
private HandButton? _activeHand;
private HandButton? _statusHandLeft;
private HandButton? _statusHandRight;
- private int _backupSuffix; //this is used when autogenerating container names if they don't have names
-
private HotbarGui? HandsGui => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
public void OnSystemLoaded(HandsSystem system)
private void UnloadPlayerHands()
{
- if (HandsGui != null)
- HandsGui.Visible = false;
-
- _handContainerIndices.Clear();
- _handLookup.Clear();
+ HandsGui?.Visible = false;
+ HandsGui?.HandContainer.ClearButtons();
_playerHandsComponent = null;
-
- foreach (var container in _handsContainers)
- {
- container.Clear();
- }
}
private void LoadPlayerHands(Entity<HandsComponent> handsComp)
{
DebugTools.Assert(_playerHandsComponent == null);
- if (HandsGui != null)
- HandsGui.Visible = true;
-
+ HandsGui?.Visible = true;
+ HandsGui?.HandContainer.PlayerHandsComponent = handsComp;
_playerHandsComponent = handsComp;
foreach (var (name, hand) in handsComp.Comp.Hands)
{
private void HandBlocked(string handName)
{
- if (!_handLookup.TryGetValue(handName, out var hand))
- {
+ if (HandsGui?.HandContainer.TryGetButton(handName, out var hand) != true)
return;
- }
- hand.Blocked = true;
+ hand!.Blocked = true;
}
private void HandUnblocked(string handName)
{
- if (!_handLookup.TryGetValue(handName, out var hand))
- {
+ if (HandsGui?.HandContainer.TryGetButton(handName, out var hand) != true)
return;
- }
- hand.Blocked = false;
- }
-
- private int GetHandContainerIndex(string containerName)
- {
- if (!_handContainerIndices.TryGetValue(containerName, out var result))
- return -1;
- return result;
+ hand!.Blocked = false;
}
private void OnItemAdded(string name, EntityUid entity)
_handsSystem.TryGetHand((playerEntity, _playerHandsComponent), name, out var handData))
{
UpdateHandStatus(hand, null, handData);
- if (handData?.EmptyRepresentative is { } representative)
+ if (handData.Value.EmptyRepresentative is { } representative)
{
SetRepresentative(hand, representative);
return;
hand.SetEntity(null);
}
- private HandsContainer GetFirstAvailableContainer()
- {
- if (_handsContainers.Count == 0)
- throw new Exception("Could not find an attached hand hud container");
- foreach (var container in _handsContainers)
- {
- if (container.IsFull)
- continue;
- return container;
- }
-
- throw new Exception("All attached hand hud containers were full!");
- }
-
- public bool TryGetHandContainer(string containerName, out HandsContainer? container)
- {
- container = null;
- var containerIndex = GetHandContainerIndex(containerName);
- if (containerIndex == -1)
- return false;
- container = _handsContainers[containerIndex];
- return true;
- }
-
//propagate hand activation to the hand system.
private void StorageActivate(GUIBoundKeyEventArgs args, SlotControl handControl)
{
return;
}
- if (!_handLookup.TryGetValue(handName, out var handControl) || handControl == _activeHand)
+ if (HandsGui?.HandContainer.TryGetButton(handName, out var handControl) != true || handControl == _activeHand)
return;
if (_activeHand != null)
_activeHand.Highlight = false;
- handControl.Highlight = true;
+ handControl!.Highlight = true;
_activeHand = handControl;
- if (HandsGui != null &&
- _playerHandsComponent != null &&
+ if (_playerHandsComponent != null &&
_player.LocalSession?.AttachedEntity is { } playerEntity &&
_handsSystem.TryGetHand((playerEntity, _playerHandsComponent), handName, out var hand))
{
var heldEnt = _handsSystem.GetHeldItem((playerEntity, _playerHandsComponent), handName);
- var foldedLocation = hand.Value.Location.GetUILocation();
- if (foldedLocation == HandUILocation.Left)
+ var foldedLocation = hand.Value.Location;
+ if (foldedLocation == HandLocation.Left)
{
_statusHandLeft = handControl;
HandsGui.UpdatePanelEntityLeft(heldEnt, hand.Value);
private HandButton? GetHand(string handName)
{
- _handLookup.TryGetValue(handName, out var handControl);
- return handControl;
+ return HandsGui?.HandContainer.GetButton(handName);
}
private HandButton AddHand(string handName, Hand hand)
button.StoragePressed += StorageActivate;
button.Pressed += HandPressed;
- if (!_handLookup.TryAdd(handName, button))
- return _handLookup[handName];
-
- if (HandsGui != null)
- {
- HandsGui.HandContainer.AddButton(button);
- }
- else
- {
- GetFirstAvailableContainer().AddButton(button);
- }
+ HandsGui?.HandContainer.TryAddButton(button);
if (hand.EmptyRepresentative is { } representative)
{
// If we don't have a status for this hand type yet, set it.
// This means we have status filled by default in most scenarios,
// otherwise the user'd need to switch hands to "activate" the hands the first time.
- if (hand.Location.GetUILocation() == HandUILocation.Left)
+ if (hand.Location == HandLocation.Left)
_statusHandLeft ??= button;
else
_statusHandRight ??= button;
_handsSystem.ReloadHandButtons();
}
- /// <summary>
- /// Swap hands from one container to the other.
- /// </summary>
- /// <param name="other"></param>
- /// <param name="source"></param>
- public void SwapHands(HandsContainer other, HandsContainer? source = null)
- {
- if (HandsGui == null && source == null)
- {
- throw new ArgumentException("Cannot swap hands if no source hand container exists!");
- }
-
- source ??= HandsGui!.HandContainer;
-
- var transfer = new List<Control>();
- foreach (var child in source.Children)
- {
- if (child is not HandButton)
- {
- continue;
- }
-
- transfer.Add(child);
- }
-
- foreach (var control in transfer)
- {
- source.RemoveChild(control);
- other.AddChild(control);
- }
- }
-
private void RemoveHand(string handName)
{
- RemoveHand(handName, out _);
- }
-
- [PublicAPI]
- private bool RemoveHand(string handName, out HandButton? handButton)
- {
- if (!_handLookup.TryGetValue(handName, out handButton))
- return false;
- if (handButton.Parent is HandsContainer handContainer)
- {
- handContainer.RemoveButton(handButton);
- }
+ if (HandsGui?.HandContainer.TryRemoveButton(handName, out var handButton) != true)
+ return;
if (_statusHandLeft == handButton)
_statusHandLeft = null;
if (_statusHandRight == handButton)
_statusHandRight = null;
- _handLookup.Remove(handName);
- handButton.Orphan();
UpdateVisibleStatusPanels();
- return true;
}
private void UpdateVisibleStatusPanels()
var leftVisible = false;
var rightVisible = false;
- foreach (var hand in _handLookup.Values)
+ if (HandsGui is null)
+ return;
+
+ foreach (var hand in HandsGui.HandContainer.GetButtons())
{
- if (hand.HandLocation.GetUILocation() == HandUILocation.Left)
+ if (hand.HandLocation == HandLocation.Left)
{
leftVisible = true;
}
}
}
- HandsGui?.UpdateStatusVisibility(leftVisible, rightVisible);
- }
-
- public string RegisterHandContainer(HandsContainer handContainer)
- {
- var name = "HandContainer_" + _backupSuffix;
-
- if (handContainer.Indexer == null)
- {
- handContainer.Indexer = name;
- _backupSuffix++;
- }
- else
- {
- name = handContainer.Indexer;
- }
-
- _handContainerIndices.Add(name, _handsContainers.Count);
- _handsContainers.Add(handContainer);
- return name;
- }
-
- public bool RemoveHandContainer(string handContainerName)
- {
- var index = GetHandContainerIndex(handContainerName);
- if (index == -1)
- return false;
- _handContainerIndices.Remove(handContainerName);
- _handsContainers.RemoveAt(index);
- return true;
- }
-
- public bool RemoveHandContainer(string handContainerName, out HandsContainer? container)
- {
- var success = _handContainerIndices.TryGetValue(handContainerName, out var index);
- container = _handsContainers[index];
- _handContainerIndices.Remove(handContainerName);
- _handsContainers.RemoveAt(index);
- return success;
+ HandsGui.UpdateStatusVisibility(leftVisible, rightVisible);
}
public void OnStateEntered(GameplayState state)
{
- if (HandsGui != null)
- HandsGui.Visible = _playerHandsComponent != null;
+ HandsGui?.Visible = _playerHandsComponent != null;
}
public override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
+ if (HandsGui is not { } handsGui)
+ return;
+
// TODO this should be event based but 2 systems modify the same component differently for some reason
- foreach (var container in _handsContainers)
+ foreach (var hand in handsGui.HandContainer.GetButtons())
{
- foreach (var hand in container.GetButtons())
- {
-
- if (!_entities.TryGetComponent(hand.Entity, out UseDelayComponent? useDelay))
- {
- hand.CooldownDisplay.Visible = false;
- continue;
- }
- var delay = _useDelay.GetLastEndingDelay((hand.Entity.Value, useDelay));
- hand.CooldownDisplay.Visible = true;
- hand.CooldownDisplay.FromTime(delay.StartTime, delay.EndTime);
+ if (!_entities.TryGetComponent(hand.Entity, out UseDelayComponent? useDelay))
+ {
+ hand.CooldownDisplay.Visible = false;
+ continue;
}
+ var delay = _useDelay.GetLastEndingDelay((hand.Entity.Value, useDelay));
+
+ hand.CooldownDisplay.Visible = true;
+ hand.CooldownDisplay.FromTime(delay.StartTime, delay.EndTime);
}
}
_inventory = UIManager.GetUIController<InventoryUIController>();
_hands = UIManager.GetUIController<HandsUIController>();
_storage = UIManager.GetUIController<StorageUIController>();
- _hands.RegisterHandContainer(handsContainer);
}
public void ReloadHotbar()
public HotbarGui()
{
RobustXamlLoader.Load(this);
- StatusPanelRight.SetSide(HandUILocation.Right);
- StatusPanelLeft.SetSide(HandUILocation.Left);
+ StatusPanelRight.SetSide(HandLocation.Right);
+ StatusPanelLeft.SetSide(HandLocation.Left);
var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
hotbarController.Setup(HandContainer);
StatusPanelRight.Update(entity, hand);
}
- public void SetHighlightHand(HandUILocation? hand)
+ public void SetHighlightHand(HandLocation? hand)
{
- StatusPanelLeft.UpdateHighlight(hand is HandUILocation.Left);
- StatusPanelRight.UpdateHighlight(hand is HandUILocation.Right);
+ StatusPanelLeft.UpdateHighlight(hand is HandLocation.Left);
+ StatusPanelRight.UpdateHighlight(hand is HandLocation.Right);
}
public void UpdateStatusVisibility(bool left, bool right)
[Virtual]
public abstract class ItemSlotUIContainer<T> : GridContainer, IItemslotUIContainer where T : SlotControl
{
- protected readonly Dictionary<string, T> Buttons = new();
+ private readonly Dictionary<string, T> _buttons = new();
- private int? _maxColumns;
+ public int? MaxColumns { get; set; }
- public int? MaxColumns
+ public virtual void ClearButtons()
{
- get => _maxColumns;
- set => _maxColumns = value;
- }
-
- public virtual bool TryAddButton(T newButton, out T button)
- {
- var tempButton = AddButton(newButton);
- if (tempButton == null)
- {
- button = newButton;
- return false;
- }
-
- button = newButton;
- return true;
- }
-
- public void ClearButtons()
- {
- foreach (var button in Buttons.Values)
+ foreach (var button in _buttons.Values)
{
- button.Dispose();
+ button.Orphan();
}
- Buttons.Clear();
+ _buttons.Clear();
}
public bool TryRegisterButton(SlotControl control, string newSlotName)
{
if (newSlotName == "")
return false;
- if (!(control is T slotButton))
+ if (control is not T slotButton)
return false;
- if (Buttons.TryGetValue(newSlotName, out var foundButton))
+
+ if (_buttons.TryGetValue(newSlotName, out var foundButton))
{
if (control == foundButton)
return true; //if the slotName is already set do nothing
throw new Exception("Could not update button to slot:" + newSlotName + " slot already assigned!");
}
- Buttons.Remove(slotButton.SlotName);
- AddButton(slotButton);
+ _buttons.Remove(slotButton.SlotName);
+ TryAddButton(slotButton);
return true;
}
{
if (control is not T newButton)
return false;
- return AddButton(newButton) != null;
- }
-
- public virtual T? AddButton(T newButton)
- {
- if (!Children.Contains(newButton) && newButton.Parent == null && newButton.SlotName != "")
- AddChild(newButton);
- Columns = _maxColumns ?? ChildCount;
- return AddButtonToDict(newButton);
+ return TryAddButton(newButton) != null;
}
- protected virtual T? AddButtonToDict(T newButton)
+ public T? TryAddButton(T newButton)
{
if (newButton.SlotName == "")
{
- Logger.Warning("Could not add button " + newButton.Name + "No slotname");
+ Log.Warning($"{newButton.Name} because it has no slot name");
+ return null;
}
- return !Buttons.TryAdd(newButton.SlotName, newButton) ? null : newButton;
- }
+ if (Children.Contains(newButton) || newButton.Parent != null)
+ return null;
- public virtual void RemoveButton(string slotName)
- {
- if (!Buttons.TryGetValue(slotName, out var button))
- return;
- RemoveButton(button);
- }
+ if (!_buttons.TryAdd(newButton.SlotName, newButton))
+ return null;
- public virtual void RemoveButtons(params string[] slotNames)
- {
- foreach (var slotName in slotNames)
- {
- RemoveButton(slotName);
- }
+ AddButton(newButton);
+ return newButton;
}
- public virtual void RemoveButtons(params T?[] buttons)
+ protected virtual void AddButton(T newButton)
{
- foreach (var button in buttons)
- {
- if (button != null)
- RemoveButton(button);
- }
+ AddChild(newButton);
+ Columns = MaxColumns ?? ChildCount;
}
- protected virtual void RemoveButtonFromDict(T button)
+ public bool TryRemoveButton(string slotName, [NotNullWhen(true)] out T? button)
{
- Buttons.Remove(button.SlotName);
+ if (!_buttons.TryGetValue(slotName, out button))
+ return false;
+
+ _buttons.Remove(button.SlotName);
+ RemoveButton(button);
+ return true;
}
- public virtual void RemoveButton(T button)
+ protected virtual void RemoveButton(T button)
{
- RemoveButtonFromDict(button);
Children.Remove(button);
- button.Dispose();
}
- public virtual T? GetButton(string slotName)
+ public T? GetButton(string slotName)
{
- return !Buttons.TryGetValue(slotName, out var button) ? null : button;
+ return _buttons.GetValueOrDefault(slotName);
}
- public virtual bool TryGetButton(string slotName, [NotNullWhen(true)] out T? button)
+ public bool TryGetButton(string slotName, [NotNullWhen(true)] out T? button)
{
return (button = GetButton(slotName)) != null;
}
[ViewVariables] private Hand? _hand;
// Tracked so we can re-run SetSide() if the theme changes.
- private HandUILocation _side;
+ private HandLocation _side;
public ItemStatusPanel()
{
IoCManager.InjectDependencies(this);
}
- public void SetSide(HandUILocation location)
+ public void SetSide(HandLocation location)
{
// AN IMPORTANT REMINDER ABOUT THIS CODE:
// In the UI, the RIGHT hand is on the LEFT on the screen.
switch (location)
{
- case HandUILocation.Right:
+ case HandLocation.Right or HandLocation.Middle:
texture = Theme.ResolveTexture("item_status_right");
textureHighlight = Theme.ResolveTexture("item_status_right_highlight");
cutOut = StyleBox.Margin.Left;
flat = StyleBox.Margin.Right;
contentMargin = MarginFromThemeColor("_itemstatus_content_margin_right");
break;
- case HandUILocation.Left:
+ case HandLocation.Left:
texture = Theme.ResolveTexture("item_status_left");
textureHighlight = Theme.ResolveTexture("item_status_left_highlight");
cutOut = StyleBox.Margin.Right;
if (!container.TryGetButton(data.SlotName, out var button))
{
button = CreateSlotButton(data);
- container.AddButton(button);
+ container.TryAddButton(button);
}
var showStorage = _entities.HasComponent<StorageComponent>(data.HeldEntity);
return;
var button = CreateSlotButton(data);
- slotGroup.AddButton(button);
+ slotGroup.TryAddButton(button);
}
private void RemoveSlot(SlotData data)
if (!_slotGroups.TryGetValue(data.SlotGroup, out var slotGroup))
return;
- slotGroup.RemoveButton(data.SlotName);
+ slotGroup.TryRemoveButton(data.SlotName, out _);
}
public void ReloadSlots()
/// <summary>
/// What side of the body this hand is on.
/// </summary>
-/// <seealso cref="HandUILocation"/>
-/// <seealso cref="HandLocationExt"/>
public enum HandLocation : byte
{
- Left,
+ Right,
Middle,
- Right
-}
-
-/// <summary>
-/// What side of the UI a hand is on.
-/// </summary>
-/// <seealso cref="HandLocationExt"/>
-/// <seealso cref="HandLocation"/>
-public enum HandUILocation : byte
-{
- Left,
- Right
-}
-
-/// <summary>
-/// Helper functions for working with <see cref="HandLocation"/>.
-/// </summary>
-public static class HandLocationExt
-{
- /// <summary>
- /// Convert a <see cref="HandLocation"/> into the appropriate <see cref="HandUILocation"/>.
- /// This maps "middle" hands to <see cref="HandUILocation.Right"/>.
- /// </summary>
- public static HandUILocation GetUILocation(this HandLocation location)
- {
- return location switch
- {
- HandLocation.Left => HandUILocation.Left,
- HandLocation.Middle => HandUILocation.Right,
- HandLocation.Right => HandUILocation.Right,
- _ => throw new ArgumentOutOfRangeException(nameof(location), location, null)
- };
- }
+ Left
}
ent.Comp.Hands.Add(handName, hand);
ent.Comp.SortedHands.Add(handName);
+ // we use LINQ + ToList instead of the list sort because it's a stable sort vs the list sort
+ ent.Comp.SortedHands = ent.Comp.SortedHands.OrderBy(handId => ent.Comp.Hands[handId].Location).ToList();
Dirty(ent);
OnPlayerAddHand?.Invoke((ent, ent.Comp), handName, hand.Location);