fix: Fixes DeltaQueue recursion, StatMod bug, and memory leak (#1233)
* Fixes recursion with DeltaQueue causing multiple sets of packets to be sent to the client. * Fixes StatMods that are expired not being checked properly. * Adds a timer to properly expire/remove stat mods instead of relying on a side-effect.
This commit is contained in:
parent
d7d914df6c
commit
59dcd24bed
3 changed files with 61 additions and 26 deletions
|
|
@ -8392,6 +8392,33 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
|||
return false;
|
||||
}
|
||||
|
||||
public virtual void RemoveStatMod(StatMod mod)
|
||||
{
|
||||
if (mod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove it just in case it was orphaned somehow.
|
||||
mod.Remove();
|
||||
|
||||
if (_statMods == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_statMods.Remove(mod))
|
||||
{
|
||||
CheckStatTimers();
|
||||
Delta(MobileDelta.Stat | GetStatDelta(mod.Type));
|
||||
}
|
||||
|
||||
if (_statMods.Count == 0)
|
||||
{
|
||||
_statMods = null;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void RemoveStatMod(string name)
|
||||
{
|
||||
if (_statMods == null || name == null)
|
||||
|
|
@ -8405,19 +8432,19 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
|||
if (mod.Name == name)
|
||||
{
|
||||
_statMods.RemoveAt(i);
|
||||
mod.Remove();
|
||||
CheckStatTimers();
|
||||
Delta(MobileDelta.Stat | GetStatDelta(mod.Type));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (_statMods.Count == 0)
|
||||
{
|
||||
_statMods = null;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual StatMod GetStatMod(string name)
|
||||
public virtual StatMod GetStatMod(string name, bool includeElapsed = false)
|
||||
{
|
||||
if (_statMods == null || name == null)
|
||||
{
|
||||
|
|
@ -8427,7 +8454,7 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
|||
for (var i = 0; i < _statMods.Count; i++)
|
||||
{
|
||||
var mod = _statMods[i];
|
||||
if (mod.Name == name)
|
||||
if (mod.Name == name && !mod.HasElapsed() || includeElapsed)
|
||||
{
|
||||
return mod;
|
||||
}
|
||||
|
|
@ -8471,41 +8498,26 @@ public class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyLis
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the total modified offset for the specified stat type. Expired <see cref="StatMod" /> instances are removed.
|
||||
/// Computes the total modified offset for the specified stat type.
|
||||
/// </summary>
|
||||
public int GetStatOffset(StatType type)
|
||||
{
|
||||
_statMods ??= new List<StatMod>();
|
||||
|
||||
if (_statMods.Count <= 0)
|
||||
if (_statMods == null || _statMods.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var offset = 0;
|
||||
|
||||
using var queue = PooledRefQueue<StatMod>.Create(8);
|
||||
for (var i = 0; i < _statMods.Count; i++)
|
||||
{
|
||||
var mod = _statMods[i];
|
||||
if (mod.HasElapsed())
|
||||
{
|
||||
queue.Enqueue(mod);
|
||||
}
|
||||
else if ((mod.Type & type) != 0)
|
||||
if ((mod.Type & type) != 0 && !mod.HasElapsed())
|
||||
{
|
||||
offset += mod.Offset;
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var mod = queue.Dequeue();
|
||||
_statMods.Remove(mod);
|
||||
Delta(MobileDelta.Stat | GetStatDelta(mod.Type));
|
||||
CheckStatTimers();
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ public partial class StatMod : MobileMod
|
|||
[SerializableField(3, setter: "private")]
|
||||
private int _offset;
|
||||
|
||||
// Added a timer and removed the processing of expirations in GetStatOffset which caused recursions in DeltaQueue
|
||||
// ProcessDeltaQueue -> Dequeue -> ProcessDelta -> SendStats packet -> get_HitsMax ->
|
||||
// - GetStatOffset -> Delta -> Queue to DeltaQueue
|
||||
private TimerExecutionToken _timerToken;
|
||||
|
||||
public StatMod(Mobile owner) : base(owner)
|
||||
{
|
||||
}
|
||||
|
|
@ -43,7 +48,26 @@ public partial class StatMod : MobileMod
|
|||
_offset = offset;
|
||||
_duration = duration;
|
||||
_added = Core.Now;
|
||||
|
||||
if (_duration > TimeSpan.Zero)
|
||||
{
|
||||
Timer.StartTimer(duration, RemoveFromOwner, out _timerToken);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasElapsed() => _duration != TimeSpan.Zero && Core.Now - _added >= _duration;
|
||||
public bool HasElapsed() => _duration > TimeSpan.Zero && Core.Now - _added >= _duration;
|
||||
|
||||
[AfterDeserialization]
|
||||
private void AfterDeserialization()
|
||||
{
|
||||
if (_duration > TimeSpan.Zero && Core.Now - _added < _duration)
|
||||
{
|
||||
Timer.StartTimer(_duration, RemoveFromOwner, out _timerToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveFromOwner() => Owner?.RemoveStatMod(this);
|
||||
|
||||
// Called by Mobile.RemoveStatMod()
|
||||
public void Remove() => _timerToken.Cancel();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -301,20 +301,19 @@ namespace Server.Spells
|
|||
|
||||
public static bool AddStatBonus(Mobile caster, Mobile target, StatType type, int bonus, TimeSpan duration)
|
||||
{
|
||||
var offset = bonus;
|
||||
var name = $"[Magic] {type} Buff";
|
||||
|
||||
var mod = target.GetStatMod(name);
|
||||
|
||||
if (mod?.Offset < 0)
|
||||
{
|
||||
target.AddStatMod(new StatMod(type, name, mod.Offset + offset, duration));
|
||||
target.AddStatMod(new StatMod(type, name, mod.Offset + bonus, duration));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mod == null || mod.Offset <= offset)
|
||||
if (mod == null || mod.Offset <= bonus)
|
||||
{
|
||||
target.AddStatMod(new StatMod(type, name, offset, duration));
|
||||
target.AddStatMod(new StatMod(type, name, bonus, duration));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue