chore(content): Cleans up abilities and some spell timers (#427)

- [X] Exposes Index/Count on the timer.
- [X] Cleans up abilities
- [X] Combines some spell context/info objects with their timers to reduce allocations
- [X] Fixes a bug where immolating weapon both finishes effect or stops in the wrong order due to a race condition.
This commit is contained in:
Kamron Batman 2021-01-23 16:40:53 -08:00 • committed by GitHub
parent 9ef7752beb
commit 178f71c6dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 469 additions and 677 deletions

View file

@ -7,7 +7,7 @@ namespace Server.Spells.Spellweaving
{
private static readonly SpellInfo m_Info = new("Essence of Wind", "Anathrae", -1);
private static readonly Dictionary<Mobile, EssenceOfWindInfo> m_Table = new();
private static readonly Dictionary<Mobile, EssenceOfWindTimer> m_Table = new();
public EssenceOfWindSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
@ -53,7 +53,10 @@ namespace Server.Spells.Spellweaving
continue;
}
m_Table[m] = new EssenceOfWindInfo(m, fcMalus, ssiMalus, duration);
var t = new EssenceOfWindTimer(m, fcMalus, ssiMalus, duration);
t.Start();
m_Table[m] = t;
BuffInfo.AddBuff(
m,
@ -73,58 +76,44 @@ namespace Server.Spells.Spellweaving
FinishSequence();
}
public static int GetFCMalus(Mobile m) => m_Table.TryGetValue(m, out var info) ? info.FCMalus : 0;
public static int GetFCMalus(Mobile m) => m_Table.TryGetValue(m, out var timer) ? timer._fcMalus : 0;
public static int GetSSIMalus(Mobile m) => m_Table.TryGetValue(m, out var info) ? info.SSIMalus : 0;
public static int GetSSIMalus(Mobile m) => m_Table.TryGetValue(m, out var timer) ? timer._ssiMalus : 0;
public static bool IsDebuffed(Mobile m) => m_Table.ContainsKey(m);
public static void StopDebuffing(Mobile m, bool message)
{
if (m_Table.TryGetValue(m, out var info))
if (m_Table.TryGetValue(m, out var timer))
{
info.Timer.DoExpire(message);
timer.DoExpire(message);
}
}
private class EssenceOfWindInfo
private class EssenceOfWindTimer : Timer
{
public EssenceOfWindInfo(Mobile defender, int fcMalus, int ssiMalus, TimeSpan duration)
private readonly Mobile _defender;
internal readonly int _fcMalus;
internal readonly int _ssiMalus;
internal EssenceOfWindTimer(Mobile defender, int fcMalus, int ssiMalus, TimeSpan duration) : base(duration)
{
Defender = defender;
FCMalus = fcMalus;
SSIMalus = ssiMalus;
Timer = new ExpireTimer(Defender, duration);
Timer.Start();
_defender = defender;
_fcMalus = fcMalus;
_ssiMalus = ssiMalus;
}
public Mobile Defender { get; }
public int FCMalus { get; }
public int SSIMalus { get; }
public ExpireTimer Timer { get; }
}
private class ExpireTimer : Timer
{
private readonly Mobile m_Mobile;
public ExpireTimer(Mobile m, TimeSpan delay) : base(delay) => m_Mobile = m;
protected override void OnTick()
{
DoExpire(true);
DoExpire();
}
public void DoExpire(bool message)
internal void DoExpire(bool message = true)
{
Stop();
m_Table.Remove(m_Mobile);
m_Table.Remove(_defender);
BuffInfo.RemoveBuff(m_Mobile, BuffIcon.EssenceOfWind);
BuffInfo.RemoveBuff(_defender, BuffIcon.EssenceOfWind);
}
}
}

View file

@ -12,7 +12,7 @@ namespace Server.Spells.Spellweaving
-1
);
private static readonly Dictionary<Mobile, GiftOfRenewalInfo> m_Table = new();
private static readonly Dictionary<Mobile, GiftOfRenewalTimer> m_Table = new();
public GiftOfRenewalSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
@ -59,29 +59,19 @@ namespace Server.Spells.Spellweaving
var skill = Caster.Skills.Spellweaving.Value;
var hitsPerRound = 5 + (int)(skill / 24) + FocusLevel;
var duration = TimeSpan.FromSeconds(30 + FocusLevel * 10);
var duration = 30 + FocusLevel * 10;
var info = new GiftOfRenewalInfo(Caster, m, hitsPerRound);
var t = new GiftOfRenewalTimer(Caster, m, hitsPerRound, duration);
Timer.DelayCall(
duration,
() =>
{
if (StopEffect(m))
{
m.PlaySound(0x455);
m.SendLocalizedMessage(1075071); // The Gift of Renewal has faded.
}
}
);
m_Table[m] = t;
m_Table[m] = info;
t.Start();
Caster.BeginAction<GiftOfRenewalSpell>();
BuffInfo.AddBuff(
m,
new BuffInfo(BuffIcon.GiftOfRenewal, 1031602, 1075797, duration, m, hitsPerRound.ToString())
new BuffInfo(BuffIcon.GiftOfRenewal, 1031602, 1075797, TimeSpan.FromSeconds(duration), m, hitsPerRound.ToString())
);
}
}
@ -96,48 +86,43 @@ namespace Server.Spells.Spellweaving
public static bool StopEffect(Mobile m)
{
if (!m_Table.Remove(m, out var info))
{
return false;
}
info.m_Timer.Stop();
BuffInfo.RemoveBuff(m, BuffIcon.GiftOfRenewal);
Timer.DelayCall(TimeSpan.FromSeconds(60), info.m_Caster.EndAction<GiftOfRenewalSpell>);
if (m_Table.Remove(m, out var timer))
{
timer.Stop();
Timer.DelayCall(TimeSpan.FromSeconds(60), timer.m_Caster.EndAction<GiftOfRenewalSpell>);
return true;
}
return true;
return false;
}
private class GiftOfRenewalInfo
private class GiftOfRenewalTimer : Timer
{
public readonly Mobile m_Caster;
public readonly int m_HitsPerRound;
public readonly Mobile m_Mobile;
public readonly InternalTimer m_Timer;
public GiftOfRenewalInfo(Mobile caster, Mobile mobile, int hitsPerRound)
internal GiftOfRenewalTimer(Mobile caster, Mobile mobile, int hitsPerRound, int duration)
: base(TimeSpan.FromSeconds(2.0), TimeSpan.FromSeconds(2.0), duration / 2)
{
m_Caster = caster;
m_Mobile = mobile;
m_HitsPerRound = hitsPerRound;
m_Timer = new InternalTimer(this);
m_Timer.Start();
}
}
private class InternalTimer : Timer
{
private readonly GiftOfRenewalInfo m_GiftInfo;
public InternalTimer(GiftOfRenewalInfo info)
: base(TimeSpan.FromSeconds(2.0), TimeSpan.FromSeconds(2.0)) =>
m_GiftInfo = info;
protected override void OnTick()
{
var m = m_GiftInfo.m_Mobile;
if (Index + 1 == Count)
{
StopEffect(m_Mobile);
m_Mobile.PlaySound(0x455);
m_Mobile.SendLocalizedMessage(1075071); // The Gift of Renewal has faded.
return;
}
var m = m_Mobile;
if (!m_Table.ContainsKey(m))
{
@ -157,9 +142,9 @@ namespace Server.Spells.Spellweaving
return;
}
var toHeal = m_GiftInfo.m_HitsPerRound;
var toHeal = m_HitsPerRound;
SpellHelper.Heal(toHeal, m, m_GiftInfo.m_Caster);
SpellHelper.Heal(toHeal, m, m_Caster);
m.FixedParticles(0x376A, 9, 32, 5005, EffectLayer.Waist);
}
}

View file

