diff --git a/data/sql/updates/db_world/2026_04_04_00.sql b/data/sql/updates/db_world/2026_04_04_00.sql new file mode 100644 index 000000000..a5a91a935 --- /dev/null +++ b/data/sql/updates/db_world/2026_04_04_00.sql @@ -0,0 +1,31 @@ +-- DB update 2026_04_03_03 -> 2026_04_04_00 +-- Fix Scarlet Monastery Cathedral Mograine/Whitemane encounter resetting during scripted phase +-- Disable evade during the scripted "fake death" / "Deep Sleep" / "resurrection" phase +-- to prevent JustExitedCombat auto-evade from triggering SMART_EVENT_EVADE and setting FAIL + +-- Mograine: Disable evade when fake-dying (actionlist 397600) +DELETE FROM `smart_scripts` WHERE `entryorguid` = 397600 AND `source_type` = 9 AND `id` = 10; +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`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) +VALUES +(397600, 9, 10, 0, 0, 0, 100, 0, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Commander Mograine - Actionlist - Disable Evade'); + +-- Mograine: Re-enable evade when reaching Whitemane after resurrection (On Reached Point 1 chain) +-- Update id 24 to link to new id 29 +UPDATE `smart_scripts` SET `link` = 29 WHERE `entryorguid` = 3976 AND `source_type` = 0 AND `id` = 24; + +DELETE FROM `smart_scripts` WHERE `entryorguid` = 3976 AND `source_type` = 0 AND `id` = 29; +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`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) +VALUES +(3976, 0, 29, 0, 61, 0, 100, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Commander Mograine - On Reached Point 1 - Enable Evade'); + +-- Whitemane: Disable evade when casting Deep Sleep (actionlist 397700) +DELETE FROM `smart_scripts` WHERE `entryorguid` = 397700 AND `source_type` = 9 AND `id` = 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`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) +VALUES +(397700, 9, 5, 0, 0, 0, 100, 0, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 'High Inquisitor Whitemane - Actionlist - Disable Evade'); + +-- Whitemane: Re-enable evade when re-engaging after resurrection (actionlist 397701) +DELETE FROM `smart_scripts` WHERE `entryorguid` = 397701 AND `source_type` = 9 AND `id` = 8; +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`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) +VALUES +(397701, 9, 8, 0, 0, 0, 100, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 'High Inquisitor Whitemane - Actionlist - Enable Evade'); diff --git a/data/sql/updates/db_world/2026_04_04_01.sql b/data/sql/updates/db_world/2026_04_04_01.sql new file mode 100644 index 000000000..e06709591 --- /dev/null +++ b/data/sql/updates/db_world/2026_04_04_01.sql @@ -0,0 +1,5 @@ +-- DB update 2026_04_04_00 -> 2026_04_04_01 +-- +DELETE FROM `item_loot_template` WHERE (`Entry` = 52676) AND (`Item` IN (47241)); +INSERT INTO `item_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES +(52676, 47241, 0, 100, 0, 1, 0, 2, 2, ''); diff --git a/data/sql/updates/db_world/2026_04_05_00.sql b/data/sql/updates/db_world/2026_04_05_00.sql new file mode 100644 index 000000000..0fbafe9f1 --- /dev/null +++ b/data/sql/updates/db_world/2026_04_05_00.sql @@ -0,0 +1,2 @@ +-- DB update 2026_04_04_01 -> 2026_04_05_00 +UPDATE `smart_scripts` SET `event_phase_mask` = 0 WHERE `entryorguid` = 27653 AND `id` IN (2, 3); diff --git a/data/sql/updates/db_world/2026_04_05_01.sql b/data/sql/updates/db_world/2026_04_05_01.sql new file mode 100644 index 000000000..8949feb7d --- /dev/null +++ b/data/sql/updates/db_world/2026_04_05_01.sql @@ -0,0 +1,17 @@ +-- DB update 2026_04_05_00 -> 2026_04_05_01 +-- +-- Alexstrasza the Life-Binder (Dragonblight) - Add Focusing Iris key menu_option. (Previously empty) +DELETE FROM `gossip_menu_option` WHERE `MenuID` = 10192; +INSERT INTO `gossip_menu_option` (`MenuID`, `OptionID`, `OptionIcon`, `OptionText`, `OptionBroadcastTextID`, `OptionType`, `OptionNpcFlag`, `ActionMenuID`, `ActionPoiID`, `BoxCoded`, `BoxMoney`, `BoxText`, `BoxBroadcastTextID`, `VerifiedBuild`) VALUES +(10192, 0, 0, 'Oh great Queen of the Dragons, I have somehow misplaced my Key to the Focusing Iris. Can you find it for me?', 32832, 1, 1, 0, 0, 0, 0, '', 0, 0), +(10192, 1, 0, 'Oh great Queen of the Dragons, I have somehow misplaced my Heroic Key to the Focusing Iris. Can you find it for me?', 32836, 1, 1, 0, 0, 0, 0, '', 0, 0); + +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 10192) AND (`SourceEntry` IN (0, 1)) AND (`SourceId` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(15, 10192, 0, 0, 0, 8, 0, 13372, 0, 0, 0, 0, 0, '', 'must have completed Quest \'The Key to the Focusing Iris\''), +(15, 10192, 0, 0, 0, 2, 0, 44582, 1, 1, 1, 0, 0, '', 'must not have item \'Key to the Focusing Iris\''), +(15, 10192, 1, 0, 0, 8, 0, 13375, 0, 0, 0, 0, 0, '', 'must have completed Quest \'The Heroic Key to the Focusing Iris\''), +(15, 10192, 1, 0, 0, 2, 0, 44581, 1, 1, 1, 0, 0, '', 'must not have item \'Heroic Key to the Focusing Iris\''); + +UPDATE `spell_dbc` SET `Targets` = 1, `Effect_1` = 24, `EffectBasePoints_1` = 1, `EffectItemType_1` = 44582 WHERE (`ID` = 60989); +UPDATE `spell_dbc` SET `Targets` = 1, `Effect_1` = 24, `EffectBasePoints_1` = 1, `EffectItemType_1` = 44581 WHERE (`ID` = 60992); diff --git a/data/sql/updates/db_world/2026_04_05_02.sql b/data/sql/updates/db_world/2026_04_05_02.sql new file mode 100644 index 000000000..4528e2540 --- /dev/null +++ b/data/sql/updates/db_world/2026_04_05_02.sql @@ -0,0 +1,9 @@ +-- DB update 2026_04_05_01 -> 2026_04_05_02 +-- Fix quest The Dread Relic +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 22369) AND (`source_type` = 0); +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 +(22369, 0, 0, 1, 54, 0, 100, 1, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Dread Relic Thrall - On Just Summoned - Set Passive'), +(22369, 0, 1, 0, 61, 0, 100, 1, 0, 0, 0, 0, 0, 0, 67, 1, 10000, 10000, 0, 0, 100, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Dread Relic Thrall - On Just Summoned - Create Timed Event'), +(22369, 0, 2, 3, 59, 0, 100, 1, 1, 0, 0, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Dread Relic Thrall - Timed Event - Set Aggressive'), +(22369, 0, 3, 0, 61, 0, 100, 1, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Dread Relic Thrall - Timed Event - Attack Summoner'), +(22369, 0, 4, 0, 4, 0, 100, 4, 0, 0, 0, 0, 0, 0, 45, 1, 1, 0, 0, 0, 0, 11, 22369, 10, 0, 0, 0, 0, 0, 0, 'Dread Relic Thrall - On Aggro - Set Data'); diff --git a/data/sql/updates/db_world/2026_04_05_03.sql b/data/sql/updates/db_world/2026_04_05_03.sql new file mode 100644 index 000000000..d0ec728eb --- /dev/null +++ b/data/sql/updates/db_world/2026_04_05_03.sql @@ -0,0 +1,19 @@ +-- DB update 2026_04_05_02 -> 2026_04_05_03 +-- Primal Ooze (6557) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 6557) AND (`source_type` = 0); +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 +(6557, 0, 0, 0, 8, 0, 100, 512, 16031, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Primal Ooze - On Spellhit 16031 (Releasing Corrupt Ooze) - Set Event Phase 2'), +(6557, 0, 1, 0, 8, 0, 100, 513, 15702, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Primal Ooze - On Spellhit 15702 (Filling Empty Jar) - Despawn'), +(6557, 0, 2, 0, 2, 0, 100, 1, 0, 30, 0, 0, 0, 0, 11, 14146, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Primal Ooze - Between 0-30% Health - Cast Clone (No Repeat)'), +(6557, 0, 3, 0, 2, 0, 100, 1, 0, 30, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Primal Ooze - Between 0-30% Health - Say Line 0 (No Repeat)'), +(6557, 0, 4, 5, 75, 2, 100, 513, 0, 10290, 3, 1000, 0, 0, 47, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Primal Ooze - Phase 2 - On Distance Creature 10290 within 3yd - Set Visibility Off'), +(6557, 0, 5, 6, 61, 2, 100, 513, 0, 0, 0, 0, 0, 0, 12, 9621, 6, 20000, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Primal Ooze - Phase 2 (Link) - Summon Gargantuan Ooze'), +(6557, 0, 6, 7, 61, 2, 100, 513, 0, 0, 0, 0, 0, 0, 11, 16032, 0, 0, 0, 0, 0, 9, 9621, 0, 10, 0, 0, 0, 0, 0, 'Primal Ooze - Phase 2 (Link) - Cast Merging Oozes on Gargantuan Ooze'), +(6557, 0, 7, 0, 61, 2, 100, 513, 0, 0, 0, 0, 0, 0, 41, 50, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Primal Ooze - Phase 2 (Link) - Despawn In 50ms'); + +-- Captured Felwood Ooze (10290) +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 10290) AND (`source_type` = 0); +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 +(10290, 0, 0, 0, 54, 0, 100, 513, 0, 0, 0, 0, 0, 0, 29, 0, 0, 1, 0, 1, 0, 9, 6557, 0, 50, 0, 0, 0, 0, 0, 'Captured Felwood Ooze - On Just Summoned - Start Follow Closest Primal Ooze'), +(10290, 0, 1, 2, 75, 0, 100, 513, 0, 6557, 3, 500, 0, 0, 47, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Captured Felwood Ooze - Within 3yd of Primal Ooze - Set Visibility Off'), +(10290, 0, 2, 0, 61, 0, 100, 513, 0, 0, 0, 0, 0, 0, 41, 500, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Captured Felwood Ooze - Within 3yd (Link) - Despawn In 500ms'); diff --git a/data/sql/updates/db_world/2026_04_10_00.sql b/data/sql/updates/db_world/2026_04_10_00.sql new file mode 100644 index 000000000..67ac00224 --- /dev/null +++ b/data/sql/updates/db_world/2026_04_10_00.sql @@ -0,0 +1,27 @@ +-- DB update 2026_04_05_03 -> 2026_04_10_00 + +-- Edit Actionlists +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2840600) AND (`source_type` = 9) AND (`id` IN (0, 1, 2, 4, 5, 8, 9)); +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 +(2840600, 9, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 54238, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Cast \'Duel Aura Check 01\''), +(2840600, 9, 1, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 52991, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Cast \'Duel Flag\''), +(2840600, 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, 'Death Knight Initiate - Actionlist - Say Line 0'), +(2840600, 9, 4, 0, 0, 0, 100, 0, 3000, 3000, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Say Line 1'), +(2840600, 9, 5, 0, 0, 0, 100, 0, 2000, 2000, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Say Line 2'), +(2840600, 9, 8, 0, 0, 0, 100, 0, 500, 500, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Set Event Phase 2'), +(2840600, 9, 9, 0, 0, 0, 100, 0, 500, 500, 0, 0, 0, 0, 42, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Set Invincibility Hp 1%'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2840601) AND (`source_type` = 9) AND (`id` IN (7, 8)); +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 +(2840601, 9, 7, 0, 0, 0, 100, 0, 1000, 1000, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Enable Evade'), +(2840601, 9, 8, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Despawn Instant'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2840602) AND (`source_type` = 9) AND (`id` IN (5, 6)); +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 +(2840602, 9, 5, 0, 0, 0, 100, 0, 1000, 1000, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Enable Evade'), +(2840602, 9, 6, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Despawn Instant'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2840611) AND (`source_type` = 9) AND (`id` IN (7, 8)); +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 +(2840611, 9, 7, 0, 0, 0, 100, 0, 1000, 1000, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Enable Evade'), +(2840611, 9, 8, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Initiate - Actionlist - Despawn Instant'); diff --git a/data/sql/updates/db_world/2026_04_10_01.sql b/data/sql/updates/db_world/2026_04_10_01.sql new file mode 100644 index 000000000..c79490358 --- /dev/null +++ b/data/sql/updates/db_world/2026_04_10_01.sql @@ -0,0 +1,3 @@ +-- DB update 2026_04_10_00 -> 2026_04_10_01 +-- +UPDATE `creature_template_addon` SET `auras` = '70203' WHERE `entry` IN (37126, 38258); diff --git a/data/sql/updates/db_world/2026_04_10_02.sql b/data/sql/updates/db_world/2026_04_10_02.sql new file mode 100644 index 000000000..965df0371 --- /dev/null +++ b/data/sql/updates/db_world/2026_04_10_02.sql @@ -0,0 +1,4 @@ +-- DB update 2026_04_10_01 -> 2026_04_10_02 + +-- Update Conditions (Death Knight Initiate) +UPDATE `conditions` SET `SourceTypeOrReferenceId` = 20, `SourceGroup` = 0, `SourceEntry` = 28406 WHERE (`SourceTypeOrReferenceId` = 15) AND (`SourceGroup` = 9765) AND (`SourceEntry` = 0) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` IN (1, 47)) AND (`ConditionTarget` = 0) AND (`ConditionValue1` IN (12733, 54238)) AND (`ConditionValue2` IN (0, 8)) AND (`ConditionValue3` = 0); diff --git a/src/server/database/Updater/DBUpdater.cpp b/src/server/database/Updater/DBUpdater.cpp index a583a33a3..813600c77 100644 --- a/src/server/database/Updater/DBUpdater.cpp +++ b/src/server/database/Updater/DBUpdater.cpp @@ -99,6 +99,7 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { + // must be lowercase return "auth"; } @@ -137,6 +138,7 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { + // must be lowercase return "world"; } @@ -175,35 +177,10 @@ bool DBUpdater::IsEnabled(uint32 const updateMask) template<> std::string DBUpdater::GetDBModuleName() { + // must be lowercase return "characters"; } -#ifdef MOD_PLAYERBOTS -// Playerbots Database -template<> -std::string DBUpdater::GetConfigEntry() -{ - return "Updates.Playerbots"; -} - -template<> -std::string DBUpdater::GetTableName() -{ - return "Playerbots"; -} - -template<> -std::string DBUpdater::GetSourceDirectory() -{ - return BuiltInConfig::GetSourceDirectory() + "/modules/mod-playerbots"; -} - -template<> -std::string DBUpdater::GetBaseFilesDirectory() -{ - return DBUpdater::GetSourceDirectory() + "/data/sql/playerbots/base/"; -} - template<> bool DBUpdater::IsEnabled(uint32 const updateMask) { diff --git a/src/server/database/Updater/UpdateFetcher.cpp b/src/server/database/Updater/UpdateFetcher.cpp index b3a7eaa86..f21841c0b 100644 --- a/src/server/database/Updater/UpdateFetcher.cpp +++ b/src/server/database/Updater/UpdateFetcher.cpp @@ -160,18 +160,30 @@ UpdateFetcher::DirectoryStorage UpdateFetcher::ReceiveIncludedDirectories() cons moduleList.emplace_back(itr); // data/sql - for (auto const& itr : moduleList) + for (auto const& moduleName : moduleList) { - std::string path = _sourceDirectory->generic_string() + "/modules/" + itr + "/data/sql/" + _dbModuleName; // modules/mod-name/data/sql/db-world - - Path const p(path); + std::string path = _sourceDirectory->generic_string() + "/modules/" + moduleName + "/data/sql/"; // modules/mod-name/data/sql/ + Path const p{path}; if (!is_directory(p)) continue; - DirectoryEntry const entry = { p, AppliedFileEntry::StateConvert("MODULE") }; - directories.push_back(entry); + directory_iterator const end; + for (directory_iterator itr{p}; itr != end; ++itr) + { + if (!is_directory(itr->path())) + continue; - LOG_TRACE("sql.updates", "Added applied modules file \"{}\" from remote.", p.filename().generic_string()); + std::filesystem::path dirPath = itr->path(); // modules/mod-name/data/sql/db-world + std::string dirName = dirPath.filename().string(); // db-world + + if (dirName.find(_dbModuleName) == std::string::npos) + continue; + + DirectoryEntry const entry = { dirPath, AppliedFileEntry::StateConvert("MODULE") }; + directories.push_back(entry); + + LOG_TRACE("sql.updates", "Added applied modules file \"{}\" from remote.", dirPath.filename().generic_string()); + } } } diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index df978f02e..cbb96029f 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -697,6 +697,20 @@ void SmartAI::MovementInform(uint32 MovementType, uint32 Data) MovepointReached(Data); } +void SmartAI::JustExitedCombat() +{ + // When evade is suppressed or disabled, don't auto-evade on combat exit. + // This prevents scripted encounters (e.g. Mograine/Whitemane) from resetting + // when bosses temporarily stop fighting during scripted phases. + if (mSuppressEvade || mEvadeDisabled) + { + EngagementOver(); + return; + } + + CreatureAI::JustExitedCombat(); +} + void SmartAI::EnterEvadeMode(EvadeReason /*why*/) { if (mSuppressEvade) diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h index e36a258b7..64e208b0d 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.h +++ b/src/server/game/AI/SmartScripts/SmartAI.h @@ -85,6 +85,9 @@ public: // Called for reaction at enter to combat if not in combat yet (enemy can be nullptr) void JustEngagedWith(Unit* enemy) override; + // Called when creature exits combat (all combat refs gone) + void JustExitedCombat() override; + // Called for reaction at stopping attack at no attackers or targets void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER) override; diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index 213ee742f..20012de6a 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -974,7 +974,7 @@ bool ConditionMgr::IsObjectMeetToConditions(ConditionSourceInfo& sourceInfo, Con bool ConditionMgr::CanHaveSourceGroupSet(ConditionSourceType sourceType) const { return (sourceType == CONDITION_SOURCE_TYPE_CREATURE_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_DISENCHANT_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_FISHING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_GAMEOBJECT_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_ITEM_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_MAIL_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_MILLING_LOOT_TEMPLATE || - sourceType == CONDITION_SOURCE_TYPE_PICKPOCKETING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_PROSPECTING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_REFERENCE_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_SKINNING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_SPELL_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_GOSSIP_MENU || sourceType == CONDITION_SOURCE_TYPE_GOSSIP_MENU_OPTION || sourceType == CONDITION_SOURCE_TYPE_VEHICLE_SPELL || + sourceType == CONDITION_SOURCE_TYPE_PICKPOCKETING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_PROSPECTING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_REFERENCE_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_SKINNING_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_SPELL_LOOT_TEMPLATE || sourceType == CONDITION_SOURCE_TYPE_GOSSIP_MENU || sourceType == CONDITION_SOURCE_TYPE_GOSSIP_MENU_OPTION || sourceType == CONDITION_SOURCE_TYPE_VEHICLE_SPELL || sourceType == CONDITION_SOURCE_TYPE_GOSSIP_HELLO || sourceType == CONDITION_SOURCE_TYPE_SPELL_IMPLICIT_TARGET || sourceType == CONDITION_SOURCE_TYPE_SPELL_CLICK_EVENT || sourceType == CONDITION_SOURCE_TYPE_SMART_EVENT || sourceType == CONDITION_SOURCE_TYPE_NPC_VENDOR || sourceType == CONDITION_SOURCE_TYPE_PLAYER_LOOT_TEMPLATE); } @@ -1808,9 +1808,15 @@ bool ConditionMgr::isSourceTypeValid(Condition* cond) return false; } break; - case CONDITION_SOURCE_TYPE_UNUSED_20: - LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_UNUSED_20 is not in use. SourceEntry = ({}), skipped", cond->SourceEntry); + case CONDITION_SOURCE_TYPE_GOSSIP_HELLO: + { + if (!sObjectMgr->GetCreatureTemplate(cond->SourceEntry)) + { + LOG_ERROR("sql.sql", "CONDITION_SOURCE_TYPE_GOSSIP_HELLO: creature entry {} in `condition` table does not exist in `creature_template`, ignoring.", cond->SourceEntry); + return false; + } break; + } case CONDITION_SOURCE_TYPE_VEHICLE_SPELL: case CONDITION_SOURCE_TYPE_SPELL_CLICK_EVENT: if (!sObjectMgr->GetCreatureTemplate(cond->SourceGroup)) diff --git a/src/server/game/Conditions/ConditionMgr.h b/src/server/game/Conditions/ConditionMgr.h index f4db6429b..5ff412854 100644 --- a/src/server/game/Conditions/ConditionMgr.h +++ b/src/server/game/Conditions/ConditionMgr.h @@ -142,7 +142,7 @@ enum ConditionSourceType CONDITION_SOURCE_TYPE_SPELL = 17, CONDITION_SOURCE_TYPE_SPELL_CLICK_EVENT = 18, CONDITION_SOURCE_TYPE_QUEST_AVAILABLE = 19, - CONDITION_SOURCE_TYPE_UNUSED_20 = 20, // placeholder + CONDITION_SOURCE_TYPE_GOSSIP_HELLO = 20, CONDITION_SOURCE_TYPE_VEHICLE_SPELL = 21, CONDITION_SOURCE_TYPE_SMART_EVENT = 22, CONDITION_SOURCE_TYPE_NPC_VENDOR = 23, diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 413c3a47b..c9ee16a99 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -2224,7 +2224,7 @@ bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) // Xinef: this should exclude self casts... // Spells that don't have effectMechanics. - if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(UI64LIT(1) << spellInfo->Mechanic)) + if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1ULL << spellInfo->Mechanic)) return true; // The above helper uses the creature_immunities table rather than a @@ -2247,7 +2247,7 @@ bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const { // Xinef: this should exclude self casts... - if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(UI64LIT(1) << spellInfo->Effects[index].Mechanic)) + if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1ULL << spellInfo->Effects[index].Mechanic)) return true; if (GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL && spellInfo->Effects[index].Effect == SPELL_EFFECT_HEAL) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 42693aed0..fbdcf0e1c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -10398,24 +10398,39 @@ void Player::ContinueTaxiFlight() TaxiPathNodeList const& nodeList = sTaxiPathNodesByPath[path]; - float bestDist = SIZE_OF_GRIDS * SIZE_OF_GRIDS; // xinef: large value - float currDist = 0.0f; + // Use triangle inequality to find the segment the player is on. + // When distPrev + distNext < distNodes, the player projects between + // the two nodes of the segment. Resume from the end node (i). + float distPrev; + float distNext = + (nodeList[0]->x - GetPositionX()) * (nodeList[0]->x - GetPositionX()) + + (nodeList[0]->y - GetPositionY()) * (nodeList[0]->y - GetPositionY()) + + (nodeList[0]->z - GetPositionZ()) * (nodeList[0]->z - GetPositionZ()); - // xinef: changed to -1, we dont want to catch last node - for (uint32 i = 0; i < nodeList.size() - 1; ++i) + for (uint32 i = 1; i < nodeList.size(); ++i) { TaxiPathNodeEntry const* node = nodeList[i]; - TaxiPathNodeEntry const* nextNode = nodeList[i + 1]; + TaxiPathNodeEntry const* prevNode = nodeList[i - 1]; - // xinef: skip nodes at another map, get last valid node on current map - if (nextNode->mapid != GetMapId() || node->mapid != GetMapId()) + // skip nodes at another map + if (node->mapid != GetMapId()) continue; - currDist = (node->x - GetPositionX()) * (node->x - GetPositionX()) + (node->y - GetPositionY()) * (node->y - GetPositionY()) + (node->z - GetPositionZ()) * (node->z - GetPositionZ()); - if (currDist < bestDist) + distPrev = distNext; + distNext = + (node->x - GetPositionX()) * (node->x - GetPositionX()) + + (node->y - GetPositionY()) * (node->y - GetPositionY()) + + (node->z - GetPositionZ()) * (node->z - GetPositionZ()); + + float distNodes = + (node->x - prevNode->x) * (node->x - prevNode->x) + + (node->y - prevNode->y) * (node->y - prevNode->y) + + (node->z - prevNode->z) * (node->z - prevNode->z); + + if (distPrev + distNext < distNodes) { startNode = i; - bestDist = currDist; + break; } } diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index f664d98a3..4839307f7 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -1436,7 +1436,7 @@ void Player::UpdatePvPState() if (pvpInfo.IsHostile) // in hostile area { - if (IsInFlight()) // on taxi + if (IsInFlight() || !m_taxi.empty()) // on taxi or taxi pending resume after login return; if (!IsPvP() || pvpInfo.EndTimer != 0) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 5b748a64d..961ace355 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -74,6 +74,7 @@ #include "WorldPacket.h" #include #include +#include float baseMoveSpeed[MAX_MOVE_TYPE] = { @@ -910,6 +911,9 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit if (!spellInfo) return false; + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) + return false; + bool immuneToAllEffects = true; bool hasCheckedEffect = false; @@ -940,8 +944,28 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { - if (IsImmunedToSchool(spellInfo)) - return true; + if (spellInfo->Id == 42292 || spellInfo->Id == 59752 || spellInfo->Id == 19574 || spellInfo->Id == 34471) + return false; + + SpellSchoolMask schoolMask = spellInfo->GetSchoolMask(); + if (schoolMask != SPELL_SCHOOL_MASK_NONE) + { + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto const& [immunitySchoolMask, immunityAuraId] : schoolList) + { + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(immunityAuraId); + if ((immunitySchoolMask & schoolMask) != schoolMask) + continue; + + if (IgnoresSchoolImmunityFromFriendlyCaster(caster, immunityAuraId, immuneSpellInfo)) + continue; + + if (spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + continue; + + return true; + } + } } return false; @@ -1204,7 +1228,8 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage if (!attacker || attacker->IsControlledByPlayer() || attacker->IsCreatedByPlayer()) { uint32 unDamage = health < damage ? health : damage; - bool damagedByPlayer = unDamage && attacker && (attacker->IsPlayer() || attacker->m_movedByPlayer != nullptr); + bool damagedByPlayer = unDamage && attacker && (attacker->IsPlayer() || attacker->m_movedByPlayer != nullptr + || attacker->GetCharmerOrOwnerGUID().IsPlayer()); victim->ToCreature()->LowerPlayerDamageReq(unDamage, damagedByPlayer); } } @@ -2193,7 +2218,7 @@ bool Unit::IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo const* s { if (spellInfo->Effects[effIndex].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE || spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_SCHOOL_DAMAGE) - if (spellInfo->GetEffectMechanicMask(effIndex) & (1 << MECHANIC_BLEED)) + if (spellInfo->GetEffectMechanicMask(effIndex) & (1ULL << MECHANIC_BLEED)) return false; } } @@ -5489,7 +5514,7 @@ void Unit::RemoveAurasWithFamily(SpellFamilyNames family, uint32 familyFlag1, ui void Unit::RemoveMovementImpairingAuras(bool withRoot) { if (withRoot) - RemoveAurasWithMechanic(1 << MECHANIC_ROOT); + RemoveAurasWithMechanic(1ULL << MECHANIC_ROOT); // Snares for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) @@ -5524,7 +5549,7 @@ void Unit::RemoveAurasWithMechanic(uint64 mechanic_mask, AuraRemoveMode removemo void Unit::RemoveAurasByShapeShift() { - uint64 mechanic_mask = (UI64LIT(1) << MECHANIC_SNARE) | (UI64LIT(1) << MECHANIC_ROOT); + uint64 mechanic_mask = (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT); for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { Aura const* aura = iter->second->GetBase(); @@ -6081,12 +6106,12 @@ bool Unit::HasAuraWithMechanic(uint64 mechanicMask) const for (AuraApplicationMap::const_iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end(); ++iter) { SpellInfo const* spellInfo = iter->second->GetBase()->GetSpellInfo(); - if (spellInfo->Mechanic && (mechanicMask & (UI64LIT(1) << spellInfo->Mechanic))) + if (spellInfo->Mechanic && (mechanicMask & (1ULL << spellInfo->Mechanic))) return true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (iter->second->HasEffect(i) && spellInfo->Effects[i].Effect && spellInfo->Effects[i].Mechanic) - if (mechanicMask & (UI64LIT(1) << spellInfo->Effects[i].Mechanic)) + if (mechanicMask & (1ULL << spellInfo->Effects[i].Mechanic)) return true; } @@ -8596,7 +8621,7 @@ float Unit::SpellPctDamageModsDone(Unit* victim, SpellInfo const* spellProto, Da // Torment the weak if (spellProto->SpellFamilyFlags[0] & 0x20600021 || spellProto->SpellFamilyFlags[1] & 0x9000) - if (victim->HasAuraWithMechanic((1 << MECHANIC_SNARE) | (1 << MECHANIC_SLOW_ATTACK))) + if (victim->HasAuraWithMechanic((1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_SLOW_ATTACK))) if (AuraEffect* aurEff = GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_GENERIC, 3263, EFFECT_0)) AddPct(DoneTotalMod, aurEff->GetAmount()); break; @@ -8915,7 +8940,7 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui if (!caster || caster->GetGUID() != (*i)->GetCasterGUID()) continue; - if (mechanicMask & uint64(UI64LIT(1) << (*i)->GetMiscValue())) + if (mechanicMask & (1ULL << (*i)->GetMiscValue())) { if ((*i)->GetAmount() > 0) { @@ -9223,7 +9248,7 @@ float Unit::SpellTakenCritChance(Unit const* caster, SpellInfo const* spellProto case SPELLFAMILY_MAGE: // Glyph of Fire Blast if (spellProto->SpellFamilyFlags[0] == 0x2 && spellProto->SpellIconID == 12) - if (HasAuraWithMechanic((1 << MECHANIC_STUN) | (1 << MECHANIC_KNOCKOUT))) + if (HasAuraWithMechanic((1ULL << MECHANIC_STUN) | (1ULL << MECHANIC_KNOCKOUT))) if (AuraEffect const* aurEff = caster->GetAuraEffect(56369, EFFECT_0)) crit_chance += aurEff->GetAmount(); break; @@ -9815,6 +9840,18 @@ uint32 Unit::GetDamageImmunityMask() const return mask; } +bool Unit::IgnoresSchoolImmunityFromFriendlyCaster(Unit const* caster, uint32 immunityAuraId, SpellInfo const* immunitySpellInfo) const +{ + if (!caster || !caster->IsFriendlyTo(this)) + return false; + + if (immunitySpellInfo) + return !immunitySpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS); + + // Creature template immunities are loaded with a placeholder spell id. + return immunityAuraId == std::numeric_limits::max(); +} + bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const { if (schoolMask == SPELL_SCHOOL_MASK_NONE) @@ -9852,7 +9889,7 @@ bool Unit::IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) con for (auto const& [immunitySchoolMask, immunityAuraId] : container) { SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); - if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this)) + if (IgnoresSchoolImmunityFromFriendlyCaster(caster, immunityAuraId, immuneAuraInfo)) continue; if (immuneAuraInfo && spellInfo->CanPierceImmuneAura(immuneAuraInfo)) @@ -9933,7 +9970,10 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) { - if ((itr->first & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second))) + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second); + if ((itr->first & schoolMask) == schoolMask + && !IgnoresSchoolImmunityFromFriendlyCaster(spell->GetCaster(), itr->second, immuneSpellInfo) + && !spellInfo->CanPierceImmuneAura(immuneSpellInfo)) { return true; } @@ -9971,7 +10011,7 @@ bool Unit::IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spel for (auto const& [immunitySchoolMask, immunityAuraId] : container) { SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); - if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this)) + if (IgnoresSchoolImmunityFromFriendlyCaster(caster, immunityAuraId, immuneAuraInfo)) continue; schoolImmunityMask |= immunitySchoolMask; @@ -10050,6 +10090,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask(); + Unit const* spellCaster = spell ? spell->GetCaster() : nullptr; if (spell) { spellSchoolMask = spell->GetSpellSchoolMask(); @@ -10064,12 +10105,8 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (!(itr->first & spellSchoolMask)) continue; - if (immuneSpellInfo && !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)) - { - Unit const* spellCaster = spell ? spell->GetCaster() : nullptr; - if (spellCaster && spellCaster->IsFriendlyTo(this)) - continue; - } + if (IgnoresSchoolImmunityFromFriendlyCaster(spellCaster, itr->second, immuneSpellInfo)) + continue; if (spellInfo->CanPierceImmuneAura(immuneSpellInfo)) continue; @@ -10369,13 +10406,13 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT // Shred, Maul - "Effects which increase Bleed damage also increase Shred damage" if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[0] & 0x00008800) - mechanicMask |= (UI64LIT(1) << MECHANIC_BLEED); + mechanicMask |= (1ULL << MECHANIC_BLEED); if (mechanicMask) { TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask](AuraEffect const* aurEff) -> bool { - if (mechanicMask & uint64(UI64LIT(1) << (aurEff->GetMiscValue()))) + if (mechanicMask & (1ULL << (aurEff->GetMiscValue()))) return true; return false; }); @@ -11220,8 +11257,8 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) if (Creature* creature = ToCreature()) { // use creature helper which now consults creature_immunities table - if (creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_SNARE) || - creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_DAZE)) + if (creature->HasMechanicTemplateImmunity(1ULL << MECHANIC_SNARE) || + creature->HasMechanicTemplateImmunity(1ULL << MECHANIC_DAZE)) break; } @@ -11242,7 +11279,7 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) if (creature && !IsPet() && !(IsControlledByPlayer() && IsVehicle()) - && !(creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_SNARE)) + && !(creature->HasMechanicTemplateImmunity(1ULL << MECHANIC_SNARE)) && !(creature->IsDungeonBoss())) { // 1.6% for each % under 30. @@ -11498,12 +11535,12 @@ Unit* Creature::SelectVictim() return target; } - // last case when creature must not go to evade mode: - // it in combat but attacker not make any damage and not enter to aggro radius to have record in threat list - // Note: creature does not have targeted movement generator but has attacker in this case - for (AttackerSet::const_iterator itr = m_attackers.begin(); itr != m_attackers.end(); ++itr) - if ((*itr) && CanCreatureAttack(*itr) && !(*itr)->IsPlayer() && !(*itr)->ToCreature()->HasUnitTypeMask(UNIT_MASK_CONTROLLABLE_GUARDIAN)) - return nullptr; + // Don't evade if another unit has us on their threat list — evading would + // end the bidirectional combat reference and remove us from their threat list, + // causing them to lose their target (e.g. an NPC fighting a guardian whose + // CanAIAttack rejects the NPC). + if (!m_threatManager.GetThreatenedByMeList().empty()) + return nullptr; if (GetVehicle()) return nullptr; @@ -11600,7 +11637,7 @@ int32 Unit::ModSpellDuration(SpellInfo const* spellProto, Unit const* target, in // xinef: also calculate self casts, spell can be reflected for example if (!positive) { - int32 mechanic = spellProto->GetSpellMechanicMaskByEffectMask(effectMask); + uint64 mechanic = spellProto->GetSpellMechanicMaskByEffectMask(effectMask); int32 durationMod; int32 durationMod_always = 0; @@ -11608,11 +11645,11 @@ int32 Unit::ModSpellDuration(SpellInfo const* spellProto, Unit const* target, in for (uint8 i = 1; i <= MECHANIC_ENRAGED; ++i) { - if (!(mechanic & 1 << i)) + if (!(mechanic & (1ULL << i))) continue; // Xinef: spells affecting movement imparing effects should not reduce duration if disoriented mechanic is present - if (i == MECHANIC_SNARE && (mechanic & (1 << MECHANIC_DISORIENTED))) + if (i == MECHANIC_SNARE && (mechanic & (1ULL << MECHANIC_DISORIENTED))) continue; // Find total mod value (negative bonus) @@ -15121,7 +15158,7 @@ Aura* Unit::AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target) if (!spellInfo) return nullptr; - if (target->IsImmunedToSpell(spellInfo)) + if (target->IsImmunedToSpell(spellInfo, effMask, this)) return nullptr; for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index f9cedb9dd..1be914c55 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1638,6 +1638,7 @@ public: void ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType = SPELL_BLOCK_TYPE_ALL); virtual bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr); bool IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster = nullptr); + bool IgnoresSchoolImmunityFromFriendlyCaster(Unit const* caster, uint32 immunityAuraId, SpellInfo const* immunitySpellInfo) const; [[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask schoolMask) const; [[nodiscard]] bool IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const; [[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask schoolMask) const; diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index d72cbc66b..8c51fef33 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -1492,7 +1492,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) AllowedLooterSet looters = item->GetAllowedLooters(); Item* _item = player->StoreNewItem(dest, roll->itemid, true, item->randomPropertyId, looters); if (_item) - sScriptMgr->OnPlayerGroupRollRewardItem(player, _item, _item->GetCount(), NEED, roll); + sScriptMgr->OnPlayerGroupRollRewardItem(player, _item, item->count, NEED, roll); player->UpdateLootAchievements(item, roll->getLoot()); } else @@ -1562,7 +1562,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) AllowedLooterSet looters = item->GetAllowedLooters(); Item* _item = player->StoreNewItem(dest, roll->itemid, true, item->randomPropertyId, looters); if (_item) - sScriptMgr->OnPlayerGroupRollRewardItem(player, _item, _item->GetCount(), GREED, roll); + sScriptMgr->OnPlayerGroupRollRewardItem(player, _item, item->count, GREED, roll); player->UpdateLootAchievements(item, roll->getLoot()); } else diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 0683a1450..a7dcfee95 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -1125,6 +1125,13 @@ void WorldSession::HandlePlayerLoginToCharInWorld(Player* pCurrChar) ChatHandler chH = ChatHandler(this); m_playerLoading = true; + // Exit vehicle on reconnect - the client has fully reset so + // the player can no longer control the vehicle. Without this + // the player is stuck: server-side still seated, but the + // client has no vehicle UI or movement control. + if (pCurrChar->GetVehicle()) + pCurrChar->ExitVehicle(); + pCurrChar->SendDungeonDifficulty(false); WorldPacket data(SMSG_LOGIN_VERIFY_WORLD, 20); diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index b355b1bcf..414ec5527 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -16,6 +16,7 @@ */ #include "Battleground.h" +#include "ConditionMgr.h" #include "BattlegroundMgr.h" #include "Creature.h" #include "DatabaseEnv.h" @@ -150,6 +151,11 @@ void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData) if (unit->GetNpcFlags() == UNIT_NPC_FLAG_NONE) return; + // Check GossipHello conditions - block gossip opening if conditions not met + ConditionList gossipConditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_GOSSIP_HELLO, unit->GetEntry()); + if (!sConditionMgr->IsObjectMeetToConditions(_player, unit, gossipConditions)) + return; + // set faction visible if needed if (FactionTemplateEntry const* factionTemplateEntry = sFactionTemplateStore.LookupEntry(unit->GetFaction())) _player->GetReputationMgr().SetVisible(factionTemplateEntry); diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index 9a9804a7d..d65883255 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -441,8 +441,26 @@ bool LootItem::AllowedForPlayer(Player const* player, ObjectGuid source) const return false; // check quest requirements - if (needs_quest && !pProto->HasFlagCu(ITEM_FLAGS_CU_IGNORE_QUEST_STATUS) && !player->HasQuestForItem(itemid)) - return false; + if (!pProto->HasFlagCu(ITEM_FLAGS_CU_IGNORE_QUEST_STATUS)) + { + if (needs_quest && !player->HasQuestForItem(itemid)) + return false; + + // Hide quest starter items when quest is already started/rewarded, + // when unique count is already reached, or when prerequisite is missing. + if (pProto->StartQuest) + { + uint32 prevQuestId = 0; + if (Quest const* startQuest = sObjectMgr->GetQuestTemplate(pProto->StartQuest)) + prevQuestId = startQuest->GetPrevQuestId(); + + if (player->GetQuestStatus(pProto->StartQuest) != QUEST_STATUS_NONE || + player->GetQuestRewardStatus(pProto->StartQuest) || + (pProto->MaxCount && player->HasItemCount(itemid, pProto->MaxCount, true)) || + (prevQuestId && !player->GetQuestRewardStatus(prevQuestId))) + return false; + } + } if (!sScriptMgr->OnAllowedForPlayerLootCheck(player, source)) return false; diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp index 88e210cfc..6f5ee4d81 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp @@ -215,15 +215,15 @@ bool OPvPCapturePoint::DelObject(uint32 type) bool OPvPCapturePoint::DelCapturePoint() { - sObjectMgr->DeleteGOData(m_capturePointSpawnId); - m_capturePointSpawnId = 0; - if (_capturePoint) { _capturePoint->SetRespawnTime(0); // not save respawn time _capturePoint->Delete(); } + sObjectMgr->DeleteGOData(m_capturePointSpawnId); + m_capturePointSpawnId = 0; + return true; } diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 5506a9461..16c3c989c 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -1223,7 +1223,7 @@ bool AuraEffect::CheckEffectProc(AuraApplication* aurApp, ProcEventInfo& eventIn case SPELL_AURA_MECHANIC_IMMUNITY: case SPELL_AURA_MOD_MECHANIC_RESISTANCE: // compare mechanic - if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (UI64LIT(1) << GetMiscValue()))) + if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (1ULL << GetMiscValue()))) return false; break; case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 69a795012..d492451a0 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -6828,7 +6828,7 @@ SpellCastResult Spell::CheckCasterAuras(bool preventionOnly) const return SPELL_CAST_OK; uint8 school_immune = 0; - uint32 mechanic_immune = 0; + uint64 mechanic_immune = 0; uint32 dispel_immune = 0; // Check if the spell grants school or mechanic immunity. @@ -6840,7 +6840,7 @@ SpellCastResult Spell::CheckCasterAuras(bool preventionOnly) const if (m_spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_SCHOOL_IMMUNITY) school_immune |= uint32(m_spellInfo->Effects[i].MiscValue); else if (m_spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MECHANIC_IMMUNITY) - mechanic_immune |= 1 << uint32(m_spellInfo->Effects[i].MiscValue); + mechanic_immune |= 1ULL << uint32(m_spellInfo->Effects[i].MiscValue); else if (m_spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_DISPEL_IMMUNITY) dispel_immune |= SpellInfo::GetDispelMask(DispelType(m_spellInfo->Effects[i].MiscValue)); } @@ -6871,13 +6871,13 @@ SpellCastResult Spell::CheckCasterAuras(bool preventionOnly) const if (usableInStun) { bool foundNotStun = false; - uint32 mask = (1 << MECHANIC_STUN) | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_HORROR); + uint64 mask = (1ULL << MECHANIC_STUN) | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_HORROR); // Barkskin should skip sleep effects, sap and fears if (m_spellInfo->Id == 22812) - mask |= 1 << MECHANIC_SAPPED | 1 << MECHANIC_HORROR | 1 << MECHANIC_SLEEP; + mask |= 1ULL << MECHANIC_SAPPED | 1ULL << MECHANIC_HORROR | 1ULL << MECHANIC_SLEEP; // Hand of Freedom, can be used while sapped and while under fear-mechanic stuns (e.g. Intimidating Shout primary target) if (m_spellInfo->Id == 1044) - mask |= (1 << MECHANIC_SAPPED) | (1 << MECHANIC_FEAR); + mask |= (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_FEAR); Unit::AuraEffectList const& stunAuras = m_caster->GetAuraEffectsByType(SPELL_AURA_MOD_STUN); for (Unit::AuraEffectList::const_iterator i = stunAuras.begin(); i != stunAuras.end(); ++i) { @@ -6936,13 +6936,13 @@ SpellCastResult Spell::CheckCasterAuras(bool preventionOnly) const { case SPELL_AURA_MOD_STUN: { - uint32 mask = 1 << MECHANIC_STUN; + uint64 mask = 1ULL << MECHANIC_STUN; // Barkskin should skip sleep effects, sap and fears if (m_spellInfo->Id == 22812) - mask |= 1 << MECHANIC_SAPPED | 1 << MECHANIC_HORROR | 1 << MECHANIC_SLEEP; + mask |= 1ULL << MECHANIC_SAPPED | 1ULL << MECHANIC_HORROR | 1ULL << MECHANIC_SLEEP; // Hand of Freedom, can be used while sapped and while under fear-mechanic stuns (e.g. Intimidating Shout primary target) if (m_spellInfo->Id == 1044) - mask |= (1 << MECHANIC_SAPPED) | (1 << MECHANIC_FEAR); + mask |= (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_FEAR); if (!usableInStun || !(auraInfo->GetAllEffectsMechanicMask() & mask)) return SPELL_FAILED_STUNNED; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 46e674736..1113d6c90 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -856,7 +856,7 @@ void Spell::EffectTriggerSpell(SpellEffIndex effIndex) SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo(); // Pounce Bleed shouldn't be removed by Cloak of Shadows. - if (spell->GetAllEffectsMechanicMask() & (UI64LIT(1) << MECHANIC_BLEED)) + if (spell->GetAllEffectsMechanicMask() & (1ULL << MECHANIC_BLEED)) return; bool dmgClassNone = false; @@ -2493,8 +2493,8 @@ void Spell::EffectSummonType(SpellEffIndex effIndex) if (properties->Category == SUMMON_CATEGORY_ALLY) { - summon->SetOwnerGUID(m_originalCaster->GetGUID()); - summon->SetFaction(m_originalCaster->GetFaction()); + summon->SetOwnerGUID(summoner->GetGUID()); + summon->SetFaction(summoner->GetFaction()); } ExecuteLogEffectSummonObject(effIndex, summon); @@ -5157,7 +5157,7 @@ void Spell::EffectDispelMechanic(SpellEffIndex effIndex) continue; if (roll_chance_i(aura->CalcDispelChance(unitTarget, !unitTarget->IsFriendlyTo(m_caster)))) { - if ((aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (UI64LIT(1) << mechanic))) + if (aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (1ULL << mechanic)) { dispel_list.push(std::make_pair(aura->GetId(), aura->GetCasterGUID())); diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 1775e9ec1..f2e5d803c 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1900,10 +1900,10 @@ uint64 SpellInfo::GetAllEffectsMechanicMask() const { uint64 mask = 0; if (Mechanic) - mask |= UI64LIT(1) << Mechanic; + mask |= 1ULL << Mechanic; for (int i = 0; i < MAX_SPELL_EFFECTS; ++i) if (Effects[i].IsEffect() && Effects[i].Mechanic) - mask |= UI64LIT(1) << Effects[i].Mechanic; + mask |= 1ULL << Effects[i].Mechanic; return mask; } @@ -1911,9 +1911,9 @@ uint64 SpellInfo::GetEffectMechanicMask(uint8 effIndex) const { uint64 mask = 0; if (Mechanic) - mask |= UI64LIT(1) << Mechanic; + mask |= 1ULL << Mechanic; if (Effects[effIndex].IsEffect() && Effects[effIndex].Mechanic) - mask |= UI64LIT(1) << Effects[effIndex].Mechanic; + mask |= 1ULL << Effects[effIndex].Mechanic; return mask; } @@ -1921,10 +1921,10 @@ uint64 SpellInfo::GetSpellMechanicMaskByEffectMask(uint32 effectMask) const { uint64 mask = 0; if (Mechanic) - mask |= UI64LIT(1) << Mechanic; + mask |= 1ULL << Mechanic; for (int i = 0; i < MAX_SPELL_EFFECTS; ++i) if ((effectMask & (1 << i)) && Effects[i].Mechanic) - mask |= UI64LIT(1) << Effects[i].Mechanic; + mask |= 1ULL << Effects[i].Mechanic; return mask; } @@ -2008,7 +2008,7 @@ AuraStateType SpellInfo::LoadAuraState() const return AURA_STATE_ENRAGE; // Bleeding aura state - if (GetAllEffectsMechanicMask() & (UI64LIT(1) << MECHANIC_BLEED)) + if (GetAllEffectsMechanicMask() & (1ULL << MECHANIC_BLEED)) return AURA_STATE_BLEEDING; // Banished aura state diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 6eb781317..62a9468bd 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -705,7 +705,7 @@ public: handler->PSendSysMessage(LANG_NPCINFO_MECHANIC_IMMUNE, Acore::StringFormat("0x{:X}", mechanicImmuneMask).c_str()); for (uint8 i = 1; i < MAX_MECHANIC; ++i) - if (mechanicImmuneMask & (UI64LIT(1) << i)) + if (mechanicImmuneMask & (1ULL << i)) handler->PSendSysMessage(mechanicImmunes[i].text, mechanicImmunes[i].flag); handler->PSendSysMessage(LANG_NPCINFO_SPELL_SCHOOL_IMMUNE, spellSchoolImmuneMask); diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp index 45ec1dccc..6538947e3 100644 --- a/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp +++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/MoltenCore/boss_ragnaros.cpp @@ -80,6 +80,7 @@ enum Events EVENT_MIGHT_OF_RAGNAROS, EVENT_LAVA_BURST, EVENT_MAGMA_BLAST, + EVENT_MELEE_SCAN, EVENT_SUBMERGE, EVENT_LAVA_BURST_TRIGGER, @@ -218,29 +219,6 @@ struct boss_ragnaros : public BossAI DoStartNoMovement(target); } - void EnterEvadeMode(EvadeReason why) override - { - if (!me->GetThreatMgr().IsThreatListEmpty()) - { - if (!_processingMagmaBurst) - { - // Boss try to evade, but still got some targets on threat list - it means that none of these targets are in melee range - cast magma blast - _processingMagmaBurst = true; - events.ScheduleEvent(EVENT_MAGMA_BLAST, 4s, PHASE_EMERGED, PHASE_EMERGED); - } - } - else - { - BossAI::EnterEvadeMode(why); - } - } - - bool CanAIAttack(Unit const* victim) const override - { - // Used for Magma Blast handling to force EnterEvadeMode if there are no melee targets - return me->IsWithinMeleeRange(victim); - } - void UpdateAI(uint32 diff) override { if (!extraEvents.Empty()) @@ -307,10 +285,7 @@ struct boss_ragnaros : public BossAI } if (!UpdateVictim()) - { - if (!_processingMagmaBurst) - return; - } + return; events.Update(diff); @@ -348,19 +323,47 @@ struct boss_ragnaros : public BossAI DoCastAOE(SPELL_LAVA_BURST); break; } + case EVENT_MELEE_SCAN: + { + // ReselectVictim() in the threat system already prefers + // in-melee-range targets, so if the current victim is out + // of melee range it means *nobody* is in range. Use that + // as the trigger for the Magma Blast fallback. + if (!IsVictimWithinMeleeRange()) + { + if (!_processingMagmaBurst) + { + _processingMagmaBurst = true; + events.ScheduleEvent(EVENT_MAGMA_BLAST, 4s, PHASE_EMERGED, PHASE_EMERGED); + } + } + else if (_processingMagmaBurst) + { + events.CancelEvent(EVENT_MAGMA_BLAST); + _processingMagmaBurst = false; + } + events.Repeat(500ms); + break; + } case EVENT_MAGMA_BLAST: { _processingMagmaBurst = false; - if (!IsVictimWithinMeleeRange()) + if (IsVictimWithinMeleeRange()) + break; + + if (Unit* victim = me->GetVictim()) { - DoCastRandomTarget(SPELL_MAGMA_BLAST); + DoCast(victim, SPELL_MAGMA_BLAST); if (!_hasYelledMagmaBurst) { Talk(SAY_MAGMABURST); _hasYelledMagmaBurst = true; } + + _processingMagmaBurst = true; + events.ScheduleEvent(EVENT_MAGMA_BLAST, 4s, PHASE_EMERGED, PHASE_EMERGED); } break; @@ -454,12 +457,14 @@ private: events.RescheduleEvent(EVENT_LAVA_BURST, 10s, PHASE_EMERGED, PHASE_EMERGED); events.RescheduleEvent(EVENT_SUBMERGE, 180s, PHASE_EMERGED, PHASE_EMERGED); events.RescheduleEvent(EVENT_MIGHT_OF_RAGNAROS, 11s, PHASE_EMERGED, PHASE_EMERGED); + events.RescheduleEvent(EVENT_MELEE_SCAN, 500ms, PHASE_EMERGED, PHASE_EMERGED); } bool IsVictimWithinMeleeRange() const { return me->GetVictim() && me->IsWithinMeleeRange(me->GetVictim()); } + }; constexpr std::array RagnarosLavaBurstSpells = { SPELL_LAVA_BURST_A, SPELL_LAVA_BURST_B, SPELL_LAVA_BURST_C, SPELL_LAVA_BURST_D, SPELL_LAVA_BURST_E, SPELL_LAVA_BURST_F, SPELL_LAVA_BURST_G, SPELL_LAVA_BURST_H }; diff --git a/src/server/scripts/Events/childrens_week.cpp b/src/server/scripts/Events/childrens_week.cpp index 1a923c7b8..841e6d3bd 100644 --- a/src/server/scripts/Events/childrens_week.cpp +++ b/src/server/scripts/Events/childrens_week.cpp @@ -19,6 +19,7 @@ #include "CreatureScript.h" #include "Player.h" #include "ScriptedCreature.h" +#include "ScriptedGossip.h" #include "SpellAuras.h" enum Orphans @@ -628,9 +629,33 @@ private: /*###### ## npc_cw_alexstrasza_trigger ######*/ +enum eAlexstrasza +{ + SPELL_KEY_TO_FOCUSING_IRIS = 60989, + SPELL_HEROIC_KEY_TO_FOCUSING_IRIS = 60992, +}; + struct npc_alexstraza_the_lifebinder : public ScriptedAI { - npc_alexstraza_the_lifebinder(Creature* creature) : ScriptedAI(creature) {} + explicit npc_alexstraza_the_lifebinder(Creature* creature) : ScriptedAI(creature) {} + + void sGossipSelect(Player* player, uint32 /*sender*/, uint32 action) override + { + ClearGossipMenuFor(player); + switch (action) + { + case 0: + CloseGossipMenuFor(player); + player->CastSpell(player, SPELL_KEY_TO_FOCUSING_IRIS, false); + break; + case 1: + CloseGossipMenuFor(player); + player->CastSpell(player, SPELL_HEROIC_KEY_TO_FOCUSING_IRIS, false); + break; + default: + break; + } + } void Reset() override { @@ -653,6 +678,8 @@ struct npc_alexstraza_the_lifebinder : public ScriptedAI case 2: me->SetOrientation(me->GetHomePosition().GetOrientation()); break; + default: + break; } } } @@ -767,8 +794,8 @@ struct npc_alexstraza_the_lifebinder : public ScriptedAI } private: - int8 phase; - uint32 timer; + int8 phase{}; + uint32 timer{}; ObjectGuid playerGUID; ObjectGuid orphanGUID; }; diff --git a/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp b/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp index d85f19f79..3b09d5cd8 100644 --- a/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp +++ b/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp @@ -136,7 +136,7 @@ enum Misc // Movement points POINT_LANDING = 1, - // Lava directions. Its used to identify to which side lava was moving by last time + // Lava directions LAVA_LEFT_SIDE = 0, LAVA_RIGHT_SIDE = 1, @@ -307,7 +307,6 @@ struct boss_sartharion : public BossAI { explicit boss_sartharion(Creature* creature) : BossAI(creature, DATA_SARTHARION), dragonsCount(0), - lastLavaSide(LAVA_RIGHT_SIDE), usedBerserk(false), below11PctReached(false) { @@ -628,8 +627,8 @@ private: extraEvents.ScheduleEvent(EVENT_SARTHARION_START_LAVA, 3600ms); extraEvents.ScheduleEvent(EVENT_SARTHARION_FINISH_LAVA, 11s); - // Send wave from left - if (lastLavaSide == LAVA_RIGHT_SIDE) + // Randomly choose which side the wave comes from + if (urand(LAVA_LEFT_SIDE, LAVA_RIGHT_SIDE) == LAVA_LEFT_SIDE) { for (uint8 i = 0; i < MAX_LEFT_LAVA_TSUNAMIS; ++i) { @@ -638,10 +637,7 @@ private: if (((i - 1) % 3 == 0) && tsunami) // If center of wave tsunami->CastSpell(tsunami, SPELL_FLAME_TSUNAMI_VISUAL, true); } - - lastLavaSide = LAVA_LEFT_SIDE; } - // from right else { for (uint8 i = 0; i < MAX_RIGHT_LAVA_TSUNAMIS; ++i) @@ -651,8 +647,6 @@ private: if (((i - 1) % 3 == 0) && tsunami) // If center of wave tsunami->CastSpell(tsunami, SPELL_FLAME_TSUNAMI_VISUAL, true); } - - lastLavaSide = LAVA_RIGHT_SIDE; } } @@ -683,7 +677,6 @@ private: EventMap extraEvents; std::list volcanoBlows; uint8 dragonsCount; - uint8 lastLavaSide; // 0 = left, 1 = right bool usedBerserk; bool below11PctReached; }; diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp index fcad4c62c..e7c71732e 100644 --- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp +++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp @@ -224,7 +224,7 @@ class spell_uk_second_wind : public AuraScript if (!spellInfo) return false; - return (spellInfo->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_STUN))) != 0; + return (spellInfo->GetAllEffectsMechanicMask() & ((1ULL << MECHANIC_ROOT) | (1ULL << MECHANIC_STUN))) != 0; } void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp index 286bb0c97..265572584 100644 --- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp +++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_lady_vashj.cpp @@ -128,7 +128,7 @@ struct boss_lady_vashj : public BossAI summon->CastSpell(summon, SPELL_MAGIC_BARRIER); break; case NPC_ENCHANTED_ELEMENTAL: - summon->GetMotionMaster()->MoveFollow(me, 0.0f, 0.0f, MOTION_SLOT_ACTIVE, false); + summon->GetMotionMaster()->MoveFollow(me, 0.0f, 0.0f, MOTION_SLOT_ACTIVE, false, false); summon->SetWalk(true); summon->SetReactState(REACT_PASSIVE); break; diff --git a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp index 83c7abefc..e5046d6a6 100644 --- a/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp +++ b/src/server/scripts/Outland/HellfireCitadel/MagtheridonsLair/boss_magtheridon.cpp @@ -242,10 +242,11 @@ struct boss_magtheridon : public BossAI void UpdateAI(uint32 diff) override { + scheduler.Update(diff); + if (!UpdateVictim()) return; - scheduler.Update(diff); _interruptScheduler.Update(diff); if (_currentPhase != 1 && !_castingQuake) diff --git a/src/server/scripts/Outland/TempestKeep/arcatraz/arcatraz.cpp b/src/server/scripts/Outland/TempestKeep/arcatraz/arcatraz.cpp index d4c067fa4..5ba771359 100644 --- a/src/server/scripts/Outland/TempestKeep/arcatraz/arcatraz.cpp +++ b/src/server/scripts/Outland/TempestKeep/arcatraz/arcatraz.cpp @@ -385,11 +385,11 @@ public: void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType, SpellSchoolMask) override { - if (attacker && attacker->GetCharmerOrOwnerOrOwnGUID().IsPlayer() && damage > 0 && !me->isActiveObject()) + if (attacker && attacker->GetCharmerOrOwnerOrOwnGUID().IsPlayer() && damage > 0 && instance->GetBossState(DATA_WARDEN_MELLICHAR) != IN_PROGRESS) { me->setActive(true); me->InterruptNonMeleeSpells(false); - me->SetImmuneToAll(true); + me->SetImmuneToAll(true, true); events.ScheduleEvent(EVENT_WARDEN_INTRO1, 1500ms); events.ScheduleEvent(EVENT_WARDEN_CHECK_PLAYERS, 1s); instance->SetBossState(DATA_WARDEN_MELLICHAR, IN_PROGRESS); diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp index 442139783..228a35642 100644 --- a/src/server/scripts/Spells/spell_dk.cpp +++ b/src/server/scripts/Spells/spell_dk.cpp @@ -3001,7 +3001,7 @@ class spell_dk_pvp_4p_bonus : public AuraScript if (!spellInfo) return false; - return (spellInfo->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_SNARE))) != 0; + return (spellInfo->GetAllEffectsMechanicMask() & ((1ULL << MECHANIC_ROOT) | (1ULL << MECHANIC_SNARE))) != 0; } void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo) diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp index 0670da786..77e4ac038 100644 --- a/src/server/scripts/Spells/spell_druid.cpp +++ b/src/server/scripts/Spells/spell_druid.cpp @@ -770,7 +770,7 @@ class spell_dru_moonkin_form_passive : public AuraScript void Absorb(AuraEffect* /*aurEff*/, DamageInfo& dmgInfo, uint32& absorbAmount) { // reduces all damage taken while Stunned in Moonkin Form - if (GetTarget()->GetUnitFlags() & (UNIT_FLAG_STUNNED) && GetTarget()->HasAuraWithMechanic(1 << MECHANIC_STUN)) + if (GetTarget()->GetUnitFlags() & (UNIT_FLAG_STUNNED) && GetTarget()->HasAuraWithMechanic(1ULL << MECHANIC_STUN)) absorbAmount = CalculatePct(dmgInfo.GetDamage(), absorbPct); } @@ -837,7 +837,7 @@ class spell_dru_primal_tenacity : public AuraScript void Absorb(AuraEffect* /*aurEff*/, DamageInfo& dmgInfo, uint32& absorbAmount) { // reduces all damage taken while Stunned in Cat Form - if (GetTarget()->GetShapeshiftForm() == FORM_CAT && GetTarget()->HasUnitFlag(UNIT_FLAG_STUNNED) && GetTarget()->HasAuraWithMechanic(1 << MECHANIC_STUN)) + if (GetTarget()->GetShapeshiftForm() == FORM_CAT && GetTarget()->HasUnitFlag(UNIT_FLAG_STUNNED) && GetTarget()->HasAuraWithMechanic(1ULL << MECHANIC_STUN)) absorbAmount = CalculatePct(dmgInfo.GetDamage(), absorbPct); } diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index dc9340479..cda844dbb 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -4372,7 +4372,7 @@ class spell_item_luffa : public SpellScript for (Unit::AuraApplicationMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) { Aura const* aura = itr->second->GetBase(); - if (!(aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (1 << MECHANIC_BLEED)) || aura->GetCasterLevel() > 60 || aura->GetSpellInfo()->IsPositive()) + if (!(aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (1ULL << MECHANIC_BLEED)) || aura->GetCasterLevel() > 60 || aura->GetSpellInfo()->IsPositive()) continue; return SPELL_CAST_OK; @@ -4392,7 +4392,7 @@ class spell_item_luffa : public SpellScript for (Unit::AuraApplicationMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) { Aura const* aura = itr->second->GetBase(); - if (!(aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (1 << MECHANIC_BLEED)) || aura->GetCasterLevel() > 60 || aura->GetSpellInfo()->IsPositive()) + if (!(aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (1ULL << MECHANIC_BLEED)) || aura->GetCasterLevel() > 60 || aura->GetSpellInfo()->IsPositive()) continue; player->RemoveAurasDueToSpell(aura->GetId(), aura->GetCasterGUID()); diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index f6d5f240a..be2d979e5 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -120,7 +120,7 @@ class spell_mage_burning_determination : public AuraScript return false; // Need Interrupt or Silenced mechanic - if (!(eventInfo.GetSpellInfo()->GetAllEffectsMechanicMask() & ((1 << MECHANIC_INTERRUPT) | (1 << MECHANIC_SILENCE)))) + if (!(eventInfo.GetSpellInfo()->GetAllEffectsMechanicMask() & ((1ULL << MECHANIC_INTERRUPT) | (1ULL << MECHANIC_SILENCE)))) return false; // Xinef: immuned effect should just eat charge diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp index 963d1d266..5b33c2fc9 100644 --- a/src/server/scripts/Spells/spell_paladin.cpp +++ b/src/server/scripts/Spells/spell_paladin.cpp @@ -1486,7 +1486,7 @@ class spell_pal_divine_purpose : public AuraScript { PreventDefaultAction(); if (Unit* target = eventInfo.GetActionTarget()) - target->RemoveAurasWithMechanic(1 << MECHANIC_STUN, AURA_REMOVE_BY_ENEMY_SPELL); + target->RemoveAurasWithMechanic(1ULL << MECHANIC_STUN, AURA_REMOVE_BY_ENEMY_SPELL); } void Register() override diff --git a/src/server/scripts/Spells/spell_priest.cpp b/src/server/scripts/Spells/spell_priest.cpp index 4ab3a1611..93b6f807f 100644 --- a/src/server/scripts/Spells/spell_priest.cpp +++ b/src/server/scripts/Spells/spell_priest.cpp @@ -473,7 +473,7 @@ class spell_pri_mana_burn : public SpellScript void HandleAfterHit() { if (Unit* unitTarget = GetHitUnit()) - unitTarget->RemoveAurasWithMechanic((1 << MECHANIC_FEAR) | (1 << MECHANIC_POLYMORPH)); + unitTarget->RemoveAurasWithMechanic((1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_POLYMORPH)); } void Register() override diff --git a/src/server/scripts/Spells/spell_rogue.cpp b/src/server/scripts/Spells/spell_rogue.cpp index 1d01f3028..302727ca6 100644 --- a/src/server/scripts/Spells/spell_rogue.cpp +++ b/src/server/scripts/Spells/spell_rogue.cpp @@ -735,13 +735,13 @@ class spell_rog_vanish_purge : public SpellScript void HandleRootRemove(SpellEffIndex /*effIndex*/) { if (GetCaster() && !GetCaster()->HasAura(SPELL_PARALYZE)) // Root from Tainted Core SSC, should not be removed by vanish. - GetCaster()->RemoveAurasWithMechanic(1 << MECHANIC_ROOT); + GetCaster()->RemoveAurasWithMechanic(1ULL << MECHANIC_ROOT); } void HandleSnareRemove(SpellEffIndex /*effIndex*/) { if (GetCaster()) - GetCaster()->RemoveAurasWithMechanic(1 << MECHANIC_SNARE); + GetCaster()->RemoveAurasWithMechanic(1ULL << MECHANIC_SNARE); } void Register() override diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp index 1a9c545a5..eef753444 100644 --- a/src/server/scripts/Spells/spell_shaman.cpp +++ b/src/server/scripts/Spells/spell_shaman.cpp @@ -1494,7 +1494,7 @@ class spell_sha_astral_shift_aura : public AuraScript bool CheckProc(ProcEventInfo& eventInfo) { if (SpellInfo const* spellInfo = eventInfo.GetSpellInfo()) - if (spellInfo->GetAllEffectsMechanicMask() & ((1 << MECHANIC_SILENCE) | (1 << MECHANIC_STUN) | (1 << MECHANIC_FEAR))) + if (spellInfo->GetAllEffectsMechanicMask() & ((1ULL << MECHANIC_SILENCE) | (1ULL << MECHANIC_STUN) | (1ULL << MECHANIC_FEAR))) return true; return false; diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp index c3410d0da..49d698a26 100644 --- a/src/server/scripts/Spells/spell_warlock.cpp +++ b/src/server/scripts/Spells/spell_warlock.cpp @@ -927,7 +927,7 @@ class spell_warl_demonic_circle_teleport : public AuraScript if (GameObject* circle = player->GetGameObject(SPELL_WARLOCK_DEMONIC_CIRCLE_SUMMON)) { player->NearTeleportTo(circle->GetPositionX(), circle->GetPositionY(), circle->GetPositionZ(), circle->GetOrientation(), false, false, false, true); - player->RemoveAurasWithMechanic(1 << MECHANIC_SNARE); + player->RemoveAurasWithMechanic(1ULL << MECHANIC_SNARE); } } } diff --git a/src/server/scripts/Spells/spell_warrior.cpp b/src/server/scripts/Spells/spell_warrior.cpp index b6a4f53e1..5462102d4 100644 --- a/src/server/scripts/Spells/spell_warrior.cpp +++ b/src/server/scripts/Spells/spell_warrior.cpp @@ -612,7 +612,7 @@ class spell_warr_shattering_throw : public SpellScript // remove shields, will still display immune to damage part if (Unit* target = GetHitUnit()) - target->RemoveAurasWithMechanic(1 << MECHANIC_IMMUNE_SHIELD, AURA_REMOVE_BY_ENEMY_SPELL); + target->RemoveAurasWithMechanic(1ULL << MECHANIC_IMMUNE_SHIELD, AURA_REMOVE_BY_ENEMY_SPELL); } void Register() override @@ -1013,7 +1013,7 @@ class spell_warr_second_wind : public AuraScript return false; // Must be from stun or root mechanic - if (!(procSpell->GetAllEffectsMechanicMask() & ((1 << MECHANIC_ROOT) | (1 << MECHANIC_STUN)))) + if (!(procSpell->GetAllEffectsMechanicMask() & ((1ULL << MECHANIC_ROOT) | (1ULL << MECHANIC_STUN)))) return false; // Not from self diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index ed7a667e7..8b6dacc8b 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -1352,22 +1352,22 @@ enum Mechanics : uint32 // Used for spell 42292 Immune Movement Impairment and Loss of Control (0x49967ca6) inline constexpr uint64 IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK = - (UI64LIT(1) << MECHANIC_CHARM) | - (UI64LIT(1) << MECHANIC_DISORIENTED) | - (UI64LIT(1) << MECHANIC_FEAR) | - (UI64LIT(1) << MECHANIC_ROOT) | - (UI64LIT(1) << MECHANIC_SLEEP) | - (UI64LIT(1) << MECHANIC_SNARE) | - (UI64LIT(1) << MECHANIC_STUN) | - (UI64LIT(1) << MECHANIC_FREEZE) | - (UI64LIT(1) << MECHANIC_KNOCKOUT) | - (UI64LIT(1) << MECHANIC_POLYMORPH) | - (UI64LIT(1) << MECHANIC_BANISH) | - (UI64LIT(1) << MECHANIC_SHACKLE) | - (UI64LIT(1) << MECHANIC_TURN) | - (UI64LIT(1) << MECHANIC_HORROR) | - (UI64LIT(1) << MECHANIC_DAZE) | - (UI64LIT(1) << MECHANIC_SAPPED); + (1ULL << MECHANIC_CHARM) | + (1ULL << MECHANIC_DISORIENTED) | + (1ULL << MECHANIC_FEAR) | + (1ULL << MECHANIC_ROOT) | + (1ULL << MECHANIC_SLEEP) | + (1ULL << MECHANIC_SNARE) | + (1ULL << MECHANIC_STUN) | + (1ULL << MECHANIC_FREEZE) | + (1ULL << MECHANIC_KNOCKOUT) | + (1ULL << MECHANIC_POLYMORPH) | + (1ULL << MECHANIC_BANISH) | + (1ULL << MECHANIC_SHACKLE) | + (1ULL << MECHANIC_TURN) | + (1ULL << MECHANIC_HORROR) | + (1ULL << MECHANIC_DAZE) | + (1ULL << MECHANIC_SAPPED); // Spell dispel type enum DispelType : uint8 diff --git a/src/test/server/game/Spells/SpellImmunityTest.cpp b/src/test/server/game/Spells/SpellImmunityTest.cpp index a88e8c048..47ffc4fee 100644 --- a/src/test/server/game/Spells/SpellImmunityTest.cpp +++ b/src/test/server/game/Spells/SpellImmunityTest.cpp @@ -22,6 +22,7 @@ #include "gtest/gtest.h" +#include #include #include #include "Unit.h" // needed for SpellSchoolMask and mask helper @@ -79,30 +80,24 @@ namespace bool HasOnlyDamageEffects(SpellDesc const& spell) { - bool hasAny = false; - - for (EffectDesc const& e : spell.effects) + bool hasAny = std::ranges::any_of(spell.effects, [](EffectDesc const& effect) { - if (e.effect == EFFECT_NONE) - continue; + return effect.effect != EFFECT_NONE; + }); - hasAny = true; - if (!IsDamageEffect(e.effect)) - return false; - } - - return hasAny; + return hasAny && std::ranges::all_of(spell.effects, [](EffectDesc const& effect) + { + return effect.effect == EFFECT_NONE || IsDamageEffect(effect.effect); + }); } // Helper to classify spells which apply a stun aura bool IsStunSpell(SpellDesc const& spell) { - for (EffectDesc const& e : spell.effects) + return std::ranges::any_of(spell.effects, [](EffectDesc const& effect) { - if (e.effect == EFFECT_APPLY_AURA && e.aura == AURA_MOD_STUN) - return true; - } - return false; + return effect.effect == EFFECT_APPLY_AURA && effect.aura == AURA_MOD_STUN; + }); } bool IsEffectBlockedByStunImmunity(EffectDesc const& effect, bool immuneToStun) @@ -130,6 +125,11 @@ namespace return hasAnyEffect; } + bool IsBlockedBySchoolImmunity(bool casterFriendly, bool immunityAppliesToFriendly) + { + return !casterFriendly || immunityAppliesToFriendly; + } + // The last parameter defaults to false to avoid updating existing tests // that don't care about mechanic immunities. Bladestorm grants a // specific mechanic immunity (including stun) that should block @@ -364,3 +364,18 @@ TEST(SpellImmunityTest, StunImmunity_DoesNotFullyBlockMixedSpell) EXPECT_TRUE(IsStunSpell(mixedSpell)); EXPECT_EQ(ComputeSpellHitResult(false, false, mixedSpell, true), SPELL_MISS_NONE); } + +TEST(SpellImmunityTest, SchoolImmunity_TemplateStyle_AllowsFriendlySpell) +{ + EXPECT_FALSE(IsBlockedBySchoolImmunity(true, false)); +} + +TEST(SpellImmunityTest, SchoolImmunity_ExplicitFriendlyBlockStillApplies) +{ + EXPECT_TRUE(IsBlockedBySchoolImmunity(true, true)); +} + +TEST(SpellImmunityTest, SchoolImmunity_BlocksHostileSpell) +{ + EXPECT_TRUE(IsBlockedBySchoolImmunity(false, false)); +}