acdream/src/AcDream.Core.Net/CombatStateWiring.cs

66 lines
2 KiB
C#

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