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

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