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

@ -15,10 +15,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
from.SendLocalizedMessage(
1063347,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}

View file

@ -14,7 +14,7 @@ namespace Server.Items
/// </summary>
public class BleedAttack : WeaponAbility
{
private static readonly Dictionary<Mobile, Timer> m_Table = new();
private static readonly Dictionary<Mobile, Timer> _table = new();
public override int BaseMana => 30;
@ -46,9 +46,9 @@ namespace Server.Items
defender.NonlocalOverheadMessage(
MessageType.Regular,
0x21,
1060758,
1060758, // ~1_NAME~ is bleeding profusely
defender.Name
); // ~1_NAME~ is bleeding profusely
);
}
defender.PlaySound(0x133);
@ -57,15 +57,15 @@ namespace Server.Items
BeginBleed(defender, attacker);
}
public static bool IsBleeding(Mobile m) => m_Table.ContainsKey(m);
public static bool IsBleeding(Mobile m) => _table.ContainsKey(m);
public static void BeginBleed(Mobile m, Mobile from)
{
m_Table.TryGetValue(m, out var t);
t?.Stop();
_table.TryGetValue(m, out var timer);
timer?.Stop();
m_Table[m] = t = new InternalTimer(from, m);
t.Start();
_table[m] = timer = new InternalTimer(from, m);
timer.Start();
}
public static void DoBleed(Mobile m, Mobile from, int level)
@ -93,7 +93,7 @@ namespace Server.Items
public static void EndBleed(Mobile m, bool message)
{
if (!m_Table.Remove(m, out var t))
if (!_table.Remove(m, out var t))
{
return;
}
@ -110,9 +110,8 @@ namespace Server.Items
{
private readonly Mobile m_From;
private readonly Mobile m_Mobile;
private int m_Count;
public InternalTimer(Mobile from, Mobile m) : base(TimeSpan.FromSeconds(2.0), TimeSpan.FromSeconds(2.0))
public InternalTimer(Mobile from, Mobile m) : base(TimeSpan.FromSeconds(2.0), TimeSpan.FromSeconds(2.0), 5)
{
m_From = from;
m_Mobile = m;
@ -121,9 +120,9 @@ namespace Server.Items
protected override void OnTick()
{
DoBleed(m_Mobile, m_From, 5 - m_Count);
DoBleed(m_Mobile, m_From, 5 - Index);
if (++m_Count == 5)
if (Index == 4)
{
EndBleed(m_Mobile, true);
}

View file

@ -8,7 +8,7 @@ namespace Server.Items
/// </summary>
public class Block : WeaponAbility
{
private static readonly Dictionary<Mobile, BlockInfo> m_Table = new();
private static readonly Dictionary<Mobile, InternalTimer> _table = new();
public override int BaseMana => 30;
@ -16,10 +16,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
from.SendLocalizedMessage(
1063347,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
@ -50,7 +48,7 @@ namespace Server.Items
public static bool GetBonus(Mobile targ, ref int bonus)
{
if (!m_Table.TryGetValue(targ, out var info))
if (!_table.TryGetValue(targ, out var info))
{
return false;
}
@ -62,35 +60,25 @@ namespace Server.Items
public static void BeginBlock(Mobile m, int bonus)
{
EndBlock(m);
m_Table[m] = new BlockInfo(m, bonus);
_table[m] = new InternalTimer(m, bonus);
}
public static void EndBlock(Mobile m)
{
if (m_Table.Remove(m, out var info))
if (_table.Remove(m, out var timer))
{
info.m_Timer?.Stop();
}
}
private class BlockInfo
{
public readonly int m_Bonus;
public readonly Timer m_Timer;
public BlockInfo(Mobile target, int bonus)
{
m_Bonus = bonus;
m_Timer = new InternalTimer(target);
timer?.Stop();
}
}
private class InternalTimer : Timer
{
private readonly Mobile m_Mobile;
public readonly int m_Bonus;
public InternalTimer(Mobile m) : base(TimeSpan.FromSeconds(6.0))
public InternalTimer(Mobile m, int bonus) : base(TimeSpan.FromSeconds(6.0))
{
m_Bonus = bonus;
m_Mobile = m;
Priority = TimerPriority.TwoFiftyMS;
}

View file

@ -9,8 +9,7 @@ namespace Server.Items
/// </summary>
public class DefenseMastery : WeaponAbility
{
private static readonly Dictionary<Mobile, DefenseMasteryInfo>
m_Table = new();
private static readonly Dictionary<Mobile, DefenseMasteryInfo> _table = new();
public override int BaseMana => 30;
@ -18,10 +17,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
from.SendLocalizedMessage(
1063347,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
@ -46,7 +43,7 @@ namespace Server.Items
((Math.Max(attacker.Skills.Bushido.Value, attacker.Skills.Ninjitsu.Value) -
50.0) / 70.0));
if (m_Table.TryGetValue(attacker, out var info))
if (_table.TryGetValue(attacker, out var info))
{
EndDefense(info);
}
@ -57,14 +54,14 @@ namespace Server.Items
info = new DefenseMasteryInfo(attacker, 80 - modifier, mod);
info.m_Timer = Timer.DelayCall(TimeSpan.FromSeconds(3.0), EndDefense, info);
m_Table[attacker] = info;
_table[attacker] = info;
attacker.Delta(MobileDelta.WeaponDamage);
}
public static bool GetMalus(Mobile targ, ref int damageMalus)
{
if (!m_Table.TryGetValue(targ, out var info))
if (!_table.TryGetValue(targ, out var info))
{
return false;
}
@ -84,7 +81,7 @@ namespace Server.Items
// No message is sent to the player.
m_Table.Remove(info.m_From);
_table.Remove(info.m_From);
info.m_From.Delta(MobileDelta.WeaponDamage);
}

View file

@ -17,17 +17,17 @@ namespace Server.Items
{
if (!base.CheckSkills( from ))
return false;
if (!(from.Weapon is Fists))
return true;
Skill skill = from.Skills.ArmsLore;
if (skill?.Base >= 80.0)
return true;
from.SendLocalizedMessage( 1061812 ); // You lack the required skill in armslore to perform that attack!
return false;
}*/
@ -43,7 +43,7 @@ namespace Server.Items
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker))
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
@ -67,7 +67,7 @@ namespace Server.Items
{
attacker.SendLocalizedMessage(1060849); // Your target is already unarmed!
}
else if (CheckMana(attacker, true))
else
{
attacker.SendLocalizedMessage(1060092); // You disarm their weapon!
defender.SendLocalizedMessage(1060093); // Your weapon has been disarmed!

View file

@ -34,7 +34,7 @@ namespace Server.Items
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker))
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
@ -60,11 +60,6 @@ namespace Server.Items
return;
}
if (!CheckMana(attacker, true))
{
return;
}
if (Core.ML && attacker is LesserHiryu && Utility.RandomDouble() <= 0.8)
{
return; // Lesser Hiryu have an 80% chance of missing this attack

View file

@ -13,7 +13,7 @@ namespace Server.Items
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker))
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
@ -32,7 +32,7 @@ namespace Server.Items
{
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
}
else if (CheckMana(attacker, true))
else
{
// attacker.SendLocalizedMessage( 1060092 ); // You disarm their weapon!
defender.SendLocalizedMessage(1062002); // You can no longer wear your ~1_ARMOR~

View file

@ -11,10 +11,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
from.SendLocalizedMessage(
1063347,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}

View file

@ -16,10 +16,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0)
{
from.SendLocalizedMessage(
1063352,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063352, "50");
return false;
}

View file

@ -16,10 +16,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
from.SendLocalizedMessage(
1063347,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}

View file

@ -19,10 +19,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
{
from.SendLocalizedMessage(
1063347,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063347, "50");
return false;
}
@ -31,7 +29,7 @@ namespace Server.Items
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker)) // Mana check after check that there are targets
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}

View file

@ -23,7 +23,7 @@ namespace Server.Items
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker))
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
@ -39,14 +39,8 @@ namespace Server.Items
if (p == null || weapon.PoisonCharges <= 0)
{
attacker.SendLocalizedMessage(
1061141
); // Your weapon must have a dose of poison to perform an infectious strike!
return;
}
if (!CheckMana(attacker, true))
{
// Your weapon must have a dose of poison to perform an infectious strike!
attacker.SendLocalizedMessage(1061141);
return;
}

View file

@ -13,7 +13,7 @@ namespace Server.Items
public static readonly TimeSpan PlayerDuration = TimeSpan.FromSeconds(6.0);
public static readonly TimeSpan NPCDuration = TimeSpan.FromSeconds(12.0);
private static readonly Dictionary<Mobile, InternalTimer> m_Table = new();
private static readonly Dictionary<Mobile, Timer> _table = new();
public override int BaseMana => 30;
@ -39,16 +39,16 @@ namespace Server.Items
}
}
public static bool IsWounded(Mobile m) => m_Table.ContainsKey(m);
public static bool IsWounded(Mobile m) => _table.ContainsKey(m);
public static void BeginWound(Mobile m, TimeSpan duration)
{
if (m_Table.TryGetValue(m, out var timer))
if (_table.TryGetValue(m, out var timer))
{
timer?.Stop();
}
m_Table[m] = timer = new InternalTimer(m, duration);
_table[m] = timer = Timer.DelayCall(duration, EndWound, m);
timer.Start();
m.YellowHealthbar = true;
@ -56,7 +56,7 @@ namespace Server.Items
public static void EndWound(Mobile m)
{
if (m_Table.Remove(m, out var timer))
if (_table.Remove(m, out var timer))
{
timer.Stop();
}
@ -64,21 +64,5 @@ namespace Server.Items
m.YellowHealthbar = false;
m.SendLocalizedMessage(1060208); // You are no longer mortally wounded.
}
private class InternalTimer : Timer
{
private readonly Mobile m_Mobile;
public InternalTimer(Mobile m, TimeSpan duration) : base(duration)
{
m_Mobile = m;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
EndWound(m_Mobile);
}
}
}
}

View file

@ -13,10 +13,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Bushido) < 50.0)
{
from.SendLocalizedMessage(
1070768,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
from.SendLocalizedMessage(1070768, "50");
return false;
}

View file

@ -13,7 +13,7 @@ namespace Server.Items
public static readonly TimeSpan FreezeDelayDuration = TimeSpan.FromSeconds(8.0);
private static readonly Dictionary<Mobile, InternalTimer> m_Table = new();
private static readonly Dictionary<Mobile, Timer> _table = new();
public override int BaseMana => 30;
@ -80,41 +80,25 @@ namespace Server.Items
BeginImmunity(defender, duration + FreezeDelayDuration);
}
public static bool IsImmune(Mobile m) => m_Table.ContainsKey(m);
public static bool IsImmune(Mobile m) => _table.ContainsKey(m);
public static void BeginImmunity(Mobile m, TimeSpan duration)
{
if (m_Table.TryGetValue(m, out var timer))
if (_table.TryGetValue(m, out var timer))
{
timer?.Stop();
}
m_Table[m] = timer = new InternalTimer(m, duration);
_table[m] = timer = Timer.DelayCall(duration, EndImmunity, m);
timer.Start();
}
public static void EndImmunity(Mobile m)
{
if (m_Table.Remove(m, out var timer))
if (_table.Remove(m, out var timer))
{
timer?.Stop();
}
}
private class InternalTimer : Timer
{
private readonly Mobile m_Mobile;
public InternalTimer(Mobile m, TimeSpan duration) : base(duration)
{
m_Mobile = m;
Priority = TimerPriority.TwoFiftyMS;
}
protected override void OnTick()
{
EndImmunity(m_Mobile);
}
}
}
}

View file

@ -18,10 +18,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Bushido) < 50.0)
{
from.SendLocalizedMessage(
1070768,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
from.SendLocalizedMessage(1070768, "50");
return false;
}

View file

@ -8,7 +8,7 @@ namespace Server.Items
/// </summary>
public class TalonStrike : WeaponAbility
{
private static readonly HashSet<Mobile> m_Table = new();
private static readonly HashSet<Mobile> _defenders = new();
public override int BaseMana => 30;
public override double DamageScalar => 1.2;
@ -17,10 +17,8 @@ namespace Server.Items
{
if (GetSkill(from, SkillName.Ninjitsu) < 50.0)
{
from.SendLocalizedMessage(
1063352,
"50"
); // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
// You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
from.SendLocalizedMessage(1063352, "50");
return false;
}
@ -29,7 +27,7 @@ namespace Server.Items
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (m_Table.Contains(defender) || !Validate(attacker) || !CheckMana(attacker, true))
if (_defenders.Contains(defender) || !Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
@ -48,7 +46,7 @@ namespace Server.Items
timer.Start();
m_Table.Add(defender);
_defenders.Add(defender);
}
private class InternalTimer : Timer
@ -58,12 +56,12 @@ namespace Server.Items
private double m_DamageRemaining;
private double m_DamageToDo;
public InternalTimer(Mobile defender, int totalDamage)
: base(
TimeSpan.Zero,
TimeSpan.FromSeconds(0.25),
12
) // 3 seconds at .25 seconds apart = 12. Confirm delay inbetween of .25 each.
// 3 seconds at 0.25 seconds apart = 12. Confirm delay in between of 0.25 each.
public InternalTimer(Mobile defender, int totalDamage) : base(
TimeSpan.Zero,
TimeSpan.FromSeconds(0.25),
12
)
{
m_Defender = defender;
m_DamageRemaining = totalDamage;
@ -77,7 +75,7 @@ namespace Server.Items
if (!m_Defender.Alive || m_DamageRemaining <= 0)
{
Stop();
m_Table.Remove(m_Defender);
_defenders.Remove(m_Defender);
return;
}
@ -101,7 +99,7 @@ namespace Server.Items
if (!m_Defender.Alive || m_DamageRemaining <= 0)
{
Stop();
m_Table.Remove(m_Defender);
_defenders.Remove(m_Defender);
}
}
}

View file

@ -185,10 +185,8 @@ namespace Server.Items
if (Core.ML && reqTactics && from.Skills.Tactics.Base < reqSkill)
{
from.SendLocalizedMessage(
1079308,
reqSkill.ToString()
); // You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
// You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
from.SendLocalizedMessage(1079308, reqSkill.ToString());
return false;
}
@ -208,17 +206,13 @@ namespace Server.Items
if (reqTactics)
{
from.SendLocalizedMessage(
1079308,
reqSkill.ToString()
); // You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
// You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
from.SendLocalizedMessage(1079308, reqSkill.ToString());
}
else
{
from.SendLocalizedMessage(
1060182,
reqSkill.ToString()
); // You need ~1_SKILL_REQUIREMENT~ weapon skill to perform that attack
// You need ~1_SKILL_REQUIREMENT~ weapon skill to perform that attack
from.SendLocalizedMessage(1060182, reqSkill.ToString());
}
return false;
@ -239,10 +233,8 @@ namespace Server.Items
return true;
}
from.SendLocalizedMessage(
1060181,
mana.ToString()
); // You need ~1_MANA_REQUIREMENT~ mana to perform that attack
// You need ~1_MANA_REQUIREMENT~ mana to perform that attack
from.SendLocalizedMessage(1060181, mana.ToString());
return false;
}

View file

@ -14,7 +14,7 @@ namespace Server.Items
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker))
if (!Validate(attacker) || !CheckMana(attacker, true))
{
return;
}
@ -33,11 +33,6 @@ namespace Server.Items
return;
}
if (!CheckMana(attacker, true))
{
return;
}
attacker.FixedEffect(0x3728, 10, 15);
attacker.PlaySound(0x2A1);

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