Hud refactor (#7202)

Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Jezithyr <jmaster9999@gmail.com>
Co-authored-by: Jezithyr <Jezithyr@gmail.com>
Co-authored-by: Visne <39844191+Visne@users.noreply.github.com>
Co-authored-by: wrexbe <wrexbe@protonmail.com>
Co-authored-by: wrexbe <81056464+wrexbe@users.noreply.github.com>
This commit is contained in:
Jezithyr
2022-10-12 01:16:23 -07:00
committed by GitHub
parent d09fbc1849
commit 571dd4e6d5
168 changed files with 6940 additions and 7817 deletions

View File

@@ -1,48 +0,0 @@
using Content.Client.HUD;
using Content.Client.Items.UI;
using Content.Shared.Hands.Components;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Hands
{
public sealed class HandButton : ItemSlotButton
{
private bool _activeHand;
private bool _highlighted;
public HandButton(int size, string textureName, string storageTextureName, IGameHud gameHud, Texture blockedTexture, HandLocation location) : base(size, textureName, storageTextureName, gameHud)
{
Location = location;
AddChild(Blocked = new TextureRect
{
Texture = blockedTexture,
TextureScale = (2, 2),
MouseFilter = MouseFilterMode.Stop,
Visible = false
});
}
public HandLocation Location { get; }
public TextureRect Blocked { get; }
public void SetActiveHand(bool active)
{
_activeHand = active;
UpdateHighlight();
}
public override void Highlight(bool highlight)
{
_highlighted = highlight;
UpdateHighlight();
}
private void UpdateHighlight()
{
// always stay highlighted if active
base.Highlight(_activeHand || _highlighted);
}
}
}

View File

@@ -15,8 +15,6 @@ namespace Content.Client.Hands
[DataField("showInHands")]
public bool ShowInHands = true;
public HandsGui? Gui { get; set; }
/// <summary>
/// Data about the current sprite layers that the hand is contributing to the owner entity. Used for sprite in-hands.
/// </summary>

View File

@@ -1,8 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Animations;
using Content.Client.Hands.UI;
using Content.Client.HUD;
using Content.Client.Examine;
using Content.Client.Verbs;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
@@ -21,24 +20,39 @@ namespace Content.Client.Hands.Systems
public sealed class HandsSystem : SharedHandsSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly ExamineSystem _examine = default!;
[Dependency] private readonly VerbSystem _verbs = default!;
public event Action<string, HandLocation>? OnPlayerAddHand;
public event Action<string>? OnPlayerRemoveHand;
public event Action<string?>? OnPlayerSetActiveHand;
public event Action<HandsComponent>? OnPlayerHandsAdded;
public event Action? OnPlayerHandsRemoved;
public event Action<string, EntityUid>? OnPlayerItemAdded;
public event Action<string, EntityUid>? OnPlayerItemRemoved;
public event Action<string>? OnPlayerHandBlocked;
public event Action<string>? OnPlayerHandUnblocked;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedHandsComponent, EntRemovedFromContainerMessage>(HandleContainerModified);
SubscribeLocalEvent<SharedHandsComponent, EntInsertedIntoContainerMessage>(HandleContainerModified);
SubscribeLocalEvent<SharedHandsComponent, EntRemovedFromContainerMessage>(HandleItemRemoved);
SubscribeLocalEvent<SharedHandsComponent, EntInsertedIntoContainerMessage>(HandleItemAdded);
SubscribeLocalEvent<HandsComponent, PlayerAttachedEvent>(HandlePlayerAttached);
SubscribeLocalEvent<HandsComponent, PlayerDetachedEvent>(HandlePlayerDetached);
SubscribeLocalEvent<HandsComponent, ComponentAdd>(HandleCompAdd);
SubscribeLocalEvent<HandsComponent, ComponentRemove>(HandleCompRemove);
SubscribeLocalEvent<HandsComponent, ComponentHandleState>(HandleComponentState);
SubscribeLocalEvent<HandsComponent, VisualsChangedEvent>(OnVisualsChanged);
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
OnHandSetActive += OnHandActivated;
}
#region StateHandling
@@ -49,30 +63,42 @@ namespace Content.Client.Hands.Systems
var handsModified = component.Hands.Count != state.Hands.Count;
var manager = EnsureComp<ContainerManagerComponent>(uid);
foreach (var hand in state.Hands)
{
if (component.Hands.TryAdd(hand.Name, hand))
{
hand.Container = _containerSystem.EnsureContainer<ContainerSlot>(uid, hand.Name, manager);
handsModified = true;
}
}
if (handsModified)
{
List<Hand> addedHands = new();
foreach (var hand in state.Hands)
{
if (component.Hands.TryAdd(hand.Name, hand))
{
hand.Container = _containerSystem.EnsureContainer<ContainerSlot>(uid, hand.Name, manager);
addedHands.Add(hand);
}
}
foreach (var name in component.Hands.Keys)
{
if (!state.HandNames.Contains(name))
component.Hands.Remove(name);
{
RemoveHand(uid, name, component);
}
}
foreach (var hand in addedHands)
{
AddHand(uid, hand, component);
}
component.SortedHands = new(state.HandNames);
}
TrySetActiveHand(uid, state.ActiveHand, component);
if (component.ActiveHand == null && state.ActiveHand == null)
return; //edge case
if (uid == _playerManager.LocalPlayer?.ControlledEntity)
UpdateGui();
if (component.ActiveHand != null && state.ActiveHand != component.ActiveHand.Name)
{
SetActiveHand(uid, component.Hands[state.ActiveHand!], component);
}
}
#endregion
@@ -175,13 +201,64 @@ namespace Content.Client.Hands.Systems
EntityManager.RaisePredictiveEvent(new RequestActivateInHandEvent(handName));
}
#region visuals
private void HandleContainerModified(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args)
public void UIInventoryExamine(string handName)
{
if (handComp.Hands.TryGetValue(args.Container.ID, out var hand))
if (!TryGetPlayerHands(out var hands) ||
!hands.Hands.TryGetValue(handName, out var hand) ||
hand.HeldEntity is not { Valid: true } entity)
{
UpdateHandVisuals(uid, args.Entity, hand);
return;
}
_examine.DoExamine(entity);
}
/// <summary>
/// Called when a user clicks on the little "activation" icon in the hands GUI. This is currently only used
/// by storage (backpacks, etc).
/// </summary>
public void UIHandOpenContextMenu(string handName)
{
if (!TryGetPlayerHands(out var hands) ||
!hands.Hands.TryGetValue(handName, out var hand) ||
hand.HeldEntity is not { Valid: true } entity)
{
return;
}
_verbs.VerbMenu.OpenVerbMenu(entity);
}
#region visuals
private void HandleItemAdded(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args)
{
if (!handComp.Hands.TryGetValue(args.Container.ID, out var hand))
return;
UpdateHandVisuals(uid, args.Entity, hand);
if (uid != _playerManager.LocalPlayer?.ControlledEntity)
return;
OnPlayerItemAdded?.Invoke(hand.Name, args.Entity);
if (HasComp<HandVirtualItemComponent>(args.Entity))
OnPlayerHandBlocked?.Invoke(hand.Name);
}
private void HandleItemRemoved(EntityUid uid, SharedHandsComponent handComp, ContainerModifiedMessage args)
{
if (!handComp.Hands.TryGetValue(args.Container.ID, out var hand))
return;
UpdateHandVisuals(uid, args.Entity, hand);
if (uid != _playerManager.LocalPlayer?.ControlledEntity)
return;
OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity);
if (HasComp<HandVirtualItemComponent>(args.Entity))
OnPlayerHandUnblocked?.Invoke(hand.Name);
}
/// <summary>
@@ -192,9 +269,6 @@ namespace Content.Client.Hands.Systems
if (!Resolve(uid, ref handComp, ref sprite, false))
return;
if (uid == _playerManager.LocalPlayer?.ControlledEntity)
UpdateGui();
if (!handComp.ShowInHands)
return;
@@ -206,6 +280,7 @@ namespace Content.Client.Hands.Systems
{
sprite.RemoveLayer(key);
}
revealedLayers.Clear();
}
else
@@ -267,52 +342,76 @@ namespace Content.Client.Hands.Systems
#endregion
#region Gui
public void UpdateGui(HandsComponent? hands = null)
{
if (hands == null && !TryGetPlayerHands(out hands) || hands.Gui == null)
return;
var states = hands.Hands.Values
.Select(hand => new GuiHand(hand.Name, hand.Location, hand.HeldEntity))
.ToArray();
hands.Gui.Update(new HandsGuiState(states, hands.ActiveHand?.Name));
}
public override bool TrySetActiveHand(EntityUid uid, string? value, SharedHandsComponent? handComp = null)
{
if (!base.TrySetActiveHand(uid, value, handComp))
return false;
if (uid == _playerManager.LocalPlayer?.ControlledEntity)
UpdateGui();
return true;
}
private void HandlePlayerAttached(EntityUid uid, HandsComponent component, PlayerAttachedEvent args)
{
component.Gui = new HandsGui(component, this);
_gameHud.HandsContainer.AddChild(component.Gui);
component.Gui.SetPositionFirst();
UpdateGui(component);
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
OnPlayerHandsAdded?.Invoke(component);
}
private static void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args)
private void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args)
{
ClearGui(component);
OnPlayerHandsRemoved?.Invoke();
}
private static void HandleCompRemove(EntityUid uid, HandsComponent component, ComponentRemove args)
private void HandleCompAdd(EntityUid uid, HandsComponent component, ComponentAdd args)
{
ClearGui(component);
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
OnPlayerHandsAdded?.Invoke(component);
}
private static void ClearGui(HandsComponent comp)
private void HandleCompRemove(EntityUid uid, HandsComponent component, ComponentRemove args)
{
comp.Gui?.Orphan();
comp.Gui = null;
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
OnPlayerHandsRemoved?.Invoke();
}
#endregion
private void AddHand(EntityUid uid, Hand newHand, SharedHandsComponent? handsComp = null)
{
AddHand(uid, newHand.Name, newHand.Location, handsComp);
}
public override void AddHand(EntityUid uid, string handName, HandLocation handLocation, SharedHandsComponent? handsComp = null)
{
base.AddHand(uid, handName, handLocation, handsComp);
if (uid == _playerManager.LocalPlayer?.ControlledEntity)
OnPlayerAddHand?.Invoke(handName, handLocation);
if (handsComp == null)
return;
if (handsComp.ActiveHand == null)
SetActiveHand(uid, handsComp.Hands[handName], handsComp);
}
public override void RemoveHand(EntityUid uid, string handName, SharedHandsComponent? handsComp = null)
{
if (uid == _playerManager.LocalPlayer?.ControlledEntity && handsComp != null &&
handsComp.Hands.ContainsKey(handName) && uid ==
_playerManager.LocalPlayer?.ControlledEntity)
{
OnPlayerRemoveHand?.Invoke(handName);
}
base.RemoveHand(uid, handName, handsComp);
}
private void OnHandActivated(SharedHandsComponent? handsComponent)
{
if (handsComponent == null)
return;
if (_playerManager.LocalPlayer?.ControlledEntity != handsComponent.Owner)
return;
if (handsComponent.ActiveHand == null)
{
OnPlayerSetActiveHand?.Invoke(null);
return;
}
OnPlayerSetActiveHand?.Invoke(handsComponent.ActiveHand.Name);
}
}
}

View File

@@ -1,6 +0,0 @@
<Control xmlns="https://spacestation14.io">
<BoxContainer Orientation="Vertical">
<Control Name="StatusContainer" />
<BoxContainer Name="HandsContainer" Orientation="Horizontal" HorizontalAlignment="Center" />
</BoxContainer>
</Control>

View File

@@ -1,258 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Content.Client.Hands.Systems;
using Content.Client.HUD;
using Content.Client.Inventory;
using Content.Client.Items.Managers;
using Content.Client.Items.UI;
using Content.Client.Resources;
using Content.Shared.CCVar;
using Content.Shared.Hands.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Input;
using Robust.Shared.Timing;
namespace Content.Client.Hands.UI
{
[GenerateTypedNameReferences]
public sealed partial class HandsGui : Control
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
[Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
private readonly HandsSystem _handsSystem;
private readonly HandsComponent _handsComponent;
private string StorageTexture => "back.png";
private Texture BlockedTexture => _resourceCache.GetTexture("/Textures/Interface/Default/blocked.png");
private ItemStatusPanel StatusPanel { get; }
[ViewVariables] private GuiHand[] _hands = Array.Empty<GuiHand>();
private string? ActiveHand { get; set; }
public HandsGui(HandsComponent hands, HandsSystem handsSystem)
{
_handsComponent = hands;
_handsSystem = handsSystem;
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle);
StatusContainer.AddChild(StatusPanel);
StatusPanel.SetPositionFirst();
}
protected override void EnteredTree()
{
base.EnteredTree();
_configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
}
protected override void ExitedTree()
{
base.ExitedTree();
_configManager.UnsubValueChanged(CCVars.HudTheme, UpdateHudTheme);
}
public void Update(HandsGuiState state)
{
ActiveHand = state.ActiveHand;
_hands = state.GuiHands;
Array.Sort(_hands, HandOrderComparer.Instance);
UpdateGui();
}
private void UpdateGui()
{
HandsContainer.DisposeAllChildren();
var entManager = IoCManager.Resolve<IEntityManager>();
foreach (var hand in _hands)
{
var newButton = MakeHandButton(hand.HandLocation);
HandsContainer.AddChild(newButton);
hand.HandButton = newButton;
var handName = hand.Name;
newButton.OnPressed += args => OnHandPressed(args, handName);
newButton.OnStoragePressed += _ => OnStoragePressed(handName);
_itemSlotManager.SetItemSlot(newButton, hand.HeldItem);
// Show blocked overlay if hand is blocked.
newButton.Blocked.Visible =
hand.HeldItem != null && entManager.HasComponent<HandVirtualItemComponent>(hand.HeldItem.Value);
}
if (TryGetActiveHand(out var activeHand))
{
activeHand.HandButton.SetActiveHand(true);
StatusPanel.Update(activeHand.HeldItem);
}
}
private void OnHandPressed(GUIBoundKeyEventArgs args, string handName)
{
if (args.Function == EngineKeyFunctions.UIClick)
{
_handsSystem.UIHandClick(_handsComponent, handName);
}
else if (TryGetHand(handName, out var hand))
{
_itemSlotManager.OnButtonPressed(args, hand.HeldItem);
}
}
private void OnStoragePressed(string handName)
{
_handsSystem.UIHandActivate(handName);
}
private bool TryGetActiveHand([NotNullWhen(true)] out GuiHand? activeHand)
{
TryGetHand(ActiveHand, out activeHand);
return activeHand != null;
}
private bool TryGetHand(string? handName, [NotNullWhen(true)] out GuiHand? foundHand)
{
foundHand = null;
if (handName == null)
return false;
foreach (var hand in _hands)
{
if (hand.Name == handName)
foundHand = hand;
}
return foundHand != null;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
foreach (var hand in _hands)
{
_itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem);
}
}
private HandButton MakeHandButton(HandLocation buttonLocation)
{
var buttonTextureName = buttonLocation switch
{
HandLocation.Right => "hand_r.png",
_ => "hand_l.png"
};
return new HandButton(ClientInventorySystem.ButtonSize, buttonTextureName, StorageTexture, _gameHud, BlockedTexture, buttonLocation);
}
private void UpdateHudTheme(int idx)
{
UpdateGui();
}
private sealed class HandOrderComparer : IComparer<GuiHand>
{
public static readonly HandOrderComparer Instance = new();
public int Compare(GuiHand? x, GuiHand? y)
{
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(null, y)) return 1;
if (ReferenceEquals(null, x)) return -1;
var orderX = Map(x.HandLocation);
var orderY = Map(y.HandLocation);
return orderX.CompareTo(orderY);
static int Map(HandLocation loc)
{
return loc switch
{
HandLocation.Left => 3,
HandLocation.Middle => 2,
HandLocation.Right => 1,
_ => throw new ArgumentOutOfRangeException(nameof(loc), loc, null)
};
}
}
}
}
/// <summary>
/// Info on a set of hands to be displayed.
/// </summary>
public sealed class HandsGuiState
{
/// <summary>
/// The set of hands to be displayed.
/// </summary>
[ViewVariables]
public GuiHand[] GuiHands { get; }
/// <summary>
/// The name of the currently active hand.
/// </summary>
[ViewVariables]
public string? ActiveHand { get; }
public HandsGuiState(GuiHand[] guiHands, string? activeHand = null)
{
GuiHands = guiHands;
ActiveHand = activeHand;
}
}
/// <summary>
/// Info on an individual hand to be displayed.
/// </summary>
public sealed class GuiHand
{
/// <summary>
/// The name of this hand.
/// </summary>
[ViewVariables]
public string Name { get; }
/// <summary>
/// Where this hand is located.
/// </summary>
[ViewVariables]
public HandLocation HandLocation { get; }
/// <summary>
/// The item being held in this hand.
/// </summary>
[ViewVariables]
public EntityUid? HeldItem { get; }
/// <summary>
/// The button in the gui associated with this hand. Assumed to be set by gui shortly after being received from the client HandsComponent.
/// </summary>
[ViewVariables]
public HandButton HandButton { get; set; } = default!;
public GuiHand(string name, HandLocation handLocation, EntityUid? heldItem)
{
Name = name;
HandLocation = handLocation;
HeldItem = heldItem;
}
}
}