feat(ui): complete retail item appraisal reports
Restore the authored examination geometry and top-origin item list, then port retail's ordered weapon, armor, magic, requirement, capacity, cooldown, use, and description branches into a dedicated formatter with DAT spell prose. Keep the remaining specialized display-name and preview gaps explicit in AP-110. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
d96ea2de98
commit
bc47bc4917
9 changed files with 1587 additions and 297 deletions
|
|
@ -61,6 +61,7 @@ public sealed class AppraisalUiControllerTests
|
|||
Assert.Equal("Atlan Weapon", Assert.Single(title.LinesProvider()).Text);
|
||||
UiText itemText = Assert.IsType<UiText>(
|
||||
layout.FindElement(AppraisalUiController.ItemTextId));
|
||||
Assert.Equal(VJustify.Top, itemText.VerticalJustify);
|
||||
string report = string.Join('\n', itemText.LinesProvider().Select(line => line.Text));
|
||||
Assert.Contains("Value: 1,250", report);
|
||||
Assert.Contains("Burden: 350", report);
|
||||
|
|
@ -237,6 +238,7 @@ public sealed class AppraisalUiControllerTests
|
|||
UiItemList list = Assert.Single(
|
||||
creaturePanel.Children.OfType<UiItemList>(),
|
||||
candidate => candidate.Top == host.Top);
|
||||
Assert.Equal(0f, background.Left);
|
||||
Assert.Equal(CreatureAppraisalLayeredList.TextInset, list.Left);
|
||||
Assert.True(host.ZOrder < viewport.ZOrder);
|
||||
Assert.True(viewport.ZOrder < list.ZOrder);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,262 @@
|
|||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Spells;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
public sealed class ItemAppraisalTextFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
public void WeaponAndMagic_AreProjectedInRetailOrderWithDatDescriptions()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000001u,
|
||||
Name = "Flaming Sword",
|
||||
Type = ItemType.MeleeWeapon,
|
||||
ValidLocations = EquipMask.MeleeWeapon,
|
||||
Value = 12_500,
|
||||
Burden = 450,
|
||||
Workmanship = 8.4f,
|
||||
};
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[353u] = 2;
|
||||
properties.Ints[204u] = 4;
|
||||
properties.Ints[106u] = 300;
|
||||
properties.Ints[107u] = 250;
|
||||
properties.Ints[108u] = 500;
|
||||
properties.Ints[117u] = 20;
|
||||
properties.Floats[29u] = 1.05d;
|
||||
properties.Strings[14u] = "Use this item to attack.";
|
||||
properties.Strings[16u] = "A finely balanced enchanted blade.";
|
||||
|
||||
SpellMetadata flame = Spell(101u, "Flame Bolt VI", "Hurls a bolt of flame.");
|
||||
SpellMetadata armor = Spell(102u, "Impenetrability VI", "Increases the target's armor.");
|
||||
AppraiseInfoParser.Parsed appraisal = Parsed(
|
||||
properties,
|
||||
spells: [101u, 0x8000_0066u],
|
||||
weapon: new AppraiseInfoParser.WeaponProfile(
|
||||
DamageType: 1u,
|
||||
WeaponTime: 30u,
|
||||
WeaponSkill: 44u,
|
||||
Damage: 40u,
|
||||
DamageVariance: 0.25d,
|
||||
DamageMod: 1d,
|
||||
WeaponLength: 1d,
|
||||
MaxVelocity: 0d,
|
||||
WeaponOffense: 1.15d,
|
||||
MaxVelocityEstimated: 0u));
|
||||
|
||||
string report = ItemAppraisalTextFormatter.Build(
|
||||
obj,
|
||||
appraisal,
|
||||
id => id switch
|
||||
{
|
||||
101u => flame,
|
||||
102u => armor,
|
||||
_ => null,
|
||||
});
|
||||
|
||||
Assert.Contains("Value: 12,500", report);
|
||||
Assert.Contains("Workmanship: Utterly flawless (8.4)", report);
|
||||
Assert.Contains("Skill: Heavy Weapons (Sword)", report);
|
||||
Assert.Contains("Damage: 30 - 40, Slashing", report);
|
||||
Assert.Contains("Elemental Damage Bonus: 4, Slashing.", report);
|
||||
Assert.Contains("Speed: Fast (30)", report);
|
||||
Assert.Contains("Bonus to Attack Skill: +15%.", report);
|
||||
Assert.Contains("Bonus to Melee Defense: +5%.", report);
|
||||
Assert.Contains("Spells: Flame Bolt VI, Impenetrability VI", report);
|
||||
Assert.Contains("Spellcraft: 300.", report);
|
||||
Assert.Contains("Mana: 250 / 500.", report);
|
||||
Assert.Contains("Mana Cost: 20.", report);
|
||||
Assert.Contains("Spell Descriptions:", report);
|
||||
Assert.Contains("Flame Bolt VI\nHurls a bolt of flame.", report);
|
||||
Assert.Contains("Enchantments:", report);
|
||||
Assert.Contains("Impenetrability VI\nIncreases the target's armor.", report);
|
||||
Assert.True(
|
||||
report.IndexOf("Skill:", StringComparison.Ordinal)
|
||||
< report.IndexOf("Spells:", StringComparison.Ordinal));
|
||||
Assert.True(
|
||||
report.IndexOf("Spells:", StringComparison.Ordinal)
|
||||
< report.IndexOf("Spell Descriptions:", StringComparison.Ordinal));
|
||||
Assert.EndsWith("A finely balanced enchanted blade.", report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArmorRequirementsAndUses_UseRetailLabelsAndResistanceBands()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000002u,
|
||||
Name = "Platemail Hauberk",
|
||||
Type = ItemType.Armor,
|
||||
ValidLocations = EquipMask.ChestArmor,
|
||||
Structure = 8,
|
||||
MaxStructure = 10,
|
||||
};
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[28u] = 200;
|
||||
properties.Ints[158u] = 1;
|
||||
properties.Ints[159u] = 6;
|
||||
properties.Ints[160u] = 250;
|
||||
properties.Ints[324u] = 4;
|
||||
properties.Ints[369u] = 50;
|
||||
properties.Ints[370u] = 3;
|
||||
properties.Bools[69u] = false;
|
||||
properties.Strings[25u] = "Tester";
|
||||
AppraiseInfoParser.Parsed appraisal = Parsed(
|
||||
properties,
|
||||
armor: new AppraiseInfoParser.ArmorProfile(
|
||||
SlashingProtection: 1.2f,
|
||||
PiercingProtection: 0.8f,
|
||||
BludgeoningProtection: 0.4f,
|
||||
ColdProtection: 0f,
|
||||
FireProtection: 1.6f,
|
||||
AcidProtection: 2f,
|
||||
NetherProtection: 1f,
|
||||
LightningProtection: 0.2f));
|
||||
|
||||
string report = ItemAppraisalTextFormatter.Build(
|
||||
obj,
|
||||
appraisal,
|
||||
_ => null);
|
||||
|
||||
Assert.Contains("Ratings: Dam +3", report);
|
||||
Assert.Contains("Armor Level: 200", report);
|
||||
Assert.Contains("Slashing: Above Average (240)", report);
|
||||
Assert.Contains("Piercing: Average (160)", report);
|
||||
Assert.Contains("Bludgeoning: Below Average (80)", report);
|
||||
Assert.Contains("Cold: None (0)", report);
|
||||
Assert.Contains("Fire: Excellent (320)", report);
|
||||
Assert.Contains("Acid: Unparalleled (400)", report);
|
||||
Assert.Contains("Electric: Poor (40)", report);
|
||||
Assert.Contains("Wield requires Viamontian", report);
|
||||
Assert.Contains("Wield requires Melee Defense 250", report);
|
||||
Assert.Contains("Use requires level 50.", report);
|
||||
Assert.Contains("Number of uses remaining: 8", report);
|
||||
Assert.Contains("Created by Tester.", report);
|
||||
Assert.Contains("This item cannot be sold.", report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Launcher_UsesDamageBonusModifierAndAmmunitionWording()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000003u,
|
||||
Name = "Longbow",
|
||||
Type = ItemType.MissileWeapon,
|
||||
ValidLocations = EquipMask.MissileWeapon,
|
||||
AmmoType = 1,
|
||||
};
|
||||
AppraiseInfoParser.Parsed appraisal = Parsed(
|
||||
new PropertyBundle(),
|
||||
weapon: new AppraiseInfoParser.WeaponProfile(
|
||||
DamageType: 2u,
|
||||
WeaponTime: 45u,
|
||||
WeaponSkill: 47u,
|
||||
Damage: 12u,
|
||||
DamageVariance: 0d,
|
||||
DamageMod: 1.13d,
|
||||
WeaponLength: 1d,
|
||||
MaxVelocity: 50d,
|
||||
WeaponOffense: 1d,
|
||||
MaxVelocityEstimated: 1u));
|
||||
|
||||
string report = ItemAppraisalTextFormatter.Build(
|
||||
obj,
|
||||
appraisal,
|
||||
_ => null);
|
||||
|
||||
Assert.Contains("Skill: Missile Weapons", report);
|
||||
Assert.Contains("Damage Bonus: 12", report);
|
||||
Assert.DoesNotContain("Damage Bonus: 12, Piercing", report);
|
||||
Assert.Contains("Damage Modifier: +13%.", report);
|
||||
Assert.Contains("Speed: Average (45)", report);
|
||||
Assert.Contains("Range: 85 yds. (based on STRENGTH 100)", report);
|
||||
Assert.Contains("Uses arrows as ammunition.", report);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpecialProperties_AreCombinedUsingRetailNames()
|
||||
{
|
||||
var obj = new ClientObject
|
||||
{
|
||||
ObjectId = 0x50000004u,
|
||||
Name = "Imbued Katar",
|
||||
Type = ItemType.MeleeWeapon,
|
||||
};
|
||||
var properties = new PropertyBundle();
|
||||
properties.Ints[279u] = 3;
|
||||
properties.Ints[292u] = 4;
|
||||
properties.Ints[179u] = 0x0000_0481;
|
||||
properties.Ints[114u] = 1;
|
||||
properties.Ints[33u] = 1;
|
||||
properties.Floats[167u] = 90d;
|
||||
properties.Bools[99u] = true;
|
||||
properties.Bools[100u] = true;
|
||||
|
||||
string report = ItemAppraisalTextFormatter.Build(
|
||||
obj,
|
||||
Parsed(properties),
|
||||
_ => null);
|
||||
|
||||
Assert.Contains("You can only carry 3 of these items.", report);
|
||||
Assert.Contains("Cooldown When Used: 1m 30s ", report);
|
||||
Assert.Contains("Cleave: 4 enemies in front arc.", report);
|
||||
Assert.Contains(
|
||||
"Properties: Critical Strike, Cold Rending, +1 Melee Defense, Attuned, Bonded, Ivoryable, Dyeable",
|
||||
report);
|
||||
Assert.Contains("This item cannot be further imbued.", report);
|
||||
}
|
||||
|
||||
private static AppraiseInfoParser.Parsed Parsed(
|
||||
PropertyBundle properties,
|
||||
uint[]? spells = null,
|
||||
AppraiseInfoParser.WeaponProfile? weapon = null,
|
||||
AppraiseInfoParser.ArmorProfile? armor = null)
|
||||
=> new(
|
||||
Guid: 0x50000001u,
|
||||
Flags: AppraiseInfoParser.IdentifyResponseFlags.IntStatsTable,
|
||||
Success: true,
|
||||
Properties: properties,
|
||||
SpellBook: spells ?? [],
|
||||
ArmorProfile: armor,
|
||||
CreatureProfile: null,
|
||||
WeaponProfile: weapon,
|
||||
HookProfile: null,
|
||||
ArmorLevels: null,
|
||||
ArmorEnchantments: null,
|
||||
WeaponEnchantments: null,
|
||||
ResistEnchantments: null);
|
||||
|
||||
private static SpellMetadata Spell(
|
||||
uint id,
|
||||
string name,
|
||||
string description)
|
||||
=> new(
|
||||
SpellId: id,
|
||||
Name: name,
|
||||
School: "War Magic",
|
||||
Family: id,
|
||||
IconId: 0u,
|
||||
SpellWords: string.Empty,
|
||||
Duration: 0f,
|
||||
ManaCost: 10,
|
||||
IsDebuff: false,
|
||||
IsFellowship: false,
|
||||
Description: description,
|
||||
SortKey: 0,
|
||||
Difficulty: 0,
|
||||
Flags: 0u,
|
||||
Generation: 6,
|
||||
IsFastWindup: false,
|
||||
IsOffensive: true,
|
||||
IsUntargeted: false,
|
||||
Speed: 0f,
|
||||
CasterEffect: 0u,
|
||||
TargetEffect: 0u,
|
||||
TargetMask: 0u,
|
||||
SpellType: 0);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue