using Xunit; using XWorld.Framework.ActorBehavior; namespace XWorld.Framework.Tests.ActorBehavior { public class BehaviorWorldTests { [Fact] public void TryStartBehavior_AllowsOnlyOneActiveBehavior() { BehaviorWorld world = TestWorld.CreateServerWorld(); Assert.True(world.TryStartBehavior(1, "slash", BehaviorInput.FromDirection(1, 0)).Success); BehaviorStartResult second = world.TryStartBehavior(1, "slash", BehaviorInput.FromDirection(1, 0)); Assert.False(second.Success); Assert.True(world.HasActiveBehavior(1)); } [Fact] public void StopBehavior_AllowsInterruptibleBehaviorToSwitch() { BehaviorWorld world = TestWorld.CreateServerWorld(); world.TryStartBehavior(1, "slash", BehaviorInput.FromDirection(1, 0)); Assert.True(world.StopBehavior(1, BehaviorStopReason.Stunned)); Assert.True(world.TryStartBehavior(1, "slash", BehaviorInput.FromDirection(1, 0)).Success); Assert.Contains(world.DrainEvents(), e => e.Kind == BehaviorEventKind.BehaviorStopped); } [Fact] public void Tick_TriggersTimelineEventsAtConfiguredTimes() { BehaviorWorld world = TestWorld.CreatePresentationWorld(); world.TryStartBehavior(1, "slash", BehaviorInput.FromDirection(1, 0)); world.Tick(0.26f); var events = world.DrainEvents(); Assert.Contains(events, e => e.Kind == BehaviorEventKind.TimelineAnimation && e.Animation == "slash_01"); Assert.Contains(events, e => e.Kind == BehaviorEventKind.TimelineEffect && e.Prefab == "Assets/Game/Art/Effect/slash.prefab"); } [Fact] public void ServerLogic_SkipsPresentationEventsAndAppliesLogic() { BehaviorWorld world = TestWorld.CreateServerWorld(); world.TryStartBehavior(1, "slash", BehaviorInput.FromDirection(1, 0)); world.Tick(0.26f); var events = world.DrainEvents(); Assert.DoesNotContain(events, e => e.Kind == BehaviorEventKind.TimelineAnimation); Assert.DoesNotContain(events, e => e.Kind == BehaviorEventKind.TimelineEffect); Assert.Contains(events, e => e.Kind == BehaviorEventKind.AttributeEffectRequested && e.TargetActorId == 2 && e.Attribute == "hp"); } [Fact] public void Projectile_SpawnsMovesHitsAndExpiresAtHitLimit() { BehaviorWorld world = TestWorld.CreateServerWorld(); world.TryStartBehavior(1, "slash", BehaviorInput.FromDirection(1, 0)); world.Tick(0.33f); Assert.True(world.ActiveProjectileCount > 0); world.Tick(0.1f); var events = world.DrainEvents(); Assert.Contains(events, e => e.Kind == BehaviorEventKind.ProjectileSpawned); Assert.Contains(events, e => e.Kind == BehaviorEventKind.ProjectileMoved); Assert.Contains(events, e => e.Kind == BehaviorEventKind.ProjectileHit && e.TargetActorId == 2); Assert.Equal(0, world.ActiveProjectileCount); } [Theory] [InlineData("Line")] [InlineData("Parabola")] [InlineData("Homing")] public void ProjectileMovementModes_UpdatePosition(string movementKind) { string fly = TestBehaviorJson.FlyObject.Replace(@"""kind"": ""Line""", @"""kind"": """ + movementKind + @""""); BehaviorConfigCatalog catalog = BehaviorConfigCatalog.LoadFromJson("fighter", TestBehaviorJson.Skill, fly, TestBehaviorJson.Buff); var world = new BehaviorWorld(new BehaviorWorldOptions { Mode = BehaviorRunMode.ClientPresentation }); world.RegisterCatalog(catalog); world.AddActor(new BehaviorActorState { ActorId = 1, CatalogType = "fighter", TeamId = 1, Position = new BehaviorVector3(0, 0, 0), Forward = new BehaviorVector3(1, 0, 0), Radius = 0.3f, Alive = true }); world.AddActor(new BehaviorActorState { ActorId = 2, CatalogType = "fighter", TeamId = 2, Position = new BehaviorVector3(4, 0, 0), Forward = new BehaviorVector3(-1, 0, 0), Radius = 0.3f, Alive = true }); world.TryStartBehavior(1, "slash", BehaviorInput.FromTargetActor(2)); world.Tick(0.33f); world.DrainEvents(); world.Tick(0.1f); Assert.Contains(world.DrainEvents(), e => e.Kind == BehaviorEventKind.ProjectileMoved && e.Position.X > 0); } } internal static class TestWorld { public static BehaviorWorld CreateServerWorld() { return Create(BehaviorRunMode.ServerLogic); } public static BehaviorWorld CreatePresentationWorld() { return Create(BehaviorRunMode.ClientPresentation); } private static BehaviorWorld Create(BehaviorRunMode mode) { BehaviorConfigCatalog catalog = BehaviorConfigCatalog.LoadFromJson( "fighter", TestBehaviorJson.Skill, TestBehaviorJson.FlyObject, TestBehaviorJson.Buff); var world = new BehaviorWorld(new BehaviorWorldOptions { Mode = mode }); world.RegisterCatalog(catalog); world.AddActor(new BehaviorActorState { ActorId = 1, CatalogType = "fighter", TeamId = 1, Position = new BehaviorVector3(0, 0, 0), Forward = new BehaviorVector3(1, 0, 0), Radius = 0.3f, Alive = true }); world.AddActor(new BehaviorActorState { ActorId = 2, CatalogType = "fighter", TeamId = 2, Position = new BehaviorVector3(1, 0, 0), Forward = new BehaviorVector3(-1, 0, 0), Radius = 0.3f, Alive = true }); return world; } } }