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, ActiveBehavior> _activeBehaviors = new Dictionary<int, ActiveBehavior>();
|
||||
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 int _nextProjectileId = 1;
|
||||
|
||||
@@ -84,6 +85,7 @@ namespace XWorld.Framework.ActorBehavior
|
||||
{
|
||||
if (deltaTime <= 0f) return;
|
||||
TickProjectiles(deltaTime);
|
||||
TickBuffs(deltaTime);
|
||||
|
||||
var completed = new List<int>();
|
||||
foreach (ActiveBehavior active in new List<ActiveBehavior>(_activeBehaviors.Values))
|
||||
@@ -117,12 +119,68 @@ namespace XWorld.Framework.ActorBehavior
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
@@ -217,10 +275,67 @@ namespace XWorld.Framework.ActorBehavior
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!_actors.TryGetValue(sourceActorId, out BehaviorActorState source)) return;
|
||||
@@ -461,5 +576,16 @@ namespace XWorld.Framework.ActorBehavior
|
||||
public float VerticalVelocity;
|
||||
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 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);
|
||||
}
|
||||
|
||||
[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
|
||||
|
||||
Reference in New Issue
Block a user