Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 262 additions & 0 deletions Content.Client/Backmen/VovaMech/BkmVovaMechHandsUIController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
using Content.Client.Gameplay;
using Content.Client.Hands.Systems;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Hands.Controls;
using Content.Client.UserInterface.Systems.Hotbar.Widgets;
using Content.Client.Verbs.UI;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Timing;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.Input;
using Robust.Shared.Timing;

namespace Content.Client.Backmen.VovaMech;

/// <summary>
/// Separate hand bar for OneStar mech innate tools, shown above the player hotbar while piloting.
/// </summary>
public sealed partial class BkmVovaMechHandsUIController : UIController, IOnStateEntered<GameplayState>, IOnSystemChanged<BkmVovaMechSystem>, IOnSystemChanged<HandsSystem>
{
[Dependency] private IEntityManager _entities = default!;

[UISystemDependency] private readonly BkmVovaMechSystem _mechSystem = default!;
[UISystemDependency] private readonly HandsSystem _handsSystem = default!;
[UISystemDependency] private readonly UseDelaySystem _useDelay = default!;

private EntityUid? _mechUid;
private HandsComponent? _mechHands;
private HandButton? _activeHand;

private HotbarGui? Hotbar => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();

public void OnSystemLoaded(BkmVovaMechSystem system)
{
system.LocalPilotedMechChanged += OnLocalPilotedMechChanged;
}

public void OnSystemUnloaded(BkmVovaMechSystem system)
{
system.LocalPilotedMechChanged -= OnLocalPilotedMechChanged;
}

public void OnSystemLoaded(HandsSystem system)
{
system.OnPlayerAddHand += OnMechHandAdded;
system.OnPlayerRemoveHand += OnMechHandRemoved;
}

public void OnSystemUnloaded(HandsSystem system)
{
system.OnPlayerAddHand -= OnMechHandAdded;
system.OnPlayerRemoveHand -= OnMechHandRemoved;
}

public void OnStateEntered(GameplayState state)
{
if (_mechUid is { } mech && _mechHands is { } hands)
LoadMechHands(mech, hands);
}

public override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);

if (_mechUid is not { } mech || _mechHands == null || Hotbar?.MechHandContainer is not { } container)
return;

SetActiveHand(_mechHands.ActiveHandId);

foreach (var hand in container.GetButtons())
RefreshHandButton(mech, hand.SlotName, hand);

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

private void OnLocalPilotedMechChanged(EntityUid? mech)
{
if (mech == null)
{
UnloadMechHands();
return;
}

if (!_entities.TryGetComponent(mech, out HandsComponent? hands))
{
_mechUid = mech;
_mechHands = null;
return;
}

LoadMechHands(mech.Value, hands);
}

private void OnMechHandAdded(Entity<HandsComponent> entity, string name, HandLocation location)
{
if (entity.Owner != _mechUid || _mechHands == null)
return;

if (!_handsSystem.TryGetHand((entity.Owner, entity.Comp), name, out var hand))
return;

AddHandButton(name, hand.Value);
SetActiveHand(_mechHands.ActiveHandId);
}
Comment on lines +92 to +112

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n --type=cs -C5 'OnPlayerAddHand' Content.Client/Hands Content.Shared/Hands

Repository: Rxup/space-station-14

Length of output: 2542


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='Content.Client/Backmen/VovaMech/BkmVovaMechHandsUIController.cs'
wc -l "$FILE"
cat -n "$FILE" | sed -n '1,220p'

printf '\n--- subscriptions/usages ---\n'
rg -n --type=cs -C4 'OnPlayerAddHand|OnPlayerRemoveHand|OnMechHandAdded|OnMechHandRemoved|LoadMechHands|HandsComponent' Content.Client/Backmen/VovaMech Content.Client Content.Shared/Hands

Repository: Rxup/space-station-14

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --type=cs -C4 'event .*LocalPilotedMechChanged|LocalPilotedMechChanged\+|LocalPilotedMechChanged -|LocalPilotedMechChanged' Content.Client/Backmen/VovaMech Content.Shared Content.Client
printf '\n--- client hands startup section ---\n'
cat -n Content.Client/Hands/Systems/HandsSystem.cs | sed -n '58,110p'
printf '\n--- add hand method section ---\n'
cat -n Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs | sed -n '70,115p'

Repository: Rxup/space-station-14

Length of output: 11926


_mechHands не подхватывается при поздней инициализации рук меха

