fix(ui): complete live targeted healing flow
Route ACE server commands through the existing chat path, bind the retail paperdoll hit mask instead of its obscured viewport, and prefer authoritative private health vitals. Record the user-confirmed live gate and pin the production DAT widget type in tests.
This commit is contained in:
parent
eb6229394a
commit
05f6222865
11 changed files with 181 additions and 24 deletions
|
|
@ -15,6 +15,9 @@ namespace AcDream.App.UI.Layout;
|
|||
/// </summary>
|
||||
public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelController
|
||||
{
|
||||
public const uint DollViewportId = 0x100001D5u;
|
||||
public const uint DollDragMaskId = 0x100001D6u;
|
||||
|
||||
// ── Slots-toggle public surface ───────────────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// The 9 armor-slot element-ids whose Visible state the Slots button (0x100005BE) toggles.
|
||||
|
|
@ -84,6 +87,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
private readonly PaperdollViewState _viewState = new();
|
||||
private readonly List<UiItemList> _armorSlots = new();
|
||||
private UiElement? _dollViewport; // UiViewport wired in Slice 3; UiElement? keeps this task independent
|
||||
private UiElement? _dollDragMask;
|
||||
private bool _disposed;
|
||||
|
||||
private PaperdollController(
|
||||
|
|
@ -137,13 +141,30 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
foreach (var id in ArmorSlotElementIds)
|
||||
if (layout.FindElement(id) is UiItemList armor) _armorSlots.Add(armor);
|
||||
|
||||
_dollViewport = layout.FindElement(0x100001D5u); // doll viewport (may be null until Slice 3)
|
||||
_dollViewport = layout.FindElement(DollViewportId); // doll viewport (may be null until Slice 3)
|
||||
// The doll IS the player: clicking it during target-use self-targets
|
||||
// like the equip cells do. (The CURSOR over it stays the pending
|
||||
// four-arrows — retail resolves target cursors off the SmartBox world
|
||||
// object only; UI hover is always pending. 2026-07-03 visual gate.)
|
||||
Action offerSelf = () => { _itemInteraction?.OfferSelfPrimaryClick(); };
|
||||
if (_dollViewport is UiViewport doll)
|
||||
doll.Clicked = () => { _itemInteraction?.OfferSelfPrimaryClick(); };
|
||||
doll.Clicked = offerSelf;
|
||||
|
||||
// Retail's authored click/drag mask sits above the viewport and owns
|
||||
// doll hit-testing (gmPaperDollUI::ListenToElementMessage). Binding only
|
||||
// the viewport cannot win a live hit against this full-height overlay.
|
||||
switch (layout.FindElement(DollDragMaskId))
|
||||
{
|
||||
case UiButton dragMaskButton:
|
||||
_dollDragMask = dragMaskButton;
|
||||
dragMaskButton.OnClick = offerSelf;
|
||||
break;
|
||||
case UiDatElement dragMaskElement:
|
||||
_dollDragMask = dragMaskElement;
|
||||
dragMaskElement.ClickThrough = false;
|
||||
dragMaskElement.OnClick = offerSelf;
|
||||
break;
|
||||
}
|
||||
|
||||
var slotsBtnEl = layout.FindElement(0x100005BEu);
|
||||
if (slotsBtnEl is UiButton slotsBtn)
|
||||
|
|
@ -266,6 +287,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
private void ApplyView()
|
||||
{
|
||||
if (_dollViewport is not null) _dollViewport.Visible = _viewState.DollVisible;
|
||||
if (_dollDragMask is not null) _dollDragMask.Visible = _viewState.DollVisible;
|
||||
foreach (var a in _armorSlots) a.Visible = _viewState.ArmorSlotsVisible;
|
||||
}
|
||||
|
||||
|
|
@ -279,5 +301,16 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
|||
_objects.ObjectRemoved -= OnObjectChanged;
|
||||
_objects.ObjectUpdated -= OnObjectChanged;
|
||||
_selection.Changed -= OnSelectionChanged;
|
||||
if (_dollViewport is UiViewport doll)
|
||||
doll.Clicked = null;
|
||||
switch (_dollDragMask)
|
||||
{
|
||||
case UiButton button:
|
||||
button.OnClick = null;
|
||||
break;
|
||||
case UiDatElement element:
|
||||
element.OnClick = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,9 @@ public static class WeenieErrorMessages
|
|||
[0x0053] = "You fail to affect _ because you are not the same sort of player killer as them!",
|
||||
[0x0054] = "You fail to affect _ because you are acting across a house boundary!",
|
||||
|
||||
// Healing
|
||||
[0x04FF] = "_ is already at full health!", // _IsAtFullHealth
|
||||
|
||||
// Inventory / etiquette
|
||||
[0x001E] = "_ is too busy to accept gifts right now.",
|
||||
[0x002B] = "_ cannot carry anymore.",
|
||||
|
|
|
|||
|
|
@ -4,17 +4,18 @@ using AcDream.Core.Player;
|
|||
namespace AcDream.UI.Abstractions.Panels.Vitals;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for the vitals HUD panel. Reads live health percentage for the
|
||||
/// local player from <see cref="CombatState"/> (which is fed by the server's
|
||||
/// <c>UpdateHealth (0x01C0)</c> GameEvent).
|
||||
/// ViewModel for the vitals HUD panel. Reads the local player's authoritative
|
||||
/// private vital snapshot, falling back to world-combat health before that
|
||||
/// snapshot is available.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Sources:</b>
|
||||
/// </para>
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>HP — <see cref="CombatState.GetHealthPercent"/> (percent-only,
|
||||
/// updated by <c>UpdateHealth (0x01C0)</c>).</item>
|
||||
/// <item>HP — <see cref="LocalPlayerState.HealthPercent"/> from
|
||||
/// <c>PrivateUpdateVital(Current)</c>, with
|
||||
/// <see cref="CombatState.GetHealthPercent"/> as fallback.</item>
|
||||
/// <item>Stamina / Mana — <see cref="LocalPlayerState"/>'s
|
||||
/// <c>StaminaPercent</c> / <c>ManaPercent</c>, populated from the
|
||||
/// <c>CreatureProfile</c> embedded in <c>PlayerDescription
|
||||
|
|
@ -60,11 +61,12 @@ public sealed class VitalsVM
|
|||
public void SetLocalPlayerGuid(uint guid) => _localPlayerGuid = guid;
|
||||
|
||||
/// <summary>
|
||||
/// Current health percent (0..1) for the local player. Returns 1.0
|
||||
/// before login or if the server has never sent an UpdateHealth for
|
||||
/// this GUID.
|
||||
/// Current health percent (0..1) for the local player. The private vital
|
||||
/// is authoritative for self; world-combat percentage is the pre-login
|
||||
/// and legacy fallback.
|
||||
/// </summary>
|
||||
public float HealthPercent => _combat.GetHealthPercent(_localPlayerGuid);
|
||||
public float HealthPercent
|
||||
=> _local?.HealthPercent ?? _combat.GetHealthPercent(_localPlayerGuid);
|
||||
|
||||
/// <summary>
|
||||
/// Stamina percent (0..1), or <c>null</c> when no
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue