feat: implement Spell Focusing Sash (Closes #22)

Adds the Stygian Abyss-era Spell Focusing Sash with full lifecycle,
equipment stat block, and damage sequence behavior:

- New SpellFocusingSash (BaseMiddleTorso) with Brittle, Mana Increase,
  Defense Chance Increase, +10 Strength Requirement, and 255/255 durability.
- Context-menu enable/disable on the worn sash.
- Sequence damage modifier: -30 -> 0 over six casts, then +2 per cast
  capped at +30 (PvM) or +20 held for five casts (PvP), resetting at the
  peak, on target change, on unequip, on disable, on death, and on logout.
- Centralized eligibility gate (SpellFocusingEligible) and shared
  SpellHelper.Damage hooks, plus a direct hook in PainSpike.
- Brittle is forced on under Core.SA via AOS.IsBrittle.
- Regression tests covering stats/property ordering, enable state
  serialization, context-menu toggle, PvM and PvP sequences, target
  resets, disabled state, and non-eligible spells.
This commit is contained in:
Crome696 2026-07-10 08:03:19 +02:00
parent fa3b9024cb
commit 28513b00dd
18 changed files with 583 additions and 0 deletions

View file

@ -958,6 +958,11 @@ namespace Server.Spells
bcFrom?.AlterSpellDamageTo(target, ref damageGiven);
bcTarget?.AlterSpellDamageFrom(from, ref damageGiven);
if (SpellFocusingSash.TryGetDamageOffset(spell, from, target, out var spellFocusingOffset))
{
damageGiven = AOS.Scale(damageGiven, 100 + spellFocusingOffset);
}
target.Damage(damageGiven, from);
bcFrom?.OnDamageSpell(target, damageGiven);
@ -1026,6 +1031,11 @@ namespace Server.Spells
bcTarget?.AlterSpellDamageFrom(from, ref dmg);
if (SpellFocusingSash.TryGetDamageOffset(spell, from, target, out var spellFocusingOffset))
{
dmg = AOS.Scale(dmg, 100 + spellFocusingOffset);
}
if (Feint.GetDamageReduction(from, target, out var feintReduction))
{
// example: 35 damage * 50 / 100 = 17 damage
@ -1110,6 +1120,11 @@ namespace Server.Spells
(m_From as BaseCreature)?.AlterSpellDamageTo(m_Target, ref m_Damage);
(m_Target as BaseCreature)?.AlterSpellDamageFrom(m_From, ref m_Damage);
if (SpellFocusingSash.TryGetDamageOffset(m_Spell, m_From, m_Target, out var spellFocusingOffset))
{
m_Damage = AOS.Scale(m_Damage, 100 + spellFocusingOffset);
}
m_Target.Damage(m_Damage);
m_Spell?.RemoveDelayedDamageContext(m_Target);
}