fix(ui): assess retained items on right click
Port UIElement_ItemList's physical-item right-click branch through the shared retained list. Select and appraise backpack, loot, paperdoll, and shortcut items through their canonical owners, while preventing RMB movement from lifting items or issuing appraisal requests. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
5ad32d5753
commit
2dd5cb80d2
17 changed files with 264 additions and 5 deletions
|
|
@ -147,6 +147,10 @@ assessment. `SelectRight` is now a release-completed configurable click with
|
||||||
the shared three-pixel drag threshold, then pulses, selects, and appraises the
|
the shared three-pixel drag threshold, then pulses, selects, and appraises the
|
||||||
picked world object through the same owners as the magnifying glass and E key.
|
picked world object through the same owners as the magnifying glass and E key.
|
||||||
RMB camera drags and empty-world releases do not issue appraisal requests.
|
RMB camera drags and empty-world releases do not issue appraisal requests.
|
||||||
|
The separate retained ItemList path now does the same for occupied backpack,
|
||||||
|
side-bag, loot-container, paperdoll-slot, and physical shortcut cells; it
|
||||||
|
selects the item before entering the shared appraisal owner, and RMB movement
|
||||||
|
neither appraises nor lifts the item.
|
||||||
|
|
||||||
**Files:** `src/AcDream.Core.Net/Messages/AppraiseInfoParser.cs`;
|
**Files:** `src/AcDream.Core.Net/Messages/AppraiseInfoParser.cs`;
|
||||||
`src/AcDream.App/UI/RetailUiRuntime.cs`;
|
`src/AcDream.App/UI/RetailUiRuntime.cs`;
|
||||||
|
|
@ -170,6 +174,8 @@ the top and assessed melee/missile/armor/magic items show their retail-ordered
|
||||||
stats and full DAT spell descriptions.
|
stats and full DAT spell descriptions.
|
||||||
Right-clicking a visible world object opens the same examination window;
|
Right-clicking a visible world object opens the same examination window;
|
||||||
right-dragging the camera does not.
|
right-dragging the camera does not.
|
||||||
|
Right-clicking an occupied retained item cell selects and examines it without
|
||||||
|
using, equipping, looting, or dragging it.
|
||||||
The Black Phyntos Hive specifically reads `Value: ???`, `Burden: Unknown`,
|
The Black Phyntos Hive specifically reads `Value: ???`, `Burden: Unknown`,
|
||||||
then its description after one retail paragraph break, without the bogus
|
then its description after one retail paragraph break, without the bogus
|
||||||
255-item/255-container line.
|
255-item/255-container line.
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ presentation; its connected gate passed. The existing magnifying-glass Assess
|
||||||
command was already retail-faithful. World right-click assessment now follows
|
command was already retail-faithful. World right-click assessment now follows
|
||||||
the configurable `SelectRight` action's retail release-completed gesture,
|
the configurable `SelectRight` action's retail release-completed gesture,
|
||||||
including drag cancellation, then reuses the canonical
|
including drag cancellation, then reuses the canonical
|
||||||
picker/selection/appraisal path. Slice 3's first connected gate exposed
|
picker/selection/appraisal path. The parallel retained ItemList branch now
|
||||||
|
selects and appraises occupied inventory, external-container, paperdoll, and
|
||||||
|
physical-shortcut cells through the same request owner. Slice 3's first connected gate exposed
|
||||||
three deeper defects, now corrected: the response enum mislabeled ACE's
|
three deeper defects, now corrected: the response enum mislabeled ACE's
|
||||||
creature bit `0x0100` and dropped monster packets while retaining the busy
|
creature bit `0x0100` and dropped monster packets while retaining the busy
|
||||||
cursor; the imported 310 x 400 examination layout was incorrectly hosted as
|
cursor; the imported 310 x 400 examination layout was incorrectly hosted as
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,9 @@ passed. The pre-existing Assess command has been reconciled against retail.
|
||||||
The same slice now includes retail world right-click assessment through the
|
The same slice now includes retail world right-click assessment through the
|
||||||
configurable `SelectRight` complete-click gesture; RMB orbit drags cancel
|
configurable `SelectRight` complete-click gesture; RMB orbit drags cancel
|
||||||
instead of appraising the release point.
|
instead of appraising the release point.
|
||||||
|
Retail's separate retained ItemList right-click branch now covers physical
|
||||||
|
backpack, loot, paperdoll, and toolbar item cells through the same selection
|
||||||
|
and appraisal owner.
|
||||||
Slice 3's first connected gate found and corrected the real remaining defects:
|
Slice 3's first connected gate found and corrected the real remaining defects:
|
||||||
ACE creature flag `0x0100` was mislabeled as a weapon profile and left the
|
ACE creature flag `0x0100` was mislabeled as a weapon profile and left the
|
||||||
appraisal busy reference held, retail's examination UI had been mounted as a
|
appraisal busy reference held, retail's examination UI had been mounted as a
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,11 @@ routes through the existing world picker, lighting pulse, canonical
|
||||||
no-op, right-drag camera orbit does not appraise its release point, and the
|
no-op, right-drag camera orbit does not appraise its release point, and the
|
||||||
independent configurable `SelectionExamine` action now reaches the same
|
independent configurable `SelectionExamine` action now reaches the same
|
||||||
request/target-mode path.
|
request/target-mode path.
|
||||||
|
The retained follow-up ports the separate
|
||||||
|
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50` branch: an occupied
|
||||||
|
backpack, side-bag, loot, paperdoll-slot, or physical toolbar cell now selects
|
||||||
|
its item and enters that same appraisal owner on a completed right-click.
|
||||||
|
Right-button movement cancels the click and can never begin an item drag.
|
||||||
|
|
||||||
## Slice 1 — spell-bar overflow arrows
|
## Slice 1 — spell-bar overflow arrows
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,40 @@ camera drag does not appraise the object under the release point. Acdream maps
|
||||||
the configurable `SelectRight` action to the same complete-click gesture and
|
the configurable `SelectRight` action to the same complete-click gesture and
|
||||||
keeps `SelectionExamine` independently configurable (retail default E).
|
keeps `SelectionExamine` independently configurable (retail default E).
|
||||||
|
|
||||||
|
### Retained physical-item lists
|
||||||
|
|
||||||
|
`UIElement_ItemList::ListenToElementMessage @ 0x004E4D50`
|
||||||
|
|
||||||
|
```text
|
||||||
|
on ItemList right-click:
|
||||||
|
item = UIItem under the mouse
|
||||||
|
if item.itemID != 0:
|
||||||
|
if this list uses single selection:
|
||||||
|
update the list selection
|
||||||
|
SetSelectedObject(item.itemID)
|
||||||
|
ClientUISystem.ExamineObject(item.itemID)
|
||||||
|
else if item.spellID != 0:
|
||||||
|
ClientUISystem.ExamineSpell(item.spellID)
|
||||||
|
```
|
||||||
|
|
||||||
|
`gmPaperDollUI::ListenToElementMessage @ 0x004A5C30` repeats the physical-item
|
||||||
|
branch for a worn item resolved through the authored paperdoll click map:
|
||||||
|
|
||||||
|
```text
|
||||||
|
on paperdoll right-click:
|
||||||
|
itemID = GetPaperDollItemUnderMouse(mouse)
|
||||||
|
if itemID != 0:
|
||||||
|
SetSelectedObject(itemID)
|
||||||
|
ClientUISystem.ExamineObject(itemID)
|
||||||
|
```
|
||||||
|
|
||||||
|
Acdream's shared retained `UiItemList` owns the corresponding physical-item
|
||||||
|
notice. Backpack, side-bag, external-container, paperdoll-slot, and physical
|
||||||
|
toolbar cells provide their selection source and route the request through the
|
||||||
|
same `ItemInteractionController` appraisal owner used by world SmartBox and
|
||||||
|
the toolbar magnifying glass. RMB motion beyond the shared three-pixel click
|
||||||
|
threshold neither appraises nor begins an item drag.
|
||||||
|
|
||||||
## SmartBox click lighting pulse
|
## SmartBox click lighting pulse
|
||||||
|
|
||||||
`UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`
|
`UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound @ 0x004E5AD0`
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
ConfigureList(_topContainer, ContainerCellSize, horizontalScroll: false, containerEmptySprite);
|
ConfigureList(_topContainer, ContainerCellSize, horizontalScroll: false, containerEmptySprite);
|
||||||
ConfigureList(_containerList, ContainerCellSize, horizontalScroll: true, containerEmptySprite);
|
ConfigureList(_containerList, ContainerCellSize, horizontalScroll: true, containerEmptySprite);
|
||||||
ConfigureList(_contentsList, ItemCellSize, horizontalScroll: true, contentsEmptySprite);
|
ConfigureList(_contentsList, ItemCellSize, horizontalScroll: true, contentsEmptySprite);
|
||||||
|
_topContainer.ExamineItemRequested = ExamineItem;
|
||||||
|
_containerList.ExamineItemRequested = ExamineItem;
|
||||||
|
_contentsList.ExamineItemRequested = ExamineItem;
|
||||||
_contentsList.RegisterDragHandler(this);
|
_contentsList.RegisterDragHandler(this);
|
||||||
|
|
||||||
if (layout.FindElement(ContentsScrollbarId) is UiScrollbar scrollbar)
|
if (layout.FindElement(ContentsScrollbarId) is UiScrollbar scrollbar)
|
||||||
|
|
@ -375,6 +378,12 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
private void Select(uint guid)
|
private void Select(uint guid)
|
||||||
=> _selection.Select(guid, SelectionChangeSource.ExternalContainer);
|
=> _selection.Select(guid, SelectionChangeSource.ExternalContainer);
|
||||||
|
|
||||||
|
private void ExamineItem(uint guid)
|
||||||
|
{
|
||||||
|
Select(guid);
|
||||||
|
_itemInteraction.ExamineSelectedOrEnterMode(guid);
|
||||||
|
}
|
||||||
|
|
||||||
private void HandlePrimaryClick(uint guid, Action fallback)
|
private void HandlePrimaryClick(uint guid, Action fallback)
|
||||||
{
|
{
|
||||||
if (_itemInteraction.OfferPrimaryClick(guid) != ItemPrimaryClickResult.NotActive)
|
if (_itemInteraction.OfferPrimaryClick(guid) != ItemPrimaryClickResult.NotActive)
|
||||||
|
|
@ -564,5 +573,8 @@ public sealed class ExternalContainerController : IItemListDragHandler, IRetaine
|
||||||
_objects.Cleared -= OnObjectsCleared;
|
_objects.Cleared -= OnObjectsCleared;
|
||||||
_selection.Changed -= OnSelectionChanged;
|
_selection.Changed -= OnSelectionChanged;
|
||||||
_itemInteraction.StateChanged -= OnInteractionStateChanged;
|
_itemInteraction.StateChanged -= OnInteractionStateChanged;
|
||||||
|
_topContainer.ExamineItemRequested = null;
|
||||||
|
_containerList.ExamineItemRequested = null;
|
||||||
|
_contentsList.ExamineItemRequested = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,9 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
||||||
_contentsGrid?.RegisterDragHandler(this);
|
_contentsGrid?.RegisterDragHandler(this);
|
||||||
_containerList?.RegisterDragHandler(this);
|
_containerList?.RegisterDragHandler(this);
|
||||||
_topContainer?.RegisterDragHandler(this);
|
_topContainer?.RegisterDragHandler(this);
|
||||||
|
if (_contentsGrid is not null) _contentsGrid.ExamineItemRequested = ExamineItem;
|
||||||
|
if (_containerList is not null) _containerList.ExamineItemRequested = ExamineItem;
|
||||||
|
if (_topContainer is not null) _topContainer.ExamineItemRequested = ExamineItem;
|
||||||
|
|
||||||
// Burden meter: vertical 11×58 bar (gmBackpackUI m_burdenMeter, retail direction 4).
|
// Burden meter: vertical 11×58 bar (gmBackpackUI m_burdenMeter, retail direction 4).
|
||||||
_burdenMeter = layout.FindElement(BurdenMeterId) as UiMeter;
|
_burdenMeter = layout.FindElement(BurdenMeterId) as UiMeter;
|
||||||
|
|
@ -778,6 +781,17 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
||||||
_selection.Select(guid, SelectionChangeSource.Inventory);
|
_selection.Select(guid, SelectionChangeSource.Inventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retail ItemList right-click: select the physical item, then enter the
|
||||||
|
/// shared appraisal request owner. This is intentionally independent from
|
||||||
|
/// left-click target mode and double-click Use/AutoWield.
|
||||||
|
/// </summary>
|
||||||
|
private void ExamineItem(uint guid)
|
||||||
|
{
|
||||||
|
SelectItem(guid);
|
||||||
|
_itemInteraction?.ExamineSelectedOrEnterMode(guid);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Open a container (side bag or main pack): it becomes both the selected item and the
|
/// <summary>Open a container (side bag or main pack): it becomes both the selected item and the
|
||||||
/// open container. Sends Use to open a side bag server-side (ViewContents arrives async).
|
/// open container. Sends Use to open a side bag server-side (ViewContents arrives async).
|
||||||
/// Owned packs are not ClientUISystem::groundObject and never send
|
/// Owned packs are not ClientUISystem::groundObject and never send
|
||||||
|
|
@ -917,6 +931,9 @@ public sealed class InventoryController : IItemListDragHandler, IRetainedPanelCo
|
||||||
_objects.ObjectUpdated -= OnObjectChanged;
|
_objects.ObjectUpdated -= OnObjectChanged;
|
||||||
_objects.Cleared -= OnObjectsCleared;
|
_objects.Cleared -= OnObjectsCleared;
|
||||||
_selection.Changed -= OnSelectionChanged;
|
_selection.Changed -= OnSelectionChanged;
|
||||||
|
if (_contentsGrid is not null) _contentsGrid.ExamineItemRequested = null;
|
||||||
|
if (_containerList is not null) _containerList.ExamineItemRequested = null;
|
||||||
|
if (_topContainer is not null) _topContainer.ExamineItemRequested = null;
|
||||||
if (_itemInteraction is not null)
|
if (_itemInteraction is not null)
|
||||||
{
|
{
|
||||||
_itemInteraction.StateChanged -= OnInteractionStateChanged;
|
_itemInteraction.StateChanged -= OnInteractionStateChanged;
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
||||||
var (element, mask, _, unlockBit) = PaperdollSlotBackgrounds.Definitions[i];
|
var (element, mask, _, unlockBit) = PaperdollSlotBackgrounds.Definitions[i];
|
||||||
if (layout.FindElement(element) is not UiItemList list) continue;
|
if (layout.FindElement(element) is not UiItemList list) continue;
|
||||||
list.RegisterDragHandler(this);
|
list.RegisterDragHandler(this);
|
||||||
|
list.ExamineItemRequested = ExamineItem;
|
||||||
list.Cell.SourceKind = ItemDragSource.Equipment;
|
list.Cell.SourceKind = ItemDragSource.Equipment;
|
||||||
list.Cell.SlotIndex = i; // definition position = equipped drag-payload SourceSlot
|
list.Cell.SlotIndex = i; // definition position = equipped drag-payload SourceSlot
|
||||||
list.Cell.EmptySprite = emptySlotSprites is not null
|
list.Cell.EmptySprite = emptySlotSprites is not null
|
||||||
|
|
@ -350,6 +351,12 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
||||||
foreach (var a in _armorSlots) a.Visible = _viewState.ArmorSlotsVisible;
|
foreach (var a in _armorSlots) a.Visible = _viewState.ArmorSlotsVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExamineItem(uint itemId)
|
||||||
|
{
|
||||||
|
_selection.Select(itemId, SelectionChangeSource.Paperdoll);
|
||||||
|
_itemInteraction.ExamineSelectedOrEnterMode(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Detach event handlers (idempotent).</summary>
|
/// <summary>Detach event handlers (idempotent).</summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
@ -361,6 +368,8 @@ public sealed class PaperdollController : IItemListDragHandler, IRetainedPanelCo
|
||||||
_objects.ObjectUpdated -= OnObjectChanged;
|
_objects.ObjectUpdated -= OnObjectChanged;
|
||||||
_objects.Cleared -= OnObjectsCleared;
|
_objects.Cleared -= OnObjectsCleared;
|
||||||
_selection.Changed -= OnSelectionChanged;
|
_selection.Changed -= OnSelectionChanged;
|
||||||
|
foreach (var (_, list) in _slots)
|
||||||
|
list.ExamineItemRequested = null;
|
||||||
if (_dollViewport is UiViewport doll)
|
if (_dollViewport is UiViewport doll)
|
||||||
doll.ClickedAt = null;
|
doll.ClickedAt = null;
|
||||||
switch (_dollDragMask)
|
switch (_dollDragMask)
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
||||||
if (_slots[i] is { } list)
|
if (_slots[i] is { } list)
|
||||||
{
|
{
|
||||||
WireClick(list);
|
WireClick(list);
|
||||||
|
list.ExamineItemRequested = ExamineItem;
|
||||||
// B.1 drag-drop spine: this controller is the drop handler for every
|
// B.1 drag-drop spine: this controller is the drop handler for every
|
||||||
// toolbar slot list; each cell knows its slot index + that it's a
|
// toolbar slot list; each cell knows its slot index + that it's a
|
||||||
// shortcut-bar source (retail UIElement_ItemList::RegisterItemListDragHandler).
|
// shortcut-bar source (retail UIElement_ItemList::RegisterItemListDragHandler).
|
||||||
|
|
@ -531,6 +532,15 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExamineItem(uint itemId)
|
||||||
|
{
|
||||||
|
if (_selectItem is not null)
|
||||||
|
_selectItem(itemId);
|
||||||
|
else
|
||||||
|
_selection?.Select(itemId, SelectionChangeSource.Toolbar);
|
||||||
|
_itemInteraction?.ExamineSelectedOrEnterMode(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
private static ItemDragAcceptance InventoryButtonDragOver(ItemDragPayload payload)
|
private static ItemDragAcceptance InventoryButtonDragOver(ItemDragPayload payload)
|
||||||
=> payload.ObjId != 0 && payload.SourceKind != ItemDragSource.ShortcutBar
|
=> payload.ObjId != 0 && payload.SourceKind != ItemDragSource.ShortcutBar
|
||||||
? ItemDragAcceptance.Accept
|
? ItemDragAcceptance.Accept
|
||||||
|
|
@ -765,6 +775,9 @@ public sealed class ToolbarController : IItemListDragHandler, IRetainedPanelCont
|
||||||
_combatState.CombatModeChanged -= SetCombatMode;
|
_combatState.CombatModeChanged -= SetCombatMode;
|
||||||
if (_selection is not null)
|
if (_selection is not null)
|
||||||
_selection.Changed -= OnSelectionChanged;
|
_selection.Changed -= OnSelectionChanged;
|
||||||
|
foreach (UiItemList? list in _slots)
|
||||||
|
if (list is not null)
|
||||||
|
list.ExamineItemRequested = null;
|
||||||
_repo.ObjectAdded -= OnRepositoryObjectChanged;
|
_repo.ObjectAdded -= OnRepositoryObjectChanged;
|
||||||
_repo.ObjectUpdated -= OnRepositoryObjectChanged;
|
_repo.ObjectUpdated -= OnRepositoryObjectChanged;
|
||||||
_repo.ObjectRemoved -= OnRepositoryObjectChanged;
|
_repo.ObjectRemoved -= OnRepositoryObjectChanged;
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,15 @@ public sealed class UiItemList : UiElement
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<object, int, int>? CatalogDropped { get; set; }
|
public Action<object, int, int>? CatalogDropped { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appraisal command supplied by the owning panel controller for physical
|
||||||
|
/// item cells. Retail <c>UIElement_ItemList::ListenToElementMessage
|
||||||
|
/// @ 0x004E4D50</c> handles its right-click branch at <c>0x004E4ECA</c>:
|
||||||
|
/// select the occupied UIItem's itemID, then call
|
||||||
|
/// <c>ClientUISystem::ExamineObject</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Action<uint>? ExamineItemRequested { get; set; }
|
||||||
|
|
||||||
private uint _cellEmptySprite;
|
private uint _cellEmptySprite;
|
||||||
/// <summary>Empty-slot sprite for THIS list's cells, resolved from the dat cell template
|
/// <summary>Empty-slot sprite for THIS list's cells, resolved from the dat cell template
|
||||||
/// (retail attribute 0x1000000e -> catalog 0x21000037 prototype's ItemSlot_Empty; see
|
/// (retail attribute 0x1000000e -> catalog 0x21000037 prototype's ItemSlot_Empty; see
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,11 @@ public class UiItemSlot : UiElement
|
||||||
case UiEventType.DoubleClick:
|
case UiEventType.DoubleClick:
|
||||||
DoubleClicked?.Invoke();
|
DoubleClicked?.Invoke();
|
||||||
return true;
|
return true;
|
||||||
|
case UiEventType.RightClick:
|
||||||
|
if (ItemId != 0
|
||||||
|
&& FindList() is { ExamineItemRequested: { } examine })
|
||||||
|
examine(ItemId);
|
||||||
|
return true;
|
||||||
|
|
||||||
case UiEventType.DragBegin:
|
case UiEventType.DragBegin:
|
||||||
// Notify the source list's handler so it can lift (remove + wire) — retail
|
// Notify the source list's handler so it can lift (remove + wire) — retail
|
||||||
|
|
|
||||||
|
|
@ -496,7 +496,10 @@ public sealed class UiRoot : UiElement
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_dragCandidate = true;
|
// Retail item drag begins from left-button movement. A right-button
|
||||||
|
// press remains a complete-click candidate for ItemList appraisal;
|
||||||
|
// it must never lift or move an inventory item.
|
||||||
|
_dragCandidate = btn == UiMouseButton.Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch raw MouseDown event (retail uses WM_LBUTTONDOWN = 0x201).
|
// Dispatch raw MouseDown event (retail uses WM_LBUTTONDOWN = 0x201).
|
||||||
|
|
@ -594,7 +597,10 @@ public sealed class UiRoot : UiElement
|
||||||
_lastClickX = x;
|
_lastClickX = x;
|
||||||
_lastClickY = y;
|
_lastClickY = y;
|
||||||
}
|
}
|
||||||
else if (btn == UiMouseButton.Right && ContainsAbsolute(target, x, y))
|
else if (btn == UiMouseButton.Right
|
||||||
|
&& ContainsAbsolute(target, x, y)
|
||||||
|
&& Math.Abs(x - _pressX) <= DragDistanceThreshold
|
||||||
|
&& Math.Abs(y - _pressY) <= DragDistanceThreshold)
|
||||||
{
|
{
|
||||||
var click = new UiEvent(target.EventId, target, UiEventType.RightClick,
|
var click = new UiEvent(target.EventId, target, UiEventType.RightClick,
|
||||||
Data0: (int)flags);
|
Data0: (int)flags);
|
||||||
|
|
|
||||||
|
|
@ -275,6 +275,35 @@ public class DragDropSpineTests
|
||||||
Assert.True(used);
|
Assert.True(used);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RightClick_withoutDrag_requestsItemListAppraisal()
|
||||||
|
{
|
||||||
|
var (root, list, _) = RootWithBoundSlot(0x5001u);
|
||||||
|
var examined = new List<uint>();
|
||||||
|
list.ExamineItemRequested = examined.Add;
|
||||||
|
|
||||||
|
root.OnMouseDown(UiMouseButton.Right, 10, 10);
|
||||||
|
root.OnMouseUp(UiMouseButton.Right, 10, 10);
|
||||||
|
|
||||||
|
Assert.Equal(new uint[] { 0x5001u }, examined);
|
||||||
|
Assert.Null(root.DragSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RightButtonMovement_cancelsAppraisal_andNeverStartsItemDrag()
|
||||||
|
{
|
||||||
|
var (root, list, _) = RootWithBoundSlot(0x5001u);
|
||||||
|
var examined = new List<uint>();
|
||||||
|
list.ExamineItemRequested = examined.Add;
|
||||||
|
|
||||||
|
root.OnMouseDown(UiMouseButton.Right, 10, 10);
|
||||||
|
root.OnMouseMove(14, 10);
|
||||||
|
root.OnMouseUp(UiMouseButton.Right, 14, 10);
|
||||||
|
|
||||||
|
Assert.Empty(examined);
|
||||||
|
Assert.Null(root.DragSource);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CompletedDrag_doesNotFireUse()
|
public void CompletedDrag_doesNotFireUse()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ public sealed class ExternalContainerControllerTests
|
||||||
public readonly List<uint> Uses = new();
|
public readonly List<uint> Uses = new();
|
||||||
public readonly List<uint> NoLonger = new();
|
public readonly List<uint> NoLonger = new();
|
||||||
public readonly List<uint> Pickups = new();
|
public readonly List<uint> Pickups = new();
|
||||||
|
public readonly List<uint> Examines = new();
|
||||||
public readonly List<(uint Item, uint Container, int Placement)> Puts = new();
|
public readonly List<(uint Item, uint Container, int Placement)> Puts = new();
|
||||||
public readonly List<(uint Item, uint Container, uint Placement, uint Amount)> Splits = new();
|
public readonly List<(uint Item, uint Container, uint Placement, uint Amount)> Splits = new();
|
||||||
public readonly UiRoot Screen = new() { Width = 800f, Height = 600f };
|
public readonly UiRoot Screen = new() { Width = 800f, Height = 600f };
|
||||||
|
|
@ -110,6 +111,7 @@ public sealed class ExternalContainerControllerTests
|
||||||
sendUseWithTarget: null,
|
sendUseWithTarget: null,
|
||||||
sendWield: null,
|
sendWield: null,
|
||||||
sendDrop: null,
|
sendDrop: null,
|
||||||
|
sendExamine: Examines.Add,
|
||||||
groundObjectId: () => State.CurrentContainerId,
|
groundObjectId: () => State.CurrentContainerId,
|
||||||
placeInBackpack: (item, _, _) => Pickups.Add(item),
|
placeInBackpack: (item, _, _) => Pickups.Add(item),
|
||||||
sendPutItemInContainer: (item, container, placement) =>
|
sendPutItemInContainer: (item, container, placement) =>
|
||||||
|
|
@ -183,6 +185,20 @@ public sealed class ExternalContainerControllerTests
|
||||||
Assert.Equal(new uint[] { Item }, h.Pickups);
|
Assert.Equal(new uint[] { Item }, h.Pickups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RightClickLoot_selectsAndExaminesWithoutPickingUp()
|
||||||
|
{
|
||||||
|
using var h = new Harness();
|
||||||
|
h.Open(Chest, new ContainerContentEntry(Item, 0u));
|
||||||
|
|
||||||
|
UiItemSlot cell = h.Contents.GetItem(0)!;
|
||||||
|
cell.OnEvent(new UiEvent(0u, cell, UiEventType.RightClick));
|
||||||
|
|
||||||
|
Assert.Equal(Item, h.Selection.SelectedObjectId);
|
||||||
|
Assert.Equal(new uint[] { Item }, h.Examines);
|
||||||
|
Assert.Empty(h.Pickups);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CloseAndRangeExit_SendUseOnceWithoutNoLongerViewing()
|
public void CloseAndRangeExit_SendUseOnceWithoutNoLongerViewing()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,8 @@ public class InventoryControllerTests
|
||||||
string? ownerName = null,
|
string? ownerName = null,
|
||||||
Action? onClose = null,
|
Action? onClose = null,
|
||||||
SelectionState? selection = null,
|
SelectionState? selection = null,
|
||||||
StackSplitQuantityState? stackSplitQuantity = null)
|
StackSplitQuantityState? stackSplitQuantity = null,
|
||||||
|
ItemInteractionController? itemInteraction = null)
|
||||||
=> InventoryController.Bind(layout, objects, () => Player,
|
=> InventoryController.Bind(layout, objects, () => Player,
|
||||||
iconIds: (_, _, _, _, _) => 0u,
|
iconIds: (_, _, _, _, _) => 0u,
|
||||||
strength: () => strength, datFont: null,
|
strength: () => strength, datFont: null,
|
||||||
|
|
@ -76,7 +77,8 @@ public class InventoryControllerTests
|
||||||
notifyMergeAttempt: mergeNotices is null ? null : (s, t) => mergeNotices.Add((s, t)),
|
notifyMergeAttempt: mergeNotices is null ? null : (s, t) => mergeNotices.Add((s, t)),
|
||||||
onClose: onClose,
|
onClose: onClose,
|
||||||
selection: selection ?? new SelectionState(),
|
selection: selection ?? new SelectionState(),
|
||||||
stackSplitQuantity: stackSplitQuantity);
|
stackSplitQuantity: stackSplitQuantity,
|
||||||
|
itemInteraction: itemInteraction);
|
||||||
|
|
||||||
private static UiButton MakeButton(uint id)
|
private static UiButton MakeButton(uint id)
|
||||||
{
|
{
|
||||||
|
|
@ -494,6 +496,37 @@ public class InventoryControllerTests
|
||||||
Assert.True(grid.GetItem(1)!.Selected);
|
Assert.True(grid.GetItem(1)!.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RightClickGridItem_selectsAndUsesSharedAppraisalOwner()
|
||||||
|
{
|
||||||
|
var (layout, grid, _, _, _, _, _, _) = BuildLayout();
|
||||||
|
var objects = new ClientObjectTable();
|
||||||
|
SeedContained(objects, 0xAu, Player, slot: 0);
|
||||||
|
var selection = new SelectionState();
|
||||||
|
var appraisals = new List<uint>();
|
||||||
|
using var interaction = new ItemInteractionController(
|
||||||
|
objects,
|
||||||
|
playerGuid: () => Player,
|
||||||
|
sendUse: null,
|
||||||
|
sendUseWithTarget: null,
|
||||||
|
sendWield: null,
|
||||||
|
sendDrop: null,
|
||||||
|
sendExamine: appraisals.Add);
|
||||||
|
Bind(
|
||||||
|
layout,
|
||||||
|
objects,
|
||||||
|
selection: selection,
|
||||||
|
itemInteraction: interaction);
|
||||||
|
|
||||||
|
grid.GetItem(0)!.OnEvent(
|
||||||
|
new UiEvent(0u, grid.GetItem(0), UiEventType.RightClick));
|
||||||
|
|
||||||
|
Assert.Equal(0xAu, selection.SelectedObjectId);
|
||||||
|
Assert.True(grid.GetItem(0)!.Selected);
|
||||||
|
Assert.Equal(new uint[] { 0xAu }, appraisals);
|
||||||
|
Assert.Equal(1, interaction.BusyCount);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TargetMode_suppressesSelectedSquare_onPendingSource()
|
public void TargetMode_suppressesSelectedSquare_onPendingSource()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ public class PaperdollControllerTests
|
||||||
IReadOnlyDictionary<uint, uint>? emptySlotSprites = null,
|
IReadOnlyDictionary<uint, uint>? emptySlotSprites = null,
|
||||||
List<(uint item, uint container, int placement)>? puts = null,
|
List<(uint item, uint container, int placement)>? puts = null,
|
||||||
List<string>? systemMessages = null,
|
List<string>? systemMessages = null,
|
||||||
|
List<uint>? examines = null,
|
||||||
Action<ItemInteractionController>? configureInteraction = null)
|
Action<ItemInteractionController>? configureInteraction = null)
|
||||||
{
|
{
|
||||||
var itemInteraction = new ItemInteractionController(
|
var itemInteraction = new ItemInteractionController(
|
||||||
|
|
@ -56,6 +57,7 @@ public class PaperdollControllerTests
|
||||||
sendUseWithTarget: null,
|
sendUseWithTarget: null,
|
||||||
sendWield: wields is null ? null : (i, m) => wields.Add((i, m)),
|
sendWield: wields is null ? null : (i, m) => wields.Add((i, m)),
|
||||||
sendDrop: null,
|
sendDrop: null,
|
||||||
|
sendExamine: examines is null ? null : examines.Add,
|
||||||
sendPutItemInContainer: puts is null
|
sendPutItemInContainer: puts is null
|
||||||
? null
|
? null
|
||||||
: (item, container, placement) => puts.Add((item, container, placement)),
|
: (item, container, placement) => puts.Add((item, container, placement)),
|
||||||
|
|
@ -116,6 +118,28 @@ public class PaperdollControllerTests
|
||||||
Assert.Equal(0u, lists[ShieldSlot].Cell.ItemId); // shield slot empty
|
Assert.Equal(0u, lists[ShieldSlot].Cell.ItemId); // shield slot empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RightClickEquippedSlot_selectsAndExaminesItem()
|
||||||
|
{
|
||||||
|
var (layout, lists) = BuildLayout();
|
||||||
|
var objects = new ClientObjectTable();
|
||||||
|
SeedEquipped(objects, 0xA01u, EquipMask.HeadWear);
|
||||||
|
var selection = new SelectionState();
|
||||||
|
var appraisals = new List<uint>();
|
||||||
|
Bind(
|
||||||
|
layout,
|
||||||
|
objects,
|
||||||
|
selection: selection,
|
||||||
|
examines: appraisals);
|
||||||
|
|
||||||
|
UiItemSlot cell = lists[HeadSlot].Cell;
|
||||||
|
cell.OnEvent(new UiEvent(0u, cell, UiEventType.RightClick));
|
||||||
|
|
||||||
|
Assert.Equal(0xA01u, selection.SelectedObjectId);
|
||||||
|
Assert.True(cell.Selected);
|
||||||
|
Assert.Equal(new uint[] { 0xA01u }, appraisals);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SessionTableClearRemovesOldEquipmentAndLocksAetheriaSlots()
|
public void SessionTableClearRemovesOldEquipmentAndLocksAetheriaSlots()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -763,6 +763,42 @@ public class ToolbarControllerTests
|
||||||
Assert.Equal(itemId, selected);
|
Assert.Equal(itemId, selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RightClickPhysicalShortcut_selectsAndExaminesItem()
|
||||||
|
{
|
||||||
|
const uint player = 0x50000001u;
|
||||||
|
const uint itemId = 0x50001001u;
|
||||||
|
var (layout, slots, _) = FakeToolbar();
|
||||||
|
var repo = new ClientObjectTable();
|
||||||
|
repo.AddOrUpdate(new ClientObject { ObjectId = itemId, Type = ItemType.Misc });
|
||||||
|
var shortcuts = new[] { new ShortcutEntry(0, itemId, 0) };
|
||||||
|
var selection = new SelectionState();
|
||||||
|
var appraisals = new List<uint>();
|
||||||
|
using var interaction = new ItemInteractionController(
|
||||||
|
repo,
|
||||||
|
playerGuid: () => player,
|
||||||
|
sendUse: null,
|
||||||
|
sendUseWithTarget: null,
|
||||||
|
sendWield: null,
|
||||||
|
sendDrop: null,
|
||||||
|
sendExamine: appraisals.Add);
|
||||||
|
using var controller = ToolbarController.Bind(
|
||||||
|
layout,
|
||||||
|
repo,
|
||||||
|
() => shortcuts,
|
||||||
|
iconIds: (_, _, _, _, _) => 1u,
|
||||||
|
useItem: _ => { },
|
||||||
|
itemInteraction: interaction,
|
||||||
|
selectItem: id => selection.Select(id, SelectionChangeSource.Toolbar),
|
||||||
|
selection: selection);
|
||||||
|
|
||||||
|
UiItemSlot cell = slots[Row1[0]].Cell;
|
||||||
|
cell.OnEvent(new UiEvent(0u, cell, UiEventType.RightClick));
|
||||||
|
|
||||||
|
Assert.Equal(itemId, selection.SelectedObjectId);
|
||||||
|
Assert.Equal(new uint[] { itemId }, appraisals);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void UseShortcut_targetModePrecedesUseAndClearsOneShotMode()
|
public void UseShortcut_targetModePrecedesUseAndClearsOneShotMode()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue