fix(ui): match retail item appraisal semantics
Preserve PublicWeenieDesc hook identity from CreateObject through the item model so hook appraisals suppress sentinel capacities exactly. Use appraisal-only Value and Burden presence, retain AddItemInfo paragraph and authored font-color selection, and port retail lock, page, enchantment, and spell-block formatting. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
bc47bc4917
commit
d3c5e06fdd
21 changed files with 982 additions and 145 deletions
177
src/AcDream.App/UI/Layout/ItemAppraisalReport.cs
Normal file
177
src/AcDream.App/UI/Layout/ItemAppraisalReport.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Font-color indices selected by retail <c>ItemExamineUI</c>. The concrete
|
||||
/// colors come from the item report's LayoutDesc property <c>0x1B</c>.
|
||||
/// </summary>
|
||||
public enum ItemAppraisalFontStyle
|
||||
{
|
||||
Normal = 0,
|
||||
Beneficial = 1,
|
||||
Detrimental = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Separator inserted before an appraisal fragment. This is the retained
|
||||
/// equivalent of <c>ItemExamineUI::AddItemInfo</c>'s final argument.
|
||||
/// </summary>
|
||||
public enum ItemAppraisalSeparator
|
||||
{
|
||||
None,
|
||||
Line,
|
||||
Paragraph,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One retail appraisal append operation: prose, separator, and authored
|
||||
/// font-color index. Keeping these separate until shaping preserves style
|
||||
/// when a long fragment wraps.
|
||||
/// </summary>
|
||||
public readonly record struct ItemAppraisalFragment(
|
||||
string Text,
|
||||
ItemAppraisalSeparator Separator,
|
||||
ItemAppraisalFontStyle Style);
|
||||
|
||||
/// <summary>
|
||||
/// Immutable item appraisal report in retail append order.
|
||||
/// </summary>
|
||||
public sealed class ItemAppraisalReport
|
||||
{
|
||||
public static ItemAppraisalReport Empty { get; } = new([]);
|
||||
|
||||
public ItemAppraisalReport(IReadOnlyList<ItemAppraisalFragment> fragments)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(fragments);
|
||||
Fragments = fragments;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ItemAppraisalFragment> Fragments { get; }
|
||||
public bool IsEmpty => Fragments.Count == 0;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
foreach (ItemAppraisalFragment fragment in Fragments)
|
||||
{
|
||||
if (text.Length != 0)
|
||||
{
|
||||
text.Append(fragment.Separator == ItemAppraisalSeparator.Paragraph
|
||||
? "\n\n"
|
||||
: "\n");
|
||||
}
|
||||
text.Append(fragment.Text);
|
||||
}
|
||||
return text.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port of <c>ItemExamineUI::AddItemInfo @ 0x004AC050/0x004ADCA0</c>.
|
||||
/// Retail appends one newline when <paramref name="sameParagraph"/> is true,
|
||||
/// two otherwise, then applies font DID index zero and the supplied color index.
|
||||
/// </summary>
|
||||
internal sealed class ItemAppraisalReportBuilder
|
||||
{
|
||||
private readonly List<ItemAppraisalFragment> _fragments = [];
|
||||
|
||||
public void Line(
|
||||
string value,
|
||||
ItemAppraisalFontStyle style = ItemAppraisalFontStyle.Normal)
|
||||
=> Add(value, sameParagraph: true, style);
|
||||
|
||||
public void Paragraph(
|
||||
string value,
|
||||
ItemAppraisalFontStyle style = ItemAppraisalFontStyle.Normal)
|
||||
=> Add(value, sameParagraph: false, style);
|
||||
|
||||
public ItemAppraisalReport Build()
|
||||
=> _fragments.Count == 0
|
||||
? ItemAppraisalReport.Empty
|
||||
: new ItemAppraisalReport(_fragments.ToArray());
|
||||
|
||||
private void Add(
|
||||
string value,
|
||||
bool sameParagraph,
|
||||
ItemAppraisalFontStyle style)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return;
|
||||
|
||||
_fragments.Add(new ItemAppraisalFragment(
|
||||
value,
|
||||
_fragments.Count == 0
|
||||
? ItemAppraisalSeparator.None
|
||||
: sameParagraph
|
||||
? ItemAppraisalSeparator.Line
|
||||
: ItemAppraisalSeparator.Paragraph,
|
||||
style));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Width-aware projection of retail appraisal fragments into the retained
|
||||
/// <see cref="UiText"/> line model.
|
||||
/// </summary>
|
||||
internal static class ItemAppraisalTextLayout
|
||||
{
|
||||
public static IReadOnlyList<UiText.Line> Shape(
|
||||
UiText target,
|
||||
ItemAppraisalReport report)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(target);
|
||||
ArgumentNullException.ThrowIfNull(report);
|
||||
|
||||
float maxWidth = Math.Max(1f, target.Width - (2f * target.Padding));
|
||||
float Measure(string value)
|
||||
=> target.DatFont?.MeasureWidth(value)
|
||||
?? target.Font?.MeasureWidth(value)
|
||||
?? value.Length * 8f;
|
||||
|
||||
var lines = new List<UiText.Line>();
|
||||
foreach (ItemAppraisalFragment fragment in report.Fragments)
|
||||
{
|
||||
if (lines.Count != 0
|
||||
&& fragment.Separator == ItemAppraisalSeparator.Paragraph)
|
||||
{
|
||||
lines.Add(new UiText.Line(string.Empty, ResolveColor(target, fragment.Style)));
|
||||
}
|
||||
|
||||
Vector4 color = ResolveColor(target, fragment.Style);
|
||||
string normalized = fragment.Text.Replace(
|
||||
"\\n",
|
||||
"\n",
|
||||
StringComparison.Ordinal);
|
||||
foreach (string logicalLine in normalized.Split('\n'))
|
||||
{
|
||||
if (logicalLine.Length == 0)
|
||||
{
|
||||
lines.Add(new UiText.Line(string.Empty, color));
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (string wrapped in ChatWindowController.WrapText(
|
||||
logicalLine,
|
||||
maxWidth,
|
||||
Measure))
|
||||
{
|
||||
lines.Add(new UiText.Line(wrapped, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static Vector4 ResolveColor(
|
||||
UiText target,
|
||||
ItemAppraisalFontStyle style)
|
||||
{
|
||||
int index = (int)style;
|
||||
return index >= 0 && index < target.FontColorPalette.Count
|
||||
? target.FontColorPalette[index]
|
||||
: target.DefaultColor;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue