feat(net): D.2b-B B-Wire — PlayerDescription delivers player properties to ClientObject
Add optional Func<uint>? playerGuid parameter (last in WireAll signature so all existing callers compile unchanged). When provided, the PD handler calls items.UpsertProperties(playerGuid(), p.Value.Properties) immediately after the null-guard, landing EncumbranceVal (PropertyInt 5) and other player stats into the player ClientObject. Upsert (create-if-absent) handles PD arriving before the player's CreateObject. Retires AP-48/AP-49 divergence rows (wired in Task 16). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
de8baa1aa1
commit
d97d84d7ca
2 changed files with 50 additions and 1 deletions
|
|
@ -65,7 +65,10 @@ public static class GameEventWiring
|
||||||
// D.5.1 Task 4: persists Shortcuts from each PlayerDescription so the
|
// D.5.1 Task 4: persists Shortcuts from each PlayerDescription so the
|
||||||
// toolbar can populate itself at login without keeping a parser reference.
|
// toolbar can populate itself at login without keeping a parser reference.
|
||||||
// Optional so all existing callers and tests compile unchanged.
|
// Optional so all existing callers and tests compile unchanged.
|
||||||
Action<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>>? onShortcuts = null)
|
Action<IReadOnlyList<PlayerDescriptionParser.ShortcutEntry>>? onShortcuts = null,
|
||||||
|
// B-Wire: the local player's server guid. When provided, the PD handler upserts
|
||||||
|
// the player's own PropertyBundle (EncumbranceVal etc.) into the player ClientObject.
|
||||||
|
Func<uint>? playerGuid = null)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(dispatcher);
|
ArgumentNullException.ThrowIfNull(dispatcher);
|
||||||
ArgumentNullException.ThrowIfNull(items);
|
ArgumentNullException.ThrowIfNull(items);
|
||||||
|
|
@ -289,6 +292,13 @@ public static class GameEventWiring
|
||||||
Console.WriteLine($"vitals: PlayerDescription body.len={e.Payload.Length} parsed={(p is null ? "NULL" : $"vec={p.Value.VectorFlags} attrs={p.Value.Attributes.Count} spells={p.Value.Spells.Count}")}");
|
Console.WriteLine($"vitals: PlayerDescription body.len={e.Payload.Length} parsed={(p is null ? "NULL" : $"vec={p.Value.VectorFlags} attrs={p.Value.Attributes.Count} spells={p.Value.Spells.Count}")}");
|
||||||
if (p is null) return;
|
if (p is null) return;
|
||||||
|
|
||||||
|
// B-Wire: deliver the player's OWN properties to the player ClientObject.
|
||||||
|
// (PD's "membership manifest" rule is about ITEMS, whose data comes from
|
||||||
|
// CreateObject; the player's own stats legitimately come from PD.) Upsert
|
||||||
|
// because PD can arrive before the player's CreateObject. Retires AP-48/AP-49.
|
||||||
|
if (playerGuid is not null)
|
||||||
|
items.UpsertProperties(playerGuid(), p.Value.Properties);
|
||||||
|
|
||||||
// K-fix13 (2026-04-26): build attrId → current map while
|
// K-fix13 (2026-04-26): build attrId → current map while
|
||||||
// iterating attributes so the skill-formula resolver below
|
// iterating attributes so the skill-formula resolver below
|
||||||
// can apply (attr1.current * mult1 + attr2.current * mult2)
|
// can apply (attr1.current * mult1 + attr2.current * mult2)
|
||||||
|
|
|
||||||
|
|
@ -496,4 +496,43 @@ public sealed class GameEventWiringTests
|
||||||
Assert.Equal(0x5001u, got![0].ObjectGuid);
|
Assert.Equal(0x5001u, got![0].ObjectGuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WireAll_PlayerDescription_UpsertsPlayerPropertiesIntoClientObject()
|
||||||
|
{
|
||||||
|
// PD with a PropertyInt32 table carrying EncumbranceVal(5)=1500. The handler
|
||||||
|
// must land it in the player ClientObject so the burden bar reads the wire value.
|
||||||
|
const uint playerGuid = 0x50000001u;
|
||||||
|
var dispatcher = new GameEventDispatcher();
|
||||||
|
var items = new ClientObjectTable();
|
||||||
|
GameEventWiring.WireAll(dispatcher, items, new CombatState(), new Spellbook(),
|
||||||
|
new ChatLog(), playerGuid: () => playerGuid);
|
||||||
|
|
||||||
|
var sb = new MemoryStream();
|
||||||
|
using var w = new BinaryWriter(sb);
|
||||||
|
w.Write(0x00000001u); // propertyFlags = PropertyInt32
|
||||||
|
w.Write(0x52u); // weenieType
|
||||||
|
// int table: u16 count, u16 buckets, then key/val pairs
|
||||||
|
w.Write((ushort)1); // count
|
||||||
|
w.Write((ushort)8); // buckets (ignored)
|
||||||
|
w.Write(5u); // key = EncumbranceVal
|
||||||
|
w.Write(1500u); // val
|
||||||
|
// vector + has_health (no vector blocks)
|
||||||
|
w.Write(0u); // vectorFlags = None
|
||||||
|
w.Write(0u); // has_health
|
||||||
|
// strict trailer
|
||||||
|
w.Write(0u); // option_flags = None
|
||||||
|
w.Write(0u); // options1
|
||||||
|
w.Write(0u); // legacy hotbar count
|
||||||
|
w.Write(0u); // spellbook_filters
|
||||||
|
w.Write(0u); // inventory count
|
||||||
|
w.Write(0u); // equipped count
|
||||||
|
|
||||||
|
var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.PlayerDescription, sb.ToArray()));
|
||||||
|
dispatcher.Dispatch(env!.Value);
|
||||||
|
|
||||||
|
var player = items.Get(playerGuid);
|
||||||
|
Assert.NotNull(player);
|
||||||
|
Assert.Equal(1500, player!.Properties.Ints[5]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue