fix(D.5.1): toolbar movable + chrome-grab + peace-only indicator + no prototype square
D1 — Toolbar not movable: toolbarRoot.Anchors = AnchorEdges.None (was Left|Top) so ApplyAnchor early-returns and doesn't re-pin the window every frame. Matches the vitalsRoot idiom exactly. D2 — Cannot grab toolbar by chrome: toolbarRoot.ClickThrough = false so HitTest succeeds over the UiDatElement chrome and the drag starts. UiDatElement ctor defaults ClickThrough=true; vitalsRoot already overrides it. C1 — All four combat-mode indicators visible at once (war/flame stacked on peace): ports gmToolbarUI::RecvNotice_SetCombatMode (acclient_2013_pseudo_c.txt:196632-196669). CombatIndicatorIds[] maps index 0-3 to NonCombat/Melee/Missile/Magic; SetCombatMode shows exactly one and hides the other three. Default to NonCombat at bind (player always spawns in peace). Wires CombatState.CombatModeChanged for live updates. Tests: CombatIndicator_defaultNonCombat_onlyPeaceVisible, CombatIndicator_setCombatModeMelee_onlyMeleeVisible, CombatIndicator_liveSignal_updatesWhenCombatStateChanges. V1 — Blue empty-slot square at top-left (prototype 0x100001B2 materialized): ImportInfos now skips top-level elements that are (a) referenced as a BaseElement by another element in the same layout AND (b) have no own state media. The CollectBaseRefsInDesc walk covers nested children; HasNoOwnMedia re-uses ToInfo's media extraction. The Resolve path reads BaseElement from the raw dat via dats.Get<LayoutDesc> — it never depends on the prototype being in the built widget tree — so the skip is safe. Conformance tests (vitals, chat) are unaffected (they exercise Build, not ImportInfos). Test: BuildFromInfos_PrototypeSkipped_DerivedPresent_PrototypeAbsent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b3e5e8b0f7
commit
bfc452d610
5 changed files with 262 additions and 11 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
|
||||
|
|
@ -39,7 +40,15 @@ public sealed class ToolbarController
|
|||
// Ids confirmed from the toolbar LayoutDesc dump.
|
||||
private static readonly uint[] HiddenIds = { 0x100001A1, 0x100001A2, 0x100001A4 };
|
||||
|
||||
// Four mutually-exclusive combat-mode indicator elements — exactly one visible at a time.
|
||||
// Index 0 = NonCombat (peace), 1 = Melee, 2 = Missile, 3 = Magic.
|
||||
// Retail ref: gmToolbarUI::RecvNotice_SetCombatMode (acclient_2013_pseudo_c.txt:196632-196669)
|
||||
// SetVisible's exactly one element depending on the incoming mode.
|
||||
private static readonly uint[] CombatIndicatorIds =
|
||||
{ 0x10000192u, 0x10000193u, 0x10000194u, 0x10000195u };
|
||||
|
||||
private readonly UiItemList?[] _slots = new UiItemList?[SlotIds.Length];
|
||||
private readonly UiElement?[] _combatIndicators = new UiElement?[CombatIndicatorIds.Length];
|
||||
private readonly ItemRepository _repo;
|
||||
private readonly Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> _shortcuts;
|
||||
private readonly Func<uint, uint, uint, uint> _iconIds; // (iconId, underlayId, overlayId) → GL tex
|
||||
|
|
@ -50,7 +59,8 @@ public sealed class ToolbarController
|
|||
ItemRepository repo,
|
||||
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> shortcuts,
|
||||
Func<uint, uint, uint, uint> iconIds,
|
||||
Action<uint> useItem)
|
||||
Action<uint> useItem,
|
||||
CombatState? combatState)
|
||||
{
|
||||
_repo = repo;
|
||||
_shortcuts = shortcuts;
|
||||
|
|
@ -64,10 +74,23 @@ public sealed class ToolbarController
|
|||
WireClick(list);
|
||||
}
|
||||
|
||||
// Cache the four mutually-exclusive combat-mode indicator elements.
|
||||
for (int i = 0; i < CombatIndicatorIds.Length; i++)
|
||||
_combatIndicators[i] = layout.FindElement(CombatIndicatorIds[i]);
|
||||
|
||||
// Hide target-object meters + stack slider (gmToolbarUI::PostInit).
|
||||
foreach (var id in HiddenIds)
|
||||
if (layout.FindElement(id) is { } e) e.Visible = false;
|
||||
|
||||
// Port of gmToolbarUI::RecvNotice_SetCombatMode (acclient_2013_pseudo_c.txt:196632-196669):
|
||||
// exactly one indicator visible at a time. Default to NonCombat (peace) — the player
|
||||
// always spawns in peace mode; retail has not yet called SetVisible when PostInit runs.
|
||||
SetCombatMode(CombatMode.NonCombat);
|
||||
|
||||
// Wire live combat-mode changes if a CombatState was provided.
|
||||
if (combatState is not null)
|
||||
combatState.CombatModeChanged += SetCombatMode;
|
||||
|
||||
// Re-bind any deferred slot whenever the repo learns about a new/updated item.
|
||||
repo.ItemAdded += _ => Populate();
|
||||
repo.ItemPropertiesUpdated += _ => Populate();
|
||||
|
|
@ -84,14 +107,21 @@ public sealed class ToolbarController
|
|||
/// <param name="shortcuts">Provider for the current shortcut bar list.</param>
|
||||
/// <param name="iconIds">Resolves (iconId, underlayId, overlayId) → GL texture handle.</param>
|
||||
/// <param name="useItem">Callback fired when a bound slot is clicked; receives the item guid.</param>
|
||||
/// <param name="combatState">
|
||||
/// Optional live combat state — when provided, the toolbar subscribes to
|
||||
/// <see cref="CombatState.CombatModeChanged"/> and updates the four mutually-exclusive
|
||||
/// combat-mode indicator elements accordingly.
|
||||
/// Pass null to skip live wiring (e.g. in unit tests that don't exercise the indicator).
|
||||
/// </param>
|
||||
public static ToolbarController Bind(
|
||||
ImportedLayout layout,
|
||||
ItemRepository repo,
|
||||
Func<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>> shortcuts,
|
||||
Func<uint, uint, uint, uint> iconIds,
|
||||
Action<uint> useItem)
|
||||
Action<uint> useItem,
|
||||
CombatState? combatState = null)
|
||||
{
|
||||
var c = new ToolbarController(layout, repo, shortcuts, iconIds, useItem);
|
||||
var c = new ToolbarController(layout, repo, shortcuts, iconIds, useItem, combatState);
|
||||
c.Populate();
|
||||
return c;
|
||||
}
|
||||
|
|
@ -123,6 +153,33 @@ public sealed class ToolbarController
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port of <c>gmToolbarUI::RecvNotice_SetCombatMode</c>
|
||||
/// (acclient_2013_pseudo_c.txt:196632-196669): show exactly one of the four
|
||||
/// mutually-exclusive combat-mode indicator elements and hide the other three.
|
||||
/// Called at bind-time with <see cref="CombatMode.NonCombat"/> (the player
|
||||
/// always starts in peace mode) and subsequently whenever
|
||||
/// <see cref="CombatState.CombatModeChanged"/> fires.
|
||||
/// </summary>
|
||||
public void SetCombatMode(CombatMode mode)
|
||||
{
|
||||
// Index → mode mapping matches CombatIndicatorIds declaration order:
|
||||
// 0 = NonCombat (peace), 1 = Melee, 2 = Missile, 3 = Magic.
|
||||
bool[] show =
|
||||
{
|
||||
mode == CombatMode.NonCombat,
|
||||
mode == CombatMode.Melee,
|
||||
mode == CombatMode.Missile,
|
||||
mode == CombatMode.Magic,
|
||||
};
|
||||
|
||||
for (int i = 0; i < _combatIndicators.Length; i++)
|
||||
{
|
||||
if (_combatIndicators[i] is { } e)
|
||||
e.Visible = show[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire the <see cref="UiItemSlot.Clicked"/> callback on a slot cell so that
|
||||
/// clicking a bound item fires <see cref="_useItem"/> with the slot's current guid.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue