From 3f7e980cd5af8f9368ee983435df28fe077d1860 Mon Sep 17 00:00:00 2001 From: Vayle Date: Sun, 8 Mar 2026 19:57:12 -0400 Subject: [PATCH 1/4] Fix suppressed dispel crash in DispelMagic --- zone/spell_effects.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index f53751431..8ea3aef60 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -7536,16 +7536,19 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value, int chance for (int slot = 0; slot < GetMaxTotalSlots(); ++slot) { auto s = buffs[slot].spellid; + // Suppressed slots store a sentinel in spellid, so use the underlying spell + // for any lookup into the spell data table. + auto dispel_spell_id = s == SPELL_SUPPRESSED ? buffs[slot].suppressedid : s; - if (s == SPELL_UNKNOWN || spells[s].dispel_flag != 0 || IsDiscipline(s)) { + if (!IsValidSpell(dispel_spell_id) || spells[dispel_spell_id].dispel_flag != 0 || IsDiscipline(dispel_spell_id)) { continue; } - if (detrimental_only && !IsDetrimentalSpell(s)) { + if (detrimental_only && !IsDetrimentalSpell(dispel_spell_id)) { continue; } - if (beneficial_only && IsDetrimentalSpell(s)) { + if (beneficial_only && IsDetrimentalSpell(dispel_spell_id)) { continue; } @@ -7567,7 +7570,7 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value, int chance break; } else { //Additional restrictions on which buffs can be suppressed - if (spells[s].short_buff_box) { + if (spells[dispel_spell_id].short_buff_box) { continue; } From 8b7fbe7f9ab9cb3e7a18d4623c8f7513204f3d69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:16:17 +0000 Subject: [PATCH 3/4] Fix: truly remove suppressed buffs on permanent dispel via RemoveSuppressedBuffBySlot Co-authored-by: Valorith <76063792+Valorith@users.noreply.github.com> --- zone/mob.h | 1 + zone/spell_effects.cpp | 69 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/zone/mob.h b/zone/mob.h index 6433b0ead..4ea174418 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -452,6 +452,7 @@ public: void BuffFadeNonPersistDeath(); void BuffFadeDetrimental(); void BuffFadeBySlot(int slot, bool iRecalcBonuses = true, bool suppress = false, uint32 suppresstics = 0); + void RemoveSuppressedBuffBySlot(int slot, bool iRecalcBonuses = true); void BuffFadeDetrimentalByCaster(Mob *caster); void BuffFadeBySitModifier(); void BuffFadeSongs(); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 8ea3aef60..f17113635 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4785,6 +4785,67 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su CalcBonuses(); } +// Permanently removes a suppressed buff without restoring the original spell. +// Use this instead of BuffFadeBySlot when you want to truly dispel a buff that +// is currently suppressed (spellid == SPELL_SUPPRESSED). BuffFadeBySlot would +// unsuppress/reapply the original buff in that situation. +void Mob::RemoveSuppressedBuffBySlot(int slot, bool iRecalcBonuses) +{ + if (slot < 0 || slot >= GetMaxTotalSlots()) + return; + + if (buffs[slot].spellid != SPELL_SUPPRESSED) + return; + + // Send the client a buff-fade packet for the visible suppression placeholder. + if (IsClient()) { + auto client = CastToClient(); + if (!client->IsDead()) { + client->MakeBuffFadePacket(RuleI(Spells, SuppressDebuffSpellID), slot); + } + } + + LogSpells("Removing suppressed buff (suppressedid [{}]) from slot [{}]", buffs[slot].suppressedid, slot); + + // The spell's effects were already removed when the buff was suppressed, + // so we only need to clear the slot bookkeeping. + buffs[slot].spellid = SPELL_UNKNOWN; + buffs[slot].suppressedid = 0; + buffs[slot].suppressedticsremaining = -1; + + if (IsPet() && GetOwner() && GetOwner()->IsClient()) { + SendPetBuffsToClient(); + } + + if ((IsClient() && !CastToClient()->GetPVP()) || + (IsPet() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) || + (IsBot() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) || + (IsMerc() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP())) + { + EQApplicationPacket *outapp = MakeBuffsPacket(); + entity_list.QueueClientsByTarget(this, outapp, false, nullptr, true, false, EQ::versions::maskSoDAndLater); + if (IsClient() && GetTarget() == this) { + CastToClient()->QueuePacket(outapp); + } + safe_delete(outapp); + } + + if (IsNPC()) { + EQApplicationPacket *outapp = MakeBuffsPacket(); + entity_list.QueueClientsByTarget(this, outapp, false, nullptr, true, false, EQ::versions::maskSoDAndLater, true); + safe_delete(outapp); + } + + if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) { + EQApplicationPacket *outapp = MakeBuffsPacket(false); + CastToClient()->FastQueuePacket(&outapp); + } + + degenerating_effects = false; + if (iRecalcBonuses) + CalcBonuses(); +} + int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) { const SPDat_Spell_Struct &spell = spells[spell_id]; @@ -7566,7 +7627,13 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value, int chance bool perma_remove = caster_is_client || !target_is_client; if (perma_remove || !RuleB(Spells, SuppressDispels)) { - BuffFadeBySlot(slot); + if (s == SPELL_SUPPRESSED) { + // Truly remove a suppressed buff. BuffFadeBySlot would unsuppress/reapply + // the original spell rather than clearing the slot. + RemoveSuppressedBuffBySlot(slot); + } else { + BuffFadeBySlot(slot); + } break; } else { //Additional restrictions on which buffs can be suppressed From a36002dbed920a600542fe4214faefd80b56aa04 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:22:35 +0000 Subject: [PATCH 4/4] Fix: skip suppressed slots in DispelMagic instead of removing them Co-authored-by: Valorith <76063792+Valorith@users.noreply.github.com> --- zone/mob.h | 1 - zone/spell_effects.cpp | 85 +++++------------------------------------- 2 files changed, 10 insertions(+), 76 deletions(-) diff --git a/zone/mob.h b/zone/mob.h index 4ea174418..6433b0ead 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -452,7 +452,6 @@ public: void BuffFadeNonPersistDeath(); void BuffFadeDetrimental(); void BuffFadeBySlot(int slot, bool iRecalcBonuses = true, bool suppress = false, uint32 suppresstics = 0); - void RemoveSuppressedBuffBySlot(int slot, bool iRecalcBonuses = true); void BuffFadeDetrimentalByCaster(Mob *caster); void BuffFadeBySitModifier(); void BuffFadeSongs(); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index f17113635..6417a0efe 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4785,67 +4785,6 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su CalcBonuses(); } -// Permanently removes a suppressed buff without restoring the original spell. -// Use this instead of BuffFadeBySlot when you want to truly dispel a buff that -// is currently suppressed (spellid == SPELL_SUPPRESSED). BuffFadeBySlot would -// unsuppress/reapply the original buff in that situation. -void Mob::RemoveSuppressedBuffBySlot(int slot, bool iRecalcBonuses) -{ - if (slot < 0 || slot >= GetMaxTotalSlots()) - return; - - if (buffs[slot].spellid != SPELL_SUPPRESSED) - return; - - // Send the client a buff-fade packet for the visible suppression placeholder. - if (IsClient()) { - auto client = CastToClient(); - if (!client->IsDead()) { - client->MakeBuffFadePacket(RuleI(Spells, SuppressDebuffSpellID), slot); - } - } - - LogSpells("Removing suppressed buff (suppressedid [{}]) from slot [{}]", buffs[slot].suppressedid, slot); - - // The spell's effects were already removed when the buff was suppressed, - // so we only need to clear the slot bookkeeping. - buffs[slot].spellid = SPELL_UNKNOWN; - buffs[slot].suppressedid = 0; - buffs[slot].suppressedticsremaining = -1; - - if (IsPet() && GetOwner() && GetOwner()->IsClient()) { - SendPetBuffsToClient(); - } - - if ((IsClient() && !CastToClient()->GetPVP()) || - (IsPet() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) || - (IsBot() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP()) || - (IsMerc() && GetOwner() && GetOwner()->IsClient() && !GetOwner()->CastToClient()->GetPVP())) - { - EQApplicationPacket *outapp = MakeBuffsPacket(); - entity_list.QueueClientsByTarget(this, outapp, false, nullptr, true, false, EQ::versions::maskSoDAndLater); - if (IsClient() && GetTarget() == this) { - CastToClient()->QueuePacket(outapp); - } - safe_delete(outapp); - } - - if (IsNPC()) { - EQApplicationPacket *outapp = MakeBuffsPacket(); - entity_list.QueueClientsByTarget(this, outapp, false, nullptr, true, false, EQ::versions::maskSoDAndLater, true); - safe_delete(outapp); - } - - if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) { - EQApplicationPacket *outapp = MakeBuffsPacket(false); - CastToClient()->FastQueuePacket(&outapp); - } - - degenerating_effects = false; - if (iRecalcBonuses) - CalcBonuses(); -} - int64 Mob::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) { const SPDat_Spell_Struct &spell = spells[spell_id]; @@ -7597,19 +7536,21 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value, int chance for (int slot = 0; slot < GetMaxTotalSlots(); ++slot) { auto s = buffs[slot].spellid; - // Suppressed slots store a sentinel in spellid, so use the underlying spell - // for any lookup into the spell data table. - auto dispel_spell_id = s == SPELL_SUPPRESSED ? buffs[slot].suppressedid : s; - if (!IsValidSpell(dispel_spell_id) || spells[dispel_spell_id].dispel_flag != 0 || IsDiscipline(dispel_spell_id)) { + // Suppressed buffs always return when suppression ends; they cannot be dispelled. + if (s == SPELL_SUPPRESSED) { continue; } - if (detrimental_only && !IsDetrimentalSpell(dispel_spell_id)) { + if (!IsValidSpell(s) || spells[s].dispel_flag != 0 || IsDiscipline(s)) { continue; } - if (beneficial_only && IsDetrimentalSpell(dispel_spell_id)) { + if (detrimental_only && !IsDetrimentalSpell(s)) { + continue; + } + + if (beneficial_only && IsDetrimentalSpell(s)) { continue; } @@ -7627,17 +7568,11 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value, int chance bool perma_remove = caster_is_client || !target_is_client; if (perma_remove || !RuleB(Spells, SuppressDispels)) { - if (s == SPELL_SUPPRESSED) { - // Truly remove a suppressed buff. BuffFadeBySlot would unsuppress/reapply - // the original spell rather than clearing the slot. - RemoveSuppressedBuffBySlot(slot); - } else { - BuffFadeBySlot(slot); - } + BuffFadeBySlot(slot); break; } else { //Additional restrictions on which buffs can be suppressed - if (spells[dispel_spell_id].short_buff_box) { + if (spells[s].short_buff_box) { continue; }