66 lines
2 KiB
C#
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();
|
|
}
|
|
}
|