using System;
using System.Collections.Concurrent;
namespace AcDream.Core.Combat;
///
/// Client-side combat state — tracks per-entity health percent and
/// emits typed events when UpdateHealth / Victim / Attacker / Defender
/// notifications arrive. Powers target HP bars, damage floaters, combat
/// log panel.
///
///
/// Retail client-side combat responsibilities (r02 §7):
///
/// -
/// Maintain a cache of "last known health percent" per entity guid.
/// UpdateHealth (0x01C0) is sent when the player queries or the
/// server broadcasts a change.
///
/// -
/// Convert raw damage events into UI-ready notifications (colored
/// floating numbers, "Critical!" flashes, body-part locations).
///
/// -
/// Track self-centered notifications (you hit / you got hit / you
/// evaded / you were evaded) so the log panel can format them
/// correctly.
///
///
///
///
///
/// The server is authoritative: this class does NOT simulate damage
/// locally (exception: a predictive-ish "estimated damage" display for
/// the attack bar UI, which can use ).
///
///
public sealed class CombatState
{
private readonly ConcurrentDictionary _healthByGuid = new();
public CombatMode CurrentMode { get; private set; } = CombatMode.NonCombat;
/// Fires when a target's health percent changes (from UpdateHealth).
public event Action? HealthChanged;
/// You (the player) got hit for some damage.
public event Action? DamageTaken;
/// You (the player) dealt some damage.
public event Action? DamageDealtAccepted;
/// You (the player) evaded an incoming hit.
public event Action? EvadedIncoming;
/// The target evaded your hit.
public event Action? MissedOutgoing;
/// An attack commit completed (0x01A7). WeenieError = 0 on success.
public event Action? AttackDone;
/// The server accepted the attack and the power bar/animation can begin.
public event Action? AttackCommenced;
/// The locally requested or server-confirmed combat mode changed.
public event Action? CombatModeChanged;
///
/// Fires when the server confirms the player landed a killing blow
/// (GameEvent KillerNotification (0x01AD)). Event payload is
/// the victim's display name + their server GUID. Used by killfeed UI
/// (future panel) and any plugin scoring kill counts.
///
public event Action? KillLanded;
public readonly record struct DamageIncoming(
string AttackerName,
uint AttackerGuid,
uint DamageType,
uint Damage,
uint HitQuadrant,
bool Critical,
uint AttackType);
public readonly record struct DamageDealt(
string DefenderName,
uint DamageType,
uint Damage,
float DamagePercent);
/// Retrieve last known health percent for a guid, or 1.0 if unknown.
public float GetHealthPercent(uint guid) =>
_healthByGuid.TryGetValue(guid, out var pct) ? pct : 1f;
public int TrackedTargetCount => _healthByGuid.Count;
// ── Inbound handlers (wired from WorldSession.GameEvents) ────────────────
public void OnUpdateHealth(uint targetGuid, float healthPercent)
{
_healthByGuid[targetGuid] = healthPercent;
HealthChanged?.Invoke(targetGuid, healthPercent);
}
public void SetCombatMode(CombatMode mode)
{
if (CurrentMode == mode)
return;
CurrentMode = mode;
CombatModeChanged?.Invoke(mode);
}
public void OnVictimNotification(
string attackerName, uint attackerGuid, uint damageType, uint damage,
uint hitQuadrant, uint critical, uint attackType)
{
DamageTaken?.Invoke(new DamageIncoming(
attackerName, attackerGuid, damageType, damage, hitQuadrant,
critical != 0, attackType));
}
public void OnDefenderNotification(
string attackerName, uint attackerGuid, uint damageType, uint damage,
uint hitQuadrant, uint critical)
{
// DefenderNotification is semantically the same as VictimNotification
// from the defender's POV — the client log merges them.
DamageTaken?.Invoke(new DamageIncoming(
attackerName, attackerGuid, damageType, damage, hitQuadrant,
critical != 0, 0));
}
public void OnAttackerNotification(
string defenderName, uint damageType, uint damage, float damagePercent)
{
DamageDealtAccepted?.Invoke(new DamageDealt(
defenderName, damageType, damage, damagePercent));
}
public void OnEvasionAttackerNotification(string defenderName)
=> MissedOutgoing?.Invoke(defenderName);
///
/// Server confirmation that the player landed a killing blow on a
/// target. Wire source: GameEvent KillerNotification (0x01AD)
/// — the parser at GameEvents.ParseKillerNotification shipped
/// alongside victim/defender notifications but was never registered
/// for dispatch until 2026-04-25 (per ISSUES.md #10).
///
public void OnKillerNotification(string victimName, uint victimGuid)
=> KillLanded?.Invoke(victimName, victimGuid);
public void OnEvasionDefenderNotification(string attackerName)
=> EvadedIncoming?.Invoke(attackerName);
public void OnAttackDone(uint attackSequence, uint weenieError)
=> AttackDone?.Invoke(attackSequence, weenieError);
public void OnCombatCommenceAttack()
=> AttackCommenced?.Invoke();
public void Clear() => _healthByGuid.Clear();
}