using NUnit.Framework; using BaseGames.AI; using BaseGames.Enemies; namespace BaseGames.Tests.EditMode.AI { public class AiStateFragmentsTests { static AiRuntime Run(System.Action build, FakeAiContext ctx) { var b = new BrainBuilder(); b.Entry("S"); build(b); return new AiRuntime(b.Build(), ctx); } [Test] public void Locomotion_SetsModeOnEnter() { var ctx = new FakeAiContext(); Run(b => AiStateFragments.Locomotion(b, "S", LocomotionMode.Patrol), ctx); Assert.AreEqual(LocomotionMode.Patrol, ctx.L.CurrentMode); } [Test] public void Locomotion_FaceMode_FacesLastKnown() { var ctx = new FakeAiContext(); ctx.S.Last = new UnityEngine.Vector2(3f, 0f); Run(b => AiStateFragments.Locomotion(b, "S", LocomotionMode.Face), ctx); Assert.AreEqual(new UnityEngine.Vector2(3f, 0f), ctx.L.FacedAt); } [Test] public void Ability_RetriggersOnTick_WhenNotRunning() { var ctx = new FakeAiContext(); var rt = Run(b => AiStateFragments.Ability(b, "S", "atk"), ctx); Assert.AreEqual(1, ctx.C.Used.Count); ctx.C.Running = null; // 模拟被受击打断 rt.Tick(0.1f); Assert.AreEqual(2, ctx.C.Used.Count); // 重新触发 } [Test] public void AbilityOnce_DoesNotRetrigger() { var ctx = new FakeAiContext(); var rt = Run(b => AiStateFragments.AbilityOnce(b, "S", "death"), ctx); Assert.AreEqual(1, ctx.C.Used.Count); ctx.C.Running = null; // 演出播完 rt.Tick(0.1f); rt.Tick(0.1f); Assert.AreEqual(1, ctx.C.Used.Count); // 不重播 } [Test] public void LostAllZones_TrueOnlyWhenBothZonesEmpty() { var ctx = new FakeAiContext(); Assert.IsTrue(AiStateFragments.LostAllZones(ctx)); ctx.S.Chase = true; Assert.IsFalse(AiStateFragments.LostAllZones(ctx)); ctx.S.Chase = false; ctx.S.Vision = true; Assert.IsFalse(AiStateFragments.LostAllZones(ctx)); } [Test] public void Locomotion_StopsOnExit() { var ctx = new FakeAiContext(); var b = new BrainBuilder(); b.Entry("S"); AiStateFragments.Locomotion(b, "S", LocomotionMode.Patrol) .To("Out").When(x => x.Sensor.InChaseZone(), "leave"); AiStateFragments.Terminal(b, "Out"); var rt = new AiRuntime(b.Build(), ctx); Assert.AreEqual(LocomotionMode.Patrol, ctx.L.CurrentMode); ctx.S.Chase = true; rt.Tick(0.1f); Assert.AreEqual("Out", rt.CurrentStateName); CollectionAssert.Contains(ctx.L.Calls, "Stop"); // 离开时收尾 } [Test] public void Ability_InterruptsOnExit() { var ctx = new FakeAiContext(); var b = new BrainBuilder(); b.Entry("S"); AiStateFragments.Ability(b, "S", "atk") .To("Out").When(x => x.Sensor.InChaseZone(), "leave"); AiStateFragments.Terminal(b, "Out"); var rt = new AiRuntime(b.Build(), ctx); Assert.AreEqual("atk", ctx.C.Running); ctx.S.Chase = true; rt.Tick(0.1f); Assert.AreEqual("Out", rt.CurrentStateName); Assert.IsNull(ctx.C.Running); // InterruptAbilities() 已清空 } [Test] public void Ability_Throws_WhenAbilityIdEmpty() { var b = new BrainBuilder(); Assert.Throws(() => AiStateFragments.Ability(b, "S", "")); } [Test] public void AbilityOnce_Throws_WhenAbilityIdNull() { var b = new BrainBuilder(); Assert.Throws(() => AiStateFragments.AbilityOnce(b, "S", null)); } } }