fix(ui): select and examine favorite spells like retail
This commit is contained in:
parent
043ab10b3c
commit
3e31b0ac70
23 changed files with 974 additions and 34 deletions
|
|
@ -165,6 +165,39 @@ public sealed class SpellComponentRequirementServiceTests
|
|||
Assert.False(service.HasRequiredComponents(50u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExaminationFormulaPreservesOrderAndReportsNestedOwnership()
|
||||
{
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 2u,
|
||||
ContainerId = 1u,
|
||||
Type = ItemType.Container,
|
||||
});
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = 3u,
|
||||
WeenieClassId = 101u,
|
||||
ContainerId = 2u,
|
||||
});
|
||||
var service = Service(
|
||||
objects,
|
||||
[10u, 11u, 10u],
|
||||
new Dictionary<uint, uint>
|
||||
{
|
||||
[10u] = 101u,
|
||||
[11u] = 102u,
|
||||
});
|
||||
|
||||
Assert.Equal(
|
||||
new uint[] { 10u, 11u, 10u },
|
||||
service.GetAppropriateFormula(50u));
|
||||
Assert.True(service.IsComponentOwned(10u));
|
||||
Assert.False(service.IsComponentOwned(11u));
|
||||
}
|
||||
|
||||
private static SpellComponentRequirementService Service(
|
||||
ClientObjectTable objects,
|
||||
IReadOnlyList<uint> components,
|
||||
|
|
|
|||
|
|
@ -245,6 +245,30 @@ public class DragDropSpineTests
|
|||
return (root, list, list.Cell);
|
||||
}
|
||||
|
||||
private static (UiRoot root, UiItemList list, UiCatalogSlot cell)
|
||||
RootWithCatalogSlot(uint entryId)
|
||||
{
|
||||
var root = new UiRoot { Width = 800, Height = 600 };
|
||||
var list = new UiItemList(_ => (1u, 1, 1))
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Width = 32,
|
||||
Height = 32,
|
||||
};
|
||||
list.Flush();
|
||||
var cell = new UiCatalogSlot
|
||||
{
|
||||
EntryId = entryId,
|
||||
Width = 32,
|
||||
Height = 32,
|
||||
SpriteResolve = _ => (1u, 1, 1),
|
||||
};
|
||||
list.AddItem(cell);
|
||||
root.AddChild(list);
|
||||
return (root, list, cell);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BeginDrag_arms_whenPayloadNonNull()
|
||||
{
|
||||
|
|
@ -359,6 +383,32 @@ public class DragDropSpineTests
|
|||
Assert.Null(root.DragSource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CatalogEntryPress_selectsBeforeRelease_withoutForgingItemIdentity()
|
||||
{
|
||||
var (root, list, cell) = RootWithCatalogSlot(42u);
|
||||
var selected = new List<uint>();
|
||||
list.PrimaryCatalogEntryPressed = selected.Add;
|
||||
|
||||
root.OnMouseDown(UiMouseButton.Left, 10, 10);
|
||||
|
||||
Assert.Equal(new uint[] { 42u }, selected);
|
||||
Assert.Equal(0u, cell.ItemId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CatalogEntryRightClick_requestsLocalCatalogExamination()
|
||||
{
|
||||
var (root, list, _) = RootWithCatalogSlot(42u);
|
||||
var examined = new List<uint>();
|
||||
list.ExamineCatalogEntryRequested = examined.Add;
|
||||
|
||||
root.OnMouseDown(UiMouseButton.Right, 10, 10);
|
||||
root.OnMouseUp(UiMouseButton.Right, 10, 10);
|
||||
|
||||
Assert.Equal(new uint[] { 42u }, examined);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompletedDrag_doesNotFireUse()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Spells;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Combat;
|
||||
|
|
@ -688,6 +689,103 @@ public sealed class AppraisalUiControllerTests
|
|||
mounted => mounted.WindowName == WindowNames.Examination);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpellExamination_UsesAuthoredLocalSubviewWithoutSelectingOrAppraisingSpellId()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadExamination();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = ObjectId,
|
||||
Name = "Selected Drudge",
|
||||
Type = ItemType.Creature,
|
||||
});
|
||||
var sent = new List<uint>();
|
||||
using var interaction = NewInteraction(objects, sent);
|
||||
var selection = new SelectionState();
|
||||
selection.Select(ObjectId, SelectionChangeSource.World);
|
||||
SpellMetadata metadata = ExaminedSpell();
|
||||
var spellbook = new Spellbook(SpellTable.Create([metadata]));
|
||||
var components = new[]
|
||||
{
|
||||
new SpellExamineComponent(
|
||||
10u,
|
||||
new SpellComponentDescriptor(100u, "Lead Scarab", 0u, 0x06000010u),
|
||||
Owned: true),
|
||||
new SpellExamineComponent(
|
||||
11u,
|
||||
new SpellComponentDescriptor(101u, "Malar Herb", 1u, 0x06000011u),
|
||||
Owned: false),
|
||||
};
|
||||
int shown = 0;
|
||||
using AppraisalUiController controller = Bind(
|
||||
layout,
|
||||
objects,
|
||||
interaction,
|
||||
new CombatState(),
|
||||
[],
|
||||
[],
|
||||
() => shown++,
|
||||
() => { },
|
||||
selection: selection,
|
||||
spellbook: spellbook,
|
||||
resolveSpellIcon: id => id + 1_000u,
|
||||
spellComponents: _ => components,
|
||||
magicSkill: _ => 200u)!;
|
||||
|
||||
Assert.True(interaction.ExamineSelectedOrEnterMode(ObjectId));
|
||||
Assert.Equal(1, interaction.BusyCount);
|
||||
|
||||
Assert.True(controller.ExamineSpell(metadata.SpellId));
|
||||
|
||||
Assert.Equal(AppraisalView.Spell, controller.ActiveView);
|
||||
Assert.Equal(1, shown);
|
||||
Assert.Equal(ObjectId, selection.SelectedObjectId);
|
||||
Assert.Equal(new uint[] { ObjectId, 0u }, sent);
|
||||
Assert.Equal(0, interaction.BusyCount);
|
||||
Assert.Equal(0u, controller.CurrentObjectId);
|
||||
Assert.True(layout.FindElement(AppraisalUiController.SpellPanelId)!.Visible);
|
||||
Assert.False(layout.FindElement(AppraisalUiController.ItemPanelId)!.Visible);
|
||||
Assert.False(layout.FindElement(AppraisalUiController.CreaturePanelId)!.Visible);
|
||||
|
||||
AssertSpellText(
|
||||
layout,
|
||||
AppraisalUiController.TitleId,
|
||||
"Incantation of Test");
|
||||
AssertSpellText(
|
||||
layout,
|
||||
AppraisalUiController.SpellSchoolTextId,
|
||||
"School: War Magic");
|
||||
AssertSpellText(
|
||||
layout,
|
||||
AppraisalUiController.SpellManaTextId,
|
||||
"Mana: 50 + 14 per target");
|
||||
AssertSpellText(
|
||||
layout,
|
||||
AppraisalUiController.SpellDurationTextId,
|
||||
"Duration: 1 min.");
|
||||
AssertSpellText(
|
||||
layout,
|
||||
AppraisalUiController.SpellRangeTextId,
|
||||
"Range: 82.0 yds.");
|
||||
string display = string.Join(
|
||||
'\n',
|
||||
((UiText)layout.FindElement(
|
||||
AppraisalUiController.SpellDisplayTextId)!)
|
||||
.LinesProvider()
|
||||
.Select(line => line.Text));
|
||||
Assert.Contains("A projected retail spell.", display);
|
||||
Assert.Contains("COMPONENTS:", display);
|
||||
Assert.Contains("Lead Scarab", display);
|
||||
Assert.Contains("Malar Herb", display);
|
||||
|
||||
UiElement iconHost = layout.FindElement(
|
||||
AppraisalUiController.SpellIconId)!;
|
||||
UiTextureElement icon = Assert.Single(
|
||||
iconHost.Children.OfType<UiTextureElement>());
|
||||
Assert.Equal(metadata.SpellId + 1_000u, icon.Texture);
|
||||
}
|
||||
|
||||
private static AppraisalUiController? Bind(
|
||||
ImportedLayout layout,
|
||||
ClientObjectTable objects,
|
||||
|
|
@ -700,14 +798,20 @@ public sealed class AppraisalUiControllerTests
|
|||
CreatureAppraisalRowTemplateFactory? creatureRows = null,
|
||||
CreatureDisplayNameResolver? creatureNames = null,
|
||||
SelectionState? selection = null,
|
||||
RetailAppraisalNameResolver? itemNames = null)
|
||||
RetailAppraisalNameResolver? itemNames = null,
|
||||
Spellbook? spellbook = null,
|
||||
Func<uint, uint>? resolveSpellIcon = null,
|
||||
Func<uint, uint>? resolveComponentIcon = null,
|
||||
Func<uint, IReadOnlyList<SpellExamineComponent>>? spellComponents = null,
|
||||
Func<MagicSchool, uint>? magicSkill = null,
|
||||
SpellExamineComponentTemplateFactory? spellComponentTemplates = null)
|
||||
=> AppraisalUiController.Bind(
|
||||
layout,
|
||||
objects,
|
||||
interaction,
|
||||
selection ?? new SelectionState(),
|
||||
combat,
|
||||
new Spellbook(),
|
||||
spellbook ?? new Spellbook(),
|
||||
() => "Tester",
|
||||
(objectId, text) => inscriptions.Add((objectId, text)),
|
||||
messages.Add,
|
||||
|
|
@ -715,7 +819,12 @@ public sealed class AppraisalUiControllerTests
|
|||
close,
|
||||
creatureRows,
|
||||
creatureNames,
|
||||
itemNames);
|
||||
itemNames,
|
||||
resolveSpellIcon,
|
||||
resolveComponentIcon,
|
||||
spellComponents,
|
||||
magicSkill,
|
||||
spellComponentTemplates);
|
||||
|
||||
private static ItemInteractionController NewInteraction(
|
||||
ClientObjectTable objects,
|
||||
|
|
@ -749,4 +858,48 @@ public sealed class AppraisalUiControllerTests
|
|||
ArmorEnchantments: null,
|
||||
WeaponEnchantments: null,
|
||||
ResistEnchantments: null);
|
||||
|
||||
private static void AssertSpellText(
|
||||
ImportedLayout layout,
|
||||
uint elementId,
|
||||
string expected)
|
||||
{
|
||||
UiText text = Assert.IsType<UiText>(layout.FindElement(elementId));
|
||||
Assert.Equal(
|
||||
expected,
|
||||
string.Join('\n', text.LinesProvider().Select(line => line.Text)));
|
||||
}
|
||||
|
||||
private static SpellMetadata ExaminedSpell()
|
||||
=> new(
|
||||
SpellId: 42u,
|
||||
Name: "Incantation of Test",
|
||||
School: "War Magic",
|
||||
Family: 1u,
|
||||
IconId: 0x06001234u,
|
||||
SpellWords: "Malar Aether",
|
||||
Duration: 90f,
|
||||
ManaCost: 50,
|
||||
IsDebuff: false,
|
||||
IsFellowship: false,
|
||||
Description: "A projected retail spell.",
|
||||
SortKey: 0,
|
||||
Difficulty: 0,
|
||||
Flags: 0u,
|
||||
Generation: 6,
|
||||
IsFastWindup: false,
|
||||
IsOffensive: true,
|
||||
IsUntargeted: false,
|
||||
Speed: 0f,
|
||||
CasterEffect: 0u,
|
||||
TargetEffect: 0u,
|
||||
TargetMask: 0u,
|
||||
SpellType: 0)
|
||||
{
|
||||
SchoolId = MagicSchool.WarMagic,
|
||||
BaseRangeConstant = 40f,
|
||||
BaseRangeModifier = 0.25f,
|
||||
ManaModifier = 14u,
|
||||
FormulaComponents = [10u, 11u],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,6 +280,45 @@ public sealed class SpellcastingUiControllerTests
|
|||
Assert.Empty(used);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FavoritePressSelectsImmediately_AndRightClickExaminesLocally()
|
||||
{
|
||||
ImportedLayout layout = LayoutImporter.Build(
|
||||
FixtureLoader.LoadCombatInfos(), NoTex, datFont: null);
|
||||
var spellbook = new Spellbook();
|
||||
spellbook.OnSpellLearned(42u, 1f);
|
||||
spellbook.OnSpellLearned(43u, 1f);
|
||||
spellbook.SetFavorite(0, 0, 42u);
|
||||
spellbook.SetFavorite(0, 1, 43u);
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject { ObjectId = 1u, Name = "Player" });
|
||||
var selection = new SelectionState();
|
||||
var examined = new List<uint>();
|
||||
using SpellcastingUiController controller = Bind(
|
||||
layout,
|
||||
spellbook,
|
||||
objects,
|
||||
_ => { },
|
||||
selection: selection,
|
||||
examineSpell: examined.Add)!;
|
||||
UiElement group = layout.FindElement(0x100000AAu)!;
|
||||
UiItemList list = Descendants(group).OfType<UiItemList>().First();
|
||||
UiCatalogSlot first = Assert.IsType<UiCatalogSlot>(list.GetItem(0));
|
||||
UiCatalogSlot second = Assert.IsType<UiCatalogSlot>(list.GetItem(1));
|
||||
Assert.True(first.Selected);
|
||||
|
||||
second.OnEvent(new UiEvent(0u, second, UiEventType.MouseDown));
|
||||
|
||||
Assert.False(first.Selected);
|
||||
Assert.True(second.Selected);
|
||||
Assert.Null(selection.SelectedObjectId);
|
||||
|
||||
second.OnEvent(new UiEvent(0u, second, UiEventType.RightClick));
|
||||
|
||||
Assert.Equal(new uint[] { 43u }, examined);
|
||||
Assert.Null(selection.SelectedObjectId);
|
||||
}
|
||||
|
||||
private static SpellcastingUiController? Bind(
|
||||
ImportedLayout layout,
|
||||
Spellbook spellbook,
|
||||
|
|
@ -287,7 +326,9 @@ public sealed class SpellcastingUiControllerTests
|
|||
Action<uint> useItem,
|
||||
Action<int, int, uint>? addFavorite = null,
|
||||
UiShortcutDigitGraphics? shortcutDigits = null,
|
||||
uint emptySlotSprite = 0u)
|
||||
uint emptySlotSprite = 0u,
|
||||
SelectionState? selection = null,
|
||||
Action<uint>? examineSpell = null)
|
||||
{
|
||||
var casting = new SpellCastingController(
|
||||
spellbook, () => null, () => 1u, () => { }, _ => { }, (_, _) => { }, _ => { });
|
||||
|
|
@ -296,11 +337,12 @@ public sealed class SpellcastingUiControllerTests
|
|||
spellId => spellId,
|
||||
item => item.ObjectId,
|
||||
useItem,
|
||||
new SelectionState(),
|
||||
selection ?? new SelectionState(),
|
||||
addFavorite ?? ((_, _, _) => { }),
|
||||
(_, _) => { },
|
||||
shortcutDigits,
|
||||
emptySlotSprite);
|
||||
emptySlotSprite,
|
||||
examineSpell);
|
||||
}
|
||||
|
||||
private static void ApplyAnchors(UiElement parent)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue