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

@ -112,28 +112,33 @@ public static class ItemAppraisalTextFormatter
if (!string.IsNullOrWhiteSpace(imbuedBy))
report.Line($"Imbued by {imbuedBy}.");
if (!properties.Ints.TryGetValue(105u, out int workmanship))
return;
if (properties.Ints.TryGetValue(170u, out int salvagedItems)
&& salvagedItems > 0)
if (properties.Ints.TryGetValue(105u, out int workmanship))
{
double average = (double)workmanship / salvagedItems;
int workmanshipBand = Math.Clamp(
(int)Math.Round(average),
0,
10);
report.Line(
$"Workmanship: {WorkmanshipAdjective(workmanshipBand)} "
+ $"({average.ToString("0.00", CultureInfo.CurrentCulture)})");
report.Paragraph(
$"Salvaged from {salvagedItems.ToString(CultureInfo.CurrentCulture)} items.");
return;
if (properties.Ints.TryGetValue(170u, out int salvagedItems)
&& salvagedItems > 0)
{
double average = (double)workmanship / salvagedItems;
int workmanshipBand = Math.Clamp(
(int)Math.Round(average),
0,
10);
report.Line(
$"Workmanship: {WorkmanshipAdjective(workmanshipBand)} "
+ $"({average.ToString("0.00", CultureInfo.CurrentCulture)})");
report.Paragraph(
$"Salvaged from {salvagedItems.ToString(CultureInfo.CurrentCulture)} items.");
}
else
{
report.Line(
$"Workmanship: {WorkmanshipAdjective(Math.Clamp(workmanship, 0, 10))} "
+ $"({workmanship.ToString(CultureInfo.CurrentCulture)})");
}
}
report.Line(
$"Workmanship: {WorkmanshipAdjective(Math.Clamp(workmanship, 0, 10))} "
+ $"({workmanship.ToString(CultureInfo.CurrentCulture)})");
// Appraisal_ShowTinkeringInfo @ 0x004B1090 appends an empty
// AddItemInfo entry unconditionally, establishing the next section.
report.BlankLine();
}
/// <summary>
@ -144,11 +149,12 @@ public static class ItemAppraisalTextFormatter
RetailReportBuilder report,
PropertyBundle properties)
{
if (properties.Ints.TryGetValue(265u, out int setId)
&& EquipmentSetName(setId) is { } setName)
{
string? setName = properties.Ints.TryGetValue(265u, out int setId)
? EquipmentSetName(setId)
: null;
bool setShown = setName is not null;
if (setShown)
report.Line($"Set: {setName}");
}
(uint Property, string Label)[] ratingProperties =
[
@ -168,11 +174,23 @@ public static class ItemAppraisalTextFormatter
.Select(pair =>
$"{pair.Label} {properties.GetInt(pair.Property).ToString(CultureInfo.CurrentCulture)}")
.ToArray();
if (ratings.Length != 0)
bool ratingsShown = ratings.Length != 0;
if (ratingsShown)
report.Line($"Ratings: {string.Join(", ", ratings)}");
if (properties.GetInt(379u) is int vitality && vitality > 0)
{
report.Line(
$"This item adds {vitality.ToString(CultureInfo.CurrentCulture)} Vitality.");
ratingsShown = true;
}
// Appraisal_ShowRatings terminates its emitted block with an empty
// append; SetAppraiseInfo adds another when either Set or Ratings
// contributed content.
if (ratingsShown)
report.BlankLine();
if (setShown || ratingsShown)
report.BlankLine();
}
/// <summary>
@ -341,7 +359,9 @@ public static class ItemAppraisalTextFormatter
return;
}
report.Line(
// Retail's first armor string begins with a literal '\n' before
// AddItemInfo adds its ordinary separator.
report.Paragraph(
$"Armor Level: {armorLevel.ToString(CultureInfo.CurrentCulture)}",
EnchantmentStyle(appraisal.ArmorEnchantments, 0x0001u));
ShowProtection(
@ -492,23 +512,34 @@ public static class ItemAppraisalTextFormatter
PropertyBundle properties,
RetailAppraisalNameResolver names)
{
// Appraisal_ShowSpecialProperties @ 0x004B0159.
report.BlankLine();
int carryLimit = properties.GetInt(279u);
if (carryLimit > 0)
{
report.BlankLine();
report.Line(
$"You can only carry "
+ $"{carryLimit.ToString("N0", CultureInfo.CurrentCulture)} "
+ "of these items.");
}
if (properties.Floats.TryGetValue(167u, out double cooldown)
&& cooldown > 0d)
{
report.Line($"Cooldown When Used: {FormatDeltaTime(cooldown)}");
if (properties.Ints.ContainsKey(280u))
report.BlankLine();
}
int cleave = properties.GetInt(292u);
if (cleave > 1)
{
report.Line(
$"Cleave: {cleave.ToString(CultureInfo.CurrentCulture)} enemies in front arc.");
report.BlankLine();
}
var special = new List<string>();
int slayer = properties.GetInt(166u);
@ -745,16 +776,23 @@ public static class ItemAppraisalTextFormatter
PropertyBundle properties)
{
int level = properties.GetInt(369u);
int skill = properties.GetInt(366u);
int difficulty = properties.GetInt(367u);
int specializedSkill = properties.GetInt(368u);
if (level > 0
|| (skill > 0 && difficulty > 0)
|| specializedSkill > 0)
{
report.BlankLine();
}
if (level > 0)
report.Line(
$"Use requires level {level.ToString(CultureInfo.CurrentCulture)}.");
int skill = properties.GetInt(366u);
int difficulty = properties.GetInt(367u);
if (skill > 0 && difficulty > 0)
report.Line(
$"Use requires {UsageSkillName(skill)} of at least "
+ $"{difficulty.ToString(CultureInfo.CurrentCulture)}.");
int specializedSkill = properties.GetInt(368u);
if (specializedSkill > 0)
report.Line(
$"Use requires specialized {UsageSkillName(specializedSkill)}.");
@ -794,11 +832,13 @@ public static class ItemAppraisalTextFormatter
report.Line(
$"Item XP: {experience.ToString("N0", CultureInfo.CurrentCulture)} / "
+ $"{nextExperience.ToString("N0", CultureInfo.CurrentCulture)}");
report.BlankLine();
if (properties.GetInt(352u) == 2)
{
report.Paragraph(
report.Line(
"This cloak has a chance to reduce an incoming attack by 200 damage.");
report.BlankLine();
}
}
@ -1776,6 +1816,10 @@ public static class ItemAppraisalTextFormatter
ItemAppraisalFontStyle style = ItemAppraisalFontStyle.Normal)
=> _report.Paragraph(value, style);
public void BlankLine(
ItemAppraisalFontStyle style = ItemAppraisalFontStyle.Normal)
=> _report.BlankLine(style);
public ItemAppraisalReport Build() => _report.Build();
}
}