fix: harden actor behavior authority logic

This commit is contained in:
ud18010
2026-07-30 13:38:10 +08:00
parent bc52b74ff8
commit f898cbab10
5 changed files with 222 additions and 22 deletions
@@ -41,6 +41,10 @@ namespace XWorld.Framework.ActorBehavior
throw new InvalidOperationException("Behavior config type mismatch: expected " + type);
int version = skillRoot.OptionalInt("version", 1);
int flyVersion = flyRoot.OptionalInt("version", 1);
int buffVersion = buffRoot.OptionalInt("version", 1);
if (flyVersion != version || buffVersion != version)
throw new InvalidOperationException("Behavior config version mismatch: expected " + version);
var flyObjects = ParseFlyObjects(flyRoot.OptionalArray("flyObjects"));
var buffs = ParseBuffs(buffRoot.OptionalArray("buffs"));
var behaviors = ParseBehaviors(skillRoot.OptionalArray("behaviors"));
@@ -229,12 +233,31 @@ namespace XWorld.Framework.ActorBehavior
foreach (TimelineEntrySpec entry in timeline)
{
if (entry.Time < 0f) throw new InvalidOperationException("Negative timeline time in " + owner);
if (entry.Kind == TimelineEntryKind.LogicEffect && !localEffects.Contains(entry.EffectId))
throw new InvalidOperationException("Missing logic effect reference " + entry.EffectId + " in " + owner);
if (entry.Kind == TimelineEntryKind.Projectile && !_flyObjects.ContainsKey(entry.FlyObjectId))
throw new InvalidOperationException("Missing flyObject reference " + entry.FlyObjectId + " in " + owner);
if (entry.Kind == TimelineEntryKind.Buff && !_buffs.ContainsKey(entry.BuffId))
throw new InvalidOperationException("Missing buff reference " + entry.BuffId + " in " + owner);
if (entry.Kind == TimelineEntryKind.Animation && string.IsNullOrEmpty(entry.Animation))
throw new InvalidOperationException("Missing animation for timeline entry in " + owner);
if (entry.Kind == TimelineEntryKind.Effect && string.IsNullOrEmpty(entry.Prefab))
throw new InvalidOperationException("Missing prefab for timeline entry in " + owner);
if (entry.Kind == TimelineEntryKind.LogicEffect)
{
if (string.IsNullOrEmpty(entry.EffectId))
throw new InvalidOperationException("Missing effectId for timeline entry in " + owner);
if (!localEffects.Contains(entry.EffectId))
throw new InvalidOperationException("Missing logic effect reference " + entry.EffectId + " in " + owner);
}
if (entry.Kind == TimelineEntryKind.Projectile)
{
if (string.IsNullOrEmpty(entry.FlyObjectId))
throw new InvalidOperationException("Missing flyObjectId for timeline entry in " + owner);
if (!_flyObjects.ContainsKey(entry.FlyObjectId))
throw new InvalidOperationException("Missing flyObject reference " + entry.FlyObjectId + " in " + owner);
}
if (entry.Kind == TimelineEntryKind.Buff)
{
if (string.IsNullOrEmpty(entry.BuffId))
throw new InvalidOperationException("Missing buffId for timeline entry in " + owner);
if (!_buffs.ContainsKey(entry.BuffId))
throw new InvalidOperationException("Missing buff reference " + entry.BuffId + " in " + owner);
}
}
}
@@ -254,10 +277,22 @@ namespace XWorld.Framework.ActorBehavior
throw new InvalidOperationException("Invalid width for " + effect.Id + " in " + owner);
foreach (LogicEffectOpSpec op in effect.Effects)
{
if (op.Type == LogicEffectOpKind.Buff && !_buffs.ContainsKey(op.BuffId))
throw new InvalidOperationException("Missing buff reference " + op.BuffId + " in " + owner);
if (op.Type == LogicEffectOpKind.Projectile && !_flyObjects.ContainsKey(op.FlyObjectId))
throw new InvalidOperationException("Missing flyObject reference " + op.FlyObjectId + " in " + owner);
if (op.Type == LogicEffectOpKind.Buff)
{
if (string.IsNullOrEmpty(op.BuffId))
throw new InvalidOperationException("Missing buffId in " + owner);
if (!_buffs.ContainsKey(op.BuffId))
throw new InvalidOperationException("Missing buff reference " + op.BuffId + " in " + owner);
}
if (op.Type == LogicEffectOpKind.Projectile)
{
if (string.IsNullOrEmpty(op.FlyObjectId))
throw new InvalidOperationException("Missing flyObjectId in " + owner);
if (!_flyObjects.ContainsKey(op.FlyObjectId))
throw new InvalidOperationException("Missing flyObject reference " + op.FlyObjectId + " in " + owner);
}
if (op.Type == LogicEffectOpKind.Attribute && string.IsNullOrEmpty(op.Attribute))
throw new InvalidOperationException("Missing attribute in " + owner);
}
}
}
@@ -251,13 +251,18 @@ namespace XWorld.Framework.ActorBehavior
private void ApplyLogicEffect(int sourceActorId, BehaviorConfigCatalog catalog, LogicEffectSpec effect, BehaviorInput input, int hitActorId)
{
foreach (int targetActorId in SelectTargets(sourceActorId, effect, input, hitActorId))
ApplyLogicEffect(sourceActorId, sourceActorId, catalog, effect, input, hitActorId);
}
private void ApplyLogicEffect(int eventSourceActorId, int originActorId, BehaviorConfigCatalog catalog, LogicEffectSpec effect, BehaviorInput input, int hitActorId)
{
foreach (int targetActorId in SelectTargets(originActorId, effect, input, hitActorId))
{
_events.Add(new BehaviorEvent
{
Kind = BehaviorEventKind.LogicEffectApplied,
ActorId = sourceActorId,
SourceActorId = sourceActorId,
ActorId = eventSourceActorId,
SourceActorId = eventSourceActorId,
TargetActorId = targetActorId,
LogicEffectId = effect.Id
});
@@ -270,8 +275,8 @@ namespace XWorld.Framework.ActorBehavior
_events.Add(new BehaviorEvent
{
Kind = BehaviorEventKind.AttributeEffectRequested,
ActorId = sourceActorId,
SourceActorId = sourceActorId,
ActorId = eventSourceActorId,
SourceActorId = eventSourceActorId,
TargetActorId = targetActorId,
LogicEffectId = effect.Id,
Attribute = op.Attribute,
@@ -280,11 +285,11 @@ namespace XWorld.Framework.ActorBehavior
}
else if (op.Type == LogicEffectOpKind.Projectile)
{
SpawnProjectile(sourceActorId, catalog, op.FlyObjectId, input);
SpawnProjectile(originActorId, catalog, op.FlyObjectId, input);
}
else if (op.Type == LogicEffectOpKind.Buff)
{
ApplyBuff(targetActorId, catalog.Type, op.BuffId, sourceActorId);
ApplyBuff(targetActorId, catalog.Type, op.BuffId, eventSourceActorId);
}
}
}
@@ -318,7 +323,7 @@ namespace XWorld.Framework.ActorBehavior
if (_options.Mode == BehaviorRunMode.ServerLogic)
{
for (int e = 0; e < buff.Spec.LogicEffects.Count; e++)
ApplyLogicEffect(buff.TargetActorId, buff.Catalog, buff.Spec.LogicEffects[e], BehaviorInput.FromTargetActor(buff.TargetActorId), buff.TargetActorId);
ApplyLogicEffect(buff.SourceActorId, buff.TargetActorId, buff.Catalog, buff.Spec.LogicEffects[e], BehaviorInput.FromTargetActor(buff.TargetActorId), buff.TargetActorId);
}
buff.NextTickTime += buff.Spec.TickInterval;
}
@@ -448,10 +453,12 @@ namespace XWorld.Framework.ActorBehavior
if (!actor.Alive) continue;
if (actor.ActorId == projectile.SourceActorId) continue;
if (actor.TeamId == source.TeamId) continue;
if (projectile.HitActorIds.Contains(actor.ActorId)) continue;
float distance = BehaviorVector3.DistanceXZ(projectile.Position, actor.Position);
if (distance > projectile.Spec.Collision.Radius + actor.Radius) continue;
projectile.HitCount++;
projectile.HitActorIds.Add(actor.ActorId);
_events.Add(new BehaviorEvent
{
Kind = BehaviorEventKind.ProjectileHit,
@@ -549,7 +556,7 @@ namespace XWorld.Framework.ActorBehavior
{
if (!actor.Alive) continue;
if (!PassesTargetFilter(source, actor, effect.Target)) continue;
if (IsInsideShape(source, actor, effect))
if (IsInsideShape(source, actor, effect, ResolveEffectDirection(source, input)))
yield return actor.ActorId;
}
}
@@ -565,7 +572,20 @@ namespace XWorld.Framework.ActorBehavior
return true;
}
private static bool IsInsideShape(BehaviorActorState source, BehaviorActorState target, LogicEffectSpec effect)
private BehaviorVector3 ResolveEffectDirection(BehaviorActorState source, BehaviorInput input)
{
if (input.Kind == BehaviorInputKind.Direction)
return input.Direction.NormalizedXZ();
if (input.Kind == BehaviorInputKind.TargetPosition)
return (input.TargetPosition - source.Position).NormalizedXZ();
if (input.Kind == BehaviorInputKind.TargetActor
&& input.TargetActorId != 0
&& _actors.TryGetValue(input.TargetActorId, out BehaviorActorState target))
return (target.Position - source.Position).NormalizedXZ();
return source.Forward.NormalizedXZ();
}
private static bool IsInsideShape(BehaviorActorState source, BehaviorActorState target, LogicEffectSpec effect, BehaviorVector3 direction)
{
float distance = BehaviorVector3.DistanceXZ(source.Position, target.Position);
if (effect.Shape == LogicShapeKind.Sphere)
@@ -576,7 +596,7 @@ namespace XWorld.Framework.ActorBehavior
{
if (distance > effect.Radius + target.Radius) return false;
BehaviorVector3 toTarget = (target.Position - source.Position).NormalizedXZ();
BehaviorVector3 forward = source.Forward.NormalizedXZ();
BehaviorVector3 forward = direction.NormalizedXZ();
float dot = Clamp(BehaviorVector3.DotXZ(forward, toTarget), -1f, 1f);
float angle = (float)(Math.Acos(dot) * 180.0 / Math.PI);
return angle <= effect.Angle * 0.5f;
@@ -584,7 +604,7 @@ namespace XWorld.Framework.ActorBehavior
if (effect.Shape == LogicShapeKind.Line)
{
float width = effect.Width > 0f ? effect.Width : target.Radius;
float dist = DistancePointToSegmentXZ(target.Position, source.Position, source.Position + source.Forward.NormalizedXZ() * effect.Radius);
float dist = DistancePointToSegmentXZ(target.Position, source.Position, source.Position + direction.NormalizedXZ() * effect.Radius);
return dist <= width + target.Radius;
}
return target.ActorId == source.ActorId;
@@ -637,6 +657,7 @@ namespace XWorld.Framework.ActorBehavior
public float VerticalVelocity;
public int HitCount;
public readonly HashSet<int> FiredTimeline = new HashSet<int>();
public readonly HashSet<int> HitActorIds = new HashSet<int>();
}
private sealed class ActiveBuff