feat(items): port retail shared cooldown overlays

Preserve public shared-cooldown metadata, resolve the authoritative cooldown enchantment with retail expiry semantics, and project the exact ten DAT-authored radial steps through the shared retained item-slot architecture.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-23 10:41:16 +02:00
parent 19e8863f4e
commit 6b1ae4fb76
27 changed files with 875 additions and 20 deletions

View file

@ -194,6 +194,8 @@ public sealed class ClientObjectTable
/// <see cref="ClientObject.Effects"/>.</summary>
public const uint UiEffectsPropertyId = 18u;
public const uint CurrentWieldedLocationPropertyId = 10u;
public const uint SharedCooldownPropertyId = 280u;
public const uint CooldownDurationPropertyId = 167u;
public int ObjectCount => _objects.Count;
public int ContainerCount => _containers.Count;
@ -613,6 +615,7 @@ public sealed class ClientObjectTable
foreach (var kv in incoming.Strings) item.Properties.Strings[kv.Key] = kv.Value;
foreach (var kv in incoming.DataIds) item.Properties.DataIds[kv.Key] = kv.Value;
foreach (var kv in incoming.InstanceIds) item.Properties.InstanceIds[kv.Key] = kv.Value;
ApplyCooldownProperties(item, incoming);
ObjectUpdated?.Invoke(item);
return true;
}
@ -640,6 +643,7 @@ public sealed class ClientObjectTable
foreach (var kv in incoming.Strings) item.Properties.Strings[kv.Key] = kv.Value;
foreach (var kv in incoming.DataIds) item.Properties.DataIds[kv.Key] = kv.Value;
foreach (var kv in incoming.InstanceIds) item.Properties.InstanceIds[kv.Key] = kv.Value;
ApplyCooldownProperties(item, incoming);
if (!existed) ObjectAdded?.Invoke(item); else ObjectUpdated?.Invoke(item);
}
@ -656,6 +660,7 @@ public sealed class ClientObjectTable
ClientObjectPlacement previous = ClientObjectPlacement.From(item);
item.Properties.Ints[propertyId] = value;
if (propertyId == UiEffectsPropertyId) item.Effects = (uint)value;
if (propertyId == SharedCooldownPropertyId) item.CooldownId = (uint)value;
if (propertyId == CurrentWieldedLocationPropertyId)
item.CurrentlyEquippedLocation = (EquipMask)(uint)value;
if (propertyId == CurrentWieldedLocationPropertyId)
@ -664,6 +669,20 @@ public sealed class ClientObjectTable
return true;
}
private static void ApplyCooldownProperties(
ClientObject item,
PropertyBundle properties)
{
if (properties.Ints.TryGetValue(
SharedCooldownPropertyId,
out int cooldownId))
item.CooldownId = (uint)cooldownId;
if (properties.Floats.TryGetValue(
CooldownDurationPropertyId,
out double cooldownDuration))
item.CooldownDuration = cooldownDuration;
}
/// <summary>
/// Apply a single PropertyInt64 update to an object's bundle and fire
/// ObjectUpdated so bound widgets refresh. Int64 counterpart of
@ -738,6 +757,9 @@ public sealed class ClientObjectTable
if (d.CombatUse is { } combatUse) obj.CombatUse = combatUse;
if (d.AmmoType is { } ammoType) obj.AmmoType = ammoType;
if (d.SpellId is { } spellId) obj.SpellId = spellId;
if (d.CooldownId is { } cooldownId) obj.CooldownId = cooldownId;
if (d.CooldownDuration is { } cooldownDuration)
obj.CooldownDuration = cooldownDuration;
if (d.RadarBlipColor is { } radarBlipColor) obj.RadarBlipColor = radarBlipColor;
if (d.RadarBehavior is { } radarBehavior) obj.RadarBehavior = radarBehavior;
if (d.ItemsCapacity is { } ic) obj.ItemsCapacity = ic;