feat(ui): drive toolbar use state from selection
Port the retail selected-object availability predicate into Core and project it through the shared interaction owner. The imported hand now follows canonical selection/object notices, keeps weapon and targeted-tool activation on the existing wield/use cursor paths, and ghosts empty or explicitly unusable selections per the connected UX requirement. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
0134122c28
commit
60387668d0
11 changed files with 269 additions and 16 deletions
|
|
@ -5,6 +5,7 @@ using AcDream.App.UI.Layout;
|
|||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Selection;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
|
@ -515,6 +516,123 @@ public class ToolbarControllerTests
|
|||
Assert.Equal(InteractionModeKind.Examine, interaction.InteractionState.Current.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseButton_tracksEmptyUnusableAndTargetedSelection()
|
||||
{
|
||||
const uint player = 0x50000001u;
|
||||
const uint pack = 0x50000002u;
|
||||
const uint component = 0x50000003u;
|
||||
const uint healthKit = 0x50000004u;
|
||||
var (layout, _, _) = FakeToolbar();
|
||||
var repo = new ClientObjectTable();
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = player, Type = ItemType.Creature });
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = pack, Type = ItemType.Container });
|
||||
repo.MoveItem(pack, player, 0);
|
||||
repo.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = component,
|
||||
Type = ItemType.SpellComponents,
|
||||
Useability = ItemUseability.No,
|
||||
});
|
||||
repo.MoveItem(component, pack, 0);
|
||||
repo.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = healthKit,
|
||||
Type = ItemType.Misc,
|
||||
Useability = 0x00220008u,
|
||||
TargetType = (uint)ItemType.Creature,
|
||||
});
|
||||
repo.MoveItem(healthKit, pack, 1);
|
||||
|
||||
var selection = new SelectionState();
|
||||
using var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
() => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
sendWield: null,
|
||||
sendDrop: null);
|
||||
using var controller = ToolbarController.Bind(
|
||||
layout,
|
||||
repo,
|
||||
() => Array.Empty<ShortcutEntry>(),
|
||||
iconIds: (_, _, _, _, _) => 0u,
|
||||
useItem: _ => { },
|
||||
itemInteraction: interaction,
|
||||
selectedObjectId: () => selection.SelectedObjectId ?? 0u,
|
||||
selection: selection);
|
||||
var use = Assert.IsType<UiButton>(layout.FindElement(UseButtonId));
|
||||
|
||||
Assert.False(use.Enabled);
|
||||
Assert.Equal("Ghosted", use.ActiveState);
|
||||
use.OnEvent(new UiEvent(0u, use, UiEventType.Click));
|
||||
Assert.Equal(InteractionModeKind.None, interaction.InteractionState.Current.Kind);
|
||||
|
||||
selection.Select(component, SelectionChangeSource.Inventory);
|
||||
Assert.False(use.Enabled);
|
||||
Assert.Equal("Ghosted", use.ActiveState);
|
||||
|
||||
selection.Select(healthKit, SelectionChangeSource.Inventory);
|
||||
Assert.True(use.Enabled);
|
||||
Assert.Equal("Normal", use.ActiveState);
|
||||
use.OnEvent(new UiEvent(0u, use, UiEventType.Click));
|
||||
Assert.Equal(
|
||||
InteractionModeKind.UseItemOnTarget,
|
||||
interaction.InteractionState.Current.Kind);
|
||||
|
||||
selection.Clear(SelectionChangeSource.Inventory);
|
||||
Assert.False(use.Enabled);
|
||||
Assert.Equal("Ghosted", use.ActiveState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseButton_selectedCombatItem_routesExistingWieldPath()
|
||||
{
|
||||
const uint player = 0x50000001u;
|
||||
const uint pack = 0x50000002u;
|
||||
const uint sword = 0x50000003u;
|
||||
var (layout, _, _) = FakeToolbar();
|
||||
var repo = new ClientObjectTable();
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = player, Type = ItemType.Creature });
|
||||
repo.AddOrUpdate(new ClientObject { ObjectId = pack, Type = ItemType.Container });
|
||||
repo.MoveItem(pack, player, 0);
|
||||
repo.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = sword,
|
||||
Type = ItemType.MeleeWeapon,
|
||||
CombatUse = 1,
|
||||
Useability = ItemUseability.No,
|
||||
ValidLocations = EquipMask.MeleeWeapon,
|
||||
});
|
||||
repo.MoveItem(sword, pack, 0);
|
||||
|
||||
var selection = new SelectionState();
|
||||
selection.Select(sword, SelectionChangeSource.Inventory);
|
||||
var wields = new List<(uint Item, uint Location)>();
|
||||
using var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
() => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
sendWield: (item, location) => wields.Add((item, location)),
|
||||
sendDrop: null);
|
||||
using var controller = ToolbarController.Bind(
|
||||
layout,
|
||||
repo,
|
||||
() => Array.Empty<ShortcutEntry>(),
|
||||
iconIds: (_, _, _, _, _) => 0u,
|
||||
useItem: _ => { },
|
||||
itemInteraction: interaction,
|
||||
selectedObjectId: () => selection.SelectedObjectId ?? 0u,
|
||||
selection: selection);
|
||||
var use = Assert.IsType<UiButton>(layout.FindElement(UseButtonId));
|
||||
|
||||
Assert.True(use.Enabled);
|
||||
use.OnEvent(new UiEvent(0u, use, UiEventType.Click));
|
||||
|
||||
Assert.Equal(new[] { (sword, (uint)EquipMask.MeleeWeapon) }, wields);
|
||||
}
|
||||
|
||||
// ── C1: combat-mode indicator tests ─────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,28 @@ public sealed class ItemInteractionPolicyTests
|
|||
private const uint Player = 0x50000001u;
|
||||
private const uint GroundContainer = 0x70000001u;
|
||||
|
||||
[Fact]
|
||||
public void ToolbarUseEnabled_matchesRetailSelectionPredicate()
|
||||
{
|
||||
Assert.True(ItemInteractionPolicy.IsToolbarUseEnabled(
|
||||
ItemType.MeleeWeapon,
|
||||
combatUse: 1,
|
||||
useability: ItemUseability.No));
|
||||
Assert.True(ItemInteractionPolicy.IsToolbarUseEnabled(
|
||||
ItemType.Armor | ItemType.Clothing,
|
||||
combatUse: 0,
|
||||
useability: ItemUseability.No));
|
||||
Assert.True(ItemInteractionPolicy.IsToolbarUseEnabled(
|
||||
ItemType.Misc,
|
||||
combatUse: 0,
|
||||
useability: 0x00220008u));
|
||||
|
||||
Assert.False(ItemInteractionPolicy.IsToolbarUseEnabled(
|
||||
ItemType.SpellComponents,
|
||||
combatUse: 0,
|
||||
useability: ItemUseability.No));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineUseResult_looseItemAndComponentPack_matchRetailPickupBranch()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue