fix: tighten Arcane Empowerment damage hooks

This commit is contained in:
Crome696 2026-07-10 16:53:59 +02:00
parent 434950ba49
commit 7a40f7f0a7
25 changed files with 87 additions and 42 deletions

View file

@ -130,6 +130,7 @@ public class ArcaneEmpowermentSpellTests
{
var caster = NewCaster();
var target = NewTarget();
var playerTarget = NewPlayerCaster();
var follower = new TestSummon
{
HitsMaxSeed = 100,
@ -148,6 +149,10 @@ public class ArcaneEmpowermentSpellTests
follower.AlterMeleeDamageTo(target, ref damage);
Assert.Equal(120, damage);
damage = 100;
follower.AlterMeleeDamageTo(playerTarget, ref damage);
Assert.Equal(112, damage);
var spellDamage = 100;
ArcaneEmpowermentSpell.ApplySpellDamage(caster, target, ref spellDamage);
Assert.Equal(120, spellDamage);
@ -156,6 +161,16 @@ public class ArcaneEmpowermentSpellTests
ArcaneEmpowermentSpell.ApplyHealing(caster, ref healing);
Assert.Equal(120, healing);
var centralDamageTarget = NewTarget();
centralDamageTarget.Hits = centralDamageTarget.HitsMax;
var centralDamageBefore = centralDamageTarget.Hits;
SpellHelper.Damage(new ArcaneEmpowermentSpell(caster), centralDamageTarget, 10);
Assert.Equal(12, centralDamageBefore - centralDamageTarget.Hits);
var sharedHealTarget = NewTarget();
SpellHelper.Heal(10, sharedHealTarget, caster);
Assert.Equal(11, sharedHealTarget.Hits);
Assert.Equal(70.0, ArcaneEmpowermentSpell.GetDispelDifficulty(follower));
ArcaneEmpowermentSpell.ApplyEffectForTests(caster, 1200, 0);
@ -173,6 +188,9 @@ public class ArcaneEmpowermentSpellTests
caster.Delete();
target.Delete();
playerTarget.Delete();
centralDamageTarget.Delete();
sharedHealTarget.Delete();
follower.Delete();
}
@ -215,6 +233,11 @@ public class ArcaneEmpowermentSpellTests
var damage = 100;
ArcaneEmpowermentSpell.ApplySpellDamage(caster, target, ref damage);
Assert.Equal(115, damage);
damage = 100;
ArcaneEmpowermentSpell.ApplySpellDamage(caster, target, ref damage, sdiAlreadyApplied: true);
Assert.Equal(100, damage);
ArcaneEmpowermentSpell.StopEffect(caster);

View file

@ -4191,7 +4191,7 @@ namespace Server.Mobiles
public virtual void AlterMeleeDamageTo(Mobile to, ref int damage)
{
TriggerAbilityAlterDamage(MonsterAbilityTrigger.GiveMeleeDamage, to, ref damage);
ArcaneEmpowermentSpell.AlterFollowerMeleeDamageTo(this, ref damage);
ArcaneEmpowermentSpell.AlterFollowerMeleeDamageTo(this, to, ref damage);
}
public virtual bool CheckFoodPreference(Item f) =>

View file

@ -930,11 +930,11 @@ namespace Server.Spells
return reflect;
}
public static void Damage(Spell spell, Mobile target, double damage)
public static void Damage(Spell spell, Mobile target, double damage, bool sdiAlreadyApplied = false)
{
var ts = GetDamageDelayForSpell(spell);
Damage(spell, ts, target, spell.Caster, damage);
Damage(spell, ts, target, spell.Caster, damage, sdiAlreadyApplied);
}
public static void Damage(TimeSpan delay, Mobile target, double damage)
@ -947,7 +947,9 @@ namespace Server.Spells
Damage(null, delay, target, from, damage);
}
public static void Damage(Spell spell, TimeSpan delay, Mobile target, Mobile from, double damage)
public static void Damage(
Spell spell, TimeSpan delay, Mobile target, Mobile from, double damage, bool sdiAlreadyApplied = false
)
{
var damageGiven = (int)damage;
@ -964,7 +966,7 @@ namespace Server.Spells
damageGiven = AOS.Scale(damageGiven, 100 + spellFocusingOffset);
}
ArcaneEmpowermentSpell.ApplySpellDamage(from, target, ref damageGiven);
ArcaneEmpowermentSpell.ApplySpellDamage(from, target, ref damageGiven, sdiAlreadyApplied);
target.Damage(damageGiven, from);
@ -978,13 +980,13 @@ namespace Server.Spells
}
else
{
new SpellDamageTimer(spell, target, from, damageGiven, delay).Start();
new SpellDamageTimer(spell, target, from, damageGiven, delay, sdiAlreadyApplied).Start();
}
}
public static void Damage(
Spell spell, Mobile target, double damage, int phys, int fire, int cold, int pois,
int nrgy, int chaos = 0, DFAlgorithm dfa = DFAlgorithm.Standard
int nrgy, int chaos = 0, DFAlgorithm dfa = DFAlgorithm.Standard, bool sdiAlreadyApplied = false
)
{
Damage(
@ -999,7 +1001,8 @@ namespace Server.Spells
pois,
nrgy,
chaos,
dfa
dfa,
sdiAlreadyApplied
);
}
@ -1021,7 +1024,8 @@ namespace Server.Spells
public static void Damage(
Spell spell, TimeSpan delay, Mobile target, Mobile from, double damage, int phys, int fire,
int cold, int pois, int nrgy, int chaos = 0, DFAlgorithm dfa = DFAlgorithm.Standard
int cold, int pois, int nrgy, int chaos = 0, DFAlgorithm dfa = DFAlgorithm.Standard,
bool sdiAlreadyApplied = false
)
{
var dmg = (int)damage;
@ -1039,7 +1043,7 @@ namespace Server.Spells
dmg = AOS.Scale(dmg, 100 + spellFocusingOffset);
}
ArcaneEmpowermentSpell.ApplySpellDamage(from, target, ref dmg);
ArcaneEmpowermentSpell.ApplySpellDamage(from, target, ref dmg, sdiAlreadyApplied);
if (Feint.GetDamageReduction(from, target, out var feintReduction))
{
@ -1064,7 +1068,9 @@ namespace Server.Spells
}
else
{
new SpellDamageTimerAOS(spell, delay, target, from, dmg, phys, fire, cold, pois, nrgy, chaos, dfa).Start();
new SpellDamageTimerAOS(
spell, delay, target, from, dmg, phys, fire, cold, pois, nrgy, chaos, dfa, sdiAlreadyApplied
).Start();
}
}
@ -1093,6 +1099,11 @@ namespace Server.Spells
}
}
public static void Heal(int amount, Mobile target, Mobile from, bool message = true)
{
target.Heal(amount, from, message);
}
public static void HealSpell(int amount, Mobile target, Mobile from, bool message = true)
{
ArcaneEmpowermentSpell.ApplyHealing(from, ref amount);
target.Heal(amount, from, message);
@ -1105,13 +1116,17 @@ namespace Server.Spells
private readonly Mobile m_From;
private readonly Spell m_Spell;
private readonly Mobile m_Target;
private readonly bool m_SdiAlreadyApplied;
private int m_Damage;
public SpellDamageTimer(Spell s, Mobile target, Mobile from, int damage, TimeSpan delay) : base(delay)
public SpellDamageTimer(
Spell s, Mobile target, Mobile from, int damage, TimeSpan delay, bool sdiAlreadyApplied
) : base(delay)
{
m_Target = target;
m_From = from;
m_Damage = damage;
m_SdiAlreadyApplied = sdiAlreadyApplied;
m_Spell = s;
if (m_Spell?.DelayedDamage == true)
@ -1130,7 +1145,7 @@ namespace Server.Spells
m_Damage = AOS.Scale(m_Damage, 100 + spellFocusingOffset);
}
ArcaneEmpowermentSpell.ApplySpellDamage(m_From, m_Target, ref m_Damage);
ArcaneEmpowermentSpell.ApplySpellDamage(m_From, m_Target, ref m_Damage, m_SdiAlreadyApplied);
m_Target.Damage(m_Damage);
m_Spell?.RemoveDelayedDamageContext(m_Target);
@ -1147,13 +1162,14 @@ namespace Server.Spells
private readonly int m_Nrgy;
private readonly int m_Phys;
private readonly int m_Pois;
private readonly bool m_SdiAlreadyApplied;
private readonly Spell m_Spell;
private readonly Mobile m_Target;
private int m_Damage;
public SpellDamageTimerAOS(
Spell s, TimeSpan delay, Mobile target, Mobile from, int damage, int phys, int fire, int cold,
int pois, int nrgy, int chaos, DFAlgorithm dfa
int pois, int nrgy, int chaos, DFAlgorithm dfa, bool sdiAlreadyApplied
) : base(delay)
{
m_Target = target;
@ -1166,6 +1182,7 @@ namespace Server.Spells
m_Nrgy = nrgy;
m_Chaos = chaos;
m_DFA = dfa;
m_SdiAlreadyApplied = sdiAlreadyApplied;
m_Spell = s;
if (m_Spell?.DelayedDamage == true)
@ -1193,7 +1210,8 @@ namespace Server.Spells
m_Pois,
m_Nrgy,
m_Chaos,
m_DFA
m_DFA,
m_SdiAlreadyApplied
);
m_Spell?.RemoveDelayedDamageContext(m_Target);

View file

@ -69,7 +69,7 @@ namespace Server.Spells.Chivalry
toHeal = m.HitsMax - m.Hits;
}
SpellHelper.Heal(toHeal, m, Caster, false);
SpellHelper.HealSpell(toHeal, m, Caster, false);
// You have had ~1_HEALED_AMOUNT~ hit points of damage healed.
m.SendLocalizedMessage(1060203, toHeal.ToString());

View file

@ -102,7 +102,7 @@ namespace Server.Spells.Chivalry
var toHeal = Math.Clamp(ComputePowerValue(10) + Utility.RandomMinMax(0, 2), 8, 24);
Caster.DoBeneficial(m);
SpellHelper.Heal(toHeal, m, Caster);
SpellHelper.HealSpell(toHeal, m, Caster);
sendEffect = true;
}

View file

@ -64,7 +64,7 @@ namespace Server.Spells.First
}
// m.Heal( toHeal, Caster );
SpellHelper.Heal((int)toHeal, m, Caster);
SpellHelper.HealSpell((int)toHeal, m, Caster);
m.FixedParticles(0x376A, 9, 32, 5005, EffectLayer.Waist);
m.PlaySound(0x1F2);

View file

@ -67,7 +67,7 @@ namespace Server.Spells.First
source.MovingParticles(m, 0x36E4, 5, 0, false, false, 0, 0, 3006, 0, 0, EffectLayer.RightHand, 0);
source.PlaySound(0x1E5);
SpellHelper.Damage(this, m, damage, 0, 100, 0, 0, 0);
SpellHelper.Damage(this, m, damage, 0, 100, 0, 0, 0, sdiAlreadyApplied: true);
}
}

View file

@ -52,7 +52,7 @@ namespace Server.Spells.Fourth
toHeal += Utility.Random(1, 10);
// m.Heal( toHeal, Caster );
SpellHelper.Heal(toHeal, m, Caster);
SpellHelper.HealSpell(toHeal, m, Caster);
m.FixedParticles(0x376A, 9, 32, 5030, EffectLayer.Waist);
m.PlaySound(0x202);

View file

@ -53,7 +53,7 @@ namespace Server.Spells.Fourth
m.BoltEffect(0);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 100);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 100, sdiAlreadyApplied: true);
}
}

View file

@ -56,7 +56,9 @@ public class BombardSpell : MysticSpell, ITargetingSpell<Mobile>
Caster.MovingEffect(m, 0x1363, 12, 1, false, true, 0, 0);
Caster.PlaySound(0x64B);
SpellHelper.Damage(this, m, GetNewAosDamage(40, 1, 5, m), 100, 0, 0, 0, 0);
SpellHelper.Damage(
this, m, GetNewAosDamage(40, 1, 5, m), 100, 0, 0, 0, 0, sdiAlreadyApplied: true
);
Timer.StartTimer(TimeSpan.FromSeconds(1.2), () =>
{

View file

@ -127,7 +127,7 @@ public class CleansingWindsSpell : MysticSpell, ITargetingSpell<Mobile>
if (toHealMod > 0)
{
SpellHelper.Heal(toHealMod, target, Caster);
SpellHelper.HealSpell(toHealMod, target, Caster);
}
}
}

View file

@ -68,7 +68,7 @@ public class EagleStrikeSpell : MysticSpell, ITargetingSpell<Mobile>
double damage = GetNewAosDamage(19, 1, 5, to);
SpellHelper.Damage(this, to, damage, 0, 0, 0, 0, 100);
SpellHelper.Damage(this, to, damage, 0, 0, 0, 0, 100, sdiAlreadyApplied: true);
to.PlaySound(0x64D);
}

