From dffc4610d5d3f85db5407d234f2e4311d7ab758a Mon Sep 17 00:00:00 2001 From: Vayle Date: Sat, 31 Jan 2026 02:29:53 +0000 Subject: [PATCH 01/13] Reapply visual/state effects for pets and NPCs after suppression expires When buff suppression expires, only clients get ReapplyBuff() called to restore visual and state effects (illusions, procs, silence, etc.). Pets and NPCs had no equivalent handling, causing permanent loss of illusions, weapon procs, and other active effects after suppression. Add non-client effect restoration in the suppression expiry path of BuffFadeBySlot() that handles illusions, silence, amnesia, and weapon procs for pets, NPCs, and bots. Fixes #32 --- zone/spell_effects.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index bd03298e1..33090b07f 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4688,6 +4688,34 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su client->QueuePacket(action_packet); safe_delete(action_packet); client->ReapplyBuff(slot, true); + } else { + // Reapply visual/state effects for non-client mobs (pets, NPCs, bots) + const auto& spell = spells[buffs[slot].spellid]; + for (int i = 0; i < EFFECT_COUNT; i++) { + switch (spell.effect_id[i]) { + case SpellEffect::Illusion: + ApplySpellEffectIllusion(spell.id, entity_list.GetMobID(buffs[slot].casterid), slot, spell.base_value[i], spell.limit_value[i], spell.max_value[i]); + break; + case SpellEffect::Silence: + Silence(true); + break; + case SpellEffect::Amnesia: + Amnesia(true); + break; + case SpellEffect::AddMeleeProc: + case SpellEffect::WeaponProc: + AddProcToWeapon(GetProcID(buffs[slot].spellid, i), false, 100 + spell.limit_value[i], buffs[slot].spellid, buffs[slot].casterlevel, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::MELEE_PROC)); + break; + case SpellEffect::DefensiveProc: + AddDefensiveProc(GetProcID(buffs[slot].spellid, i), 100 + spell.limit_value[i], buffs[slot].spellid, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::DEFENSIVE_PROC)); + break; + case SpellEffect::RangedProc: + AddRangedProc(GetProcID(buffs[slot].spellid, i), 100 + spell.limit_value[i], buffs[slot].spellid, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::RANGED_PROC)); + break; + default: + break; + } + } } } else { buffs[slot].spellid = SPELL_UNKNOWN; From 43a146da6dde0be06ddd27741ba21fc73091e27e Mon Sep 17 00:00:00 2001 From: Vayle <76063792+Valorith@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:39:11 -0500 Subject: [PATCH 02/13] Update zone/spell_effects.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- zone/spell_effects.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 33090b07f..3ec303d3f 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4690,6 +4690,8 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su client->ReapplyBuff(slot, true); } else { // Reapply visual/state effects for non-client mobs (pets, NPCs, bots) + if (!IsValidSpell(buffs[slot].spellid)) + return false; const auto& spell = spells[buffs[slot].spellid]; for (int i = 0; i < EFFECT_COUNT; i++) { switch (spell.effect_id[i]) { From 35953234c3db9bfe4550ee0a1c99d44378fa7b91 Mon Sep 17 00:00:00 2001 From: Vayle <76063792+Valorith@users.noreply.github.com> Date: Fri, 30 Jan 2026 22:39:42 -0500 Subject: [PATCH 04/13] Update zone/spell_effects.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- zone/spell_effects.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 3ec303d3f..56f40c1d9 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4693,6 +4693,11 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su if (!IsValidSpell(buffs[slot].spellid)) return false; const auto& spell = spells[buffs[slot].spellid]; + // Restore nimbus (visual aura) effect before processing individual spell effects, + // mirroring Client::ReapplyBuff and bot buff restoration behavior. + if (spell.nimbus_effect) { + SetNimbusEffect(spell.nimbus_effect); + } for (int i = 0; i < EFFECT_COUNT; i++) { switch (spell.effect_id[i]) { case SpellEffect::Illusion: From 57edda7535b6ce10783afe8ae84c6a3cc7ac3e06 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 03:40:51 +0000 Subject: [PATCH 05/13] Add DivineAura effect restoration for non-client mobs Co-authored-by: Valorith <76063792+Valorith@users.noreply.github.com> --- zone/spell_effects.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 3ec303d3f..f202adb19 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4704,6 +4704,9 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su case SpellEffect::Amnesia: Amnesia(true); break; + case SpellEffect::DivineAura: + invulnerable = true; + break; case SpellEffect::AddMeleeProc: case SpellEffect::WeaponProc: AddProcToWeapon(GetProcID(buffs[slot].spellid, i), false, 100 + spell.limit_value[i], buffs[slot].spellid, buffs[slot].casterlevel, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::MELEE_PROC)); From 28e30dfeeceef7074656e6868421d70574781a15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:21:45 +0000 Subject: [PATCH 07/13] Fix: Change return false to return in void function BuffFadeBySlot Co-authored-by: Valorith <76063792+Valorith@users.noreply.github.com> --- zone/spell_effects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 56f40c1d9..4b36eae47 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4691,7 +4691,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su } else { // Reapply visual/state effects for non-client mobs (pets, NPCs, bots) if (!IsValidSpell(buffs[slot].spellid)) - return false; + return; const auto& spell = spells[buffs[slot].spellid]; // Restore nimbus (visual aura) effect before processing individual spell effects, // mirroring Client::ReapplyBuff and bot buff restoration behavior. From 7129acc3b01d613e69412c72d8f3b288fba1825e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:23:15 +0000 Subject: [PATCH 08/13] Fix compilation error: change return false to return in void function Co-authored-by: Valorith <76063792+Valorith@users.noreply.github.com> --- zone/spell_effects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index f202adb19..c6b80b229 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4691,7 +4691,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su } else { // Reapply visual/state effects for non-client mobs (pets, NPCs, bots) if (!IsValidSpell(buffs[slot].spellid)) - return false; + return; const auto& spell = spells[buffs[slot].spellid]; for (int i = 0; i < EFFECT_COUNT; i++) { switch (spell.effect_id[i]) { From ff7dbce6f28a4717e8ce0530d0f8b0c1bd91c4bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 04:24:44 +0000 Subject: [PATCH 10/13] Fix compilation error: change return false to return in void function Co-authored-by: Valorith <76063792+Valorith@users.noreply.github.com> --- zone/spell_effects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 56f40c1d9..4b36eae47 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4691,7 +4691,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su } else { // Reapply visual/state effects for non-client mobs (pets, NPCs, bots) if (!IsValidSpell(buffs[slot].spellid)) - return false; + return; const auto& spell = spells[buffs[slot].spellid]; // Restore nimbus (visual aura) effect before processing individual spell effects, // mirroring Client::ReapplyBuff and bot buff restoration behavior. From fa3bbec7396fdd1554076a540e6cdc81948e6465 Mon Sep 17 00:00:00 2001 From: Vayle <76063792+Valorith@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:12:58 -0500 Subject: [PATCH 11/13] Update zone/spell_effects.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- zone/spell_effects.cpp | 62 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 88208508e..eb88b10f1 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4693,37 +4693,37 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su if (!IsValidSpell(buffs[slot].spellid)) return; const auto& spell = spells[buffs[slot].spellid]; - // Restore nimbus (visual aura) effect before processing individual spell effects, - // mirroring Client::ReapplyBuff and bot buff restoration behavior. - if (spell.nimbus_effect) { - SetNimbusEffect(spell.nimbus_effect); - } - for (int i = 0; i < EFFECT_COUNT; i++) { - switch (spell.effect_id[i]) { - case SpellEffect::Illusion: - ApplySpellEffectIllusion(spell.id, entity_list.GetMobID(buffs[slot].casterid), slot, spell.base_value[i], spell.limit_value[i], spell.max_value[i]); - break; - case SpellEffect::Silence: - Silence(true); - break; - case SpellEffect::Amnesia: - Amnesia(true); - break; - case SpellEffect::DivineAura: - invulnerable = true; - break; - case SpellEffect::AddMeleeProc: - case SpellEffect::WeaponProc: - AddProcToWeapon(GetProcID(buffs[slot].spellid, i), false, 100 + spell.limit_value[i], buffs[slot].spellid, buffs[slot].casterlevel, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::MELEE_PROC)); - break; - case SpellEffect::DefensiveProc: - AddDefensiveProc(GetProcID(buffs[slot].spellid, i), 100 + spell.limit_value[i], buffs[slot].spellid, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::DEFENSIVE_PROC)); - break; - case SpellEffect::RangedProc: - AddRangedProc(GetProcID(buffs[slot].spellid, i), 100 + spell.limit_value[i], buffs[slot].spellid, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::RANGED_PROC)); - break; - default: - break; + if (IsValidSpell(buffs[slot].spellid)) { + const auto &spell = spells[buffs[slot].spellid]; + // Restore nimbus (visual aura) effect before processing individual spell effects, + // mirroring Client::ReapplyBuff and bot buff restoration behavior. + if (spell.nimbus_effect) { + SetNimbusEffect(spell.nimbus_effect); + } + for (int i = 0; i < EFFECT_COUNT; i++) { + switch (spell.effect_id[i]) { + case SpellEffect::Illusion: + ApplySpellEffectIllusion(spell.id, entity_list.GetMobID(buffs[slot].casterid), slot, spell.base_value[i], spell.limit_value[i], spell.max_value[i]); + break; + case SpellEffect::Silence: + Silence(true); + break; + case SpellEffect::Amnesia: + Amnesia(true); + break; + case SpellEffect::AddMeleeProc: + case SpellEffect::WeaponProc: + AddProcToWeapon(GetProcID(buffs[slot].spellid, i), false, 100 + spell.limit_value[i], buffs[slot].spellid, buffs[slot].casterlevel, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::MELEE_PROC)); + break; + case SpellEffect::DefensiveProc: + AddDefensiveProc(GetProcID(buffs[slot].spellid, i), 100 + spell.limit_value[i], buffs[slot].spellid, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::DEFENSIVE_PROC)); + break; + case SpellEffect::RangedProc: + AddRangedProc(GetProcID(buffs[slot].spellid, i), 100 + spell.limit_value[i], buffs[slot].spellid, GetSpellProcLimitTimer(buffs[slot].spellid, ProcType::RANGED_PROC)); + break; + default: + break; + } } } } From 5e43850e8ef5333b03b9e08265bc4620eba53866 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 20:33:33 +0000 Subject: [PATCH 13/13] Restrict suppression system to clients and client pets only Co-authored-by: Valorith <76063792+Valorith@users.noreply.github.com> --- zone/spell_effects.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index eb88b10f1..eeada73a8 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4688,8 +4688,9 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses, bool suppress, uint32 su client->QueuePacket(action_packet); safe_delete(action_packet); client->ReapplyBuff(slot, true); - } else { - // Reapply visual/state effects for non-client mobs (pets, NPCs, bots) + } else if (IsPet() && GetOwner() && GetOwner()->IsClient()) { + // Reapply visual/state effects for client pets only + // All other non-client mobs (NPCs, bots, mercs) use normal dispel mechanic if (!IsValidSpell(buffs[slot].spellid)) return; const auto& spell = spells[buffs[slot].spellid];