@ -12,8 +12,7 @@ namespace Server.Spells.Spellweaving
-1
);
private static readonly Dictionary<BaseWeapon, ImmolatingWeaponEntry> m_WeaponDamageTable =
new();
private static readonly Dictionary<BaseWeapon, ImmolatingWeaponTimer> m_Table = new();
public ImmolatingWeaponSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
@ -54,9 +53,10 @@ namespace Server.Spells.Spellweaving
var duration = 10 + (int)(skill / 24) + FocusLevel;
var damage = 5 + (int)(skill / 24) + FocusLevel;
var stopTimer = Timer.DelayCall(TimeSpan.FromSeconds(duration), StopImmolating, weapon);
var t = new ImmolatingWeaponTimer(TimeSpan.FromSeconds(duration), damage, Caster, weapon);
m_Table[weapon] = t;
t.Start();
m_WeaponDamageTable[weapon] = new ImmolatingWeaponEntry(damage, stopTimer, Caster);
weapon.InvalidateProperties();
}
}
@ -64,60 +64,53 @@ namespace Server.Spells.Spellweaving
FinishSequence();
}
public static bool IsImmolating(BaseWeapon weapon) => m_WeaponDamageTable.ContainsKey(weapon);
public static bool IsImmolating(BaseWeapon weapon) => m_Table.ContainsKey(weapon);
public static int GetImmolatingDamage(BaseWeapon weapon) =>
m_WeaponDamageTable.TryGetValue(weapon, out var entry) ? entry.m_Damage : 0;
m_Table.TryGetValue(weapon, out var entry) ? entry._damage : 0;
public static void DoEffect(BaseWeapon weapon, Mobile target)
{
Timer.DelayCall(TimeSpan.FromSeconds(0.25), FinishEffect, new DelayedEffectEntry(weapon, target));
if (m_Table.Remove(weapon, out var timer))
{
timer.Stop();
Timer.DelayCall(TimeSpan.FromSeconds(0.25), FinishEffect, target, timer);
}
}
private static void FinishEffect(DelayedEffectEntry effect)
private static void FinishEffect(Mobile target, ImmolatingWeaponTimer timer)
{
if (m_WeaponDamageTable.TryGetValue(effect.m_Weapon, out var entry))
{
AOS.Damage(effect.m_Target, entry.m_Caster, entry.m_Damage, 0, 100, 0, 0, 0);
}
AOS.Damage(target, timer._caster, timer._damage, 0, 100, 0, 0, 0);
}
public static void StopImmolating(BaseWeapon weapon)
{
if (!m_WeaponDamageTable.Remove(weapon, out var entry))
if (m_Table.Remove(weapon, out var timer))
{
return;
}
timer._caster?.PlaySound(0x27);
timer.Stop();
entry.m_Caster?.PlaySound(0x27);
entry.m_Timer.Stop();
weapon.InvalidateProperties();
}
private class ImmolatingWeaponEntry
{
public readonly Mobile m_Caster;
public readonly int m_Damage;
public readonly Timer m_Timer;
public ImmolatingWeaponEntry(int damage, Timer stopTimer, Mobile caster)
{
m_Damage = damage;
m_Timer = stopTimer;
m_Caster = caster;
weapon.InvalidateProperties();
}
}
private class DelayedEffectEntry
private class ImmolatingWeaponTimer : Timer
{
public readonly Mobile m_Target;
public readonly BaseWeapon m_Weapon;
public readonly Mobile _caster;
public readonly int _damage;
public readonly BaseWeapon _weapon;
public DelayedEffectEntry(BaseWeapon weapon, Mobile target)
public ImmolatingWeaponTimer(TimeSpan duration, int damage, Mobile caster, BaseWeapon weapon) : base(duration)
{
m_Weapon = weapon;
m_Target = target;
_damage = damage;
_caster = caster;
_weapon = weapon;
}
protected override void OnTick()
{
StopImmolating(_weapon);
}
}
}