fix: harden actor behavior authority logic
This commit is contained in:
@@ -34,6 +34,17 @@ namespace XWorld.Framework.Tests.ActorBehavior
|
||||
Assert.Contains("type", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadFromJson_RejectsMismatchedVersion()
|
||||
{
|
||||
string mismatchedFlyObject = TestBehaviorJson.FlyObject.Replace("\"version\": 1", "\"version\": 2");
|
||||
|
||||
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
BehaviorConfigCatalog.LoadFromJson("fighter", TestBehaviorJson.Skill, mismatchedFlyObject, TestBehaviorJson.Buff));
|
||||
|
||||
Assert.Contains("version", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadFromJson_RejectsMissingReferences()
|
||||
{
|
||||
@@ -75,6 +86,24 @@ namespace XWorld.Framework.Tests.ActorBehavior
|
||||
Assert.Contains("width", lineEx.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadFromJson_RejectsTimelineEntriesMissingRequiredFields()
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
BehaviorConfigCatalog.LoadFromJson("fighter", TestBehaviorJson.SkillMissingAnimationName, TestBehaviorJson.FlyObject, TestBehaviorJson.Buff));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
BehaviorConfigCatalog.LoadFromJson("fighter", TestBehaviorJson.SkillMissingEffectPrefab, TestBehaviorJson.FlyObject, TestBehaviorJson.Buff));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadFromJson_RejectsLogicOpsMissingRequiredFields()
|
||||
{
|
||||
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
BehaviorConfigCatalog.LoadFromJson("fighter", TestBehaviorJson.SkillMissingAttributeName, TestBehaviorJson.FlyObject, TestBehaviorJson.Buff));
|
||||
|
||||
Assert.Contains("attribute", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExampleConfigFiles_LoadFromRepository()
|
||||
{
|
||||
@@ -230,6 +259,51 @@ namespace XWorld.Framework.Tests.ActorBehavior
|
||||
}]
|
||||
}";
|
||||
|
||||
public const string SkillMissingAnimationName = @"{
|
||||
""type"": ""fighter"",
|
||||
""version"": 1,
|
||||
""behaviors"": [{
|
||||
""id"": ""bad_animation"",
|
||||
""duration"": 0.2,
|
||||
""timeline"": [
|
||||
{ ""time"": 0.0, ""kind"": ""Animation"" }
|
||||
],
|
||||
""logicEffects"": []
|
||||
}]
|
||||
}";
|
||||
|
||||
public const string SkillMissingEffectPrefab = @"{
|
||||
""type"": ""fighter"",
|
||||
""version"": 1,
|
||||
""behaviors"": [{
|
||||
""id"": ""bad_effect"",
|
||||
""duration"": 0.2,
|
||||
""timeline"": [
|
||||
{ ""time"": 0.0, ""kind"": ""Effect"" }
|
||||
],
|
||||
""logicEffects"": []
|
||||
}]
|
||||
}";
|
||||
|
||||
public const string SkillMissingAttributeName = @"{
|
||||
""type"": ""fighter"",
|
||||
""version"": 1,
|
||||
""behaviors"": [{
|
||||
""id"": ""bad_attribute"",
|
||||
""duration"": 0.2,
|
||||
""timeline"": [
|
||||
{ ""time"": 0.0, ""kind"": ""LogicEffect"", ""effectId"": ""bad_hit"" }
|
||||
],
|
||||
""logicEffects"": [{
|
||||
""id"": ""bad_hit"",
|
||||
""shape"": ""Sphere"",
|
||||
""radius"": 1.0,
|
||||
""target"": ""Enemy"",
|
||||
""effects"": [{ ""type"": ""Attribute"", ""value"": -1 }]
|
||||
}]
|
||||
}]
|
||||
}";
|
||||
|
||||
public const string SkillWithInvalidLine = @"{
|
||||
""type"": ""fighter"",
|
||||
""version"": 1,
|
||||
|
||||
@@ -32,6 +32,38 @@ namespace XWorld.Framework.Tests.ActorBehavior
|
||||
Assert.Contains(world.DrainEvents(), e =>
|
||||
e.Kind == BehaviorEventKind.AttributeEffectRequested && e.TargetActorId == 3);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("sector_test")]
|
||||
[InlineData("line_test")]
|
||||
public void DirectionalShapes_UseInputDirectionInsteadOfActorForward(string behaviorId)
|
||||
{
|
||||
BehaviorWorld world = TestTargetWorld.Create();
|
||||
|
||||
world.TryStartBehavior(1, behaviorId, BehaviorInput.FromDirection(0, 1));
|
||||
world.Tick(0.01f);
|
||||
var events = world.DrainEvents();
|
||||
|
||||
Assert.Contains(events, e =>
|
||||
e.Kind == BehaviorEventKind.AttributeEffectRequested && e.TargetActorId == 4);
|
||||
Assert.DoesNotContain(events, e =>
|
||||
e.Kind == BehaviorEventKind.AttributeEffectRequested && e.TargetActorId == 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectionalShapes_UseTargetPositionDirectionInsteadOfActorForward()
|
||||
{
|
||||
BehaviorWorld world = TestTargetWorld.Create();
|
||||
|
||||
world.TryStartBehavior(1, "sector_test", BehaviorInput.FromTargetPosition(0, 0, 2));
|
||||
world.Tick(0.01f);
|
||||
var events = world.DrainEvents();
|
||||
|
||||
Assert.Contains(events, e =>
|
||||
e.Kind == BehaviorEventKind.AttributeEffectRequested && e.TargetActorId == 4);
|
||||
Assert.DoesNotContain(events, e =>
|
||||
e.Kind == BehaviorEventKind.AttributeEffectRequested && e.TargetActorId == 2);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class TestTargetWorld
|
||||
|
||||
@@ -116,6 +116,23 @@ namespace XWorld.Framework.Tests.ActorBehavior
|
||||
Assert.Equal(0, world.ActiveBuffCount(2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuffTickLogic_AttributesEffectsToOriginalSource()
|
||||
{
|
||||
BehaviorWorld world = TestWorld.CreateServerWorld();
|
||||
|
||||
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.AttributeEffectRequested
|
||||
&& e.SourceActorId == 1
|
||||
&& e.TargetActorId == 2
|
||||
&& e.Attribute == "moveSpeed");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClientPresentation_BuffTickDoesNotEmitAuthoritativeAttributeRequests()
|
||||
{
|
||||
@@ -159,6 +176,27 @@ namespace XWorld.Framework.Tests.ActorBehavior
|
||||
|
||||
Assert.Contains(events, e => e.Kind == BehaviorEventKind.TimelineEffect && e.FlyObjectId == "blade_wave" && e.Prefab == "Assets/Game/Art/Effect/blade_wave.prefab");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Projectile_DoesNotHitSameActorMoreThanOnce()
|
||||
{
|
||||
string fly = TestBehaviorJson.FlyObject.Replace("\"hitLimit\": 1", "\"hitLimit\": 2");
|
||||
BehaviorConfigCatalog catalog = BehaviorConfigCatalog.LoadFromJson("fighter", TestBehaviorJson.Skill, fly, 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, "slash", BehaviorInput.FromDirection(1, 0));
|
||||
world.Tick(0.33f);
|
||||
world.DrainEvents();
|
||||
world.Tick(0.1f);
|
||||
world.Tick(0.1f);
|
||||
var events = world.DrainEvents();
|
||||
|
||||
Assert.Equal(1, events.Count(e => e.Kind == BehaviorEventKind.ProjectileHit && e.TargetActorId == 2));
|
||||
Assert.Equal(1, world.ActiveProjectileCount);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class TestWorld
|
||||
|
||||
Reference in New Issue
Block a user