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

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