191 lines
8.9 KiB
C#
191 lines
8.9 KiB
C#
using System.Linq;
|
|
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);
|
|
}
|
|
|
|
[Fact]
|
|
public void Buff_TicksByIntervalRepeatCountAndExpires()
|
|
{
|
|
BehaviorWorld world = TestWorld.CreateServerWorld();
|
|
|
|
Assert.True(world.ApplyBuff(2, "fighter", "slow_01", 1));
|
|
Assert.Equal(1, world.ActiveBuffCount(2));
|
|
|
|
world.Tick(1.0f);
|
|
world.Tick(1.0f);
|
|
world.Tick(1.0f);
|
|
world.Tick(0.1f);
|
|
var events = world.DrainEvents();
|
|
|
|
Assert.Contains(events, e => e.Kind == BehaviorEventKind.BuffAdded && e.TargetActorId == 2);
|
|
Assert.Equal(3, events.Count(e => e.Kind == BehaviorEventKind.BuffTicked && e.TargetActorId == 2));
|
|
Assert.Contains(events, e => e.Kind == BehaviorEventKind.BuffRemoved && e.TargetActorId == 2);
|
|
Assert.Equal(0, world.ActiveBuffCount(2));
|
|
}
|
|
|
|
[Fact]
|
|
public void ClientPresentation_BuffTickDoesNotEmitAuthoritativeAttributeRequests()
|
|
{
|
|
BehaviorWorld world = TestWorld.CreatePresentationWorld();
|
|
|
|
Assert.True(world.ApplyBuff(2, "fighter", "slow_01", 1));
|
|
world.DrainEvents();
|
|
world.Tick(1.0f);
|
|
var events = world.DrainEvents();
|
|
|
|
Assert.Contains(events, e => e.Kind == BehaviorEventKind.BuffTicked && e.TargetActorId == 2);
|
|
Assert.DoesNotContain(events, e => e.Kind == BehaviorEventKind.AttributeEffectRequested);
|
|
Assert.DoesNotContain(events, e => e.Kind == BehaviorEventKind.LogicEffectApplied);
|
|
}
|
|
|
|
[Fact]
|
|
public void BehaviorTimelineBuff_AppliesConfiguredBuff()
|
|
{
|
|
BehaviorConfigCatalog catalog = BehaviorConfigCatalog.LoadFromJson("fighter", TestBehaviorJson.SkillWithBuffTimeline, TestBehaviorJson.FlyObject, TestBehaviorJson.Buff);
|
|
var world = new BehaviorWorld(new BehaviorWorldOptions { Mode = BehaviorRunMode.ServerLogic });
|
|
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 });
|
|
|
|
world.TryStartBehavior(1, "slow_cast", BehaviorInput.FromTargetActor(2));
|
|
world.Tick(0.01f);
|
|
var events = world.DrainEvents();
|
|
|
|
Assert.Contains(events, e => e.Kind == BehaviorEventKind.BuffAdded && e.TargetActorId == 2 && e.BuffId == "slow_01");
|
|
Assert.Equal(1, world.ActiveBuffCount(2));
|
|
}
|
|
|
|
[Fact]
|
|
public void ProjectileTimelineEffect_FiresInClientPresentation()
|
|
{
|
|
BehaviorWorld world = TestWorld.CreatePresentationWorld();
|
|
|
|
world.TryStartBehavior(1, "slash", BehaviorInput.FromDirection(1, 0));
|
|
world.Tick(0.33f);
|
|
var events = world.DrainEvents();
|
|
|
|
Assert.Contains(events, e => e.Kind == BehaviorEventKind.TimelineEffect && e.FlyObjectId == "blade_wave" && e.Prefab == "Assets/Game/Art/Effect/blade_wave.prefab");
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|