using Theriapolis.Core.Data; using Theriapolis.Core.Rules.Character; using Theriapolis.Core.Rules.Combat; using Theriapolis.Core.Rules.Stats; using Theriapolis.Core.Util; using Xunit; namespace Theriapolis.Tests.Combat; public sealed class ReachAndCoverTests { private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory)); [Fact] public void EdgeToEdge_Adjacent_ReturnsZero() { var a = MakeMediumNpc(new Vec2(5, 5)); var b = MakeMediumNpc(new Vec2(6, 5)); Assert.Equal(0, ReachAndCover.EdgeToEdgeChebyshev(a, b)); } [Fact] public void EdgeToEdge_OneTileApart_ReturnsOne() { var a = MakeMediumNpc(new Vec2(5, 5)); var b = MakeMediumNpc(new Vec2(7, 5)); // 1 empty tile between Assert.Equal(1, ReachAndCover.EdgeToEdgeChebyshev(a, b)); } [Fact] public void EdgeToEdge_LargeAttacker_FootprintCountedCorrectly() { var large = MakeLargeNpc(new Vec2(0, 0)); // occupies (0..1, 0..1) var medium = MakeMediumNpc(new Vec2(3, 0)); // 1 empty tile between (large's right edge = 1, medium = 3) Assert.Equal(1, ReachAndCover.EdgeToEdgeChebyshev(large, medium)); } [Fact] public void IsInReach_MeleeAdjacent_True() { var a = MakeMediumNpc(new Vec2(0, 0)); var b = MakeMediumNpc(new Vec2(1, 0)); var attack = new AttackOption { Name = "Bite", Damage = new DamageRoll(1, 4, 0, DamageType.Piercing), ReachTiles = 1 }; Assert.True(ReachAndCover.IsInReach(a, b, attack)); } [Fact] public void IsInReach_MeleeOutOfReach_False() { var a = MakeMediumNpc(new Vec2(0, 0)); var b = MakeMediumNpc(new Vec2(3, 0)); var attack = new AttackOption { Name = "Bite", Damage = new DamageRoll(1, 4, 0, DamageType.Piercing), ReachTiles = 1 }; Assert.False(ReachAndCover.IsInReach(a, b, attack)); } [Fact] public void IsInReach_RangedShortRange_True() { var a = MakeMediumNpc(new Vec2(0, 0)); var b = MakeMediumNpc(new Vec2(8, 0)); var bow = new AttackOption { Name = "Bow", Damage = new DamageRoll(1, 6, 0, DamageType.Piercing), RangeShortTiles = 16, RangeLongTiles = 64 }; Assert.True(ReachAndCover.IsInReach(a, b, bow)); Assert.False(ReachAndCover.IsLongRange(a, b, bow)); } [Fact] public void IsInReach_RangedLongRange_TrueWithLongRangeFlag() { var a = MakeMediumNpc(new Vec2(0, 0)); var b = MakeMediumNpc(new Vec2(40, 0)); var bow = new AttackOption { Name = "Bow", Damage = new DamageRoll(1, 6, 0, DamageType.Piercing), RangeShortTiles = 16, RangeLongTiles = 64 }; Assert.True(ReachAndCover.IsInReach(a, b, bow)); Assert.True(ReachAndCover.IsLongRange(a, b, bow)); } [Fact] public void StepToward_MovesOneTileTowardGoal() { var step = ReachAndCover.StepToward(new Vec2(0, 0), new Vec2(5, 3)); Assert.Equal(1, step.X); Assert.Equal(1, step.Y); } [Fact] public void StepToward_AtGoal_ReturnsSamePosition() { var step = ReachAndCover.StepToward(new Vec2(5, 5), new Vec2(5, 5)); Assert.Equal(5, step.X); Assert.Equal(5, step.Y); } private Combatant MakeMediumNpc(Vec2 pos) => Combatant.FromNpcTemplate(_content.Npcs.Templates.First(t => t.Id == "wolf"), id: 1, pos); private Combatant MakeLargeNpc(Vec2 pos) => Combatant.FromNpcTemplate(_content.Npcs.Templates.First(t => t.Id == "bear_brown"), id: 2, pos); }