Merge pull request #199 from mod-playerbots/test-staging

Test staging
This commit is contained in:
Keleborn
2026-05-01 13:12:47 -07:00
committed by GitHub
33 changed files with 413 additions and 148 deletions

View File

@@ -0,0 +1,37 @@
-- 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));
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');

View File

@@ -0,0 +1,10 @@
-- 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;
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');

View File

@@ -0,0 +1,7 @@
-- 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);
INSERT INTO `spell_proc` (`SpellId`, `SpellPhaseMask`, `Cooldown`) VALUES
(58835, 2, 2000),
(57935, 2, 2000);

View File

@@ -0,0 +1,7 @@
-- 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.
-- 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);

View File

@@ -0,0 +1,57 @@
-- 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)
-- 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;

View File

@@ -0,0 +1,18 @@
-- 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));
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));

View File

@@ -0,0 +1,44 @@
-- 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));
-- 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');

View File

@@ -0,0 +1,31 @@
-- DB update 2026_04_23_02 -> 2026_04_24_00
-- 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\'');

View File

@@ -0,0 +1,6 @@
-- 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);
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.');

View File

@@ -0,0 +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);

View File

@@ -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())

View File

@@ -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))
{

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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:

View File

@@ -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).

View File

@@ -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;

View File

@@ -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();
@@ -5784,17 +5785,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)
{

View File

@@ -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);

View File

@@ -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<HolidayRule> 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<int>(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<int>(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<uint32_t> HolidayDateCalculator::GetDarkmoonFaireDates(int locationO
if (dayOffset != 0)
{
date.tm_mday += dayOffset;
mktime(&date); // Normalize
NormalizeDate(date);
}
dates.push_back(PackDate(date));
}

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -18,6 +18,9 @@
#include "PlayerScript.h"
#include "ScriptMgr.h"
#include "ScriptMgrMacros.h"
#include "World.h"
#include <algorithm>
void ScriptMgr::OnPlayerBeforeDurabilityRepair(Player* player, ObjectGuid npcGUID, ObjectGuid itemGUID, float& discountMod, uint8 guildBank)
{
@@ -936,6 +939,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<uint16> enabledHooks)
: ScriptObject(name, PLAYERHOOK_END)
{

View File

@@ -217,6 +217,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
};
@@ -836,6 +837,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

View File

@@ -15,13 +15,14 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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()

View File

@@ -18,6 +18,7 @@
#ifndef SCRIPT_OBJECT_SERVER_SCRIPT_H_
#define SCRIPT_OBJECT_SERVER_SCRIPT_H_
#include "IoContext.h"
#include "ScriptObject.h"
#include <vector>
#include <memory> // 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() { }

View File

@@ -173,7 +173,7 @@ public: /* SpellScriptLoader */
void CreateSpellScriptLoaders(uint32 spellId, std::vector<std::pair<SpellScriptLoader*, std::multimap<uint32, uint32>::iterator>>& scriptVector);
public: /* ServerScript */
void OnNetworkStart();
void OnNetworkStart(Acore::Asio::IoContext& ioContext);
void OnNetworkStop();
void OnSocketOpen(std::shared_ptr<WorldSocket> const& socket);
void OnSocketClose(std::shared_ptr<WorldSocket> const& socket);
@@ -488,6 +488,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);

View File

@@ -70,7 +70,7 @@ bool WorldSocketMgr::StartWorldNetwork(Acore::Asio::IoContext& ioContext, std::s
_acceptor->AsyncAcceptWithCallback<&WorldSocketMgr::OnSocketAccept>();
sScriptMgr->OnNetworkStart();
sScriptMgr->OnNetworkStart(ioContext);
return true;
}

View File

@@ -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];

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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