From 32dd31b55a7678fad672ef1d10d1a5630117ba46 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:02:15 -0500 Subject: [PATCH 01/27] fix(Core/SmartAI): Keep combat pose on aggro (#25534) Co-authored-by: blinkysc Co-authored-by: ccrs --- src/server/game/AI/SmartScripts/SmartAI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index cbb96029f..c80e2a0ac 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -911,7 +911,7 @@ void SmartAI::AttackStart(Unit* who) return; } - if (who && me->Attack(who, me->IsWithinMeleeRange(who))) + if (who && me->Attack(who, mCanAutoAttack)) { if (!me->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT)) { From 549000c80e703ddfcc11c13dc4eb63fc1181603e Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:37:20 +0200 Subject: [PATCH 02/27] fix(DB/SAI): Last Rites quest now resets properly after the recent regression. (#25547) --- .../pending_db_world/rev_1776881994.sql | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1776881994.sql diff --git a/data/sql/updates/pending_db_world/rev_1776881994.sql b/data/sql/updates/pending_db_world/rev_1776881994.sql new file mode 100644 index 000000000..2886d3886 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1776881994.sql @@ -0,0 +1,36 @@ + +-- Edit Row 1002 (from On Waypoint Ended to MovementInfo) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = -101303) AND (`source_type` = 0) AND (`id` IN (1002)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(-101303, 0, 1002, 0, 34, 0, 100, 0, 8, 12, 0, 0, 0, 0, 80, 2617002, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Thassarian - On MovementInfo - Run Script: Last Rites RP Part 1'); + +-- Edit Action List 2617001 (add Set Run Off & change start waypoint with Move To Pos) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2617001) AND (`source_type` = 9) AND (`id` IN (4, 5)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(2617001, 9, 4, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 59, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Thassarian - Actionlist - Set Run Off'), +(2617001, 9, 5, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 69, 12, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 3713.36, 3570.72, 477.599, 0, 'Thassarian - Actionlist - Move To Position'); + +-- Edit Action List 2617004 (Add Set Home Pos, Set Run On) +DELETE FROM `smart_scripts` WHERE (`source_type` = 9 AND `entryorguid` = 2617004); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(2617004, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 3722.53, 3567.26, 477.571, 0, 'Thassarian - Actionlist - Set Home Position'), +(2617004, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Thassarian - Actionlist - Set Run On'), +(2617004, 9, 2, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 3722.53, 3567.26, 477.571, 0, 'Thassarian - Actionlist - Move To Position'), +(2617004, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Thassarian - Actionlist - Say Line 0'); + +-- Delete Old Set Home Pos from Action List 2617005 +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2617005) AND (`source_type` = 9) AND (`id` IN (15)); + +-- Edit Leryssa Action Lists (set Thassarian target from closest creature to creature guid) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2525101) AND (`source_type` = 9) AND (`id` IN (3, 4, 5)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(2525101, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 10, 101303, 26170, 0, 0, 0, 0, 0, 0, 'Leryssa - Actionlist - Thassarian Say Line 3'), +(2525101, 9, 4, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 90, 8, 0, 0, 0, 0, 0, 10, 101303, 26170, 0, 0, 0, 0, 0, 0, 'Leryssa - Actionlist - Set Thassarian Standstate Kneel'), +(2525101, 9, 5, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 82, 2, 0, 0, 0, 0, 0, 10, 101303, 26170, 0, 0, 0, 0, 0, 0, 'Leryssa - Actionlist - Set Thassarian as Questgiver'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2525102) AND (`source_type` = 9) AND (`id` IN (2, 4, 6, 7)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(2525102, 9, 2, 0, 0, 0, 100, 0, 7200, 7200, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 10, 101303, 26170, 0, 0, 0, 0, 0, 0, 'Leryssa - Actionlist - Thassarian Say Line 4'), +(2525102, 9, 4, 0, 0, 0, 100, 0, 14200, 14200, 0, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 10, 101303, 26170, 0, 0, 0, 0, 0, 0, 'Leryssa - Actionlist - Thassarian Say Line 5'), +(2525102, 9, 6, 0, 0, 0, 100, 0, 14200, 14200, 0, 0, 0, 0, 1, 6, 0, 0, 0, 0, 0, 10, 101303, 26170, 0, 0, 0, 0, 0, 0, 'Leryssa - Actionlist - Thassarian Say Line 6'), +(2525102, 9, 7, 0, 0, 0, 100, 0, 30000, 30000, 0, 0, 0, 0, 223, 2, 0, 0, 0, 0, 0, 10, 101303, 26170, 0, 0, 0, 0, 0, 0, 'Leryssa - Actionlist - Send Action to Thassarian, Cleanup Quest Event'); From 354233675ccecbd013b70cf07b0aaf4c43f9bf85 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Apr 2026 20:38:37 +0000 Subject: [PATCH 03/27] chore(DB): import pending files Referenced commit(s): 549000c80e703ddfcc11c13dc4eb63fc1181603e --- .../rev_1776881994.sql => db_world/2026_04_22_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1776881994.sql => db_world/2026_04_22_00.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1776881994.sql b/data/sql/updates/db_world/2026_04_22_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1776881994.sql rename to data/sql/updates/db_world/2026_04_22_00.sql index 2886d3886..f361b36d2 100644 --- a/data/sql/updates/pending_db_world/rev_1776881994.sql +++ b/data/sql/updates/db_world/2026_04_22_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_21_01 -> 2026_04_22_00 -- Edit Row 1002 (from On Waypoint Ended to MovementInfo) DELETE FROM `smart_scripts` WHERE (`entryorguid` = -101303) AND (`source_type` = 0) AND (`id` IN (1002)); From f1a5526b238a7e796c06ffc17a713859b473fef7 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:01:38 -0300 Subject: [PATCH 04/27] fix(DB/SAI): make Sigrid Iceborn attackable in Battle at Valhalas (#25537) Co-authored-by: Claude Opus 4.7 (1M context) --- .../updates/pending_db_world/rev_1776809863778409500.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1776809863778409500.sql diff --git a/data/sql/updates/pending_db_world/rev_1776809863778409500.sql b/data/sql/updates/pending_db_world/rev_1776809863778409500.sql new file mode 100644 index 000000000..62bf23c9a --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1776809863778409500.sql @@ -0,0 +1,9 @@ +DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryorguid` = 31242 AND `id` = 3; +DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryorguid` = 31271 AND `id` = 2; +DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryorguid` = 31277 AND `id` = 6; +DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryorguid` = 14688 AND `id` = 23; +INSERT INTO `smart_scripts` (`entryorguid`,`source_type`,`id`,`link`,`event_type`,`event_phase_mask`,`event_chance`,`event_flags`,`event_param1`,`event_param2`,`event_param3`,`event_param4`,`event_param5`,`event_param6`,`action_type`,`action_param1`,`action_param2`,`action_param3`,`action_param4`,`action_param5`,`action_param6`,`target_type`,`target_param1`,`target_param2`,`target_param3`,`target_param4`,`target_x`,`target_y`,`target_z`,`target_o`,`comment`) VALUES +(31242, 0, 3, 0, 54, 0, 100, 0, 0, 0, 0, 0, 0, 0, 19, 768, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Sigrid Iceborn - On Just Summoned - Remove Flags Immune To PC/NPC'), +(31271, 0, 2, 0, 54, 0, 100, 0, 0, 0, 0, 0, 0, 0, 19, 768, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Carnage - On Just Summoned - Remove Flags Immune To PC/NPC'), +(31277, 0, 6, 0, 54, 0, 100, 0, 0, 0, 0, 0, 0, 0, 19, 768, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Thane Banahogg - On Just Summoned - Remove Flags Immune To PC/NPC'), +(14688, 0,23, 0, 1, 0, 100, 0, 14000, 14000, 0, 0, 0, 0, 19, 768, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Prince Sandoval - Out of Combat 14s - Remove Flags Immune To PC/NPC'); From e424355c6ba0521b40b5477adafb671c6258ab17 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Apr 2026 23:02:47 +0000 Subject: [PATCH 05/27] chore(DB): import pending files Referenced commit(s): f1a5526b238a7e796c06ffc17a713859b473fef7 --- .../rev_1776809863778409500.sql => db_world/2026_04_22_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1776809863778409500.sql => db_world/2026_04_22_01.sql} (97%) diff --git a/data/sql/updates/pending_db_world/rev_1776809863778409500.sql b/data/sql/updates/db_world/2026_04_22_01.sql similarity index 97% rename from data/sql/updates/pending_db_world/rev_1776809863778409500.sql rename to data/sql/updates/db_world/2026_04_22_01.sql index 62bf23c9a..8a55e591b 100644 --- a/data/sql/updates/pending_db_world/rev_1776809863778409500.sql +++ b/data/sql/updates/db_world/2026_04_22_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_22_00 -> 2026_04_22_01 DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryorguid` = 31242 AND `id` = 3; DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryorguid` = 31271 AND `id` = 2; DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryorguid` = 31277 AND `id` = 6; From d4866a0f595915836dffdf14588e3067ce7a8726 Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:03:02 -0300 Subject: [PATCH 06/27] fix(Core/Battlegrounds): respawn SotA demolishers after death (#25551) Co-authored-by: Claude Opus 4.7 --- src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp index 9d42e1965..2d04cc763 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp @@ -1118,7 +1118,7 @@ void BattlegroundSA::UpdateDemolisherSpawns() BG_SA_NpcSpawnlocs[i][2], BG_SA_NpcSpawnlocs[i][3]); Demolisher->SetVisible(true); - Demolisher->Respawn(); + Demolisher->Respawn(true); DemoliserRespawnList.erase(i); } } From 6540ec32c90373797770ab935698ce195d81e2c3 Mon Sep 17 00:00:00 2001 From: sogladev Date: Thu, 23 Apr 2026 01:07:07 +0200 Subject: [PATCH 07/27] fix(DB/Proc): add 2s ICD to Vesperon's Twilight Torment (#25545) --- .../updates/pending_db_world/rev_1776869250749529676.sql | 6 ++++++ src/server/game/Spells/SpellInfoCorrections.cpp | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1776869250749529676.sql diff --git a/data/sql/updates/pending_db_world/rev_1776869250749529676.sql b/data/sql/updates/pending_db_world/rev_1776869250749529676.sql new file mode 100644 index 000000000..6da125843 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1776869250749529676.sql @@ -0,0 +1,6 @@ +-- +-- Add ICD to Vesperon's Twilight Torment during Vesperon's fight and Sartharion Hardmode +DELETE FROM `spell_proc` WHERE `SpellId` IN (58835, 57935); +INSERT INTO `spell_proc` (`SpellId`, `SpellPhaseMask`, `Cooldown`) VALUES +(58835, 2, 2000), +(57935, 2, 2000); diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index f5d1d83e2..2b819330e 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -5184,6 +5184,12 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->InterruptFlags &= ~SPELL_INTERRUPT_FLAG_INTERRUPT; }); + // Twilight Torment + ApplySpellFix({ 57935, 58835 }, [](SpellInfo* spellInfo) + { + spellInfo->ProcCharges = 0; + }); + for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; From e2de7b98c8bdbca74420df5ddda2abd04ba192fd Mon Sep 17 00:00:00 2001 From: sogladev Date: Thu, 23 Apr 2026 01:07:36 +0200 Subject: [PATCH 08/27] fix(DB/Conditions): Vesperon's (solo) Twilight Torment hits players (#25546) --- .../updates/pending_db_world/rev_1776870484034455872.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1776870484034455872.sql diff --git a/data/sql/updates/pending_db_world/rev_1776870484034455872.sql b/data/sql/updates/pending_db_world/rev_1776870484034455872.sql new file mode 100644 index 000000000..624994c65 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1776870484034455872.sql @@ -0,0 +1,6 @@ +-- +-- Removes 'Twilight Torment - Target players which has phase mask 1'; +-- This does not work as described in the comment, nor is it accurate. +-- This condition is also only set for the Vesperon Twilight Torment +-- application, not for the Sartharion fight. +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 17) AND (`SourceGroup` = 0) AND (`SourceEntry` = 57935) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 26) AND (`ConditionTarget` = 1) AND (`ConditionValue1` = 1) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); From 72591ee8415ad93aa47fddb0dc7bda57ffe9e06b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Apr 2026 23:08:22 +0000 Subject: [PATCH 09/27] chore(DB): import pending files Referenced commit(s): 6540ec32c90373797770ab935698ce195d81e2c3 --- .../rev_1776869250749529676.sql => db_world/2026_04_22_02.sql} | 1 + .../rev_1776870484034455872.sql => db_world/2026_04_22_03.sql} | 1 + 2 files changed, 2 insertions(+) rename data/sql/updates/{pending_db_world/rev_1776869250749529676.sql => db_world/2026_04_22_02.sql} (85%) rename data/sql/updates/{pending_db_world/rev_1776870484034455872.sql => db_world/2026_04_22_03.sql} (92%) diff --git a/data/sql/updates/pending_db_world/rev_1776869250749529676.sql b/data/sql/updates/db_world/2026_04_22_02.sql similarity index 85% rename from data/sql/updates/pending_db_world/rev_1776869250749529676.sql rename to data/sql/updates/db_world/2026_04_22_02.sql index 6da125843..dc0849cf5 100644 --- a/data/sql/updates/pending_db_world/rev_1776869250749529676.sql +++ b/data/sql/updates/db_world/2026_04_22_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_22_01 -> 2026_04_22_02 -- -- Add ICD to Vesperon's Twilight Torment during Vesperon's fight and Sartharion Hardmode DELETE FROM `spell_proc` WHERE `SpellId` IN (58835, 57935); diff --git a/data/sql/updates/pending_db_world/rev_1776870484034455872.sql b/data/sql/updates/db_world/2026_04_22_03.sql similarity index 92% rename from data/sql/updates/pending_db_world/rev_1776870484034455872.sql rename to data/sql/updates/db_world/2026_04_22_03.sql index 624994c65..775961b29 100644 --- a/data/sql/updates/pending_db_world/rev_1776870484034455872.sql +++ b/data/sql/updates/db_world/2026_04_22_03.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_22_02 -> 2026_04_22_03 -- -- Removes 'Twilight Torment - Target players which has phase mask 1'; -- This does not work as described in the comment, nor is it accurate. From 092fc7cc9e97566212df9f98febb810b64e12d58 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 23 Apr 2026 03:46:08 -0500 Subject: [PATCH 10/27] fix(Core/AI): keep charmed creature victim set by charmer (#25552) --- src/server/game/AI/CreatureAI.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 345ee7ad7..4dfeb897c 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -358,6 +358,10 @@ bool CreatureAI::UpdateVictim() return false; } + // Charmed creatures: the charmer controls target selection, don't interfere + if (me->IsCharmed()) + return me->GetVictim() != nullptr; + if (!me->HasReactState(REACT_PASSIVE)) { if (Unit* victim = me->SelectVictim()) From f5d41851125d654f58f5434fb99641c447ea144b Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:22:22 -0500 Subject: [PATCH 11/27] fix(Core/Events): portable holiday date normalization (#25555) Co-authored-by: blinkysc --- .../game/Events/HolidayDateCalculator.cpp | 101 +++++++++++++++--- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/src/server/game/Events/HolidayDateCalculator.cpp b/src/server/game/Events/HolidayDateCalculator.cpp index 06552a3cf..c507f5413 100644 --- a/src/server/game/Events/HolidayDateCalculator.cpp +++ b/src/server/game/Events/HolidayDateCalculator.cpp @@ -26,6 +26,73 @@ constexpr double DEG_TO_RAD = PI / 180.0; // Helper: sin/cos in degrees inline double sind(double deg) { return std::sin(deg * DEG_TO_RAD); } +// mktime() rejects pre-1970 dates on MSVC; these helpers normalize without it. +static bool IsLeapYear(int year) +{ + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +} + +static int DaysInMonth(int year, int month) +{ + static int const table[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + return (month == 2 && IsLeapYear(year)) ? 29 : table[month - 1]; +} + +static void NormalizeDate(std::tm& date) +{ + int year = date.tm_year + 1900; + int month = date.tm_mon + 1; + int day = date.tm_mday; + + while (month > 12) + { + month -= 12; + ++year; + } + while (month < 1) + { + month += 12; + --year; + } + while (day > DaysInMonth(year, month)) + { + day -= DaysInMonth(year, month); + ++month; + if (month > 12) + { + month = 1; + ++year; + } + } + while (day < 1) + { + --month; + if (month < 1) + { + month = 12; + --year; + } + day += DaysInMonth(year, month); + } + + date.tm_year = year - 1900; + date.tm_mon = month - 1; + date.tm_mday = day; + + // Zeller's congruence; remap 0=Sat to POSIX 0=Sun. + int zy = year; + int zm = month; + if (zm < 3) + { + zm += 12; + --zy; + } + int const K = zy % 100; + int const J = zy / 100; + int const h = (day + (13 * (zm + 1)) / 5 + K + K / 4 + J / 4 + 5 * J) % 7; + date.tm_wday = (h + 6) % 7; +} + // Static holiday rules configuration static const std::vector HolidayRules = { // Lunar Festival: Chinese New Year - 1 day (event starts day before CNY) @@ -104,7 +171,7 @@ std::tm HolidayDateCalculator::CalculateEasterSunday(int year) result.tm_year = year - 1900; result.tm_mon = month - 1; result.tm_mday = day; - mktime(&result); // Normalize and fill in other fields + NormalizeDate(result); return result; } @@ -116,7 +183,7 @@ std::tm HolidayDateCalculator::CalculateNthWeekday(int year, int month, Weekday date.tm_year = year - 1900; date.tm_mon = month - 1; date.tm_mday = 1; - mktime(&date); + NormalizeDate(date); // Find first occurrence of the target weekday int const daysUntilWeekday = (static_cast(weekday) - date.tm_wday + 7) % 7; @@ -125,7 +192,7 @@ std::tm HolidayDateCalculator::CalculateNthWeekday(int year, int month, Weekday // Move to nth occurrence date.tm_mday += (n - 1) * 7; - mktime(&date); // Normalize (handles month overflow) + NormalizeDate(date); return date; } @@ -136,13 +203,13 @@ std::tm HolidayDateCalculator::CalculateWeekdayOnOrAfter(int year, int month, in date.tm_year = year - 1900; date.tm_mon = month - 1; date.tm_mday = day; - mktime(&date); + NormalizeDate(date); // Find days until the target weekday (0 if already on that day) int const daysUntilWeekday = (static_cast(weekday) - date.tm_wday + 7) % 7; date.tm_mday += daysUntilWeekday; - mktime(&date); // Normalize + NormalizeDate(date); return date; } @@ -288,7 +355,7 @@ std::tm HolidayDateCalculator::CalculateLunarNewYear(int year) result.tm_year = cnyYear - 1900; result.tm_mon = cnyMonth - 1; result.tm_mday = cnyDay; - mktime(&result); + NormalizeDate(result); return result; } } @@ -298,7 +365,7 @@ std::tm HolidayDateCalculator::CalculateLunarNewYear(int year) fallback.tm_year = year - 1900; fallback.tm_mon = 0; // January fallback.tm_mday = 25; - mktime(&fallback); + NormalizeDate(fallback); return fallback; } @@ -354,7 +421,7 @@ std::tm HolidayDateCalculator::CalculateAutumnEquinox(int year) result.tm_year = eqYear - 1900; result.tm_mon = eqMonth - 1; result.tm_mday = eqDay; - mktime(&result); + NormalizeDate(result); return result; } @@ -408,7 +475,7 @@ std::tm HolidayDateCalculator::CalculateWinterSolstice(int year) result.tm_year = solYear - 1900; result.tm_mon = solMonth - 1; result.tm_mday = solDay; - mktime(&result); + NormalizeDate(result); return result; } @@ -423,7 +490,7 @@ std::tm HolidayDateCalculator::CalculateHolidayDate(const HolidayRule& rule, int result.tm_year = year - 1900; result.tm_mon = rule.month - 1; result.tm_mday = rule.day; - mktime(&result); + NormalizeDate(result); break; } case HolidayCalculationType::NTH_WEEKDAY: @@ -432,7 +499,7 @@ std::tm HolidayDateCalculator::CalculateHolidayDate(const HolidayRule& rule, int if (rule.offset != 0) { result.tm_mday += rule.offset; - mktime(&result); // Normalize + NormalizeDate(result); } break; } @@ -440,7 +507,7 @@ std::tm HolidayDateCalculator::CalculateHolidayDate(const HolidayRule& rule, int { result = CalculateEasterSunday(year); result.tm_mday += rule.offset; - mktime(&result); // Normalize + NormalizeDate(result); break; } case HolidayCalculationType::LUNAR_NEW_YEAR: @@ -449,7 +516,7 @@ std::tm HolidayDateCalculator::CalculateHolidayDate(const HolidayRule& rule, int if (rule.offset != 0) { result.tm_mday += rule.offset; - mktime(&result); // Normalize + NormalizeDate(result); } break; } @@ -459,7 +526,7 @@ std::tm HolidayDateCalculator::CalculateHolidayDate(const HolidayRule& rule, int if (rule.offset != 0) { result.tm_mday += rule.offset; - mktime(&result); // Normalize + NormalizeDate(result); } break; } @@ -469,7 +536,7 @@ std::tm HolidayDateCalculator::CalculateHolidayDate(const HolidayRule& rule, int if (rule.offset != 0) { result.tm_mday += rule.offset; - mktime(&result); // Normalize + NormalizeDate(result); } break; } @@ -479,7 +546,7 @@ std::tm HolidayDateCalculator::CalculateHolidayDate(const HolidayRule& rule, int if (rule.offset != 0) { result.tm_mday += rule.offset; - mktime(&result); // Normalize + NormalizeDate(result); } break; } @@ -571,7 +638,7 @@ std::vector HolidayDateCalculator::GetDarkmoonFaireDates(int locationO if (dayOffset != 0) { date.tm_mday += dayOffset; - mktime(&date); // Normalize + NormalizeDate(date); } dates.push_back(PackDate(date)); } From f9833c0dd45468ddca5daa323cf5ed34cfda1494 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:12:15 -0500 Subject: [PATCH 12/27] fix(DB/SAI): migrate 'Deadliest Trap' quest to manual-spawn groups (#25554) Co-authored-by: r00ty-tc --- .../rev_1776906471063424160.sql | 56 +++++++++++++++++++ .../game/AI/SmartScripts/SmartScriptMgr.cpp | 2 + 2 files changed, 58 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1776906471063424160.sql diff --git a/data/sql/updates/pending_db_world/rev_1776906471063424160.sql b/data/sql/updates/pending_db_world/rev_1776906471063424160.sql new file mode 100644 index 000000000..663e75723 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1776906471063424160.sql @@ -0,0 +1,56 @@ +-- +-- Fix quest "The Deadliest Trap Ever Laid" (11097 Scryer / 11101 Aldor): +-- the AC-specific SCRIPTED_SPAWN (state=0 on AI_INIT -> despawn self) +-- pattern loops forever under the ported dynamic spawn system because +-- each respawn creates a new creature whose AI_INIT fires the despawn +-- again. Migrate to MANUAL_SPAWN spawn groups and SPAWN_SPAWNGROUP / +-- DESPAWN_SPAWNGROUP, which is what the spawn system port was built for. +-- + +-- Four manual-spawn groups: two per quest (defenders, skybreakers) +DELETE FROM `spawn_group_template` WHERE `groupId` IN (100, 101, 102, 103); +INSERT INTO `spawn_group_template` (`groupId`, `groupName`, `groupFlags`) VALUES +(100, 'Sanctum of the Stars - Defenders', 0x04), +(101, 'Sanctum of the Stars - Skybreakers', 0x04), +(102, 'Altar of Sha''tar - Defenders', 0x04), +(103, 'Altar of Sha''tar - Skybreakers', 0x04); + +-- Assign existing creature spawns to their groups +DELETE FROM `spawn_group` WHERE `groupId` = 100; +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) SELECT 100, 0, `guid` FROM `creature` WHERE `id1` = 23435; +DELETE FROM `spawn_group` WHERE `groupId` = 101; +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) SELECT 101, 0, `guid` FROM `creature` WHERE `id1` = 23440; +DELETE FROM `spawn_group` WHERE `groupId` = 102; +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) SELECT 102, 0, `guid` FROM `creature` WHERE `id1` = 23453; +DELETE FROM `spawn_group` WHERE `groupId` = 103; +INSERT INTO `spawn_group` (`groupId`, `spawnType`, `spawnId`) SELECT 103, 0, `guid` FROM `creature` WHERE `id1` = 23441; + +-- Skybreakers respawn mid-event (wave mechanic) +UPDATE `creature` SET `spawntimesecs` = 5 WHERE `id1` IN (23440, 23441); + +-- Drop the creatures' AI_INIT self-despawn events (dormancy now handled by MANUAL_SPAWN) +DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `event_type` = 37 AND `action_type` = 226 AND `entryorguid` IN (23435, 23440, 23441, 23453); + +-- Commander Hobb: On Just Died cleanup -> DESPAWN_SPAWNGROUP +UPDATE `smart_scripts` SET `action_type` = 132, `action_param1` = 100, `action_param2` = 0, `action_param3` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Hobb - On Just Died - Despawn Defenders Group' WHERE `entryorguid` = 23434 AND `source_type` = 0 AND `id` = 7; +UPDATE `smart_scripts` SET `action_type` = 132, `action_param1` = 101, `action_param2` = 0, `action_param3` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Hobb - On Just Died - Despawn Skybreakers Group' WHERE `entryorguid` = 23434 AND `source_type` = 0 AND `id` = 8; + +-- Commander Arcus (Aldor): same cleanup +UPDATE `smart_scripts` SET `action_type` = 132, `action_param1` = 102, `action_param2` = 0, `action_param3` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Arcus - On Just Died - Despawn Defenders Group' WHERE `entryorguid` = 23452 AND `source_type` = 0 AND `id` = 7; +UPDATE `smart_scripts` SET `action_type` = 132, `action_param1` = 103, `action_param2` = 0, `action_param3` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Arcus - On Just Died - Despawn Skybreakers Group' WHERE `entryorguid` = 23452 AND `source_type` = 0 AND `id` = 8; + +-- Hobb start actionlist: SPAWN_SPAWNGROUP +UPDATE `smart_scripts` SET `action_type` = 131, `action_param1` = 100, `action_param2` = 0, `action_param3` = 0, `action_param4` = 0, `action_param5` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Hobb - Actionlist - Spawn Sanctum Defenders Group' WHERE `entryorguid` = 2343400 AND `source_type` = 9 AND `id` = 3; +UPDATE `smart_scripts` SET `action_type` = 131, `action_param1` = 101, `action_param2` = 0, `action_param3` = 0, `action_param4` = 0, `action_param5` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Hobb - Actionlist - Spawn Dragonmaw Skybreakers Group' WHERE `entryorguid` = 2343400 AND `source_type` = 9 AND `id` = 10; + +-- Hobb complete actionlist: DESPAWN_SPAWNGROUP +UPDATE `smart_scripts` SET `action_type` = 132, `action_param1` = 100, `action_param2` = 0, `action_param3` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Hobb - Actionlist - Despawn Defenders Group' WHERE `entryorguid` = 2343402 AND `source_type` = 9 AND `id` = 0; +UPDATE `smart_scripts` SET `action_type` = 132, `action_param1` = 101, `action_param2` = 0, `action_param3` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Hobb - Actionlist - Despawn Skybreakers Group' WHERE `entryorguid` = 2343402 AND `source_type` = 9 AND `id` = 1; + +-- Arcus start actionlist +UPDATE `smart_scripts` SET `action_type` = 131, `action_param1` = 102, `action_param2` = 0, `action_param3` = 0, `action_param4` = 0, `action_param5` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Arcus - Actionlist - Spawn Altar Defenders Group' WHERE `entryorguid` = 2345200 AND `source_type` = 9 AND `id` = 5; +UPDATE `smart_scripts` SET `action_type` = 131, `action_param1` = 103, `action_param2` = 0, `action_param3` = 0, `action_param4` = 0, `action_param5` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Arcus - Actionlist - Spawn Dragonmaw Skybreakers Group' WHERE `entryorguid` = 2345200 AND `source_type` = 9 AND `id` = 9; + +-- Arcus complete actionlist +UPDATE `smart_scripts` SET `action_type` = 132, `action_param1` = 102, `action_param2` = 0, `action_param3` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Arcus - Actionlist - Despawn Defenders Group' WHERE `entryorguid` = 2345202 AND `source_type` = 9 AND `id` = 0; +UPDATE `smart_scripts` SET `action_type` = 132, `action_param1` = 103, `action_param2` = 0, `action_param3` = 0, `target_type` = 1, `target_param1` = 0, `target_param3` = 0, `comment` = 'Commander Arcus - Actionlist - Despawn Skybreakers Group' WHERE `entryorguid` = 2345202 AND `source_type` = 9 AND `id` = 1; diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 4b90be524..f2bb0bedd 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -2054,6 +2054,8 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_WORLD_SCRIPT: case SMART_ACTION_SET_GOSSIP_MENU: case SMART_ACTION_SUMMON_GAMEOBJECT_GROUP: + case SMART_ACTION_SPAWN_SPAWNGROUP: + case SMART_ACTION_DESPAWN_SPAWNGROUP: break; default: LOG_ERROR("sql.sql", "SmartAIMgr: Not handled action_type({}), event_type({}), Entry {} SourceType {} Event {}, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id); From c83fc175a3a10324f6f64504b609ed639fbc7543 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Apr 2026 13:13:25 +0000 Subject: [PATCH 13/27] chore(DB): import pending files Referenced commit(s): f9833c0dd45468ddca5daa323cf5ed34cfda1494 --- .../rev_1776906471063424160.sql => db_world/2026_04_23_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1776906471063424160.sql => db_world/2026_04_23_00.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1776906471063424160.sql b/data/sql/updates/db_world/2026_04_23_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1776906471063424160.sql rename to data/sql/updates/db_world/2026_04_23_00.sql index 663e75723..e4baebe6f 100644 --- a/data/sql/updates/pending_db_world/rev_1776906471063424160.sql +++ b/data/sql/updates/db_world/2026_04_23_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_22_03 -> 2026_04_23_00 -- -- Fix quest "The Deadliest Trap Ever Laid" (11097 Scryer / 11101 Aldor): -- the AC-specific SCRIPTED_SPAWN (state=0 on AI_INIT -> despawn self) From 4fd0c385dad3bd7417ea82301b246208f6977a9e Mon Sep 17 00:00:00 2001 From: Axel Cocat Date: Thu, 23 Apr 2026 16:41:26 +0200 Subject: [PATCH 14/27] feat(Core/Scripting): Pass `IoContext` to `OnNetworkStart` (#25501) --- src/server/game/Scripting/ScriptDefines/ServerScript.cpp | 7 ++++--- src/server/game/Scripting/ScriptDefines/ServerScript.h | 2 ++ src/server/game/Scripting/ScriptMgr.h | 2 +- src/server/game/Server/WorldSocketMgr.cpp | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/server/game/Scripting/ScriptDefines/ServerScript.cpp b/src/server/game/Scripting/ScriptDefines/ServerScript.cpp index 32998652c..faab0ca53 100644 --- a/src/server/game/Scripting/ScriptDefines/ServerScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/ServerScript.cpp @@ -15,13 +15,14 @@ * with this program. If not, see . */ -#include "ServerScript.h" +#include "IoContext.h" #include "ScriptMgr.h" #include "ScriptMgrMacros.h" +#include "ServerScript.h" -void ScriptMgr::OnNetworkStart() +void ScriptMgr::OnNetworkStart(Acore::Asio::IoContext& ioContext) { - CALL_ENABLED_HOOKS(ServerScript, SERVERHOOK_ON_NETWORK_START, script->OnNetworkStart()); + CALL_ENABLED_HOOKS(ServerScript, SERVERHOOK_ON_NETWORK_START, script->OnNetworkStart(ioContext)); } void ScriptMgr::OnNetworkStop() diff --git a/src/server/game/Scripting/ScriptDefines/ServerScript.h b/src/server/game/Scripting/ScriptDefines/ServerScript.h index 07ac15b8f..c583856e2 100644 --- a/src/server/game/Scripting/ScriptDefines/ServerScript.h +++ b/src/server/game/Scripting/ScriptDefines/ServerScript.h @@ -18,6 +18,7 @@ #ifndef SCRIPT_OBJECT_SERVER_SCRIPT_H_ #define SCRIPT_OBJECT_SERVER_SCRIPT_H_ +#include "IoContext.h" #include "ScriptObject.h" #include #include // NOTE: this import is NEEDED (even though some IDEs report it as unused) @@ -41,6 +42,7 @@ protected: public: // Called when reactive socket I/O is started (WorldSocketMgr). virtual void OnNetworkStart() { } + virtual void OnNetworkStart(Acore::Asio::IoContext& /*ioContext*/) { OnNetworkStart(); } // Called when reactive I/O is stopped. virtual void OnNetworkStop() { } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 9fdc06897..5e7bf86c5 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -154,7 +154,7 @@ public: /* SpellScriptLoader */ void CreateSpellScriptLoaders(uint32 spellId, std::vector::iterator>>& scriptVector); public: /* ServerScript */ - void OnNetworkStart(); + void OnNetworkStart(Acore::Asio::IoContext& ioContext); void OnNetworkStop(); void OnSocketOpen(std::shared_ptr const& socket); void OnSocketClose(std::shared_ptr const& socket); diff --git a/src/server/game/Server/WorldSocketMgr.cpp b/src/server/game/Server/WorldSocketMgr.cpp index 3b5053e1d..d53993eba 100644 --- a/src/server/game/Server/WorldSocketMgr.cpp +++ b/src/server/game/Server/WorldSocketMgr.cpp @@ -70,7 +70,7 @@ bool WorldSocketMgr::StartWorldNetwork(Acore::Asio::IoContext& ioContext, std::s _acceptor->AsyncAcceptWithCallback<&WorldSocketMgr::OnSocketAccept>(); - sScriptMgr->OnNetworkStart(); + sScriptMgr->OnNetworkStart(ioContext); return true; } From cdd2c71a4f7d5b0bcb0f1c91378317c784112b6b Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Thu, 23 Apr 2026 23:55:57 +0200 Subject: [PATCH 15/27] fix(DB/SAI): Set right target to Unworthy Initiate Anchor actions. (#25564) --- .../updates/pending_db_world/rev_1776967556.sql | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1776967556.sql diff --git a/data/sql/updates/pending_db_world/rev_1776967556.sql b/data/sql/updates/pending_db_world/rev_1776967556.sql new file mode 100644 index 000000000..2ed158d62 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1776967556.sql @@ -0,0 +1,17 @@ + +-- Update targets +UPDATE `smart_scripts` SET `target_param1` = 65915, `target_param2` = 191577 WHERE (`entryorguid` IN (-128571)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65916, `target_param2` = 191580 WHERE (`entryorguid` IN (-128567)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65920, `target_param2` = 191581 WHERE (`entryorguid` IN (-128570)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65921, `target_param2` = 191582 WHERE (`entryorguid` IN (-128576)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65923, `target_param2` = 191583 WHERE (`entryorguid` IN (-128565)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65925, `target_param2` = 191584 WHERE (`entryorguid` IN (-128574)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65926, `target_param2` = 191585 WHERE (`entryorguid` IN (-128573)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65927, `target_param2` = 191586 WHERE (`entryorguid` IN (-128568)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65928, `target_param2` = 191587 WHERE (`entryorguid` IN (-128566)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65929, `target_param2` = 191588 WHERE (`entryorguid` IN (-128572)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65930, `target_param2` = 191589 WHERE (`entryorguid` IN (-128575)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); +UPDATE `smart_scripts` SET `target_param1` = 65931, `target_param2` = 191590 WHERE (`entryorguid` IN (-128569)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); + +-- Delete creature_addon table from an Unworthy Initiate. +DELETE FROM `creature_addon` WHERE (`guid` IN (128740)); From f4d8f84db3b1a17bc677cd518133ca1d7a82907d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Apr 2026 21:57:06 +0000 Subject: [PATCH 16/27] chore(DB): import pending files Referenced commit(s): cdd2c71a4f7d5b0bcb0f1c91378317c784112b6b --- .../rev_1776967556.sql => db_world/2026_04_23_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1776967556.sql => db_world/2026_04_23_01.sql} (97%) diff --git a/data/sql/updates/pending_db_world/rev_1776967556.sql b/data/sql/updates/db_world/2026_04_23_01.sql similarity index 97% rename from data/sql/updates/pending_db_world/rev_1776967556.sql rename to data/sql/updates/db_world/2026_04_23_01.sql index 2ed158d62..0a45eebd5 100644 --- a/data/sql/updates/pending_db_world/rev_1776967556.sql +++ b/data/sql/updates/db_world/2026_04_23_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_23_00 -> 2026_04_23_01 -- Update targets UPDATE `smart_scripts` SET `target_param1` = 65915, `target_param2` = 191577 WHERE (`entryorguid` IN (-128571)) AND (`source_type` = 0) AND (`id` IN (1, 2, 3)); From 13c208b0f298002326b429df0c8ba6184009910e Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:02:40 +0200 Subject: [PATCH 17/27] fix(DB/SAI): Move Magister Aledis behavior to SmartAI. (#25560) --- .../pending_db_world/rev_1776948696.sql | 43 +++++++ .../Outland/zone_hellfire_peninsula.cpp | 108 ------------------ 2 files changed, 43 insertions(+), 108 deletions(-) create mode 100644 data/sql/updates/pending_db_world/rev_1776948696.sql diff --git a/data/sql/updates/pending_db_world/rev_1776948696.sql b/data/sql/updates/pending_db_world/rev_1776948696.sql new file mode 100644 index 000000000..9e75d8d6b --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1776948696.sql @@ -0,0 +1,43 @@ + +-- Remove Mount from Creature Addon (it is setted using SAI). +UPDATE `creature_addon` SET `mount` = 0 WHERE (`guid` IN (86873)); + +-- Set General SAI. +UPDATE `creature_template` SET `AIName` = 'SmartAI', `ScriptName` = '' WHERE `entry` = 20159; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 20159); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(20159, 0, 0, 1, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 83, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Respawn - Remove Npc Flags Questgiver'), +(20159, 0, 1, 2, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 18, 256, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Respawn - Set Flags Immune To Players'), +(20159, 0, 2, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 43, 0, 18696, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Respawn - Mount To Model 18696'), +(20159, 0, 3, 4, 62, 0, 100, 0, 8081, 0, 0, 0, 0, 0, 64, 23, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Gossip Option 0 Selected - Store Targetlist'), +(20159, 0, 4, 5, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Gossip Option 0 Selected - Remove Npc Flags Gossip'), +(20159, 0, 5, 6, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Gossip Option 0 Selected - Set Event Phase 1'), +(20159, 0, 6, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2015900, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Gossip Option 0 Selected - Run Script'), +(20159, 0, 7, 8, 2, 1, 100, 0, 0, 20, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Between 0-20% Health - Set Event Phase 2 (Phase 1)'), +(20159, 0, 8, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2015901, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Between 0-20% Health - Run Script (Phase 1)'), +(20159, 0, 9, 10, 7, 1, 100, 0, 0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Evade - Set Event Phase 0 (Phase 1)'), +(20159, 0, 10, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2015902, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Evade - Run Script (Phase 1)'), +(20159, 0, 11, 0, 0, 0, 100, 0, 0, 0, 5000, 8000, 0, 0, 11, 20823, 64, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - In Combat - Cast \'Fireball\''), +(20159, 0, 12, 0, 106, 0, 100, 0, 5000, 10000, 12000, 18000, 0, 10, 11, 11831, 64, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - On Hostile in Range - Cast \'Frost Nova\''); + +-- Set Action Lists. +DELETE FROM `smart_scripts` WHERE (`source_type` = 9) AND (`entryorguid` IN (2015900, 2015901, 2015902)); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(2015900, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 42, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Set Invincibility Hp 1%'), +(2015900, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 206, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Dismount'), +(2015900, 9, 2, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Say Line 0'), +(2015900, 9, 3, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 19, 256, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Remove Flags Immune To Players'), +(2015900, 9, 4, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 2, 14, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Set Faction 14'), +(2015900, 9, 5, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 12, 23, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Start Attacking'), +(2015901, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Stop Attack'), +(2015901, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 18, 256, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Set Flags Immune To Players'), +(2015901, 9, 2, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 2, 1604, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Set Faction 1604'), +(2015901, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 82, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Add Npc Flags Questgiver'), +(2015901, 9, 4, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Say Line 1'), +(2015901, 9, 5, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 60000, 30, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Despawn In 60000 ms'), +(2015902, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 82, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Add Npc Flags Gossip'), +(2015902, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 18, 256, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Set Flags Immune To Players'), +(2015902, 9, 2, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Reset Invincibility Hp'), +(2015902, 9, 3, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 2, 1604, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Set Faction 1604'), +(2015902, 9, 4, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 43, 0, 18696, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Magister Aledis - Actionlist - Mount To Model 18696'); diff --git a/src/server/scripts/Outland/zone_hellfire_peninsula.cpp b/src/server/scripts/Outland/zone_hellfire_peninsula.cpp index ad55d673e..b8dfd4deb 100644 --- a/src/server/scripts/Outland/zone_hellfire_peninsula.cpp +++ b/src/server/scripts/Outland/zone_hellfire_peninsula.cpp @@ -457,113 +457,6 @@ public: } }; -enum Aledis -{ - SAY_CHALLENGE = 0, - SAY_DEFEATED = 1, - EVENT_TALK = 1, - EVENT_ATTACK = 2, - EVENT_EVADE = 3, - EVENT_FIREBALL = 4, - EVENT_FROSTNOVA = 5, - SPELL_FIREBALL = 20823, - SPELL_FROSTNOVA = 11831, -}; - -struct npc_magister_aledis : public ScriptedAI -{ - npc_magister_aledis(Creature* creature) : ScriptedAI(creature) { } - - void StartFight(Player* player) - { - me->Dismount(); - me->SetFacingToObject(player); - me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP); - _playerGUID = player->GetGUID(); - _events.ScheduleEvent(EVENT_TALK, 2s); - } - - void Reset() override - { - me->RestoreFaction(); - me->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); - me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP); - me->SetImmuneToPC(true); - } - - void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*spellInfo = nullptr*/) override - { - if (damage > me->GetHealth() || me->HealthBelowPctDamaged(20, damage)) - { - damage = 0; - - _events.Reset(); - me->RestoreFaction(); - me->RemoveAllAuras(); - me->GetThreatMgr().ClearAllThreat(); - me->CombatStop(true); - me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER); - me->SetImmuneToPC(true); - Talk(SAY_DEFEATED); - - _events.ScheduleEvent(EVENT_EVADE, 1min); - } - } - - void UpdateAI(uint32 diff) override - { - _events.Update(diff); - - while (uint32 eventId = _events.ExecuteEvent()) - { - switch (eventId) - { - case EVENT_TALK: - Talk(SAY_CHALLENGE); - _events.ScheduleEvent(EVENT_ATTACK, 2s); - break; - case EVENT_ATTACK: - me->SetImmuneToPC(false); - me->SetFaction(FACTION_MONSTER); - if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGUID)) - { - AttackStart(player); - } - _events.ScheduleEvent(EVENT_FIREBALL, 1ms); - _events.ScheduleEvent(EVENT_FROSTNOVA, 5s); - break; - case EVENT_FIREBALL: - DoCast(SPELL_FIREBALL); - _events.ScheduleEvent(EVENT_FIREBALL, 10s); - break; - case EVENT_FROSTNOVA: - DoCastAOE(SPELL_FROSTNOVA); - _events.ScheduleEvent(EVENT_FROSTNOVA, 20s); - break; - case EVENT_EVADE: - EnterEvadeMode(); - break; - } - } - - if (UpdateVictim()) - { - DoMeleeAttackIfReady(); - } - } - - void sGossipSelect(Player* player, uint32 /*sender*/, uint32 /*action*/) override - { - CloseGossipMenuFor(player); - me->StopMoving(); - StartFight(player); - } - -private: - EventMap _events; - ObjectGuid _playerGUID; -}; - enum Beacon { NPC_STONESCHYE_WHELP = 16927, @@ -645,6 +538,5 @@ void AddSC_hellfire_peninsula() new npc_fel_guard_hound(); new go_beacon(); - RegisterCreatureAI(npc_magister_aledis); RegisterGameObjectAI(go_magtheridons_head); } From 81b71b718b489733e47fb4aa96d47f3477ef5b89 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Apr 2026 22:03:45 +0000 Subject: [PATCH 18/27] chore(DB): import pending files Referenced commit(s): 13c208b0f298002326b429df0c8ba6184009910e --- .../rev_1776948696.sql => db_world/2026_04_23_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1776948696.sql => db_world/2026_04_23_02.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1776948696.sql b/data/sql/updates/db_world/2026_04_23_02.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1776948696.sql rename to data/sql/updates/db_world/2026_04_23_02.sql index 9e75d8d6b..3259acf9f 100644 --- a/data/sql/updates/pending_db_world/rev_1776948696.sql +++ b/data/sql/updates/db_world/2026_04_23_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_23_01 -> 2026_04_23_02 -- Remove Mount from Creature Addon (it is setted using SAI). UPDATE `creature_addon` SET `mount` = 0 WHERE (`guid` IN (86873)); From 19855382fbebb8c659990e5860d4bfa48f9fd06d Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:07:38 -0500 Subject: [PATCH 19/27] fix(Scripts/HoL): Restart Bjarngrim escort after JustRespawned (#25567) Co-authored-by: blinkysc --- .../Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp index 55ea322f0..6e534d390 100644 --- a/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp +++ b/src/server/scripts/Northrend/Ulduar/HallsOfLightning/boss_bjarngrim.cpp @@ -129,6 +129,13 @@ struct boss_bjarngrim : public npc_escortAI AddWaypoint(7, 1262.0f, -26.9f, 33.5f, 0); } + void JustRespawned() override + { + npc_escortAI::JustRespawned(); + me->SetWalk(true); + Start(true, ObjectGuid::Empty, nullptr, false, true); + } + void Reset() override { events.Reset(); From 0f2841ddcf3e2e010ac187c9358ed45353ed9273 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 23 Apr 2026 17:58:16 -0500 Subject: [PATCH 20/27] fix(Core/Combat): Training dummy stuck combat after pet recall (#25565) Co-authored-by: blinkysc --- src/server/scripts/World/npcs_special.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index 3e7ebb34e..0713d3600 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -329,6 +329,12 @@ struct npc_training_dummy : NullCreatureAI return; _combatTimer[attacker->GetGUID()] = 5s; + + // Pet attacks engage the owner via propagation without firing + // JustEnteredCombat here, so track the owner's timer too. + if (Unit* owner = attacker->GetCharmerOrOwner()) + if (me->GetCombatManager().IsInCombatWith(owner)) + _combatTimer[owner->GetGUID()] = 5s; } void UpdateAI(uint32 diff) override From cd3cccc9613e9f9816e0bf0ed2f19a079efa37e8 Mon Sep 17 00:00:00 2001 From: Axel Cocat Date: Fri, 24 Apr 2026 08:17:31 +0200 Subject: [PATCH 21/27] feat(Core/Scripting): Add `OnPlayerBeforeGetLevelForXPGain` hook (#25295) --- .../game/Entities/Player/KillRewarder.cpp | 22 +++++++++++++------ .../game/Entities/Player/KillRewarder.h | 2 ++ src/server/game/Entities/Player/Player.cpp | 10 ++++++--- .../game/Entities/Player/PlayerQuest.cpp | 5 ++++- src/server/game/Handlers/LFGHandler.cpp | 12 +++++++--- src/server/game/Miscellaneous/Formulas.cpp | 5 ++++- .../Scripting/ScriptDefines/PlayerScript.cpp | 9 ++++++++ .../Scripting/ScriptDefines/PlayerScript.h | 9 ++++++++ src/server/game/Scripting/ScriptMgr.h | 1 + 9 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/server/game/Entities/Player/KillRewarder.cpp b/src/server/game/Entities/Player/KillRewarder.cpp index 78d24b4db..0bbb4c541 100644 --- a/src/server/game/Entities/Player/KillRewarder.cpp +++ b/src/server/game/Entities/Player/KillRewarder.cpp @@ -67,7 +67,7 @@ KillRewarder::KillRewarder(Player* killer, Unit* victim, bool isBattleGround) : // 1. Initialize internal variables to default values. _killer(killer), _victim(victim), _group(killer->GetGroup()), - _groupRate(1.0f), _maxNotGrayMember(nullptr), _count(0), _aliveSumLevel(0), _sumLevel(0), _xp(0), + _groupRate(1.0f), _maxNotGrayMember(nullptr), _maxNotGrayMemberLevel(0), _count(0), _aliveSumLevel(0), _sumLevel(0), _xp(0), _isFullXP(false), _maxLevel(0), _isBattleGround(isBattleGround), _isPvP(false) { // mark the credit as pvp if victim is player @@ -89,7 +89,7 @@ void KillRewarder::_InitGroupData() if (Player* member = itr->GetSource()) if ((_killer == member || member->IsAtGroupRewardDistance(_victim))) { - const uint8 lvl = member->GetLevel(); + const uint8 lvl = _GetPlayerLevel(member); if (member->IsAlive()) { // 2.1. _count - number of alive group members within reward distance; @@ -104,9 +104,10 @@ void KillRewarder::_InitGroupData() // 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance, // for whom victim is not gray; uint32 grayLevel = Acore::XP::GetGrayLevel(lvl); - if (_victim->GetLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->GetLevel() < lvl)) + if (_victim->GetLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMemberLevel < lvl)) { _maxNotGrayMember = member; + _maxNotGrayMemberLevel = lvl; } } // 2.5. _sumLevel - sum of levels of group members within reward distance; @@ -114,7 +115,7 @@ void KillRewarder::_InitGroupData() } // 2.6. _isFullXP - flag identifying that for all group members victim is not gray, // so 100% XP will be rewarded (50% otherwise). - _isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMember->GetLevel()); + _isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMemberLevel); } else _count = 1; @@ -153,7 +154,7 @@ void KillRewarder::_RewardXP(Player* player, float rate) // * set to 0 if player's level is more than maximum level of not gray member; // * cut XP in half if _isFullXP is false. if (_maxNotGrayMember && player->IsAlive() && - _maxNotGrayMember->GetLevel() >= player->GetLevel()) + _maxNotGrayMemberLevel >= _GetPlayerLevel(player)) xp = _isFullXP ? uint32(xp * rate) : // Reward FULL XP if all group members are not gray. uint32(xp * rate / 2) + 1; // Reward only HALF of XP if some of group members are gray. @@ -208,8 +209,8 @@ void KillRewarder::_RewardPlayer(Player* player, bool isDungeon) // Give reputation and kill credit only in PvE. if (!_isPvP || _isBattleGround) { - float xpRate = _group ? _groupRate * float(player->GetLevel()) / _aliveSumLevel : /*Personal rate is 100%.*/ 1.0f; // Group rate depends on the sum of levels. - sScriptMgr->OnPlayerRewardKillRewarder(player, this, isDungeon, xpRate); // Personal rate is 100%. + float xpRate = _group ? _groupRate * float(_GetPlayerLevel(player)) / _aliveSumLevel : /*Personal rate is 100%.*/ 1.0f; // Group rate depends on the sum of levels. + sScriptMgr->OnPlayerRewardKillRewarder(player, this, isDungeon, xpRate); // Personal rate is 100%. if (_xp) { @@ -264,6 +265,13 @@ void KillRewarder::_RewardGroup() } } +uint8 KillRewarder::_GetPlayerLevel(Player const* player) +{ + uint8 level = player->GetLevel(); + sScriptMgr->OnPlayerBeforeGetLevelForXPGain(player, level); + return level; +} + void KillRewarder::Reward() { // 3. Reward killer (and group, if necessary). diff --git a/src/server/game/Entities/Player/KillRewarder.h b/src/server/game/Entities/Player/KillRewarder.h index a91a2a045..269d82604 100644 --- a/src/server/game/Entities/Player/KillRewarder.h +++ b/src/server/game/Entities/Player/KillRewarder.h @@ -43,12 +43,14 @@ private: void _RewardKillCredit(Player* player); void _RewardPlayer(Player* player, bool isDungeon); void _RewardGroup(); + uint8 _GetPlayerLevel(Player const* player); Player* _killer; Unit* _victim; Group* _group; float _groupRate; Player* _maxNotGrayMember; + uint8 _maxNotGrayMemberLevel; uint32 _count; uint32 _aliveSumLevel; uint32 _sumLevel; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index b7dcf8724..389a2edae 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -2370,6 +2370,7 @@ void Player::GiveXP(uint32 xp, Unit* victim, float group_rate, bool isLFGReward) } uint8 level = GetLevel(); + sScriptMgr->OnPlayerBeforeGetLevelForXPGain(this, level); // Favored experience increase START uint32 zone = GetZoneId(); @@ -5775,17 +5776,20 @@ void Player::CheckAreaExploreAndOutdoor() if (areaEntry->area_level > 0) { - if (GetLevel() >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) + uint8 playerLevel = GetLevel(); + sScriptMgr->OnPlayerBeforeGetLevelForXPGain(this, playerLevel); + + if (playerLevel >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) { SendExplorationExperience(areaId, 0); } else { - int32 diff = int32(GetLevel()) - areaEntry->area_level; + int32 diff = int32(playerLevel) - areaEntry->area_level; uint32 XP = 0; if (diff < -5) { - XP = uint32(sObjectMgr->GetBaseXP(GetLevel() + 5) * sWorld->getRate(RATE_XP_EXPLORE)); + XP = uint32(sObjectMgr->GetBaseXP(playerLevel + 5) * sWorld->getRate(RATE_XP_EXPLORE)); } else if (diff > 5) { diff --git a/src/server/game/Entities/Player/PlayerQuest.cpp b/src/server/game/Entities/Player/PlayerQuest.cpp index edfe48c9a..1a6ff32f1 100644 --- a/src/server/game/Entities/Player/PlayerQuest.cpp +++ b/src/server/game/Entities/Player/PlayerQuest.cpp @@ -1435,8 +1435,11 @@ bool Player::TakeQuestSourceItem(uint32 questId, bool msg) uint32 Player::CalculateQuestRewardXP(Quest const* quest) { + uint8 level = GetLevel(); + sScriptMgr->OnPlayerBeforeGetLevelForXPGain(this, level); + // apply world quest rate - uint32 xp = uint32(quest->XPValue(GetLevel()) * GetQuestRate(quest->IsDFQuest())); + uint32 xp = uint32(quest->XPValue(level) * GetQuestRate(quest->IsDFQuest())); // handle SPELL_AURA_MOD_XP_QUEST_PCT auras xp *= GetTotalAuraMultiplier(SPELL_AURA_MOD_XP_QUEST_PCT); diff --git a/src/server/game/Handlers/LFGHandler.cpp b/src/server/game/Handlers/LFGHandler.cpp index 934f6b84c..befc84be1 100644 --- a/src/server/game/Handlers/LFGHandler.cpp +++ b/src/server/game/Handlers/LFGHandler.cpp @@ -23,6 +23,7 @@ #include "ObjectMgr.h" #include "Opcodes.h" #include "Player.h" +#include "ScriptMgr.h" #include "WorldPacket.h" #include "WorldSession.h" @@ -188,10 +189,13 @@ void WorldSession::HandleLfgPlayerLockInfoRequestOpcode(WorldPacket& /*recvData* if (quest) { uint8 playerLevel = GetPlayer() ? GetPlayer()->GetLevel() : 0; + uint8 playerLevelForXP = playerLevel; + sScriptMgr->OnPlayerBeforeGetLevelForXPGain(GetPlayer(), playerLevelForXP); + data << uint8(done); data << uint32(quest->GetRewOrReqMoney(playerLevel)); - if (!GetPlayer()->IsMaxLevel()) - data << uint32(quest->XPValue(playerLevel)); + if (playerLevelForXP < GetPlayer()->GetUInt32Value(PLAYER_FIELD_MAX_LEVEL)) + data << uint32(quest->XPValue(playerLevelForXP)); else data << uint32(0); data << uint32(0); @@ -479,6 +483,8 @@ void WorldSession::SendLfgPlayerReward(lfg::LfgPlayerRewardData const& rewardDat uint8 itemNum = rewardData.quest->GetRewItemsCount(); uint8 playerLevel = GetPlayer() ? GetPlayer()->GetLevel() : 0; + uint8 playerLevelForXP = playerLevel; + sScriptMgr->OnPlayerBeforeGetLevelForXPGain(GetPlayer(), playerLevelForXP); WorldPacket data(SMSG_LFG_PLAYER_REWARD, 4 + 4 + 1 + 4 + 4 + 4 + 4 + 4 + 1 + itemNum * (4 + 4 + 4)); data << uint32(rewardData.rdungeonEntry); // Random Dungeon Finished @@ -486,7 +492,7 @@ void WorldSession::SendLfgPlayerReward(lfg::LfgPlayerRewardData const& rewardDat data << uint8(rewardData.done); data << uint32(1); data << uint32(rewardData.quest->GetRewOrReqMoney(playerLevel)); - data << uint32(rewardData.quest->XPValue(playerLevel)); + data << uint32(rewardData.quest->XPValue(playerLevelForXP)); data << uint32(0); data << uint32(0); data << uint8(itemNum); diff --git a/src/server/game/Miscellaneous/Formulas.cpp b/src/server/game/Miscellaneous/Formulas.cpp index e8b6ea568..f2ee58c49 100644 --- a/src/server/game/Miscellaneous/Formulas.cpp +++ b/src/server/game/Miscellaneous/Formulas.cpp @@ -21,6 +21,7 @@ #include "Creature.h" #include "Log.h" #include "Player.h" +#include "ScriptMgr.h" #include "World.h" uint32 Acore::XP::BaseGain(uint8 pl_level, uint8 mob_level, ContentLevels content) @@ -79,7 +80,9 @@ uint32 Acore::XP::Gain(Player* player, Unit* unit, bool isBattleGround /*= false { float xpMod = 1.0f; - gain = BaseGain(player->GetLevel(), unit->GetLevel(), GetContentLevelsForMapAndZone(unit->GetMapId(), unit->GetZoneId())); + uint8 playerLevel = player->GetLevel(); + sScriptMgr->OnPlayerBeforeGetLevelForXPGain(player, playerLevel); + gain = BaseGain(playerLevel, unit->GetLevel(), GetContentLevelsForMapAndZone(unit->GetMapId(), unit->GetZoneId())); if (gain && creature) { diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp index fa43072c2..1c550761b 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp @@ -18,6 +18,9 @@ #include "PlayerScript.h" #include "ScriptMgr.h" #include "ScriptMgrMacros.h" +#include "World.h" + +#include void ScriptMgr::OnPlayerBeforeDurabilityRepair(Player* player, ObjectGuid npcGUID, ObjectGuid itemGUID, float& discountMod, uint8 guildBank) { @@ -930,6 +933,12 @@ void ScriptMgr::OnPlayerLearnTaxiNode(Player const* player, uint32 nodeId) CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_LEARN_TAXI_NODE, script->OnPlayerLearnTaxiNode(player, nodeId)); } +void ScriptMgr::OnPlayerBeforeGetLevelForXPGain(Player const* player, uint8& level) +{ + CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_BEFORE_GET_LEVEL_FOR_XP_GAIN, script->OnPlayerBeforeGetLevelForXPGain(player, level)); + level = std::clamp(level, uint8(1), uint8(sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))); +} + PlayerScript::PlayerScript(const char* name, std::vector enabledHooks) : ScriptObject(name, PLAYERHOOK_END) { diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.h b/src/server/game/Scripting/ScriptDefines/PlayerScript.h index 63806c1e2..dba182707 100644 --- a/src/server/game/Scripting/ScriptDefines/PlayerScript.h +++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.h @@ -211,6 +211,7 @@ enum PlayerHook PLAYERHOOK_ON_GIVE_REPUTATION, PLAYERHOOK_ON_GET_REPUTATION_PRICE_DISCOUNT, PLAYERHOOK_ON_LEARN_TAXI_NODE, + PLAYERHOOK_ON_BEFORE_GET_LEVEL_FOR_XP_GAIN, PLAYERHOOK_END }; @@ -829,6 +830,14 @@ public: * @param nodeId The id of the learned taxi node */ virtual void OnPlayerLearnTaxiNode(Player const* /*player*/, uint32 /*nodeId*/) {} + + /** + * @brief This hook is called when XP is calculated for the player, and is used to modify the player level used in the XP formulas. + * + * @param player Contains information about the Player + * @param level The level that should be used for XP gain calculations + */ + virtual void OnPlayerBeforeGetLevelForXPGain(Player const* /*player*/, uint8& /*level*/) {} }; #endif diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 5e7bf86c5..bce4a8214 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -467,6 +467,7 @@ public: /* PlayerScript */ void OnPlayerGetReputationPriceDiscount(Player const* player, Creature const* creature, float& discount); void OnPlayerGetReputationPriceDiscount(Player const* player, FactionTemplateEntry const* factionTemplate, float& discount); void OnPlayerLearnTaxiNode(Player const* player, uint32 nodeId); + void OnPlayerBeforeGetLevelForXPGain(Player const* player, uint8& level); // Anti cheat void AnticheatSetCanFlybyServer(Player* player, bool apply); From 116e5f4585591dd7838acc49e2d07e2395883b0e Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:28:41 +0200 Subject: [PATCH 22/27] fix(DB/SAI): Update Death Knight Initiate general SmartAI. (#25570) --- .../pending_db_world/rev_1776987229.sql | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1776987229.sql diff --git a/data/sql/updates/pending_db_world/rev_1776987229.sql b/data/sql/updates/pending_db_world/rev_1776987229.sql new file mode 100644 index 000000000..fa5f21f08 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1776987229.sql @@ -0,0 +1,30 @@ + +-- Update SAI. +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 28406; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28406); +INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES +(28406, 0, 0, 1, 62, 0, 100, 0, 9765, 0, 0, 0, 0, 0, 64, 12, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Gossip Option 0 Selected - Store Targetlist'), +(28406, 0, 1, 2, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Gossip Option 0 Selected - Remove Npc Flags Gossip'), +(28406, 0, 2, 3, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Gossip Option 0 Selected - Close Gossip'), +(28406, 0, 3, 4, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Gossip Option 0 Selected - Set Orientation Stored'), +(28406, 0, 4, 5, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52996, 2, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Gossip Option 0 Selected - Cast \'Duel!\''), +(28406, 0, 5, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2840600, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Gossip Option 0 Selected - Run Script'), +(28406, 0, 6, 7, 2, 2, 100, 0, 0, 1, 500, 500, 0, 0, 18, 768, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Between 0-1% Health - Set Flags Immune To Players & Immune To NPC\'s (Phase 2)'), +(28406, 0, 7, 8, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Between 0-1% Health - Remove All Gameobjects (Phase 2)'), +(28406, 0, 8, 9, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 2, 2082, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Between 0-1% Health - Set Faction 2082 (Phase 2)'), +(28406, 0, 9, 10, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Between 0-1% Health - Stop Attack (Phase 2)'), +(28406, 0, 10, 11, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Between 0-1% Health - Stop Attack (Phase 2)'), +(28406, 0, 11, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2840601, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Between 0-1% Health - Run Script (Phase 2)'), +(28406, 0, 12, 13, 5, 2, 100, 0, 0, 0, 1, 0, 0, 0, 18, 768, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Killed Unit - Set Flags Immune To Players & Immune To NPC\'s (Phase 2)'), +(28406, 0, 13, 14, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Killed Unit - Remove All Gameobjects (Phase 2)'), +(28406, 0, 14, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2840602, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Killed Unit - Run Script (Phase 2)'), +(28406, 0, 15, 16, 104, 2, 100, 0, 1, 191126, 1, 20, 500, 0, 18, 768, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Less Than 1 Units in Range - Set Flags Immune To Players & Immune To NPC\'s (Phase 2)'), +(28406, 0, 16, 17, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Less Than 1 Units in Range - Remove All Gameobjects (Phase 2)'), +(28406, 0, 17, 18, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Less Than 1 Units in Range - Stop Attack (Phase 2)'), +(28406, 0, 18, 19, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Less Than 1 Units in Range - Stop Attack (Phase 2)'), +(28406, 0, 19, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 80, 2840611, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - On Less Than 1 Units in Range - Run Script (Phase 2)'), +(28406, 0, 20, 0, 0, 0, 100, 0, 1000, 2000, 18000, 22000, 0, 0, 11, 52372, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - In Combat - Cast \'Icy Touch\''), +(28406, 0, 21, 0, 0, 0, 100, 0, 500, 1500, 10000, 14000, 0, 0, 11, 52373, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - In Combat - Cast \'Plague Strike\''), +(28406, 0, 22, 0, 0, 0, 100, 0, 3000, 4000, 4000, 6000, 0, 0, 11, 52374, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - In Combat - Cast \'Blood Strike\''), +(28406, 0, 23, 0, 0, 0, 100, 0, 3000, 4000, 4000, 6000, 0, 0, 11, 52375, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - In Combat - Cast \'Death Coil\''); From a1ff3ed3cf1359f7bdc11ba72b5113e07b21cc15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Apr 2026 08:29:51 +0000 Subject: [PATCH 23/27] chore(DB): import pending files Referenced commit(s): 116e5f4585591dd7838acc49e2d07e2395883b0e --- .../rev_1776987229.sql => db_world/2026_04_24_00.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1776987229.sql => db_world/2026_04_24_00.sql} (99%) diff --git a/data/sql/updates/pending_db_world/rev_1776987229.sql b/data/sql/updates/db_world/2026_04_24_00.sql similarity index 99% rename from data/sql/updates/pending_db_world/rev_1776987229.sql rename to data/sql/updates/db_world/2026_04_24_00.sql index fa5f21f08..455d739b4 100644 --- a/data/sql/updates/pending_db_world/rev_1776987229.sql +++ b/data/sql/updates/db_world/2026_04_24_00.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_23_02 -> 2026_04_24_00 -- Update SAI. UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 28406; From 69c9149ed3745a72bb1bd40a6a73df1fcabaa3a7 Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:30:42 +0200 Subject: [PATCH 24/27] feat(Core/Conditions): Add new Condition Unit_In_Combat. (#25569) --- data/sql/updates/pending_db_world/rev_1777019405.sql | 5 +++++ src/server/game/Conditions/ConditionMgr.cpp | 11 +++++++++++ src/server/game/Conditions/ConditionMgr.h | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 data/sql/updates/pending_db_world/rev_1777019405.sql diff --git a/data/sql/updates/pending_db_world/rev_1777019405.sql b/data/sql/updates/pending_db_world/rev_1777019405.sql new file mode 100644 index 000000000..e7c43479c --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1777019405.sql @@ -0,0 +1,5 @@ + +-- Set New Condition (Death Knight Initiate). +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 20) AND (`SourceGroup` = 0) AND (`SourceEntry` = 28406) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 106) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 0) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(20, 0, 28406, 0, 0, 106, 0, 0, 0, 0, 1, 0, 0, '', 'Death Knight Initiate gossip only available if the npc is not in combat.'); diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index a577bf9a6..90319545d 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -591,6 +591,12 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) condMeets = unit->IsCharmed(); break; } + case CONDITION_UNIT_IN_COMBAT: + { + if (Unit* unit = object->ToUnit()) + condMeets = unit->IsInCombat(); + break; + } case CONDITION_WORLD_SCRIPT: { condMeets = sWorldState->IsConditionFulfilled(ConditionValue1, ConditionValue2); @@ -804,6 +810,9 @@ uint32 Condition::GetSearcherTypeMaskForCondition() case CONDITION_CHARMED: mask |= GRID_MAP_TYPE_MASK_CREATURE | GRID_MAP_TYPE_MASK_PLAYER; break; + case CONDITION_UNIT_IN_COMBAT: + mask |= GRID_MAP_TYPE_MASK_CREATURE | GRID_MAP_TYPE_MASK_PLAYER; + break; case CONDITION_PLAYER_QUEUED_RANDOM_DUNGEON: mask |= GRID_MAP_TYPE_MASK_PLAYER; break; @@ -834,6 +843,7 @@ uint32 Condition::GetMaxAvailableConditionTargets() case CONDITION_SOURCE_TYPE_SPELL_CLICK_EVENT: case CONDITION_SOURCE_TYPE_GOSSIP_MENU: case CONDITION_SOURCE_TYPE_GOSSIP_MENU_OPTION: + case CONDITION_SOURCE_TYPE_GOSSIP_HELLO: case CONDITION_SOURCE_TYPE_NPC_VENDOR: case CONDITION_SOURCE_TYPE_SPELL_PROC: case CONDITION_SOURCE_TYPE_OBJECT_VISIBILITY: @@ -2624,6 +2634,7 @@ bool ConditionMgr::isConditionTypeValid(Condition* cond) case CONDITION_TAXI: case CONDITION_IN_WATER: case CONDITION_CHARMED: + case CONDITION_UNIT_IN_COMBAT: default: break; } diff --git a/src/server/game/Conditions/ConditionMgr.h b/src/server/game/Conditions/ConditionMgr.h index 585048a83..5cfd108fe 100644 --- a/src/server/game/Conditions/ConditionMgr.h +++ b/src/server/game/Conditions/ConditionMgr.h @@ -89,8 +89,9 @@ enum ConditionTypes CONDITION_WORLD_SCRIPT = 103, // conditionId state 0 true if WorldState::IsConditionFulfilled returns true CONDITION_AI_DATA = 104, // dataId value 0 true if AI::GetData returns value CONDITION_PLAYER_QUEUED_RANDOM_DUNGEON = 105, // checkDifficulty difficulty 0 true if player is queued for a random dungeon via RDF + CONDITION_UNIT_IN_COMBAT = 106, // 0 0 0 true if unit is engaged in combat - CONDITION_AC_END = 106 // placeholder + CONDITION_AC_END = 107 // placeholder }; /*! Documentation on implementing a new ConditionSourceType: From 44c9bc3e2d3d5d67d06dae976fa0d8743f80f421 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Apr 2026 08:31:01 +0000 Subject: [PATCH 25/27] chore(DB): import pending files Referenced commit(s): a1ff3ed3cf1359f7bdc11ba72b5113e07b21cc15 --- .../rev_1777019405.sql => db_world/2026_04_24_01.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1777019405.sql => db_world/2026_04_24_01.sql} (94%) diff --git a/data/sql/updates/pending_db_world/rev_1777019405.sql b/data/sql/updates/db_world/2026_04_24_01.sql similarity index 94% rename from data/sql/updates/pending_db_world/rev_1777019405.sql rename to data/sql/updates/db_world/2026_04_24_01.sql index e7c43479c..7bdd4a483 100644 --- a/data/sql/updates/pending_db_world/rev_1777019405.sql +++ b/data/sql/updates/db_world/2026_04_24_01.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_24_00 -> 2026_04_24_01 -- Set New Condition (Death Knight Initiate). DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 20) AND (`SourceGroup` = 0) AND (`SourceEntry` = 28406) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 106) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 0) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); From 6ce787d03e242ed8de77b4e68c88c7d595a97c51 Mon Sep 17 00:00:00 2001 From: Rocco Silipo <108557877+Rorschach91@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:36:16 +0200 Subject: [PATCH 26/27] fix(DB/Condition): Set right ConditionTarget on Death Knight Initiate Condition. (#25573) --- data/sql/updates/pending_db_world/rev_1777034383.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 data/sql/updates/pending_db_world/rev_1777034383.sql diff --git a/data/sql/updates/pending_db_world/rev_1777034383.sql b/data/sql/updates/pending_db_world/rev_1777034383.sql new file mode 100644 index 000000000..4928c8e3a --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1777034383.sql @@ -0,0 +1,3 @@ + +-- Edit Condition. +UPDATE `conditions` SET `ConditionTarget` = 1 WHERE (`SourceTypeOrReferenceId` = 20) AND (`SourceGroup` = 0) AND (`SourceEntry` = 28406) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 106) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 0) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); From 83c4f834832211e2cb012acda5b0dda74af7da85 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Apr 2026 14:37:29 +0000 Subject: [PATCH 27/27] chore(DB): import pending files Referenced commit(s): 6ce787d03e242ed8de77b4e68c88c7d595a97c51 --- .../rev_1777034383.sql => db_world/2026_04_24_02.sql} | 1 + 1 file changed, 1 insertion(+) rename data/sql/updates/{pending_db_world/rev_1777034383.sql => db_world/2026_04_24_02.sql} (88%) diff --git a/data/sql/updates/pending_db_world/rev_1777034383.sql b/data/sql/updates/db_world/2026_04_24_02.sql similarity index 88% rename from data/sql/updates/pending_db_world/rev_1777034383.sql rename to data/sql/updates/db_world/2026_04_24_02.sql index 4928c8e3a..41aeddb8e 100644 --- a/data/sql/updates/pending_db_world/rev_1777034383.sql +++ b/data/sql/updates/db_world/2026_04_24_02.sql @@ -1,3 +1,4 @@ +-- DB update 2026_04_24_01 -> 2026_04_24_02 -- Edit Condition. UPDATE `conditions` SET `ConditionTarget` = 1 WHERE (`SourceTypeOrReferenceId` = 20) AND (`SourceGroup` = 0) AND (`SourceEntry` = 28406) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 106) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 0) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0);