refactor(app): compose settings and developer tools

This commit is contained in:
Erik 2026-07-22 16:11:34 +02:00
parent 60a1698ce7
commit cd7b519f78
24 changed files with 2073 additions and 333 deletions

View file

@ -21,13 +21,14 @@ namespace AcDream.UI.Abstractions.Panels.Chat;
/// unchanged transcript does not require a queue snapshot each frame.
/// </para>
/// </summary>
public sealed class ChatVM
public sealed class ChatVM : IDisposable
{
/// <summary>Default number of tail entries rendered.</summary>
public const int DefaultDisplayLimit = 20;
private readonly ChatLog _log;
private readonly int _displayLimit;
private bool _disposed;
/// <summary>
/// Sender name of the most recent INCOMING Tell. Drives the
@ -104,6 +105,14 @@ public sealed class ChatVM
LastOutgoingTellTarget = entry.Sender;
}
public void Dispose()
{
if (_disposed)
return;
_log.EntryAppended -= OnEntryAppended;
_disposed = true;
}
/// <summary>
/// Append a client-side system line to the chat log. Used by
/// client-handled commands (/help, /clear, future) to surface

View file

@ -67,7 +67,7 @@ public readonly record struct ToastMessage(
/// debug panels appear.
/// </para>
/// </summary>
public sealed class DebugVM
public sealed class DebugVM : IDisposable
{
/// <summary>Maximum number of combat-event lines kept in the ring.</summary>
public const int MaxCombatEvents = 25;
@ -103,6 +103,8 @@ public sealed class DebugVM
private readonly Func<int> _getParticleCount;
private readonly Func<float> _getFps;
private readonly Func<float> _getFrameMs;
private readonly CombatState _combat;
private bool _disposed;
private readonly Queue<CombatEventLine> _combatEvents = new();
private readonly Queue<ToastMessage> _toasts = new();
@ -144,7 +146,7 @@ public sealed class DebugVM
Func<float> getFrameMs,
CombatState combat)
{
if (combat is null) throw new ArgumentNullException(nameof(combat));
_combat = combat ?? throw new ArgumentNullException(nameof(combat));
_getPlayerPosition = getPlayerPosition ?? throw new ArgumentNullException(nameof(getPlayerPosition));
_getPlayerHeadingDeg = getPlayerHeadingDeg ?? throw new ArgumentNullException(nameof(getPlayerHeadingDeg));
_getPlayerCellId = getPlayerCellId ?? throw new ArgumentNullException(nameof(getPlayerCellId));
@ -177,21 +179,12 @@ public sealed class DebugVM
// Self-subscribe to combat events. Each one becomes a typed entry
// in the ring; the panel renders them in TextColored. Replaces
// the old DebugOverlay.BindCombat side-channel.
combat.DamageTaken += d => Push(CombatEventKind.Error,
$"<< {d.AttackerName} hit you for {d.Damage}{(d.Critical ? " CRIT!" : "")}");
combat.DamageDealtAccepted += d => Push(CombatEventKind.Info,
$">> you hit {d.DefenderName} for {d.Damage}");
combat.EvadedIncoming += attacker => Push(CombatEventKind.Warn,
$"<< {attacker}'s attack missed you");
combat.MissedOutgoing += defender => Push(CombatEventKind.Info,
$">> your attack missed {defender}");
combat.AttackDone += (_, weenieError) =>
{
if (weenieError != 0)
Push(CombatEventKind.Error, $"!! attack failed (error 0x{weenieError:X})");
};
combat.KillLanded += (victim, _) =>
Push(CombatEventKind.Info, $"** you killed {victim}");
_combat.DamageTaken += OnDamageTaken;
_combat.DamageDealtAccepted += OnDamageDealt;
_combat.EvadedIncoming += OnEvadedIncoming;
_combat.MissedOutgoing += OnMissedOutgoing;
_combat.AttackDone += OnAttackDone;
_combat.KillLanded += OnKillLanded;
}
// ── Read-through value surfaces ───────────────────────────────────
@ -510,4 +503,46 @@ public sealed class DebugVM
while (_combatEvents.Count > MaxCombatEvents)
_combatEvents.Dequeue();
}
public void Dispose()
{
if (_disposed)
return;
_combat.DamageTaken -= OnDamageTaken;
_combat.DamageDealtAccepted -= OnDamageDealt;
_combat.EvadedIncoming -= OnEvadedIncoming;
_combat.MissedOutgoing -= OnMissedOutgoing;
_combat.AttackDone -= OnAttackDone;
_combat.KillLanded -= OnKillLanded;
_disposed = true;
}
private void OnDamageTaken(CombatState.DamageIncoming damage) =>
Push(
CombatEventKind.Error,
$"<< {damage.AttackerName} hit you for {damage.Damage}" +
(damage.Critical ? " CRIT!" : string.Empty));
private void OnDamageDealt(CombatState.DamageDealt damage) =>
Push(
CombatEventKind.Info,
$">> you hit {damage.DefenderName} for {damage.Damage}");
private void OnEvadedIncoming(string attacker) =>
Push(CombatEventKind.Warn, $"<< {attacker}'s attack missed you");
private void OnMissedOutgoing(string defender) =>
Push(CombatEventKind.Info, $">> your attack missed {defender}");
private void OnAttackDone(uint _, uint weenieError)
{
if (weenieError != 0)
Push(
CombatEventKind.Error,
$"!! attack failed (error 0x{weenieError:X})");
}
private void OnKillLanded(string victim, uint _) =>
Push(CombatEventKind.Info, $"** you killed {victim}");
}