using AcDream.Core.Combat; namespace AcDream.Core.Net; /// /// Routes the local player's server-owned combat-mode quality into /// . Retail /// ClientCombatSystem::OnQualityChanged @ 0x0056C1B0 reads /// PropertyInt 40 and calls SetCombatMode on every change. /// public static class CombatStateWiring { public const uint CombatModePropertyId = 40u; public static IDisposable Wire( WorldSession session, CombatState combat, Func? accepting = null) { ArgumentNullException.ThrowIfNull(session); ArgumentNullException.ThrowIfNull(combat); Action handler = update => { if (accepting?.Invoke() == false) return; ApplyPlayerIntProperty(combat, update.Property, update.Value); }; session.PlayerIntPropertyUpdated += handler; return new EventSubscription(session, handler); } /// /// Pure event adapter exposed for conformance tests. Composite or unknown /// values are ignored: retail's quality contains one concrete mode. /// public static bool ApplyPlayerIntProperty( CombatState combat, uint property, int value) { ArgumentNullException.ThrowIfNull(combat); if (property != CombatModePropertyId) return false; CombatMode mode = (CombatMode)value; if (mode is not (CombatMode.NonCombat or CombatMode.Melee or CombatMode.Missile or CombatMode.Magic)) return false; combat.SetCombatMode(mode); return true; } private sealed class EventSubscription( WorldSession session, Action handler) : IDisposable { private Action? _unsubscribe = () => session.PlayerIntPropertyUpdated -= handler; public void Dispose() => Interlocked.Exchange(ref _unsubscribe, null)?.Invoke(); } }