## Summary Consolidates the duplicated inline AOS attribute → `ObjectPropertyList` emission that each item base copy-pastes into per-family `GetProperties(IPropertyList)` methods, mirroring the existing `AosSkillBonuses.GetProperties` precedent. ### Per-family `GetProperties(IPropertyList)` - **`AosAttributes`** — the 24 common attributes in canonical cliloc-ascending order, with optional `damageBonus` / `hitChanceBonus` / `luckBonus` params so item-computed bonuses (e.g. `GetDamageBonus()`) stay out of the family type. - **`AosWeaponAttributes`** — `UseBestSkill`, the `Hit*` block (1060416–1060430), `MageWeapon` (`30 - prop`), `SelfRepair`. - **`AosArmorAttributes`** — `MageArmor`, `SelfRepair` (the always-direct members; `LowerStatReq`/`DurabilityBonus` stay inline since they're item-computed in armor but container-direct in clothing). ### Rewired all 6 `AosAttributes`-emitting item bases `BaseJewel`, `BaseArmor`, `BaseClothing`, `BaseWeapon`, `BaseTalisman`, `Spellbook` now call the family methods instead of inlining the chain. Net: large dedup in `BaseWeapon`/`BaseArmor`/`BaseClothing`/`BaseTalisman`/`Spellbook`. ## Behavior change: tooltip line **order** (set preserved) This is **not** a pure no-op refactor, and that's unavoidable. Today the families are emitted **interleaved in cliloc order**, and the relative order differs per item class — e.g. `BonusDex` (1060409) is emitted early in `BaseArmor` but **after** the `Hit*` block in `BaseWeapon`. No single emission order reproduces every class byte-for-byte, so consolidating into contiguous per-family blocks necessarily **de-interleaves**: lines regroup **specific → general** (family-specific, then common `AosAttributes`). - The **set** of emitted `(cliloc, argument)` lines per item is preserved **exactly** — nothing dropped, added, or value-changed. - Only the **order** of lines within a tooltip changes for `BaseArmor` / `BaseWeapon` / `BaseClothing`. `BaseJewel` / `BaseTalisman` / `Spellbook` were already canonical, so those are byte-identical. ## Tests - **Golden set-invariance tests** per item base (`BaseArmor/Clothing/Jewel/Weapon/Talisman/Spellbook PropertiesTests`) — each was written to pass against current `main` **before** the rewire (locking the emitted-line set), then confirmed still passing after, proving no line is lost/added/changed. - Family-level unit tests for each `GetProperties` (canonical order, computed-bonus folding, the `AosArmorAttributes` exclusions). - `dotnet build` clean; full `UOContent.Tests` green. (Pre-existing `AccountPacket`/`GumpPacket`/`MobilePacket`/`ClientEnumerator` golden-test failures reproduce on unmodified `main` and are unrelated to this change.)
74 lines
2.2 KiB
C#
74 lines
2.2 KiB
C#
using System;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using Server;
|
|
using Server.Items;
|
|
using Xunit;
|
|
|
|
namespace UOContent.Tests;
|
|
|
|
[Collection("Sequential UOContent Tests")]
|
|
public class AosWeaponAttributesPropertiesTests
|
|
{
|
|
private static Dictionary<int, string> Decode(ObjectPropertyList opl)
|
|
{
|
|
opl.Terminate();
|
|
var buffer = opl.Buffer;
|
|
var map = new Dictionary<int, string>();
|
|
var pos = 15;
|
|
while (true)
|
|
{
|
|
var cliloc = BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(pos));
|
|
pos += 4;
|
|
if (cliloc == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
var byteLen = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(pos));
|
|
pos += 2;
|
|
map[cliloc] = Encoding.Unicode.GetString(buffer, pos, byteLen);
|
|
pos += byteLen;
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
[Fact]
|
|
public void EmitsHitEffectsUseBestSkillMageWeaponSelfRepair()
|
|
{
|
|
var attrs = new AosWeaponAttributes(null)
|
|
{
|
|
UseBestSkill = 1,
|
|
HitFireball = 12,
|
|
HitLeechHits = 20,
|
|
MageWeapon = 25,
|
|
SelfRepair = 3
|
|
};
|
|
|
|
var opl = new ObjectPropertyList(null);
|
|
attrs.GetProperties(opl);
|
|
var map = Decode(opl);
|
|
|
|
Assert.Equal("", map[1060400]); // UseBestSkill (no-arg)
|
|
Assert.Equal("12", map[1060420]); // HitFireball
|
|
Assert.Equal("20", map[1060422]); // HitLeechHits
|
|
Assert.Equal("5", map[1060438]); // MageWeapon => 30 - 25
|
|
Assert.Equal("3", map[1060450]); // SelfRepair
|
|
Assert.False(map.ContainsKey(1060435)); // LowerStatReq not emitted without the param
|
|
}
|
|
|
|
[Fact]
|
|
public void EmitsLowerStatReqWhenPassed()
|
|
{
|
|
var attrs = new AosWeaponAttributes(null) { MageWeapon = 25 };
|
|
|
|
var opl = new ObjectPropertyList(null);
|
|
attrs.GetProperties(opl, lowerStatReq: 40); // computed value passed by the weapon
|
|
var map = Decode(opl);
|
|
|
|
Assert.Equal("40", map[1060435]); // lower requirements, in cliloc order before MageWeapon
|
|
Assert.Equal("5", map[1060438]); // MageWeapon still emitted
|
|
}
|
|
}
|