View file

@ -65,7 +65,7 @@ public class HailStormSpell : MysticSpell, ITargetingSpell<IPoint3D>
{
var m = pool.Dequeue();
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0);
SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0, sdiAlreadyApplied: true);
}
}
}

View file

@ -61,7 +61,9 @@ public class NetherBoltSpell : MysticSpell, ITargetingSpell<Mobile>
);
source.PlaySound(0x211);
SpellHelper.Damage(this, m, GetNewAosDamage(10, 1, 4, m), 0, 0, 0, 0, 0, 100);
SpellHelper.Damage(
this, m, GetNewAosDamage(10, 1, 4, m), 0, 0, 0, 0, 0, 100, sdiAlreadyApplied: true
);
}
}

View file

@ -76,7 +76,7 @@ public class NetherCycloneSpell : MysticSpell, ITargetingSpell<IPoint3D>
{
var m = pool.Dequeue();
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 0, 100);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 0, 100, sdiAlreadyApplied: true);
var resistedReduction = reduction - m.Skills.MagicResist.Value / 800.0;

View file

@ -55,7 +55,7 @@ public class SpellPlagueSpell : MysticSpell, ITargetingSpell<Mobile>
// Before Time of Legends, SDI was not applied to initial damage.
var damage = GetNewAosDamage(33, 1, 5, Core.TOL, m);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 0);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 0, sdiAlreadyApplied: Core.TOL);
var timer = new SpellPlagueTimer(this, m);

View file

@ -74,7 +74,7 @@ namespace Server.Spells.Second
m.PlaySound(0x1F1);
}
SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0);
SpellHelper.Damage(this, m, damage, 0, 0, 100, 0, 0, sdiAlreadyApplied: true);
}
}

View file

@ -84,7 +84,7 @@ namespace Server.Spells.Seventh
toDeal *= GetDamageScalar(m);
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, toDeal, 0, 0, 0, 0, 100);
SpellHelper.Damage(this, m, toDeal, 0, 0, 0, 0, 100, sdiAlreadyApplied: true);
m.BoltEffect(0);
}

View file

@ -54,7 +54,7 @@ namespace Server.Spells.Seventh
m.FixedParticles(0x3709, 10, 30, 5052, EffectLayer.LeftFoot);
m.PlaySound(0x208);
SpellHelper.Damage(this, m, damage, 0, 100, 0, 0, 0);
SpellHelper.Damage(this, m, damage, 0, 100, 0, 0, 0, sdiAlreadyApplied: true);
}
}

View file

@ -93,7 +93,7 @@ namespace Server.Spells.Seventh
toDeal *= GetDamageScalar(m);
Caster.DoHarmful(m);
SpellHelper.Damage(this, m, toDeal, 0, 100, 0, 0, 0);
SpellHelper.Damage(this, m, toDeal, 0, 100, 0, 0, 0, sdiAlreadyApplied: true);
Caster.MovingParticles(m, 0x36D4, 7, 0, false, true, 9501, 1, 0, 0x100);
}

View file

@ -57,7 +57,7 @@ namespace Server.Spells.Sixth
source.MovingParticles(m, 0x379F, 7, 0, false, true, 0, 0, 3043, 4043, 0x211, EffectLayer.RightHand, 0);
source.PlaySound(0x20A);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 100);
SpellHelper.Damage(this, m, damage, 0, 0, 0, 0, 100, sdiAlreadyApplied: true);
}
}

View file

@ -100,7 +100,7 @@ namespace Server.Spells.Sixth
_target.FixedParticles(0x36BD, 20, 10, 5044, EffectLayer.Head);
_target.PlaySound(0x307);
SpellHelper.Damage(_spell, _target, damage, 0, 100, 0, 0, 0);
SpellHelper.Damage(_spell, _target, damage, 0, 100, 0, 0, 0, sdiAlreadyApplied: true);
}
_spell?.RemoveDelayedDamageContext(_attacker);

View file

@ -82,7 +82,7 @@ namespace Server.Spells.Spellweaving
}
}
internal static void ApplySpellDamage(Mobile caster, Mobile target, ref int damage)
internal static void ApplySpellDamage(Mobile caster, Mobile target, ref int damage, bool sdiAlreadyApplied = false)
{
if (damage <= 0 || caster?.Alive != true || target?.Deleted != false ||
!_table.TryGetValue(caster, out var context))
@ -95,9 +95,9 @@ namespace Server.Spells.Spellweaving
if (playerTarget)
{
// GetNewAosDamage already includes item SDI and caps it at 15% in PvP. Arcane
// Empowerment may use only the remaining room so the combined value stays capped.
var itemSdi = AosAttributes.GetValue(caster, AosAttribute.SpellDamage);
// When the generated damage already includes item SDI, Arcane Empowerment may use
// only the remaining room so the combined PvP value stays capped.
var itemSdi = sdiAlreadyApplied ? AosAttributes.GetValue(caster, AosAttribute.SpellDamage) : 0;
bonus = Math.Min(bonus, Math.Max(0, PvPSdiCap - itemSdi));
}
@ -129,9 +129,9 @@ namespace Server.Spells.Spellweaving
return AOS.Scale(baseHitsMax, SummonHealthBonus);
}
internal static void AlterFollowerMeleeDamageTo(BaseCreature follower, ref int damage)
internal static void AlterFollowerMeleeDamageTo(BaseCreature follower, Mobile target, ref int damage)
{
AlterFollowerDamageTo(follower, null, ref damage);
AlterFollowerDamageTo(follower, target, ref damage);
}
internal static void AlterFollowerSpellDamageTo(BaseCreature follower, Mobile target, ref int damage)

View file

@ -138,7 +138,7 @@ namespace Server.Spells.Spellweaving
var toHeal = _hitsPerRound;
SpellHelper.Heal(toHeal, _mobile, _caster);
SpellHelper.HealSpell(toHeal, _mobile, _caster);
_mobile.FixedParticles(0x376A, 9, 32, 5005, EffectLayer.Waist);
}
}

View file

@ -55,7 +55,7 @@ namespace Server.Spells.Third
source.MovingParticles(m, 0x36D4, 7, 0, false, true, 0, 0, 9502, 4019, 0x160, EffectLayer.RightHand, 0);
source.PlaySound(Core.AOS ? 0x15E : 0x44B);
SpellHelper.Damage(this, m, damage, 0, 100, 0, 0, 0);
SpellHelper.Damage(this, m, damage, 0, 100, 0, 0, 0, sdiAlreadyApplied: true);
}
}