feat(D.5.2): capture UiEffects from CreateObject weenie header
Previously, weenieFlags bit 0x80 (UiEffects) was read + discarded with `pos += 4`. Now it is captured into `uiEffects` and surfaced as `Parsed.UiEffects` — the sole wire path for the effect bitfield since PropertyInt.UiEffects (18) has no [AssessmentProperty] and never appears in appraise responses. Test builder gains `uint uiEffects = 0` param; write line updated to use it. Three new parse tests: UiEffects_Captured, UiEffectsThenIconOverlay (cursor-arithmetic regression), and NoUiEffectsBit_LeavesUiEffectsZero. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5a2af61508
commit
8df0b64676
2 changed files with 60 additions and 6 deletions
|
|
@ -156,7 +156,12 @@ public static class CreateObject
|
|||
// IconComposer.GetIcon already composites these layers in the correct
|
||||
// retail order (underlay / base / overlay+tint / effect).
|
||||
uint IconOverlayId = 0,
|
||||
uint IconUnderlayId = 0);
|
||||
uint IconUnderlayId = 0,
|
||||
// D.5.2 (2026-06-17): UiEffects bitfield (weenieFlags 0x80) — drives the icon's
|
||||
// effect recolor (Magical=0x1 … Nether=0x1000). The ONLY wire path for the effect
|
||||
// state (PropertyInt.UiEffects=18 has no [AssessmentProperty] → not in appraise).
|
||||
// Previously read + discarded at the UiEffects skip. 0 = no effect.
|
||||
uint UiEffects = 0);
|
||||
|
||||
/// <summary>
|
||||
/// The relevant subset of the server-sent <c>MovementData</c> /
|
||||
|
|
@ -571,7 +576,7 @@ public static class CreateObject
|
|||
// 0x00000010 Usable u32 KEPT
|
||||
// 0x00000020 UseRadius f32 KEPT
|
||||
// 0x00080000 TargetType u32 (skip)
|
||||
// 0x00000080 UiEffects u32 (skip)
|
||||
// 0x00000080 UiEffects u32 CAPTURE (D.5.2)
|
||||
// 0x00000200 CombatUse sbyte/1 byte (skip)
|
||||
// 0x00000400 Structure u16 (skip)
|
||||
// 0x00000800 MaxStructure u16 (skip)
|
||||
|
|
@ -605,6 +610,7 @@ public static class CreateObject
|
|||
float? useRadius = null;
|
||||
uint iconOverlayId = 0;
|
||||
uint iconUnderlayId = 0;
|
||||
uint uiEffects = 0;
|
||||
uint weenieFlags2 = 0;
|
||||
try
|
||||
{
|
||||
|
|
@ -666,10 +672,10 @@ public static class CreateObject
|
|||
if (body.Length - pos < 4) throw new FormatException("trunc TargetType");
|
||||
pos += 4;
|
||||
}
|
||||
if ((weenieFlags & 0x00000080u) != 0) // UiEffects u32
|
||||
if ((weenieFlags & 0x00000080u) != 0) // UiEffects u32 ← CAPTURE
|
||||
{
|
||||
if (body.Length - pos < 4) throw new FormatException("trunc UiEffects");
|
||||
pos += 4;
|
||||
uiEffects = ReadU32(body, ref pos);
|
||||
}
|
||||
if ((weenieFlags & 0x00000200u) != 0) // CombatUse sbyte (1 byte)
|
||||
{
|
||||
|
|
@ -808,7 +814,8 @@ public static class CreateObject
|
|||
friction, elasticity,
|
||||
IconId: iconId,
|
||||
Useability: useability, UseRadius: useRadius,
|
||||
IconOverlayId: iconOverlayId, IconUnderlayId: iconUnderlayId);
|
||||
IconOverlayId: iconOverlayId, IconUnderlayId: iconUnderlayId,
|
||||
UiEffects: uiEffects);
|
||||
|
||||
// Local helper: if we ran out of fields past PhysicsData, still
|
||||
// return the useful prefix (guid/position/setup/animParts/textures/palettes/scale/motion).
|
||||
|
|
|
|||
|
|
@ -332,6 +332,52 @@ public sealed class CreateObjectTests
|
|||
Assert.Equal(0x06004444u, parsed.Value.IconUnderlayId);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// D.5.2 (2026-06-17): UiEffects bitfield (weenieFlags bit 0x80) — captured
|
||||
// instead of skipped. Drives the icon's effect-overlay recolor.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void TryParse_UiEffects_Captured()
|
||||
{
|
||||
// weenieFlags 0x80 = UiEffects; value 0x1 = Magical.
|
||||
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
|
||||
guid: 0x50000010u, name: "MagicWand", itemType: (uint)ItemType.Caster,
|
||||
weenieFlags: 0x80u, uiEffects: 0x1u);
|
||||
|
||||
var parsed = CreateObject.TryParse(body);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(0x1u, parsed!.Value.UiEffects);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_UiEffectsThenIconOverlay_BothCaptured()
|
||||
{
|
||||
// Verifies the cursor still reaches IconOverlay after reading (not skipping) UiEffects.
|
||||
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
|
||||
guid: 0x50000011u, name: "GlowSword", itemType: (uint)ItemType.MeleeWeapon,
|
||||
weenieFlags: 0x80u | 0x40000000u, uiEffects: 0x4u, iconOverlayId: 0x1ABCu);
|
||||
|
||||
var parsed = CreateObject.TryParse(body);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(0x4u, parsed!.Value.UiEffects);
|
||||
Assert.Equal(0x06001ABCu, parsed.Value.IconOverlayId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_NoUiEffectsBit_LeavesUiEffectsZero()
|
||||
{
|
||||
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
|
||||
guid: 0x50000012u, name: "PlainRock", itemType: (uint)ItemType.Misc, weenieFlags: 0u);
|
||||
|
||||
var parsed = CreateObject.TryParse(body);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(0u, parsed!.Value.UiEffects);
|
||||
}
|
||||
|
||||
private static byte[] BuildMinimalCreateObjectWithWeenieHeader(
|
||||
uint guid,
|
||||
string name,
|
||||
|
|
@ -341,6 +387,7 @@ public sealed class CreateObjectTests
|
|||
uint weenieFlags = 0,
|
||||
uint weenieFlags2 = 0,
|
||||
uint iconId = 0,
|
||||
uint uiEffects = 0,
|
||||
uint? value = null,
|
||||
uint? useability = null,
|
||||
float? useRadius = null,
|
||||
|
|
@ -400,7 +447,7 @@ public sealed class CreateObjectTests
|
|||
bytes.AddRange(tmp.ToArray());
|
||||
}
|
||||
if ((weenieFlags & 0x00080000u) != 0) WriteU32(bytes, 0); // TargetType u32
|
||||
if ((weenieFlags & 0x00000080u) != 0) WriteU32(bytes, 0); // UiEffects u32
|
||||
if ((weenieFlags & 0x00000080u) != 0) WriteU32(bytes, uiEffects); // UiEffects u32
|
||||
if ((weenieFlags & 0x00000200u) != 0) bytes.Add(0); // CombatUse sbyte/1 byte
|
||||
if ((weenieFlags & 0x00000400u) != 0) WriteU16(bytes, structure ?? 0); // Structure u16
|
||||
if ((weenieFlags & 0x00000800u) != 0) WriteU16(bytes, maxStructure ?? 0); // MaxStructure u16
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue