fix(ui): preserve retail item titles and spacing

Carry PublicWeenieDesc material type into the live object model so examination titles use the DAT-authored material prefix. Preserve retail AddItemInfo empty appends and embedded armor separator, restoring the deliberate blank rows between appraisal sections.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-24 05:21:01 +02:00
parent d78d09cfbc
commit 6718ee45a0
19 changed files with 341 additions and 46 deletions

View file

@ -93,6 +93,43 @@ public sealed class AppraisalUiControllerTests
Assert.Equal(1, closed);
}
[Fact]
public void ItemResponse_TitleUsesRetailMaterialDecoratedAppropriateName()
{
ImportedLayout layout = FixtureLoader.LoadExamination();
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject
{
ObjectId = ObjectId,
Name = "Steel Toed Boots",
Type = ItemType.Clothing,
MaterialType = 77u,
});
using var interaction = NewInteraction(objects, []);
var names = new RetailAppraisalNameResolver(
new Dictionary<uint, string> { [77u] = "Reed Shark Hide" },
new CreatureDisplayNameResolver(new Dictionary<uint, string>()));
using AppraisalUiController controller = Bind(
layout,
objects,
interaction,
new CombatState(),
[],
[],
() => { },
() => { },
itemNames: names)!;
interaction.ExamineSelectedOrEnterMode(ObjectId);
Assert.True(controller.Apply(Parsed(new PropertyBundle())));
UiText title = Assert.IsType<UiText>(
layout.FindElement(AppraisalUiController.TitleId));
Assert.Equal(
"Reed Shark Hide Steel Toed Boots",
Assert.Single(title.LinesProvider()).Text);
}
[Fact]
public void CreatureResponse_SelectsCreatureSubviewAndRefreshesInCombatWithoutBusy()
{
@ -662,7 +699,8 @@ public sealed class AppraisalUiControllerTests
Action close,
CreatureAppraisalRowTemplateFactory? creatureRows = null,
CreatureDisplayNameResolver? creatureNames = null,
SelectionState? selection = null)
SelectionState? selection = null,
RetailAppraisalNameResolver? itemNames = null)
=> AppraisalUiController.Bind(
layout,
objects,
@ -676,7 +714,8 @@ public sealed class AppraisalUiControllerTests
show,
close,
creatureRows,
creatureNames);
creatureNames,
itemNames);
private static ItemInteractionController NewInteraction(
ClientObjectTable objects,

View file

@ -112,7 +112,7 @@ public sealed class ItemAppraisalTextFormatterTests
_ => null);
Assert.Equal(
"Value: ???\nBurden: Unknown\n\n"
"Value: ???\nBurden: Unknown\n\n\n\n"
+ "A hollowed out tree trunk that has a Phyntos Wasp Hive in it.",
report.ToString());
Assert.DoesNotContain("Can hold", report.ToString());
@ -128,6 +128,16 @@ public sealed class ItemAppraisalTextFormatterTests
Assert.Equal(ItemAppraisalSeparator.Line, burden.Separator);
Assert.Equal("Burden: Unknown", burden.Text);
},
tinkeringBoundary =>
{
Assert.Equal(ItemAppraisalSeparator.Line, tinkeringBoundary.Separator);
Assert.Equal(string.Empty, tinkeringBoundary.Text);
},
specialPropertiesBoundary =>
{
Assert.Equal(ItemAppraisalSeparator.Line, specialPropertiesBoundary.Separator);
Assert.Equal(string.Empty, specialPropertiesBoundary.Text);
},
description =>
{
Assert.Equal(ItemAppraisalSeparator.Paragraph, description.Separator);
@ -180,11 +190,59 @@ public sealed class ItemAppraisalTextFormatterTests
_ => null);
Assert.Equal(
"Value: ???\nBurden: Unknown\n\n"
"Value: ???\nBurden: Unknown\n\n\n\n"
+ "Can hold up to 24 items and 1 containers.",
report);
}
[Fact]
public void Boots_PreserveRetailBlankSectionBoundaries()
{
var obj = new ClientObject
{
ObjectId = 0x50000016u,
Name = "Steel Toed Boots",
Type = ItemType.Clothing,
ValidLocations = EquipMask.FootWear,
Priority = 0x0001_0000u,
};
var properties = new PropertyBundle();
properties.Ints[19u] = 1_250;
properties.Ints[5u] = 180;
properties.Ints[105u] = 7;
properties.Ints[28u] = 80;
AppraiseInfoParser.Parsed appraisal = Parsed(
properties,
armor: new AppraiseInfoParser.ArmorProfile(
SlashingProtection: 1f,
PiercingProtection: 1f,
BludgeoningProtection: 1f,
ColdProtection: 1f,
FireProtection: 1f,
AcidProtection: 1f,
NetherProtection: 1f,
LightningProtection: 1f));
ItemAppraisalReport report = ItemAppraisalTextFormatter.BuildReport(
obj,
appraisal,
_ => null);
Assert.Contains(
"Workmanship: Flawless (7)\n\nCovers Feet\n\nArmor Level: 80",
report.ToString());
var text = new UiText { Width = 1_000f };
string[] lines = ItemAppraisalTextLayout.Shape(text, report)
.Select(line => line.Text)
.ToArray();
int workmanship = Array.IndexOf(lines, "Workmanship: Flawless (7)");
Assert.True(workmanship >= 0);
Assert.Equal(string.Empty, lines[workmanship + 1]);
Assert.Equal("Covers Feet", lines[workmanship + 2]);
Assert.Equal(string.Empty, lines[workmanship + 3]);
Assert.Equal("Armor Level: 80", lines[workmanship + 4]);
}
[Fact]
public void BookCapacity_UsesRetailUsedThenTotalPropertyOrder()
{

View file

@ -681,6 +681,7 @@ public sealed class CreateObjectTests
var parsed = CreateObject.TryParse(body);
Assert.NotNull(parsed);
Assert.Equal(7u, parsed.Value.MaterialType);
Assert.Equal(11u, parsed.Value.CooldownId);
Assert.Equal(12.5, parsed.Value.CooldownDuration);
Assert.Equal(0x50000001u, parsed.Value.PetOwnerId);

View file

@ -70,6 +70,7 @@ public sealed class ObjectTableWiringTests
PetOwnerId = 0x50000001u,
CooldownId = 42u,
CooldownDuration = 30d,
MaterialType = 77u,
};
var d = ObjectTableWiring.ToWeenieData(spawn);
@ -122,6 +123,7 @@ public sealed class ObjectTableWiringTests
Assert.Equal(0x50000001u, d.PetOwnerId);
Assert.Equal(42u, d.CooldownId);
Assert.Equal(30d, d.CooldownDuration);
Assert.Equal(77u, d.MaterialType);
}
[Fact]

View file

@ -50,6 +50,17 @@ public sealed class WorldSessionRadarTests
Assert.Equal(0x50000002u, spawn.PetOwnerId);
}
[Fact]
public void ToEntitySpawn_PreservesMaterialType()
{
var spawn = WorldSession.ToEntitySpawn(MinimalParsed() with
{
MaterialType = 77u,
});
Assert.Equal(77u, spawn.MaterialType);
}
private static CreateObject.Parsed MinimalParsed() => new(
Guid: 0x50000001u,
Position: null,

View file

@ -471,6 +471,16 @@ public sealed class ClientObjectTableTests
Assert.Equal("Pyreal Scarabs", table.Get(0x500000B9u)!.GetAppropriateName());
}
[Fact]
public void Ingest_MaterialType_PreservesWireValueWithoutClobberingWhenAbsent()
{
var table = new ClientObjectTable();
table.Ingest(FullWeenie(0x500000BCu) with { MaterialType = 77u });
table.Ingest(FullWeenie(0x500000BCu) with { MaterialType = null });
Assert.Equal(77u, table.Get(0x500000BCu)!.MaterialType);
}
[Fact]
public void Ingest_AmmoType_PreservesWireValueForAutoWieldCompatibility()
{