using Theriapolis.Core.Rules.Combat; using Theriapolis.Core.Rules.Stats; using Theriapolis.Core.Util; using Xunit; namespace Theriapolis.Tests.Combat; public sealed class DamageRollTests { [Theory] [InlineData("1d6", 1, 6, 0)] [InlineData("2d8+2", 2, 8, 2)] [InlineData("1d4-1", 1, 4, -1)] [InlineData("3d6", 3, 6, 0)] [InlineData("d8", 1, 8, 0)] [InlineData(" 1 d 6 + 1", 1, 6, 1)] public void Parse_ProducesExpectedShape(string expr, int n, int sides, int mod) { var d = DamageRoll.Parse(expr, DamageType.Slashing); Assert.Equal(n, d.DiceCount); Assert.Equal(sides, d.DiceSides); Assert.Equal(mod, d.FlatMod); } [Fact] public void Parse_PureFlatNumber_HasNoDice() { var d = DamageRoll.Parse("5", DamageType.Bludgeoning); Assert.Equal(0, d.DiceCount); Assert.Equal(0, d.DiceSides); Assert.Equal(5, d.FlatMod); } [Fact] public void Parse_BadExpressionThrows() { Assert.Throws(() => DamageRoll.Parse("1d", DamageType.Slashing)); Assert.Throws(() => DamageRoll.Parse("", DamageType.Slashing)); } [Fact] public void Roll_Range_StaysWithinMinAndMax() { var rng = new SeededRng(0xCAFEUL); var d = DamageRoll.Parse("2d6+3", DamageType.Slashing); for (int i = 0; i < 1000; i++) { int v = d.Roll(sides => (int)(rng.NextUInt64() % (ulong)sides) + 1); Assert.InRange(v, d.Min(), d.Max()); } } [Fact] public void Roll_Crit_DoublesDiceButNotFlatMod() { var roller = new FixedRoller(new[] { 1 }); // every die rolls 1 var d = DamageRoll.Parse("2d6+3", DamageType.Slashing); // Normal: 2 dice ⇒ 2*1 + 3 = 5 Assert.Equal(5, d.Roll(roller.Next)); // Crit: 4 dice ⇒ 4*1 + 3 = 7 (NOT 4*1 + 6 = 10 — flat mod doesn't double) roller.Reset(); Assert.Equal(7, d.Roll(roller.Next, isCrit: true)); } [Fact] public void Roll_NeverNegative() { var d = DamageRoll.Parse("1d4-10", DamageType.Slashing); for (int i = 0; i < 50; i++) { // Force the die to roll 1 (the worst case): result would be 1-10 = -9, clamped to 0. int v = d.Roll(_ => 1); Assert.True(v >= 0); } } private sealed class FixedRoller { private readonly int[] _values; private int _idx; public FixedRoller(int[] values) { _values = values; } public int Next(int _) { int v = _values[_idx % _values.Length]; _idx++; return v; } public void Reset() => _idx = 0; } }