feat(ui): finish retail toolbar controls
Discover all seven panel launchers from their DAT panel-id attributes, route mounted panels through event-driven retained window state, and ghost unavailable panels. Port Use and Examine selection/target behavior with exact Appraise dispatch and retail cursor modes. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
21fefce0e0
commit
d3d1c895a0
19 changed files with 478 additions and 72 deletions
|
|
@ -1995,6 +1995,7 @@ public sealed class GameWindow : IDisposable
|
|||
Objects,
|
||||
playerGuid: () => _playerServerGuid,
|
||||
sendUse: g => _liveSession?.SendUse(g),
|
||||
sendExamine: g => _liveSession?.SendAppraise(g),
|
||||
sendUseWithTarget: (source, target) => _liveSession?.SendUseWithTarget(source, target),
|
||||
sendWield: (item, mask) => _liveSession?.SendGetAndWieldItem(item, mask),
|
||||
sendDrop: item => _liveSession?.SendDropItem(item),
|
||||
|
|
@ -11946,7 +11947,7 @@ public sealed class GameWindow : IDisposable
|
|||
break;
|
||||
|
||||
case AcDream.UI.Abstractions.Input.InputAction.EscapeKey:
|
||||
if (_itemInteractionController?.IsTargetModeActive == true)
|
||||
if (_itemInteractionController?.IsAnyTargetModeActive == true)
|
||||
_itemInteractionController.CancelTargetMode();
|
||||
else if (_cameraController?.IsFlyMode == true)
|
||||
_cameraController.ToggleFly(); // exit fly, release cursor
|
||||
|
|
@ -12092,7 +12093,7 @@ public sealed class GameWindow : IDisposable
|
|||
// Target-use mode picks WITH self (retail: kit-heal yourself by
|
||||
// clicking your own toon); plain selection keeps the self-exclusion.
|
||||
var picked = PickWorldGuidAtCursor(
|
||||
includeSelf: _itemInteractionController?.IsTargetModeActive == true);
|
||||
includeSelf: _itemInteractionController?.IsAnyTargetModeActive == true);
|
||||
|
||||
if (picked is uint guid)
|
||||
{
|
||||
|
|
@ -12133,7 +12134,7 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
else
|
||||
{
|
||||
if (_itemInteractionController?.IsTargetModeActive == true)
|
||||
if (_itemInteractionController?.IsAnyTargetModeActive == true)
|
||||
return;
|
||||
_debugVm?.AddToast("Nothing to select");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,9 +113,7 @@ public sealed class CursorFeedbackController
|
|||
// window occludes the world (no found object → pending). The one
|
||||
// UI-side source retail-style cells contribute is an occupied item
|
||||
// slot's own item.
|
||||
RetailCursorTargetMode targetMode = _itemInteraction?.IsTargetModeActive == true
|
||||
? RetailCursorTargetMode.UseTarget
|
||||
: RetailCursorTargetMode.None;
|
||||
RetailCursorTargetMode targetMode = ModeFromInteraction(_itemInteraction);
|
||||
uint hoverTarget = hover is null
|
||||
? _worldTargetProvider?.Invoke() ?? 0u
|
||||
: targetMode == RetailCursorTargetMode.UseTarget
|
||||
|
|
@ -265,9 +263,18 @@ public sealed class CursorFeedbackController
|
|||
|
||||
private RetailCursorTargetMode EffectiveTargetMode(CursorFeedbackSnapshot snapshot)
|
||||
=> snapshot.TargetMode == RetailCursorTargetMode.None
|
||||
&& _itemInteraction?.IsTargetModeActive == true
|
||||
? RetailCursorTargetMode.UseTarget
|
||||
: snapshot.TargetMode;
|
||||
? ModeFromInteraction(_itemInteraction)
|
||||
: snapshot.TargetMode;
|
||||
|
||||
private static RetailCursorTargetMode ModeFromInteraction(
|
||||
ItemInteractionController? interaction)
|
||||
=> interaction?.InteractionState.Current.Kind switch
|
||||
{
|
||||
InteractionModeKind.Use => RetailCursorTargetMode.Use,
|
||||
InteractionModeKind.Examine => RetailCursorTargetMode.Examine,
|
||||
InteractionModeKind.UseItemOnTarget => RetailCursorTargetMode.UseTarget,
|
||||
_ => RetailCursorTargetMode.None,
|
||||
};
|
||||
|
||||
private static CursorFeedbackKind KindForResize(ResizeEdges edges)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
private readonly Func<uint> _playerGuid;
|
||||
private readonly Func<long> _nowMs;
|
||||
private readonly Action<uint>? _sendUse;
|
||||
private readonly Action<uint>? _sendExamine;
|
||||
private readonly Action<uint, uint>? _sendUseWithTarget;
|
||||
private readonly Action<uint, uint>? _sendWield;
|
||||
private readonly Action<uint>? _sendDrop;
|
||||
|
|
@ -90,6 +91,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
Action<uint, uint>? sendUseWithTarget,
|
||||
Action<uint, uint>? sendWield,
|
||||
Action<uint>? sendDrop,
|
||||
Action<uint>? sendExamine = null,
|
||||
Func<long>? nowMs = null,
|
||||
Action<string>? toast = null,
|
||||
InteractionState? interactionState = null,
|
||||
|
|
@ -108,6 +110,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
|
||||
_playerGuid = playerGuid ?? throw new ArgumentNullException(nameof(playerGuid));
|
||||
_sendUse = sendUse;
|
||||
_sendExamine = sendExamine;
|
||||
_sendUseWithTarget = sendUseWithTarget;
|
||||
_sendWield = sendWield;
|
||||
_sendDrop = sendDrop;
|
||||
|
|
@ -152,6 +155,9 @@ public sealed class ItemInteractionController : IDisposable
|
|||
public bool IsTargetModeActive
|
||||
=> _interactionState.Current.Kind == InteractionModeKind.UseItemOnTarget;
|
||||
|
||||
public bool IsAnyTargetModeActive
|
||||
=> _interactionState.Current.Kind != InteractionModeKind.None;
|
||||
|
||||
public bool IsPendingSource(uint itemGuid)
|
||||
=> itemGuid != 0 && itemGuid == PendingSourceItem;
|
||||
|
||||
|
|
@ -182,7 +188,8 @@ public sealed class ItemInteractionController : IDisposable
|
|||
/// </summary>
|
||||
public ItemPrimaryClickResult OfferPrimaryClick(uint targetGuid)
|
||||
{
|
||||
if (!IsTargetModeActive)
|
||||
InteractionModeKind mode = _interactionState.Current.Kind;
|
||||
if (mode == InteractionModeKind.None)
|
||||
{
|
||||
long now = _nowMs();
|
||||
if (targetGuid != 0
|
||||
|
|
@ -193,7 +200,25 @@ public sealed class ItemInteractionController : IDisposable
|
|||
return ItemPrimaryClickResult.NotActive;
|
||||
}
|
||||
|
||||
bool accepted = AcquireTarget(targetGuid);
|
||||
bool accepted;
|
||||
switch (mode)
|
||||
{
|
||||
case InteractionModeKind.Use:
|
||||
ClearTargetMode();
|
||||
accepted = ActivateItem(targetGuid);
|
||||
break;
|
||||
case InteractionModeKind.Examine:
|
||||
ClearTargetMode();
|
||||
accepted = targetGuid != 0 && _sendExamine is not null;
|
||||
if (accepted)
|
||||
_sendExamine!(targetGuid);
|
||||
break;
|
||||
case InteractionModeKind.UseItemOnTarget:
|
||||
accepted = AcquireTarget(targetGuid);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown interaction mode {mode}.");
|
||||
}
|
||||
_consumedPrimaryClickTarget = targetGuid;
|
||||
_consumedPrimaryClickMs = _nowMs();
|
||||
return accepted
|
||||
|
|
@ -204,6 +229,33 @@ public sealed class ItemInteractionController : IDisposable
|
|||
public ItemPrimaryClickResult OfferSelfPrimaryClick()
|
||||
=> OfferPrimaryClick(_playerGuid());
|
||||
|
||||
/// <summary>
|
||||
/// Retail toolbar Use button: use the current selection immediately, or arm
|
||||
/// one-shot <c>TARGET_MODE_USE</c> when no object is selected.
|
||||
/// Retail reference: <c>gmToolbarUI::ListenToElementMessage @ 0x004BEE90</c>.
|
||||
/// </summary>
|
||||
public bool UseSelectedOrEnterMode(uint selectedObjectId)
|
||||
{
|
||||
if (selectedObjectId != 0)
|
||||
return ActivateItem(selectedObjectId);
|
||||
return _interactionState.EnterUse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail toolbar Examine button: appraise the current selection immediately,
|
||||
/// or arm one-shot <c>TARGET_MODE_EXAMINE</c> when no object is selected.
|
||||
/// Retail reference: <c>gmToolbarUI::ListenToElementMessage @ 0x004BEE90</c>.
|
||||
/// </summary>
|
||||
public bool ExamineSelectedOrEnterMode(uint selectedObjectId)
|
||||
{
|
||||
if (selectedObjectId == 0)
|
||||
return _interactionState.EnterExamine();
|
||||
if (_sendExamine is null)
|
||||
return false;
|
||||
_sendExamine(selectedObjectId);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ActivateItem(uint itemGuid)
|
||||
{
|
||||
if (itemGuid == 0) return false;
|
||||
|
|
@ -264,7 +316,7 @@ public sealed class ItemInteractionController : IDisposable
|
|||
|
||||
public void CancelTargetMode()
|
||||
{
|
||||
if (!IsTargetModeActive) return;
|
||||
if (!IsAnyTargetModeActive) return;
|
||||
ClearTargetMode();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,13 +45,19 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
private static readonly uint[] CombatIndicatorIds =
|
||||
{ 0x10000192u, 0x10000193u, 0x10000194u, 0x10000195u };
|
||||
|
||||
private const uint CharacterButtonId = 0x10000199u;
|
||||
private const uint PanelIdAttribute = 0x10000029u;
|
||||
private const uint UseButtonId = 0x1000019Du;
|
||||
private const uint ExamineButtonId = 0x100001A5u;
|
||||
private const uint InventoryButtonId = 0x100001B1u;
|
||||
private static readonly uint[] PanelButtonIds =
|
||||
{ 0x10000197u, 0x10000198u, 0x10000199u, 0x1000055Au, 0x1000019Au, 0x1000019Bu, InventoryButtonId };
|
||||
|
||||
private readonly UiItemList?[] _slots = new UiItemList?[SlotIds.Length];
|
||||
private readonly UiElement?[] _combatIndicators = new UiElement?[CombatIndicatorIds.Length];
|
||||
private readonly UiButton? _characterButton;
|
||||
private readonly UiButton? _inventoryButton;
|
||||
private readonly UiButton? _useButton;
|
||||
private readonly UiButton? _examineButton;
|
||||
private readonly List<(uint PanelId, UiButton Button)> _panelButtons = new();
|
||||
private readonly ClientObjectTable _repo;
|
||||
private readonly CombatState? _combatState;
|
||||
private readonly Func<IReadOnlyList<ShortcutEntry>> _shortcuts;
|
||||
|
|
@ -63,6 +69,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
private readonly Action<uint>? _sendRemoveShortcut; // (index)
|
||||
private readonly ItemInteractionController? _itemInteraction;
|
||||
private readonly Action<uint>? _selectItem;
|
||||
private readonly Func<uint> _selectedObjectId;
|
||||
private readonly Func<uint>? _playerGuid;
|
||||
private readonly Action<uint, uint, int>? _sendPutItemInContainer;
|
||||
private bool _disposed;
|
||||
|
|
@ -94,6 +101,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
Action<uint>? sendRemoveShortcut = null,
|
||||
Action? toggleCombat = null,
|
||||
Action<uint>? selectItem = null,
|
||||
Func<uint>? selectedObjectId = null,
|
||||
Func<uint>? playerGuid = null,
|
||||
Action<uint, uint, int>? sendPutItemInContainer = null)
|
||||
{
|
||||
|
|
@ -109,6 +117,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
_sendAddShortcut = sendAddShortcut;
|
||||
_sendRemoveShortcut = sendRemoveShortcut;
|
||||
_selectItem = selectItem;
|
||||
_selectedObjectId = selectedObjectId ?? (() => 0u);
|
||||
_playerGuid = playerGuid;
|
||||
_sendPutItemInContainer = sendPutItemInContainer;
|
||||
|
||||
|
|
@ -140,7 +149,13 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
button.OnClick = toggleCombat;
|
||||
}
|
||||
|
||||
_characterButton = layout.FindElement(CharacterButtonId) as UiButton;
|
||||
foreach (uint elementId in PanelButtonIds)
|
||||
{
|
||||
if (layout.FindElement(elementId) is UiButton panelButton
|
||||
&& panelButton.TryGetEnumAttribute(PanelIdAttribute, out uint panelId))
|
||||
_panelButtons.Add((panelId, panelButton));
|
||||
}
|
||||
|
||||
_inventoryButton = layout.FindElement(InventoryButtonId) as UiButton;
|
||||
if (_inventoryButton is not null)
|
||||
{
|
||||
|
|
@ -152,6 +167,13 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
_inventoryButton.OnItemDrop = HandleInventoryButtonDrop;
|
||||
}
|
||||
|
||||
_useButton = layout.FindElement(UseButtonId) as UiButton;
|
||||
_examineButton = layout.FindElement(ExamineButtonId) as UiButton;
|
||||
if (_useButton is not null)
|
||||
_useButton.OnClick = () => _itemInteraction?.UseSelectedOrEnterMode(_selectedObjectId());
|
||||
if (_examineButton is not null)
|
||||
_examineButton.OnClick = () => _itemInteraction?.ExamineSelectedOrEnterMode(_selectedObjectId());
|
||||
|
||||
// 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.
|
||||
|
|
@ -240,42 +262,54 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
Action<uint>? sendRemoveShortcut = null,
|
||||
Action? toggleCombat = null,
|
||||
Action<uint>? selectItem = null,
|
||||
Func<uint>? selectedObjectId = null,
|
||||
Func<uint>? playerGuid = null,
|
||||
Action<uint, uint, int>? sendPutItemInContainer = null)
|
||||
{
|
||||
var c = new ToolbarController(layout, repo, shortcuts, iconIds, useItem, combatState,
|
||||
peaceDigits, warDigits, emptyDigits, itemInteraction,
|
||||
sendAddShortcut, sendRemoveShortcut, toggleCombat, selectItem,
|
||||
playerGuid, sendPutItemInContainer);
|
||||
selectedObjectId, playerGuid, sendPutItemInContainer);
|
||||
c.Populate();
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>Wire the retail status-bar launch buttons to top-level window toggles.</summary>
|
||||
public void BindWindowToggles(Action? toggleInventory, Action? toggleCharacter)
|
||||
/// <summary>
|
||||
/// Binds all seven authored panel buttons by DAT panel id. Buttons whose panel
|
||||
/// is not registered are ghosted, matching retail unavailable-panel behavior.
|
||||
/// </summary>
|
||||
public void BindPanelButtons(Func<uint, bool> isAvailable, Action<uint> togglePanel)
|
||||
{
|
||||
if (_inventoryButton is not null)
|
||||
ArgumentNullException.ThrowIfNull(isAvailable);
|
||||
ArgumentNullException.ThrowIfNull(togglePanel);
|
||||
|
||||
foreach (var (panelId, button) in _panelButtons)
|
||||
{
|
||||
_inventoryButton.OnClick = () =>
|
||||
bool available = isAvailable(panelId);
|
||||
button.Enabled = available;
|
||||
button.OnClick = !available ? null : () =>
|
||||
{
|
||||
if (_itemInteraction?.AcquireSelfTarget() == true) return;
|
||||
toggleInventory?.Invoke();
|
||||
if (ReferenceEquals(button, _inventoryButton)
|
||||
&& _itemInteraction?.OfferSelfPrimaryClick()
|
||||
is not null and not ItemPrimaryClickResult.NotActive)
|
||||
return;
|
||||
togglePanel(panelId);
|
||||
};
|
||||
}
|
||||
if (_characterButton is not null)
|
||||
_characterButton.OnClick = toggleCharacter;
|
||||
}
|
||||
|
||||
public void SetInventoryOpen(bool open) => SetButtonOpen(_inventoryButton, open);
|
||||
|
||||
public void SetCharacterOpen(bool open) => SetButtonOpen(_characterButton, open);
|
||||
|
||||
private static void SetButtonOpen(UiButton? button, bool open)
|
||||
public void SetPanelOpen(uint panelId, bool open)
|
||||
{
|
||||
if (button is not null)
|
||||
button.TrySetRetailState(open
|
||||
// gmToolbarUI::RecvNotice_SetPanelVisibility @ 0x004BD300 maps visible
|
||||
// to state 6 (Highlight) and hidden to state 1 (Normal).
|
||||
foreach (var entry in _panelButtons)
|
||||
{
|
||||
if (entry.PanelId != panelId || !entry.Button.Enabled) continue;
|
||||
entry.Button.TrySetRetailState(open
|
||||
? UiButtonStateMachine.Highlight
|
||||
: UiButtonStateMachine.Normal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -584,6 +618,21 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
|||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
foreach (var (_, button) in _panelButtons)
|
||||
button.OnClick = null;
|
||||
foreach (var indicator in _combatIndicators)
|
||||
if (indicator is UiButton button)
|
||||
button.OnClick = null;
|
||||
if (_useButton is not null)
|
||||
_useButton.OnClick = null;
|
||||
if (_examineButton is not null)
|
||||
_examineButton.OnClick = null;
|
||||
if (_inventoryButton is not null)
|
||||
{
|
||||
_inventoryButton.OnItemDragOver = null;
|
||||
_inventoryButton.OnItemDrop = null;
|
||||
}
|
||||
|
||||
if (_combatState is not null)
|
||||
_combatState.CombatModeChanged -= SetCombatMode;
|
||||
_repo.ObjectAdded -= OnRepositoryObjectChanged;
|
||||
|
|
|
|||
48
src/AcDream.App/UI/RetailPanelCatalog.cs
Normal file
48
src/AcDream.App/UI/RetailPanelCatalog.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
namespace AcDream.App.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Retail toolbar panel ids carried by DAT property <c>0x10000029</c>.
|
||||
/// Retail reference: <c>gmToolbarUI::PostInit @ 0x004BEA80</c> discovers the
|
||||
/// seven buttons and reads this property from each one.
|
||||
/// Only panels mounted in the retained runtime are mapped; other authored
|
||||
/// toolbar buttons remain present but ghosted until their panel ships.
|
||||
/// </summary>
|
||||
public static class RetailPanelCatalog
|
||||
{
|
||||
public const uint Inventory = 7u;
|
||||
public const uint Character = 11u;
|
||||
|
||||
private static readonly (uint PanelId, string WindowName)[] Mounted =
|
||||
{
|
||||
(Inventory, WindowNames.Inventory),
|
||||
(Character, WindowNames.Character),
|
||||
};
|
||||
|
||||
public static IReadOnlyList<(uint PanelId, string WindowName)> MountedPanels => Mounted;
|
||||
|
||||
public static bool TryGetWindowName(uint panelId, out string windowName)
|
||||
{
|
||||
foreach (var entry in Mounted)
|
||||
{
|
||||
if (entry.PanelId != panelId) continue;
|
||||
windowName = entry.WindowName;
|
||||
return true;
|
||||
}
|
||||
|
||||
windowName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetPanelId(string windowName, out uint panelId)
|
||||
{
|
||||
foreach (var entry in Mounted)
|
||||
{
|
||||
if (!string.Equals(entry.WindowName, windowName, StringComparison.Ordinal)) continue;
|
||||
panelId = entry.PanelId;
|
||||
return true;
|
||||
}
|
||||
|
||||
panelId = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -128,6 +128,8 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
MountCharacter();
|
||||
MountPlugins();
|
||||
MountInventory();
|
||||
Host.WindowManager.WindowVisibilityChanged += OnWindowVisibilityChanged;
|
||||
BindToolbarPanelButtons();
|
||||
SyncToolbarWindowButtons();
|
||||
|
||||
if (bindings.Persistence is { } persistence)
|
||||
|
|
@ -197,22 +199,34 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
public void RestoreLayout() => _persistence?.RestoreAll();
|
||||
|
||||
public bool ToggleWindow(string name)
|
||||
{
|
||||
bool visible = Host.ToggleWindow(name);
|
||||
SyncToolbarWindowButtons();
|
||||
return visible;
|
||||
}
|
||||
=> Host.ToggleWindow(name);
|
||||
|
||||
public void CloseWindow(string name)
|
||||
{
|
||||
Host.HideWindow(name);
|
||||
SyncToolbarWindowButtons();
|
||||
}
|
||||
=> Host.HideWindow(name);
|
||||
|
||||
public void SyncToolbarWindowButtons()
|
||||
{
|
||||
ToolbarController?.SetInventoryOpen(Host.IsWindowVisible(WindowNames.Inventory));
|
||||
ToolbarController?.SetCharacterOpen(Host.IsWindowVisible(WindowNames.Character));
|
||||
if (ToolbarController is null) return;
|
||||
foreach (var (panelId, windowName) in RetailPanelCatalog.MountedPanels)
|
||||
ToolbarController.SetPanelOpen(panelId, Host.IsWindowVisible(windowName));
|
||||
}
|
||||
|
||||
private void BindToolbarPanelButtons()
|
||||
{
|
||||
ToolbarController?.BindPanelButtons(
|
||||
panelId => RetailPanelCatalog.TryGetWindowName(panelId, out string name)
|
||||
&& Host.WindowManager.TryGet(name, out _),
|
||||
panelId =>
|
||||
{
|
||||
if (RetailPanelCatalog.TryGetWindowName(panelId, out string name))
|
||||
ToggleWindow(name);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnWindowVisibilityChanged(string windowName, bool visible)
|
||||
{
|
||||
if (RetailPanelCatalog.TryGetPanelId(windowName, out uint panelId))
|
||||
ToolbarController?.SetPanelOpen(panelId, visible);
|
||||
}
|
||||
|
||||
private ImportedLayout? Import(uint layoutId)
|
||||
|
|
@ -374,6 +388,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
peace, war, empty, b.ItemInteraction, b.SendAddShortcut, b.SendRemoveShortcut,
|
||||
toggleCombat: b.ToggleCombat,
|
||||
selectItem: guid => b.Selection.Select(guid, SelectionChangeSource.Toolbar),
|
||||
selectedObjectId: () => b.Selection.SelectedObjectId ?? 0u,
|
||||
playerGuid: b.PlayerGuid,
|
||||
sendPutItemInContainer: b.SendPutItemInContainer);
|
||||
ToolbarInputController = new ToolbarInputController(ToolbarController, b.Selection);
|
||||
|
|
@ -420,9 +435,6 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
SelectedObjectController),
|
||||
});
|
||||
ConfigureToolbarCollapse(layout, (UiCollapsibleFrame)handle.OuterFrame, root.Height);
|
||||
ToolbarController.BindWindowToggles(
|
||||
() => ToggleWindow(WindowNames.Inventory),
|
||||
() => ToggleWindow(WindowNames.Character));
|
||||
Console.WriteLine("[D.5.1] retail toolbar window from LayoutDesc importer (0x21000016).");
|
||||
}
|
||||
|
||||
|
|
@ -635,6 +647,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_persistence?.Dispose();
|
||||
Host.WindowManager.WindowVisibilityChanged -= OnWindowVisibilityChanged;
|
||||
Host.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public sealed class RetailWindowManager : IDisposable
|
|||
|
||||
public bool IsLocked => _root.UiLocked;
|
||||
public IReadOnlyCollection<RetailWindowHandle> Windows => _byName.Values;
|
||||
public event Action<string, bool>? WindowVisibilityChanged;
|
||||
|
||||
public RetailWindowHandle Register(
|
||||
string name,
|
||||
|
|
@ -233,6 +234,7 @@ public sealed class RetailWindowManager : IDisposable
|
|||
}
|
||||
|
||||
handle.NotifyVisibility(visible);
|
||||
WindowVisibilityChanged?.Invoke(handle.Name, visible);
|
||||
}
|
||||
|
||||
private void OnKeyboardFocusChanged(UiElement? oldFocus, UiElement? newFocus)
|
||||
|
|
|
|||
|
|
@ -123,6 +123,20 @@ public sealed class UiButton : UiElement, IUiGlobalTimeListener, IUiDatStateful
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>Reads a resolved enum-valued DAT attribute from this button.</summary>
|
||||
public bool TryGetEnumAttribute(uint propertyId, out uint value)
|
||||
{
|
||||
if (_info.TryGetEffectiveProperty(propertyId, out var property)
|
||||
&& property.Kind == UiPropertyKind.Enum)
|
||||
{
|
||||
value = checked((uint)property.UnsignedValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ActiveCursorStateName => ActiveState;
|
||||
|
||||
public bool TrySetRetailState(uint stateId)
|
||||
|
|
|
|||
|
|
@ -1331,6 +1331,13 @@ public sealed class WorldSession : IDisposable
|
|||
SendGameAction(InteractRequests.BuildUseWithTarget(seq, sourceGuid, targetGuid));
|
||||
}
|
||||
|
||||
/// <summary>Send retail IdentifyObject/Appraise (0x00C8).</summary>
|
||||
public void SendAppraise(uint targetGuid)
|
||||
{
|
||||
uint seq = NextGameActionSequence();
|
||||
SendGameAction(AppraiseRequest.Build(seq, targetGuid));
|
||||
}
|
||||
|
||||
/// <summary>Send PutItemInContainer (0x0019) - move an item into a container at a slot. placement
|
||||
/// = the target slot (server packs/shifts); the drag-drop drop handler computes it. Retail:
|
||||
/// CM_Inventory::Event_PutItemInContainer -> ACE Player.HandleActionPutItemInContainer.</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue