feat(ui): complete retail creature appraisal details
Port the separate creature rating list, layer the animated preview between authored row chrome and text, and follow selection while the examination floaty is visible. Preserve the remaining item-preview and font-state gaps in AP-110. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
7eaa68a5f4
commit
d96ea2de98
11 changed files with 578 additions and 61 deletions
|
|
@ -3,6 +3,7 @@ using AcDream.App.UI.Layout;
|
|||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
|
@ -129,6 +130,7 @@ public sealed class AppraisalUiControllerTests
|
|||
Assert.True(controller.Apply(Parsed(properties, creature)));
|
||||
Assert.Equal(AppraisalView.Creature, controller.ActiveView);
|
||||
Assert.Equal(1, shown);
|
||||
controller.OnShown();
|
||||
Assert.False(layout.FindElement(AppraisalUiController.ItemPanelId)!.Visible);
|
||||
Assert.True(layout.FindElement(AppraisalUiController.CreaturePanelId)!.Visible);
|
||||
|
||||
|
|
@ -139,7 +141,7 @@ public sealed class AppraisalUiControllerTests
|
|||
Assert.Equal(new[] { ObjectId, ObjectId }, sent);
|
||||
Assert.Equal(0, interaction.BusyCount);
|
||||
|
||||
layout.Root.Visible = false;
|
||||
controller.OnHidden();
|
||||
controller.Tick(0.75);
|
||||
Assert.Equal(new[] { ObjectId, ObjectId }, sent);
|
||||
|
||||
|
|
@ -188,6 +190,8 @@ public sealed class AppraisalUiControllerTests
|
|||
var properties = new PropertyBundle();
|
||||
properties.Ints[2u] = 77;
|
||||
properties.Ints[25u] = 80;
|
||||
properties.Ints[0x133u] = 35;
|
||||
properties.Ints[0x13Au] = 4;
|
||||
var profile = new AppraiseInfoParser.CreatureProfile(
|
||||
Flags: 0x08u,
|
||||
Health: 295u,
|
||||
|
|
@ -224,7 +228,19 @@ public sealed class AppraisalUiControllerTests
|
|||
|
||||
UiElement host = layout.FindElement(
|
||||
AppraisalUiController.CreatureStatsListId)!;
|
||||
UiItemList list = Assert.Single(host.Children.OfType<UiItemList>());
|
||||
UiElement creaturePanel = layout.FindElement(
|
||||
AppraisalUiController.CreaturePanelId)!;
|
||||
UiViewport viewport = Assert.IsType<UiViewport>(
|
||||
layout.FindElement(AppraisalUiController.CreatureViewportId));
|
||||
UiItemList background = Assert.Single(
|
||||
host.Children.OfType<UiItemList>());
|
||||
UiItemList list = Assert.Single(
|
||||
creaturePanel.Children.OfType<UiItemList>(),
|
||||
candidate => candidate.Top == host.Top);
|
||||
Assert.Equal(CreatureAppraisalLayeredList.TextInset, list.Left);
|
||||
Assert.True(host.ZOrder < viewport.ZOrder);
|
||||
Assert.True(viewport.ZOrder < list.ZOrder);
|
||||
Assert.Same(background.Scroll, list.Scroll);
|
||||
Assert.Equal(9, list.GetNumUIItems());
|
||||
string[] labels = new string[9];
|
||||
string[] values = new string[9];
|
||||
|
|
@ -253,6 +269,97 @@ public sealed class AppraisalUiControllerTests
|
|||
"295/295 (100 %)", "190/190", "550/550",
|
||||
],
|
||||
values);
|
||||
|
||||
UiElement extraHost = layout.FindElement(
|
||||
AppraisalUiController.CreatureExtraListId)!;
|
||||
UiItemList extra = Assert.Single(
|
||||
creaturePanel.Children.OfType<UiItemList>(),
|
||||
candidate => candidate.Top == extraHost.Top);
|
||||
Assert.Equal(3, extra.GetNumUIItems());
|
||||
UiTemplateListSlot rating =
|
||||
Assert.IsType<UiTemplateListSlot>(extra.GetItem(1));
|
||||
Assert.Equal(
|
||||
"Dmg/CritDmg",
|
||||
Assert.Single(((UiText)rating.Content.FindElement(
|
||||
CreatureAppraisalRowTemplateFactory.LabelId)!)
|
||||
.LinesProvider()).Text);
|
||||
Assert.Equal(
|
||||
"Rating: 35/4",
|
||||
Assert.Single(((UiText)rating.Content.FindElement(
|
||||
CreatureAppraisalRowTemplateFactory.ValueId)!)
|
||||
.LinesProvider()).Text);
|
||||
Assert.True(extraHost.ZOrder < viewport.ZOrder);
|
||||
Assert.True(viewport.ZOrder < extra.ZOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VisibleExaminationFollowsSelectionAcrossSubviewsAndClosesOnClear()
|
||||
{
|
||||
const uint otherObjectId = 0x50000003u;
|
||||
ImportedLayout layout = FixtureLoader.LoadExamination();
|
||||
var objects = new ClientObjectTable();
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = ObjectId,
|
||||
Name = "Drudge",
|
||||
Type = ItemType.Creature,
|
||||
});
|
||||
objects.AddOrUpdate(new ClientObject
|
||||
{
|
||||
ObjectId = otherObjectId,
|
||||
Name = "Sword",
|
||||
Type = ItemType.MeleeWeapon,
|
||||
});
|
||||
var selection = new SelectionState();
|
||||
var sent = new List<uint>();
|
||||
int closed = 0;
|
||||
using var interaction = NewInteraction(objects, sent);
|
||||
using AppraisalUiController controller = Bind(
|
||||
layout,
|
||||
objects,
|
||||
interaction,
|
||||
new CombatState(),
|
||||
[],
|
||||
[],
|
||||
() => { },
|
||||
() => closed++,
|
||||
selection: selection)!;
|
||||
|
||||
selection.Select(ObjectId, SelectionChangeSource.World);
|
||||
interaction.ExamineSelectedOrEnterMode(ObjectId);
|
||||
Assert.True(controller.Apply(Parsed(
|
||||
new PropertyBundle(),
|
||||
new AppraiseInfoParser.CreatureProfile(
|
||||
Flags: 0u,
|
||||
Health: 1u,
|
||||
HealthMax: 1u,
|
||||
Strength: null,
|
||||
Endurance: null,
|
||||
Quickness: null,
|
||||
Coordination: null,
|
||||
Focus: null,
|
||||
Self: null,
|
||||
Stamina: null,
|
||||
Mana: null,
|
||||
StaminaMax: null,
|
||||
ManaMax: null,
|
||||
AttributeHighlights: null,
|
||||
AttributeColors: null))));
|
||||
controller.OnShown();
|
||||
|
||||
selection.Select(otherObjectId, SelectionChangeSource.Inventory);
|
||||
Assert.Equal(new[] { ObjectId, otherObjectId }, sent);
|
||||
Assert.True(controller.Apply(Parsed(
|
||||
new PropertyBundle(),
|
||||
guid: otherObjectId)));
|
||||
Assert.Equal(AppraisalView.Item, controller.ActiveView);
|
||||
|
||||
selection.Clear(SelectionChangeSource.World);
|
||||
Assert.Equal(1, closed);
|
||||
|
||||
controller.OnHidden();
|
||||
selection.Select(ObjectId, SelectionChangeSource.World);
|
||||
Assert.Equal(new[] { ObjectId, otherObjectId }, sent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -540,11 +647,13 @@ public sealed class AppraisalUiControllerTests
|
|||
Action show,
|
||||
Action close,
|
||||
CreatureAppraisalRowTemplateFactory? creatureRows = null,
|
||||
CreatureDisplayNameResolver? creatureNames = null)
|
||||
CreatureDisplayNameResolver? creatureNames = null,
|
||||
SelectionState? selection = null)
|
||||
=> AppraisalUiController.Bind(
|
||||
layout,
|
||||
objects,
|
||||
interaction,
|
||||
selection ?? new SelectionState(),
|
||||
combat,
|
||||
new Spellbook(),
|
||||
() => "Tester",
|
||||
|
|
@ -569,9 +678,10 @@ public sealed class AppraisalUiControllerTests
|
|||
|
||||
private static AppraiseInfoParser.Parsed Parsed(
|
||||
PropertyBundle properties,
|
||||
AppraiseInfoParser.CreatureProfile? creature = null)
|
||||
AppraiseInfoParser.CreatureProfile? creature = null,
|
||||
uint guid = ObjectId)
|
||||
=> new(
|
||||
Guid: ObjectId,
|
||||
Guid: guid,
|
||||
Flags: creature is null
|
||||
? AppraiseInfoParser.IdentifyResponseFlags.IntStatsTable
|
||||
: AppraiseInfoParser.IdentifyResponseFlags.CreatureProfile,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
|
@ -45,6 +47,53 @@ public sealed class CreatureAppraisalRowsTests
|
|||
Assert.Equal(CreatureAppraisalValueStyle.Normal, rows[1].Style);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtraRatingsFollowRetailGroupingFormattingAndSeparators()
|
||||
{
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[0x133u] = 35;
|
||||
properties.Ints[0x139u] = 7;
|
||||
properties.Ints[0x13Au] = 4;
|
||||
properties.Ints[0x134u] = 12;
|
||||
properties.Ints[0x13Bu] = 3;
|
||||
properties.Ints[0x13Cu] = 2;
|
||||
properties.Ints[0x15Eu] = 9;
|
||||
properties.Ints[0x15Fu] = 6;
|
||||
|
||||
IReadOnlyList<CreatureAppraisalRow> rows =
|
||||
CreatureAppraisalRows.BuildExtra(properties);
|
||||
|
||||
Assert.Equal(5, rows.Count);
|
||||
Assert.Equal(("", ""), (rows[0].Label, rows[0].Value));
|
||||
Assert.Equal(
|
||||
("Dmg/CritDmg", "Rating: 35/4"),
|
||||
(rows[1].Label, rows[1].Value));
|
||||
Assert.Equal(
|
||||
("Dmg/CritDmg", "Resist: 12/2"),
|
||||
(rows[2].Label, rows[2].Value));
|
||||
Assert.Equal(
|
||||
("DoT/Life:", "Resist: 9/6"),
|
||||
(rows[3].Label, rows[3].Value));
|
||||
Assert.Equal(("", ""), (rows[4].Label, rows[4].Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CritOnlyTriggersRatingRowWhileHealingBoostAloneDoesNot()
|
||||
{
|
||||
var critOnly = new PropertyBundle();
|
||||
critOnly.Ints[0x139u] = 8;
|
||||
|
||||
IReadOnlyList<CreatureAppraisalRow> rows =
|
||||
CreatureAppraisalRows.BuildExtra(critOnly);
|
||||
|
||||
Assert.Equal(3, rows.Count);
|
||||
Assert.Equal("Rating: 0/0", rows[1].Value);
|
||||
|
||||
var healingOnly = new PropertyBundle();
|
||||
healingOnly.Ints[0x143u] = 20;
|
||||
Assert.Empty(CreatureAppraisalRows.BuildExtra(healingOnly));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthoredRowTemplateCarriesRetailOverlappingLabelValueGeometry()
|
||||
{
|
||||
|
|
@ -76,6 +125,43 @@ public sealed class CreatureAppraisalRowsTests
|
|||
Assert.Equal(string.Empty, resolver.Resolve(999));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayeredTemplateSeparatesChromeFromForegroundText()
|
||||
{
|
||||
var templates = new CreatureAppraisalRowTemplateFactory(
|
||||
FixtureLoader.LoadExaminationRowTemplateInfos(),
|
||||
_ => (0u, 0, 0),
|
||||
defaultFont: null);
|
||||
var row = new CreatureAppraisalRow(
|
||||
"Strength",
|
||||
"120",
|
||||
CreatureAppraisalValueStyle.Normal);
|
||||
|
||||
UiTemplateListSlot background = templates.Create(
|
||||
row,
|
||||
CreatureAppraisalRowLayer.Background);
|
||||
UiTemplateListSlot foreground = templates.Create(
|
||||
row,
|
||||
CreatureAppraisalRowLayer.Foreground);
|
||||
|
||||
Assert.Empty(((UiText)background.Content.FindElement(
|
||||
CreatureAppraisalRowTemplateFactory.LabelId)!).LinesProvider());
|
||||
Assert.Empty(((UiText)background.Content.FindElement(
|
||||
CreatureAppraisalRowTemplateFactory.ValueId)!).LinesProvider());
|
||||
Assert.False(
|
||||
Assert.IsType<UiDatElement>(foreground.Content.Root).MediaVisible);
|
||||
Assert.Equal(
|
||||
"Strength",
|
||||
Assert.Single(((UiText)foreground.Content.FindElement(
|
||||
CreatureAppraisalRowTemplateFactory.LabelId)!)
|
||||
.LinesProvider()).Text);
|
||||
Assert.Equal(
|
||||
"120",
|
||||
Assert.Single(((UiText)foreground.Content.FindElement(
|
||||
CreatureAppraisalRowTemplateFactory.ValueId)!)
|
||||
.LinesProvider()).Text);
|
||||
}
|
||||
|
||||
private static AppraiseInfoParser.CreatureProfile Profile(
|
||||
uint health,
|
||||
uint healthMax,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue