feat: add actor behavior buffs
This commit is contained in:
@@ -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,10 +275,67 @@ 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)
|
||||||
{
|
{
|
||||||
if (!_actors.TryGetValue(sourceActorId, out BehaviorActorState source)) return;
|
if (!_actors.TryGetValue(sourceActorId, out BehaviorActorState source)) return;
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user