Если HandsComponent появляется после LocalPilotedMechChanged, _mechHands остаётся null до пересадки: OnMechHandAdded сразу выходит по этому флагу, а других путей повторно вызвать LoadMechHands нет. Добавь восстановление в OnMechHandAdded (или отдельный хук на старт HandsComponent), иначе UI рук меха может так и не появиться в текущей сессии пилотирования.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Client/Backmen/VovaMech/BkmVovaMechHandsUIController.cs` around lines
92 - 112, `_mechHands` can stay null when `HandsComponent` is added after
`LocalPilotedMechChanged`, so the mech hands UI never initializes in the current
session. Update `OnMechHandAdded` in `BkmVovaMechHandsUIController` to recover
from this late-start case by reloading hands when the added component belongs to
`_mechUid` and `_mechHands` is still null, or add a dedicated startup hook for
`HandsComponent` that calls `LoadMechHands`. Keep the existing `AddHandButton`
and `SetActiveHand` flow intact once hands are restored.


private void OnMechHandRemoved(Entity<HandsComponent> entity, string name)
{
if (entity.Owner != _mechUid || Hotbar?.MechHandContainer is not { } container)
return;

container.TryRemoveButton(name, out _);
SetActiveHand(_mechHands?.ActiveHandId);
}

private void LoadMechHands(EntityUid mech, HandsComponent hands)
{
UnloadMechHands();

_mechUid = mech;
_mechHands = hands;

if (Hotbar == null)
return;

Hotbar.MechHandsRow.Visible = true;
Hotbar.MechHandContainer.PlayerHandsComponent = hands;
Hotbar.MechHandContainer.ClearButtons();

foreach (var handId in hands.SortedHands)
{
if (!_handsSystem.TryGetHand((mech, hands), handId, out var hand))
continue;

AddHandButton(handId, hand.Value);
}

SetActiveHand(hands.ActiveHandId);

if (hands.ActiveHandId == null && hands.SortedHands.Count > 0)
_mechSystem.RequestSetMechHand(hands.SortedHands[0]);
}

private void UnloadMechHands()
{
_mechUid = null;
_mechHands = null;
_activeHand = null;

if (Hotbar == null)
return;

Hotbar.MechHandsRow.Visible = false;
Hotbar.MechHandContainer.ClearButtons();
Hotbar.MechHandContainer.PlayerHandsComponent = null;
}

private void AddHandButton(string handId, Hand hand)
{
if (Hotbar?.MechHandContainer is not { } || _mechUid is not { } mech)
return;

var button = new HandButton(handId, hand.Location);
button.Pressed += HandPressed;

Hotbar.MechHandContainer.TryAddButton(button);
RefreshHandButton(mech, handId, button);
}

private void RefreshHandButton(EntityUid mech, string handId, HandButton button)
{
if (_mechHands == null)
return;

if (_handsSystem.TryGetHeldItem((mech, _mechHands), handId, out var held) &&
_entities.TryGetComponent(held, out VirtualItemComponent? virt))
{
button.SetEntity(virt.BlockingEntity);
button.Blocked = true;
return;
}

button.SetEntity(held);
button.Blocked = false;
}

private void HandPressed(GUIBoundKeyEventArgs args, SlotControl hand)
{
if (_mechUid is not { } mech || _mechHands == null)
return;

var handsEnt = (mech, _mechHands);

if (args.Function == EngineKeyFunctions.UIClick)
{
MechHandClick(handsEnt, hand.SlotName);
SetActiveHand(_mechHands.ActiveHandId);
args.Handle();
}
else if (args.Function == EngineKeyFunctions.UseSecondary)
{
if (_handsSystem.TryGetHeldItem(handsEnt, hand.SlotName, out var held))
UIManager.GetUIController<VerbMenuUIController>().OpenVerbMenu(held.Value);

args.Handle();
}
else if (args.Function == ContentKeyFunctions.ActivateItemInWorld)
{
_handsSystem.TryActivateItemInHand(mech, _mechHands, hand.SlotName);
args.Handle();
}
else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld)
{
_handsSystem.TryUseItemInHand(mech, altInteract: true, _mechHands, hand.SlotName);
args.Handle();
}
}

private void MechHandClick(Entity<HandsComponent> ent, string handName)
{
var hands = ent.Comp;

if (handName != hands.ActiveHandId)
{
_mechSystem.RequestSetMechHand(handName);
return;
}

if (_handsSystem.GetActiveItem(ent.AsNullable()) != null)
_handsSystem.TryUseItemInHand(ent.Owner);
}

private void SetActiveHand(string? handName)
{
if (handName == null)
{
_activeHand?.Highlight = false;
_activeHand = null;
return;
}

if (Hotbar?.MechHandContainer.TryGetButton(handName, out var handControl) != true || handControl == _activeHand)
return;

_activeHand?.Highlight = false;
handControl!.Highlight = true;
_activeHand = handControl;
}
}
Loading
Loading