Complete the retail cast-intent, target, component, enchantment, and busy-state paths; mount the DAT-authored spell bar, spellbook, component book, effects panels, and shared panel lifecycle; and add scoped input plus conformance coverage. Co-Authored-By: Codex <noreply@openai.com>
143 lines
6.3 KiB
C#
143 lines
6.3 KiB
C#
using AcDream.Core.Items;
|
|
using AcDream.Core.Player;
|
|
|
|
namespace AcDream.Core.Net;
|
|
|
|
/// <summary>
|
|
/// Wires WorldSession quality/property events into the client object table:
|
|
/// PublicUpdatePropertyInt (0x02CE) = live int apply, PrivateUpdatePropertyInt
|
|
/// (0x02CD) = player int (burden), PrivateUpdatePropertyInt64 (0x02CF) = player
|
|
/// 64-bit qualities (experience), SetStackSize (0x0197) = stack count,
|
|
/// InventoryRemoveObject (0x0024) = inventory-view removal.
|
|
/// CreateObject/DeleteObject application remains exposed as pure helpers so
|
|
/// the App live-entity owner can freshness-gate the shared projections first.
|
|
/// Retail: ACCObjectMaint::CreateObject / DeleteObject (the weenie_object_table side).
|
|
/// </summary>
|
|
public static class ObjectTableWiring
|
|
{
|
|
/// <summary>
|
|
/// Subscribe <paramref name="table"/> to quality, inventory, and property
|
|
/// updates whose freshness is independent of the physics timestamp pack.
|
|
/// </summary>
|
|
public static void Wire(
|
|
WorldSession session,
|
|
ClientObjectTable table,
|
|
Func<uint>? playerGuid = null,
|
|
LocalPlayerState? localPlayer = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(session);
|
|
ArgumentNullException.ThrowIfNull(table);
|
|
|
|
// Create/Delete/Pickup are deliberately not subscribed here. Their
|
|
// shared live-object timestamps must be accepted before either the
|
|
// retained weenie projection or the render projection mutates.
|
|
|
|
// 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 =>
|
|
table.UpdateIntProperty(u.Guid, u.Property, u.Value);
|
|
|
|
// B-Wire: PrivateUpdatePropertyInt (0x02CD) carries no guid — it targets the
|
|
// local player. Route it to the player object so live EncumbranceVal updates the
|
|
// burden bar. The player ClientObject is created at login by the PD UpsertProperties
|
|
// 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 =>
|
|
{
|
|
if (playerGuid is not null)
|
|
table.UpdateIntProperty(playerGuid(), u.Property, u.Value);
|
|
};
|
|
|
|
// 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(
|
|
table, localPlayer, playerGuid?.Invoke() ?? 0u, u);
|
|
|
|
// B-Wire: SetStackSize (0x0197) — update the object's stack count + value.
|
|
session.StackSizeUpdated += u => table.UpdateStackSize(u.Guid, u.StackSize, u.Value);
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies one CreateObject to the retained object projection after the
|
|
/// owning runtime accepts its physics generation. Internal for a
|
|
/// socket-free conformance test of the subscription body.
|
|
/// </summary>
|
|
public static void ApplyEntitySpawn(
|
|
ClientObjectTable table,
|
|
WorldSession.EntitySpawn spawn,
|
|
bool replaceGeneration = false)
|
|
{
|
|
WeenieData data = ToWeenieData(spawn);
|
|
if (replaceGeneration)
|
|
table.ReplaceGeneration(data, spawn.InstanceSequence);
|
|
else
|
|
table.Ingest(data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies a true DeleteObject only after the owning runtime accepts the
|
|
/// exact object incarnation. Pickup still removes only the 3-D projection.
|
|
/// </summary>
|
|
public static void ApplyEntityDelete(
|
|
ClientObjectTable table,
|
|
Messages.DeleteObject.Parsed delete)
|
|
{
|
|
table.RemoveLogicalGeneration(delete.Guid, delete.InstanceSequence);
|
|
}
|
|
|
|
/// <summary>Shared application step kept internal for byte-to-state conformance tests.</summary>
|
|
internal static void ApplyPlayerInt64PropertyUpdate(
|
|
ClientObjectTable table,
|
|
LocalPlayerState? localPlayer,
|
|
uint playerGuid,
|
|
WorldSession.PlayerInt64PropertyUpdate update)
|
|
{
|
|
if (playerGuid != 0u)
|
|
table.UpdateInt64Property(playerGuid, update.Property, update.Value);
|
|
localPlayer?.OnInt64PropertyUpdate(update.Property, update.Value);
|
|
}
|
|
|
|
/// <summary>Translate the wire spawn into the table's merge patch.</summary>
|
|
public static WeenieData ToWeenieData(WorldSession.EntitySpawn s) => new(
|
|
Guid: s.Guid,
|
|
Name: s.Name,
|
|
Type: s.ItemType is { } it ? (ItemType)it : (ItemType?)null,
|
|
WeenieClassId: s.WeenieClassId,
|
|
IconId: s.IconId,
|
|
IconOverlayId: s.IconOverlayId,
|
|
IconUnderlayId: s.IconUnderlayId,
|
|
Effects: s.UiEffects,
|
|
Value: s.Value,
|
|
StackSize: s.StackSize,
|
|
StackSizeMax: s.StackSizeMax,
|
|
Burden: s.Burden,
|
|
ContainerId: s.ContainerId,
|
|
WielderId: s.WielderId,
|
|
ValidLocations: s.ValidLocations,
|
|
CurrentWieldedLocation: s.CurrentWieldedLocation,
|
|
Priority: s.Priority,
|
|
ItemsCapacity: s.ItemsCapacity,
|
|
ContainersCapacity: s.ContainersCapacity,
|
|
Structure: s.Structure,
|
|
MaxStructure: s.MaxStructure,
|
|
Workmanship: s.Workmanship,
|
|
Useability: s.Useability,
|
|
TargetType: s.TargetType,
|
|
RadarBlipColor: s.RadarBlipColor,
|
|
RadarBehavior: s.RadarBehavior,
|
|
PublicWeenieBitfield: s.ObjectDescriptionFlags,
|
|
CombatUse: s.CombatUse,
|
|
PluralName: s.PluralName,
|
|
PetOwnerId: s.PetOwnerId,
|
|
AmmoType: s.AmmoType,
|
|
SpellId: s.SpellId);
|
|
}
|