b451f83174
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
100 lines
3.6 KiB
C#
100 lines
3.6 KiB
C#
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);
|
|
}
|