feat: add actor behavior buffs

This commit is contained in:
ud18010
2026-07-30 13:17:41 +08:00
parent 52741e68a8
commit c12e9ea9d7
2 changed files with 149 additions and 2 deletions
@@ -10,6 +10,7 @@ namespace XWorld.Framework.ActorBehavior
private readonly Dictionary<int, BehaviorActorState> _actors = new Dictionary<int, BehaviorActorState>(); private readonly Dictionary<int, BehaviorActorState> _actors = new Dictionary<int, BehaviorActorState>();
private readonly Dictionary<int, ActiveBehavior> _activeBehaviors = new Dictionary<int, ActiveBehavior>(); private readonly Dictionary<int, ActiveBehavior> _activeBehaviors = new Dictionary<int, ActiveBehavior>();
private readonly List<ActiveProjectile> _projectiles = new List<ActiveProjectile>(); private readonly List<ActiveProjectile> _projectiles = new List<ActiveProjectile>();
private readonly Dictionary<int, List<ActiveBuff>> _buffs = new Dictionary<int, List<ActiveBuff>>();
private readonly List<BehaviorEvent> _events = new List<BehaviorEvent>(); private readonly List<BehaviorEvent> _events = new List<BehaviorEvent>();
private int _nextProjectileId = 1; private int _nextProjectileId = 1;
@@ -84,6 +85,7 @@ namespace XWorld.Framework.ActorBehavior
{ {
if (deltaTime <= 0f) return; if (deltaTime <= 0f) return;
TickProjectiles(deltaTime); TickProjectiles(deltaTime);
TickBuffs(deltaTime);
var completed = new List<int>(); var completed = new List<int>();
foreach (ActiveBehavior active in new List<ActiveBehavior>(_activeBehaviors.Values)) foreach (ActiveBehavior active in new List<ActiveBehavior>(_activeBehaviors.Values))
@@ -117,12 +119,68 @@ namespace XWorld.Framework.ActorBehavior
public int ActiveBuffCount(int actorId) public int ActiveBuffCount(int actorId)
{ {
return 0; return _buffs.TryGetValue(actorId, out List<ActiveBuff> buffs) ? buffs.Count : 0;
} }
public bool ApplyBuff(int targetActorId, string catalogType, string buffId, int sourceActorId) public bool ApplyBuff(int targetActorId, string catalogType, string buffId, int sourceActorId)
{ {
return false; if (!_actors.ContainsKey(targetActorId)) return false;
if (!_catalogs.TryGetValue(catalogType, out BehaviorConfigCatalog catalog)) return false;
if (!catalog.Buffs.TryGetValue(buffId, out BuffSpec spec)) return false;
if (!_buffs.TryGetValue(targetActorId, out List<ActiveBuff> activeBuffs))
{
activeBuffs = new List<ActiveBuff>();
_buffs[targetActorId] = activeBuffs;
}
ActiveBuff existing = null;
for (int i = 0; i < activeBuffs.Count; i++)
{
if (activeBuffs[i].Spec.Id == buffId)
{
existing = activeBuffs[i];
break;
}
}
if (existing != null && spec.Stacking == BuffStackingKind.IgnoreIfExists)
return true;
if (existing != null && spec.Stacking == BuffStackingKind.RefreshDuration)
{
existing.Elapsed = 0f;
existing.NextTickTime = spec.TickInterval > 0f ? spec.TickInterval : spec.Duration;
existing.TickCount = 0;
_events.Add(new BehaviorEvent
{
Kind = BehaviorEventKind.BuffAdded,
ActorId = sourceActorId,
SourceActorId = sourceActorId,
TargetActorId = targetActorId,
BuffId = spec.Id
});
return true;
}
activeBuffs.Add(new ActiveBuff
{
SourceActorId = sourceActorId,
TargetActorId = targetActorId,
Catalog = catalog,
Spec = spec,
Elapsed = 0f,
NextTickTime = spec.TickInterval > 0f ? spec.TickInterval : spec.Duration,
TickCount = 0
});
_events.Add(new BehaviorEvent
{
Kind = BehaviorEventKind.BuffAdded,
ActorId = sourceActorId,
SourceActorId = sourceActorId,
TargetActorId = targetActorId,
BuffId = spec.Id
});
return true;
} }
private void FireTimeline(ActiveBehavior active, float previousElapsed, float currentElapsed) private void FireTimeline(ActiveBehavior active, float previousElapsed, float currentElapsed)
@@ -217,9 +275,66 @@ namespace XWorld.Framework.ActorBehavior
{ {
SpawnProjectile(sourceActorId, catalog, op.FlyObjectId, input); SpawnProjectile(sourceActorId, catalog, op.FlyObjectId, input);
} }
else if (op.Type == LogicEffectOpKind.Buff)
{
ApplyBuff(targetActorId, catalog.Type, op.BuffId, sourceActorId);
} }
} }
} }
}
private void TickBuffs(float deltaTime)
{
var actorIds = new List<int>(_buffs.Keys);
for (int actorIndex = 0; actorIndex < actorIds.Count; actorIndex++)
{
int actorId = actorIds[actorIndex];
List<ActiveBuff> activeBuffs = _buffs[actorId];
var expired = new List<ActiveBuff>();
for (int i = 0; i < activeBuffs.Count; i++)
{
ActiveBuff buff = activeBuffs[i];
buff.Elapsed += deltaTime;
while (buff.Spec.TickInterval > 0f
&& buff.Elapsed + 0.00001f >= buff.NextTickTime
&& (buff.Spec.RepeatCount <= 0 || buff.TickCount < buff.Spec.RepeatCount))
{
buff.TickCount++;
_events.Add(new BehaviorEvent
{
Kind = BehaviorEventKind.BuffTicked,
ActorId = buff.SourceActorId,
SourceActorId = buff.SourceActorId,
TargetActorId = buff.TargetActorId,
BuffId = buff.Spec.Id
});
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);
buff.NextTickTime += buff.Spec.TickInterval;
}
bool repeatReached = buff.Spec.RepeatCount > 0 && buff.TickCount >= buff.Spec.RepeatCount;
bool durationReached = buff.Spec.Duration > 0f && buff.Elapsed >= buff.Spec.Duration;
if (repeatReached || durationReached)
{
expired.Add(buff);
_events.Add(new BehaviorEvent
{
Kind = BehaviorEventKind.BuffRemoved,
ActorId = buff.SourceActorId,
SourceActorId = buff.SourceActorId,
TargetActorId = buff.TargetActorId,
BuffId = buff.Spec.Id
});
}
}
for (int i = 0; i < expired.Count; i++)
activeBuffs.Remove(expired[i]);
if (activeBuffs.Count == 0)
_buffs.Remove(actorId);
}
}
private void SpawnProjectile(int sourceActorId, BehaviorConfigCatalog catalog, string flyObjectId, BehaviorInput input) private void SpawnProjectile(int sourceActorId, BehaviorConfigCatalog catalog, string flyObjectId, BehaviorInput input)
{ {
@@ -461,5 +576,16 @@ namespace XWorld.Framework.ActorBehavior
public float VerticalVelocity; public float VerticalVelocity;
public int HitCount; public int HitCount;
} }
private sealed class ActiveBuff
{
public int SourceActorId;
public int TargetActorId;
public BehaviorConfigCatalog Catalog;
public BuffSpec Spec;
public float Elapsed;
public float NextTickTime;
public int TickCount;
}
} }
} }
@@ -1,3 +1,4 @@
using System.Linq;
using Xunit; using Xunit;
using XWorld.Framework.ActorBehavior; using XWorld.Framework.ActorBehavior;
@@ -94,6 +95,26 @@ namespace XWorld.Framework.Tests.ActorBehavior
Assert.Contains(world.DrainEvents(), e => e.Kind == BehaviorEventKind.ProjectileMoved && e.Position.X > 0); 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));
}
} }
internal static class TestWorld internal static class TestWorld