fix(ui): select and examine favorite spells like retail

This commit is contained in:
Erik 2026-07-24 06:47:54 +02:00
parent 043ab10b3c
commit 3e31b0ac70
23 changed files with 974 additions and 34 deletions

View file

@ -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],
};
}