namespace Theriapolis.Core.Rules.Combat; /// /// Outcome of a single call. Captures /// every dice value for log reconstruction and test assertions. /// public sealed record AttackResult { public required int AttackerId { get; init; } public required int TargetId { get; init; } public required string AttackName { get; init; } public required int D20Roll { get; init; } // the kept d20 (post advantage/disadvantage) public int? D20Other { get; init; } // the other d20 when adv/disadv was rolled public required int ToHitBonus { get; init; } public required int AttackTotal { get; init; } // D20Roll + ToHitBonus public required int TargetAc { get; init; } // includes cover public required bool Hit { get; init; } public required bool Crit { get; init; } public required int DamageRolled { get; init; } // 0 if missed public required int TargetHpAfter { get; init; } public required SituationFlags Situation { get; init; } public string FormatLog(string attackerName, string targetName) { if (!Hit) return $"{attackerName} → {targetName}: miss ({AttackName} {AttackTotal} vs AC {TargetAc})"; string critTag = Crit ? " [CRIT]" : ""; return $"{attackerName} → {targetName}: {DamageRolled} dmg ({AttackName} {AttackTotal} vs AC {TargetAc}){critTag} → HP {TargetHpAfter}"; } }