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:
parent
9ef7752beb
commit
178f71c6dc
33 changed files with 469 additions and 677 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue