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

@ -838,10 +838,9 @@ namespace Server.Spells
public void Add(Mobile m, Timer t)
{
if (m_Contexts.TryGetValue(m, out var oldTimer))
if (m_Contexts.Remove(m, out var oldTimer))
{
oldTimer.Stop();
m_Contexts.Remove(m);
}
m_Contexts.Add(m, t);
@ -850,6 +849,7 @@ namespace Server.Spells
public void Remove(Mobile m)
{
m_Contexts.Remove(m);
// TODO: Should we stop the timer?
}
}

View file

@ -78,9 +78,7 @@ namespace Server.Spells.Bushido
m_Table.TryGetValue(m, out var timer);
timer?.Stop();
m_Table[m] = timer = new InternalTimer(m);
timer.Start();
m_Table[m] = Timer.DelayCall(TimeSpan.FromSeconds(30.0), EndCountering, m);
}
public static void StopCountering(Mobile m)
@ -93,21 +91,10 @@ namespace Server.Spells.Bushido
OnEffectEnd(m, typeof(CounterAttack));
}
private class InternalTimer : Timer
private static void EndCountering(Mobile m)
{
private readonly Mobile m_Mobile;
public InternalTimer(Mobile m) : base(TimeSpan.FromSeconds(30.0))
{
m_Mobile = m;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
StopCountering(m_Mobile);
m_Mobile.SendLocalizedMessage(1063119); // You return to your normal stance.
}
StopCountering(m);
m.SendLocalizedMessage(1063119); // You return to your normal stance.
}
}
}

View file

@ -5,8 +5,7 @@ namespace Server.Spells.Bushido
{
public class HonorableExecution : SamuraiMove
{
private static readonly Dictionary<Mobile, HonorableExecutionInfo> m_Table =
new();
private static readonly Dictionary<Mobile, HonorableExecutionTimer> m_Table = new();
public override int BaseMana => 0;
public override double RequiredSkill => 25.0;
@ -14,13 +13,9 @@ namespace Server.Spells.Bushido
public override TextDefinition AbilityMessage =>
new(1063122); // You better kill your enemy with your next hit or you'll be rather sorry...
public override double GetDamageScalar(Mobile attacker, Mobile defender)
{
var bushido = attacker.Skills.Bushido.Value;
public override double GetDamageScalar(Mobile attacker, Mobile defender) =>
// TODO: 20 -> Perfection
return 1.0 + bushido * 20 / 10000;
}
1.0 + attacker.Skills.Bushido.Value * 20 / 10000;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
@ -30,27 +25,20 @@ namespace Server.Spells.Bushido
}
ClearCurrentMove(attacker);
if (m_Table.TryGetValue(attacker, out var info))
{
info.Clear();
info.m_Timer?.Stop();
}
RemovePenalty(attacker);
if (!defender.Alive)
{
attacker.FixedParticles(0x373A, 1, 17, 0x7E2, EffectLayer.Waist);
var bushido = attacker.Skills.Bushido.Value;
bushido *= bushido;
attacker.Hits += 20 + (int)(bushido * bushido / 480.0);
attacker.Hits += 20 + (int)(bushido / 480.0);
var swingBonus = Math.Max((int)(bushido * bushido / 720.0), 1);
var swingBonus = Math.Max(1, (int)(bushido / 720.0));
info = new HonorableExecutionInfo(attacker, swingBonus);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(20.0), RemovePenalty, info.m_Mobile);
m_Table[attacker] = info;
m_Table[attacker] = new HonorableExecutionTimer(attacker, swingBonus);
}
else
{
@ -70,12 +58,10 @@ namespace Server.Spells.Bushido
mods.Add(new DefaultSkillMod(SkillName.MagicResist, true, -resSpells));
}
info = new HonorableExecutionInfo(attacker, mods);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(7.0), RemovePenalty, info.m_Mobile);
m_Table[attacker] = info;
m_Table[attacker] = new HonorableExecutionTimer(attacker, mods);
}
attacker.Delta(MobileDelta.WeaponDamage);
CheckGain(attacker);
}
@ -85,28 +71,30 @@ namespace Server.Spells.Bushido
public static void RemovePenalty(Mobile target)
{
if (!m_Table.Remove(target, out var info) || !info.m_Penalty)
if (m_Table.Remove(target, out var timer))
{
return;
timer.Clear();
}
info.Clear();
info.m_Timer?.Stop();
}
private class HonorableExecutionInfo
private class HonorableExecutionTimer : Timer
{
public readonly Mobile m_Mobile;
public readonly List<object> m_Mods;
public readonly bool m_Penalty;
public readonly int m_SwingBonus;
public Timer m_Timer;
public HonorableExecutionInfo(Mobile from, List<object> mods) : this(from, 0, mods, mods != null)
public HonorableExecutionTimer(Mobile from, List<object> mods) : this(TimeSpan.FromSeconds(7.0), from, 0, mods, mods != null)
{
}
public HonorableExecutionInfo(Mobile from, int swingBonus, List<object> mods = null, bool penalty = false)
public HonorableExecutionTimer(Mobile from, int swingBonus) : this(TimeSpan.FromSeconds(20.0), from, swingBonus)
{
}
public HonorableExecutionTimer(
TimeSpan duration, Mobile from, int swingBonus, List<object> mods = null, bool penalty = false)
: base(duration)
{
m_Mobile = from;
m_SwingBonus = swingBonus;
@ -116,6 +104,12 @@ namespace Server.Spells.Bushido
Apply();
}
protected override void OnTick()
{
m_Mobile?.Delta(MobileDelta.WeaponDamage);
RemovePenalty(m_Mobile);
}
public void Apply()
{
if (m_Mods == null)
@ -140,6 +134,8 @@ namespace Server.Spells.Bushido
public void Clear()
{
Stop();
if (m_Mods == null)
{
return;
@ -151,11 +147,11 @@ namespace Server.Spells.Bushido
if (mod is ResistanceMod resistanceMod)
{
m_Mobile.RemoveResistanceMod(resistanceMod);
m_Mobile?.RemoveResistanceMod(resistanceMod);
}
else if (mod is SkillMod skillMod)
{
m_Mobile.RemoveSkillMod(skillMod);
m_Mobile?.RemoveSkillMod(skillMod);
}
}
}

View file

@ -19,7 +19,7 @@ namespace Server.Spells.Fifth
Reagent.Nightshade
);
private static readonly Dictionary<Mobile, InternalTimer> m_Timers = new();
private static readonly Dictionary<Mobile, Timer> m_Table = new();
public IncognitoSpell(Mobile caster, Item scroll = null) : base(caster, scroll, m_Info)
{
@ -55,24 +55,34 @@ namespace Server.Spells.Fifth
if (Sigil.ExistsOn(Caster))
{
Caster.SendLocalizedMessage(1010445); // You cannot incognito if you have a sigil
return;
}
else if (!Caster.CanBeginAction<IncognitoSpell>())
if (!Caster.CanBeginAction<IncognitoSpell>())
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
return;
}
else if (Caster.BodyMod == 183 || Caster.BodyMod == 184)
if (Caster.BodyMod == 183 || Caster.BodyMod == 184)
{
Caster.SendLocalizedMessage(1042402); // You cannot use incognito while wearing body paint
return;
}
else if (DisguiseTimers.IsDisguised(Caster))
if (DisguiseTimers.IsDisguised(Caster))
{
Caster.SendLocalizedMessage(1061631); // You can't do that while disguised.
return;
}
else if (!Caster.CanBeginAction<PolymorphSpell>() || Caster.IsBodyMod)
if (!Caster.CanBeginAction<PolymorphSpell>() || Caster.IsBodyMod)
{
DoFizzle();
return;
}
else if (CheckSequence())
if (CheckSequence())
{
if (Caster.BeginAction<IncognitoSpell>())
{
@ -98,19 +108,10 @@ namespace Server.Spells.Fifth
StopTimer(Caster);
var timeVal = 6 * Caster.Skills.Magery.Fixed / 50 + 1;
if (timeVal > 144)
{
timeVal = 144;
}
var timeVal = Math.Min(6 * Caster.Skills.Magery.Fixed / 50 + 1, 144);
var length = TimeSpan.FromSeconds(timeVal);
var t = new InternalTimer(Caster, length);
m_Timers[Caster] = t;
t.Start();
m_Table[Caster] = Timer.DelayCall(length, EndIncognito, Caster);
BuffInfo.AddBuff(Caster, new BuffInfo(BuffIcon.Incognito, 1075819, length, Caster));
}
@ -125,52 +126,31 @@ namespace Server.Spells.Fifth
public static void StopTimer(Mobile m)
{
if (!m_Timers.TryGetValue(m, out var t))
if (m_Table.Remove(m, out var t))
{
t.Stop();
}
BuffInfo.RemoveBuff(m, BuffIcon.Incognito);
}
private static void EndIncognito(Mobile m)
{
if (m.CanBeginAction<IncognitoSpell>())
{
return;
}
t.Stop();
m_Timers.Remove(m);
BuffInfo.RemoveBuff(m, BuffIcon.Incognito);
}
(m as PlayerMobile)?.SetHairMods(-1, -1);
private class InternalTimer : Timer
{
private readonly Mobile m_Owner;
m.BodyMod = 0;
m.HueMod = -1;
m.NameMod = null;
m.EndAction<IncognitoSpell>();
public InternalTimer(Mobile owner, TimeSpan length) : base(length)
{
m_Owner = owner;
/*
int val = ((6 * owner.Skills.Magery.Fixed) / 50) + 1;
if (val > 144)
val = 144;
Delay = TimeSpan.FromSeconds( val );
* */
Priority = TimerPriority.OneSecond;
}
protected override void OnTick()
{
if (m_Owner.CanBeginAction<IncognitoSpell>())
{
return;
}
(m_Owner as PlayerMobile)?.SetHairMods(-1, -1);
m_Owner.BodyMod = 0;
m_Owner.HueMod = -1;
m_Owner.NameMod = null;
m_Owner.EndAction<IncognitoSpell>();
BaseArmor.ValidateMobile(m_Owner);
BaseClothing.ValidateMobile(m_Owner);
}
BaseArmor.ValidateMobile(m);
BaseClothing.ValidateMobile(m);
}
}
}

View file

@ -17,8 +17,7 @@ namespace Server.Spells.Mysticism
Reagent.SulfurousAsh
);
private static readonly Dictionary<Mobile, SpellPlagueContext>
m_Table = new();
private static readonly Dictionary<Mobile, SpellPlagueTimer> m_Table = new();
public SpellPlagueSpell(Mobile caster, Item scroll = null)
: base(caster, scroll, m_Info)
@ -67,16 +66,16 @@ namespace Server.Spells.Mysticism
var damage = GetNewAosDamage(33, 1, 5, targeted);
SpellHelper.Damage(this, targeted, damage, 0, 0, 0, 0, 0);
var context = new SpellPlagueContext(this, targeted);
var timer = new SpellPlagueTimer(this, targeted);
if (m_Table.TryGetValue(targeted, out var oldContext))
if (m_Table.TryGetValue(targeted, out var oldtimer))
{
oldContext.SetNext(context);
oldtimer.SetNext(timer);
}
else
{
m_Table[targeted] = context;
context.Start();
m_Table[targeted] = timer;
timer.StartPlague();
}
}
@ -114,94 +113,86 @@ namespace Server.Spells.Mysticism
to.FixedParticles(0x3779, 1, 15, 0x251E, 0x43, 7, EffectLayer.Head, 0);
}
private class SpellPlagueContext
private class SpellPlagueTimer : Timer
{
private readonly SpellPlagueSpell m_Owner;
private readonly Mobile m_Target;
private int m_Explosions;
private DateTime m_LastExploded;
private SpellPlagueContext m_Next;
private Timer m_Timer;
private SpellPlagueTimer m_Next;
public SpellPlagueContext(SpellPlagueSpell owner, Mobile target)
public SpellPlagueTimer(SpellPlagueSpell owner, Mobile target) : base(TimeSpan.FromSeconds(8.0))
{
m_Owner = owner;
m_Target = target;
}
public void SetNext(SpellPlagueContext context)
public void SetNext(SpellPlagueTimer timer)
{
if (m_Next == null)
{
m_Next = context;
m_Next = timer;
}
else
{
m_Next.SetNext(context);
m_Next.SetNext(timer);
}
}
public void Start()
public void StartPlague()
{
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(8.0), EndPlague);
m_Timer.Start();
BuffInfo.AddBuff(
m_Target,
new BuffInfo(BuffIcon.SpellPlague, 1031690, 1080167, TimeSpan.FromSeconds(8.5), m_Target)
);
Start();
}
public void OnDamage()
{
if (DateTime.Now > m_LastExploded + TimeSpan.FromSeconds(2.0))
if (DateTime.Now <= m_LastExploded + TimeSpan.FromSeconds(2.0))
{
var exploChance = 90 - m_Explosions * 30;
return;
}
var resist = m_Target.Skills.MagicResist.Value;
var exploChance = 90 - m_Explosions * 30;
if (resist >= 70)
var resist = m_Target.Skills.MagicResist.Value;
if (resist >= 70)
{
exploChance -= (int)((resist - 70.0) * 3.0 / 10.0);
}
if (exploChance > Utility.Random(100))
{
m_Owner.VisualEffect(m_Target);
var damage = m_Owner.GetNewAosDamage(15 + m_Explosions * 3, 1, 5, m_Target);
m_Explosions++;
m_LastExploded = DateTime.Now;
SpellHelper.Damage(m_Owner, m_Target, damage, 0, 0, 0, 0, 0, 100);
if (m_Explosions >= 3)
{
exploChance -= (int)((resist - 70.0) * 3.0 / 10.0);
}
if (exploChance > Utility.Random(100))
{
m_Owner.VisualEffect(m_Target);
var damage = m_Owner.GetNewAosDamage(15 + m_Explosions * 3, 1, 5, m_Target);
m_Explosions++;
m_LastExploded = DateTime.Now;
SpellHelper.Damage(m_Owner, m_Target, damage, 0, 0, 0, 0, 0, 100);
if (m_Explosions >= 3)
{
EndPlague();
}
EndPlague();
}
}
}
private void EndPlague()
public void EndPlague(bool restart = true)
{
EndPlague(true);
}
public void EndPlague(bool restart)
{
m_Timer?.Stop();
if (restart && m_Next != null)
{
m_Table[m_Target] = m_Next;
m_Next.Start();
m_Next.StartPlague();
}
else
{
m_Table.Remove(m_Target);
BuffInfo.RemoveBuff(m_Target, BuffIcon.SpellPlague);
}
}

View file

@ -73,14 +73,13 @@ namespace Server.Spells.Necromancy
public static void ClearMindRotScalar(Mobile m)
{
if (!m_Table.Remove(m, out var tmpB))
if (m_Table.Remove(m, out var tmpB))
{
return;
tmpB.m_MRExpireTimer.Stop();
m.SendLocalizedMessage(1060872); // Your mind feels normal again.
}
BuffInfo.RemoveBuff(m, BuffIcon.Mindrot);
tmpB.m_MRExpireTimer.Stop();
m.SendLocalizedMessage(1060872); // Your mind feels normal again.
}
public static bool HasMindRotScalar(Mobile m) => m_Table.ContainsKey(m);

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Server.Items;
using Server.SkillHandlers;
@ -7,7 +8,7 @@ namespace Server.Spells.Ninjitsu
{
public class DeathStrike : NinjaMove
{
private static readonly Dictionary<Mobile, DeathStrikeInfo> m_Table = new();
private static readonly Dictionary<Mobile, DeathStrikeTimer> m_Table = new();
public override int BaseMana => 30;
public override double RequiredSkill => 85.0;
@ -50,16 +51,16 @@ namespace Server.Spells.Ninjitsu
var damageBonus = 0;
if (m_Table.Remove(defender, out var info))
if (m_Table.Remove(defender, out var timer))
{
defender.SendLocalizedMessage(1063092); // Your opponent lands another Death Strike!
if (info.m_Steps > 0)
if (timer.Steps > 0)
{
damageBonus = attacker.Skills.Ninjitsu.Fixed / 150;
}
info.m_Timer?.Stop();
timer.Stop();
}
else
{
@ -71,113 +72,108 @@ namespace Server.Spells.Ninjitsu
defender.FixedParticles(0x374A, 1, 17, 0x26BC, EffectLayer.Waist);
attacker.PlaySound(attacker.Female ? 0x50D : 0x50E);
info = new DeathStrikeInfo(defender, attacker, damageBonus, isRanged)
{
m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(5.0), ProcessDeathStrike, defender)
};
var t = new DeathStrikeTimer(defender, attacker, damageBonus, isRanged);
m_Table[defender] = info;
m_Table[defender] = t;
t.Start();
CheckGain(attacker);
}
public static void AddStep(Mobile m)
{
if (m_Table.TryGetValue(m, out var info) && ++info.m_Steps >= 5)
if (m_Table.TryGetValue(m, out var timer) && ++timer.Steps >= 5)
{
ProcessDeathStrike(m);
timer.ProcessDeathStrike();
}
}
private static void ProcessDeathStrike(Mobile defender)
{
if (!m_Table.Remove(defender, out var info))
{
return;
}
int damage;
var ninjitsu = info.m_Attacker.Skills.Ninjitsu.Value;
var stalkingBonus = Tracking.GetStalkingBonus(info.m_Attacker, info.m_Target);
if (Core.ML)
{
var scalar = (info.m_Attacker.Skills.Hiding.Value +
info.m_Attacker.Skills.Stealth.Value) / 220;
if (scalar > 1)
{
scalar = 1;
}
// New formula doesn't apply DamageBonus anymore, caps must be, directly, 60/30.
if (info.m_Steps >= 5)
{
damage = (int)Math.Floor(Math.Min(60, ninjitsu / 3 * (0.3 + 0.7 * scalar) + stalkingBonus));
}
else
{
damage = (int)Math.Floor(Math.Min(30, ninjitsu / 9 * (0.3 + 0.7 * scalar) + stalkingBonus));
}
if (info.m_isRanged)
{
damage /= 2;
}
}
else
{
var divisor = info.m_Steps >= 5 ? 30 : 80;
var baseDamage = ninjitsu / divisor * 10;
var maxDamage = info.m_Steps >= 5 ? 62 : 22;
damage = Math.Clamp((int)(baseDamage + stalkingBonus), 0, maxDamage) + info.m_DamageBonus;
}
if (Core.ML)
{
info.m_Target.Damage(damage, info.m_Attacker); // Damage is direct.
}
else
{
AOS.Damage(
info.m_Target,
info.m_Attacker,
damage,
true,
100,
0,
0,
0,
0,
0,
0,
false,
false,
true
); // Damage is physical.
}
info.m_Timer?.Stop();
}
private class DeathStrikeInfo
private class DeathStrikeTimer : Timer
{
public readonly Mobile m_Attacker;
public readonly int m_DamageBonus;
public readonly bool m_isRanged;
public readonly Mobile m_Target;
public int m_Steps;
public Timer m_Timer;
public int Steps { get; set; }
public DeathStrikeInfo(Mobile target, Mobile attacker, int damageBonus, bool isRanged)
internal DeathStrikeTimer(Mobile target, Mobile attacker, int damageBonus, bool isRanged)
: base(TimeSpan.FromSeconds(5.0))
{
m_Target = target;
m_Attacker = attacker;
m_DamageBonus = damageBonus;
m_isRanged = isRanged;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void OnTick()
{
ProcessDeathStrike();
}
public void ProcessDeathStrike()
{
int damage;
var ninjitsu = m_Attacker.Skills.Ninjitsu.Value;
var stalkingBonus = Tracking.GetStalkingBonus(m_Attacker, m_Target);
if (Core.ML)
{
var scalar = Math.Min(1, (m_Attacker.Skills.Hiding.Value +
m_Attacker.Skills.Stealth.Value) / 220);
// New formula doesn't apply DamageBonus anymore, caps must be, directly, 60/30.
if (Steps >= 5)
{
damage = (int)Math.Floor(Math.Min(60, ninjitsu / 3 * (0.3 + 0.7 * scalar) + stalkingBonus));
}
else
{
damage = (int)Math.Floor(Math.Min(30, ninjitsu / 9 * (0.3 + 0.7 * scalar) + stalkingBonus));
}
if (m_isRanged)
{
damage /= 2;
}
}
else
{
var divisor = Steps >= 5 ? 30 : 80;
var baseDamage = ninjitsu / divisor * 10;
var maxDamage = Steps >= 5 ? 62 : 22;
damage = Math.Clamp((int)(baseDamage + stalkingBonus), 0, maxDamage) + m_DamageBonus;
}
if (Core.ML)
{
m_Target.Damage(damage, m_Attacker); // Damage is direct.
}
else
{
AOS.Damage(
m_Target,
m_Attacker,
damage,
true,
100,
0,
0,
0,
0,
0,
0,
false,
false,
true
); // Damage is physical.
}
Stop();
}
}
}
}

View file

@ -6,7 +6,7 @@ namespace Server.Spells.Ninjitsu
{
public class KiAttack : NinjaMove
{
private static readonly Dictionary<Mobile, KiAttackInfo> m_Table = new();
private static readonly Dictionary<Mobile, KiAttackTimer> m_Table = new();
public override int BaseMana => 25;
public override double RequiredSkill => 80.0;
@ -21,10 +21,9 @@ namespace Server.Spells.Ninjitsu
return;
}
var info = new KiAttackInfo(from);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(2.0), EndKiAttack, info);
m_Table[from] = info;
var t = new KiAttackTimer(from);
m_Table[from] = t;
t.Start();
}
public override bool Validate(Mobile from)
@ -87,53 +86,43 @@ namespace Server.Spells.Ninjitsu
public override void OnClearMove(Mobile from)
{
if (m_Table.Remove(from, out var info))
if (m_Table.Remove(from, out var t))
{
info.m_Timer.Stop();
t.Stop();
}
}
public static double GetBonus(Mobile from)
{
if (!m_Table.TryGetValue(from, out var info))
if (!m_Table.TryGetValue(from, out var t))
{
return 0;
}
var xDelta = info.m_Location.X - from.X;
var yDelta = info.m_Location.Y - from.Y;
var xDelta = t.m_Location.X - from.X;
var yDelta = t.m_Location.Y - from.Y;
var bonus = Math.Sqrt(xDelta * xDelta + yDelta * yDelta);
if (bonus > 20.0)
{
bonus = 20.0;
}
return bonus;
return Math.Min(Math.Sqrt(xDelta * xDelta + yDelta * yDelta), 20.0);
}
private static void EndKiAttack(KiAttackInfo info)
{
info.m_Timer?.Stop();
ClearCurrentMove(info.m_Mobile);
info.m_Mobile.SendLocalizedMessage(1063102); // You failed to complete your Ki Attack in time.
m_Table.Remove(info.m_Mobile);
}
private class KiAttackInfo
private class KiAttackTimer : Timer
{
public readonly Mobile m_Mobile;
public Point3D m_Location;
public Timer m_Timer;
public KiAttackInfo(Mobile m)
public KiAttackTimer(Mobile m) : base(TimeSpan.FromSeconds(2.0))
{
m_Mobile = m;
m_Location = m.Location;
}
protected override void OnTick()
{
ClearCurrentMove(m_Mobile);
m_Mobile.SendLocalizedMessage(1063102); // You failed to complete your Ki Attack in time.
m_Table.Remove(m_Mobile);
}
}
}
}

View file

@ -19,7 +19,7 @@ namespace Server.Spells.Seventh
Reagent.MandrakeRoot
);
private static readonly Dictionary<Mobile, InternalTimer> m_Timers = new();
private static readonly Dictionary<Mobile, Timer> m_Table = new();
private readonly int m_NewBody;
@ -29,45 +29,47 @@ namespace Server.Spells.Seventh
public override bool CheckCast()
{
/*if (Caster.Mounted)
var caster = Caster;
/*if (caster.Mounted)
{
Caster.SendLocalizedMessage( 1042561 ); //Please dismount first.
caster.SendLocalizedMessage( 1042561 ); //Please dismount first.
return false;
}
else */
if (Sigil.ExistsOn(Caster))
if (Sigil.ExistsOn(caster))
{
Caster.SendLocalizedMessage(1010521); // You cannot polymorph while you have a Town Sigil
caster.SendLocalizedMessage(1010521); // You cannot polymorph while you have a Town Sigil
return false;
}
if (TransformationSpellHelper.UnderTransformation(Caster))
if (TransformationSpellHelper.UnderTransformation(caster))
{
Caster.SendLocalizedMessage(1061633); // You cannot polymorph while in that form.
caster.SendLocalizedMessage(1061633); // You cannot polymorph while in that form.
return false;
}
if (DisguiseTimers.IsDisguised(Caster))
if (DisguiseTimers.IsDisguised(caster))
{
Caster.SendLocalizedMessage(502167); // You cannot polymorph while disguised.
caster.SendLocalizedMessage(502167); // You cannot polymorph while disguised.
return false;
}
if (Caster.BodyMod == 183 || Caster.BodyMod == 184)
if (caster.BodyMod == 183 || caster.BodyMod == 184)
{
Caster.SendLocalizedMessage(1042512); // You cannot polymorph while wearing body paint
caster.SendLocalizedMessage(1042512); // You cannot polymorph while wearing body paint
return false;
}
if (!Caster.CanBeginAction<PolymorphSpell>())
if (!caster.CanBeginAction<PolymorphSpell>())
{
if (Core.ML)
{
EndPolymorph(Caster);
EndPolymorph(caster);
}
else
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
caster.SendLocalizedMessage(1005559); // This spell is already in effect.
}
return false;
@ -75,9 +77,9 @@ namespace Server.Spells.Seventh
if (m_NewBody == 0)
{
var gump = Core.SE ? (Gump)new NewPolymorphGump(Caster, Scroll) : new PolymorphGump(Caster, Scroll);
var gump = Core.SE ? (Gump)new NewPolymorphGump(caster, Scroll) : new PolymorphGump(caster, Scroll);
Caster.SendGump(gump);
caster.SendGump(gump);
return false;
}
@ -86,51 +88,67 @@ namespace Server.Spells.Seventh
public override void OnCast()
{
/*if (Caster.Mounted)
var caster = Caster;
/*if (caster.Mounted)
{
Caster.SendLocalizedMessage( 1042561 ); //Please dismount first.
}
else */
if (Sigil.ExistsOn(Caster))
caster.SendLocalizedMessage(1042561); // Please dismount first.
return;
}*/
if (Sigil.ExistsOn(caster))
{
Caster.SendLocalizedMessage(1010521); // You cannot polymorph while you have a Town Sigil
caster.SendLocalizedMessage(1010521); // You cannot polymorph while you have a Town Sigil
return;
}
else if (!Caster.CanBeginAction<PolymorphSpell>())
if (!caster.CanBeginAction<PolymorphSpell>())
{
if (Core.ML)
{
EndPolymorph(Caster);
EndPolymorph(caster);
}
else
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
caster.SendLocalizedMessage(1005559); // This spell is already in effect.
}
return;
}
else if (TransformationSpellHelper.UnderTransformation(Caster))
if (TransformationSpellHelper.UnderTransformation(caster))
{
Caster.SendLocalizedMessage(1061633); // You cannot polymorph while in that form.
caster.SendLocalizedMessage(1061633); // You cannot polymorph while in that form.
return;
}
else if (DisguiseTimers.IsDisguised(Caster))
if (DisguiseTimers.IsDisguised(caster))
{
Caster.SendLocalizedMessage(502167); // You cannot polymorph while disguised.
caster.SendLocalizedMessage(502167); // You cannot polymorph while disguised.
return;
}
else if (Caster.BodyMod == 183 || Caster.BodyMod == 184)
if (caster.BodyMod == 183 || caster.BodyMod == 184)
{
Caster.SendLocalizedMessage(1042512); // You cannot polymorph while wearing body paint
caster.SendLocalizedMessage(1042512); // You cannot polymorph while wearing body paint
return;
}
else if (!Caster.CanBeginAction<IncognitoSpell>() || Caster.IsBodyMod)
if (!caster.CanBeginAction<IncognitoSpell>() || caster.IsBodyMod)
{
DoFizzle();
return;
}
else if (CheckSequence())
if (CheckSequence())
{
if (Caster.BeginAction<PolymorphSpell>())
if (caster.BeginAction<PolymorphSpell>())
{
if (m_NewBody != 0)
{
if (!((Body)m_NewBody).IsHuman)
{
var mt = Caster.Mount;
var mt = caster.Mount;
if (mt != null)
{
@ -138,35 +156,33 @@ namespace Server.Spells.Seventh
}
}
Caster.BodyMod = m_NewBody;
caster.BodyMod = m_NewBody;
if (m_NewBody == 400 || m_NewBody == 401)
{
Caster.HueMod = Caster.Race.RandomSkinHue();
caster.HueMod = caster.Race.RandomSkinHue();
}
else
{
Caster.HueMod = 0;
caster.HueMod = 0;
}
BaseArmor.ValidateMobile(Caster);
BaseClothing.ValidateMobile(Caster);
BaseArmor.ValidateMobile(caster);
BaseClothing.ValidateMobile(caster);
if (!Core.ML)
{
StopTimer(Caster);
StopTimer(caster);
var timer = new InternalTimer(Caster);
var duration = Math.Max((int)caster.Skills.Magery.Value, 120);
m_Timers[Caster] = timer;
timer.Start();
m_Table[caster] = Timer.DelayCall(TimeSpan.FromSeconds(duration), EndPolymorph, caster);
}
}
}
else
{
Caster.SendLocalizedMessage(1005559); // This spell is already in effect.
caster.SendLocalizedMessage(1005559); // This spell is already in effect.
}
}
@ -175,13 +191,10 @@ namespace Server.Spells.Seventh
public static void StopTimer(Mobile m)
{
if (!m_Timers.TryGetValue(m, out var timer))
if (m_Table.Remove(m, out var timer))
{
return;
timer.Stop();
}
timer?.Stop();
m_Timers.Remove(m);
}
private static void EndPolymorph(Mobile m)
@ -198,30 +211,5 @@ namespace Server.Spells.Seventh
BaseArmor.ValidateMobile(m);
BaseClothing.ValidateMobile(m);
}
private class InternalTimer : Timer
{
private readonly Mobile m_Owner;
public InternalTimer(Mobile owner) : base(TimeSpan.FromSeconds(0))
{
m_Owner = owner;
var val = (int)owner.Skills.Magery.Value;
if (val > 120)
{
val = 120;
}
Delay = TimeSpan.FromSeconds(val);
Priority = TimerPriority.OneSecond;
}
protected override void OnTick()
{
EndPolymorph(m_Owner);
}
}
}
}

View file

@ -62,19 +62,21 @@ namespace Server.Spells.Sixth
var duration = TimeSpan.FromSeconds(1.2 * Caster.Skills.Magery.Fixed / 10);
Timer t = new InternalTimer(m, duration);
BuffInfo.RemoveBuff(m, BuffIcon.HidingAndOrStealth);
BuffInfo.AddBuff(m, new BuffInfo(BuffIcon.Invisibility, 1075825, duration, m)); // Invisibility/Invisible
m_Table[m] = t;
t.Start();
m_Table[m] = Timer.DelayCall(duration, EndInvisiblity, m);
}
FinishSequence();
}
private static void EndInvisiblity(Mobile m)
{
m.RevealingAction();
RemoveTimer(m);
}
public override bool CheckCast()
{
if (DuelContext.CheckSuddenDeath(Caster))
@ -100,22 +102,5 @@ namespace Server.Spells.Sixth
t.Stop();
}
}
private class InternalTimer : Timer
{
private readonly Mobile m_Mobile;
public InternalTimer(Mobile m, TimeSpan duration) : base(duration)
{
Priority = TimerPriority.OneSecond;
m_Mobile = m;
}
protected override void OnTick()
{
m_Mobile.RevealingAction();
RemoveTimer(m_Mobile);
}
}
}
}

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);
}
}
}