refactor(net): own session wiring subscriptions

This commit is contained in:
Erik 2026-07-21 10:45:15 +02:00
parent 88fe1db37b
commit 7d452aa6a2
8 changed files with 577 additions and 61 deletions

View file

@ -19,7 +19,7 @@ public static class ObjectTableWiring
/// Subscribe <paramref name="table"/> to quality, inventory, and property
/// updates whose freshness is independent of the physics timestamp pack.
/// </summary>
public static void Wire(
public static IDisposable Wire(
WorldSession session,
ClientObjectTable table,
Func<uint>? playerGuid = null,
@ -27,6 +27,7 @@ public static class ObjectTableWiring
{
ArgumentNullException.ThrowIfNull(session);
ArgumentNullException.ThrowIfNull(table);
var subscriptions = new SubscriptionSet();
// Create/Delete/Pickup are deliberately not subscribed here. Their
// shared live-object timestamps must be accepted before either the
@ -35,8 +36,10 @@ public static class ObjectTableWiring
// B-Wire: apply EVERY PropertyInt update on a visible object (0x02CE), not just
// UiEffects — the server is the authority on object properties. UpdateIntProperty
// stores it in the bundle and still mirrors UiEffects → the typed Effects field.
session.ObjectIntPropertyUpdated += u =>
Action<WorldSession.ObjectIntPropertyUpdate> objectIntUpdated = u =>
table.UpdateIntProperty(u.Guid, u.Property, u.Value);
session.ObjectIntPropertyUpdated += objectIntUpdated;
subscriptions.Add(() => session.ObjectIntPropertyUpdated -= objectIntUpdated);
// B-Wire: PrivateUpdatePropertyInt (0x02CD) carries no guid — it targets the
// local player. Route it to the player object so live EncumbranceVal updates the
@ -44,26 +47,38 @@ public static class ObjectTableWiring
// call (which precedes any live 0x02CD), so UpdateIntProperty finds it. If it somehow
// hasn't yet, this no-ops (UpdateIntProperty returns false on an unknown guid) rather
// than creating a phantom — the next PD / CreateObject seeds it.
session.PlayerIntPropertyUpdated += u =>
Action<WorldSession.PlayerIntPropertyUpdate> playerIntUpdated = u =>
{
if (playerGuid is not null)
table.UpdateIntProperty(playerGuid(), u.Property, u.Value);
};
session.PlayerIntPropertyUpdated += playerIntUpdated;
subscriptions.Add(() => session.PlayerIntPropertyUpdated -= playerIntUpdated);
// Retail's qualities system owns one local-player value and notifies every
// registered panel. acdream currently exposes that value through two projections:
// ClientObjectTable (retained UI) and LocalPlayerState (Core consumers/fallback).
// Apply the authoritative 0x02CF update to both in this one wiring owner so they
// cannot drift after the login PlayerDescription snapshot.
session.PlayerInt64PropertyUpdated += u => ApplyPlayerInt64PropertyUpdate(
Action<WorldSession.PlayerInt64PropertyUpdate> playerInt64Updated = u =>
ApplyPlayerInt64PropertyUpdate(
table, localPlayer, playerGuid?.Invoke() ?? 0u, u);
session.PlayerInt64PropertyUpdated += playerInt64Updated;
subscriptions.Add(() => session.PlayerInt64PropertyUpdated -= playerInt64Updated);
// B-Wire: SetStackSize (0x0197) — update the object's stack count + value.
session.StackSizeUpdated += u => table.UpdateStackSize(u.Guid, u.StackSize, u.Value);
Action<WorldSession.StackSizeUpdate> stackSizeUpdated = u =>
table.UpdateStackSize(u.Guid, u.StackSize, u.Value);
session.StackSizeUpdated += stackSizeUpdated;
subscriptions.Add(() => session.StackSizeUpdated -= stackSizeUpdated);
// B-Wire: InventoryRemoveObject (0x0024) — the object left the player's inventory
// view; drop it from the table (retail ClientUISystem removes it from maintenance).
session.InventoryObjectRemoved += guid => table.Remove(guid);
Action<uint> inventoryObjectRemoved = guid => table.Remove(guid);
session.InventoryObjectRemoved += inventoryObjectRemoved;
subscriptions.Add(() => session.InventoryObjectRemoved -= inventoryObjectRemoved);
return subscriptions;
}
/// <summary>