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
|
|
@ -1,3 +1,4 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Combat;
|
||||
|
|
@ -47,6 +48,8 @@ public sealed class AppraisalUiControllerTests
|
|||
|
||||
Assert.True(interaction.ExamineSelectedOrEnterMode(ObjectId));
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[19u] = 1_250;
|
||||
properties.Ints[5u] = 350;
|
||||
properties.Strings[16u] = "A finely balanced weapon.";
|
||||
properties.Strings[7u] = "Remember the fallen.";
|
||||
properties.Strings[8u] = "Tester";
|
||||
|
|
@ -62,6 +65,13 @@ public sealed class AppraisalUiControllerTests
|
|||
UiText itemText = Assert.IsType<UiText>(
|
||||
layout.FindElement(AppraisalUiController.ItemTextId));
|
||||
Assert.Equal(VJustify.Top, itemText.VerticalJustify);
|
||||
Assert.Equal(
|
||||
[
|
||||
new Vector4(1f, 1f, 1f, 1f),
|
||||
new Vector4(0f, 1f, 0f, 1f),
|
||||
new Vector4(1f, 0f, 0f, 1f),
|
||||
],
|
||||
itemText.FontColorPalette);
|
||||
string report = string.Join('\n', itemText.LinesProvider().Select(line => line.Text));
|
||||
Assert.Contains("Value: 1,250", report);
|
||||
Assert.Contains("Burden: 350", report);
|
||||
|
|
@ -597,6 +607,8 @@ public sealed class AppraisalUiControllerTests
|
|||
ObjectId = ObjectId,
|
||||
Name = "Hooked Decoration",
|
||||
ContainerId = 0x50000002u,
|
||||
HookItemTypes = (uint)ItemType.Misc,
|
||||
HookType = 1u,
|
||||
});
|
||||
var messages = new List<string>();
|
||||
using var interaction = NewInteraction(objects, []);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
|
|
@ -22,6 +24,8 @@ public sealed class ItemAppraisalTextFormatterTests
|
|||
};
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[353u] = 2;
|
||||
properties.Ints[19u] = 12_500;
|
||||
properties.Ints[5u] = 450;
|
||||
properties.Ints[204u] = 4;
|
||||
properties.Ints[106u] = 300;
|
||||
properties.Ints[107u] = 250;
|
||||
|
|
@ -83,6 +87,232 @@ public sealed class ItemAppraisalTextFormatterTests
|
|||
Assert.EndsWith("A finely balanced enchanted blade.", report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HookWithHookProfile_UsesRetailUnknownsAndSuppressesBogusCapacity()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000005u,
|
||||
Name = "Black Phyntos Hive",
|
||||
Type = ItemType.Container,
|
||||
ItemsCapacity = 255,
|
||||
ContainersCapacity = 255,
|
||||
HookItemTypes = (uint)ItemType.Container,
|
||||
HookType = 4u,
|
||||
};
|
||||
var properties = new PropertyBundle();
|
||||
properties.Strings[16u] =
|
||||
"A hollowed out tree trunk that has a Phyntos Wasp Hive in it.";
|
||||
|
||||
ItemAppraisalReport report = ItemAppraisalTextFormatter.BuildReport(
|
||||
obj,
|
||||
Parsed(
|
||||
properties,
|
||||
hook: new AppraiseInfoParser.HookProfile(
|
||||
Flags: 0u,
|
||||
ValidLocations: 0u,
|
||||
AmmoType: 0u)),
|
||||
_ => null);
|
||||
|
||||
Assert.Equal(
|
||||
"Value: ???\nBurden: Unknown\n\n"
|
||||
+ "A hollowed out tree trunk that has a Phyntos Wasp Hive in it.",
|
||||
report.ToString());
|
||||
Assert.DoesNotContain("Can hold", report.ToString());
|
||||
Assert.Collection(
|
||||
report.Fragments,
|
||||
value =>
|
||||
{
|
||||
Assert.Equal(ItemAppraisalSeparator.None, value.Separator);
|
||||
Assert.Equal("Value: ???", value.Text);
|
||||
},
|
||||
burden =>
|
||||
{
|
||||
Assert.Equal(ItemAppraisalSeparator.Line, burden.Separator);
|
||||
Assert.Equal("Burden: Unknown", burden.Text);
|
||||
},
|
||||
description =>
|
||||
{
|
||||
Assert.Equal(ItemAppraisalSeparator.Paragraph, description.Separator);
|
||||
Assert.Equal(properties.Strings[16u], description.Text);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OrdinaryContainer_PresentsCapacityAsRetailParagraph()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000006u,
|
||||
Name = "Backpack",
|
||||
Type = ItemType.Container,
|
||||
ItemsCapacity = 24,
|
||||
ContainersCapacity = 1,
|
||||
};
|
||||
|
||||
string report = ItemAppraisalTextFormatter.Build(
|
||||
obj,
|
||||
Parsed(new PropertyBundle()),
|
||||
_ => null);
|
||||
|
||||
Assert.Equal(
|
||||
"Value: ???\nBurden: Unknown\n\n"
|
||||
+ "Can hold up to 24 items and 1 containers.",
|
||||
report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BookCapacity_UsesRetailUsedThenTotalPropertyOrder()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x5000000Bu,
|
||||
Name = "Book",
|
||||
Type = ItemType.Writable,
|
||||
};
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[174u] = 3;
|
||||
properties.Ints[175u] = 10;
|
||||
|
||||
string report = ItemAppraisalTextFormatter.Build(
|
||||
obj,
|
||||
Parsed(properties),
|
||||
_ => null);
|
||||
|
||||
Assert.EndsWith("3 of 10 pages full.", report);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData((ushort)0x0000, ItemAppraisalFontStyle.Detrimental)]
|
||||
[InlineData((ushort)0x0008, ItemAppraisalFontStyle.Beneficial)]
|
||||
public void WeaponDamage_UsesRetailEnchantmentFontSelection(
|
||||
ushort highHalf,
|
||||
ItemAppraisalFontStyle expected)
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000007u,
|
||||
Name = "Sword",
|
||||
Type = ItemType.MeleeWeapon,
|
||||
ValidLocations = EquipMask.MeleeWeapon,
|
||||
};
|
||||
AppraiseInfoParser.Parsed parsed = Parsed(
|
||||
new PropertyBundle(),
|
||||
weapon: new AppraiseInfoParser.WeaponProfile(
|
||||
DamageType: 1u,
|
||||
WeaponTime: 30u,
|
||||
WeaponSkill: 44u,
|
||||
Damage: 40u,
|
||||
DamageVariance: 0d,
|
||||
DamageMod: 1d,
|
||||
WeaponLength: 1d,
|
||||
MaxVelocity: 0d,
|
||||
WeaponOffense: 1d,
|
||||
MaxVelocityEstimated: 0u),
|
||||
weaponEnchantments: ((ushort)0x0008, highHalf));
|
||||
|
||||
ItemAppraisalReport report = ItemAppraisalTextFormatter.BuildReport(
|
||||
obj,
|
||||
parsed,
|
||||
_ => null);
|
||||
|
||||
ItemAppraisalFragment damage = Assert.Single(
|
||||
report.Fragments,
|
||||
fragment => fragment.Text.StartsWith(
|
||||
"Damage:",
|
||||
StringComparison.Ordinal));
|
||||
Assert.Equal(expected, damage.Style);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TextLayout_PreservesRetailPaletteAcrossParagraphs()
|
||||
{
|
||||
Vector4 normal = Vector4.One;
|
||||
var beneficial = new Vector4(0f, 1f, 0f, 1f);
|
||||
var detrimental = new Vector4(1f, 0f, 0f, 1f);
|
||||
var text = new UiText
|
||||
{
|
||||
Width = 1_000f,
|
||||
FontColorPalette = [normal, beneficial, detrimental],
|
||||
};
|
||||
var report = new ItemAppraisalReport(
|
||||
[
|
||||
new ItemAppraisalFragment(
|
||||
"Normal",
|
||||
ItemAppraisalSeparator.None,
|
||||
ItemAppraisalFontStyle.Normal),
|
||||
new ItemAppraisalFragment(
|
||||
"High",
|
||||
ItemAppraisalSeparator.Line,
|
||||
ItemAppraisalFontStyle.Beneficial),
|
||||
new ItemAppraisalFragment(
|
||||
"Low",
|
||||
ItemAppraisalSeparator.Paragraph,
|
||||
ItemAppraisalFontStyle.Detrimental),
|
||||
]);
|
||||
|
||||
IReadOnlyList<UiText.Line> lines = ItemAppraisalTextLayout.Shape(
|
||||
text,
|
||||
report);
|
||||
|
||||
Assert.Equal(["Normal", "High", "", "Low"], lines.Select(line => line.Text));
|
||||
Assert.Equal(normal, lines[0].Color);
|
||||
Assert.Equal(beneficial, lines[1].Color);
|
||||
Assert.Equal(detrimental, lines[3].Color);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LockInformation_UsesRetailPresenceRulesWordingAndParagraphs()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000009u,
|
||||
Name = "Locked Chest",
|
||||
Type = ItemType.Container,
|
||||
};
|
||||
var properties = new PropertyBundle();
|
||||
properties.Bools[3u] = true;
|
||||
properties.Ints[38u] = 212;
|
||||
properties.Ints[173u] = 42;
|
||||
|
||||
ItemAppraisalReport report = ItemAppraisalTextFormatter.BuildReport(
|
||||
obj,
|
||||
Parsed(properties),
|
||||
_ => null);
|
||||
|
||||
Assert.Contains(
|
||||
report.Fragments,
|
||||
fragment => fragment.Text == "Locked"
|
||||
&& fragment.Separator == ItemAppraisalSeparator.Paragraph);
|
||||
Assert.Contains(
|
||||
report.Fragments,
|
||||
fragment => fragment.Text
|
||||
== "The lock looks difficult to pick (Resistance 212)."
|
||||
&& fragment.Separator == ItemAppraisalSeparator.Paragraph);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingLockedFlag_ReportsLockpickBonusInsteadOfInventingLockState()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x5000000Au,
|
||||
Name = "Lockpick",
|
||||
Type = ItemType.Misc,
|
||||
};
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[38u] = -7;
|
||||
|
||||
string report = ItemAppraisalTextFormatter.Build(
|
||||
obj,
|
||||
Parsed(properties),
|
||||
_ => null);
|
||||
|
||||
Assert.Contains("Bonus to Lockpick Skill: -7", report);
|
||||
Assert.DoesNotContain("Locked", report);
|
||||
Assert.DoesNotContain("Unlocked", report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArmorRequirementsAndUses_UseRetailLabelsAndResistanceBands()
|
||||
{
|
||||
|
|
@ -215,7 +445,9 @@ public sealed class ItemAppraisalTextFormatterTests
|
|||
PropertyBundle properties,
|
||||
uint[]? spells = null,
|
||||
AppraiseInfoParser.WeaponProfile? weapon = null,
|
||||
AppraiseInfoParser.ArmorProfile? armor = null)
|
||||
AppraiseInfoParser.ArmorProfile? armor = null,
|
||||
AppraiseInfoParser.HookProfile? hook = null,
|
||||
(ushort Highlight, ushort Color)? weaponEnchantments = null)
|
||||
=> new(
|
||||
Guid: 0x50000001u,
|
||||
Flags: AppraiseInfoParser.IdentifyResponseFlags.IntStatsTable,
|
||||
|
|
@ -225,10 +457,10 @@ public sealed class ItemAppraisalTextFormatterTests
|
|||
ArmorProfile: armor,
|
||||
CreatureProfile: null,
|
||||
WeaponProfile: weapon,
|
||||
HookProfile: null,
|
||||
HookProfile: hook,
|
||||
ArmorLevels: null,
|
||||
ArmorEnchantments: null,
|
||||
WeaponEnchantments: null,
|
||||
WeaponEnchantments: weaponEnchantments,
|
||||
ResistEnchantments: null);
|
||||
|
||||
private static SpellMetadata Spell(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue